From 01ddd7e3cc8788308663740a857ab694f417c3f8 Mon Sep 17 00:00:00 2001 From: "S. Seegel" Date: Wed, 16 Dec 2020 02:32:32 +0100 Subject: [PATCH] Better timing tolerance Add weather sensors wh2, wh7000 some python3 issues fix docu & PC PIR --- README.md | 7 +- apps/connair.py | 2 +- apps/ec3000.py | 4 +- apps/emoncms.py | 11 +- apps/lacrosse.py | 4 +- apps/lacrossegw.py | 485 ++++++++++++++++++++++--------------------- apps/maxrx.py | 8 +- apps/mqttdash.py | 38 ++-- apps/rcprotocols.py | 271 +++++++++++++++--------- apps/rcpulse.py | 4 +- apps/rcpulsegw.py | 31 +-- raspyrfm/__init__.py | 4 +- 12 files changed, 476 insertions(+), 393 deletions(-) diff --git a/README.md b/README.md index 2cdc157..2df9181 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ rfm.set_params( ## connair.py emulate a gateway for controlling RC sockets via the app power-switch. Compatible to "Brennenstuhl Brematic", Intertechno "ITGW-433", "ConnAir" -see https://power-switch.eu/ +Here you find a python client controlling this gateway: https://github.com/markusressel/raspyrfm-client ## emoncms.py receive lacrosse-sensors with the RaspyRFM and post them to the open energy monitor, see https://openenergymonitor.org/ @@ -67,3 +67,8 @@ La crosse {'batlo': False, 'AFC': 376, 'init': False, 'T': (19.7, 'C'), 'RSSI': ## Product [Module RaspbyRFM Seegel Systeme](http://www.seegel-systeme.de/produkt/raspyrfm-ii/) + +## Blog articles +* [Software installation & examples (german)](http://www.seegel-systeme.de/2015/09/02/ein-funkmodul-fuer-den-raspberry-raspyrfm/) +* [Control RC switches with RaspyRFM (german)](https://www.seegel-systeme.de/2015/09/05/funksteckdosen-mit-dem-raspberry-pi-steuern/) +* [Receive lacrosse sensors (german)](http://www.seegel-systeme.de/2015/02/07/funkthermometer/) \ No newline at end of file diff --git a/apps/connair.py b/apps/connair.py index 31dfea4..f6dba81 100755 --- a/apps/connair.py +++ b/apps/connair.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env python import socket from raspyrfm import * diff --git a/apps/ec3000.py b/apps/ec3000.py index 7637855..62cc4b2 100755 --- a/apps/ec3000.py +++ b/apps/ec3000.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env python from raspyrfm import * import sys @@ -113,7 +113,7 @@ def calcword(datain): result |= d return result -print "Waiting for sensors..." +print("Waiting for sensors...") while 1: data = rfm.receive(56) data = descramble(data[0]) diff --git a/apps/emoncms.py b/apps/emoncms.py index 2b689b2..2750bc0 100755 --- a/apps/emoncms.py +++ b/apps/emoncms.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env python from raspyrfm import * import sensors @@ -7,7 +7,6 @@ import time import requests import json - URL = 'https://emoncms.org/input/post.json' APIKEY = '123456789123456789' @@ -44,7 +43,7 @@ def LogSensor(data): if s['sensorId'] == data['ID']: if data['ID'] in lasttimes: if time.time() - lasttimes[data['ID']] < s['minInterval']: - print "discarded value" + print("discarded value") return lasttimes[data['ID']] = time.time() @@ -55,9 +54,9 @@ def LogSensor(data): payload = {'apikey': APIKEY, 'node': s['node'], 'json': json.dumps(values)} r = requests.get(URL, params = payload) - print "Sending to emon:", payload, "Result:", r + print("Sending to emon:", payload, "Result:", r) return - print "No match for ID" + print("No match for ID") if raspyrfm_test(2, RFM69): @@ -65,7 +64,7 @@ if raspyrfm_test(2, RFM69): elif raspyrfm_test(1, RFM69): rfm = RaspyRFM(1, RFM69) #when using a single single 868 MHz RaspyRFM else: - print "No RFM69 module found!" + print("No RFM69 module found!") exit() rfm.set_params( diff --git a/apps/lacrosse.py b/apps/lacrosse.py index 7ca4dcd..dc994c1 100755 --- a/apps/lacrosse.py +++ b/apps/lacrosse.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env python from raspyrfm import * import sensors @@ -12,7 +12,7 @@ if raspyrfm_test(2, RFM69): rfm = RaspyRFM(2, RFM69) #when using the RaspyRFM twin elif raspyrfm_test(1, RFM69): print("Found RaspyRFM single") - rfm = RaspyRFM(1, RFM69) #when using a single single 868 MHz RaspyRFM + rfm = RaspyRFM(1, RFM69) #when using a single 868 MHz RaspyRFM else: print("No RFM69 module found!") exit() diff --git a/apps/lacrossegw.py b/apps/lacrossegw.py index 26f5218..baeb0fc 100755 --- a/apps/lacrossegw.py +++ b/apps/lacrossegw.py @@ -1,229 +1,231 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env python # -*- coding: utf-8 -*- -from raspyrfm import * +import raspyrfm import sensors -from sensors import rawsensor import sys import time import threading import math import json from datetime import datetime -#import os -import SocketServer -import SimpleHTTPServer -import urlparse + +try: + #python2.7 + import SocketServer as socketserver + from urlparse import urlparse, parse_qs + from SimpleHTTPServer import SimpleHTTPRequestHandler as Handler +except ImportError: + #python3 + import socketserver + from http.server import SimpleHTTPRequestHandler as Handler + from urllib.parse import urlparse, parse_qs + lock = threading.Lock() with open("lacrossegw.conf") as jfile: - config = json.load(jfile) + config = json.load(jfile) -if raspyrfm_test(2, RFM69): - print("Found RaspyRFM twin") - rfm = RaspyRFM(2, RFM69) #when using the RaspyRFM twin -elif raspyrfm_test(1, RFM69): - print("Found RaspyRFM single") - rfm = RaspyRFM(1, RFM69) #when using a single single 868 MHz RaspyRFM +if raspyrfm.raspyrfm_test(2, raspyrfm.RFM69): + print("Found RaspyRFM twin") + rfm = raspyrfm.RaspyRFM(2, raspyrfm.RFM69) #when using the RaspyRFM twin +elif raspyrfm.raspyrfm_test(1, raspyrfm.RFM69): + print("Found RaspyRFM single") + rfm = raspyrfm.RaspyRFM(1, raspyrfm.RFM69) #when using a single single 868 MHz RaspyRFM else: - print("No RFM69 module found!") - exit() + print("No RFM69 module found!") + exit() try: - from influxdb import InfluxDBClient - influxClient = InfluxDBClient( - host=config["influxdb"]["host"], - port=config["influxdb"]["port"], - username=config["influxdb"]["user"], - password=config["influxdb"]["pass"] - ) + from influxdb import InfluxDBClient + influxClient = InfluxDBClient( + host=config["influxdb"]["host"], + port=config["influxdb"]["port"], + username=config["influxdb"]["user"], + password=config["influxdb"]["pass"] + ) - createDb = True - for db in influxClient.get_list_database(): - if db["name"] == config["influxdb"]["database"]: - createDb = False - break - if createDb: - influxClient.create_database(config["influxdb"]["database"]) - influxClient.switch_database(config["influxdb"]["database"]) - influxClient.alter_retention_policy("autogen", duration="2h", replication=1, shard_duration="1h") - influxClient.create_retention_policy("one_week", duration="1w", replication=1, shard_duration='24h') - influxClient.create_retention_policy("one_year", database=config["influxdb"]["database"], duration="365d", replication=1, shard_duration='1w') - influxClient.create_continuous_query("three_min", 'SELECT mean(T) as "T", mean(RH) as "RH", mean(AH) as "AH", mean(DEW) as "DEW" INTO "one_week"."lacrosse" from "lacrosse" GROUP BY time(3m),*') - influxClient.create_continuous_query("three_hour", 'SELECT mean(T) as "T", mean(RH) as "RH", mean(AH) as "AH", mean(DEW) as "DEW" INTO "one_week"."lacrosse" from "lacrosse" GROUP BY time(3h),*') - else: - influxClient.switch_database(config["influxdb"]["database"]) + createDb = True + for db in influxClient.get_list_database(): + if db["name"] == config["influxdb"]["database"]: + createDb = False + break + if createDb: + influxClient.create_database(config["influxdb"]["database"]) + influxClient.switch_database(config["influxdb"]["database"]) + influxClient.alter_retention_policy("autogen", duration="2h", replication=1, shard_duration="1h") + influxClient.create_retention_policy("one_week", duration="1w", replication=1, shard_duration='24h') + influxClient.create_retention_policy("one_year", database=config["influxdb"]["database"], duration="365d", replication=1, shard_duration='1w') + influxClient.create_continuous_query("three_min", 'SELECT mean(T) as "T", mean(RH) as "RH", mean(AH) as "AH", mean(DEW) as "DEW" INTO "one_week"."lacrosse" from "lacrosse" GROUP BY time(3m),*') + influxClient.create_continuous_query("three_hour", 'SELECT mean(T) as "T", mean(RH) as "RH", mean(AH) as "AH", mean(DEW) as "DEW" INTO "one_week"."lacrosse" from "lacrosse" GROUP BY time(3h),*') + else: + influxClient.switch_database(config["influxdb"]["database"]) except Exception as ex: - influxClient = None - print("influx init error: " + ex.__class__.__name__ + " " + (''.join(ex.args))) + influxClient = None + print("influx init error: " + ex.__class__.__name__ + " " + (''.join(ex.args))) try: - import paho.mqtt.client as mqtt - mqttClient = mqtt.Client() - mqttClient.connect("localhost", 1883, 60) - mqttClient.loop_start() + import paho.mqtt.client as mqtt + mqttClient = mqtt.Client() + mqttClient.connect("localhost", 1883, 60) + mqttClient.loop_start() except: - mqttClient = None - print("mqtt init error") + mqttClient = None + print("mqtt init error") rfm.set_params( Freq = 868.300, #MHz center frequency Datarate = 9.579, #kbit/s baudrate - ModulationType = rfm69.FSK, #modulation - Deviation = 30, #kHz frequency deviation OBW = 69.6/77.2 kHz, h = 6.3/3.5 + ModulationType = raspyrfm.rfm69.FSK, #modulation SyncPattern = [0x2d, 0xd4], #syncword Bandwidth = 100, #kHz bandwidth - #AfcBandwidth = 150, - #AfcFei = 0x0E, RssiThresh = -100, #-100 dB RSSI threshold ) class BaudChanger(threading.Thread): - baud = False - def __init__(self): - threading.Thread.__init__(self) + baud = False + def __init__(self): + threading.Thread.__init__(self) - def run(self): - while True: - time.sleep(15) - print "Change baudrate" - if self.baud: - rfm.set_params(Datarate = 9.579) - else: - rfm.set_params(Datarate = 17.241) - self.baud = not self.baud + def run(self): + while True: + time.sleep(15) + print("Change baudrate") + if self.baud: + rfm.set_params(Datarate = 9.579) + else: + rfm.set_params(Datarate = 17.241) + self.baud = not self.baud baudChanger = BaudChanger() baudChanger.daemon = True baudChanger.start() def writeInflux(payload): - if not influxClient: - return - T = payload["T"] - wr = { - "measurement": "lacrosse", - "fields": { - "T": T - }, - "tags": {"sensor": payload["ID"] if not ("room" in payload) else payload["room"]} - } + if not influxClient: + return + T = payload["T"] + wr = { + "measurement": "lacrosse", + "fields": { + "T": T + }, + "tags": {"sensor": payload["ID"] if not ("room" in payload) else payload["room"]} + } - if ("RH" in payload): - wr["fields"]["RH"] = payload['RH'] - wr["fields"]["DEW"] = payload["DEW"] - wr["fields"]["AH"] = payload["AH"] + if ("RH" in payload): + wr["fields"]["RH"] = payload['RH'] + wr["fields"]["DEW"] = payload["DEW"] + wr["fields"]["AH"] = payload["AH"] - influxClient.write_points([wr]) + influxClient.write_points([wr]) def getCacheSensor(id, sensorConfig = None): - sensor = None - if (id in cache) and ((datetime.now() - cache[id]["ts"]).total_seconds() < 180): - sensor = cache[id]["payload"] - if sensorConfig is not None: - if ('tMax' in sensorConfig) and (sensor["T"] > sensorConfig["tMax"]): - sensor["tStatus"] = "high" + sensor = None + if (id in cache) and ((datetime.now() - cache[id]["ts"]).total_seconds() < 180): + sensor = cache[id]["payload"] + if sensorConfig is not None: + if ('tMax' in sensorConfig) and (sensor["T"] > sensorConfig["tMax"]): + sensor["tStatus"] = "high" - if ('tMin' in sensorConfig) and (sensor["T"] < sensorConfig["tMin"]): - sensor["tStatus"] = "low" + if ('tMin' in sensorConfig) and (sensor["T"] < sensorConfig["tMin"]): + sensor["tStatus"] = "low" - if ('tStatus' not in sensor) and ('tMax' in sensorConfig or 'tMin' in sensorConfig): - sensor["tStatus"] = "ok" + if ('tStatus' not in sensor) and ('tMax' in sensorConfig or 'tMin' in sensorConfig): + sensor["tStatus"] = "ok" - if ('RH' in sensor): - outSensor = getCacheSensor(sensorConfig["idOutside"]) if 'idOutside' in sensorConfig else None - if (outSensor is not None) and ('AH' in outSensor): - sensor["AHratio"] = round((outSensor["AH"] / sensor["AH"] - 1) * 100) - sensor["RHvent"] = round(outSensor["DD"] / sensor["SDD"] * 100) + if ('RH' in sensor): + outSensor = getCacheSensor(sensorConfig["idOutside"]) if 'idOutside' in sensorConfig else None + if (outSensor is not None) and ('AH' in outSensor): + sensor["AHratio"] = round((outSensor["AH"] / sensor["AH"] - 1) * 100) + sensor["RHvent"] = round(outSensor["DD"] / sensor["SDD"] * 100) - if ('rhMax' in sensorConfig) and (sensor["RH"] > sensorConfig["rhMax"]): - #too wet! - sensor["rhStatus"] = "high" - if "AHratio" in sensor: - if sensor["AHratio"] <= -10: - sensor["window"] = "open" - elif sensor["AHratio"] >= 10: - sensor["window"] = "close" + if ('rhMax' in sensorConfig) and (sensor["RH"] > sensorConfig["rhMax"]): + #too wet! + sensor["rhStatus"] = "high" + if "AHratio" in sensor: + if sensor["AHratio"] <= -10: + sensor["window"] = "open" + elif sensor["AHratio"] >= 10: + sensor["window"] = "close" - if ('rhMin' in sensorConfig) and (sensor["RH"] < sensorConfig["rhMin"]): - #too dry - sensor["rhStatus"] = "low" - if "AHratio" in sensor: - if sensor["AHratio"] >= 10: - sensor["window"] = "open" - elif sensor["AHratio"] <= -10: - sensor["window"] = "close" + if ('rhMin' in sensorConfig) and (sensor["RH"] < sensorConfig["rhMin"]): + #too dry + sensor["rhStatus"] = "low" + if "AHratio" in sensor: + if sensor["AHratio"] >= 10: + sensor["window"] = "open" + elif sensor["AHratio"] <= -10: + sensor["window"] = "close" - if 'rhStatus' not in sensor and ('rhMin' in sensorConfig or 'rhMax' in sensorConfig): - sensor["rhStatus"] = "ok" + if 'rhStatus' not in sensor and ('rhMin' in sensorConfig or 'rhMax' in sensorConfig): + sensor["rhStatus"] = "ok" - return sensor + return sensor -class MyHttpRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): - def getjson(self): - self.send_response(200) - self.send_header('Content-type', 'application/json') - self.end_headers() - resp = {"sensors": []} - idlist = [] - - lock.acquire() - for csens in config["sensors"]: - id = csens["id"] - sensor = getCacheSensor(id, csens) - if sensor is not None: - #print("Sensor: ", sensor) - idlist.append(id) - else: - sensor = {} - sensor["room"] = csens["name"] - sensor["ID"] = id - resp["sensors"].append(sensor) +class MyHttpRequestHandler(Handler): + def getjson(self): + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.end_headers() + resp = {"sensors": []} + idlist = [] + + lock.acquire() + for csens in config["sensors"]: + id = csens["id"] + sensor = getCacheSensor(id, csens) + if sensor is not None: + idlist.append(id) + else: + sensor = {} + sensor["room"] = csens["name"] + sensor["ID"] = id + resp["sensors"].append(sensor) - for id in cache: - if id in idlist: - continue + for id in cache: + if id in idlist: + continue - sensor = getCacheSensor(id) - if sensor is not None: - resp["sensors"].append(sensor) + sensor = getCacheSensor(id) + if sensor is not None: + resp["sensors"].append(sensor) - lock.release() + lock.release() - self.wfile.write(json.dumps(resp)) - - def do_GET(self): - url = urlparse.urlparse(self.path) - p = url.path - q = urlparse.parse_qs(url.query) - if 'name' in q: - name = q['name'][0] + self.wfile.write(json.dumps(resp).encode()) + + def do_GET(self): + url = urlparse(self.path) + p = url.path + q = parse_qs(url.query) + if 'name' in q: + name = q['name'][0] - if p == '/data': - self.getjson() + if p == '/data': + self.getjson() - elif p == '/': - self.path = 'index.html' - return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) + elif p == '/': + self.path = 'index.html' + return Handler.do_GET(self) - elif p == '/history' and influxClient and name: - resp = influxClient.query( - "SELECT mean(T), mean(RH) FROM one_week.lacrosse WHERE (sensor = $name) AND time >= now() - 4h GROUP BY time(3m) fill(none)", - bind_params={'name': name} - ) - self.send_response(200) - self.send_header('Content-type', 'application/json') - self.end_headers() - self.wfile.write(json.dumps(resp.raw['series'][0]['values'])) - - else: - return self.send_error(404, self.responses.get(404)[0]) + elif p == '/history' and influxClient and name: + resp = influxClient.query( + "SELECT mean(T), mean(RH) FROM one_week.lacrosse WHERE (sensor = $name) AND time >= now() - 4h GROUP BY time(3m) fill(none)", + bind_params={'name': name} + ) + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.end_headers() + self.wfile.write(json.dumps(resp.raw['series'][0]['values']).encode()) + else: + return self.send_error(404, self.responses.get(404)[0]) -class Server(SocketServer.ThreadingMixIn, SocketServer.TCPServer): - pass +class Server(socketserver.ThreadingMixIn, socketserver.TCPServer): + pass cache = {} @@ -232,94 +234,93 @@ server_thread = threading.Thread(target=server.serve_forever) server_thread.daemon = True server_thread.start() -print "Waiting for sensors..." +print("Waiting for sensors...") while 1: - rxObj = rfm.receive(7) + rxObj = rfm.receive(7) - try: - sensorObj = rawsensor.CreateSensor(rxObj) - sensorData = sensorObj.GetData() - payload = {} - ID = sensorData["ID"] - payload["ID"] = ID - T = sensorData["T"][0] - payload["T"] = T - except: - continue + try: + sensorObj = sensors.rawsensor.CreateSensor(rxObj) + sensorData = sensorObj.GetData() + payload = {} + ID = sensorData["ID"] + payload["ID"] = ID + T = sensorData["T"][0] + payload["T"] = T + except: + continue - payload["rssi"] = rxObj[1] - payload["afc"] = rxObj[2] - payload["batlo"] = sensorData['batlo'] - payload["init"] = sensorData["init"] - lock.acquire() - for csens in config["sensors"]: - if sensorData['ID'] == csens["id"]: - payload["room"] = csens["name"] - break + payload["rssi"] = rxObj[1] + payload["afc"] = rxObj[2] + payload["batlo"] = sensorData['batlo'] + payload["init"] = sensorData["init"] + lock.acquire() + for csens in config["sensors"]: + if sensorData['ID'] == csens["id"]: + payload["room"] = csens["name"] + break - if 'RH' in sensorData: - RH = int(sensorData['RH'][0]) - payload["RH"] = RH - a = 7.5 - b = 237.4 - SDD = 6.1078 * 10**(a*T/(b+T)) + if 'RH' in sensorData: + RH = int(sensorData['RH'][0]) + payload["RH"] = RH + a = 7.5 + b = 237.4 + SDD = 6.1078 * 10**(a*T/(b+T)) - DD = RH / 100.0 * SDD - v = math.log10(DD/6.1078) - payload["DEW"] = round(b*v/(a-v), 1) - payload["AH"] = round(10**5 * 18.016/8314.3 * DD/(T+273.15), 1) - payload["SDD"] = SDD - payload["DD"] = DD + DD = RH / 100.0 * SDD + v = math.log10(DD/6.1078) + payload["DEW"] = round(b*v/(a-v), 1) + payload["AH"] = round(10**5 * 18.016/8314.3 * DD/(T+273.15), 1) + payload["SDD"] = SDD + payload["DD"] = DD - DD = RH / 80.0 * SDD - v = math.log10(DD/6.1078) - payload["DEW80"] = round(b*v/(a-v), 1) + DD = RH / 80.0 * SDD + v = math.log10(DD/6.1078) + payload["DEW80"] = round(b*v/(a-v), 1) - DD = RH / 60.0 * SDD - v = math.log10(DD/6.1078) - payload["DEW60"] = round(b*v/(a-v), 1) + DD = RH / 60.0 * SDD + v = math.log10(DD/6.1078) + payload["DEW60"] = round(b*v/(a-v), 1) + if not ID in cache: + cache[ID] = {} + cache[ID]["count"] = 1 + cache[ID]["payload"] = payload + cache[ID]["payload"]["tMin"] = T + cache[ID]["payload"]["tMax"] = T + else: + payload["tMin"] = cache[ID]["payload"]["tMin"] + payload["tMax"] = cache[ID]["payload"]["tMax"] + if payload["tMin"] > T: + payload["tMin"] = T + if payload["tMax"] < T: + payload["tMax"] = T + + cache[ID]["payload"] = payload - if not ID in cache: - cache[ID] = {} - cache[ID]["count"] = 1 - cache[ID]["payload"] = payload - cache[ID]["payload"]["tMin"] = T - cache[ID]["payload"]["tMax"] = T - else: - payload["tMin"] = cache[ID]["payload"]["tMin"] - payload["tMax"] = cache[ID]["payload"]["tMax"] - if payload["tMin"] > T: - payload["tMin"] = T - if payload["tMax"] < T: - payload["tMax"] = T - - cache[ID]["payload"] = payload + cache[ID]["ts"] = datetime.now() + cache[ID]["count"] += 1 - cache[ID]["ts"] = datetime.now() - cache[ID]["count"] += 1 + line = u" ID: {:2} ".format(ID); + line += u'room {:12} '.format(payload["room"][:12] if ("room" in payload) else "---") + line += u' T: {:5} \u00b0C '.format(payload["T"]) + if "RH" in payload: + line += 'RH: {:2} % '.format(payload["RH"]) + line += "battery: " + ("LOW " if payload["batlo"] else "OK ") + line += "init: " + ("true " if payload["init"] else "false ") - line = u" ID: {:2} ".format(ID); - line += u'room {:12} '.format(payload["room"][:12] if ("room" in payload) else "---") - line += u' T: {:5} \u00b0C '.format(payload["T"]) - if "RH" in payload: - line += 'RH: {:2} % '.format(payload["RH"]) - line += "battery: " + ("LOW " if payload["batlo"] else "OK ") - line += "init: " + ("true " if payload["init"] else "false ") + print('------------------------------------------------------------------------------') + print(line) + lock.release() - print('------------------------------------------------------------------------------') - print(line) - lock.release() + try: + if influxClient: + writeInflux(payload) + except: + pass - try: - if influxClient: - writeInflux(payload) - except: - pass - - try: - if mqttClient: - mqttClient.publish('home/lacrosse/'+ payload['ID'], json.dumps(payload)) - except: - pass + try: + if mqttClient: + mqttClient.publish('home/lacrosse/'+ payload['ID'], json.dumps(payload)) + except: + pass diff --git a/apps/maxrx.py b/apps/maxrx.py index dd9b247..da1188b 100755 --- a/apps/maxrx.py +++ b/apps/maxrx.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env python from raspyrfm import * import sys @@ -92,11 +92,11 @@ def Decode(frame): valve = payload[1] dst = payload[2] / 2.0 info += ", mode " + str(devflags & 0x03) - if (devflags & (1<<7)) <> 0: + if (devflags & (1<<7)) != 0: info += ", bat err" - if (devflags & (1<<6)) <> 0: + if (devflags & (1<<6)) != 0: info += ", com err" - if (devflags & (1<<5)) <> 0: + if (devflags & (1<<5)) != 0: info += ", locked" info += ", valve " + str(valve) + "%" info += ", set T " + str(dst) diff --git a/apps/mqttdash.py b/apps/mqttdash.py index d92cf99..03a2ef3 100755 --- a/apps/mqttdash.py +++ b/apps/mqttdash.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env python import rcprotocols import time @@ -8,30 +8,18 @@ mqttClient = mqtt.Client() mqttClient.connect("localhost", 1883, 60) mqttClient.loop_start() -rx = rcprotocols.PulseReceiver(1) +def publish(proto, params): + print("Publish: " + str(proto) + " / " + params) + mqttClient.publish("home/rcbuttons/" + proto, params) -lastEvents = {} - -def publish(proto, code): - print("Publish: " + str(proto) + " / " + str(code)) - mqttClient.publish("home/rcbuttons/" + proto, code) +def rxcb(dec, train): + if dec is None: + print("raw", train) + return + for d in dec: + publish(d["protocol"], str(d["params"])) + +rctrx = rcprotocols.RcTransceiver(1, 433.92, rxcb) while True: - time.sleep(0.1) - evts = rx.getEvents() - if evts: - for e in evts: - if e["protocol"] in lastEvents: - le = lastEvents[e["protocol"]] - if e["code"] != le["code"]: - publish(e["protocol"], e["code"]) - le["code"] = e["code"] - elif (time.time() - le["ts"] > 1): - publish(e["protocol"], e["code"]) - le["ts"] = time.time() - else: - lastEvents[e["protocol"]] = { - "ts": time.time(), - "code": e["code"] - } - publish(e["protocol"], e["code"]) + time.sleep(1) diff --git a/apps/rcprotocols.py b/apps/rcprotocols.py index 1173c66..b5f4f64 100644 --- a/apps/rcprotocols.py +++ b/apps/rcprotocols.py @@ -12,7 +12,10 @@ PARAM_COMMAND = ('a', 'command') PARAM_CODE = ('c', 'code') PARAM_DIPS = ('d', 'dips') -class RcProtocol: +CLASS_RCSWITCH = "switch" +CLASS_RCWEATHER = "weather" + +class RcPulse: def __init__(self): self.__numbits = 0 self._ookdata = bytearray() @@ -22,21 +25,23 @@ class RcProtocol: self._lastdecode = None self._lastdecodetime = 0 - sympulses = [] - for i in self._symbols: - sympulses += self._symbols[i] - sympulses.sort(reverse=True) - i = 0 - while i q: + q = 1.0 * symlist[i] / symlist[i+1] + p1 = 1.0 * symlist[i] + p2 = 1.0 * symlist[i+1] + + f = (p2-p1) / (p2+p1) + + self._minwidth = self._timebase * (1 - f) + self._maxwidth = self._timebase * (1 + f) def _reset(self): self.__numbits = 0 @@ -142,24 +147,25 @@ class RcProtocol: def encode(self, params): pass -class TristateBase(RcProtocol): #Baseclass for old intertechno, Brennenstuhl, ... +class TristateBase(RcPulse): #Baseclass for old intertechno, Brennenstuhl, ... def __init__(self): - self._timebase = 300 + if not hasattr(self, "_timebase"): + self._timebase = 300 self._repetitions = 4 - self._pattern = "[01fF]{12}" + self._pattern = "[01F]{12}" self._symbols = { - '0': [1, 4, 1, 4], - '1': [4, 1, 4, 1], - 'f': [1, 4, 4, 1], - 'F': [1, 4, 4, 1], + '0': [1, 3, 1, 3], + '1': [3, 1, 3, 1], + 'F': [1, 3, 3, 1], } self._footer = [1, 31] - RcProtocol.__init__(self) + self._class = CLASS_RCSWITCH + RcPulse.__init__(self) def _encode_int(self, ival, digits): code = "" for i in range(digits): - code += "f" if (ival & 0x01) > 0 else "0" + code += "F" if (ival & 0x01) > 0 else "0" ival >>= 1 return code @@ -167,20 +173,20 @@ class TristateBase(RcProtocol): #Baseclass for old intertechno, Brennenstuhl, .. i = 0 while tristateval != "": i <<= 1 - if tristateval[-1] != '0': + if tristateval[-1] == "F": i |= 1 tristateval = tristateval[:-1] return i -class Tristate(TristateBase): #old intertechno +class Tristate(TristateBase): def __init__(self): self._name = "tristate" TristateBase.__init__(self) self.params = [PARAM_CODE] def encode(self, params, timebase=None, repetitions=None): - return self._build_frame(params["code"], timebase, repetitions) + return self._build_frame(params["code"].upper(), timebase, repetitions) def decode(self, pulsetrain): symbols, tb, rep = self._decode_symbols(pulsetrain[0:-2]) @@ -199,7 +205,7 @@ class ITTristate(TristateBase): #old intertechno symbols += self._encode_int(ord(params["house"][0]) - ord('A'), 4) symbols += self._encode_int(int(params["unit"]) - 1, 2) symbols += self._encode_int(int(params["group"]) - 1, 2) - symbols += "0f" + symbols += "0F" symbols += self._encode_command(params["command"]) return self._build_frame(symbols, timebase, repetitions) @@ -210,10 +216,10 @@ class ITTristate(TristateBase): #old intertechno "house": chr(self._decode_int(symbols[:4]) + ord('A')), "unit": self._decode_int(symbols[4:6]) + 1, "group": self._decode_int(symbols[6:8]) + 1, - "command": self._decode_command(symbols[10:12].upper()), + "command": self._decode_command(symbols[10:12]), }, tb, rep -class Brennenstuhl(TristateBase): #old intertechno +class Brennenstuhl(TristateBase): def __init__(self): self._name = "brennenstuhl" TristateBase.__init__(self) @@ -248,7 +254,7 @@ class Brennenstuhl(TristateBase): #old intertechno "command": self._decode_command(symbols[10:12].upper()), }, tb, rep -class PPM1(RcProtocol): #Intertechno, Hama, ... +class PPM1(RcPulse): #Intertechno, Hama, Nexa, Telldus, ... ''' PDM1: Pulse Position Modulation Every bit consists of 2 shortpulses. Long distance between these pulses 2 pulses -> 1, else -> 0 @@ -265,15 +271,16 @@ class PPM1(RcProtocol): #Intertechno, Hama, ... '1': [1, 5, 1, 1], } self._footer = [1, 39] - RcProtocol.__init__(self) + self._class = CLASS_RCSWITCH + RcPulse.__init__(self) class Intertechno(PPM1): def __init__(self): - PPM1.__init__(self) self._name = "intertechno" - self._timebase = 275 self.params = [PARAM_ID, PARAM_UNIT, PARAM_COMMAND] self._commands = {"on": "1", "off": "0"} + self._timebase = 275 + PPM1.__init__(self) def _encode_unit(self, unit): return "{:04b}".format(int(unit) - 1) @@ -311,9 +318,9 @@ class Hama(Intertechno): return 16 - int(unit, 2) -class PWM1(RcProtocol): +class PWM1(RcPulse): ''' - PWM1: Pulse Width Modulation + PWM1: Pulse Width Modulation 24 bit Wide pulse -> 1, small pulse -> 0 Frame: header, payload, footer Used by Emylo, Logilight, ... @@ -327,14 +334,15 @@ class PWM1(RcProtocol): '0': [1, 3], } self._footer = [1, 31] - RcProtocol.__init__(self) + self._class = CLASS_RCSWITCH + RcPulse.__init__(self) class Logilight(PWM1): def __init__(self): - PWM1.__init__(self) self._name = "logilight" self.params = [PARAM_ID, PARAM_UNIT, PARAM_COMMAND] self._commands = {"on": "1", "learn": "1", "off": "0"} + PWM1.__init__(self) def _encode_unit(self, unit): res = "" @@ -373,10 +381,10 @@ class Logilight(PWM1): class Emylo(PWM1): def __init__(self): - PWM1.__init__(self) self._name = "emylo" self.params = [PARAM_ID, PARAM_COMMAND] self._commands = {'A': '0001', 'B': '0010', 'C': '0100', 'D': '1000'} + PWM1.__init__(self) def encode(self, params, timebase=None, repetitions=None): symbols = "" @@ -392,7 +400,7 @@ class Emylo(PWM1): "command": self._decode_command(symbols[-4:]) }, tb, rep -class FS20(RcProtocol): +class FS20(RcPulse): def __init__(self): self._name = "fs20" self._timebase = 200 @@ -405,7 +413,8 @@ class FS20(RcProtocol): self._header = [2, 2] * 12 + [3, 3] self._footer = [1, 100] self.params = [PARAM_ID,PARAM_UNIT, PARAM_COMMAND] - RcProtocol.__init__(self) + self._class = CLASS_RCSWITCH + RcPulse.__init__(self) def __encode_byte(self, b): b &= 0xFF @@ -439,7 +448,7 @@ class FS20(RcProtocol): "command": int(symbols[40:48], 2), }, tb, rep -class Voltcraft(RcProtocol): +class Voltcraft(RcPulse): ''' PPM: Pulse Position Modulation Pulse in middle of a symbol: 0, end of symbol: 1 @@ -458,7 +467,8 @@ class Voltcraft(RcProtocol): self._footer = [132] self.params = [PARAM_ID, PARAM_UNIT, PARAM_COMMAND] self._commands = {"off": "000", "alloff": "100", "on": "010", "allon": "110", "dimup": "101", "dimdown": "111"} - RcProtocol.__init__(self) + self._class = CLASS_RCSWITCH + RcPulse.__init__(self) def encode(self, params, timebase=None, repetitions=None): if params["command"] in ["on", "off"]: @@ -483,27 +493,11 @@ class Voltcraft(RcProtocol): "command": self._decode_command(symbols[14:17]) }, tb, rep -class PWM2(RcProtocol): +class PilotaCasa(RcPulse): ''' - PWM2: Pulse Width Modulation + Pulse Width Modulation 32 bit Wide pulse -> 0, small pulse -> 1 - Frame: header, payload, footer - Used by Pilota casa ''' - def __init__(self): - self._name = "pwm2" - self._timebase = 600 - self._repetitions = 10 - self._pattern = "[01]{32}" - self._symbols = { - '1': [1, 2], - '0': [2, 1], - } - self._footer = [1, 11] - self.params = [PARAM_CODE] - RcProtocol.__init__(self) - -class PilotaCasa(PWM2): __codes = { '110001': (1, 1, 'on'), '111110': (1, 1, 'off'), '011001': (1, 2, 'on'), '010001': (1, 2, 'off'), @@ -521,9 +515,18 @@ class PilotaCasa(PWM2): } def __init__(self): - PWM2.__init__(self) self._name = "pilota" + self._timebase = 550 + self._repetitions = 5 + self._pattern = "[01]{32}" + self._symbols = { + '1': [1, 2], + '0': [2, 1], + } + self._footer = [1, 12] self.params = [PARAM_ID, PARAM_GROUP, PARAM_UNIT, PARAM_COMMAND] + self._class = CLASS_RCSWITCH + RcPulse.__init__(self) def encode(self, params, timebase=None, repetitions=None): symbols = '01' @@ -551,36 +554,33 @@ class PilotaCasa(PWM2): "command": c[2], }, tb, rep -class PCPIR(RcProtocol): #pilota casa PIR sensor +class PCPIR(TristateBase): #pilota casa PIR sensor + ''' + Pilota Casa IR sensor + ''' def __init__(self): self._name = "pcpir" - self._timebase = 400 - self._repetitions = 5 - self._pattern = "[01]{12}" - self._symbols = { - '1': [1, 3, 1, 3], - '0': [1, 3, 3, 1], - } - RcProtocol.__init__(self) - self._parser.add_argument("-c", "--code", required=True) + self.params = [PARAM_ID, PARAM_COMMAND] + self._timebase = 500 + self._commands = {"off": "0", "on": "F"} + TristateBase.__init__(self) + + def encode(self, params, timebase=None, repetitions=None): + symbols = "" + return self._build_frame(symbols, timebase, repetitions) def decode(self, pulsetrain): - code, tb, rep = self._decode_symbols(pulsetrain[0:-2]) - if code: + symbols, tb, rep = self._decode_symbols(pulsetrain[0:-2]) + if symbols: + print("PCIR", symbols, pulsetrain) return { - "protocol": self._name, - "code": code, - "timebase": tb, - }, rep + "id": self._decode_int(symbols[5:10]), + "unit": self._decode_int(symbols[0:5]), + "command": self._decode_command(symbols[11:12]) + }, tb, rep - def encode(self, args): - self._reset() - self._add_symbols(args.code) - self._add_pulses([1, 12]) - self._add_finish() - return self._ookdata, self._timebase, self._repetitions -class REVRitterShutter(RcProtocol): +class REVRitterShutter(RcPulse): ''' Pulse Width Modulation 24 bit, Short pulse: 0, long pulse: 1 @@ -597,7 +597,8 @@ class REVRitterShutter(RcProtocol): } self._footer = [1, 85] self.params = [PARAM_ID] - RcProtocol.__init__(self) + self._class = CLASS_RCSWITCH + RcPulse.__init__(self) def encode(self, params, timebase=None, repetitions=None): symbols = "{:024b}".format(int(params["id"])) @@ -610,6 +611,91 @@ class REVRitterShutter(RcProtocol): "id": int(symbols[0:24], 2), }, tb, rep +class WH2(RcPulse): + ''' + Temperature & humidity sensor WH2, Telldus + Pulse Duration Modulation + Short, middle = 1, long middle = 0 + ''' + def __init__(self): + self._name = "wh2" + self._class = CLASS_RCWEATHER + self._timebase = 500 + self._pattern = "11111111[01]{39}" + self._symbols = { + '1': [1, 2], + '0': [3, 2], + } + self._footer = [2, 49] + RcPulse.__init__(self) + + def decode(self, pulsetrain): + symbols, tb, rep = self._decode_symbols(pulsetrain[0:-2]) + if symbols: + symbols += "0" + res = {} + + res["id"] = "{:02x}".format(int(symbols[12:20], 2)) + T = int(symbols[20:32], 2) + if T >= 1<<11: + T -= 1<<12 + T /= 10.0 + res["T"] = T + + RH = int(symbols[32:40], 2) + if RH != 0xFF: + res["RH"] = RH + + return res, tb, rep + +class WS7000(RcPulse): + ''' + Temperature & humidity sensor LaCrosee/ELV + Pulse Width Modulation + Short = 1, long = 2 + ''' + def __init__(self): + self._name = "ws7000" + self._class = CLASS_RCWEATHER + self._timebase = 400 + self._pattern = "^0{5,}1([01]{4}1){6,}[01]{4,5}$" + self._symbols = { + '1': [1, 2], + '0': [2, 1], + } + self._footer = [2, 49] + RcPulse.__init__(self) + + def decode(self, pulsetrain): + symbols, tb, rep = self._decode_symbols(pulsetrain[0:-2]) + if symbols: + symbols = symbols[symbols.find("000001") + 6:] + n = [] #nibbles + i = 0 + check = 0 + while i <= len(symbols) - 4: + n.append(int(symbols[i:i+4][::-1], 2)) + i += 5 + if i <= len(symbols) - 4: + check ^= n[-1] + if check != 0: + return + + res = {} + res["id"] = (n[0] << 4) | (n[1] & 0x07) + + if n[0] == 1: #WS7000 has subtypes + #WS7000-22/25 + t = n[2] * 0.1 + n[3] * 1 + n[4] * 10 + if (n[1] & 0x8) == 0x8: + t = -t + res["T"] = t + res["unit"] = n[1] & 0x7 + h = n[5] * 0.1 + n[6] * 1 + n[7] * 10 + if h > 0: + res["RH"] = h + return res, tb, rep + protocols = [ Tristate(), ITTristate(), @@ -618,13 +704,13 @@ protocols = [ Hama(), Logilight(), Emylo(), - #PWM2(), Voltcraft(), - #PCPIR(), - #PDM1(), + PCPIR(), FS20(), PilotaCasa(), - REVRitterShutter() + REVRitterShutter(), + WH2(), + WS7000(), ] def get_protocol(name): @@ -633,7 +719,6 @@ def get_protocol(name): return p return None - RXDATARATE = 20.0 #kbit/s class RfmPulseTRX(threading.Thread): def __init__(self, module, rxcb, frequency): @@ -742,7 +827,7 @@ class RcTransceiver(threading.Thread): if params: succ = True if not rep: - res.append({"protocol": p._name, "params": params}) + res.append({"protocol": p._name, "class": p._class, "params": params}) except Exception as e: pass diff --git a/apps/rcpulse.py b/apps/rcpulse.py index 7a44433..5de6c5d 100755 --- a/apps/rcpulse.py +++ b/apps/rcpulse.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env python from raspyrfm import * import sys @@ -50,4 +50,4 @@ while True: if (state == "on"): state = "off" else: - state = "on" \ No newline at end of file + state = "on" diff --git a/apps/rcpulsegw.py b/apps/rcpulsegw.py index eda7d36..d79d1db 100755 --- a/apps/rcpulsegw.py +++ b/apps/rcpulsegw.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env python import socket import threading @@ -27,10 +27,11 @@ def rxcb(dec, train): if len(dec) > 0: payload = {"decode": dec, "raw": train} - print(payload) if payload is not None: + print("RX", payload) + s = json.dumps(payload) + "\n" for client in clients: - client.send(payload) + client.send(s) if not raspyrfm_test(args.module, RFM69): @@ -38,28 +39,32 @@ if not raspyrfm_test(args.module, RFM69): exit() rctrx = rcprotocols.RcTransceiver(args.module, args.frequency, rxcb) - -lock = threading.Lock() +lock = threading.Lock() class clientthread(threading.Thread): def __init__(self, socket): self.__socket = socket threading.Thread.__init__(self) - def send(self, obj): - self.__socket.send(json.dumps(obj)) + def send(self, s): + self.__socket.sendall(s.encode()) def run(self): + buf = "" while True: - chunk = self.__socket.recv(1024) + chunk = self.__socket.recv(32).decode() if len(chunk) == 0: del self.__socket break + lock.acquire() try: - #print(chunk) - lock.acquire() - d = json.loads(chunk) - rctrx.send(d["protocol"], d["params"]) + buf += chunk + while "\n" in buf: + line = buf[:buf.find("\n")] + buf = buf[buf.find("\n") + 1:] + d = json.loads(line) + print("TX", d) + rctrx.send(d["protocol"], d["params"]) except: pass lock.release() @@ -70,4 +75,4 @@ while True: ct = clientthread(client) ct.daemon = True ct.start() - clients.append(ct) \ No newline at end of file + clients.append(ct) diff --git a/raspyrfm/__init__.py b/raspyrfm/__init__.py index d925782..3587a64 100644 --- a/raspyrfm/__init__.py +++ b/raspyrfm/__init__.py @@ -35,7 +35,7 @@ def RaspyRFM(mod, type): """ s = __get_hw_params(mod) - + if s: if type == RFM69: return rfm69.Rfm69(s[0], s[1]) @@ -45,4 +45,4 @@ def RaspyRFM(mod, type): def raspyrfm_test(mod, type): s = __get_hw_params(mod) if s: - return rfm69.Rfm69.test(s[0], s[1]) \ No newline at end of file + return rfm69.Rfm69.test(s[0], s[1])