Better timing tolerance

Add weather sensors wh2, wh7000
some python3 issues
fix docu & PC PIR
This commit is contained in:
S. Seegel 2020-12-16 02:32:32 +01:00
parent 3e70559ef2
commit 01ddd7e3cc
12 changed files with 476 additions and 393 deletions

View file

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

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python2.7
#!/usr/bin/env python
import socket
from raspyrfm import *

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<len(sympulses) - 1:
if (sympulses[i] == sympulses[i+1]):
del sympulses[i]
else:
i += 1
p1 = sympulses.pop(0)
p2 = sympulses.pop(0)
f = (1.0 * p1 / p2 - 1) / (1.0 * p1/p2 + 2)
self._minwidth = self._timebase - self._timebase * f
self._maxwidth = self._timebase + self._timebase * f
symset = set()
for k in self._symbols:
symset |= set(self._symbols[k])
symlist = list(symset)
symlist.sort()
q = 0
for i in range(len(symlist) - 1):
if 1.0 * symlist[i] / symlist[i+1] > 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

View file

@ -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"
state = "on"

View file

@ -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)
clients.append(ct)

View file

@ -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])
return rfm69.Rfm69.test(s[0], s[1])