6 from hbmqtt.client import MQTTClient
7 from hbmqtt.mqtt.constants import QOS_0, QOS_1, QOS_2
9 from .nl_serial import NooliteSerial
10 from .utils import Singleton
12 from uuid import uuid1
13 from socket import gethostname
15 logger = logging.getLogger(__name__)
17 client_id = gethostname()+'-'+str(uuid1())
19 INPUT_TOPIC = '%s/send'
20 OUTPUT_TOPIC = '%s/receive'
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),
34 self.write_topic = OUTPUT_TOPIC % mqtt_topic
35 loop.create_task(self.send_command_to_noolite())
38 await self.mqtt_client.connect(self.mqtt_uri)
39 await self.mqtt_client.subscribe(self.subscriptions)
42 logger.info('Waiting messages from mqtt...')
43 message = await self.mqtt_client.deliver_message()
46 payload = message.publish_packet.payload.data
48 logger.info('In message: {} -> {}'.format(topic, payload.decode('utf-8')))
50 if topic.startswith(self.read_topic):
51 subtopic = topic[len(self.read_topic)+1:]
56 payload = json.loads(payload.decode())
57 except Exception as e:
60 await self.commands_to_send_queue.put(payload)
64 address = subtopic.split('/')
66 channel = int(address[0])
70 channel = int(address[0])
77 command = command.lower()
79 print("%s: %s (%s)" % (command,channel,id))
81 if id == '.' or id == 'TX-F':
82 mtrf_command = { "mode": 2, "ch": channel, "cmd": 2, "ctr": 1 }
84 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 }
86 mtrf_command = { "mode": 0, "ch": channel, "cmd": 2 }
88 mtrf_command = { "mode": 0, "ch": channel, "cmd": 2 }
89 elif command == "off":
90 if id == '.' or id == 'TX-F':
91 mtrf_command = { "mode": 2, "ch": channel, "cmd": 0, "ctr": 1 }
93 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 }
95 mtrf_command = { "mode": 0, "ch": channel, "cmd": 0 }
97 mtrf_command = { "mode": 0, "ch": channel, "cmd": 0 }
98 elif command == "brightness":
99 brightness = int(payload)
100 if id == '.' or id == 'TX-F':
101 mtrf_command = { "mode": 2, "ch": channel, "cmd": 6, "d0": brightness, "ctr": 1 }
103 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 }
105 mtrf_command = { "mode": 0, "ch": channel, "cmd": 6, "d0": brightness }
107 mtrf_command = { "mode": 0, "ch": channel, "cmd": 6, "d0": brightness }
108 elif command == "state":
109 if id == '.' or id == 'TX-F':
110 mtrf_command = { "mode": 2, "ch": channel, "cmd": 128, "ctr": 1 }
112 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 }
113 elif command == "load_preset":
114 if id == '.' or id == 'TX-F':
115 mtrf_command = { "mode": 2, "ch": channel, "cmd": 7, "ctr": 1 }
117 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 }
119 mtrf_command = { "mode": 0, "ch": channel, "cmd": 7 }
121 mtrf_command = { "mode": 0, "ch": channel, "cmd": 7 }
122 elif command == "save_preset":
123 if id == '.' or id == 'TX-F':
124 mtrf_command = { "mode": 2, "ch": channel, "cmd": 8, "ctr": 1 }
126 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 }
128 mtrf_command = { "mode": 0, "ch": channel, "cmd": 8 }
130 mtrf_command = { "mode": 0, "ch": channel, "cmd": 8 }
131 elif command == "temp_on":
132 delay = (int(payload) + 3)//5
135 if id == '.' or id == 'TX-F':
136 mtrf_command = { "mode": 2, "ch": channel, "cmd": 25, "fmt": 6, "d0": d0, "d1": d1, "ctr": 1 }
138 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 }
140 mtrf_command = { "mode": 0, "ch": channel, "cmd": 25, "fmt": 6, "d0": d0, "d1": d1 }
142 mtrf_command = { "mode": 0, "ch": channel, "cmd": 25, "fmt": 6, "d0": d0, "d1": d1 }
143 elif command == "bind":
145 mtrf_command = { "mode": 2, "ch": channel, "cmd": 15 }
147 mtrf_command = { "mode": 0, "ch": channel, "cmd": 15 }
149 mtrf_command = { "mode": 1, "ch": channel, "cmd": 15 }
151 mtrf_command = { "mode": 3, "ch": channel, "cmd": 15 }
153 mtrf_command = { "mode": 0, "ch": channel, "cmd": 15 }
154 elif command == "unbind":
155 if id == '.' or id == 'TX-F':
156 mtrf_command = { "mode": 2, "ch": channel, "cmd": 9 }
158 mtrf_command = { "mode": 0, "ch": channel, "cmd": 9 }
160 mtrf_command = { "mode": 1, "ch": channel, "cmd": 9, "ctr": 5 }
162 mtrf_command = { "mode": 3, "ch": channel, "cmd": 9, "ctr": 5 }
164 mtrf_command = { "mode": 0, "ch": channel, "cmd": 9 }
165 elif command == "service":
166 if id == '.' or id == 'TX-F':
167 mtrf_command = { "mode": 2, "ch": channel, "cmd": 131, "d0": 1 }
169 mtrf_command = { "mode": 2, "ch": channel, "cmd": 131, "d0": 1, "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 }
172 await self.commands_to_send_queue.put(mtrf_command)
174 except Exception as e:
179 async def send_command_to_noolite(self):
180 last_command_send_time = 0
182 logger.info('Waiting commands to send...')
183 payload = await self.commands_to_send_queue.get()
184 logger.info('Get command from queue: {}'.format(payload))
186 # Формируем и отправляем команду к noolite
187 noolite_cmd = self.payload_to_noolite_command(payload)
189 if time.time() - last_command_send_time < self.commands_delay:
190 logger.info('Wait before send next command: {}'.format(
191 self.commands_delay - (time.time() - last_command_send_time)))
192 await asyncio.sleep(self.commands_delay - (time.time() - last_command_send_time))
195 await self.noolite_serial.send_command(**noolite_cmd)
196 except TypeError as e:
197 logger.exception(str(e))
198 last_command_send_time = time.time()
200 async def input_serial_data(self, command):
201 command = self.noolite_response_to_payload(command.to_list())
202 logger.info('Pub command: {}'.format(command))
204 topic = "%s/%s/%s" % (self.write_topic, command['ch'], command['id'])
206 topic = "%s/%s" % (self.write_topic, command['ch'])
207 await self.mqtt_client.publish(topic=topic, message=json.dumps(command).encode())
210 def payload_to_noolite_command(payload):
214 def noolite_response_to_payload(payload):
219 mode = [ 'TX', 'RX', 'TX-F', 'RX-F', 'SERVICE', 'FIRMWARE' ] [payload[1]]
220 message['mode'] = mode
225 message['ctr'] = [ 'OK', 'NORESP', 'ERROR', 'BOUND' ] [payload[2]]
239 message['data'] = data
242 message['id'] = '%0.2X%0.2X%0.2X%0.2X' % (payload[11], payload[12], payload[13], payload[14])
245 message['command'] = 'OFF'
247 message['command'] = 'BRIGHT_DOWN'
249 message['command'] = 'ON'
251 message['command'] = 'BRIGHT_UP'
253 message['command'] = 'SWITCH'
255 message['command'] = 'SWITCH'
257 message['command'] = 'BRIGHT_BACK'
259 message['command'] = 'BRIGHT_BACK'
261 message['command'] = 'SET_BRIGHTNESS'
263 message['command'] = 'LOAD_PRESET'
265 message['command'] = 'SAVE_PRESET'
267 message['command'] = 'UNBIND'
269 message['command'] = 'STOP_REG'
271 # message['command'] = 'BRIGHTNESS_STEP_DOWN'
273 # message['command'] = 'BRIGHTNESS_STEP_UP'
275 # message['command'] = 'BRIGHT_REG'
277 message['command'] = 'BIND'
279 message['command'] = 'ROLL_COLOUR'
281 message['command'] = 'SWITCH_COLOUR'
283 message['command'] = 'SWITCH_MODE'
285 message['command'] = 'SPEED_MODE_BACK'
287 message['command'] = 'BATTERY_LOW'
289 message['command'] = 'SENS_TEMP_HUMI'
290 t = data[0] + 256*(data[1] % 16)
291 if (data[1] % 16) // 8:
295 dev_type = (data[1] // 16) % 8
297 message['dev_type'] = [ 'RESERVED', 'PT112', 'PT111' ][dev_type]
300 message['dev_battery_low'] = (data[1] // 128)
304 message['aux'] = data[3]
306 message['command'] = 'TEMPORARY_ON'
308 message['delay'] = data[0] * 5
310 message['delay'] = data[0] * 5 + data[1]*5*256
312 message['command'] = 'MODES'
314 message['command'] = 'READ_STATE'
316 message['command'] = 'WRITE_STATE'
318 message['command'] = 'SEND_STATE'
321 message['dev_type'] = 'SLU-1-300'
322 message['dev_firmware'] = data[1]
324 dev_state = data[2] % 16
326 message['dev_state'] = [ 'OFF', 'ON', 'TEMPORARY_ON' ][dev_state]
329 dev_mode = data[2] // 128
331 message['dev_binding'] = 'ON'
332 message['brightness'] = data[3]
334 message['dev_aux'] = data[2]
335 message['dev_legacy'] = data[3]
337 message['dev_free'] = data[3]
338 message['dev_free_legacy'] = data[2]
340 message['command'] = 'SERVICE'
342 message['command'] = 'CLEAR_MEMORY'