d0b26b3ba6ace0291b4639af9f7cf09fa18de9ba
[mqtt-noolite.git] / nmd / nl_mqtt.py
1 import time
2 import json
3 import logging
4 import asyncio
5
6 from hbmqtt.client import MQTTClient
7 from hbmqtt.mqtt.constants import QOS_0, QOS_1, QOS_2
8
9 from .nl_serial import NooliteSerial
10 from .utils import Singleton
11
12 from uuid import uuid1
13 from socket import gethostname
14
15 logger = logging.getLogger(__name__)
16
17 client_id = gethostname()+'-'+str(uuid1())
18
19 INPUT_TOPIC = '%s/send'
20 OUTPUT_TOPIC = '%s/receive'
21
22 class MqttDriver(metaclass=Singleton):
23     def __init__(self, mtrf_tty_name, loop, mqtt_uri='mqtt://127.0.0.1/', mqtt_topic='noolite', commands_delay=0.1):
24         self.mqtt_client = MQTTClient(client_id=client_id,config={'auto_reconnect': True})
25         self.mqtt_uri = mqtt_uri
26         self.commands_delay = commands_delay
27         self.noolite_serial = NooliteSerial(loop=loop, tty_name=mtrf_tty_name,
28                                             input_command_callback_method=self.input_serial_data)
29         self.commands_to_send_queue = asyncio.Queue()
30         self.read_topic = INPUT_TOPIC % mqtt_topic
31         self.subscriptions = [
32             ( self.read_topic+'/#', QOS_0),
33         ]
34         self.write_topic = OUTPUT_TOPIC % mqtt_topic
35         loop.create_task(self.send_command_to_noolite())
36
37     async def run(self):
38         await self.mqtt_client.connect(self.mqtt_uri)
39         await self.mqtt_client.subscribe(self.subscriptions)
40
41         while True:
42             logger.info('Waiting messages from mqtt...')
43             message = await self.mqtt_client.deliver_message()
44
45             topic = message.topic
46             payload = message.publish_packet.payload.data
47
48             logger.info('In message: {}\n{}'.format(topic, payload))
49
50             if topic.startswith(self.read_topic):
51               subtopic = topic[len(self.read_topic)+1:]
52               print(subtopic)
53
54               if subtopic == '':
55                 try:
56                   payload = json.loads(payload.decode())
57                 except Exception as e:
58                   logger.exception(e)
59                   continue
60                 await self.commands_to_send_queue.put(payload)
61               else:
62                 try:
63                   address = subtopic.split('/')
64                   if len(address)==2:
65                     channel = int(address[0])
66                     command = address[1]
67                     id = None
68                   elif len(address)==3:
69                     channel = int(address[0])
70                     command = address[2]
71                     id = address[1]
72                   elif len(address)==1:
73                     command = address[0]
74                     channel = None
75                     id = None
76                   command = command.lower()  
77                   print("%s: %s (%s)" % (command,channel,id))
78                   if command == "on":
79                     if id == '.':
80                       mtrf_command = { "mode": 2, "ch": channel, "cmd": 2 }
81                     elif id:
82                       mtrf_command = { "mode": 2, "ch": channel, "cmd": 2, "id0": int(id[0:2],16), "id1": int(id[2:4],16), "id2": int(id[4:6],16), "id3": int(id[6:8],16), "ctr": 8 }
83                     else:
84                       mtrf_command = { "mode": 0, "ch": channel, "cmd": 2 }
85                   elif command == "off":
86                     if id == '.':
87                       mtrf_command = { "mode": 2, "ch": channel, "cmd": 0 }
88                     elif id:
89                       mtrf_command = { "mode": 2, "ch": channel, "cmd": 0, "id0": int(id[0:2],16), "id1": int(id[2:4],16), "id2": int(id[4:6],16), "id3": int(id[6:8],16), "ctr": 8 }
90                     else:
91                       mtrf_command = { "mode": 0, "ch": channel, "cmd": 0 }
92                   elif command == "brightness":
93                     brightness = int(payload)
94                     if id == '.':
95                       mtrf_command = { "mode": 2, "ch": channel, "cmd": 6, "d0": brightness }
96                     elif id:
97                       mtrf_command = { "mode": 2, "ch": channel, "cmd": 6, "d0": brightness, "id0": int(id[0:2],16), "id1": int(id[2:4],16), "id2": int(id[4:6],16), "id3": int(id[6:8],16), "ctr": 8 }
98                     else:
99                       mtrf_command = { "mode": 0, "ch": channel, "cmd": 6, "d0": brightness }
100                   elif command == "state":
101                     if id == '.':
102                       mtrf_command = { "mode": 2, "ch": channel, "cmd": 128 }
103                     elif id:
104                       mtrf_command = { "mode": 2, "ch": channel, "cmd": 128, "id0": int(id[0:2],16), "id1": int(id[2:4],16), "id2": int(id[4:6],16), "id3": int(id[6:8],16), "ctr": 8 }
105                   elif command == "load_preset":
106                     if id == '.':
107                       mtrf_command = { "mode": 2, "ch": channel, "cmd": 7 }
108                     elif id:
109                       mtrf_command = { "mode": 2, "ch": channel, "cmd": 7, "id0": int(id[0:2],16), "id1": int(id[2:4],16), "id2": int(id[4:6],16), "id3": int(id[6:8],16), "ctr": 8 }
110                     else:
111                       mtrf_command = { "mode": 0, "ch": channel, "cmd": 7 }
112                   elif command == "save_preset":
113                     if id == '.':
114                       mtrf_command = { "mode": 2, "ch": channel, "cmd": 8 }
115                     elif id:
116                       mtrf_command = { "mode": 2, "ch": channel, "cmd": 8, "id0": int(id[0:2],16), "id1": int(id[2:4],16), "id2": int(id[4:6],16), "id3": int(id[6:8],16), "ctr": 8 }
117                     else:
118                       mtrf_command = { "mode": 0, "ch": channel, "cmd": 8 }
119                   elif command == "temp_on":
120                     delay = (int(payload) + 3)//5
121                     d0 = delay % 256
122                     d1 = delay // 256
123                     if id == '.':
124                       mtrf_command = { "mode": 2, "ch": channel, "cmd": 25, "fmt": 6, "d0": d0, "d1": d1 }
125                     elif id:
126                       mtrf_command = { "mode": 2, "ch": channel, "cmd": 25, "fmt": 6, "d0": d0, "d1": d1, "id0": int(id[0:2],16), "id1": int(id[2:4],16), "id2": int(id[4:6],16), "id3": int(id[6:8],16), "ctr": 8 }
127                     else:
128                       mtrf_command = { "mode": 0, "ch": channel, "cmd": 25, "fmt": 6, "d0": d0, "d1": d1 }
129                 except Exception as e:
130                   logger.exception(e)
131                   continue
132                 await self.commands_to_send_queue.put(mtrf_command)
133                  
134
135     async def send_command_to_noolite(self):
136         last_command_send_time = 0
137         while True:
138             logger.info('Waiting commands to send...')
139             payload = await self.commands_to_send_queue.get()
140             logger.info('Get command from queue: {}'.format(payload))
141
142             # Формируем и отправляем команду к noolite
143             noolite_cmd = self.payload_to_noolite_command(payload)
144
145             if time.time() - last_command_send_time < self.commands_delay:
146                 logger.info('Wait before send next command: {}'.format(
147                     self.commands_delay - (time.time() - last_command_send_time)))
148                 await asyncio.sleep(self.commands_delay - (time.time() - last_command_send_time))
149
150             try:
151                 await self.noolite_serial.send_command(**noolite_cmd)
152             except TypeError as e:
153                 logger.exception(str(e))
154             last_command_send_time = time.time()
155
156     async def input_serial_data(self, command):
157         logger.info('Pub command: {}'.format(command))
158         command = self.noolite_response_to_payload(command.to_list())
159         try:
160           topic = "%s/%s/%s" % (self.write_topic, command['ch'], command['id'])
161         except:
162           topic = "%s/%s" % (self.write_topic, command['ch'])  
163         await self.mqtt_client.publish(topic=topic, message=json.dumps(command).encode())
164
165     @staticmethod        
166     def payload_to_noolite_command(payload):
167         return payload
168
169     @staticmethod
170     def noolite_response_to_payload(payload):
171
172         message = {}
173
174         try:
175           mode = [ 'TX', 'RX', 'TX-F', 'RX-F', 'SERVICE', 'FIRMWARE' ] [payload[1]]
176           message['mode'] = mode
177         finally:
178           None  
179
180         try:
181           message['ctr'] = [ 'OK', 'NORESP', 'ERROR', 'BOUND' ] [payload[2]]
182         finally:
183           None  
184
185         ch = payload[4]
186         message['ch'] = ch
187
188         cmd = payload[5]
189         message['cmd'] = cmd
190
191         fmt = payload[6]
192         message['fmt'] = fmt
193
194         data = payload[7:11]
195         message['data'] = data
196
197         if payload[1] >= 2:
198           message['id'] = '%0.2X%0.2X%0.2X%0.2X' % (payload[11], payload[12], payload[13], payload[14])
199
200         if cmd == 0:
201           message['command'] = 'OFF'
202         elif cmd == 1: 
203           message['command'] = 'BRIGHT_DOWN'
204         elif cmd == 2: 
205           message['command'] = 'ON'
206         elif cmd == 3: 
207           message['command'] = 'BRIGHT_UP'
208         elif cmd == 4: 
209           message['command'] = 'SWITCH'
210         elif cmd == 5: 
211           message['command'] = 'SWITCH'
212         elif cmd == 5: 
213           message['command'] = 'BRIGHT_BACK'
214         elif cmd == 5: 
215           message['command'] = 'BRIGHT_BACK'
216         elif cmd == 6: 
217           message['command'] = 'SET_BRIGHTNESS'
218         elif cmd == 7: 
219           message['command'] = 'LOAD_PRESET'
220         elif cmd == 8: 
221           message['command'] = 'SAVE_PRESET'
222         elif cmd == 9: 
223           message['command'] = 'UNBIND'
224         elif cmd == 10: 
225           message['command'] = 'STOP_REG'
226 #        elif cmd == 11: 
227 #          message['command'] = 'BRIGHTNESS_STEP_DOWN'
228 #        elif cmd == 12: 
229 #          message['command'] = 'BRIGHTNESS_STEP_UP'
230 #        elif cmd == 13: 
231 #          message['command'] = 'BRIGHT_REG'
232         elif cmd == 15: 
233           message['command'] = 'BIND'
234         elif cmd == 16: 
235           message['command'] = 'ROLL_COLOUR'
236         elif cmd == 17: 
237           message['command'] = 'SWITCH_COLOUR'
238         elif cmd == 18: 
239           message['command'] = 'SWITCH_MODE'
240         elif cmd == 19: 
241           message['command'] = 'SPEED_MODE_BACK'
242         elif cmd == 20: 
243           message['command'] = 'BATTERY_LOW'
244         elif cmd == 21: 
245           message['command'] = 'SENS_TEMP_HUMI'
246           t = data[0] + 256*(data[1] % 16)
247           if (data[1] % 16) // 8:
248             t = -(4096 - t )
249           t = t / 10  
250           message['t'] = t
251           dev_type = (data[1] // 16) % 8
252           try:
253             message['dev_type'] = [ 'RESERVED', 'PT112', 'PT111' ][dev_type]
254           finally:
255             None  
256           message['dev_battery_low'] = (data[1] // 128)
257           if dev_type == 2:
258             h = data[2]
259             message['h'] = h
260           message['aux'] = data[3]  
261         elif cmd == 25: 
262           message['command'] = 'TEMPORARY_ON'
263           if fmt == 5:
264             message['delay'] = data[0] * 5
265           elif fmt == 6:
266             message['delay'] = data[0] * 5 + data[1]*5*256
267         elif cmd == 26: 
268           message['command'] = 'MODES'
269         elif cmd == 128: 
270           message['command'] = 'READ_STATE'
271         elif cmd == 129: 
272           message['command'] = 'WRITE_STATE'
273         elif cmd == 130: 
274           message['command'] = 'SEND_STATE'
275           dev_type = data[0]
276           if dev_type == 5:
277             message['dev_type'] = 'SLU-1-300'
278           message['dev_firmware'] = data[1]
279           if fmt == 0:
280             dev_state = data[2] % 16
281             try:
282               message['dev_state'] = [ 'OFF', 'ON', 'TEMPORARY_ON' ][dev_state]
283             finally:
284               None
285             dev_mode = data[2] // 128
286             if dev_mode:
287               message['dev_binding'] = 'ON'
288             message['brightness'] = data[3]
289           elif fmt == 1:
290             message['dev_aux'] = data[2]
291             message['dev_legacy'] = data[3]
292           elif fmt == 2:
293             message['dev_free'] = data[3]
294             message['dev_free_legacy'] = data[2]    
295         elif cmd == 131: 
296           message['command'] = 'SERVICE'
297         elif cmd == 132: 
298           message['command'] = 'CLEAR_MEMORY'
299           
300         return message
301