def get_client_with_messages(msgs: dict) -> HomieClient: c = HomieClient() for topic, payload in msgs.items(): msg = MQTTMessage() msg.topic = topic.encode('utf-8') msg.payload = payload.encode('utf-8') c.on_message(None, None, msg) return c
async def test_read_write(mqtt, client_id): """Test MQTT transport read and write.""" mqtt_client = mqtt.return_value topic = f"{IN_PREFIX}/0/255/3/1/11" payload = "test" msg = MQTTMessage() msg.topic = topic.encode() msg.payload = payload.encode() messages = [msg] async def mock_messages(): """Mock the messages generator.""" for message in messages: yield message @asynccontextmanager async def filter_messages(): """Mock filter messages.""" yield mock_messages() mqtt_client.unfiltered_messages.side_effect = filter_messages transport = MQTTClient(HOST, PORT, IN_PREFIX, OUT_PREFIX) await transport.connect() assert mqtt.call_count == 1 assert mqtt.call_args == call( HOST, PORT, client_id=client_id, logger=PAHO_MQTT_LOGGER, clean_session=True, ) await asyncio.sleep(0) read = await transport.read() assert read == "0;255;3;1;11;test" await transport.write(read) assert mqtt_client.publish.call_count == 1 assert mqtt_client.publish.call_args == call( f"{OUT_PREFIX}/0/255/3/1/11", qos=1, retain=False, timeout=10, payload=payload ) await transport.disconnect() assert mqtt_client.disconnect.call_count == 1
async def test_read_failure(mqtt, client_id): """Test MQTT transport read failure.""" mqtt_client = mqtt.return_value topic = f"{IN_PREFIX}/0/0/0/0/0" payload = "test" msg = MQTTMessage() msg.topic = topic.encode() msg.payload = payload.encode() messages = [msg] async def mock_messages(): """Mock the messages generator.""" for message in messages: yield message @asynccontextmanager async def filter_messages(): """Mock filter messages.""" yield mock_messages() raise MqttError("Boom") mqtt_client.unfiltered_messages.side_effect = filter_messages transport = MQTTClient(HOST, PORT, IN_PREFIX, OUT_PREFIX) await transport.connect() assert mqtt.call_count == 1 assert mqtt.call_args == call( HOST, PORT, client_id=client_id, logger=PAHO_MQTT_LOGGER, clean_session=True, ) await asyncio.sleep(0) read = await transport.read() assert read == "0;0;0;0;0;test" with pytest.raises(TransportFailedError): await transport.read() await transport.disconnect() assert mqtt_client.disconnect.call_count == 1
def test_mqtt_handle_on_message_save_device_data(app_and_ctx, capsys): device_id = 9999 # not present tid_bi = 'b209eba637a54f1f617cf5a6f925e4eb9fc083e66029061018b369e64b9864d7' # blind_index(hex_to_key("622c23fe2623e54ba103c13b88072ca3fdc5836fc459cb2b5c31d8df3f07ebc2"), "8") payload = b"{'added': 6987, 'num_data': 31164, 'data': 'gAAAAABcTFAz9Wr5ZsnMcVYbQiXlnZCvT36MfDatZNyLwDpm_ixbzkZhM1NA4w7MN2p3CW3gyTA8gYtuKtDTomhulszvLTFfPA==', 'tid': 'encrypted_tid(8)', 'tid_bi': '%b', 'correctness_hash': '$2b$12$9hxKg4pjXbm0kpbItQTd2uMICAGn2ntRw1qQskHIL/7tLa3ISIlmO'}" % tid_bi.encode( ) topic = b"d:%a/server/save_data" % device_id msg = MQTTMessage(topic=topic) msg.payload = payload invalid_topic = b"d:%a/server/invalid" % device_id msg_invalid = MQTTMessage(topic=invalid_topic) msg_invalid.payload = payload from app.mqtt.mqtt import handle_on_message app, ctx = app_and_ctx with app.app_context(): handle_on_message(None, None, msg_invalid, app, db) captured = capsys.readouterr() assert f"Invalid topic: {msg_invalid.topic}" in captured.out handle_on_message(None, None, msg, app, db) captured = capsys.readouterr() assert f"Device with id: {device_id} doesn't exist." in captured.out device_data = db.session.query(DeviceData).filter( and_( DeviceData.tid_bi == tid_bi, DeviceData.device_id == device_id, )).first() assert device_data is None msg.payload = payload # reassign, because `handle_on_message` causes side-effect and converts it to `dict` device_id = 23 topic = b"d:%a/server/save_data" % device_id msg.topic = topic handle_on_message(None, None, msg, app, db) device_data = db.session.query(DeviceData).filter( and_( DeviceData.tid_bi == tid_bi, DeviceData.device_id == device_id, )).first() assert device_data is not None assert device_data.added == 6987 assert device_data.num_data == 31164
def test_subscribe(mock_broker, mocker): mqttc = MqttClient(port=1883, callback=callback) mqttc.subscribe(topic="sensor/temperature") # create a fake MQTT message msg = MQTTMessage(mid=MID) msg.topic = b"sensor/temperature" msg.payload = b"22.5" mocker.spy(mqttc, "callback") # trigger 2 messages received on the MQTT bus mqttc._client.on_message(mqttc._client, None, msg) mqttc._client.on_message(mqttc._client, None, msg) mqttc.stop() assert mqttc.callback.call_count == 2
def test_integration(): device_discovered = Mock() node_discovered = Mock() property_discovered = Mock() device_updated = Mock() node_updated = Mock() property_updated = Mock() c = HomieClient() c.on_device_discovered = device_discovered c.on_node_discovered = node_discovered c.on_property_discovered = property_discovered c.on_device_updated = device_updated c.on_node_updated = node_updated c.on_property_updated = property_updated with open('tests/messages.txt') as f: for line in f.readlines(): topic, payload = line.split(' ', 1) msg = MQTTMessage() msg.topic = topic.encode('utf-8') msg.payload = payload.strip().encode('utf-8') c.on_message(None, None, msg) assert c.sensor1.state == 'ready' assert len(c.sensor1.nodes) == 2 assert len(c.sensor1.dht.properties) == 2 assert len(c.sensor1.bmp.properties) == 1 assert c.sensor1.dht.temperature == { 'name': 'Temperature', 'unit': '°C', 'value': 19.82 } assert c.sensor1.dht.humidity == { 'name': 'Humidity', 'unit': '%', 'value': 61.9 } assert c.sensor1.bmp.pressure == { 'name': 'Pressure', 'unit': 'mbar', 'value': 1021.69 } assert c.powermeter.state == 'ready' assert len(c.powermeter.nodes) == 1 assert len(c.powermeter.powermeter.properties) == 2 assert c.powermeter.powermeter.power_delivered == { 'name': 'Power delivered', 'unit': 'W', 'value': 410 } assert c.powermeter.powermeter.power_returned == { 'name': 'Power returned', 'unit': 'W', 'value': 1500 } device_discovered.assert_has_calls( [call(c.powermeter), call(c.sensor1)], any_order=True) node_discovered.assert_has_calls([ call(node) for node in [c.powermeter.powermeter, c.sensor1.bmp, c.sensor1.dht] ], any_order=True) property_discovered.assert_has_calls([ call(e[0], e[1]) for e in [(c.sensor1.dht, 'temperature'), (c.sensor1.dht, 'humidity'), (c.sensor1.bmp, 'pressure'), (c.powermeter.powermeter, 'power_delivered'), (c.powermeter.powermeter, 'power_returned')] ], any_order=True) property_updated.assert_has_calls([ call(e[0], e[1], e[2]) for e in [(c.sensor1.dht, 'temperature', { 'name': 'Temperature', 'unit': '°C', 'value': 20.12 }), (c.sensor1.dht, 'temperature', { 'name': 'Temperature', 'unit': '°C', 'value': 20.08 }), (c.sensor1.dht, 'temperature', { 'name': 'Temperature', 'unit': '°C', 'value': 19.82 }), (c.sensor1.dht, 'humidity', { 'name': 'Humidity', 'unit': '%', 'value': 63.5 }), (c.sensor1.dht, 'humidity', { 'name': 'Humidity', 'unit': '%', 'value': 63.2 }), (c.sensor1.dht, 'humidity', { 'name': 'Humidity', 'unit': '%', 'value': 61.9 }), (c.sensor1.bmp, 'pressure', { 'name': 'Pressure', 'unit': 'mbar', 'value': 1021.7 }), (c.sensor1.bmp, 'pressure', { 'name': 'Pressure', 'unit': 'mbar', 'value': 1021.71 }), (c.sensor1.bmp, 'pressure', { 'name': 'Pressure', 'unit': 'mbar', 'value': 1021.69 }), (c.powermeter.powermeter, 'power_delivered', { 'name': 'Power delivered', 'unit': 'W', 'value': 356 }), (c.powermeter.powermeter, 'power_delivered', { 'name': 'Power delivered', 'unit': 'W', 'value': 390 }), (c.powermeter.powermeter, 'power_delivered', { 'name': 'Power delivered', 'unit': 'W', 'value': 410 }), (c.powermeter.powermeter, 'power_returned', { 'name': 'Power returned', 'unit': 'W', 'value': 1300 }), (c.powermeter.powermeter, 'power_returned', { 'name': 'Power returned', 'unit': 'W', 'value': 1400 }), (c.powermeter.powermeter, 'power_returned', { 'name': 'Power returned', 'unit': 'W', 'value': 1500 })] ], any_order=True)