diff --git a/backend-apis b/backend-apis index 272c8fe..85a6ffd 160000 --- a/backend-apis +++ b/backend-apis @@ -1 +1 @@ -Subproject commit 272c8fe27261787cd5ea1d1e13088ed35e24dc81 +Subproject commit 85a6ffd5168f42ac18561e25cc2cf334cd8ded17 diff --git a/tests/default_value.py b/tests/default_value.py index 5a04f1f..7c405cf 100644 --- a/tests/default_value.py +++ b/tests/default_value.py @@ -24,6 +24,11 @@ IMPLEMENTED_API_VERSION = 0 +TEST_CDC1 = [ + { "endpoint": 1, "payload": bytes.fromhex("AABBCCDDEEFF") }, + { "endpoint": 0x20000, "payload": bytes.fromhex("334455") }, +] + # Todo add more fields in config NODE_CONFIG_1 = dict([("sink_id", SINK_ID), ("node_address", 123)]) @@ -55,5 +60,6 @@ ("stored_type", SCRATCHPAD_TYPE), ("stored_status", SCRATCHPAD_STATUS), ("processed_scratchpad", SCRATCHPAD_INFO), - ("stored_scratchpad", SCRATCHPAD_INFO) + ("stored_scratchpad", SCRATCHPAD_INFO), + ("configuration_data_content", TEST_CDC1) ]) diff --git a/tests/test_feature_flags.py b/tests/test_feature_flags.py new file mode 100644 index 0000000..fd1dcca --- /dev/null +++ b/tests/test_feature_flags.py @@ -0,0 +1,20 @@ +# flake8: noqa + +import pytest +import wirepas_mesh_messaging +from wirepas_mesh_messaging.proto import GatewayFeature as GatewayFeature_pb + +@pytest.mark.parametrize("protobuf_feature_flag", GatewayFeature_pb.items()) +def test_decoding_errors(protobuf_feature_flag): + """ + Make sure all the feature flags in the internal + (wirepas_mesh_messaging.OptionalGatewayFeature) enum are aligned with the + protobuf side if the protobuf definition is updated in this library. + """ + + name, value = protobuf_feature_flag + try: + wirepas_mesh_messaging.GatewayFeature(value) + except ValueError as e: + raise ValueError(f"{name} is not defined in GatewayFeature") from e + diff --git a/tests/test_get_config_data_item.py b/tests/test_get_config_data_item.py new file mode 100644 index 0000000..3e31f7b --- /dev/null +++ b/tests/test_get_config_data_item.py @@ -0,0 +1,122 @@ +# flake8: noqa + +import pytest +import base64 +import wirepas_mesh_messaging +from wirepas_mesh_messaging.proto import GenericMessage +from google.protobuf.json_format import ParseDict +from default_value import * + + +def test_parse_request_message(): + TEST_REQ_ID = 200 + TEST_ENDPOINT = 3000 + + message_description = { + "wirepas": { + "get_configuration_data_item_req": { + "header": {"req_id": TEST_REQ_ID}, + "endpoint": TEST_ENDPOINT + } + } + } + + message = ParseDict(message_description, GenericMessage()) + assert message.IsInitialized() + + request = wirepas_mesh_messaging.GetConfigurationDataItemRequest.from_payload( + message.SerializeToString() + ) + + assert TEST_REQ_ID == request.req_id + assert TEST_ENDPOINT == request.cdc_endpoint + +def test_encode_request_message(): + TEST_ENDPOINT = 2000 + + request = wirepas_mesh_messaging.GetConfigurationDataItemRequest(SINK_ID, TEST_ENDPOINT) + + parsed_request = wirepas_mesh_messaging.GetConfigurationDataItemRequest.from_payload(request.payload) + + for k, v in request.__dict__.items(): + assert v == parsed_request.__dict__[k] + +def test_parse_response_message(): + TEST_ENDPOINT = 700 + TEST_PAYLOAD = bytes.fromhex("BBAABBAADDAADDAA") + + message_description = { + "wirepas": { + "get_configuration_data_item_resp": { + "header": { + "req_id": REQUEST_ID, + "gw_id": GATEWAY_ID, + "sink_id": SINK_ID_2, + "res": 0, + "time_ms_epoch": RX_TIME_MS_EPOCH + }, + "configuration_data_item": { + "endpoint": TEST_ENDPOINT, + # bytes objects are base64 encoded in protobuf JSON format + "payload": base64.b64encode(TEST_PAYLOAD) + } + } + } + } + + message = ParseDict(message_description, GenericMessage()) + assert message.IsInitialized() + + response = wirepas_mesh_messaging.GetConfigurationDataItemResponse.from_payload( + message.SerializeToString() + ) + + assert REQUEST_ID == response.req_id + assert GATEWAY_ID == response.gw_id + assert SINK_ID_2 == response.sink_id + assert RES_OK == response.res + assert RX_TIME_MS_EPOCH == response.time_ms_epoch + assert TEST_ENDPOINT == response.cdc_endpoint + assert TEST_PAYLOAD == response.cdc_payload + +def test_encode_response_message(): + TEST_ENDPOINT = 9000 + TEST_PAYLOAD = bytes.fromhex("FEFE1234") + + request = wirepas_mesh_messaging.GetConfigurationDataItemResponse( + REQUEST_ID, GATEWAY_ID, RES_OK, SINK_ID, TEST_ENDPOINT, TEST_PAYLOAD + ) + + parsed_request = wirepas_mesh_messaging.GetConfigurationDataItemResponse.from_payload(request.payload) + + for k, v in request.__dict__.items(): + assert v == parsed_request.__dict__[k] + +def test_encode_response_with_no_item(): + response = wirepas_mesh_messaging.GetConfigurationDataItemResponse( + REQUEST_ID, GATEWAY_ID, RES_KO, SINK_ID + ) + parsed_response = wirepas_mesh_messaging.GetConfigurationDataItemResponse.from_payload(response.payload) + + assert None == parsed_response.cdc_endpoint + assert None == parsed_response.cdc_payload + +def test_encode_response_with_empty_payload(): + TEST_ENDPOINT = 543 + TEST_PAYLOAD = bytes() + response = wirepas_mesh_messaging.GetConfigurationDataItemResponse( + REQUEST_ID, GATEWAY_ID, RES_KO, SINK_ID, TEST_ENDPOINT, TEST_PAYLOAD + ) + parsed_response = wirepas_mesh_messaging.GetConfigurationDataItemResponse.from_payload(response.payload) + + assert TEST_ENDPOINT == parsed_response.cdc_endpoint + assert TEST_PAYLOAD == parsed_response.cdc_payload + +def test_encoding_response_with_endpoint_but_no_payload_should_fail(): + response = wirepas_mesh_messaging.GetConfigurationDataItemResponse( + REQUEST_ID, GATEWAY_ID, RES_KO, SINK_ID, 1000 + ) + + with pytest.raises(Exception): + response.payload + diff --git a/tests/test_get_configs.py b/tests/test_get_configs.py index 1f8143c..63555f0 100644 --- a/tests/test_get_configs.py +++ b/tests/test_get_configs.py @@ -26,5 +26,7 @@ def test_generate_parse_response(): request.payload ) + assert DUMMY_CONFIGS == request2.configs for k, v in request.__dict__.items(): assert v == request2.__dict__[k] + diff --git a/tests/test_get_gw_info.py b/tests/test_get_gw_info.py index 0f6a154..840745c 100644 --- a/tests/test_get_gw_info.py +++ b/tests/test_get_gw_info.py @@ -26,12 +26,33 @@ def test_generate_parse_response(): "Version x.y", max_scratchpad_size=512, implemented_api_version=IMPLEMENTED_API_VERSION, + gateway_features=[ + wirepas_mesh_messaging.GatewayFeature.GW_FEATURE_SCRATCHPAD_CHUNK_V1, + wirepas_mesh_messaging.GatewayFeature.GW_FEATURE_CONFIGURATION_DATA_V1 + ] ) request2 = wirepas_mesh_messaging.GetGatewayInfoResponse.from_payload( request.payload ) + expected_members = [ + "gw_id", + "sink_id", + "req_id", + "res", + "time_ms_epoch", + "current_time_s_epoch", + "gateway_model", + "gateway_version", + "implemented_api_version", + "max_scratchpad_size", + "gateway_features" + ] + for field in expected_members: + assert field in request.__dict__ + assert field in request2.__dict__ + for k, v in request.__dict__.items(): assert v == request2.__dict__[k] @@ -52,5 +73,4 @@ def test_generate_parse_response_not_all_optional(): ) for k, v in request.__dict__.items(): - print(k) assert v == request2.__dict__[k] diff --git a/tests/test_set_config.py b/tests/test_set_config.py index caef362..bb8d805 100644 --- a/tests/test_set_config.py +++ b/tests/test_set_config.py @@ -28,3 +28,18 @@ def test_generate_parse_response(): for k, v in request.__dict__.items(): assert v == request2.__dict__[k] + + +def test_generate_parse_response_with_readonly_fields(): + request = wirepas_mesh_messaging.SetConfigResponse( + REQUEST_ID, GATEWAY_ID, RES_OK, SINK_ID_2, NODE_CONFIG_2 + ) + + request2 = wirepas_mesh_messaging.SetConfigResponse.from_payload( + request.payload + ) + + assert NODE_CONFIG_2 == request2.config + for k, v in request.__dict__.items(): + assert v == request2.__dict__[k] + diff --git a/tests/test_set_config_data_item.py b/tests/test_set_config_data_item.py new file mode 100644 index 0000000..e235d4d --- /dev/null +++ b/tests/test_set_config_data_item.py @@ -0,0 +1,98 @@ +# flake8: noqa + +import pytest +import base64 +import wirepas_mesh_messaging +from wirepas_mesh_messaging.proto import GenericMessage +from google.protobuf.json_format import ParseDict +from default_value import * + + +def test_parse_request_message(): + TEST_REQ_ID = 300 + TEST_ENDPOINT = 500 + TEST_PAYLOAD = bytes.fromhex("AABBCCDDEEFF") + + message_description = { + "wirepas": { + "set_configuration_data_item_req": { + "header": {"req_id": TEST_REQ_ID}, + "configuration_data_item": { + "endpoint": TEST_ENDPOINT, + # bytes objects are base64 encoded in protobuf JSON format + "payload": base64.b64encode(TEST_PAYLOAD) + } + } + } + } + + message = ParseDict(message_description, GenericMessage()) + assert message.IsInitialized() + + request = wirepas_mesh_messaging.SetConfigurationDataItemRequest.from_payload( + message.SerializeToString() + ) + + assert TEST_REQ_ID == request.req_id + assert TEST_ENDPOINT == request.cdc_endpoint + assert TEST_PAYLOAD == request.cdc_payload + +def test_encode_request_message(): + TEST_ENDPOINT = 1000 + TEST_PAYLOAD = bytes.fromhex("102030") + + request = wirepas_mesh_messaging.SetConfigurationDataItemRequest(SINK_ID, TEST_ENDPOINT, TEST_PAYLOAD) + + parsed_request = wirepas_mesh_messaging.SetConfigurationDataItemRequest.from_payload(request.payload) + + for k, v in request.__dict__.items(): + assert v == parsed_request.__dict__[k] + +def test_encode_request_with_empty_payload(): + request = wirepas_mesh_messaging.SetConfigurationDataItemRequest(SINK_ID, 100, bytes()) + parsed_request = wirepas_mesh_messaging.SetConfigurationDataItemRequest.from_payload(request.payload) + + assert bytes() == parsed_request.cdc_payload + +def test_encoding_request_with_none_payload_should_fail(): + request = wirepas_mesh_messaging.SetConfigurationDataItemRequest(SINK_ID, 100, None) + + with pytest.raises(TypeError): + request.payload + +def test_parse_response_message(): + message_description = { + "wirepas": { + "set_configuration_data_item_resp": { + "header": { + "req_id": REQUEST_ID, + "gw_id": GATEWAY_ID, + "sink_id": SINK_ID_2, + "res": 0, + "time_ms_epoch": RX_TIME_MS_EPOCH + } + } + } + } + + message = ParseDict(message_description, GenericMessage()) + assert message.IsInitialized() + + response = wirepas_mesh_messaging.SetConfigurationDataItemResponse.from_payload( + message.SerializeToString() + ) + + assert REQUEST_ID == response.req_id + assert GATEWAY_ID == response.gw_id + assert SINK_ID_2 == response.sink_id + assert RES_OK == response.res + assert RX_TIME_MS_EPOCH == response.time_ms_epoch + +def test_encode_response_message(): + request = wirepas_mesh_messaging.SetConfigurationDataItemResponse(REQUEST_ID, GATEWAY_ID, RES_OK, SINK_ID) + + parsed_request = wirepas_mesh_messaging.SetConfigurationDataItemResponse.from_payload(request.payload) + + for k, v in request.__dict__.items(): + assert v == parsed_request.__dict__[k] + diff --git a/tests/test_status.py b/tests/test_status.py index 6b3a205..ff77227 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -1,5 +1,6 @@ # flake8: noqa +import pytest import wirepas_mesh_messaging from default_value import * @@ -18,6 +19,7 @@ def test_generate_parse_event_complete(): status2 = wirepas_mesh_messaging.StatusEvent.from_payload(status.payload) + assert DUMMY_CONFIGS == status2.sink_configs for k, v in status.__dict__.items(): assert v == status2.__dict__[k] @@ -28,3 +30,22 @@ def test_generate_parse_event_with_max_size(): for k, v in status.__dict__.items(): assert v == status2.__dict__[k] + +def test_generate_parse_event_with_feature_flags(): + gw_features = [ + wirepas_mesh_messaging.GatewayFeature.GW_FEATURE_SCRATCHPAD_CHUNK_V1, + wirepas_mesh_messaging.GatewayFeature.GW_FEATURE_CONFIGURATION_DATA_V1 + ] + status = wirepas_mesh_messaging.StatusEvent(GATEWAY_ID, GATEWAY_STATE, gateway_features=gw_features) + + status_parsed = wirepas_mesh_messaging.StatusEvent.from_payload(status.payload) + + for k, v in status.__dict__.items(): + assert v == status_parsed.__dict__[k] + +def test_encoding_message_with_invalid_feature_flag(): + status = wirepas_mesh_messaging.StatusEvent(GATEWAY_ID, GATEWAY_STATE, gateway_features=["INVALID_VALUE12345"]) + + with pytest.raises(Exception): + status.payload + diff --git a/wirepas_mesh_messaging/__init__.py b/wirepas_mesh_messaging/__init__.py index 5fb0a16..0b7ce3d 100644 --- a/wirepas_mesh_messaging/__init__.py +++ b/wirepas_mesh_messaging/__init__.py @@ -32,6 +32,9 @@ from .gateway_result_code import GatewayResultCode from .otap_helper import ScratchpadStatus, ScratchpadType, ScratchpadAction, ProcessingDelay from .wirepas_exceptions import GatewayAPIParsingException +from .set_config_data_item import SetConfigurationDataItemRequest, SetConfigurationDataItemResponse +from .get_config_data_item import GetConfigurationDataItemRequest, GetConfigurationDataItemResponse +from .gateway_feature import GatewayFeature from google.protobuf.internal import api_implementation diff --git a/wirepas_mesh_messaging/config_helper.py b/wirepas_mesh_messaging/config_helper.py index ab8c8e5..2882f49 100644 --- a/wirepas_mesh_messaging/config_helper.py +++ b/wirepas_mesh_messaging/config_helper.py @@ -7,7 +7,7 @@ See file LICENSE for full license details. """ -from .proto import ON, OFF, NodeRole +from .proto import ON, OFF, NodeRole, ConfigurationDataItem from .otap_helper import ( set_scratchpad_info, @@ -308,6 +308,10 @@ def parse_config_ro(message_obj, dic): message_obj.firmware_version.dev, ] + for cdc_item in message_obj.configuration_data_content: + cdc = dic.setdefault("configuration_data_content", []) + cdc.append({"endpoint": cdc_item.endpoint, "payload": cdc_item.payload}) + def set_config_ro(message_obj, dic): """ @@ -342,3 +346,10 @@ def set_config_ro(message_obj, dic): except KeyError: # Field is unknown, just skip it pass + + for cdc_definition in dic.get("configuration_data_content", []): + cdc_item = ConfigurationDataItem() + cdc_item.endpoint = cdc_definition["endpoint"] + cdc_item.payload = cdc_definition["payload"] + message_obj.configuration_data_content.append(cdc_item) + diff --git a/wirepas_mesh_messaging/gateway_feature.py b/wirepas_mesh_messaging/gateway_feature.py new file mode 100644 index 0000000..cf6100d --- /dev/null +++ b/wirepas_mesh_messaging/gateway_feature.py @@ -0,0 +1,22 @@ +""" + Gateway feature + ========== + + .. Copyright: + Copyright 2025 Wirepas Ltd under Apache License, Version 2.0. + See file LICENSE for full license details. +""" + +import enum +from .proto import GatewayFeature as GatewayFeature_pb + +class GatewayFeature(enum.Enum): + """ + Class that represent all possible gateway feature flags. + Keep a one-to-one mapping with current protobuf flags to ease conversion. + """ + + GW_FEATURE_UNKNOWN = GatewayFeature_pb.UNKNOWN + GW_FEATURE_SCRATCHPAD_CHUNK_V1 = GatewayFeature_pb.SCRATCHPAD_CHUNK_V1 + GW_FEATURE_CONFIGURATION_DATA_V1 = GatewayFeature_pb.CONFIGURATION_DATA_V1 + diff --git a/wirepas_mesh_messaging/get_config_data_item.py b/wirepas_mesh_messaging/get_config_data_item.py new file mode 100644 index 0000000..84978e8 --- /dev/null +++ b/wirepas_mesh_messaging/get_config_data_item.py @@ -0,0 +1,114 @@ +""" + Set configuration data item + ========== + + .. Copyright: + Copyright 2025 Wirepas Ltd under Apache License, Version 2.0. + See file LICENSE for full license details. +""" + +from .proto import GenericMessage + +from .request import Request +from .response import Response + + +class GetConfigurationDataItemRequest(Request): + """ + GetConfigurationDataItemRequest: request to get a configuration data item + on a given sink + + Attributes: + sink_id (str): id of the sink (dependant on gateway) + cdc_endpoint (int): configuration data item endpoint + req_id (int): unique request id + + """ + def __init__(self, sink_id, cdc_endpoint, req_id=None, **kwargs): + super().__init__(req_id=req_id, **kwargs) + self.sink_id = sink_id + self.cdc_endpoint = cdc_endpoint + + @staticmethod + def _get_related_message(generic_message): + return generic_message.wirepas.get_configuration_data_item_req + + @classmethod + def from_payload(cls, payload): + req = cls._decode_and_get_related_message(payload) + + header = Request._parse_request_header(req.header) + + return cls(req.header.sink_id, + req.endpoint, + req_id=header["req_id"], + time_ms_epoch=header["time_ms_epoch"]) + + @property + def payload(self): + message = GenericMessage() + + req_msg = self._get_related_message(message) + self._load_request_header(req_msg) + + req_msg.endpoint = self.cdc_endpoint + + return message.SerializeToString() + + +class GetConfigurationDataItemResponse(Response): + """ + GetConfigurationDataItemResponse: Response to answer a GetConfigurationDataItemRequest + + Attributes: + req_id (int): unique request id that this Response is associated + gw_id (str): gateway unique identifier + res (GatewayResultCode): result of the operation + sink_id (str): id of the sink (dependant on gateway) + cdc_endpoint (int): configuration data item endpoint. Optional. + If provided, cdc_payload should be provided too. + cdc_payload (bytes): configuration data item payload. Optional. + If provided, cdc_endpoint should be provided too. + """ + def __init__(self, req_id, gw_id, res, sink_id, cdc_endpoint=None, cdc_payload=None, **kwargs): + super().__init__(req_id, gw_id, res, sink_id, **kwargs) + self.cdc_endpoint = cdc_endpoint + self.cdc_payload = cdc_payload + + @staticmethod + def _get_related_message(generic_message): + return generic_message.wirepas.get_configuration_data_item_resp + + @classmethod + def from_payload(cls, payload): + response = cls._decode_and_get_related_message(payload) + + header = Response._parse_response_header(response.header) + + cdc_endpoint = None + cdc_payload = None + if response.configuration_data_item.IsInitialized(): + cdc_endpoint = response.configuration_data_item.endpoint + cdc_payload = response.configuration_data_item.payload + + return cls(header["req_id"], + header["gw_id"], + header["res"], + header["sink_id"], + cdc_endpoint, + cdc_payload, + time_ms_epoch=header["time_ms_epoch"]) + + @property + def payload(self): + message = GenericMessage() + + response = self._get_related_message(message) + self._load_response_header(response) + + if self.cdc_endpoint is not None: + response.configuration_data_item.endpoint = self.cdc_endpoint + if self.cdc_payload is not None: + response.configuration_data_item.payload = self.cdc_payload + + return message.SerializeToString() diff --git a/wirepas_mesh_messaging/get_gw_info.py b/wirepas_mesh_messaging/get_gw_info.py index b905896..fb22c2c 100644 --- a/wirepas_mesh_messaging/get_gw_info.py +++ b/wirepas_mesh_messaging/get_gw_info.py @@ -8,6 +8,7 @@ """ from .proto import GenericMessage +from .gateway_feature import GatewayFeature from .request import Request from .response import Response @@ -58,6 +59,7 @@ class GetGatewayInfoResponse(Response): gateway_version (string): gateway version (managed by gateway integrator) max_scratchpad_size (int): max scratchpad size gateway support. If bigger, transfer must happen as chunks, if None it is unlimited + gateway_features (list): list of GatewayFeature objects for supported features """ def __init__( @@ -69,6 +71,7 @@ def __init__( gateway_model=None, gateway_version=None, max_scratchpad_size=None, + gateway_features=None, implemented_api_version=None, **kwargs ): @@ -78,6 +81,7 @@ def __init__( self.gateway_version = gateway_version self.implemented_api_version = implemented_api_version self.max_scratchpad_size = max_scratchpad_size + self.gateway_features = gateway_features if gateway_features else [] @staticmethod def _get_related_message(generic_message): @@ -93,6 +97,10 @@ def from_payload(cls, payload): if max_size == 0: max_size = None + gateway_features = [] + for gateway_feature in response.info.gw_features: + gateway_features.append(GatewayFeature(gateway_feature)) + return cls( d["req_id"], d["gw_id"], @@ -102,6 +110,7 @@ def from_payload(cls, payload): gateway_version=response.info.gw_version, implemented_api_version=response.info.implemented_api_version, max_scratchpad_size=max_size, + gateway_features=gateway_features, time_ms_epoch=d["time_ms_epoch"] ) @@ -126,4 +135,7 @@ def payload(self): if self.max_scratchpad_size is not None: response.info.max_scratchpad_size = self.max_scratchpad_size + for gateway_feature in self.gateway_features: + response.info.gw_features.append(gateway_feature.value) + return message.SerializeToString() diff --git a/wirepas_mesh_messaging/set_config_data_item.py b/wirepas_mesh_messaging/set_config_data_item.py new file mode 100644 index 0000000..1e048e0 --- /dev/null +++ b/wirepas_mesh_messaging/set_config_data_item.py @@ -0,0 +1,102 @@ +""" + Set configuration data item + ========== + + .. Copyright: + Copyright 2025 Wirepas Ltd under Apache License, Version 2.0. + See file LICENSE for full license details. +""" + +from .proto import GenericMessage + +from .request import Request +from .response import Response + + +class SetConfigurationDataItemRequest(Request): + """ + SetConfigurationDataItemRequest: request to set or remove a configuration + data item on a given sink + + Attributes: + sink_id (str): id of the sink (dependant on gateway) + cdc_endpoint (int): configuration data item endpoint + cdc_payload (bytes): configuration data item payload. Set to an empty + bytes object for removing an item + req_id (int): unique request id + + """ + def __init__(self, sink_id, cdc_endpoint, cdc_payload, req_id=None, **kwargs): + super().__init__(req_id=req_id, **kwargs) + self.sink_id = sink_id + self.cdc_endpoint = cdc_endpoint + self.cdc_payload = cdc_payload + + @staticmethod + def _get_related_message(generic_message): + return generic_message.wirepas.set_configuration_data_item_req + + @classmethod + def from_payload(cls, payload): + req = cls._decode_and_get_related_message(payload) + + header = Request._parse_request_header(req.header) + + cdc_item = req.configuration_data_item + + return cls(req.header.sink_id, + cdc_item.endpoint, + cdc_item.payload, + req_id=header["req_id"], + time_ms_epoch=header["time_ms_epoch"]) + + @property + def payload(self): + message = GenericMessage() + + req_msg = self._get_related_message(message) + self._load_request_header(req_msg) + + req_msg.configuration_data_item.endpoint = self.cdc_endpoint + req_msg.configuration_data_item.payload = self.cdc_payload + + return message.SerializeToString() + + +class SetConfigurationDataItemResponse(Response): + """ + SetConfigurationDataItemResponse: Response to answer a SetConfigurationDataItemRequest + + Attributes: + req_id (int): unique request id that this Response is associated + gw_id (str): gateway unique identifier + res (GatewayResultCode): result of the operation + sink_id (str): id of the sink (dependant on gateway) + """ + def __init__(self, req_id, gw_id, res, sink_id, **kwargs): + super().__init__(req_id, gw_id, res, sink_id, **kwargs) + + @staticmethod + def _get_related_message(generic_message): + return generic_message.wirepas.set_configuration_data_item_resp + + @classmethod + def from_payload(cls, payload): + response = cls._decode_and_get_related_message(payload) + + header = Response._parse_response_header(response.header) + + return cls(header["req_id"], + header["gw_id"], + header["res"], + header["sink_id"], + time_ms_epoch=header["time_ms_epoch"]) + + @property + def payload(self): + message = GenericMessage() + + response = self._get_related_message(message) + self._load_response_header(response) + + return message.SerializeToString() diff --git a/wirepas_mesh_messaging/status.py b/wirepas_mesh_messaging/status.py index 312f1c4..2345d5d 100644 --- a/wirepas_mesh_messaging/status.py +++ b/wirepas_mesh_messaging/status.py @@ -9,6 +9,7 @@ import enum from .proto import GenericMessage, ON, OFF +from .gateway_feature import GatewayFeature from .event import Event @@ -52,6 +53,8 @@ class StatusEvent(Event): sink_configs (list): list of dictionnary containing the sink configs gateway_model (string): gateway model gateway_version (string): gateway version + max_scratchpad_size (int): maximum scratchpad size + gateway_features (list): list of GatewayFeature objects for supported features """ def __init__( @@ -64,6 +67,7 @@ def __init__( gateway_model=None, gateway_version=None, max_scratchpad_size=None, + gateway_features=None, **kwargs ): super(StatusEvent, self).__init__(gw_id, event_id=event_id, **kwargs) @@ -73,6 +77,7 @@ def __init__( self.gateway_model = gateway_model self.gateway_version = gateway_version self.max_scratchpad_size = max_scratchpad_size + self.gateway_features = gateway_features if gateway_features else [] @staticmethod def _get_related_message(generic_message): @@ -119,6 +124,10 @@ def from_payload(cls, payload): # First config configs = [config] + gateway_features = [] + for gateway_feature in event.gw_features: + gateway_features.append(GatewayFeature(gateway_feature)) + d = Event._parse_event_header(event.header) return cls(d["gw_id"], online, @@ -127,7 +136,8 @@ def from_payload(cls, payload): time_ms_epoch=d["time_ms_epoch"], gateway_model=gw_model, gateway_version=gw_version, - max_scratchpad_size=max_scratchpad_size) + max_scratchpad_size=max_scratchpad_size, + gateway_features=gateway_features) @property def payload(self): @@ -162,4 +172,7 @@ def payload(self): if self.max_scratchpad_size is not None: status.max_scratchpad_size = self.max_scratchpad_size + for gateway_feature in self.gateway_features: + status.gw_features.append(gateway_feature.value) + return message.SerializeToString()