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

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

71 72 73 74 75
        if self.vFeedback > self.maxBrightness:
            if self.debug: print(self.MQTTname, "Feedback brightness above maxBrightness. This will be reduced to", self.maxBrightness)
            self.setV(self.maxBrightness, reason="Feedback above maxBrightness")
        if self.debug: print(self.MQTTname,"Feedback Ende.")

76
    def setV(self, v, t=None, reason=None):
77
        if self.debug: print(self.MQTTname, "New Value set to:", v, "Old Value:", self.v)
78 79 80 81
        if v > self.maxBrightness:
            if self.debug: print(self.MQTTname, "While trying to set brightness to:", v, ", maxBrightness limited this to", self.maxBrightness)
            v = self.maxBrightness
            reason = "maxBrightness reached" if reason is None else reason+" and maxBrightness reached"
82 83 84 85 86 87 88 89 90 91
        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)
92
        if self.debug: print(self.MQTTname, "setV Ende")
93 94 95 96

    def checkAction(self, topic, msg):
        for x in self.defaultTopics:  # Normally the topics, which switches the relais on
            if topic == x:
97
                if self.debug: print(self.MQTTname,"Default Topic:", topic, "msg:", msg)
98 99
                if msg['type'] in ('TFBHSB55', 'FBH63'):  # motion sensors
                    self.timeLastStateConfirm = time.time()
100
                    if self.debug: print(self.MQTTname, "Check Motion Sensors")
101
                    lux = msg['lux'] if 'lux' in msg else 100
102
                    if msg['v'] > 0 and self.luxThreshold > lux:
103 104
                        self.lockingPIRs.add(topic)
                        self.lightdesire = rLightDesire.lockedDueToPIR
105
                        self.setV(self.maxBrightness, reason="PIR motion")
106
                    else:
107
                        self.lockingPIRs.discard(topic)
108 109 110 111
                        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")
112
                    if self.debug: print(self.MQTTname, "len(self.lockingPIRs)", len(self.lockingPIRs), "self.lockingPIRs",
113
                          self.lockingPIRs)
114
                elif msg['type'] in ('FRW', 'FHF', 'FSM60B', 'FTK', 'FTKE'):  # smoke, window handle, waterleak, magnet, handle bar
115
                    if msg['v'] > 0:
116
                        self.setV(self.maxBrightness, reason="defaultTopics "+msg['type'])
117 118
                    #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'])
119
                elif msg['type'] == "PTM215":  # switches
120
                    if msg['v'] > 0: self.setV(self.maxBrightness, reason="PTM215 pressed down, switch on")  # Switch on, if key pressed down
121
                else:
122 123 124 125
                    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)
126 127 128 129
                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
130 131 132 133 134 135 136 137
                    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:
138
                        self.lockingPIRs.discard(topic)
139 140 141 142 143
                        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)
144
                  else:
145 146
                    print(self.MQTTname, "Error: Type not supported for prolongTopics.")

147 148
        for x in self.toggleTopics:  # topics do a toggle of the relais
            if topic == x:
149
                if self.debug: print(self.MQTTname, "Toggle Topic:", topic, "msg:", msg)
150 151 152
                if msg['type'] == "PTM215":  # switches
                    if msg['v'] > 0:  # on key pressed down
                        if self.v == 0:
153
                            self.setV(self.maxBrightness, reason="PTM215 pressed down, toggle")  # Toggle
154 155 156
                        else:
                            self.setV(0, reason="PTM215 pressed down, toggle")
                else:
157
                    print(self.MQTTname, "Error: Type not supported for toggle.")
158 159 160 161 162 163
        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:
164
                            self.setV(self.maxBrightness, reason="PTM215 pressed down, dimm up")  # Toggle
165 166 167 168
                        else:
                            self.setV(0, reason="PTM215 pressed down, dimm down")
                else:
                    print(self.MQTTname, "Error: Type not supported for dimm.")
169 170
        for x in self.switchOffTopics:  # topics switch off the relais
            if topic == x:
171
                if self.debug: print(self.MQTTname, "Switch Off Topic:", topic, "msg:", msg)
172 173 174
                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:
175
                    print(self.MQTTname, "Error: Type not supported for switch off.")
176 177
        for x in self.inverseSwitchOnTopics:  # topics switch on the relais, but inverted input
            if topic == x:
178
                if self.debug: print(self.MQTTname, "inverseSwitchOnTopics:", topic, "msg:", msg)
179
                if msg['type'] in ('FRW', 'FHF', 'FSM60B', 'FTK', 'FTKE'):
180
                    if msg['v'] == 0: self.setV(self.maxBrightness, reason="inverseSwitchOnTopics "+msg['type'])  # Switch on
181
                else:
182
                    print(self.MQTTname, "Error: Type not supported for inverseSwitchOnTopics.")
183 184

    def printCurrentState(self):
185
        if self.debug: print(self.MQTTname, "Relais",self.MQTTname,"v:",self.v,self.state,self.lightdesire,"lockingPIRs:",self.lockingPIRs)
186 187 188 189

    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
190
                if self.debug: print(self.MQTTname, "Time up!")
191 192 193
                self.setV(0, reason="time up")  # switch off now
                self.state = rLightDesire.stable  # no dimming etc. any longer
        if self.vFeedback is None:
194
            if self.debug: print(self.MQTTname, "No Feedback from Relais yet.")
195 196 197
        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
198
                print(self.MQTTname, "No OR wrong Feedback from Relais since last chance since more than 10 seconds.")
199 200 201 202 203
                self.v = self.vFeedback
                self.timeLastChange = self.timeFeedback
                self.timeLastStateConfirm = self.timeFeedback
                self.state = rStates.noFeedback
                self.printCurrentState()
204
                self.publishCurrentStatus(reason="no or wrong feedback from relais")