class HausNet: """Give access to the network plant to clients""" def __init__(self, loop, mqtt_host: str, mqtt_port: int, config: Dict[str, Any]): self.plant = PlantBuilder().build(config, loop, mqtt_host, mqtt_port) def device_assemblies(self) -> Dict[str, DeviceAssembly]: """ Convenience accessor to devices. """ return self.plant.device_assemblies def start(self): """ Convenience accessor to plant startup. """ self.plant.start()
async def test_can_build_float_sensor(self): """Test that a floating-point sensor can be built from a blueprint.""" blueprint = { 'sensor_1': { 'type': 'sensor', 'device_id': 'thermo', 'config': { 'state.type': 'float', 'state.unit': 'F', }, }, 'sensor_2': { 'type': 'sensor', 'device_id': 'thermo', 'config': { 'state.type': 'float', }, } } plant = PlantBuilder().build(blueprint, self.loop) self.assertEqual(3, len(plant.device_assemblies), "Expected two devices + root to be built") self.assertEqual('F', plant.device_assemblies['sensor_1'].device.state.unit, "Expected a unit 'F'") self.assertIsInstance(plant.device_assemblies['sensor_1'].device.state, FloatState, "Expected a floating point state") self.assertIsNone( plant.device_assemblies['sensor_2'].device.state.unit, "Unit should not exist") self.assertIsInstance(plant.device_assemblies['sensor_1'].device.state, FloatState, "Expected a floating point state")
async def test_can_build_single_node_with_single_device(self): """Can a basic node + device be built?""" blueprint = { 'test_node': { 'type': 'node', 'device_id': 'test/ABC123', 'devices': { 'test_switch': { 'type': 'basic_switch', 'device_id': 'switch', } } } } plant = PlantBuilder().build(blueprint, self.loop) self.assertEqual(len(plant.device_assemblies), 3, "Expected 3 device interfaces") root = plant.device_assemblies['root'].device self.assertEqual(len(root.sub_devices), 1, "Expected one device at the root of the tree") self.assertIs(root.sub_devices['test_node'].__class__, NodeDevice, "Top-level device should be a NodeDevice") node: NodeDevice = cast(NodeDevice, root.sub_devices['test_node']) self.assertEqual(node.device_id, 'test/ABC123', "Expected 'test/ABC123' as device_id for node") self.assertEqual(len(node.sub_devices), 1, "Expected one sub-device") self.assertIn('test_switch', node.sub_devices, "Expected test_switch sub-device key") sub_device: BasicSwitch = cast(BasicSwitch, node.sub_devices['test_switch']) self.assertIs(sub_device.__class__, BasicSwitch, "Sub-device should be a BasicSwitch") self.assertEqual(sub_device.device_id, 'switch')
async def test_receive_sensor_sample(self, mock_client): blueprint = { 'test_node': { 'type': 'node', 'device_id': 'test/ABC123', 'devices': { 'test_sensor': { 'type': 'sensor', 'device_id': 'thermo', 'config': { 'state.type': 'float', 'state.unit': 'F' } } } }, 'test_node_2': { 'type': 'node', 'device_id': 'test/123ABC', 'devices': { 'test_switch': { 'type': 'basic_switch', 'device_id': 'switch', }, 'test_sensor': { 'type': 'sensor', 'device_id': 'thermo', 'config': { 'state.type': 'float', 'state.unit': 'F' } } } } } plant = PlantBuilder().build(blueprint, self.loop) await plant.upstream_source.queue.put({ 'topic': 'hausnet/test/ABC123/upstream', 'message': '{"thermo":{"state":13}}' }) await plant.upstream_source.queue.put({ 'topic': 'hausnet/test/ABC123/upstream', 'message': '{"thermo":{"state":14}}' }) plant.start() await plant.upstream_source.queue.join() plant.stop() assembly = plant.device_assemblies['test_node.test_sensor'] queue = assembly.upstream_pipe.sink.queue self.assertEqual( {'state': 13}, queue.get_nowait(), "Value of 13 expected from thermo device" ) self.assertEqual( {'state': 14}, queue.get_nowait(), "Value of 14 expected from thermo device" )
async def test_send_state_change_to_switch(self, mock_client): """ Send a state change downstream. """ blueprint = { 'test_node': { 'type': 'node', 'device_id': 'test/ABC123', 'devices': { 'test_switch': { 'type': 'basic_switch', 'device_id': 'switch', }, 'test_sensor': { 'type': 'sensor', 'device_id': 'thermo', 'config': { 'state.type': 'float', 'state.unit': 'F' } } } }, 'test_node_2': { 'type': 'node', 'device_id': 'test/123ABC', 'devices': { 'test_switch': { 'type': 'basic_switch', 'device_id': 'switch', }, 'test_sensor': { 'type': 'sensor', 'device_id': 'thermo', 'config': { 'state.type': 'float', 'state.unit': 'F' } } } } } plant = PlantBuilder().build(blueprint, self.loop) assembly = plant.device_assemblies['test_node.test_switch'] await assembly.client_in_queue.put({'state': OnOffState.ON}) await assembly.client_in_queue.put({'state': OnOffState.OFF}) plant.start() await assembly.client_in_queue.join() plant.stop() queue = plant.downstream_dest_queue self.assertEqual( {'topic': 'hausnet/test/ABC123/downstream', 'message': '{"switch":{"state":"ON"}}'}, queue.sync_q.get_nowait(), "ON message to switch expected" ) self.assertEqual( {'topic': 'hausnet/test/ABC123/downstream', 'message': '{"switch":{"state":"OFF"}}'}, queue.sync_q.get_nowait(), "OFF message to switch expected" )
async def test_switch_upstream_wiring_delivers(self): """Test that a basic switch state updates get delivered to the external world""" blueprint = { 'test_node': { 'type': 'node', 'device_id': 'test/ABC123', 'devices': { 'test_switch_1': { 'type': 'basic_switch', 'device_id': 'switch_1', }, 'test_switch_2': { 'type': 'basic_switch', 'device_id': 'switch_2', } } } } plant = PlantBuilder().build(blueprint, self.loop) messages = [{ 'topic': 'hausnet/test/ABC123/upstream', 'message': '{"switch_1": {"state": "OFF"}}' }, { 'topic': 'hausnet/test/ABC123/upstream', 'message': '{"switch_2": {"state": "ON"}}' }] out_messages = [] in_queue = plant.upstream_src_queue.sync_q for message in messages: in_queue.put(message) plant.upstream_source.start() await plant.upstream_source.queue.join() plant.upstream_source.stop() out_messages.append(await plant.device_assemblies['test_node.test_switch_1']. upstream_pipe.sink.queue.get()) plant.device_assemblies[ 'test_node.test_switch_1'].upstream_pipe.sink.queue.task_done() out_messages.append(await plant.device_assemblies['test_node.test_switch_2']. upstream_pipe.sink.queue.get()) plant.device_assemblies[ 'test_node.test_switch_2'].upstream_pipe.sink.queue.task_done() self.assertEqual( plant.device_assemblies['test_node.test_switch_1'].device.state. value, OnOffState.OFF, "Expected switch 1 to be OFF") self.assertEqual( plant.device_assemblies['test_node.test_switch_2'].device.state. value, OnOffState.ON, "Expected switch 1 to be ON")
def command_loop(): loop = asyncio.get_event_loop() up_stream = SyncToAsyncBufferedStream(loop) bundles = PlantBuilder(up_stream).build(conf.HAUSNET_CONFIG) mqtt_client = MqttClient(up_stream) async def sink_to_stdout(message): print(message) async def main(): for name, device_bundle in bundles.items(): await subscribe(device_bundle.upstream_pipe, AsyncAnonymousObserver(sink_to_stdout)) await mqtt_client._up_stream.stream() loop.run_until_complete(main()) loop.close()
async def test_switch_downstream_wiring_delivers(self): """Test that a basic switch state changes get delivered to the MQTT end of the stream""" blueprint = { 'test_node': { 'type': 'node', 'device_id': 'test/ABC123', 'devices': { 'test_switch': { 'type': 'basic_switch', 'device_id': 'switch_1', }, } } } plant = PlantBuilder().build(blueprint, self.loop) messages = [{'state': 'ON'}, {'state': 'OFF'}] out_messages = [] source = plant.device_assemblies[ 'test_node.test_switch'].downstream_pipe.source in_queue = source.queue for message in messages: await in_queue.put(message) source.start() await source.queue.join() source.stop() out_queue = plant.downstream_dest_queue out_messages.append(out_queue.sync_q.get()) out_messages.append(out_queue.sync_q.get()) self.assertEqual( out_messages[0], { 'topic': 'hausnet/test/ABC123/downstream', 'message': '{"switch_1":{"state":"ON"}}' }, "Expected an 'ON' JSON message on topic 'hausnet/test/ABC123/downstream'" ) self.assertEqual( out_messages[1], { 'topic': 'hausnet/test/ABC123/downstream', 'message': '{"switch_1":{"state":"OFF"}}' }, "Expected an 'OFF' JSON message on topic 'hausnet/test/ABC123/downstream'" )
def __init__(self, loop, mqtt_host: str, mqtt_port: int, config: Dict[str, Any]): self.plant = PlantBuilder().build(config, loop, mqtt_host, mqtt_port)
async def test_can_build_multiple_nodes_with_multiple_devices(self): """Can a set of basic nodes, each with multiple devices be built?""" blueprint = { 'test_node_1': { 'type': 'node', 'device_id': 'test/ABC123', 'devices': { 'test_switch_A': { 'type': 'basic_switch', 'device_id': 'switch_1', }, 'test_switch_B': { 'type': 'basic_switch', 'device_id': 'switch_2', } } }, 'test_node_2': { 'type': 'node', 'device_id': 'test/ABC124', 'devices': { 'test_switch_A': { 'type': 'basic_switch', 'device_id': 'switch_1', }, 'test_switch_B': { 'type': 'basic_switch', 'device_id': 'switch_2', }, 'test_switch_C': { 'type': 'basic_switch', 'device_id': 'switch_3', }, } } } plant = PlantBuilder().build(blueprint, self.loop) self.assertEqual(len(plant.device_assemblies), 8, "Expected 8 device interfaces") root = plant.device_assemblies['root'].device self.assertEqual(len(root.sub_devices), 2, "Expected two nodes at the root of the tree") node_1 = root.sub_devices['test_node_1'] self.assertIs(node_1.__class__, NodeDevice, "Top-level device #1 should be a NodeDevice") node_2 = root.sub_devices['test_node_2'] self.assertIs(node_2.__class__, NodeDevice, "Top-level device #2 should be a NodeDevice") node_1: NodeDevice = cast( NodeDevice, plant.device_assemblies['test_node_1'].device) self.assertEqual(node_1.device_id, 'test/ABC123', "Expected 'test/ABC123' as device_id for node") node_2 = cast(NodeDevice, plant.device_assemblies['test_node_2'].device) self.assertEqual(node_2.device_id, 'test/ABC124', "Expected 'test/ABC124' as device_id for node") # Test sub-devices on node 1 self.assertEqual(len(node_1.sub_devices), 2, "Expected two sub-devices") self.assertIn('test_switch_A', node_1.sub_devices, "Expected test_switch_A sub-device key") self.assertIn('test_switch_B', node_1.sub_devices, "Expected test_switch_B sub-device key") sub_device: BasicSwitch = cast(BasicSwitch, node_1.sub_devices['test_switch_A']) self.assertIs(sub_device.__class__, BasicSwitch, "Sub-device A should be a BasicSwitch") self.assertEqual(sub_device.device_id, 'switch_1', "Sub-device A's ID should be switch_1") sub_device = cast(BasicSwitch, node_1.sub_devices['test_switch_B']) self.assertIs(sub_device.__class__, BasicSwitch, "Sub-device B should be a BasicSwitch") self.assertEqual(sub_device.device_id, 'switch_2', "Sub-device B's ID should be switch_2") # Test sub-devices on node B self.assertEqual(len(node_2.sub_devices), 3, "Expected two sub-devices") self.assertIn('test_switch_A', node_2.sub_devices, "Expected test_switch_A sub-device key") self.assertIn('test_switch_B', node_2.sub_devices, "Expected test_switch_B sub-device key") self.assertIn('test_switch_C', node_2.sub_devices, "Expected test_switch_C sub-device key") sub_device: BasicSwitch = cast(BasicSwitch, node_2.sub_devices['test_switch_A']) self.assertIs(sub_device.__class__, BasicSwitch, "Sub-device A should be a BasicSwitch") self.assertEqual(sub_device.device_id, 'switch_1', "Sub-device A's ID should be switch_1") sub_device = cast(BasicSwitch, node_2.sub_devices['test_switch_B']) self.assertIs(sub_device.__class__, BasicSwitch, "Sub-device B should be a BasicSwitch") self.assertEqual(sub_device.device_id, 'switch_2', "Sub-device B's ID should be switch_2") sub_device = cast(BasicSwitch, node_2.sub_devices['test_switch_C']) self.assertIs(sub_device.__class__, BasicSwitch, "Sub-device C should be a BasicSwitch") self.assertEqual(sub_device.device_id, 'switch_3', "Sub-device C's ID should be switch_3")