Initial commit

This commit is contained in:
Sebastian Lohff 2019-07-09 22:01:39 +02:00
commit f94c1d1cf1
14 changed files with 437 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.pyc
__pycache__
*.swp

14
mqtt-sensord.conf.example Normal file
View File

@ -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
mqtt_sensord/__init__.py Normal file
View File

45
mqtt_sensord/mqtt_sensord.py Executable file
View File

@ -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()

View File

@ -0,0 +1,8 @@
import sys
if sys.implementation == 'micropython':
from esp import MQTTClient
else:
from .paho_mqtt import MQTTClient
__all__ = [MQTTClient]

View File

@ -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")

View File

@ -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))

View File

@ -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 *

View File

@ -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()
}

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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])

22
setup.py Normal file
View File

@ -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'
]
},
)