Browse Source

Shelly pro3 added

master
Dirk Alders 1 week ago
parent
commit
fd546ffcc3
3 changed files with 171 additions and 2 deletions
  1. 1
    0
      devices/__init__.py
  2. 102
    1
      devices/base.py
  3. 68
    1
      devices/shelly.py

+ 1
- 0
devices/__init__.py View File

@@ -32,6 +32,7 @@ except ImportError:
32 32
     ROOT_LOGGER_NAME = 'root'
33 33
 
34 34
 from devices.shelly import shelly as shelly_sw1
35
+from devices.shelly import shelly_rpc as shelly_pro3
35 36
 from devices.tradfri import tradfri_light as tradfri_sw
36 37
 from devices.tradfri import tradfri_light as tradfri_sw_br
37 38
 from devices.tradfri import tradfri_light as tradfri_sw_br_ct

+ 102
- 1
devices/base.py View File

@@ -4,7 +4,6 @@
4 4
 from base import mqtt_base
5 5
 from base import videv_base
6 6
 import json
7
-import time
8 7
 
9 8
 
10 9
 def is_json(data):
@@ -118,6 +117,108 @@ class base(mqtt_base):
118 117
             self.logger.error("Unknown tx toptic. Set TX_TOPIC of class to a known value")
119 118
 
120 119
 
120
+class base_rpc(mqtt_base):
121
+    SRC_RESPONSE = "/response"
122
+    SRC_NULL = "/null"
123
+    #
124
+    EVENTS_TOPIC = "/events/rpc"
125
+    TX_TOPIC = "/rpc"
126
+    RESPONSE_TOPIC = SRC_RESPONSE + "/rpc"
127
+    NULL_TOPIC = SRC_NULL + "/rpc"
128
+    #
129
+    RPC_ID_GET_STATUS = 1
130
+    RPC_ID_SET = 1734
131
+    #
132
+
133
+    def __init__(self, mqtt_client, topic):
134
+        super().__init__(mqtt_client, topic, default_values=dict.fromkeys(self.RX_KEYS))
135
+        # data storage
136
+        self.__cfg_by_mid__ = None
137
+        # initialisations
138
+        mqtt_client.add_callback(topic=self.topic, callback=self.receive_callback)
139
+        mqtt_client.add_callback(topic=self.topic+"/#", callback=self.receive_callback)
140
+        #
141
+        self.add_callback(None, None, self.__state_logging__, on_change_only=False)
142
+        #
143
+        self.rpc_get_status()
144
+
145
+    def receive_callback(self, client, userdata, message):
146
+        data = json.loads(message.payload)
147
+        #
148
+        if message.topic == self.topic + self.EVENTS_TOPIC:
149
+            self.events(data)
150
+        elif message.topic == self.topic + self.RESPONSE_TOPIC:
151
+            self.response(data)
152
+        elif message.topic == self.topic + self.NULL_TOPIC or message.topic == self.topic + self.TX_TOPIC or message.topic == self.topic + "/online":
153
+            pass    # Ignore response
154
+        else:
155
+            self.logger.warning("Unexpected message received: %s::%s", message.topic, json.dumps(data, sort_keys=True, indent=4))
156
+
157
+    def events(self, data):
158
+        for rx_key in data["params"]:
159
+            if rx_key == "events":
160
+                for evt in data["params"]["events"]:
161
+                    key = evt["component"]
162
+                    event = evt["event"]
163
+                    if key in self.RX_KEYS:
164
+                        if event == "btn_down":
165
+                            self.set(key, True)
166
+                        elif event == "btn_up":
167
+                            self.set(key, False)
168
+                        else:
169
+                            key = key + ":" + event
170
+                            if key in self.RX_KEYS:
171
+                                self.set(key, True)
172
+                            else:
173
+                                self.logger.warning("Unexpected event with data=%s", json.dumps(data, sort_keys=True, indent=4))
174
+            elif rx_key in self.RX_KEYS:
175
+                state = data["params"][rx_key].get("output")
176
+                if state is not None:
177
+                    self.set(rx_key, state)
178
+
179
+    def response(self, data):
180
+        try:
181
+            rpc_id = data.get("id")
182
+        except AttributeError:
183
+            rpc_id = None
184
+        try:
185
+            rpc_method = data.get("method")
186
+        except AttributeError:
187
+            rpc_method = None
188
+        if rpc_id == self.RPC_ID_GET_STATUS:
189
+            #
190
+            # Shelly.GetStatus
191
+            #
192
+            for rx_key in data.get("result", []):
193
+                if rx_key in self.RX_KEYS:
194
+                    key_data = data["result"][rx_key]
195
+                    state = key_data.get("output", key_data.get("state"))
196
+                    if state is not None:
197
+                        self.set(rx_key, state)
198
+        else:
199
+            self.logger.warning("Unexpected response with data=%s", json.dumps(data, sort_keys=True, indent=4))
200
+
201
+    def rpc_tx(self, **kwargs):
202
+        if not "id" in kwargs:
203
+            raise AttributeError("'id' is missing in keyword arguments")
204
+        self.mqtt_client.send(self.topic + self.TX_TOPIC, json.dumps(kwargs))
205
+
206
+    def rpc_get_status(self):
207
+        self.rpc_tx(
208
+            id=self.RPC_ID_GET_STATUS,
209
+            src=self.topic + self.SRC_RESPONSE,
210
+            method="Shelly.GetStatus"
211
+        )
212
+
213
+    def rpc_switch_set(self, key, state: bool):
214
+        self.rpc_tx(
215
+            id=self.RPC_ID_SET,
216
+            src=self.topic + self.SRC_NULL,
217
+            method="Switch.Set",
218
+            params={"id": int(key[-1]), "on": state}
219
+        )
220
+
221
+
121 222
 class base_output(base):
122 223
     def __init__(self, mqtt_client, topic):
123 224
         super().__init__(mqtt_client, topic)

+ 68
- 1
devices/shelly.py View File

@@ -2,7 +2,7 @@
2 2
 # -*- coding: utf-8 -*-
3 3
 #
4 4
 from devices.base import base_output
5
-import logging
5
+from devices.base import base_rpc
6 6
 import task
7 7
 
8 8
 
@@ -169,3 +169,70 @@ class shelly(base_output):
169 169
                 self.set_output_0(False)
170 170
             if self.output_1:
171 171
                 self.set_output_1(False)
172
+
173
+
174
+class shelly_rpc(base_rpc):
175
+    KEY_OUTPUT_0 = "switch:0"
176
+    KEY_OUTPUT_1 = "switch:1"
177
+    KEY_OUTPUT_2 = "switch:2"
178
+    KEY_INPUT_0 = "input:0"
179
+    KEY_INPUT_1 = "input:1"
180
+    KEY_INPUT_2 = "input:2"
181
+    KEY_LONGPUSH_0 = "input:0:long_push"
182
+    KEY_LONGPUSH_1 = "input:1:long_push"
183
+    KEY_LONGPUSH_2 = "input:2:long_push"
184
+    KEY_SINGLEPUSH_0 = "input:0:single_push"
185
+    KEY_SINGLEPUSH_1 = "input:1:single_push"
186
+    KEY_SINGLEPUSH_2 = "input:2:single_push"
187
+    KEY_DOUBLEPUSH_0 = "input:0:double_push"
188
+    KEY_DOUBLEPUSH_1 = "input:1:double_push"
189
+    KEY_DOUBLEPUSH_2 = "input:2:double_push"
190
+    KEY_TRIPLEPUSH_0 = "input:0:triple_push"
191
+    KEY_TRIPLEPUSH_1 = "input:1:triple_push"
192
+    KEY_TRIPLEPUSH_2 = "input:2:triple_push"
193
+
194
+    RX_KEYS = [KEY_OUTPUT_0, KEY_OUTPUT_1, KEY_OUTPUT_2, KEY_INPUT_0, KEY_INPUT_1, KEY_INPUT_2,
195
+               KEY_LONGPUSH_0, KEY_LONGPUSH_1, KEY_LONGPUSH_2, KEY_SINGLEPUSH_0, KEY_SINGLEPUSH_1, KEY_SINGLEPUSH_2,
196
+               KEY_DOUBLEPUSH_0, KEY_DOUBLEPUSH_1, KEY_DOUBLEPUSH_2, KEY_TRIPLEPUSH_0, KEY_TRIPLEPUSH_1, KEY_TRIPLEPUSH_2]
197
+
198
+    def __state_logging__(self, inst, key, data):
199
+        if key in [self.KEY_OUTPUT_0, self.KEY_OUTPUT_1, self.KEY_OUTPUT_2]:
200
+            self.logger.info("State change of '%s' to '%s'", key, repr(data))
201
+        elif key in [self.KEY_INPUT_0, self.KEY_INPUT_1, self.KEY_INPUT_2]:
202
+            self.logger.info("Input action '%s' with '%s'", key, repr(data))
203
+        elif key in [self.KEY_LONGPUSH_0, self.KEY_LONGPUSH_1, self.KEY_LONGPUSH_2,
204
+                     self.KEY_SINGLEPUSH_0, self.KEY_SINGLEPUSH_1, self.KEY_SINGLEPUSH_2,
205
+                     self.KEY_DOUBLEPUSH_0, self.KEY_DOUBLEPUSH_1, self.KEY_DOUBLEPUSH_2,
206
+                     self.KEY_TRIPLEPUSH_0, self.KEY_TRIPLEPUSH_1, self.KEY_TRIPLEPUSH_2]:
207
+            self.logger.info("Input action '%s'", key)
208
+
209
+    def set_output_0(self, state):
210
+        """state: [True, False]"""
211
+        self.rpc_switch_set(self.KEY_OUTPUT_0, state)
212
+
213
+    def set_output_0_mcb(self, device, key, data):
214
+        self.set_output_0(data)
215
+
216
+    def toggle_output_0_mcb(self, device, key, data):
217
+        self.set_output_0(not self.get(self.KEY_OUTPUT_0))
218
+
219
+    def set_output_1(self, state):
220
+        """state: [True, False]"""
221
+        self.rpc_switch_set(self.KEY_OUTPUT_1, state)
222
+
223
+    def set_output_1_mcb(self, device, key, data):
224
+        self.set_output_1(data)
225
+
226
+    def toggle_output_1_mcb(self, device, key, data):
227
+        print(self.get(self.KEY_OUTPUT_1))
228
+        self.set_output_1(not self.get(self.KEY_OUTPUT_1))
229
+
230
+    def set_output_2(self, state):
231
+        """state: [True, False]"""
232
+        self.rpc_switch_set(self.KEY_OUTPUT_2, state)
233
+
234
+    def set_output_2_mcb(self, device, key, data):
235
+        self.set_output_2(data)
236
+
237
+    def toggle_output_2_mcb(self, device, key, data):
238
+        self.set_output_2(not self.get(self.KEY_OUTPUT_2))

Loading…
Cancel
Save