Initial commit
This commit is contained in:
commit
f94c1d1cf1
|
@ -0,0 +1,3 @@
|
|||
*.pyc
|
||||
__pycache__
|
||||
*.swp
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"host": {
|
||||
"name": "dev-pi",
|
||||
"interval": 10
|
||||
},
|
||||
"mqtt": {
|
||||
"host": "127.0.0.1",
|
||||
"user": "sensornode",
|
||||
"password": "password"
|
||||
},
|
||||
"sensors": [
|
||||
{"id": 1, "name": "Pi Humidity 1", "type": "pi-dht22", "description": "nope"}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import sys
|
||||
import time
|
||||
|
||||
from . import sensorlib
|
||||
from .mqttlib import MQTTClient
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-c", "--config", default="/etc/mqtt-sensord.conf")
|
||||
args = parser.parse_args()
|
||||
|
||||
config = sensorlib.load_config(args.config)
|
||||
sensorlib.Sensor.configure(config['host']['name'], sys.platform)
|
||||
|
||||
mcfg = config['mqtt']
|
||||
if 'client_id' in config['mqtt']:
|
||||
client_id = config['mqtt']['client_id']
|
||||
else:
|
||||
client_id = config['host']['name']
|
||||
|
||||
mqtt = MQTTClient(mcfg['host'], mcfg['user'], mcfg['password'], client_id)
|
||||
mqtt_topic = mcfg['sensor_topic']
|
||||
print("Connected to", mqtt.host, "with topic", mqtt_topic)
|
||||
|
||||
sensors = []
|
||||
for scfg in config['sensors']:
|
||||
s = sensorlib.Sensor.make_sensor(**scfg)
|
||||
sensors.append(s)
|
||||
|
||||
last_measurement = time.time()
|
||||
while True:
|
||||
print("Getting values from sensors...")
|
||||
for sensor in sensors:
|
||||
data = sensor.gen_datapoint()
|
||||
print(mqtt_topic, data)
|
||||
mqtt.send_data(mqtt_topic, data)
|
||||
time.sleep(max(0.0, time.time() + config['host']['interval'] - last_measurement))
|
||||
last_measurement = time.time()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,8 @@
|
|||
import sys
|
||||
|
||||
if sys.implementation == 'micropython':
|
||||
from esp import MQTTClient
|
||||
else:
|
||||
from .paho_mqtt import MQTTClient
|
||||
|
||||
__all__ = [MQTTClient]
|
|
@ -0,0 +1,14 @@
|
|||
class MQTTClientBase:
|
||||
def __init__(self, host, user, password, client_id):
|
||||
self.host = host
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.client_id = client_id
|
||||
|
||||
self._connect()
|
||||
|
||||
def send_data(self, topic, data):
|
||||
raise NotImplementedError("Send not implemented")
|
||||
|
||||
def _connect(self):
|
||||
raise NotImplementedError("Connect not implemented")
|
|
@ -0,0 +1,15 @@
|
|||
import json
|
||||
|
||||
import paho.mqtt.client as mqtt
|
||||
|
||||
from .base import MQTTClientBase
|
||||
|
||||
|
||||
class MQTTClient(MQTTClientBase):
|
||||
def _connect(self):
|
||||
self._mqtt = mqtt.Client(self.client_id)
|
||||
self._mqtt.username_pw_set(self.user, self.password)
|
||||
self._mqtt.connect(self.host)
|
||||
|
||||
def send_data(self, topic, data):
|
||||
self._mqtt.publish(topic, json.dumps(data))
|
|
@ -0,0 +1,11 @@
|
|||
import sys
|
||||
|
||||
from .base import Sensor
|
||||
from .config import load_config
|
||||
|
||||
if sys.implementation == 'micropython':
|
||||
from .esp_sensors import *
|
||||
elif getattr(sys.implementation, '_multiarch') == 'arm-linux-gnueabihf':
|
||||
from .pi_sensors import *
|
||||
from .misc_sensors import *
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
def sensor(cls):
|
||||
Sensor.register(cls)
|
||||
|
||||
return cls
|
||||
|
||||
|
||||
class Sensor(object):
|
||||
hostname = None
|
||||
platform = None
|
||||
_sensor_classes = {}
|
||||
|
||||
def __init__(self, id, name, type, pin, description, **sensor_conf):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.pin = pin
|
||||
self.description = description
|
||||
|
||||
@classmethod
|
||||
def configure(cls, hostname, platform):
|
||||
cls.hostname = hostname
|
||||
cls.platform = platform
|
||||
|
||||
@classmethod
|
||||
def register(cls, new_cls):
|
||||
cls._sensor_classes[new_cls.sensor_class] = new_cls
|
||||
|
||||
@classmethod
|
||||
def make_sensor(cls, **kwargs):
|
||||
sensor_cls = cls._sensor_classes[kwargs['type']]
|
||||
return sensor_cls(**kwargs)
|
||||
|
||||
def get_data(self):
|
||||
raise NotImplementedError("You missed a spot!")
|
||||
|
||||
def gen_datapoint(self):
|
||||
return {
|
||||
'type': 'measurement',
|
||||
'tags': {
|
||||
'name': self.name,
|
||||
'sub-id': self.id,
|
||||
'sensor': self.type,
|
||||
'hostname': self.hostname,
|
||||
'platform': self.platform,
|
||||
},
|
||||
'fields': self.get_data()
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import sys
|
||||
|
||||
if sys.implementation == 'micropython':
|
||||
import ujson as json
|
||||
else:
|
||||
import json
|
||||
|
||||
|
||||
def load_config(path):
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
|
@ -0,0 +1,43 @@
|
|||
import dht
|
||||
import ds18x20
|
||||
import machine
|
||||
import onewire
|
||||
import time
|
||||
|
||||
from .base import Sensor
|
||||
|
||||
|
||||
class DS18B20(Sensor):
|
||||
sensor_class = 'esp-ds18b20'
|
||||
|
||||
def __init__(self, **sensor_conf):
|
||||
super(DS18B20, self).__init__(**sensor_conf)
|
||||
|
||||
self.o = onewire.OneWire(machine.Pin(self.pin))
|
||||
self.ds = ds18x20.DS18X20(self.o)
|
||||
self.sID = self.ds.scan()[0]
|
||||
|
||||
def get_data(self):
|
||||
self.ds.convert_temp()
|
||||
time.sleep(0.1)
|
||||
return {"temp": self.ds.read_temp(self.sID)}
|
||||
Sensor.register(DS18B20)
|
||||
|
||||
|
||||
class DHT(Sensor):
|
||||
sensor_class = 'esp-dht'
|
||||
|
||||
def __init__(self, **sensor_conf):
|
||||
super(DHT, self).__init__(**sensor_conf)
|
||||
if sensor_conf["type"] == "dht11":
|
||||
self.dht = dht.DHT11(machine.Pin(self.pin))
|
||||
elif sensor_conf["type"] == "dht22":
|
||||
self.dht = dht.DHT22(machine.Pin(self.pin))
|
||||
else:
|
||||
raise ValueError("Unknown type")
|
||||
|
||||
def get_data(self):
|
||||
self.dht.measure()
|
||||
time.sleep(0.1)
|
||||
return {"temp": self.dht.temperature(), "humidity": self.dht.humidity()}
|
||||
Sensor.register(DHT)
|
|
@ -0,0 +1,140 @@
|
|||
import fcntl
|
||||
import random
|
||||
import threading
|
||||
import time
|
||||
|
||||
from .base import Sensor, sensor
|
||||
|
||||
|
||||
@sensor
|
||||
class FakeSensor(Sensor):
|
||||
sensor_class = 'fake'
|
||||
|
||||
def __init__(self, **sensor_conf):
|
||||
super(FakeSensor, self).__init__(**sensor_conf)
|
||||
|
||||
def get_data(self):
|
||||
return {'random': random.randint(0, 20)}
|
||||
|
||||
|
||||
@sensor
|
||||
class CO2Meter(Sensor):
|
||||
sensor_class = 'co2meter'
|
||||
HIDIOCSFEATURE_9 = 0xC0094806
|
||||
TEMP = 0x42
|
||||
CO2 = 0x50
|
||||
HUM = 0x44
|
||||
|
||||
def __init__(self, dev_path, **sensor_conf):
|
||||
super(CO2Meter, self).__init__(**sensor_conf)
|
||||
|
||||
self.dev_path = dev_path
|
||||
self.key = b'\x11\x11\x11\x11\x11\x11\x11\x11'
|
||||
self._dev = None
|
||||
self.values = {}
|
||||
|
||||
self.init_meter()
|
||||
self.start()
|
||||
|
||||
def init_meter(self):
|
||||
self._dev = open(self.dev_path, "a+b", 0)
|
||||
set_report = b'\x00' + self.key
|
||||
fcntl.ioctl(self._dev, self.HIDIOCSFEATURE_9, set_report)
|
||||
|
||||
def decrypt(self, data):
|
||||
cstate = [0x48, 0x74, 0x65, 0x6D, 0x70, 0x39, 0x39, 0x65]
|
||||
shuffle = [2, 4, 0, 7, 1, 6, 5, 3]
|
||||
|
||||
phase1 = bytearray(8)
|
||||
for i, o in enumerate(shuffle):
|
||||
phase1[o] = data[i]
|
||||
|
||||
phase2 = bytearray(8)
|
||||
for i in range(8):
|
||||
phase2[i] = phase1[i] ^ self.key[i]
|
||||
|
||||
phase3 = bytearray(8)
|
||||
for i in range(8):
|
||||
phase3[i] = (phase2[i] >> 3 | phase2[i - 1] << 5) & 0xff
|
||||
|
||||
ctmp = bytearray(8)
|
||||
for i in range(8):
|
||||
ctmp[i] = (cstate[i] >> 4 | cstate[i] << 4) & 0xff
|
||||
|
||||
out = bytearray(8)
|
||||
for i in range(8):
|
||||
out[i] = (0x100 + phase3[i] - ctmp[i]) & 0xff
|
||||
|
||||
return out
|
||||
|
||||
def start(self):
|
||||
self._stop = False
|
||||
self._worker_thread = threading.Thread(target=self._worker, daemon=True)
|
||||
self._worker_thread.start()
|
||||
|
||||
def stop(self):
|
||||
self._stop = True
|
||||
self._worker_thread = None
|
||||
|
||||
def _worker(self):
|
||||
while not self._stop:
|
||||
try:
|
||||
data = self._dev.read(8)
|
||||
decrypted = self.decrypt(data)
|
||||
if decrypted[4] == 0x0d and sum(decrypted[:3]) & 0xff == decrypted[3]:
|
||||
op = decrypted[0]
|
||||
val = decrypted[1] << 8 | decrypted[2]
|
||||
self.values[op] = val
|
||||
else:
|
||||
print("Error decrypting data:", data, '==>', decrypted)
|
||||
except (IOError, OSError) as e:
|
||||
print("Device error: {} - trying to reopen".format(e))
|
||||
self.values = {}
|
||||
try:
|
||||
self._dev.close()
|
||||
except Exception:
|
||||
pass
|
||||
while self._dev.closed:
|
||||
time.sleep(1)
|
||||
try:
|
||||
self.init_meter()
|
||||
except (IOError, OSError) as e:
|
||||
print("Device error: {} - trying to reopen".format(e))
|
||||
|
||||
|
||||
def get_co2(self):
|
||||
if 0x50 in self.values:
|
||||
return self.values[0x50]
|
||||
return None
|
||||
|
||||
def get_temp(self):
|
||||
if 0x42 in self.values:
|
||||
return self.values[0x42] / 16.0 - 273.15
|
||||
return None
|
||||
|
||||
def get_humidity(self):
|
||||
if 0x44 in self.values:
|
||||
return self.values[0x44] / 100.0
|
||||
return None
|
||||
|
||||
def get_data(self):
|
||||
return dict(temp=self.get_temp(), co2=self.get_co2())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
import time
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("device", help="Device do read from")
|
||||
args = parser.parse_args()
|
||||
|
||||
co2 = CO2Meter(args.device)
|
||||
co2.start()
|
||||
try:
|
||||
while True:
|
||||
if co2.get_temp() and co2.get_co2():
|
||||
print("{:2.4}°C {:4} PPM CO2".format(co2.get_temp(), co2.get_co2()))
|
||||
time.sleep(2)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
|
@ -0,0 +1,64 @@
|
|||
import os
|
||||
import re
|
||||
|
||||
import Adafruit_DHT
|
||||
|
||||
from .base import Sensor, sensor
|
||||
|
||||
|
||||
@sensor
|
||||
class DS18B20(Sensor):
|
||||
sensor_class = 'pi-ds18b20'
|
||||
|
||||
def __init__(self, **sensor_conf):
|
||||
super(DS18B20, self).__init__(**sensor_conf)
|
||||
|
||||
self._sensor_id = None
|
||||
if 'hw_id' in sensor_conf:
|
||||
self._sensor_id = sensor_conf['hw_id']
|
||||
else:
|
||||
self._find_sensor()
|
||||
|
||||
def _find_sensor(self):
|
||||
basepath = "/sys/bus/w1/devices"
|
||||
for d in os.listdir(basepath):
|
||||
if 'master' not in d and '-' in d and \
|
||||
os.path.exists(os.path.join(basepath, d, 'w1_slave')):
|
||||
self._sensor_id = d
|
||||
break
|
||||
else:
|
||||
raise ValueError("No sensor found in filesystem")
|
||||
|
||||
def get_data(self):
|
||||
temp = None
|
||||
try:
|
||||
path = "/sys/bus/w1/devices/{}/w1_slave".format(self._sensor_id)
|
||||
with open(path) as f:
|
||||
data = f.read()
|
||||
m = re.search(r".* t=(?P<temp>\d+)$", data)
|
||||
if m:
|
||||
temp = int(m.group('temp')) / 1000.0
|
||||
except IOError:
|
||||
# log?
|
||||
pass
|
||||
|
||||
return dict(temp=temp)
|
||||
|
||||
|
||||
@sensor
|
||||
class DHT(Sensor):
|
||||
sensor_class = 'pi-dht'
|
||||
|
||||
def __init__(self, **sensor_conf):
|
||||
super(DHT, self).__init__(**sensor_conf)
|
||||
|
||||
if sensor_conf['dht_type'] == 'dht11':
|
||||
self._dht_type = Adafruit_DHT.DHT11
|
||||
elif sensor_conf['dht_type'] == 'dht22':
|
||||
self._dht_type = Adafruit_DHT.DHT22
|
||||
else:
|
||||
raise ValueError("Unknown DHT")
|
||||
|
||||
def get_data(self):
|
||||
data = Adafruit_DHT.read_retry(self._dht_type, self.pin)
|
||||
return dict(humidity=data[0], temp=data[1])
|
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(name='mqtt-sensord',
|
||||
version='0.1.0',
|
||||
description='MQTT sensor reader daemon',
|
||||
author='Sebastian Lohff',
|
||||
author_email='seba@someserver.de',
|
||||
url='https://git.someserver.de/seba/mqtt-sensord/',
|
||||
python_requires='>=3.5',
|
||||
packages=find_packages(),
|
||||
install_requires=[
|
||||
'paho-mqtt',
|
||||
"Adafruit_DHT ; platform_machine=='armv6l' or platform_machine=='armv7l'"
|
||||
],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'mqtt-sensord = mqtt_sensord.mqtt_sensord:main'
|
||||
]
|
||||
},
|
||||
)
|
Loading…
Reference in New Issue