relais.py 11.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
#!/usr/bin/python3

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:
23 24
    def __init__(self, MQTTClient, MQTTName, MQTTRelaisTopic, defaultTopics=None, toggleTopics=None, dimmTopics=None,
                 switchOffTopics=None,
25
                 inverseSwitchOnTopics=None, prolongateStateTopics=None, defaultSwitchOffTime=None, debug=False, relaisType="relais", luxthreshold=100):
26
        self.MQTTname = MQTTName     #string
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
27
        self.type = relaisType # possible: "relais" and "dimmer"
28
        self.debug = debug
29
        self.luxthreshold = luxthreshold #below this threshold in lux the PIR switches on 
30 31 32 33 34 35 36 37 38 39 40
        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()
41
        self.dimmTopics = dimmTopics if dimmTopics is not None else set()
42 43
        self.switchOffTopics = switchOffTopics if switchOffTopics is not None else set()
        self.inverseSwitchOnTopics = inverseSwitchOnTopics if inverseSwitchOnTopics is not None else set()
44
        self.prolongateStateTopics = prolongateStateTopics if prolongateStateTopics is not None else set()
45 46 47 48 49 50 51 52 53 54 55 56 57 58

        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": self.state.name, "type": self.type, "lightdesire": self.lightdesire.name,
                                "v": self.v, "time": t, "reason":reason}, sort_keys=True), qos=1, retain=True)

    def relaisMsg(self, j):
59
        if self.debug: print(self.MQTTname,"Feedback-Msg from Relais: ",j)
60 61 62 63 64 65 66
        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")
67
        if self.debug: print(self.MQTTname,"Feedback Ende.")
68 69 70
        self.printCurrentState()

    def setV(self, v, t=None, reason=None):
71
        if self.debug: print(self.MQTTname, "New Value set to:", v, "Old Value:", self.v)
72 73 74 75 76 77 78 79 80 81
        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)
82
        if self.debug: print(self.MQTTname, "setV Ende")
83 84 85 86

    def checkAction(self, topic, msg):
        for x in self.defaultTopics:  # Normally the topics, which switches the relais on
            if topic == x:
87
                if self.debug: print(self.MQTTname,"Default Topic:", topic, "msg:", msg)
88 89
                if msg['type'] in ('TFBHSB55', 'FBH63'):  # motion sensors
                    self.timeLastStateConfirm = time.time()
90
                    if self.debug: print(self.MQTTname, "Check Motion Sensors")
91 92
                    lux = msg['lux'] if 'lux' in msg else 100
                    if msg['v'] > 0 and self.luxthreshold > lux:
93 94
                        self.lockingPIRs.add(topic)
                        self.lightdesire = rLightDesire.lockedDueToPIR
95
                        self.setV(1, reason="PIR motion")
96
                    else:
97
                        self.lockingPIRs.discard(topic)
98 99 100 101
                        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")
102
                    if self.debug: print(self.MQTTname, "len(self.lockingPIRs)", len(self.lockingPIRs), "self.lockingPIRs",
103
                          self.lockingPIRs)
104
                elif msg['type'] in ('FRW', 'FHF', 'FSM60B', 'FTK', 'FTKE'):  # smoke, window handle, waterleak, magnet, handle bar
105 106
                    if msg['v'] > 0:
                        self.setV(1, reason="defaultTopics "+msg['type'])
107 108
                    #else: #if these lines are active, eg the default status msg of smoke sensor will switch off a light
                    #    self.setV(0, reason="defaultTopics "+msg['type'])
109 110 111
                elif msg['type'] == "PTM215":  # switches
                    if msg['v'] > 0: self.setV(1, reason="PTM215 pressed down, switch on")  # Switch on, if key pressed down
                else:
112 113 114 115
                    print(self.MQTTname, "Error: Type not supported for defaultTopics.")
        for x in self.prolongateStateTopics:  # topics will only prolong the relais state
            if topic == x:
                if self.debug: print(self.MQTTname,"Prolong Topic:", topic, "msg:", msg)
116 117 118 119
                if self.v is None:
                  if self.debug: print(self.MQTTname,"no prolong operation possible, because self.v is not known yet")
                else:
                  if msg['type'] in ('TFBHSB55', 'FBH63'):  # motion sensors
120 121 122 123 124 125 126 127
                    if self.v > 0:
                        self.timeLastStateConfirm = time.time() # to avoid immediate switch off after last PIR
                    if msg['v'] > 0:
                        if self.v > 0: #difference to default behaviour
                            self.lockingPIRs.add(topic)
                            self.lightdesire = rLightDesire.lockedDueToPIR
                            self.publishCurrentStatus(reason="PIR motion prolong")
                    else:
128
                        self.lockingPIRs.discard(topic)
129 130 131 132 133
                        if self.v > 0 and len(self.lockingPIRs) == 0:
                            self.lightdesire = rLightDesire.stable
                            self.publishCurrentStatus(reason="last PIR sees no motion")
                    if self.debug: print(self.MQTTname, "Prolong State Topic:", "len(self.lockingPIRs)", len(self.lockingPIRs),
                          "self.lockingPIRs", self.lockingPIRs)
134
                  else:
135 136
                    print(self.MQTTname, "Error: Type not supported for prolongTopics.")

137 138
        for x in self.toggleTopics:  # topics do a toggle of the relais
            if topic == x:
139
                if self.debug: print(self.MQTTname, "Toggle Topic:", topic, "msg:", msg)
140 141 142 143 144 145 146
                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
                        else:
                            self.setV(0, reason="PTM215 pressed down, toggle")
                else:
147
                    print(self.MQTTname, "Error: Type not supported for toggle.")
148 149 150 151 152 153 154 155 156 157 158
        for x in self.dimmTopics:  # topics do dimming, not yet implemented. currently like toggle
            if topic == x:
                if self.debug: print(self.MQTTname, "Dimm 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, dimm up")  # Toggle
                        else:
                            self.setV(0, reason="PTM215 pressed down, dimm down")
                else:
                    print(self.MQTTname, "Error: Type not supported for dimm.")
159 160
        for x in self.switchOffTopics:  # topics switch off the relais
            if topic == x:
161
                if self.debug: print(self.MQTTname, "Switch Off Topic:", topic, "msg:", msg)
162 163 164
                if msg['type'] == "PTM215":  # switches
                    if msg['v'] > 0: self.setV(0, reason="PTM pressed down, switch off")  # Switch Off, if key pressed down
                else:
165
                    print(self.MQTTname, "Error: Type not supported for switch off.")
166 167
        for x in self.inverseSwitchOnTopics:  # topics switch on the relais, but inverted input
            if topic == x:
168
                if self.debug: print(self.MQTTname, "inverseSwitchOnTopics:", topic, "msg:", msg)
169 170 171
                if msg['type'] in ('FRW', 'FHF', 'FSM60B', 'FTK', 'FTKE'):
                    if msg['v'] == 0: self.setV(1, reason="inverseSwitchOnTopics "+msg['type'])  # Switch on
                else:
172
                    print(self.MQTTname, "Error: Type not supported for inverseSwitchOnTopics.")
173 174

    def printCurrentState(self):
175
        if self.debug: print(self.MQTTname, "Relais",self.MQTTname,"v:",self.v,self.state,self.lightdesire,"lockingPIRs:",self.lockingPIRs)
176 177 178 179

    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
180
                if self.debug: print(self.MQTTname, "Time up!")
181 182 183
                self.setV(0, reason="time up")  # switch off now
                self.state = rLightDesire.stable  # no dimming etc. any longer
        if self.vFeedback is None:
184
            if self.debug: print(self.MQTTname, "No Feedback from Relais yet.")
185 186 187
        else:
            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
188
                print(self.MQTTname, "No OR wrong Feedback from Relais since last chance since more than 10 seconds.")
189 190 191 192 193
                self.v = self.vFeedback
                self.timeLastChange = self.timeFeedback
                self.timeLastStateConfirm = self.timeFeedback
                self.state = rStates.noFeedback
                self.printCurrentState()
194
                self.publishCurrentStatus(reason="no or wrong feedback from relais")