cbc.post_entity(entity=entity) iotac = IoTAClient(url=IOTA_URL, fiware_header=fiware_header) iotac.post_groups(service_groups=groups) iotac.post_devices(devices=devices) # ToDo: Retrieve all iot resources from the IoT-Agent # Get the group and device configurations from the server group = iotac.get_group(resource="/iot/json", apikey=APIKEY) weather_station = iotac.get_device(device_id="device:001") zone_temperature_sensor = iotac.get_device(device_id="device:002") heater = iotac.get_device(device_id="device:003") # ToDo: Get context entities from the Context Broker # (exclude the IoT device ones) building = cbc.get_entity(entity_id="urn:ngsi-ld:building:001", entity_type="Building") thermal_zone = cbc.get_entity(entity_id="ThermalZone:001", entity_type="ThermalZone") # ToDo: Semantically connect the weather station and the building. By # adding a `hasWeatherStation` attribute of type `Relationship`. For the # connection from the weather station to the building add a static # attribute to the weather station # create the context attribute for the building and add it to the # building entity has_weather_station = NamedContextAttribute( name="hasWeatherStation", type="Relationship", value=weather_station.entity_name) building.add_attributes(attrs=[has_weather_station])
def simulation( TEMPERATURE_MAX=10, # maximal ambient temperature TEMPERATURE_MIN=-5, # minimal ambient temperature TEMPERATURE_ZONE_START=10, # start value of the zone temperature T_SIM_START=0, # simulation start time in seconds T_SIM_END=24 * 60 * 60, # simulation end time in seconds COM_STEP=60 * 15, # 1 min communication step in seconds SLEEP_TIME=0.2 # sleep time between every simulation step ): # create a fiware header object fiware_header = FiwareHeader(service=SERVICE, service_path=SERVICE_PATH) # instantiate simulation model sim_model = SimulationModel(t_start=T_SIM_START, t_end=T_SIM_END, temp_max=TEMPERATURE_MAX, temp_min=TEMPERATURE_MIN, temp_start=TEMPERATURE_ZONE_START) # Create clients and restore devices and groups from files groups = parse_file_as(List[ServiceGroup], READ_GROUPS_FILEPATH) devices = parse_file_as(List[Device], READ_DEVICES_FILEPATH) cbc = ContextBrokerClient(url=CB_URL, fiware_header=fiware_header) iotac = IoTAClient(url=IOTA_URL, fiware_header=fiware_header) iotac.post_groups(service_groups=groups, update=True) iotac.post_devices(devices=devices, update=True) # Get the device configurations from the server weather_station = iotac.get_device(device_id="device:001") zone_temperature_sensor = iotac.get_device(device_id="device:002") heater = iotac.get_device(device_id="device:003") # Get the service group configurations from the server group = iotac.get_group(resource="/iot/json", apikey=APIKEY) # Create a http subscriptions that get triggered by updates of your # device attributes and send data to Quantum Leap. qlc = QuantumLeapClient(url=QL_URL, fiware_header=fiware_header) qlc.post_subscription(entity_id=weather_station.entity_name, entity_type=weather_station.entity_type, cb_url="http://orion:1026", ql_url="http://quantumleap:8668", throttling=0) qlc.post_subscription(entity_id=zone_temperature_sensor.entity_name, entity_type=zone_temperature_sensor.entity_type, cb_url="http://orion:1026", ql_url="http://quantumleap:8668", throttling=0) qlc.post_subscription(entity_id=heater.entity_name, entity_type=heater.entity_type, cb_url="http://orion:1026", ql_url="http://quantumleap:8668", throttling=0) # 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 mqttc.username_pw_set(username=MQTT_USER, password=MQTT_PW) # Implement a callback function that gets triggered when the # command is sent to the device. The incoming command should update the # heater power of the simulation model def on_command(client, obj, msg): """ Callback for incoming commands """ # Decode the message payload using the libraries builtin encoders apikey, device_id, payload = \ client.get_encoder(PayloadProtocol.IOTA_JSON).decode_message( msg=msg) # Update the heating power of the simulation model sim_model.heater_power = payload["heater_power"] # Acknowledge the command. client.publish(device_id=device_id, command_name=next(iter(payload)), payload=payload) # Add the command callback to your MQTTClient. This will get # triggered for the specified device_id mqttc.add_command_callback(device_id=heater.device_id, callback=on_command) # connect to the mqtt broker and subscribe to your topic mqtt_url = urlparse(MQTT_BROKER_URL_EXPOSED) mqttc.connect(host=mqtt_url.hostname, port=mqtt_url.port, keepalive=60, bind_address="", bind_port=0, clean_start=mqtt.MQTT_CLEAN_START_FIRST_ONLY, properties=None) # subscribe to all incoming command topics for the registered devices mqttc.subscribe() # create a non-blocking thread for mqtt communication mqttc.loop_start() # define lists to store historical data history_weather_station = [] history_zone_temperature_sensor = [] history_heater_power = [] # simulation without heater # Create a loop that publishes regularly a message to the broker # that holds the simulation time "simtime" and the corresponding # temperature "temperature" the loop should. You may use the `object_id` # or the attribute name as key in your payload. print("Simulation starts") for t_sim in range(sim_model.t_start, sim_model.t_end + int(COM_STEP), int(COM_STEP)): # publish the simulated ambient temperature mqttc.publish(device_id=weather_station.device_id, payload={ "temperature": sim_model.t_amb, "simtime": sim_model.t_sim }) # publish the simulated zone temperature mqttc.publish(device_id=zone_temperature_sensor.device_id, payload={ "temperature": sim_model.t_zone, "simtime": sim_model.t_sim }) # publish the 'simtime' for the heater device mqttc.publish(device_id=heater.device_id, payload={"simtime": sim_model.t_sim}) # simulation step for next loop sim_model.do_step(int(t_sim + COM_STEP)) # wait for one second before publishing the next values time.sleep(SLEEP_TIME) # Get corresponding entities and write values to history weather_station_entity = cbc.get_entity( entity_id=weather_station.entity_name, entity_type=weather_station.entity_type) # append the data to the local history history_weather_station.append({ "simtime": weather_station_entity.simtime.value, "temperature": weather_station_entity.temperature.value }) # Get ZoneTemperatureSensor and write values to history zone_temperature_sensor_entity = cbc.get_entity( entity_id=zone_temperature_sensor.entity_name, entity_type=zone_temperature_sensor.entity_type) history_zone_temperature_sensor.append({ "simtime": zone_temperature_sensor_entity.simtime.value, "temperature": zone_temperature_sensor_entity.temperature.value }) # Get ZoneTemperatureSensor and write values to history heater_entity = cbc.get_entity(entity_id=heater.entity_name, entity_type=heater.entity_type) history_heater_power.append({ "simtime": heater_entity.simtime.value, "heater_power": sim_model.heater_power }) # close the mqtt listening thread mqttc.loop_stop() # disconnect the mqtt device mqttc.disconnect() clear_iot_agent(url=IOTA_URL, fiware_header=fiware_header) clear_context_broker(url=CB_URL, fiware_header=fiware_header) return history_weather_station, history_zone_temperature_sensor, history_heater_power
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_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()
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, zone_temperature_sensor, heater], service_groups=[group]) # set user data if required mqttc.username_pw_set(username=MQTT_USER, password=MQTT_PW) # ToDo: Implement a callback function that gets triggered when the # command is sent to the device. The incoming command schould update the # heater attribute of the simulation model
# ToDo: Create an IoTAClient iotac = ... # ToDo: Provision service group and add it to your IoTAMQTTClient ... # ToDo: Provision the devices at the IoTA-Agent # provision the WeatherStation device iotac.post_device(device=weather_station, update=True) # ToDo: provision the zone temperature device ... # ToDo: Check in the context broker if the entities corresponding to your # devices where correctly created # ToDo: Create a context broker client cbc = ContextBrokerClient(url=CB_URL, fiware_header=fiware_header) # Get WeatherStation entity print(cbc.get_entity(weather_station.entity_name).json(indent=2)) # ToDo: Get ZoneTemperatureSensor entity print(...) # ToDo: create an MQTTv5 client using filip.clients.mqtt.IoTAMQTTClient mqttc = IoTAMQTTClient(protocol=...) # ToDo: Register the service group with your MQTT-Client mqttc.add_service_group(service_group=service_group) # ToDo: Register devices with your MQTT-Client # register the weather station mqttc.add_device(weather_station) # ToDo: register the zone temperature sensor ... # The IoTAMQTTClient automatically creates the outgoing topics from the # device configuration during runtime. Hence, we need to construct them
value="Small office building " "with good insulation " "standard") # add all properties to your building using the # `add_attribute` function of your building object building.add_attributes(attrs=[building_description, category, address]) # ToDo: Create a context broker client and add the fiware_header cbc = ContextBrokerClient(url=CB_URL, fiware_header=fiware_header) # ToDo: Send your building model to the context broker. Check the client # for the proper function cbc.post_entity(entity=building) # Update your local building model with the one from the server building = cbc.get_entity(entity_id=building.id, entity_type=building.type) # print your `building model` as json print(f"This is your building model: \n {building.json(indent=2)} \n") # ToDo: create a `opening hours` property and add it to the building object # in the context broker. Do not update the whole entity! In real # scenarios it might have been modified by other users. opening_hours = NamedContextAttribute( name="openingHours", type="array", value=["Mo-Fr 10:00-19:00", "Sa closed", "Su closed"]) cbc.update_or_append_entity_attributes(entity_id=building.id, entity_type=building.type, attrs=[opening_hours])
class Control: """PID controller with FIWARE interface""" def __init__(self): """Initialization""" # # use envirnment variables # os.environ["CONFIG_FILE"] = "False" # define parameters self.params = {} self.params['controller_name'] = os.getenv("CONTROLLER_NAME", 'PID_1') self.params['type'] = "PID_Controller" self.params['setpoint'] = float(os.getenv("SETPOINT", '293.15')) # reverse mode can be activated by passing negative tunings to controller self.params['Kp'] = float(os.getenv("KP", '1.0')) self.params['Ki'] = float(os.getenv("KI", '0')) self.params['Kd'] = float(os.getenv("KD", '0')) self.params['lim_low'] = float(os.getenv("LIM_LOW", '0')) self.params['lim_upper'] = float(os.getenv("LIM_UPPER", '100')) self.params['pause_time'] = float(os.getenv("PAUSE_TIME", 0.2)) self.params['sensor_entity_name'] = os.getenv("SENSOR_ENTITY_NAME", '') self.params['sensor_type'] = os.getenv("SENSOR_TYPE", None) self.params['sensor_attr'] = os.getenv("SENSOR_ATTR", '') self.params['actuator_entity_name'] = os.getenv( "ACTUATOR_ENTITY_NAME", '') self.params['actuator_type'] = os.getenv("ACTUATOR_TYPE", '') self.params['actuator_command'] = os.getenv("ACTUATOR_COMMAND", '') self.params['actuator_command_value'] = self.params[ 'actuator_command'] + '_info' self.params['service'] = os.getenv("FIWARE_SERVICE", '') self.params['service_path'] = os.getenv("FIWARE_SERVICE_PATH", '') self.params['cb_url'] = os.getenv("CB_URL", "http://localhost:1026") # settings for security mode self.security_mode = os.getenv("SECURITY_MODE", 'False').lower() in ('true', '1', 'yes') self.params['token'] = (None, None) # Get token from keycloak in security mode if self.security_mode: self.kp = KeycloakPython() self.params['token'] = self.kp.get_access_token() # Create simple pid instance self.pid = PID(self.params['Kp'], self.params['Ki'], self.params['Kd'], setpoint=self.params['setpoint'], output_limits=(self.params['lim_low'], self.params['lim_upper'])) # Additional parameters self.auto_mode = True self.u = None # control variable u self.y_act = self.params[ 'setpoint'] # set the initial measurement to set point # Create the fiware header fiware_header = FiwareHeader(service=self.params['service'], service_path=self.params['service_path']) # Create orion context broker client self.ORION_CB = ContextBrokerClient(url=self.params['cb_url'], fiware_header=fiware_header) def create_entity(self): """Creates entitiy of PID controller in orion context broker""" try: self.ORION_CB.get_entity(entity_id=self.params['controller_name'], entity_type=self.params['type']) print('Entity name already assigned') except requests.exceptions.HTTPError as err: msg = err.args[0] if "NOT FOUND" not in msg.upper(): raise # throw other errors except "entity not found" print('[INFO]: Create new PID entity') pid_entity = ContextEntity(id=f"{self.params['controller_name']}", type=self.params['type']) cb_attrs = [] for attr in ['Kp', 'Ki', 'Kd', 'lim_low', 'lim_upper', 'setpoint']: cb_attrs.append( NamedContextAttribute(name=attr, type="Number", value=self.params[attr])) pid_entity.add_attributes(attrs=cb_attrs) self.ORION_CB.post_entity(entity=pid_entity, update=True) def update_params(self): """Read PID parameters of entity in context broker and updates PID control parameters""" # read PID parameters from context broker for attr in ['Kp', 'Ki', 'Kd', 'lim_low', 'lim_upper', 'setpoint']: self.params[attr] = float( self.ORION_CB.get_attribute_value( entity_id=self.params['controller_name'], entity_type=self.params['type'], attr_name=attr)) # update PID parameters self.pid.tunings = (self.params['Kp'], self.params['Ki'], self.params['Kd']) self.pid.output_limits = (self.params['lim_low'], self.params['lim_upper']) self.pid.setpoint = self.params['setpoint'] # read measured values from CB try: # read the current actuator value u (synchronize the value with the actuator) self.u = self.ORION_CB.get_attribute_value( entity_id=self.params['actuator_entity_name'], entity_type=self.params['actuator_type'], attr_name=self.params['actuator_command_value']) if not isinstance(self.u, (int, float)): self.u = None # read the value of process variable y from sensor y = self.ORION_CB.get_attribute_value( entity_id=self.params['sensor_entity_name'], entity_type=self.params['sensor_type'], attr_name=self.params['sensor_attr']) # set 0 if empty if y == " ": y = '0' # convert to float self.y_act = float(y) except requests.exceptions.HTTPError as err: msg = err.args[0] if "NOT FOUND" not in msg.upper(): raise self.auto_mode = False print("Controller connection fails") else: # If no errors are raised self.auto_mode = True # Update the actual actuator value to allow warm star after interruption self.pid.set_auto_mode(self.auto_mode, last_output=self.u) def run(self): """Calculation of PID output""" try: if self.auto_mode: # if connection is good, auto_mode = True -> controller active # calculate PID output self.u = self.pid(self.y_act) # build command command = NamedCommand(name=self.params['actuator_command'], value=round(self.u, 3)) self.ORION_CB.post_command( entity_id=self.params['actuator_entity_name'], entity_type=self.params['actuator_type'], command=command) except requests.exceptions.HTTPError as err: msg = err.args[0] if "NOT FOUND" not in msg.upper(): raise self.auto_mode = False print("Controller connection fails") def update_token(self): """ Update the token if necessary. Write the latest token into the header of CB client. """ token = self.kp.check_update_token_validity( input_token=self.params['token'], min_valid_time=60) if all(token): # if a valid token is returned self.params['token'] = token # Update the header with token self.ORION_CB.headers.update( {"Authorization": f"Bearer {self.params['token'][0]}"}) def control_loop(self): """The control loop""" try: while True: # Update token if run in security mode if self.security_mode: self.update_token() else: pass self.update_params() self.run() time.sleep(self.params['pause_time']) finally: print("control loop fails") os.abort()
class TestContextBroker(unittest.TestCase): """ Test class for ContextBrokerClient """ def setUp(self) -> None: """ Setup test data Returns: None """ self.fiware_header = FiwareHeader( service=settings.FIWARE_SERVICE, service_path=settings.FIWARE_SERVICEPATH) clear_all(fiware_header=self.fiware_header, cb_url=settings.CB_URL, iota_url=settings.IOTA_JSON_URL) self.resources = { "entities_url": "/v2/entities", "types_url": "/v2/types", "subscriptions_url": "/v2/subscriptions", "registrations_url": "/v2/registrations" } self.attr = {'temperature': {'value': 20.0, 'type': 'Number'}} self.entity = ContextEntity(id='MyId', type='MyType', **self.attr) self.client = ContextBrokerClient( url=settings.CB_URL, fiware_header=self.fiware_header) self.subscription = Subscription.parse_obj({ "description": "One subscription to rule them all", "subject": { "entities": [ { "idPattern": ".*", "type": "Room" } ], "condition": { "attrs": [ "temperature" ], "expression": { "q": "temperature>40" } } }, "notification": { "http": { "url": "http://localhost:1234" }, "attrs": [ "temperature", "humidity" ] }, "expires": datetime.now(), "throttling": 0 }) def test_management_endpoints(self): """ Test management functions of context broker client """ with ContextBrokerClient( url=settings.CB_URL, fiware_header=self.fiware_header) as client: self.assertIsNotNone(client.get_version()) self.assertEqual(client.get_resources(), self.resources) def test_statistics(self): """ Test statistics of context broker client """ with ContextBrokerClient( url=settings.CB_URL, fiware_header=self.fiware_header) as client: self.assertIsNotNone(client.get_statistics()) @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_pagination(self): """ Test pagination of context broker client Test pagination. only works if enough entities are available """ with ContextBrokerClient( url=settings.CB_URL, fiware_header=self.fiware_header) as client: entities_a = [ContextEntity(id=str(i), type=f'filip:object:TypeA') for i in range(0, 1000)] client.update(action_type=ActionType.APPEND, entities=entities_a) entities_b = [ContextEntity(id=str(i), type=f'filip:object:TypeB') for i in range(1000, 2001)] client.update(action_type=ActionType.APPEND, entities=entities_b) self.assertLessEqual(len(client.get_entity_list(limit=1)), 1) self.assertLessEqual(len(client.get_entity_list(limit=999)), 999) self.assertLessEqual(len(client.get_entity_list(limit=1001)), 1001) self.assertLessEqual(len(client.get_entity_list(limit=2001)), 2001) @clean_test(fiware_service=settings.FIWARE_SERVICE, fiware_servicepath=settings.FIWARE_SERVICEPATH, cb_url=settings.CB_URL) def test_entity_filtering(self): """ Test filter operations of context broker client """ with ContextBrokerClient( url=settings.CB_URL, fiware_header=self.fiware_header) as client: # test patterns with self.assertRaises(ValueError): client.get_entity_list(id_pattern='(&()?') with self.assertRaises(ValueError): client.get_entity_list(type_pattern='(&()?') entities_a = [ContextEntity(id=str(i), type=f'filip:object:TypeA') for i in range(0, 5)] client.update(action_type=ActionType.APPEND, entities=entities_a) entities_b = [ContextEntity(id=str(i), type=f'filip:object:TypeB') for i in range(6, 10)] client.update(action_type=ActionType.APPEND, entities=entities_b) entities_all = client.get_entity_list() entities_by_id_pattern = client.get_entity_list( id_pattern='.*[1-5]') self.assertLess(len(entities_by_id_pattern), len(entities_all)) entities_by_type_pattern = client.get_entity_list( type_pattern=".*TypeA$") self.assertLess(len(entities_by_type_pattern), len(entities_all)) qs = QueryString(qs=[('presentValue', '>', 0)]) entities_by_query = client.get_entity_list(q=qs) self.assertLess(len(entities_by_query), len(entities_all)) # test options for opt in list(AttrsFormat): entities_by_option = client.get_entity_list(response_format=opt) self.assertEqual(len(entities_by_option), len(entities_all)) self.assertEqual(client.get_entity( entity_id='0', response_format=opt), entities_by_option[0]) with self.assertRaises(ValueError): client.get_entity_list(response_format='not in AttrFormat') client.update(action_type=ActionType.DELETE, entities=entities_a) client.update(action_type=ActionType.DELETE, entities=entities_b) @clean_test(fiware_service=settings.FIWARE_SERVICE, fiware_servicepath=settings.FIWARE_SERVICEPATH, cb_url=settings.CB_URL) def test_entity_operations(self): """ Test entity operations of context broker client """ with ContextBrokerClient( url=settings.CB_URL, fiware_header=self.fiware_header) as client: client.post_entity(entity=self.entity, update=True) res_entity = client.get_entity(entity_id=self.entity.id) client.get_entity(entity_id=self.entity.id, attrs=['temperature']) self.assertEqual(client.get_entity_attributes( entity_id=self.entity.id), res_entity.get_properties( response_format='dict')) res_entity.temperature.value = 25 client.update_entity(entity=res_entity) self.assertEqual(client.get_entity(entity_id=self.entity.id), res_entity) res_entity.add_attributes({'pressure': ContextAttribute( type='Number', value=1050)}) client.update_entity(entity=res_entity) self.assertEqual(client.get_entity(entity_id=self.entity.id), res_entity) @clean_test(fiware_service=settings.FIWARE_SERVICE, fiware_servicepath=settings.FIWARE_SERVICEPATH, cb_url=settings.CB_URL) def test_attribute_operations(self): """ Test attribute operations of context broker client """ with ContextBrokerClient( url=settings.CB_URL, fiware_header=self.fiware_header) as client: entity = self.entity attr_txt = NamedContextAttribute(name='attr_txt', type='Text', value="Test") attr_bool = NamedContextAttribute(name='attr_bool', type='Boolean', value=True) attr_float = NamedContextAttribute(name='attr_float', type='Number', value=round(random.random(), 5)) attr_list = NamedContextAttribute(name='attr_list', type='StructuredValue', value=[1, 2, 3]) attr_dict = NamedContextAttribute(name='attr_dict', type='StructuredValue', value={'key': 'value'}) entity.add_attributes([attr_txt, attr_bool, attr_float, attr_list, attr_dict]) self.assertIsNotNone(client.post_entity(entity=entity, update=True)) res_entity = client.get_entity(entity_id=entity.id) for attr in entity.get_properties(): self.assertIn(attr, res_entity.get_properties()) res_attr = client.get_attribute(entity_id=entity.id, attr_name=attr.name) self.assertEqual(type(res_attr.value), type(attr.value)) self.assertEqual(res_attr.value, attr.value) value = client.get_attribute_value(entity_id=entity.id, attr_name=attr.name) # unfortunately FIWARE returns an int for 20.0 although float # is expected if isinstance(value, int) and not isinstance(value, bool): value = float(value) self.assertEqual(type(value), type(attr.value)) self.assertEqual(value, attr.value) for attr_name, attr in entity.get_properties( response_format='dict').items(): client.update_entity_attribute(entity_id=entity.id, attr_name=attr_name, attr=attr) value = client.get_attribute_value(entity_id=entity.id, attr_name=attr_name) # unfortunately FIWARE returns an int for 20.0 although float # is expected if isinstance(value, int) and not isinstance(value, bool): value = float(value) self.assertEqual(type(value), type(attr.value)) self.assertEqual(value, attr.value) new_value = 1337.0 client.update_attribute_value(entity_id=entity.id, attr_name='temperature', value=new_value) attr_value = client.get_attribute_value(entity_id=entity.id, attr_name='temperature') self.assertEqual(attr_value, new_value) @clean_test(fiware_service=settings.FIWARE_SERVICE, fiware_servicepath=settings.FIWARE_SERVICEPATH, cb_url=settings.CB_URL) def test_type_operations(self): """ Test type operations of context broker client """ with ContextBrokerClient( url=settings.CB_URL, fiware_header=self.fiware_header) as client: self.assertIsNotNone(client.post_entity(entity=self.entity, update=True)) client.get_entity_types() client.get_entity_types(options='count') client.get_entity_types(options='values') client.get_entity_type(entity_type='MyType') client.delete_entity(entity_id=self.entity.id, entity_type=self.entity.type) @unittest.skip('Does currently not work in CI') @clean_test(fiware_service=settings.FIWARE_SERVICE, fiware_servicepath=settings.FIWARE_SERVICEPATH, cb_url=settings.CB_URL) def test_subscriptions(self): """ Test subscription operations of context broker client """ with ContextBrokerClient( url=settings.CB_URL, fiware_header=self.fiware_header) as client: sub_id = client.post_subscription(subscription=self.subscription, skip_initial_notification=True) sub_res = client.get_subscription(subscription_id=sub_id) time.sleep(1) sub_update = sub_res.copy( update={'expires': datetime.now() + timedelta(days=1)}) client.update_subscription(subscription=sub_update) sub_res_updated = client.get_subscription(subscription_id=sub_id) self.assertNotEqual(sub_res.expires, sub_res_updated.expires) self.assertEqual(sub_res.id, sub_res_updated.id) self.assertGreaterEqual(sub_res_updated.expires, sub_res.expires) # test duplicate prevention and update sub = self.subscription.copy() id1 = client.post_subscription(sub) sub_first_version = client.get_subscription(id1) sub.description = "This subscription shall not pass" id2 = client.post_subscription(sub, update=False) self.assertEqual(id1, id2) sub_second_version = client.get_subscription(id2) self.assertEqual(sub_first_version.description, sub_second_version.description) id2 = client.post_subscription(sub, update=True) self.assertEqual(id1, id2) sub_second_version = client.get_subscription(id2) self.assertNotEqual(sub_first_version.description, sub_second_version.description) # test that duplicate prevention does not prevent to much sub2 = self.subscription.copy() sub2.description = "Take this subscription to Fiware" sub2.subject.entities[0] = { "idPattern": ".*", "type": "Building" } id3 = client.post_subscription(sub2) self.assertNotEqual(id1, id3) @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_subscription_set_status(self): """ Test subscription operations of context broker client """ sub = self.subscription.copy( update={'expires': datetime.now() + timedelta(days=1)}) with ContextBrokerClient( url=settings.CB_URL, fiware_header=self.fiware_header) as client: sub_id = client.post_subscription(subscription=sub) sub_res = client.get_subscription(subscription_id=sub_id) self.assertEqual(sub_res.status, Status.ACTIVE) sub_inactive = sub_res.copy(update={'status': Status.INACTIVE}) client.update_subscription(subscription=sub_inactive) sub_res_inactive = client.get_subscription(subscription_id=sub_id) self.assertEqual(sub_res_inactive.status, Status.INACTIVE) sub_active = sub_res_inactive.copy(update={'status': Status.ACTIVE}) client.update_subscription(subscription=sub_active) sub_res_active = client.get_subscription(subscription_id=sub_id) self.assertEqual(sub_res_active.status, Status.ACTIVE) sub_expired = sub_res_active.copy( update={'expires': datetime.now() - timedelta(days=365)}) client.update_subscription(subscription=sub_expired) sub_res_expired = client.get_subscription(subscription_id=sub_id) self.assertEqual(sub_res_expired.status, Status.EXPIRED) @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_mqtt_subscriptions(self): mqtt_url = settings.MQTT_BROKER_URL mqtt_topic = ''.join([settings.FIWARE_SERVICE, settings.FIWARE_SERVICEPATH]) notification = self.subscription.notification.copy( update={'http': None, 'mqtt': Mqtt(url=mqtt_url, topic=mqtt_topic)}) subscription = self.subscription.copy( update={'notification': notification, 'description': 'MQTT test subscription', 'expires': None}) entity = ContextEntity(id='myID', type='Room', **self.attr) self.client.post_entity(entity=entity) sub_id = self.client.post_subscription(subscription) sub_message = None def on_connect(client, userdata, flags, reasonCode, properties=None): if reasonCode != 0: logger.error(f"Connection failed with error code: " f"'{reasonCode}'") raise ConnectionError else: logger.info("Successfully, connected with result code " + str( reasonCode)) client.subscribe(mqtt_topic) def on_subscribe(client, userdata, mid, granted_qos, properties=None): logger.info("Successfully subscribed to with QoS: %s", granted_qos) def on_message(client, userdata, msg): logger.info(msg.topic + " " + str(msg.payload)) nonlocal sub_message sub_message = Message.parse_raw(msg.payload) def on_disconnect(client, userdata, reasonCode, properties=None): logger.info("MQTT client disconnected with reasonCode " + str(reasonCode)) import paho.mqtt.client as mqtt mqtt_client = mqtt.Client(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 # connect to the server mqtt_url = urlparse(mqtt_url) mqtt_client.connect(host=mqtt_url.hostname, port=mqtt_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() new_value = 50 self.client.update_attribute_value(entity_id=entity.id, attr_name='temperature', value=new_value, entity_type=entity.type) time.sleep(5) # test if the subscriptions arrives and the content aligns with updates self.assertIsNotNone(sub_message) self.assertEqual(sub_id, sub_message.subscriptionId) self.assertEqual(new_value, sub_message.data[0].temperature.value) mqtt_client.loop_stop() mqtt_client.disconnect() time.sleep(1) @clean_test(fiware_service=settings.FIWARE_SERVICE, fiware_servicepath=settings.FIWARE_SERVICEPATH, cb_url=settings.CB_URL) def test_batch_operations(self): """ Test batch operations of context broker client """ with ContextBrokerClient( url=settings.CB_URL, fiware_header=self.fiware_header) as client: entities = [ContextEntity(id=str(i), type=f'filip:object:TypeA') for i in range(0, 1000)] client.update(entities=entities, action_type=ActionType.APPEND) entities = [ContextEntity(id=str(i), type=f'filip:object:TypeB') for i in range(0, 1000)] client.update(entities=entities, action_type=ActionType.APPEND) entity = EntityPattern(idPattern=".*", typePattern=".*TypeA$") query = Query.parse_obj( {"entities": [entity.dict(exclude_unset=True)]}) self.assertEqual(1000, len(client.query(query=query, response_format='keyValues'))) @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_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_patch_entity(self) -> None: """ Test the methode: patch_entity Returns: None """ # setup test-entity entity = ContextEntity(id="test_id1", type="test_type1") attr1 = NamedContextAttribute(name="attr1", value="1") attr1.metadata["m1"] = \ NamedMetadata(name="meta1", type="metatype", value="2") attr2 = NamedContextAttribute(name="attr2", value="2") attr1.metadata["m2"] = \ NamedMetadata(name="meta2", type="metatype", value="3") entity.add_attributes([attr1, attr2]) # sub-Test1: Post new self.client.patch_entity(entity=entity) self.assertEqual(entity, self.client.get_entity(entity_id=entity.id)) self.tearDown() # sub-Test2: ID/type of old_entity changed self.client.post_entity(entity=entity) test_entity = ContextEntity(id="newID", type="newType") test_entity.add_attributes([attr1, attr2]) self.client.patch_entity(test_entity, old_entity=entity) self.assertEqual(test_entity, self.client.get_entity(entity_id=test_entity.id)) self.assertRaises(RequestException, self.client.get_entity, entity_id=entity.id) self.tearDown() # sub-Test3: a non valid old_entity is provided, entity exists self.client.post_entity(entity=entity) old_entity = ContextEntity(id="newID", type="newType") self.client.patch_entity(entity, old_entity=old_entity) self.assertEqual(entity, self.client.get_entity(entity_id=entity.id)) self.tearDown() # sub-Test4: no old_entity provided, entity is new old_entity = ContextEntity(id="newID", type="newType") self.client.patch_entity(entity, old_entity=old_entity) self.assertEqual(entity, self.client.get_entity(entity_id=entity.id)) self.tearDown() # sub-Test5: no old_entity provided, entity is new old_entity = ContextEntity(id="newID", type="newType") self.client.patch_entity(entity, old_entity=old_entity) self.assertEqual(entity, self.client.get_entity(entity_id=entity.id)) self.tearDown() # sub-Test6: New attr, attr del, and attr changed. No Old_entity given self.client.post_entity(entity=entity) test_entity = ContextEntity(id="test_id1", type="test_type1") attr1_changed = NamedContextAttribute(name="attr1", value="2") attr1_changed.metadata["m4"] = \ NamedMetadata(name="meta3", type="metatype5", value="4") attr3 = NamedContextAttribute(name="attr3", value="3") test_entity.add_attributes([attr1_changed, attr3]) self.client.patch_entity(test_entity) self.assertEqual(test_entity, self.client.get_entity(entity_id=entity.id)) self.tearDown() # sub-Test7: Attr changes, concurrent changes in Fiware, # old_entity given self.client.post_entity(entity=entity) concurrent_entity = ContextEntity(id="test_id1", type="test_type1") attr1_changed = copy.deepcopy(attr1) attr1_changed.metadata["m1"].value = "3" attr1_changed.value = "4" concurrent_entity.add_attributes([attr1_changed, attr2]) self.client.patch_entity(concurrent_entity) user_entity = copy.deepcopy(entity) attr3 = NamedContextAttribute(name="attr3", value="3") user_entity.add_attributes([attr3]) self.client.patch_entity(user_entity, old_entity=entity) result_entity = concurrent_entity result_entity.add_attributes([attr2, attr3]) self.assertEqual(result_entity, self.client.get_entity(entity_id=entity.id)) self.tearDown() def tearDown(self) -> None: """ Cleanup test server """ self.client.close() clear_all(fiware_header=self.fiware_header, cb_url=settings.CB_URL, iota_url=settings.IOTA_JSON_URL)