def setUp(self) -> None: self.fiware_header = FiwareHeader( service=settings.FIWARE_SERVICE, service_path=settings.FIWARE_SERVICEPATH) self.service_group_json = ServiceGroup( apikey=settings.FIWARE_SERVICEPATH.strip('/'), resource="/iot/json") self.service_group_ul = ServiceGroup( apikey=settings.FIWARE_SERVICEPATH.strip('/'), resource="/iot/d") # create a device configuration device_attr = DeviceAttribute(name='temperature', object_id='t', type="Number") device_command = DeviceCommand(name='heater', type="Boolean") self.device_json = Device(device_id='my_json_device', entity_name='my_json_device', entity_type='Thing', protocol='IoTA-JSON', transport='MQTT', apikey=self.service_group_json.apikey, attributes=[device_attr], commands=[device_command]) self.device_ul = Device(device_id='my_ul_device', entity_name='my_ul_device', entity_type='Thing', protocol='PDI-IoTA-UltraLight', transport='MQTT', apikey=self.service_group_ul.apikey, attributes=[device_attr], commands=[device_command]) self.mqttc = IoTAMQTTClient() def on_connect(mqttc, obj, flags, rc): mqttc.logger.info("rc: " + str(rc)) def on_connect_fail(mqttc, obj): mqttc.logger.info("Connect failed") def on_publish(mqttc, obj, mid): mqttc.logger.info("mid: " + str(mid)) def on_subscribe(mqttc, obj, mid, granted_qos): mqttc.logger.info("Subscribed: " + str(mid) + " " + str(granted_qos)) def on_log(mqttc, obj, level, string): mqttc.logger.info(string) self.mqttc.on_connect = on_connect self.mqttc.on_connect_fail = on_connect_fail self.mqttc.on_publish = on_publish self.mqttc.on_subscribe = on_subscribe self.mqttc.on_log = on_log
def __validate_device(self, device: Union[Device, Dict]) -> Device: """ Validates configuration of an IoT Device Args: device: device model to check on Returns: Device: validated model Raises: AssertionError: for faulty configurations """ if isinstance(device, dict): device = Device.parse_obj(device) assert isinstance(device, Device), "Invalid device configuration!" assert device.transport == TransportProtocol.MQTT, \ "Unsupported transport protocol found in device configuration!" if device.apikey in self.service_groups.keys(): pass # check if matching service group is registered else: msg = "Could not find matching service group! " \ "Communication may not work correctly!" self.logger.warning(msg=msg) warnings.warn(message=msg) return device
def test_metadata(self): """ Test for metadata works but the api of iot agent-json seems not working correctly Returns: None """ metadata = {"accuracy": {"type": "Text", "value": "+-5%"}} attr = DeviceAttribute(name="temperature", object_id="temperature", type="Number", metadata=metadata) device = Device(**self.device) device.device_id = "device_with_meta" device.add_attribute(attribute=attr) logger.info(device.json(indent=2)) with IoTAClient( url=settings.IOTA_JSON_URL, fiware_header=self.fiware_header) as client: client.post_device(device=device) logger.info(client.get_device(device_id=device.device_id).json( indent=2, exclude_unset=True)) with ContextBrokerClient( url=settings.CB_URL, fiware_header=self.fiware_header) as client: logger.info(client.get_entity(entity_id=device.entity_name).json( indent=2))
def get_device(self, *, device_id: str) -> Device: """ Returns all the information about a particular device. Args: device_id: Raises: requests.RequestException, if device does not exist Returns: Device """ url = urljoin(self.base_url, f'iot/devices/{device_id}') headers = self.headers try: res = self.get(url=url, headers=headers) if res.ok: return Device.parse_obj(res.json()) res.raise_for_status() except requests.RequestException as err: msg = f"Device {device_id} was not found" self.log_error(err=err, msg=msg) raise
def update_device(self, *, device: Device, add: bool = True) -> None: """ Updates a device from the device registry. Adds, removes attributes from the device entry and changes attributes values. It does not change device settings (endpoint,..) and only adds attributes to the corresponding entity, their it does not change any attribute value and does not delete removed attributes Args: device: add (bool): If device not found add it Returns: None """ url = urljoin(self.base_url, f'iot/devices/{device.device_id}') headers = self.headers try: res = self.put(url=url, headers=headers, json=device.dict(include={ 'attributes', 'lazy', 'commands', 'static_attributes' }, exclude_none=True)) if res.ok: self.logger.info("Device '%s' successfully updated!", device.device_id) elif (res.status_code == 404) & (add is True): self.post_device(device=device, update=False) else: res.raise_for_status() except requests.RequestException as err: msg = f"Could not update device '{device.device_id}'" self.log_error(err=err, msg=msg) raise
def test_device_model(self): device = Device(**self.device) self.assertEqual(self.device, device.dict(exclude_unset=True))
def test_patch_device(self): """ Test the methode: patch_device of the iota client """ device = Device(**self.device) device.endpoint = "http://test.com" device.transport = "MQTT" device.add_attribute(DeviceAttribute( name="Att1", object_id="o1", type=DataType.STRUCTUREDVALUE)) device.add_attribute(StaticDeviceAttribute( name="Stat1", value="test", type=DataType.STRUCTUREDVALUE)) device.add_attribute(StaticDeviceAttribute( name="Stat2", value="test", type=DataType.STRUCTUREDVALUE)) device.add_command(DeviceCommand(name="Com1")) # use patch_device to post self.client.patch_device(device=device) cb_client = ContextBrokerClient(url=settings.CB_URL, fiware_header=self.fiware_header) # test if attributes exists correctly live_entity = cb_client.get_entity(entity_id=device.entity_name) live_entity.get_attribute("Att1") live_entity.get_attribute("Com1") live_entity.get_attribute("Com1_info") live_entity.get_attribute("Com1_status") self.assertEqual(live_entity.get_attribute("Stat1").value, "test") # change device attributes and update device.get_attribute("Stat1").value = "new_test" device.delete_attribute(device.get_attribute("Stat2")) device.delete_attribute(device.get_attribute("Att1")) device.delete_attribute(device.get_attribute("Com1")) device.add_attribute(DeviceAttribute( name="Att2", object_id="o1", type=DataType.STRUCTUREDVALUE)) device.add_attribute(StaticDeviceAttribute( name="Stat3", value="test3", type=DataType.STRUCTUREDVALUE)) device.add_command(DeviceCommand(name="Com2")) self.client.patch_device(device=device, cb_url=settings.CB_URL) # test if update does what it should, for the device. It does not # change the entity completely: live_entity = cb_client.get_entity(entity_id=device.entity_name) with self.assertRaises(KeyError): live_entity.get_attribute("Att1") with self.assertRaises(KeyError): live_entity.get_attribute("Com1_info") with self.assertRaises(KeyError): live_entity.get_attribute("Stat2") self.assertEqual(live_entity.get_attribute("Stat1").value, "new_test") live_entity.get_attribute("Stat3") live_entity.get_attribute("Com2_info") live_entity.get_attribute("Att2") # test update where device information were changed device_settings = {"endpoint": "http://localhost:7071", "device_id": "new_id", "entity_name": "new_name", "entity_type": "new_type", "timestamp": False, "apikey": "zuiop", "protocol": "HTTP", "transport": "HTTP"} for key, value in device_settings.items(): device.__setattr__(key, value) self.client.patch_device(device=device) live_device = self.client.get_device(device_id=device.device_id) self.assertEqual(live_device.__getattribute__(key), value) cb_client.close()
def test_update_device(self): """ Test the methode: update_device of the iota client """ device = Device(**self.device) device.endpoint = "http://test.com" device.transport = "MQTT" device.add_attribute(DeviceAttribute( name="Att1", object_id="o1", type=DataType.STRUCTUREDVALUE)) device.add_attribute(StaticDeviceAttribute( name="Stat1", value="test", type=DataType.STRUCTUREDVALUE)) device.add_attribute(StaticDeviceAttribute( name="Stat2", value="test", type=DataType.STRUCTUREDVALUE)) device.add_command(DeviceCommand(name="Com1")) # use update_device to post self.client.update_device(device=device, add=True) cb_client = ContextBrokerClient(url=settings.CB_URL, fiware_header=self.fiware_header) # test if attributes exists correctly live_entity = cb_client.get_entity(entity_id=device.entity_name) live_entity.get_attribute("Att1") live_entity.get_attribute("Com1") live_entity.get_attribute("Com1_info") live_entity.get_attribute("Com1_status") self.assertEqual(live_entity.get_attribute("Stat1").value, "test") # change device attributes and update device.get_attribute("Stat1").value = "new_test" device.delete_attribute(device.get_attribute("Stat2")) device.delete_attribute(device.get_attribute("Att1")) device.delete_attribute(device.get_attribute("Com1")) device.add_attribute(DeviceAttribute( name="Att2", object_id="o1", type=DataType.STRUCTUREDVALUE)) device.add_attribute(StaticDeviceAttribute( name="Stat3", value="test3", type=DataType.STRUCTUREDVALUE)) device.add_command(DeviceCommand(name="Com2")) # device.endpoint = "http://localhost:8080" self.client.update_device(device=device) # test if update does what it should, for the device. It does not # change the entity completely: live_device = self.client.get_device(device_id=device.device_id) with self.assertRaises(KeyError): live_device.get_attribute("Att1") with self.assertRaises(KeyError): live_device.get_attribute("Com1_info") with self.assertRaises(KeyError): live_device.get_attribute("Stat2") self.assertEqual(live_device.get_attribute("Stat1").value, "new_test") live_device.get_attribute("Stat3") live_device.get_command("Com2") live_device.get_attribute("Att2") cb_client.close()
def test_deletions(self): """ Test the deletion of a context entity/device if the state is always correctly cleared """ self.tearDown() device_id = 'device_id' entity_id = 'entity_id' device = Device(device_id=device_id, entity_name=entity_id, entity_type='Thing2', protocol='IoTA-JSON', transport='HTTP', apikey='filip-iot-test-device') cb_client = ContextBrokerClient(url=settings.CB_URL, fiware_header=self.fiware_header) # Test 1: Only delete device # delete without optional parameter -> entity needs to continue existing self.client.post_device(device=device) self.client.delete_device(device_id=device_id, cb_url=settings.CB_URL) self.assertTrue( len(cb_client.get_entity_list(entity_ids=[entity_id])) == 1) cb_client.delete_entity(entity_id=entity_id, entity_type='Thing2') # Test 2:Delete device and corresponding entity # delete with optional parameter -> entity needs to be deleted self.client.post_device(device=device) self.client.delete_device(device_id=device_id, cb_url=settings.CB_URL, delete_entity=True) self.assertTrue( len(cb_client.get_entity_list(entity_ids=[entity_id])) == 0) # Test 3:Delete device and corresponding entity, # that is linked to multiple devices # delete with optional parameter -> entity needs to be deleted self.client.post_device(device=device) device2 = copy.deepcopy(device) device2.device_id = "device_id2" self.client.post_device(device=device2) self.assertRaises(Exception, self.client.delete_device, device_id=device_id, delete_entity=True) self.assertTrue( len(cb_client.get_entity_list(entity_ids=[entity_id])) == 1) self.client.delete_device(device_id=device2.device_id) # Test 4: Only delete entity # delete without optional parameter -> device needs to continue existing self.client.post_device(device=device) cb_client.delete_entity(entity_id=entity_id, entity_type='Thing2') self.client.get_device(device_id=device_id) self.client.delete_device(device_id=device_id) # Test 5: Delete entity, and all devices # # delete with optional parameter -> all devices need to be deleted self.client.post_device(device=device) device2 = copy.deepcopy(device) device2.device_id = "device_id2" self.client.post_device(device=device2) cb_client.delete_entity(entity_id=entity_id, delete_devices=True, entity_type='Thing2', iota_url=settings.IOTA_JSON_URL) self.assertEqual(len(self.client.get_device_list()), 0)
class TestMQTTClient(unittest.TestCase): """ Test case for IoTAMQTTClient """ def setUp(self) -> None: self.fiware_header = FiwareHeader( service=settings.FIWARE_SERVICE, service_path=settings.FIWARE_SERVICEPATH) self.service_group_json = ServiceGroup( apikey=settings.FIWARE_SERVICEPATH.strip('/'), resource="/iot/json") self.service_group_ul = ServiceGroup( apikey=settings.FIWARE_SERVICEPATH.strip('/'), resource="/iot/d") # create a device configuration device_attr = DeviceAttribute(name='temperature', object_id='t', type="Number") device_command = DeviceCommand(name='heater', type="Boolean") self.device_json = Device(device_id='my_json_device', entity_name='my_json_device', entity_type='Thing', protocol='IoTA-JSON', transport='MQTT', apikey=self.service_group_json.apikey, attributes=[device_attr], commands=[device_command]) self.device_ul = Device(device_id='my_ul_device', entity_name='my_ul_device', entity_type='Thing', protocol='PDI-IoTA-UltraLight', transport='MQTT', apikey=self.service_group_ul.apikey, attributes=[device_attr], commands=[device_command]) self.mqttc = IoTAMQTTClient() def on_connect(mqttc, obj, flags, rc): mqttc.logger.info("rc: " + str(rc)) def on_connect_fail(mqttc, obj): mqttc.logger.info("Connect failed") def on_publish(mqttc, obj, mid): mqttc.logger.info("mid: " + str(mid)) def on_subscribe(mqttc, obj, mid, granted_qos): mqttc.logger.info("Subscribed: " + str(mid) + " " + str(granted_qos)) def on_log(mqttc, obj, level, string): mqttc.logger.info(string) self.mqttc.on_connect = on_connect self.mqttc.on_connect_fail = on_connect_fail self.mqttc.on_publish = on_publish self.mqttc.on_subscribe = on_subscribe self.mqttc.on_log = on_log def test_original_functionality(self): """ demonstrate normal client behavior For additional examples on how to use the client please check: https://github.com/eclipse/paho.mqtt.python/tree/master/examples define callbacks methods""" first_topic = f"/filip/{settings.FIWARE_SERVICEPATH.strip('/')}/first" second_topic = f"/filip/{settings.FIWARE_SERVICEPATH.strip('/')}/second" first_payload = "filip_test_1" second_payload = "filip_test_2" def on_message_first(mqttc, obj, msg, properties=None): self.assertEqual(msg.payload.decode('utf-8'), first_payload) def on_message_second(mqttc, obj, msg, properties=None): self.assertEqual(msg.payload.decode('utf-8'), second_payload) self.mqttc.message_callback_add(sub=first_topic, callback=on_message_first) self.mqttc.message_callback_add(sub=second_topic, callback=on_message_second) mqtt_broker_url = urlparse(settings.MQTT_BROKER_URL) self.mqttc.connect(host=mqtt_broker_url.hostname, port=mqtt_broker_url.port, keepalive=60, bind_address="", bind_port=0, clean_start=MQTT_CLEAN_START_FIRST_ONLY, properties=None) self.mqttc.subscribe(topic=first_topic) # create a non blocking loop self.mqttc.loop_start() self.mqttc.publish(topic=first_topic, payload="filip_test") # add additional subscription to connection self.mqttc.subscribe(topic=second_topic) self.mqttc.publish(topic=second_topic, payload="filip_test") # remove subscriptions and callbacks self.mqttc.message_callback_remove(first_topic) self.mqttc.message_callback_remove(second_topic) self.mqttc.unsubscribe(first_topic) self.mqttc.unsubscribe(second_topic) # stop network loop and disconnect cleanly self.mqttc.loop_stop() self.mqttc.disconnect() def test_init(self): devices = [self.device_json, self.device_ul] mqttc = IoTAMQTTClient(devices=devices, service_groups=[self.service_group_json]) self.assertListEqual(mqttc.devices, devices) def test_service_groups(self): self.mqttc.add_service_group(service_group=self.service_group_json) with self.assertRaises(AssertionError): self.mqttc.add_service_group(service_group="SomethingRandom") with self.assertRaises(ValueError): self.mqttc.add_service_group( service_group=self.service_group_json.dict()) self.assertEqual( self.service_group_json, self.mqttc.get_service_group(self.service_group_json.apikey)) self.mqttc.update_service_group(service_group=self.service_group_json) with self.assertRaises(KeyError): self.mqttc.update_service_group( service_group=self.service_group_json.copy( update={'apikey': 'someOther'})) with self.assertRaises(KeyError): self.mqttc.delete_service_group(apikey="SomethingRandom") self.mqttc.delete_service_group(apikey=self.service_group_json.apikey) def test_devices(self): with self.assertRaises(ValueError): self.mqttc.devices = [self.device_ul, self.device_ul] self.mqttc.add_device(device=self.device_json) with self.assertRaises(ValueError): self.mqttc.add_device(device=self.device_json) self.mqttc.get_device(self.device_json.device_id) self.mqttc.update_device(device=self.device_json) with self.assertRaises(KeyError): self.mqttc.update_device(device=self.device_json.copy( update={'device_id': "somethingRandom"})) self.mqttc.delete_device(device_id=self.device_json.device_id) @clean_test(fiware_service=settings.FIWARE_SERVICE, fiware_servicepath=settings.FIWARE_SERVICEPATH, cb_url=settings.CB_URL, iota_url=settings.IOTA_JSON_URL) def test_add_command_callback_json(self): """ Test for receiving commands for a specific device Returns: None """ for group in self.mqttc.service_groups: self.mqttc.delete_service_group(group.apikey) for device in self.mqttc.devices: self.mqttc.delete_device(device.device_id) def on_command(client, obj, msg): apikey, device_id, payload = \ client.get_encoder(PayloadProtocol.IOTA_JSON).decode_message( msg=msg) # acknowledge a command. Here command are usually single # messages. The first key is equal to the commands name. client.publish(device_id=device_id, command_name=next(iter(payload)), payload=payload) self.mqttc.add_service_group(self.service_group_json) self.mqttc.add_device(self.device_json) self.mqttc.add_command_callback(device_id=self.device_json.device_id, callback=on_command) from filip.clients.ngsi_v2 import HttpClient, HttpClientConfig httpc_config = HttpClientConfig(cb_url=settings.CB_URL, iota_url=settings.IOTA_JSON_URL) httpc = HttpClient(fiware_header=self.fiware_header, config=httpc_config) httpc.iota.post_group(service_group=self.service_group_json, update=True) httpc.iota.post_device(device=self.device_json, update=True) mqtt_broker_url = urlparse(settings.MQTT_BROKER_URL) self.mqttc.connect(host=mqtt_broker_url.hostname, port=mqtt_broker_url.port, keepalive=60, bind_address="", bind_port=0, clean_start=MQTT_CLEAN_START_FIRST_ONLY, properties=None) self.mqttc.subscribe() entity = httpc.cb.get_entity(entity_id=self.device_json.device_id, entity_type=self.device_json.entity_type) context_command = NamedCommand(name=self.device_json.commands[0].name, value=False) self.mqttc.loop_start() httpc.cb.post_command(entity_id=entity.id, entity_type=entity.type, command=context_command) time.sleep(2) # close the mqtt listening thread self.mqttc.loop_stop() # disconnect the mqtt device self.mqttc.disconnect() entity = httpc.cb.get_entity(entity_id=self.device_json.device_id, entity_type=self.device_json.entity_type) # The main part of this test, for all this setup was done self.assertEqual("OK", entity.heater_status.value) @clean_test(fiware_service=settings.FIWARE_SERVICE, fiware_servicepath=settings.FIWARE_SERVICEPATH, cb_url=settings.CB_URL, iota_url=settings.IOTA_JSON_URL) def test_publish_json(self): """ Test for receiving commands for a specific device Returns: None """ for group in self.mqttc.service_groups: self.mqttc.delete_service_group(group.apikey) for device in self.mqttc.devices: self.mqttc.delete_device(device.device_id) self.mqttc.add_service_group(self.service_group_json) self.mqttc.add_device(self.device_json) from filip.clients.ngsi_v2 import HttpClient, HttpClientConfig httpc_config = HttpClientConfig(cb_url=settings.CB_URL, iota_url=settings.IOTA_JSON_URL) httpc = HttpClient(fiware_header=self.fiware_header, config=httpc_config) httpc.iota.post_group(service_group=self.service_group_json, update=True) httpc.iota.post_device(device=self.device_json, update=True) mqtt_broker_url = urlparse(settings.MQTT_BROKER_URL) self.mqttc.connect(host=mqtt_broker_url.hostname, port=mqtt_broker_url.port, keepalive=60, bind_address="", bind_port=0, clean_start=MQTT_CLEAN_START_FIRST_ONLY, properties=None) self.mqttc.loop_start() payload = randrange(0, 100, 1) / 1000 self.mqttc.publish( device_id=self.device_json.device_id, payload={self.device_json.attributes[0].object_id: payload}) time.sleep(1) entity = httpc.cb.get_entity(entity_id=self.device_json.device_id, entity_type=self.device_json.entity_type) self.assertEqual(payload, entity.temperature.value) payload = randrange(0, 100, 1) / 1000 self.mqttc.publish(device_id=self.device_json.device_id, attribute_name="temperature", payload=payload) time.sleep(1) entity = httpc.cb.get_entity(entity_id=self.device_json.device_id, entity_type=self.device_json.entity_type) self.assertEqual(payload, entity.temperature.value) # These test do currently not workt due to time stamp parsing # self.mqttc.publish(device_id=self.device_json.device_id, # payload={self.device_json.attributes[ # 0].object_id: 50}, # timestamp=True) # time.sleep(1) # entity = httpc.cb.get_entity(entity_id=self.device_json.device_id, # entity_type=self.device_json.entity_type) # self.assertEqual(50, entity.temperature.value) # # from datetime import datetime, timedelta # timestamp = datetime.now() + timedelta(days=1) # timestamp = timestamp.astimezone().isoformat() # self.mqttc.publish(device_id=self.device_json.device_id, # payload={self.device_json.attributes[ # 0].object_id: 60, # 'timeInstant': timestamp}) # time.sleep(1) # entity = httpc.cb.get_entity(entity_id=self.device_json.device_id, # entity_type=self.device_json.entity_type) # self.assertEqual(60, entity.temperature.value) # self.assertEqual(timestamp, entity.TimeInstant.value) # # print(entity.json(indent=2)) # close the mqtt listening thread self.mqttc.loop_stop() # disconnect the mqtt device self.mqttc.disconnect() @clean_test(fiware_service=settings.FIWARE_SERVICE, fiware_servicepath=settings.FIWARE_SERVICEPATH, cb_url=settings.CB_URL, iota_url=settings.IOTA_UL_URL) def test_add_command_callback_ultralight(self): """ Test for receiving commands for a specific device Returns: None """ for group in self.mqttc.service_groups: self.mqttc.delete_service_group(group.apikey) for device in self.mqttc.devices: self.mqttc.delete_device(device.device_id) def on_command(client, obj, msg): apikey, device_id, payload = \ client.get_encoder(PayloadProtocol.IOTA_UL).decode_message( msg=msg) # acknowledge a command. Here command are usually single # messages. The first key is equal to the commands name. client.publish(device_id=device_id, command_name=next(iter(payload)), payload={'heater': True}) self.mqttc.add_service_group(self.service_group_ul) self.mqttc.add_device(self.device_ul) self.mqttc.add_command_callback(device_id=self.device_ul.device_id, callback=on_command) from filip.clients.ngsi_v2 import HttpClient, HttpClientConfig httpc_config = HttpClientConfig(cb_url=settings.CB_URL, iota_url=settings.IOTA_UL_URL) httpc = HttpClient(fiware_header=self.fiware_header, config=httpc_config) httpc.iota.post_group(service_group=self.service_group_ul) httpc.iota.post_device(device=self.device_ul, update=True) mqtt_broker_url = urlparse(settings.MQTT_BROKER_URL) self.mqttc.connect(host=mqtt_broker_url.hostname, port=mqtt_broker_url.port, keepalive=60, bind_address="", bind_port=0, clean_start=MQTT_CLEAN_START_FIRST_ONLY, properties=None) self.mqttc.subscribe() entity = httpc.cb.get_entity(entity_id=self.device_ul.device_id, entity_type=self.device_ul.entity_type) context_command = NamedCommand(name=self.device_ul.commands[0].name, value=False) self.mqttc.loop_start() httpc.cb.post_command(entity_id=entity.id, entity_type=entity.type, command=context_command) time.sleep(5) # close the mqtt listening thread self.mqttc.loop_stop() # disconnect the mqtt device self.mqttc.disconnect() entity = httpc.cb.get_entity(entity_id=self.device_ul.device_id, entity_type=self.device_ul.entity_type) # The main part of this test, for all this setup was done self.assertEqual("OK", entity.heater_status.value) @clean_test(fiware_service=settings.FIWARE_SERVICE, fiware_servicepath=settings.FIWARE_SERVICEPATH, cb_url=settings.CB_URL, iota_url=settings.IOTA_UL_URL) def test_publish_ultralight(self): """ Test for receiving commands for a specific device Returns: None """ for group in self.mqttc.service_groups: self.mqttc.delete_service_group(group.apikey) for device in self.mqttc.devices: self.mqttc.delete_device(device.device_id) self.mqttc.add_service_group(self.service_group_ul) self.mqttc.add_device(self.device_ul) from filip.clients.ngsi_v2 import HttpClient, HttpClientConfig httpc_config = HttpClientConfig(cb_url=settings.CB_URL, iota_url=settings.IOTA_UL_URL) httpc = HttpClient(fiware_header=self.fiware_header, config=httpc_config) httpc.iota.post_group(service_group=self.service_group_ul, update=True) httpc.iota.post_device(device=self.device_ul, update=True) time.sleep(0.5) mqtt_broker_url = urlparse(settings.MQTT_BROKER_URL) self.mqttc.connect(host=mqtt_broker_url.hostname, port=mqtt_broker_url.port, keepalive=60, bind_address="", bind_port=0, clean_start=MQTT_CLEAN_START_FIRST_ONLY, properties=None) self.mqttc.loop_start() payload = randrange(0, 100, 1) / 1000 self.mqttc.publish( device_id=self.device_ul.device_id, payload={self.device_ul.attributes[0].object_id: payload}) time.sleep(1) entity = httpc.cb.get_entity(entity_id=self.device_ul.device_id, entity_type=self.device_ul.entity_type) self.assertEqual(payload, entity.temperature.value) payload = randrange(0, 100, 1) / 1000 self.mqttc.publish(device_id=self.device_ul.device_id, attribute_name="temperature", payload=payload) time.sleep(1) entity = httpc.cb.get_entity(entity_id=self.device_ul.device_id, entity_type=self.device_ul.entity_type) self.assertEqual(payload, entity.temperature.value) # These test do currently not workt due to time stamp parsing # self.mqttc.publish(device_id=self.device_ul.device_id, # payload={self.device_ul.attributes[0].object_id: # 50}, # timestamp=True) # time.sleep(1) # entity = httpc.cb.get_entity(entity_id=self.device_ul.device_id, # entity_type=self.device_ul.entity_type) # self.assertEqual(50, entity.temperature.value) # # from datetime import datetime, timedelta # timestamp = datetime.now() + timedelta(days=1) # timestamp = timestamp.astimezone().isoformat() # self.mqttc.publish(device_id=self.device_ul.device_id, # payload={self.device_ul.attributes[0].object_id: # 60, # 'timeInstant': timestamp}) # time.sleep(1) # entity = httpc.cb.get_entity(entity_id=self.device_ul.device_id, # entity_type=self.device_ul.entity_type) # self.assertEqual(60, entity.temperature.value) # self.assertEqual(timestamp, entity.TimeInstant.value) # # print(entity.json(indent=2)) # close the mqtt listening thread self.mqttc.loop_stop() # disconnect the mqtt device self.mqttc.disconnect() def tearDown(self) -> None: """ Cleanup test server """ clear_all(fiware_header=self.fiware_header, cb_url=settings.CB_URL, iota_url=[settings.IOTA_JSON_URL, settings.IOTA_UL_URL])
def test_fiware_safe_fields(self): """ Tests all fields of models/ngsi_v2/iot.py that have a regex to be FIWARE safe Returns: None """ from pydantic.error_wrappers import ValidationError valid_strings: List[str] = ["name", "test123", "3_:strange-Name!"] invalid_strings: List[str] = [ "my name", "Test?", "#False", "/notvalid" ] special_strings: List[str] = ["id", "type", "geo:location"] # Test if all needed fields, detect all invalid strings for string in invalid_strings: self.assertRaises(ValidationError, IoTABaseAttribute, name=string, type="name", entity_name="name", entity_type="name") self.assertRaises(ValidationError, IoTABaseAttribute, name="name", type=string, entity_name="name", entity_type="name") self.assertRaises(ValidationError, IoTABaseAttribute, name="name", type="name", entity_name=string, entity_type="name") self.assertRaises(ValidationError, IoTABaseAttribute, name="name", type="name", entity_name="name", entity_type=string) self.assertRaises(ValidationError, DeviceCommand, name=string, type="name") self.assertRaises(ValidationError, ServiceGroup, entity_type=string, resource="", apikey="") self.assertRaises(ValidationError, Device, device_id="", entity_name=string, entity_type="name", transport=TransportProtocol.HTTP) self.assertRaises(ValidationError, Device, device_id="", entity_name="name", entity_type=string, transport=TransportProtocol.HTTP) # Test if all needed fields, do not trow wrong errors for string in valid_strings: IoTABaseAttribute(name=string, type=string, entity_name=string, entity_type=string) DeviceCommand(name=string, type="name") ServiceGroup(entity_type=string, resource="", apikey="") Device(device_id="", entity_name=string, entity_type=string, transport=TransportProtocol.HTTP) # Test for the special-string protected field if all strings are blocked for string in special_strings: with self.assertRaises(ValidationError): IoTABaseAttribute(name=string, type="name", entity_name="name", entity_type="name") with self.assertRaises(ValidationError): IoTABaseAttribute(name="name", type=string, entity_name="name", entity_type="name") with self.assertRaises(ValidationError): DeviceCommand(name=string, type="name") # Test for the normal protected field if all strings are allowed for string in special_strings: IoTABaseAttribute(name="name", type="name", entity_name=string, entity_type=string) ServiceGroup(entity_type=string, resource="", apikey="") Device(device_id="", entity_name=string, entity_type=string, transport=TransportProtocol.HTTP)
# ## 1.2 Device configuration # service_group_json = ServiceGroup(apikey=SERVICE_PATH.strip('/'), resource="/iot/json") service_group_ul = ServiceGroup(apikey=SERVICE_PATH.strip('/'), resource="/iot/d") device_attr = DeviceAttribute(name='temperature', object_id='t', type="Number") device_command = DeviceCommand(name='heater', type="Boolean") device_json = Device(device_id='my_json_device', entity_name='my_json_device', entity_type='Thing', protocol='IoTA-JSON', transport='MQTT', apikey=service_group_json.apikey, attributes=[device_attr], commands=[device_command]) device_ul = Device(device_id='my_ul_device', entity_name='my_ul_device', entity_type='Thing', protocol='PDI-IoTA-UltraLight', transport='MQTT', apikey=service_group_ul.apikey, attributes=[device_attr], commands=[device_command]) # ## 1.3 IoTAMQTTClient #
# post it to the IoT-Agent. It should be mapped to the `type` heater # create the simtime attribute and add during device creation t_sim = DeviceAttribute(name='simtime', object_id='t_sim', type="Number") # ToDo: create the command attribute of name `heater_on` (currently it is # not possible to add metadata here) cmd = DeviceCommand(name="heater_on", type=DataType.BOOLEAN) # ToDo: create the device configuration and send it to the server heater = Device(device_id="device:003", entity_name="urn:ngsi-ld:Heater:001", entity_type="Heater", apikey=APIKEY, attributes=[t_sim], commands=[cmd], transport='MQTT', protocol='IoTA-JSON') iotac.post_device(device=heater) # ToDo: Check the entity that corresponds to your device heater_entity = cbc.get_entity(entity_id=heater.entity_name, entity_type=heater.entity_type) print(f"Your device entity before running the simulation: \n " f"{heater_entity.json(indent=2)}") # create a MQTTv5 client with paho-mqtt and the known groups and devices. mqttc = IoTAMQTTClient(protocol=mqtt.MQTTv5, devices=[weather_station,
# Create a service group and add it to your service_group = ServiceGroup(apikey=APIKEY, resource="/iot/json") # ToDo: create two IoTA-MQTT devices for the weather station and the zone # temperature sensor. Also add the simulation time as `active attribute` # to each device! # # create the weather station device # create the simtime attribute and add during device creation t_sim = DeviceAttribute(name='simtime', object_id='t_sim', type="Number") weather_station = Device(device_id='device:001', entity_name='urn:ngsi-ld:WeatherStation:001', entity_type='WeatherStation', protocol='IoTA-JSON', transport='MQTT', apikey=APIKEY, attributes=[t_sim], commands=[]) # create a temperature attribute and add it via the api of the # `device`-model. Use the 't_amb' as `object_id`. `object_id` specifies # what key will be used in the MQTT Message payload t_amb = DeviceAttribute(name='temperature', object_id='t_amb', type="Number") weather_station.add_attribute(t_amb) # ToDo: create the zone temperature device add the t_sim attribute upon # creation
def test_command_with_mqtt(self): """ Test if a command can be send to a device in FIWARE To test this a virtual device is created and provisioned to FIWARE and a hosted MQTT Broker This test only works if the address of a running MQTT Broker is given in the environment ('MQTT_BROKER_URL') The main part of this test was taken out of the iot_mqtt_example, see there for a complete documentation """ mqtt_broker_url = settings.MQTT_BROKER_URL device_attr1 = DeviceAttribute(name='temperature', object_id='t', type="Number", metadata={ "unit": {"type": "Unit", "value": { "name": { "type": "Text", "value": "degree " "Celsius" } }} }) # creating a static attribute that holds additional information static_device_attr = StaticDeviceAttribute(name='info', type="Text", value="Filip example for " "virtual IoT device") # creating a command that the IoT device will liston to device_command = DeviceCommand(name='heater', type="Boolean") device = Device(device_id='MyDevice', entity_name='MyDevice', entity_type='Thing2', protocol='IoTA-JSON', transport='MQTT', apikey=settings.FIWARE_SERVICEPATH.strip('/'), attributes=[device_attr1], static_attributes=[static_device_attr], commands=[device_command]) device_attr2 = DeviceAttribute(name='humidity', object_id='h', type="Number", metadata={ "unitText": {"value": "percent", "type": "Text"}}) device.add_attribute(attribute=device_attr2) # Send device configuration to FIWARE via the IoT-Agent. We use the # general ngsiv2 httpClient for this. service_group = ServiceGroup( service=self.fiware_header.service, subservice=self.fiware_header.service_path, apikey=settings.FIWARE_SERVICEPATH.strip('/'), resource='/iot/json') # create the Http client node that once sent the device cannot be posted # again and you need to use the update command config = HttpClientConfig(cb_url=settings.CB_URL, iota_url=settings.IOTA_JSON_URL) client = HttpClient(fiware_header=self.fiware_header, config=config) client.iota.post_group(service_group=service_group, update=True) client.iota.post_device(device=device, update=True) time.sleep(0.5) # check if the device is correctly configured. You will notice that # unfortunately the iot API does not return all the metadata. However, # it will still appear in the context-entity device = client.iota.get_device(device_id=device.device_id) # check if the data entity is created in the context broker entity = client.cb.get_entity(entity_id=device.device_id, entity_type=device.entity_type) # create a mqtt client that we use as representation of an IoT device # following the official documentation of Paho-MQTT. # https://www.eclipse.org/paho/index.php?page=clients/python/ # docs/index.php # The callback for when the mqtt client receives a CONNACK response from # the server. All callbacks need to have this specific arguments, # Otherwise the client will not be able to execute them. def on_connect(client, userdata, flags, reasonCode, properties=None): client.subscribe(f"/{device.apikey}/{device.device_id}/cmd") # Callback when the command topic is succesfully subscribed def on_subscribe(client, userdata, mid, granted_qos, properties=None): pass # NOTE: We need to use the apikey of the service-group to send the # message to the platform def on_message(client, userdata, msg): data = json.loads(msg.payload) res = {k: v for k, v in data.items()} client.publish(topic=f"/json/{service_group.apikey}" f"/{device.device_id}/cmdexe", payload=json.dumps(res)) def on_disconnect(client, userdata, reasonCode, properties=None): pass mqtt_client = mqtt.Client(client_id="filip-test", userdata=None, protocol=mqtt.MQTTv5, transport="tcp") # add our callbacks to the client mqtt_client.on_connect = on_connect mqtt_client.on_subscribe = on_subscribe mqtt_client.on_message = on_message mqtt_client.on_disconnect = on_disconnect # extract the form the environment mqtt_broker_url = urlparse(mqtt_broker_url) mqtt_client.connect(host=mqtt_broker_url.hostname, port=mqtt_broker_url.port, keepalive=60, bind_address="", bind_port=0, clean_start=mqtt.MQTT_CLEAN_START_FIRST_ONLY, properties=None) # create a non-blocking thread for mqtt communication mqtt_client.loop_start() for attr in device.attributes: mqtt_client.publish( topic=f"/json/{service_group.apikey}/{device.device_id}/attrs", payload=json.dumps({attr.object_id: random.randint(0, 9)})) time.sleep(5) entity = client.cb.get_entity(entity_id=device.device_id, entity_type=device.entity_type) # create and send a command via the context broker context_command = NamedCommand(name=device_command.name, value=False) client.cb.post_command(entity_id=entity.id, entity_type=entity.type, command=context_command) time.sleep(5) # check the entity the command attribute should now show OK entity = client.cb.get_entity(entity_id=device.device_id, entity_type=device.entity_type) # The main part of this test, for all this setup was done self.assertEqual("OK", entity.heater_status.value) # close the mqtt listening thread mqtt_client.loop_stop() # disconnect the mqtt device mqtt_client.disconnect()
def test_device_endpoints(self): """ Test device creation """ with IoTAClient( url=settings.IOTA_JSON_URL, fiware_header=self.fiware_header) as client: client.get_device_list() device = Device(**self.device) attr = DeviceAttribute(name='temperature', object_id='t', type='Number', entity_name='test') attr_command = DeviceCommand(name='open') attr_lazy = LazyDeviceAttribute(name='pressure', object_id='p', type='Text', entity_name='pressure') attr_static = StaticDeviceAttribute(name='hasRoom', type='Relationship', value='my_partner_id') device.add_attribute(attr) device.add_attribute(attr_command) device.add_attribute(attr_lazy) device.add_attribute(attr_static) client.post_device(device=device) device_res = client.get_device(device_id=device.device_id) self.assertEqual(device.dict(exclude={'service', 'service_path', 'timezone'}), device_res.dict(exclude={'service', 'service_path', 'timezone'})) self.assertEqual(self.fiware_header.service, device_res.service) self.assertEqual(self.fiware_header.service_path, device_res.service_path)
static_device_attr = StaticDeviceAttribute(name='info', type="Text", value="Filip example for " "virtual IoT device") # creating a command that the IoT device will liston to device_command = DeviceCommand(name='heater') # NOTE: You need to know that if you define an apikey for a single device it # will be only used for outgoing traffic. This is does not become very clear # in the official documentation. # https://fiware-iotagent-json.readthedocs.io/en/latest/usermanual/index.html device = Device(device_id='MyDevice', entity_name='MyDevice', entity_type='Thing2', protocol='IoTA-JSON', transport='MQTT', apikey=DEVICE_APIKEY, attributes=[device_attr1], static_attributes=[static_device_attr], commands=[device_command]) # You can also add additional attributes via the Device API device_attr2 = DeviceAttribute( name='humidity', object_id='h', type="Number", metadata={"unitText": { "value": "percent", "type": "Text" }})
def patch_device(self, device: Device, patch_entity: bool = True, cb_url: AnyHttpUrl = settings.CB_URL) -> None: """ Updates a device state in Fiware, if the device does not exists it is created, else its values are updated. If the device settings (endpoint,..) were changed the device and entity are deleted and re-added. If patch_entity is true the corresponding entity in the ContextBroker is also correctly updated. Else only new attributes are added there. Args: device (Device): Device to be posted to /updated in Fiware patch_entity (bool): If true the corresponding entity is completely synced cb_url (AnyHttpUrl): Url of the ContextBroker where the entity is found Returns: None """ try: live_device = self.get_device(device_id=device.device_id) except requests.RequestException: # device does not exist yet, post it self.post_device(device=device) return # if the device settings were changed we need to delete the device # and repost it settings_dict = { "device_id", "service", "service_path", "entity_name", "entity_type", "timestamp", "apikey", "endpoint", "protocol", "transport", "expressionLanguage" } import json live_settings = live_device.dict(include=settings_dict) new_settings = device.dict(include=settings_dict) if not live_settings == new_settings: self.delete_device(device_id=device.device_id, delete_entity=True, force_entity_deletion=True) self.post_device(device=device) return # We are at a state where the device exists, but only attributes were # changed. # we need to update the device, and the context entry separately, # as update device only takes over a part of the changes to the # ContextBroker. # update device self.update_device(device=device) # update context entry # 1. build context entity from information in device # 2. patch it from filip.models.ngsi_v2.context import \ ContextEntity, NamedContextAttribute def build_context_entity_from_device(device: Device) -> ContextEntity: from filip.models.base import DataType entity = ContextEntity(id=device.entity_name, type=device.entity_type) for command in device.commands: entity.add_attributes([ # Command attribute will be registered by the device_update NamedContextAttribute(name=f"{command.name}_info", type=DataType.COMMAND_RESULT), NamedContextAttribute(name=f"{command.name}_status", type=DataType.COMMAND_STATUS) ]) for attribute in device.attributes: entity.add_attributes([ NamedContextAttribute(name=attribute.name, type=DataType.STRUCTUREDVALUE, metadata=attribute.metadata) ]) for static_attribute in device.static_attributes: entity.add_attributes([ NamedContextAttribute(name=static_attribute.name, type=static_attribute.type, value=static_attribute.value, metadata=static_attribute.metadata) ]) return entity if patch_entity: from filip.clients.ngsi_v2 import ContextBrokerClient with ContextBrokerClient( url=cb_url, fiware_header=self.fiware_headers) as client: client.patch_entity( entity=build_context_entity_from_device(device))
"entity_name": "sensor1", "entity_type": "Sensor", "timezone": 'Europe/Berlin', "timestamp": True, "apikey": "1234", "protocol": "IoTA-UL", "transport": "MQTT", "lazy": [], "commands": [], "attributes": [], "static_attributes": [], "internal_attributes": [], "explicitAttrs": False, "ngsiVersion": "v2" } device1 = Device(**example_device) # Direct Parameters: device2 = Device(device_id="sensor009", service=SERVICE, service_path=SERVICE_PATH, entity_name="sensor2", entity_type="Sensor", transport=TransportProtocol.HTTP, endpoint="http://localhost:1234") # ## 2.2 Device Attributes # # To a device attributes can be added, they will automatically be # mirrored to the related context entity. # Each attribute needs a unique name.
zone_temperature_sensor = ... # ToDo: Get the service group configurations from the server group = iotac.get_group(resource="/iot/json", apikey=...) # ToDo: Create and additional device holding a command attribute and # post it to the IoT-Agent. It should be mapped to the `type` heater # create the simtime attribute and add during device creation t_sim = DeviceAttribute(name='simtime', object_id='t_sim', type="Number") # ToDo: create the command attribute of name `heater_on` (currently it is # not possible to add metadata here) cmd = DeviceCommand(name=..., type=...) # ToDo: create the device configuration and send it to the server name it heater = Device(...) iotac.post_device(device=heater) # ToDo: Check the entity that corresponds to your device heater_entity = cbc.get_entity(entity_id=heater.entity_name, entity_type=heater.entity_type) print(f"Your device entity before running the simulation: \n " f"{heater_entity.json(indent=2)}") # create a MQTTv5 client with paho-mqtt and the known groups and devices. mqttc = IoTAMQTTClient( protocol=mqtt.MQTTv5, devices=[weather_station, zone_temperature_sensor, heater], service_groups=[group]) # set user data if required