Commit 2c8a83bc authored by Peter-Bernd Otte's avatar Peter-Bernd Otte

Update python/relais-control/,...

Update python/relais-control/, python/relais-control/, python/relais-control/requirements.txt, python/relais-control/config.yml files
parent 0cb27ef7
import json
import time
from datetime import datetime
import paho.mqtt.client as paho
#import numbers
#import parse
from relais import *
import yaml #pip3 install pyyaml
import os #for os.path.isfile
import logging, argparse
format = "%(asctime)-9s %(levelname)-8s %(message)s"
logging.basicConfig(format=format, datefmt="%H:%M:%S")
logger = logging.getLogger("Actor")
parser = argparse.ArgumentParser(description='Workload distributor for trivial parallelism.')
parser.add_argument("-v", "--verbosity", help="increase output verbosity", default=0, action="count")
parser.add_argument("-c", "--config-file", help="provide filepath for YAML configuration file", default="config.yml", type=str)
parser.add_argument("-b", "--mqtt-broker-host", help="MQTT broker hostname", default="locahlhost")
parser.add_argument("mqtt_client_name", help="MQTT client name. Needs to be unique in the MQTT namespace, eg fsr-ww.", type=str)
args = parser.parse_args()
logger.setLevel(logging.WARNING-(args.verbosity*10 if args.verbosity <=2 else 20) )
#MQTTClientName = "I2CActor"
if os.path.exists(args.config_file):
logger.debug("config file exists")
mlast = {}
def on_connect(client, userdata, flags, rc):
if rc==0:"MQTT connected OK. Return code "+str(rc) )
for x in relaisList:
# client.subscribe("#")
for y in x.defaultTopics: client.subscribe(y)
for y in x.toggleTopics: client.subscribe(y)
for y in x.switchOffTopics: client.subscribe(y)
for y in x.inverseSwitchOnTopics: client.subscribe(y)
logger.debug("MQTT: Subscribed to all topics")
logger.error("Bad connection. Return code="+str(rc))
def on_disconnect(client, userdata, rc):
if rc != 0:
logger.warning("Unexpected MQTT disconnection. Will auto-reconnect")
def on_message(client, userdata, message):
global mlast
t =
t = time.mktime(t.timetuple()) + t.microsecond / 1E6
m = message.payload.decode("utf-8")
logger.debug("received:"+str(m)+" "+message.topic)
# the lambda in loads is for converting the object name into a int, if appropiate
j = json.loads(m, object_hook=lambda d: {int(k) if k.lstrip('-').isdigit() else k: v for k, v in d.items()})
logger.debug("Topic: "+message.topic+" JSON: "+str(j))
if type(j) is not dict: #anpassen, falls es keine Zeitinfo hat
if 'time' in j:
if t-j['time'] > 10: # corrects the time of too much in the future
j['time'] = t
else: j['time'] = t
for x in relaisList:
if message.topic == x.MQTTRelaisTopic:
if message.topic == x.MQTTRelaisTopic+"/set":
mlast[message.topic] = j
client= paho.Client(args.mqtt_client_name)
client.on_connect = on_connect
client.on_disconnect = on_disconnect"connecting to broker: "+args.mqtt_broker_host+". If it fails, check whether the broker is reachable. Check the -b option.")
client.loop_start() #start loop to process received messages in separate thread
logger.debug("MQTT Loop started.")
# test with:
# mosquitto_pub -t homie/fsr-ww/1 -m '{"setv": 0, "time": 1564251546.330417, "type": "FUD14", "v": 1}'
# mosquitto_sub -t 'homie/I2CActor/testraum/testrelais' -v
relaisList = []
relaisList.append(relais(MQTTClient=client, MQTTName="homie/"+args.mqtt_client_name+"/testraum/testrelais",
defaultTopics=["homie/enocean/test/schalter",'homie/enocean/wohnzimmer/bewegungsmelder/bewegungsmelderDeckeGaube', 'homie/enocean/bügelzimmerUG/bewegungsmelder/bewegungsmelderDecke'],
#relaisList.append(relais("testraum/testrelais2", "homie/fsr-ww/2", ["test/schalter2"], 5))
while True:
for x in relaisList:
# x.relaisMsg(json.loads('{"time": 1564578103.410478, "type": "FSR14", "setv": 1.0, "v": 1.0}'))
time.sleep((1-time.time() % 1)/1) #every second, even if the processing before took longer
\ No newline at end of file
import json
import time
import paho.mqtt.client as paho
from enum import Enum # install with pip3 install enum34
class rStates(Enum):
# unknown = 0
waitForFeedback = 0 #no feedback yet, but not too long ago, that it get's noFeedback
confirmed = 1
noFeedback = 2 #if no feedback from relais came within fixed time
switching = 3 #eg if FUD14 is dimmin gup or down and has not yet reached it final value
class rLightDesire(Enum):
stable = 0 #the light brightness should stay as it is
dimmUp = 1
dimmDown = 2
lockedDueToPIR = 3 # in case an enocean PIR reported montion, the relais switch off timeout should be halted
class relais:
def __init__(self, MQTTClient, MQTTName, MQTTRelaisTopic, defaultTopics=None, toggleTopics=None, switchOffTopics=None,
inverseSwitchOnTopics=None, defaultSwitchOffTime=None):
self.MQTTname = MQTTName #string
self.type = "relais"
self.MQTTClient = MQTTClient #object
self.MQTTRelaisTopic = MQTTRelaisTopic #string
self.lockingPIRs = set([]) # Name of PIR locking the on state
self.defaultSwitchOffTime = defaultSwitchOffTime # None or int (time in seconds)
self.state = rStates.waitForFeedback
self.lightdesire = rLightDesire.stable
self.defaultTopics = defaultTopics if defaultTopics is not None else set() # MQTT-Name (includes also type in JSON message)
self.toggleTopics = toggleTopics if toggleTopics is not None else set()
self.switchOffTopics = switchOffTopics if switchOffTopics is not None else set()
self.inverseSwitchOnTopics = inverseSwitchOnTopics if inverseSwitchOnTopics is not None else set()
self.v = None # state, the relais should have
self.timeLastChange = None # last change of v
self.timeLastStateConfirm = None # last time, v has been changed or confirmed while calling setV
self.vFeedback = None # state, reported from relais
self.timeFeedback = None
def publishCurrentStatus(self, t=None, reason=None):
if t is None: t = time.time()
self.MQTTClient.publish(self.MQTTname, json.dumps({"state":, "type": self.type, "lightdesire":,
"v": self.v, "time": t, "reason":reason}, sort_keys=True), qos=1, retain=True)
def relaisMsg(self, j):
print("Feedback-Msg from Relais (",self.MQTTname,"): ",j)
if self.v is None: self.v = j['v']
if self.timeLastChange is None: self.timeLastChange = j['time']
self.vFeedback = j['v']
self.timeFeedback = j['time']
if self.timeLastStateConfirm is None: self.timeLastStateConfirm = time.time()
self.state = rStates.confirmed if self.vFeedback == self.v else rStates.switching
self.publishCurrentStatus(reason="relais feedback")
print("Feedback Ende.")
def setV(self, v, t=None, reason=None):
print("New Value set to:", v, "Old Value:", self.v, "Relais:", self.MQTTname)
if t is None: t = time.time()
if len(self.lockingPIRs) == 0 or v == 0: self.lightdesire = rLightDesire.stable
if self.v != v: # a change of output state
self.v = v
self.timeLastChange = t
self.state = rStates.waitForFeedback
self.MQTTClient.publish(self.MQTTRelaisTopic + "/set", self.v, qos=1,
retain=False) # send command to Relais
self.timeLastStateConfirm = t
self.publishCurrentStatus(t, reason)
print("setV Ende")
def checkAction(self, topic, msg):
for x in self.defaultTopics: # Normally the topics, which switches the relais on
if topic == x:
print("Default Topic:", topic, "msg:", msg)
if msg['type'] in ('TFBHSB55', 'FBH63'): # motion sensors
self.timeLastStateConfirm = time.time()
if msg['v'] > 0:
self.lightdesire = rLightDesire.lockedDueToPIR
self.setV(1, reason="PIR Motion")
if len(self.lockingPIRs) == 0:
self.lightdesire = rLightDesire.stable
self.timeLastStateConfirm = time.time() # to avoid immediate switch off after last PIR
self.publishCurrentStatus(reason="last PIR sees no motion")
print("Relais:", self.MQTTname, "len(self.lockingPIRs)", len(self.lockingPIRs), "self.lockingPIRs",
elif msg['type'] in ('FRW', 'FHF', 'FSM60B', 'FTK',
'FTKE'): # smoke detector, window handle, waterleak detector, magnet sensor, handle bar sensor
if msg['v'] > 0:
self.setV(1, reason="defaultTopics "+msg['type'])
elif msg['type'] == "PTM215": # switches
if msg['v'] > 0: self.setV(1, reason="PTM215 pressed down, switch on") # Switch on, if key pressed down
print("Error: Type not supported for normal actions.")
for x in self.toggleTopics: # topics do a toggle of the relais
if topic == x:
print("Toggle Topic:", topic, "msg:", msg)
if msg['type'] == "PTM215": # switches
if msg['v'] > 0: # on key pressed down
if self.v == 0:
self.setV(1, reason="PTM215 pressed down, toggle") # Toggle
self.setV(0, reason="PTM215 pressed down, toggle")
print("Error: Type not supported for toggle.")
for x in self.switchOffTopics: # topics switch off the relais
if topic == x:
print("Switch Off Topic:", topic, "msg:", msg)
if msg['type'] == "PTM215": # switches
if msg['v'] > 0: self.setV(0, reason="PTM pressed down, switch off") # Switch Off, if key pressed down
print("Error: Type not supported for switch off.")
for x in self.inverseSwitchOnTopics: # topics switch on the relais, but inverted input
if topic == x:
print("inverseSwitchOnTopics:", topic, "msg:", msg)
if msg['type'] in ('FRW', 'FHF', 'FSM60B', 'FTK', 'FTKE'):
if msg['v'] == 0: self.setV(1, reason="inverseSwitchOnTopics "+msg['type']) # Switch on
print("Error: Type not supported for inverseSwitchOnTopics.")
def printCurrentState(self):
def check(self):
if self.lightdesire == rLightDesire.stable and self.state != rStates.noFeedback and self.v is not None:
if (self.defaultSwitchOffTime is not None) and self.v > 0 and self.timeLastStateConfirm + self.defaultSwitchOffTime < time.time(): # test for switch off
print("Time up!")
self.setV(0, reason="time up") # switch off now
self.state = rLightDesire.stable # no dimming etc. any longer
if self.vFeedback is None:
print("No Feedback from Relais, yet: "+self.MQTTname)
t = time.time()
if self.v!=self.vFeedback and t-self.timeLastChange>10 and t-self.timeFeedback > 10: #if feedback state differs too long from desired state
print("No OR wrong Feedback from Relais since last chance since more than 10 seconds.")
self.v = self.vFeedback
self.timeLastChange = self.timeFeedback
self.timeLastStateConfirm = self.timeFeedback
self.state = rStates.noFeedback
self.publishCurrentStatus(reason="no feedback from relais")
# test with:
# mosquitto_pub -t homie/fsr-ww/1 -m '{"setv": 0, "time": 1564251546.330417, "type": "FUD14", "v": 1}'
# mosquitto_sub -t 'homie/I2CActor/testraum/testrelais' -v
\ No newline at end of file
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment