relais-control.py 8.57 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
#!/usr/bin/python3

import json
import time
from datetime import datetime
import paho.mqtt.client as paho
from relais import *
import yaml #pip3 install pyyaml
import os #for os.path.isfile
import logging, argparse
11
import sys
12
import traceback
13

14
logging.basicConfig(format="%(asctime)-15s %(levelname)-8s  %(message)s")
15 16 17 18 19
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)
20
parser.add_argument("-b", "--mqtt-broker-host", help="MQTT broker hostname", default="localhost")
21 22 23 24 25 26 27 28 29 30 31 32 33
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) )

mlast = {}

def on_connect(client, userdata, flags, rc):
  if rc==0:
    logger.info("MQTT connected OK. Return code "+str(rc) )
    for x in relaisList:
#        client.subscribe("#")
        client.subscribe(x.MQTTRelaisTopic)
        for y in x.defaultTopics: client.subscribe(y)
34
        logger.debug("MQTT: "+x.MQTTname+" defaultTopics subscribed")
35 36 37 38 39 40
        for y in x.toggleTopics:
          client.subscribe(y)
          logger.debug("MQTT: "+x.MQTTname+" toggleTopic: "+y)
        for y in x.dimmTopics:
          client.subscribe(y)
          logger.debug("MQTT: "+x.MQTTname+" dimmTopics: "+y)
41
        logger.debug("MQTT: "+x.MQTTname+" toggleTopics subscribed")
42
        for y in x.switchOffTopics: client.subscribe(y)
43
        logger.debug("MQTT: "+x.MQTTname+" switchOffTopics subscribed")
44
        for y in x.inverseSwitchOnTopics: client.subscribe(y)
45
        logger.debug("MQTT: "+x.MQTTname+" inverseSwitchOnTopics subscribed")
46 47 48 49 50
        if isinstance(x.prolongateStateTopics, list):
          for y in x.prolongateStateTopics:
            logger.debug("MQTT: "+x.MQTTname+" x.prolongateStateTopics: "+y)
            client.subscribe(y)
            logger.debug("MQTT: "+x.MQTTname+" x.prolongateStateTopics: "+y)
51
        client.subscribe(x.MQTTname+"/set")
52 53
        logger.debug("MQTT: "+x.MQTTname+": Topic /set subscribed")
    logger.info("MQTT: Success, subscribed to all topics")
54 55 56 57 58 59 60 61
  else:
    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):
62
  try:
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
    global mlast
    t = datetime.now()
    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
      j={'v':j,'time':t}
    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:
        x.relaisMsg(j)
      x.checkAction(message.topic,j)
83
      if message.topic == x.MQTTname+"/set":
84
        x.setV(j['v'],t=j['time'],reason="/set cmd called")
85 86

    mlast[message.topic] = j
87 88 89
  except:
        traceback.print_exc()
        quit(0)
90 91 92

client= paho.Client(args.mqtt_client_name)

93 94 95 96 97 98 99
# 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 = []
debug = True if args.verbosity>1 else False

100 101 102 103 104 105 106 107 108
ConfigFile = args.config_file
if not os.path.exists(ConfigFile):
  ConfigFile = "default.yml"
if not os.path.exists(ConfigFile):
  logger.error("Config file "+args.config_file+" and "+ConfigFile+" not found. A config file is required.")
  sys.exit(2)

logger.info("Reading config file {}".format(ConfigFile) )
with open(ConfigFile, 'r') as ymlfile:
109 110
    cfg = yaml.full_load(ymlfile) # see https://github.com/yaml/pyyaml/wiki/PyYAML-yaml.load(input)-Deprecation

111
for section in cfg:
112 113 114
    MQTTName="homie/"+args.mqtt_client_name+"/"+section
    logger.info("Adding Relais with MQTT topic: "+MQTTName)
    logger.info("Configuration: "+str(cfg[section]))
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
115 116 117 118
    relaisType = "relais"
    if "type" in cfg[section]: 
      if cfg[section]["type"] == "dimmer":
        relaisType = "dimmer"
119 120 121 122 123 124 125
    if "MQTTRelaisTopic" not in cfg[section]:
      logger.error("MQTTRelaisTopic property is missing. Will not be added.")
      continue
    for x in relaisList:
      if x.MQTTRelaisTopic == cfg[section]["MQTTRelaisTopic"]:
        logger.error("MQTTRelaisTopic: "+str(cfg[section]["MQTTRelaisTopic"])+" does already exist. Will not be added.")
        continue
126 127 128 129 130
    luxThreshold = 100
    if "luxThreshold" in cfg[section]:
      luxThreshold = cfg[section]["luxThreshold"]
      if type(luxThreshold) is not int: luxThreshold = 100
      logger.debug("luxThreshold used: "+str(luxThreshold))
131 132 133 134 135 136 137 138 139
    maxBrightness=1.0
    if "maxBrightness" in cfg[section]:
      maxBrightness = cfg[section]["maxBrightness"]
      if type(maxBrightness) not in (float, int): 
        logger.warning("maxBrightness "+str(maxBrightness)+" not a number. Default value 1.0 will be used.")
        maxBrightness=1
      if maxBrightness>1 or maxBrightness<0: 
        logger.warning("maxBrightness "+str(maxBrightness)+" not in range 0..1. Default value 1.0 will be used.")
        maxBrightness=1
140

141 142 143 144 145 146
    defaultTopics = cfg[section]["defaultTopics"] if "defaultTopics" in cfg[section] and isinstance(cfg[section]["defaultTopics"],(list, str)) else []
    toggleTopics = cfg[section]["toggleTopics"] if "toggleTopics" in cfg[section] and isinstance(cfg[section]["toggleTopics"],(list, str)) else []
    dimmTopics = cfg[section]["dimmTopics"] if "dimmTopics" in cfg[section] and isinstance(cfg[section]["dimmTopics"],(list, str)) else []
    switchOffTopics = cfg[section]["switchOffTopics"] if "switchOffTopics" in cfg[section] and isinstance(cfg[section]["switchOffTopics"],(list, str)) else []
    inverseSwitchOnTopics = cfg[section]["inverseSwitchOnTopics"] if "inverseSwitchOnTopics" in cfg[section] and isinstance(cfg[section]["inverseSwitchOnTopics"],(list, str)) else []
    prolongateStateTopics = cfg[section]["prolongateStateTopics"] if "prolongateStateTopics" in cfg[section] and isinstance(cfg[section]["prolongateStateTopics"],(list,str)) else []
147 148 149 150

#    print(type(toggleTopics))
    if type(defaultTopics) is not list: defaultTopics = [defaultTopics]
    if type(toggleTopics) is not list: toggleTopics = [toggleTopics]
151 152 153
    if type(dimmTopics) is not list: dimmTopics = [dimmTopics]
    if type(switchOffTopics) is not list: switchOffTopics = [switchOffTopics]
    if type(inverseSwitchOnTopics) is not list: inverseSwitchOnTopics = [inverseSwitchOnTopics]
154
    if type(prolongateStateTopics) is not list: prolongateStateTopics = [prolongateStateTopics]
155

156
    defaultSwitchOffTime = cfg[section]["defaultSwitchOffTime"] if "defaultSwitchOffTime" in cfg[section] and isinstance(cfg[section]["defaultSwitchOffTime"],(int,float,str)) else None
157 158 159
    debugItem = cfg[section]["debug"] if "debug" in cfg[section] else debug

    relaisList.append(relais(MQTTClient=client, MQTTName=MQTTName,
160 161
	MQTTRelaisTopic=cfg[section]["MQTTRelaisTopic"], toggleTopics=toggleTopics, dimmTopics=dimmTopics,
        switchOffTopics=switchOffTopics,
162
	defaultTopics=defaultTopics, inverseSwitchOnTopics=inverseSwitchOnTopics,
163
        prolongateStateTopics=prolongateStateTopics,
164 165
	defaultSwitchOffTime=defaultSwitchOffTime, debug=debugItem, relaisType=relaisType, luxThreshold=luxThreshold,
    maxBrightness=maxBrightness))
166 167 168
    logger.info("Adding successfully.")


169 170 171
client.on_message=on_message
client.on_connect = on_connect
client.on_disconnect = on_disconnect
172
client.enable_logger(logger) #info: https://www.eclipse.org/paho/clients/python/docs/#callbacks
173 174 175 176 177
logger.info("connecting to broker: "+args.mqtt_broker_host+". If it fails, check whether the broker is reachable. Check the -b option.")
client.connect(args.mqtt_broker_host)
client.loop_start() #start loop to process received messages in separate thread
logger.debug("MQTT Loop started.")

178
sleepTime = 1 if args.verbosity>0 else 0.025
179 180 181
while True:
  for x in relaisList:
    x.check()
182
  time.sleep((1-time.time() % 1) * sleepTime) #every second, even if the processing before took longer
183 184

client.disconnect()
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
185
client.loop_stop()