5261d43bc6e8525131bf3c08d3c1226f579ed4a9
[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
130                   await self.commands_to_send_queue.put(mtrf_command)
131
132                 except Exception as e:
133                   logger.exception(e)
134                   continue
135                  
136
137     async def send_command_to_noolite(self):
138         last_command_send_time = 0
139         while True:
140             logger.info('Waiting commands to send...')
141             payload = await self.commands_to_send_queue.get()
142             logger.info('Get command from queue: {}'.format(payload))
143
144             # Формируем и отправляем команду к noolite
145             noolite_cmd = self.payload_to_noolite_command(payload)
146
147             if time.time() - last_command_send_time < self.commands_delay:
148                 logger.info('Wait before send next command: {}'.format(
149                     self.commands_delay - (time.time() - last_command_send_time)))
150                 await asyncio.sleep(self.commands_delay - (time.time() - last_command_send_time))
151
152             try:
153                 await self.noolite_serial.send_command(**noolite_cmd)
154             except TypeError as e:
155                 logger.exception(str(e))
156             last_command_send_time = time.time()
157
158     async def input_serial_data(self, command):
159         logger.info('Pub command: {}'.format(command))
160         command = self.noolite_response_to_payload(command.to_list())
161         try:
162           topic = "%s/%s/%s" % (self.write_topic, command['ch'], command['id'])
163         except:
164           topic = "%s/%s" % (self.write_topic, command['ch'])  
165         await self.mqtt_client.publish(topic=topic, message=json.dumps(command).encode())
166
167     @staticmethod        
168     def payload_to_noolite_command(payload):
169         return payload
170
171     @staticmethod
172     def noolite_response_to_payload(payload):
173
174         message = {}
175
176         try:
177           mode = [ 'TX', 'RX', 'TX-F', 'RX-F', 'SERVICE', 'FIRMWARE' ] [payload[1]]
178           message['mode'] = mode
179         finally:
180           None  
181
182         try:
183           message['ctr'] = [ 'OK', 'NORESP', 'ERROR', 'BOUND' ] [payload[2]]
184         finally:
185           None  
186
187         ch = payload[4]
188         message['ch'] = ch
189
190         cmd = payload[5]
191         message['cmd'] = cmd
192
193         fmt = payload[6]
194         message['fmt'] = fmt
195
196         data = payload[7:11]
197         message['data'] = data
198
199         if payload[1] >= 2:
200           message['id'] = '%0.2X%0.2X%0.2X%0.2X' % (payload[11], payload[12], payload[13], payload[14])
201
202         if cmd == 0:
203           message['command'] = 'OFF'
204         elif cmd == 1: 
205           message['command'] = 'BRIGHT_DOWN'
206         elif cmd == 2: 
207           message['command'] = 'ON'
208         elif cmd == 3: 
209           message['command'] = 'BRIGHT_UP'
210         elif cmd == 4: 
211           message['command'] = 'SWITCH'
212         elif cmd == 5: 
213           message['command'] = 'SWITCH'
214         elif cmd == 5: 
215           message['command'] = 'BRIGHT_BACK'
216         elif cmd == 5: 
217           message['command'] = 'BRIGHT_BACK'
218         elif cmd == 6: 
219           message['command'] = 'SET_BRIGHTNESS'
220         elif cmd == 7: 
221           message['command'] = 'LOAD_PRESET'
222         elif cmd == 8: 
223           message['command'] = 'SAVE_PRESET'
224         elif cmd == 9: 
225           message['command'] = 'UNBIND'
226         elif cmd == 10: 
227           message['command'] = 'STOP_REG'
228 #        elif cmd == 11: 
229 #          message['command'] = 'BRIGHTNESS_STEP_DOWN'
230 #        elif cmd == 12: 
231 #          message['command'] = 'BRIGHTNESS_STEP_UP'
232 #        elif cmd == 13: 
233 #          message['command'] = 'BRIGHT_REG'
234         elif cmd == 15: 
235           message['command'] = 'BIND'
236         elif cmd == 16: 
237           message['command'] = 'ROLL_COLOUR'
238         elif cmd == 17: 
239           message['command'] = 'SWITCH_COLOUR'
240         elif cmd == 18: 
241           message['command'] = 'SWITCH_MODE'
242         elif cmd == 19: 
243           message['command'] = 'SPEED_MODE_BACK'
244         elif cmd == 20: 
245           message['command'] = 'BATTERY_LOW'
246         elif cmd == 21: 
247           message['command'] = 'SENS_TEMP_HUMI'
248           t = data[0] + 256*(data[1] % 16)
249           if (data[1] % 16) // 8:
250             t = -(4096 - t )
251           t = t / 10  
252           message['t'] = t
253           dev_type = (data[1] // 16) % 8
254           try:
255             message['dev_type'] = [ 'RESERVED', 'PT112', 'PT111' ][dev_type]
256           finally:
257             None  
258           message['dev_battery_low'] = (data[1] // 128)
259           if dev_type == 2:
260             h = data[2]
261             message['h'] = h
262           message['aux'] = data[3]  
263         elif cmd == 25: 
264           message['command'] = 'TEMPORARY_ON'
265           if fmt == 5:
266             message['delay'] = data[0] * 5
267           elif fmt == 6:
268             message['delay'] = data[0] * 5 + data[1]*5*256
269         elif cmd == 26: 
270           message['command'] = 'MODES'
271         elif cmd == 128: 
272           message['command'] = 'READ_STATE'
273         elif cmd == 129: 
274           message['command'] = 'WRITE_STATE'
275         elif cmd == 130: 
276           message['command'] = 'SEND_STATE'
277           dev_type = data[0]
278           if dev_type == 5:
279             message['dev_type'] = 'SLU-1-300'
280           message['dev_firmware'] = data[1]
281           if fmt == 0:
282             dev_state = data[2] % 16
283             try:
284               message['dev_state'] = [ 'OFF', 'ON', 'TEMPORARY_ON' ][dev_state]
285             finally:
286               None
287             dev_mode = data[2] // 128
288             if dev_mode:
289               message['dev_binding'] = 'ON'
290             message['brightness'] = data[3]
291           elif fmt == 1:
292             message['dev_aux'] = data[2]
293             message['dev_legacy'] = data[3]
294           elif fmt == 2:
295             message['dev_free'] = data[3]
296             message['dev_free_legacy'] = data[2]    
297         elif cmd == 131: 
298           message['command'] = 'SERVICE'
299         elif cmd == 132: 
300           message['command'] = 'CLEAR_MEMORY'
301           
302         return message
303