From bdaef3930cce64522f0db5cf5cbc0d88053a83c5 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 21 Oct 2021 22:50:50 +0300 Subject: [PATCH 01/55] Fix message sending --- agrirouter/messaging/clients/http.py | 17 ++++++++++++----- agrirouter/messaging/decode.py | 8 ++++---- agrirouter/messaging/encode.py | 10 +++++----- agrirouter/messaging/enums.py | 15 +++++++++++++++ agrirouter/messaging/messages.py | 5 +++-- agrirouter/messaging/parameters/dto.py | 10 +++++----- agrirouter/messaging/parameters/service.py | 8 ++++---- agrirouter/messaging/request.py | 2 +- agrirouter/messaging/services/cloud.py | 4 ++-- agrirouter/messaging/services/commons.py | 19 +++++++++++-------- agrirouter/messaging/services/messaging.py | 14 +++++++------- agrirouter/onboarding/exceptions.py | 4 ++++ agrirouter/onboarding/response.py | 4 ++++ agrirouter/utils/utc_time_util.py | 4 ++++ agrirouter/utils/uuid_util.py | 2 +- 15 files changed, 82 insertions(+), 44 deletions(-) diff --git a/agrirouter/messaging/clients/http.py b/agrirouter/messaging/clients/http.py index fa3110e9..08b19664 100644 --- a/agrirouter/messaging/clients/http.py +++ b/agrirouter/messaging/clients/http.py @@ -2,6 +2,7 @@ import json import os import ssl +from urllib.parse import urlparse from agrirouter.messaging.certification import create_certificate_file_from_pen from agrirouter.onboarding.dto import ConnectionCriteria @@ -20,8 +21,7 @@ def __init__( self.on_message_callback = on_message_callback self.timeout = timeout - @staticmethod - def make_connection(certificate_file_path: str, onboard_response: SoftwareOnboardingResponse): + def make_connection(self, certificate_file_path: str, onboard_response: SoftwareOnboardingResponse): context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) context.load_cert_chain( certfile=certificate_file_path, @@ -29,8 +29,8 @@ def make_connection(certificate_file_path: str, onboard_response: SoftwareOnboar password=onboard_response.get_authentication().get_secret(), ) connection = http.client.HTTPSConnection( - host=onboard_response.connection_criteria.get_host(), - port=onboard_response.connection_criteria.get_port(), + host=self.get_host(onboard_response.connection_criteria.get_measures()), + port=self.get_port(onboard_response.connection_criteria.get_measures()), context=context ) return connection @@ -44,7 +44,7 @@ def send(self, method: str, onboard_response: SoftwareOnboardingResponse, reques method=method, url=onboard_response.get_connection_criteria().get_measures(), headers=self.headers, - body=json.dumps(request_body) + body=json.dumps(request_body.json_serialize()) ) else: connection.request( @@ -66,3 +66,10 @@ def unsubscribe(self): def _start_loop(self): pass + + def get_host(self, uri): + return urlparse(uri).netloc + + def get_port(self, uri): + return urlparse(uri).port if urlparse(uri).port else None + diff --git a/agrirouter/messaging/decode.py b/agrirouter/messaging/decode.py index 839a0e48..dd9672b0 100644 --- a/agrirouter/messaging/decode.py +++ b/agrirouter/messaging/decode.py @@ -40,11 +40,11 @@ def decode_response(message: bytes) -> DecodedMessage: def decode_details(details: Any): - if details.type_url == TypeUrl.get_type_url(Messages.__name__): + if details.type_url == TypeUrl.get_type_url(Messages): return Messages().MergeFromString(details.value) - elif details.type_url == TypeUrl.get_type_url(ListEndpointsResponse.__name__): + elif details.type_url == TypeUrl.get_type_url(ListEndpointsResponse): return ListEndpointsResponse().MergeFromString(details.value) - elif details.type_url == TypeUrl.get_type_url(HeaderQueryResponse.__name__): + elif details.type_url == TypeUrl.get_type_url(HeaderQueryResponse): return HeaderQueryResponse().MergeFromString(details.value) - elif details.type_url == TypeUrl.get_type_url(MessageQueryResponse.__name__): + elif details.type_url == TypeUrl.get_type_url(MessageQueryResponse): return MessageQueryResponse().MergeFromString(details.value) diff --git a/agrirouter/messaging/encode.py b/agrirouter/messaging/encode.py index fe74c229..5ad1361c 100644 --- a/agrirouter/messaging/encode.py +++ b/agrirouter/messaging/encode.py @@ -18,23 +18,23 @@ def write_proto_parts_to_buffer(parts: list, buffer: bytes = b""): return buffer -def encode_message(header_parameters: MessageHeaderParameters, payload_parameters: MessagePayloadParameters) -> bytes: +def encode_message(header_parameters: MessageHeaderParameters, payload_parameters: MessagePayloadParameters) -> str: request_envelope = encode_header(header_parameters) request_payload = encode_payload(payload_parameters) raw_data = write_proto_parts_to_buffer([request_envelope, request_payload]) - return base64.b64encode(raw_data) + return base64.b64encode(raw_data).decode() def encode_header(header_parameters: MessageHeaderParameters) -> RequestEnvelope: request_envelope = RequestEnvelope() - request_envelope.application_id = header_parameters.get_application_message_id() \ + request_envelope.application_message_id = header_parameters.get_application_message_id() \ if header_parameters.get_application_message_id() else new_uuid() request_envelope.application_message_seq_no = header_parameters.get_application_message_seq_no() request_envelope.technical_message_type = header_parameters.get_technical_message_type() request_envelope.mode = header_parameters.get_mode() - request_envelope.timestamp = now_as_utc_timestamp() + request_envelope.timestamp.FromDatetime(now_as_utc_timestamp()) return request_envelope @@ -42,5 +42,5 @@ def encode_payload(payload_parameters: MessagePayloadParameters) -> RequestPaylo any_proto_wrapper = Any() any_proto_wrapper.type_url = payload_parameters.get_type_url() any_proto_wrapper.value = payload_parameters.get_value() - request_payload = RequestPayloadWrapper(any_proto_wrapper) + request_payload = RequestPayloadWrapper(details=any_proto_wrapper) return request_payload diff --git a/agrirouter/messaging/enums.py b/agrirouter/messaging/enums.py index 1247118f..0b9354cf 100644 --- a/agrirouter/messaging/enums.py +++ b/agrirouter/messaging/enums.py @@ -13,3 +13,18 @@ class TechnicalMessageType(BaseEnum): FEED_MESSAGE_QUERY = "dke:feed_message_query" CLOUD_ONBOARD_ENDPOINTS = "dke:cloud_onboard_endpoints" CLOUD_OFFBOARD_ENDPOINTS = "dke:cloud_offboard_endpoints" + + +class CapabilityTypeDefinitions(BaseEnum): + ISO_11783_TASKDATA_ZIP = "iso:11783:-10:taskdata:zip" + ISO_11783_DEVICE_DESCRIPTION_PROTOBUF = "iso:11783:-10:device_description:protobuf" + ISO_11783_TIMELOG_PROTOBUF = "iso:11783:-10:time_log:protobuf" + IMG_BMP = "img:bmp" + IMG_JPEG = "img:jpeg" + IMG_PNG = "img:png" + SHP_SHAPE_ZIP = "shp:shape:zip" + DOC_PDF = "doc:pdf" + VID_AVI = "vid:avi" + VID_MP4 = "vid:mp4" + VID_WMV = "vid:wmv" + GPS_INFO = "gps:info" diff --git a/agrirouter/messaging/messages.py b/agrirouter/messaging/messages.py index 1e5228fd..2d5d1671 100644 --- a/agrirouter/messaging/messages.py +++ b/agrirouter/messaging/messages.py @@ -3,6 +3,7 @@ from typing import Union, List, Dict from agrirouter.messaging.exceptions import WrongFieldError +from agrirouter.utils.utc_time_util import now_as_utc_str class EncodedMessage: @@ -36,12 +37,12 @@ class Message: def __init__(self, content): self.content = content - self.timestamp = datetime.utcnow() + self.timestamp = now_as_utc_str() def json_serialize(self) -> dict: return { self.MESSAGE: self.content, - self.TIMESTAMP: self.timestamp.strftime("%Y-%m-%dT%H:%M:%S.%fZ") + self.TIMESTAMP: self.timestamp } diff --git a/agrirouter/messaging/parameters/dto.py b/agrirouter/messaging/parameters/dto.py index 3c1404de..4646a1b6 100644 --- a/agrirouter/messaging/parameters/dto.py +++ b/agrirouter/messaging/parameters/dto.py @@ -9,7 +9,7 @@ class Parameters: def __init__(self, *, application_message_seq_no: str, - application_message_id: int = None, + application_message_id: str = None, team_set_context_id: str ): self.application_message_seq_no = application_message_seq_no @@ -42,8 +42,8 @@ class MessageParameters(Parameters): def __init__(self, *, application_message_seq_no: str, - application_message_id: int, - team_set_context_id: str, + application_message_id: str, + team_set_context_id: str = None, onboarding_response: BaseOnboardingResonse ): super(MessageParameters, self).__init__( @@ -54,7 +54,7 @@ def __init__(self, self.onboarding_response = onboarding_response - def get_onboarding_response(self): + def get_onboarding_response(self) -> BaseOnboardingResonse: return self.onboarding_response @@ -63,7 +63,7 @@ class MessagingParameters(MessageParameters): def __init__(self, *, application_message_seq_no: str = None, - application_message_id: int = None, + application_message_id: str = None, team_set_context_id: str = None, onboarding_response: BaseOnboardingResonse, encoded_messages=None diff --git a/agrirouter/messaging/parameters/service.py b/agrirouter/messaging/parameters/service.py index 0769fa08..f4ee63b6 100644 --- a/agrirouter/messaging/parameters/service.py +++ b/agrirouter/messaging/parameters/service.py @@ -18,7 +18,7 @@ def __init__(self, application_message_seq_no: str = None, recipients: list = None, chunk_component: ChunkComponent = None, - application_message_id: int = None, + application_message_id: str = None, ): super(MessageHeaderParameters, self).__init__( application_message_seq_no=application_message_seq_no, @@ -217,7 +217,7 @@ def set_validity_period(self, validity_period: ValidityPeriod): class ListEndpointsParameters(MessageParameters): def __init__(self, technical_message_type: str = None, - direction: str = None, + direction: int = None, filtered: bool = False, **kwargs): self.technical_message_type = technical_message_type @@ -231,10 +231,10 @@ def get_technical_message_type(self) -> str: def set_technical_message_type(self, technical_message_type: str): self.technical_message_type = technical_message_type - def get_direction(self) -> str: + def get_direction(self) -> int: return self.direction - def set_direction(self, direction: str): + def set_direction(self, direction: int): self.direction = direction def is_filtered(self): diff --git a/agrirouter/messaging/request.py b/agrirouter/messaging/request.py index 4de0a69e..9cc9507e 100644 --- a/agrirouter/messaging/request.py +++ b/agrirouter/messaging/request.py @@ -11,7 +11,7 @@ class MessageRequest: def __init__(self, sensor_alternate_id: str, capability_alternate_id: str, - messages: List[Message] + messages: List[dict] ): self.sensor_alternate_id = sensor_alternate_id self.capability_alternate_id = capability_alternate_id diff --git a/agrirouter/messaging/services/cloud.py b/agrirouter/messaging/services/cloud.py index cd40a465..217462e2 100644 --- a/agrirouter/messaging/services/cloud.py +++ b/agrirouter/messaging/services/cloud.py @@ -28,7 +28,7 @@ def encode(parameters: CloudOnboardParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(OnboardingRequest.__name__), + type_url=TypeUrl.get_type_url(OnboardingRequest), value=onboarding_request.SerializeToString() ) @@ -58,7 +58,7 @@ def encode(parameters: CloudOffboardParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(OffboardingRequest.__name__), + type_url=TypeUrl.get_type_url(OffboardingRequest), value=offboarding_request.SerializeToString() ) diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index 939a6fdc..ca2b4cad 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -3,12 +3,13 @@ import requests -from agrirouter.messaging.certification import create_certificate_file +from agrirouter.messaging.certification import create_certificate_file_from_pen from agrirouter.messaging.clients.http import HttpClient from agrirouter.messaging.clients.mqtt import MqttClient from agrirouter.messaging.messages import Message from agrirouter.messaging.request import MessageRequest from agrirouter.messaging.result import MessagingResult +from agrirouter.onboarding.exceptions import BadMessagingResult class AbstractMessagingClient(ABC): @@ -18,10 +19,10 @@ def create_message_request(parameters) -> MessageRequest: messages = [] for encoded_message in parameters.get_encoded_messages(): message = Message(encoded_message) - messages.append(message) + messages.append(message.json_serialize()) message_request = MessageRequest( - parameters.get_sensor_alternate_id(), - parameters.get_capability_alternate_id(), + parameters.get_onboarding_response().get_sensor_alternate_id(), + parameters.get_onboarding_response().get_capability_alternate_id(), messages ) return message_request @@ -33,8 +34,8 @@ def send(self, parameters): class HttpMessagingService(AbstractMessagingClient): - def __init__(self, on_message_callback, timeout): - self.client = HttpClient(on_message_callback=on_message_callback, timeout=timeout) + def __init__(self, on_message_callback): + self.client = HttpClient(on_message_callback=on_message_callback) def send(self, parameters) -> MessagingResult: request = self.create_message_request(parameters) @@ -43,7 +44,9 @@ def send(self, parameters) -> MessagingResult: parameters.get_onboarding_response(), request ) - result = MessagingResult([parameters.get_message_id()]) + if response.status in [400, 401, 403, 404, 500]: + raise BadMessagingResult(f"Messaging Request failed with status code {response.status}") + result = MessagingResult([parameters.get_application_message_id()]) return result def subscribe(self): @@ -76,7 +79,7 @@ def send(self, parameters, qos: int = 0) -> MessagingResult: self.onboarding_response.get_connection_criteria().get_measures(), mqtt_payload, qos=qos ) - result = MessagingResult([parameters.get_message_id()]) + result = MessagingResult([parameters.get_application_message_id()]) return result def subscribe(self): diff --git a/agrirouter/messaging/services/messaging.py b/agrirouter/messaging/services/messaging.py index e071b028..5a6cdb80 100644 --- a/agrirouter/messaging/services/messaging.py +++ b/agrirouter/messaging/services/messaging.py @@ -55,7 +55,7 @@ def encode(parameters: CapabilityParameters) -> EncodedMessage: capability_specification.capabilities = parameters.get_capability_parameters() message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(CapabilitySpecification.__name__), + type_url=TypeUrl.get_type_url(CapabilitySpecification), value=capability_specification.SerializeToString() ) @@ -85,7 +85,7 @@ def encode(parameters: FeedConfirmParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(MessageConfirm.__name__), + type_url=TypeUrl.get_type_url(MessageConfirm), value=message_confirm.SerializeToString() ) @@ -115,7 +115,7 @@ def encode(parameters: FeedDeleteParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(MessageConfirm.__name__), + type_url=TypeUrl.get_type_url(MessageConfirm), value=message_confirm.SerializeToString() ) @@ -147,7 +147,7 @@ def encode(parameters: ListEndpointsParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(ListEndpointsQuery.__name__), + type_url=TypeUrl.get_type_url(ListEndpointsQuery), value=list_endpoints_query.SerializeToString() ) @@ -179,7 +179,7 @@ def encode(parameters: QueryMessageParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(MessageQuery.__name__), + type_url=TypeUrl.get_type_url(MessageQuery), value=message_query.SerializeToString() ) @@ -211,7 +211,7 @@ def encode(parameters: QueryHeaderParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(MessageQuery.__name__), + type_url=TypeUrl.get_type_url(MessageQuery), value=message_query.SerializeToString() ) @@ -241,7 +241,7 @@ def encode(parameters: SubscriptionParameters) -> EncodedMessage: ) message_payload_parameters = MessagePayloadParameters( - type_url=TypeUrl.get_type_url(Subscription.__name__), + type_url=TypeUrl.get_type_url(Subscription), value=subscription.SerializeToString() ) diff --git a/agrirouter/onboarding/exceptions.py b/agrirouter/onboarding/exceptions.py index 490e51e3..fb3b024a 100644 --- a/agrirouter/onboarding/exceptions.py +++ b/agrirouter/onboarding/exceptions.py @@ -21,3 +21,7 @@ class RequestNotSigned(AgriRouuterBaseException): Details on: https://docs.my-agrirouter.com/agrirouter-interface-documentation/latest/ integration/onboarding.html#signing-requests """ + + +class BadMessagingResult(AgriRouuterBaseException): + _message = "Messaging Request failed" diff --git a/agrirouter/onboarding/response.py b/agrirouter/onboarding/response.py index 50826c4c..d62ce45f 100644 --- a/agrirouter/onboarding/response.py +++ b/agrirouter/onboarding/response.py @@ -1,3 +1,5 @@ +import json + from requests import Response from agrirouter.onboarding.dto import ErrorResponse, ConnectionCriteria, Authentication @@ -54,6 +56,8 @@ def __init__(self, http_response: Response): measures=response_body.get("connectionCriteria").get("measures"), commands=response_body.get("connectionCriteria").get("commands"), host=response_body.get("connectionCriteria").get("host"), + port=response_body.get("connectionCriteria").get("port"), + client_id=response_body.get("connectionCriteria").get("client_id") ) if response_body.get("connectionCriteria", None) else None self.authentication = Authentication( diff --git a/agrirouter/utils/utc_time_util.py b/agrirouter/utils/utc_time_util.py index b86f3afb..b87ca808 100644 --- a/agrirouter/utils/utc_time_util.py +++ b/agrirouter/utils/utc_time_util.py @@ -2,5 +2,9 @@ def now_as_utc_timestamp(): + return datetime.utcnow() + + +def now_as_utc_str(): timestamp = datetime.utcnow() return timestamp.strftime("%Y-%m-%dT%H:%M:%S.%fZ") diff --git a/agrirouter/utils/uuid_util.py b/agrirouter/utils/uuid_util.py index 0c9bd872..8e500ead 100644 --- a/agrirouter/utils/uuid_util.py +++ b/agrirouter/utils/uuid_util.py @@ -2,4 +2,4 @@ def new_uuid(): - return uuid.uuid4() + return str(uuid.uuid4()) From c52a9b69da78984c32c35c9cf16275a6d0a29dea Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Fri, 22 Oct 2021 00:05:23 +0300 Subject: [PATCH 02/55] Fix revoking --- agrirouter/revoking/parameters.py | 6 +++--- agrirouter/revoking/request.py | 6 +++++- agrirouter/revoking/request_body.py | 11 ++++------- agrirouter/revoking/revoking.py | 6 +++--- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/agrirouter/revoking/parameters.py b/agrirouter/revoking/parameters.py index 95a73332..cf706b02 100644 --- a/agrirouter/revoking/parameters.py +++ b/agrirouter/revoking/parameters.py @@ -8,7 +8,7 @@ def __init__(self, account_id, endpoint_ids, utc_timestamp, - timestamp, + time_zone, content_type=ContentTypes.APPLICATION_JSON.value ): @@ -17,7 +17,7 @@ def __init__(self, self.account_id = account_id self.endpoint_ids = endpoint_ids self.utc_timestamp = utc_timestamp - self.timestamp = timestamp + self.time_zone = time_zone def get_header_params(self): return { @@ -30,5 +30,5 @@ def get_body_params(self): "account_id": self.account_id, "endpoint_ids": self.endpoint_ids, "utc_timestamp": self.utc_timestamp, - "timestamp": self.timestamp, + "time_zone": self.time_zone, } diff --git a/agrirouter/revoking/request.py b/agrirouter/revoking/request.py index cb8605fa..1fea09e1 100644 --- a/agrirouter/revoking/request.py +++ b/agrirouter/revoking/request.py @@ -18,8 +18,12 @@ def get_data(self): def get_header(self): return self.header.get_header() + def get_body_content(self): + return self.body.json().replace("\n", "") + def sign(self, private_key): - signature = create_signature(self.body.json(new_lines=False), private_key) + body = self.get_body_content() + signature = create_signature(body, private_key) self.header.sign(signature) @property diff --git a/agrirouter/revoking/request_body.py b/agrirouter/revoking/request_body.py index 6ec70b2d..7bdea6e5 100644 --- a/agrirouter/revoking/request_body.py +++ b/agrirouter/revoking/request_body.py @@ -26,14 +26,11 @@ def _set_params(self, ) -> None: self.params = { - "account_id": account_id, - "endpoint_ids": endpoint_ids, + "accountId": account_id, + "endpointIds": endpoint_ids, "UTCTimestamp": utc_timestamp, "timeZone": time_zone, } - def json(self, new_lines: bool = True) -> str: - result = json.dumps(self.get_parameters(), indent="") - if not new_lines: - return result.replace("\n", "") - return result + def json(self) -> str: + return json.dumps(self.get_parameters(), separators=(',', ':')) diff --git a/agrirouter/revoking/revoking.py b/agrirouter/revoking/revoking.py index aaa8f871..b835bc8f 100644 --- a/agrirouter/revoking/revoking.py +++ b/agrirouter/revoking/revoking.py @@ -29,9 +29,9 @@ def _perform_request(self, params: RevokingParameter, url: str) -> requests.Resp request = self._create_request(params, url) request.sign(self._private_key) if request.is_signed: - return requests.post( + return requests.delete( url=request.get_url(), - data=request.get_data(), + json=request.get_data(), headers=request.get_header() ) raise RequestNotSigned @@ -40,4 +40,4 @@ def revoke(self, params: RevokingParameter) -> RevokingResponse: url = self._environment.get_revoke_url() http_response = self._perform_request(params=params, url=url) - return RevokingResponse(http_response) \ No newline at end of file + return RevokingResponse(http_response) From e0b69e807921bf679e30ef5065e5d1abcb0b7761 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Fri, 22 Oct 2021 00:13:40 +0300 Subject: [PATCH 03/55] Fix MqttMessagingService --- agrirouter/messaging/services/commons.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index ca2b4cad..7afa7d7c 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -1,3 +1,4 @@ +import json import os from abc import ABC, abstractmethod @@ -65,7 +66,7 @@ def __init__(self, self.onboarding_response = onboarding_response self.client = MqttClient( - client_id=self.onboarding_response.get_client_id(), + client_id="asdfg", on_message_callback=on_message_callback, ) self.client.connect( @@ -74,9 +75,10 @@ def __init__(self, ) def send(self, parameters, qos: int = 0) -> MessagingResult: - mqtt_payload = self.create_message_request(parameters) + message_request = self.create_message_request(parameters) + mqtt_payload = message_request.json_serialize() self.client.publish( - self.onboarding_response.get_connection_criteria().get_measures(), mqtt_payload, + self.onboarding_response.get_connection_criteria().get_measures(), json.dumps(mqtt_payload), qos=qos ) result = MessagingResult([parameters.get_application_message_id()]) From 81bae132c90289bd0197b332d7a79b1f2112c33f Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Mon, 25 Oct 2021 14:39:45 +0300 Subject: [PATCH 04/55] Update examples.txt --- examples.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/examples.txt b/examples.txt index e59fe910..2fce2d32 100644 --- a/examples.txt +++ b/examples.txt @@ -112,3 +112,21 @@ True "deviceAlternateId": "c067272a-d3a7-4dcf-ab58-5c45ba66ad60", "sensorAlternateId": "5564ce96-385f-448a-9502-9ea3c940a259" } + + +>>> ########################## +>>> Messaging + + +>>> client = HttpClient(lambda x: x) +>>> messaging_service = HttpMessagingService(client) +>>> list_endpoint_parameters = ListEndpointsParameters( + technical_message_type=CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value, + direction=2, + filtered=False, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) +>>> list_endpoint_service = ListEndpointsService(messaging_service) +>>> list_endpoint_service.send(list_endpoint_parameters) From 0ad04c936e354302ea3e4ebd2354b3900ab99b0c Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Mon, 25 Oct 2021 15:16:28 +0300 Subject: [PATCH 05/55] Fix messaging in examples --- examples.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/examples.txt b/examples.txt index 2fce2d32..cf24a7a3 100644 --- a/examples.txt +++ b/examples.txt @@ -118,6 +118,7 @@ True >>> Messaging +>>> from agrirouter.messaging.clients.http import HttpClient >>> client = HttpClient(lambda x: x) >>> messaging_service = HttpMessagingService(client) >>> list_endpoint_parameters = ListEndpointsParameters( From 9abf794a5bafec5f319ef9a56df9e64862a9179a Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Mon, 25 Oct 2021 18:51:59 +0300 Subject: [PATCH 06/55] Refactor Mqtt client --- agrirouter/messaging/clients/mqtt.py | 9 ++++++++- agrirouter/messaging/services/commons.py | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/agrirouter/messaging/clients/mqtt.py b/agrirouter/messaging/clients/mqtt.py index 4e8dd555..72ff399d 100644 --- a/agrirouter/messaging/clients/mqtt.py +++ b/agrirouter/messaging/clients/mqtt.py @@ -7,7 +7,7 @@ class MqttClient: def __init__(self, - client_id: str = "", + client_id, on_message_callback: callable = None, userdata: Any = None, clean_session: bool = True @@ -88,6 +88,13 @@ def unsubscribe(self, topics: List[str]) -> tuple: def _get_on_connect_callback() -> callable: def on_connect(client, userdata, flags, rc, properties=None): + print("Connection started") + with open("connection.txt", "w") as file: + file.write("Connection started") + if rc == 0: + file.write("Connected!!") + else: + file.write("Do not Connected!!") if rc == 0: print("Connected to MQTT Broker!") else: diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index 7afa7d7c..11702561 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -60,13 +60,14 @@ def unsubscribe(self): class MqttMessagingService(AbstractMessagingClient): def __init__(self, + client_id, onboarding_response, on_message_callback: callable = None, ): self.onboarding_response = onboarding_response self.client = MqttClient( - client_id="asdfg", + client_id=client_id, on_message_callback=on_message_callback, ) self.client.connect( From c90ba11c9cd9975383416e4cd186eaefe9d5bedf Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Mon, 25 Oct 2021 21:28:02 +0300 Subject: [PATCH 07/55] Update examples --- examples.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/examples.txt b/examples.txt index cf24a7a3..b4ebf13d 100644 --- a/examples.txt +++ b/examples.txt @@ -119,7 +119,12 @@ True >>> from agrirouter.messaging.clients.http import HttpClient ->>> client = HttpClient(lambda x: x) +>>> from agrirouter.messaging.clients.mqtt import MqttClient +>>> from agrirouter.messaging.enums import CapabilityTypeDefinitions +>>> from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService +>>> from agrirouter import ListEndpointsParameters, ListEndpointsService + +>>> client = HttpClient(client_id="8c942k45-c03d-44y2-affc-206e893m63a50", on_message_callback=lambda x: x) # Enter your client_id and on_message_callback >>> messaging_service = HttpMessagingService(client) >>> list_endpoint_parameters = ListEndpointsParameters( technical_message_type=CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value, From f944e84308b999c444cdaf692055cf2f6152ecec Mon Sep 17 00:00:00 2001 From: Alexey Date: Tue, 26 Oct 2021 00:45:36 +0300 Subject: [PATCH 08/55] add test script --- example_script.py | 116 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 example_script.py diff --git a/example_script.py b/example_script.py new file mode 100644 index 00000000..3d2b25aa --- /dev/null +++ b/example_script.py @@ -0,0 +1,116 @@ +public_key = """-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzGt41/+kSOTlO1sJvLIN +6RAFaOn6GiCNX/Ju0oVT4VMDHfuQMI5t9+ZgBxFmUhtY5/eykQfYJVGac/cy5xyk +F/1xpMzltK7pfo7XZpfHjkHLPUOeaHW0zE+g2vopQOARKE5LSguCBUhdtfFuiheR +IP0EU+MtEQDhlfiqYLAJkAvZHluCH9q6hawn0t/G873jlzsrXBqIgKboXqyz1lRE +SvMyqX04Xwaq1CgAZjHXBVWvbuOriCR0P2n13/nkCgBgLd/ORwVilb4GQDXkkCSg +uOVcRU3s/KG/OVJTonHVlLvDzBA5GLrpZMpzC4EfzXBM98s4Vj6IOAIQeY84Sppj +qwIDAQAB +-----END PUBLIC KEY-----""" + +private_key = """-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDMa3jX/6RI5OU7 +Wwm8sg3pEAVo6foaII1f8m7ShVPhUwMd+5Awjm335mAHEWZSG1jn97KRB9glUZpz +9zLnHKQX/XGkzOW0rul+jtdml8eOQcs9Q55odbTMT6Da+ilA4BEoTktKC4IFSF21 +8W6KF5Eg/QRT4y0RAOGV+KpgsAmQC9keW4If2rqFrCfS38bzveOXOytcGoiApuhe +rLPWVERK8zKpfThfBqrUKABmMdcFVa9u46uIJHQ/afXf+eQKAGAt385HBWKVvgZA +NeSQJKC45VxFTez8ob85UlOicdWUu8PMEDkYuulkynMLgR/NcEz3yzhWPog4AhB5 +jzhKmmOrAgMBAAECggEAEEr6mUCzb+nqiWYSqxsH980CmV+Yww9YJU8V3SqqSlnK +9E9SKUSY6DrQ6Y9N9/pdBjQcY+nbpPHRnS+VO41xWMYnEisQneuZCbDJ40/ypFiD +IfFrRUkobWZlXD63Hggd5fgDkTXEmbYwXemN1WzWcOopt6PyOho3YLQupEEzqerb +XkzBFWwWO9589fbWnlaSoJPtgA8gFxeJJkU3kG10Epj6wV17yo6DuyVZpemGPTUL +uVl7yNx9O/Lp8UXRlBtSEEBQqoJaGy9mzVZyobXNKvdlZxwlkbJQpZB/m4dzqbyn +Wv+lSJdmbOnOzc67FfRqHf/irIdg6aInJd6WxZ3rPQKBgQDlxrcePlzpNwJOWtXb +sneHU50Lx73u183q5dtKlH/FudhOgP4aot6+q1KDu3b9rRakGJUKQYoLgGNwhl/7 +5CF0iKQE+5JZ5R9YpwFoDuALjPfic5vFN76G851ccz5pfThLjCMV1NgKJskaefP0 +OdV+UW9qOIxR8UAMntWTTrQzFwKBgQDjv+2Kz1/KsXSPaw+mJKsmUnC2YbqeAr+9 +Dwm7Hr0RZWkkS2EjqcMxvq0D8bYcuJvrlZFmB/r6Ly0MKlfsUT+64LAQnKHhlCUi +vlE7VuDOR16lC4ZCPeWtjrL45fpj+Lhe54m7rCT8F+Ocdxv2yNQrSBbQ6epOVuDz +XJaSRt/AjQKBgQCrBZPIS+yFnO73eP6SLixvKhnK6dmBi1h1zK3CvfK4LZJFJBd9 +pdoampOo/wAa4hjm/HD6GDvyQZZB65JHfs4z2XwTRVfx1urU5kDSvbeegUcDYr7/ +NHV4JpzqcdBzXcNn359BoZFHRQUL0tdz4RP5mA1QR1SRrPnaKuKWaM8Q8wKBgQC5 +mY9br+PAqxzyQ61dGETh1g1ElCAg5NyclcS4WTR7GMm2ajefeJk50MnujOx8O3XV +Zu422AoQGKH9aAR+8Teec70HzJ2f17rrtW09jm9lq4PVvK6NDSQ/bCst6z1Ce07F +CKuV5ZO+XTmAKREA7Gj7XKQ7XGU1sldf+/Q5AMkXgQKBgQC4lXL9zLV/vfWUTPSR +qlGcS2+WYtjWPapDZa+7zlxGdPgOTri4nJO69Bs9ReLlzsYKSBihfpWPxcl9sS65 +KFrlBkR/vzKYjCFXB6cmMP61mUrgGQRoYJQBetAyEiXZL3zjt1R/Dndk0kHkVmHr +HjmgzBRxXFy5uph6Ue6dxyszaA== +-----END PRIVATE KEY-----""" + + +application_id = "8c947a45-c57d-42d2-affc-206e21d63a50" # # store here your application id. You can find it in AR UI + + +######################################################## +# Authorization +print("Authorization...\n") + +import agrirouter as ar + +auth_params = ar.AuthUrlParameter(application_id=application_id, response_type="onboard") +auth_client = ar.Authorization("QA", public_key=public_key, private_key=private_key) +auth_url = auth_client.get_auth_request_url(auth_params) # use this url to authorize the user as described at https://docs.my-agrirouter.com/agrirouter-interface-documentation/latest/integration/authorization.html#perform-authorization +print(f"auth_url={auth_url}") + +auth_result_url = input("Enter auth_url (the url the user was redirected to after his authorization, see above): ") # the url the user was redirected to after his authorization. +auth_response = auth_client.extract_auth_response(auth_result_url) # auth_response contains the results of the auth process +auth_client.verify_auth_response(auth_response) # you may verify auth_response to ensure answer was from AR + +print(f"auth_response is successful: {auth_response.is_successful}") # True if user accepted application, False if he rejected + +print(f"auth_response is valid: {auth_response.is_valid}") # Result of verification, if False, response was not validated by public key. Doesn't indicate the auth was successfull. Accessible only after response verifying + + +# Get dict containing data from auth process you will use for futher communication. +# If auth was rejected, contains {"error"} key. +# If auth was accepted, contains {signature, state, token, credentials{account, expires, regcode}} keys +# Even if response verifying was not processed or failed, the results will be returned. But in that case you act on your risk. +auth_data = auth_response.get_auth_result() +print(f"auth_data: {auth_data}") + +######################################################## + +# Onboarding +print("Onboarding...\n") + + +from agrirouter.onboarding.enums import GateWays + +id_ = "urn:myapp:snr00003234" # just unique +certification_version_id = "edd5d6b7-45bb-4471-898e-ff9c2a7bf56f" # get from AR UI +time_zone = "+03:00" + +onboarding_client = ar.SoftwareOnboarding("QA", public_key=public_key, private_key=private_key) +onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, certification_version_id=certification_version_id, gateway_id=GateWays.REST.value, time_zone=time_zone, reg_code=auth_data["credentials"]["regcode"]) +onboarding_verifying_response = onboarding_client.verify(onboarding_parameters) +print(f"onboarding_verifying_response: {onboarding_verifying_response}") +print(f"onboarding_verifying_response.status_code: {onboarding_verifying_response.status_code}") +print(f"onboarding_verifying_response.text: {onboarding_verifying_response}") +onboarding_response = onboarding_client.onboard(onboarding_parameters) +print(f"onboarding_response.status_code: {onboarding_response.status_code}") +print(f"onboarding_response.text: {onboarding_response.text}") + + +########################## +# Messaging + + +from agrirouter.messaging.clients.http import HttpClient +from agrirouter.messaging.clients.mqtt import MqttClient +from agrirouter.messaging.enums import CapabilityTypeDefinitions +from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService +from agrirouter import ListEndpointsParameters, ListEndpointsService +from agrirouter.utils.uuid_util import new_uuid + +client = HttpClient(on_message_callback=lambda x: x) # Enter your client_id and on_message_callback +messaging_service = HttpMessagingService(client) +list_endpoint_parameters = ListEndpointsParameters( + technical_message_type=CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value, + direction=2, + filtered=False, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) +list_endpoint_service = ListEndpointsService(messaging_service) +list_endpoint_service.send(list_endpoint_parameters) From 68674dfea85f8ba252ffaa35b599af52b32dbcda Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 26 Oct 2021 00:52:55 +0300 Subject: [PATCH 09/55] Update example --- examples.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples.txt b/examples.txt index b4ebf13d..a8850774 100644 --- a/examples.txt +++ b/examples.txt @@ -123,6 +123,8 @@ True >>> from agrirouter.messaging.enums import CapabilityTypeDefinitions >>> from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService >>> from agrirouter import ListEndpointsParameters, ListEndpointsService +>>> from agrirouter.utils.uuid_util import new_uuid + >>> client = HttpClient(client_id="8c942k45-c03d-44y2-affc-206e893m63a50", on_message_callback=lambda x: x) # Enter your client_id and on_message_callback >>> messaging_service = HttpMessagingService(client) From 005c7caff53e5100f803494f2a62982d6b5d42b1 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 26 Oct 2021 01:07:07 +0300 Subject: [PATCH 10/55] remove odd prints --- example_script.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/example_script.py b/example_script.py index 3d2b25aa..2a9badc3 100644 --- a/example_script.py +++ b/example_script.py @@ -83,9 +83,8 @@ onboarding_client = ar.SoftwareOnboarding("QA", public_key=public_key, private_key=private_key) onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, certification_version_id=certification_version_id, gateway_id=GateWays.REST.value, time_zone=time_zone, reg_code=auth_data["credentials"]["regcode"]) onboarding_verifying_response = onboarding_client.verify(onboarding_parameters) -print(f"onboarding_verifying_response: {onboarding_verifying_response}") print(f"onboarding_verifying_response.status_code: {onboarding_verifying_response.status_code}") -print(f"onboarding_verifying_response.text: {onboarding_verifying_response}") +print(f"onboarding_verifying_response.text: {onboarding_verifying_response.text}") onboarding_response = onboarding_client.onboard(onboarding_parameters) print(f"onboarding_response.status_code: {onboarding_response.status_code}") print(f"onboarding_response.text: {onboarding_response.text}") From 2cc77078490e2c3a36aaee164cb7f71352db4813 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 26 Oct 2021 10:47:13 +0300 Subject: [PATCH 11/55] Refactor http client --- agrirouter/messaging/clients/http.py | 24 ++++-------------------- agrirouter/messaging/services/commons.py | 4 ++-- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/agrirouter/messaging/clients/http.py b/agrirouter/messaging/clients/http.py index 08b19664..20e9746a 100644 --- a/agrirouter/messaging/clients/http.py +++ b/agrirouter/messaging/clients/http.py @@ -13,14 +13,6 @@ class HttpClient: headers = {"Content-Type": "application/json"} - def __init__( - self, - on_message_callback: callable, - timeout=20 - ): - self.on_message_callback = on_message_callback - self.timeout = timeout - def make_connection(self, certificate_file_path: str, onboard_response: SoftwareOnboardingResponse): context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) context.load_cert_chain( @@ -58,18 +50,10 @@ def send(self, method: str, onboard_response: SoftwareOnboardingResponse, reques return response - def subscribe(self): - pass - - def unsubscribe(self): - pass - - def _start_loop(self): - pass - - def get_host(self, uri): + @staticmethod + def get_host(uri): return urlparse(uri).netloc - def get_port(self, uri): + @staticmethod + def get_port(uri): return urlparse(uri).port if urlparse(uri).port else None - diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index 11702561..84717719 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -35,8 +35,8 @@ def send(self, parameters): class HttpMessagingService(AbstractMessagingClient): - def __init__(self, on_message_callback): - self.client = HttpClient(on_message_callback=on_message_callback) + def __init__(self): + self.client = HttpClient() def send(self, parameters) -> MessagingResult: request = self.create_message_request(parameters) From a44be3db12630d31f68765469cb303b037cface7 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 26 Oct 2021 10:53:31 +0300 Subject: [PATCH 12/55] Fix examples --- example_script.py | 4 ++-- examples.txt | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/example_script.py b/example_script.py index 2a9badc3..8d08c0ed 100644 --- a/example_script.py +++ b/example_script.py @@ -101,8 +101,8 @@ from agrirouter import ListEndpointsParameters, ListEndpointsService from agrirouter.utils.uuid_util import new_uuid -client = HttpClient(on_message_callback=lambda x: x) # Enter your client_id and on_message_callback -messaging_service = HttpMessagingService(client) + +messaging_service = HttpMessagingService() list_endpoint_parameters = ListEndpointsParameters( technical_message_type=CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value, direction=2, diff --git a/examples.txt b/examples.txt index a8850774..78e169a5 100644 --- a/examples.txt +++ b/examples.txt @@ -126,8 +126,7 @@ True >>> from agrirouter.utils.uuid_util import new_uuid ->>> client = HttpClient(client_id="8c942k45-c03d-44y2-affc-206e893m63a50", on_message_callback=lambda x: x) # Enter your client_id and on_message_callback ->>> messaging_service = HttpMessagingService(client) +>>> messaging_service = HttpMessagingService() >>> list_endpoint_parameters = ListEndpointsParameters( technical_message_type=CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value, direction=2, From a1362f78b131034bd89103e5b2c4c6b4fcd8332b Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 26 Oct 2021 12:39:32 +0300 Subject: [PATCH 13/55] Add subscription to examples --- example_script.py | 21 ++++++++++++++++++--- examples.txt | 23 +++++++++++++++++++---- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/example_script.py b/example_script.py index 8d08c0ed..0995c898 100644 --- a/example_script.py +++ b/example_script.py @@ -94,13 +94,13 @@ # Messaging -from agrirouter.messaging.clients.http import HttpClient -from agrirouter.messaging.clients.mqtt import MqttClient from agrirouter.messaging.enums import CapabilityTypeDefinitions +from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService -from agrirouter import ListEndpointsParameters, ListEndpointsService +from agrirouter import ListEndpointsParameters, ListEndpointsService, SubscriptionService, SubscriptionParameters from agrirouter.utils.uuid_util import new_uuid +# List Endpoints messaging_service = HttpMessagingService() list_endpoint_parameters = ListEndpointsParameters( @@ -113,3 +113,18 @@ ) list_endpoint_service = ListEndpointsService(messaging_service) list_endpoint_service.send(list_endpoint_parameters) + +# Subscription + +messaging_service = HttpMessagingService() +subscription_service = SubscriptionService(messaging_service) + +tmt = CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value +subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) +subscription_parameters = SubscriptionParameters( + subscription_items=[subscription_item], + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, +) +subscription_service.send(subscription_parameters) diff --git a/examples.txt b/examples.txt index 78e169a5..fcb13cd4 100644 --- a/examples.txt +++ b/examples.txt @@ -115,16 +115,16 @@ True >>> ########################## ->>> Messaging +>>> # Messaging ->>> from agrirouter.messaging.clients.http import HttpClient ->>> from agrirouter.messaging.clients.mqtt import MqttClient >>> from agrirouter.messaging.enums import CapabilityTypeDefinitions +>>> from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription >>> from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService ->>> from agrirouter import ListEndpointsParameters, ListEndpointsService +>>> from agrirouter import ListEndpointsParameters, ListEndpointsService, SubscriptionService, SubscriptionParameters >>> from agrirouter.utils.uuid_util import new_uuid +>>> # List Endpoints >>> messaging_service = HttpMessagingService() >>> list_endpoint_parameters = ListEndpointsParameters( @@ -137,3 +137,18 @@ True ) >>> list_endpoint_service = ListEndpointsService(messaging_service) >>> list_endpoint_service.send(list_endpoint_parameters) + +>>> # Subscription + +>>> messaging_service = HttpMessagingService() +>>> subscription_service = SubscriptionService(messaging_service) + +>>> tmt = CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value +>>> subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) +>>> subscription_parameters = SubscriptionParameters( + subscription_items=[subscription_item], + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, +) +>>> subscription_service.send(subscription_parameters) From f0bc39f0b133e8aff00846cbe76b051cf6e4c450 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 26 Oct 2021 12:44:47 +0300 Subject: [PATCH 14/55] Refactor SubscriptionParameters --- agrirouter/messaging/parameters/dto.py | 4 ++-- agrirouter/messaging/parameters/service.py | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/agrirouter/messaging/parameters/dto.py b/agrirouter/messaging/parameters/dto.py index 4646a1b6..17998bf0 100644 --- a/agrirouter/messaging/parameters/dto.py +++ b/agrirouter/messaging/parameters/dto.py @@ -8,7 +8,7 @@ class Parameters: def __init__(self, *, - application_message_seq_no: str, + application_message_seq_no: int, application_message_id: str = None, team_set_context_id: str ): @@ -41,7 +41,7 @@ def validate(self): class MessageParameters(Parameters): def __init__(self, *, - application_message_seq_no: str, + application_message_seq_no: int, application_message_id: str, team_set_context_id: str = None, onboarding_response: BaseOnboardingResonse diff --git a/agrirouter/messaging/parameters/service.py b/agrirouter/messaging/parameters/service.py index f4ee63b6..48ccbd59 100644 --- a/agrirouter/messaging/parameters/service.py +++ b/agrirouter/messaging/parameters/service.py @@ -6,6 +6,7 @@ from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription from agrirouter.generated.messaging.request.payload.feed.feed_requests_pb2 import ValidityPeriod from agrirouter.messaging.parameters.dto import MessageParameters, Parameters +from agrirouter.onboarding.response import BaseOnboardingResonse class MessageHeaderParameters(Parameters): @@ -330,10 +331,20 @@ def set_validity_period(self, validity_period: list) -> None: class SubscriptionParameters(MessageParameters): def __init__(self, + *, + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse, subscription_items: List[Subscription.MessageTypeSubscriptionItem] = None, - **kwargs): + ): self.subscription_items = subscription_items if subscription_items else [] - super(SubscriptionParameters, self).__init__(**kwargs) + super(SubscriptionParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_subscription_items(self) -> List[Subscription.MessageTypeSubscriptionItem]: return self.subscription_items From 0c47fb8e481c65db301a47e1928e8dfd537761d5 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 26 Oct 2021 18:06:34 +0300 Subject: [PATCH 15/55] Fix Http Client --- agrirouter/messaging/clients/http.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/agrirouter/messaging/clients/http.py b/agrirouter/messaging/clients/http.py index 20e9746a..4381d983 100644 --- a/agrirouter/messaging/clients/http.py +++ b/agrirouter/messaging/clients/http.py @@ -11,7 +11,10 @@ class HttpClient: - headers = {"Content-Type": "application/json"} + headers = { + "Content-Type": "application/json", + "Accept": "application/json" + } def make_connection(self, certificate_file_path: str, onboard_response: SoftwareOnboardingResponse): context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) @@ -34,14 +37,14 @@ def send(self, method: str, onboard_response: SoftwareOnboardingResponse, reques if request_body is not None: connection.request( method=method, - url=onboard_response.get_connection_criteria().get_measures(), + url=self.get_path(onboard_response.get_connection_criteria().get_measures()), headers=self.headers, body=json.dumps(request_body.json_serialize()) ) else: connection.request( method=method, - url=onboard_response.get_connection_criteria().get_measures(), + url=self.get_path(onboard_response.get_connection_criteria().get_measures()), headers=self.headers, ) response = connection.getresponse() @@ -57,3 +60,7 @@ def get_host(uri): @staticmethod def get_port(uri): return urlparse(uri).port if urlparse(uri).port else None + + @staticmethod + def get_path(uri): + return urlparse(uri).path From 738db91ab440f940cd7fda79a8f658494e14f5c9 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Wed, 27 Oct 2021 18:47:53 +0300 Subject: [PATCH 16/55] Fix revoking test --- tests/test_revoking/test_parameters.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_revoking/test_parameters.py b/tests/test_revoking/test_parameters.py index 6cb97298..2c1c737e 100644 --- a/tests/test_revoking/test_parameters.py +++ b/tests/test_revoking/test_parameters.py @@ -8,15 +8,15 @@ class TestRevokingParameter: content_type = "json" account_id = "111" endpoint_ids = "endpoint_1" - utc_timestamp = "+03:00" - timestamp = "01-01-2021" + time_zone = "+03:00" + utc_timestamp = "01-01-2021" test_object = RevokingParameter( application_id=application_id, content_type=content_type, account_id=account_id, endpoint_ids=endpoint_ids, utc_timestamp=utc_timestamp, - timestamp=timestamp, + time_zone=time_zone ) def test_get_header_params(self): @@ -27,4 +27,4 @@ def test_get_body_params(self): assert self.test_object.get_body_params()["account_id"] == self.account_id assert self.test_object.get_body_params()["endpoint_ids"] == self.endpoint_ids assert self.test_object.get_body_params()["utc_timestamp"] == self.utc_timestamp - assert self.test_object.get_body_params()["timestamp"] == self.timestamp + assert self.test_object.get_body_params()["time_zone"] == self.time_zone From 6b389f1cac79f3c258b18092ce691ea3b465766c Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Wed, 27 Oct 2021 20:31:53 +0300 Subject: [PATCH 17/55] Fix outbox --- agrirouter/messaging/clients/http.py | 30 +++++++++--- agrirouter/messaging/decode.py | 25 +++++++--- agrirouter/messaging/exceptions.py | 8 ++++ agrirouter/messaging/messages.py | 8 ++-- agrirouter/messaging/result.py | 8 +++- agrirouter/messaging/services/commons.py | 8 +--- agrirouter/messaging/services/http/outbox.py | 23 ++++++---- agrirouter/utils/type_url.py | 48 ++++++++++---------- 8 files changed, 101 insertions(+), 57 deletions(-) diff --git a/agrirouter/messaging/clients/http.py b/agrirouter/messaging/clients/http.py index 4381d983..429be1e7 100644 --- a/agrirouter/messaging/clients/http.py +++ b/agrirouter/messaging/clients/http.py @@ -16,7 +16,7 @@ class HttpClient: "Accept": "application/json" } - def make_connection(self, certificate_file_path: str, onboard_response: SoftwareOnboardingResponse): + def make_connection(self, certificate_file_path: str, uri: str, onboard_response: SoftwareOnboardingResponse): context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) context.load_cert_chain( certfile=certificate_file_path, @@ -24,27 +24,43 @@ def make_connection(self, certificate_file_path: str, onboard_response: Software password=onboard_response.get_authentication().get_secret(), ) connection = http.client.HTTPSConnection( - host=self.get_host(onboard_response.connection_criteria.get_measures()), - port=self.get_port(onboard_response.connection_criteria.get_measures()), + host=self.get_host(uri), + port=self.get_port(uri), context=context ) return connection - def send(self, method: str, onboard_response: SoftwareOnboardingResponse, request_body=None): + def send_measure(self, onboard_response: SoftwareOnboardingResponse, request_body=None): + return self.send( + method="POST", + uri=onboard_response.get_connection_criteria().get_measures(), + onboard_response=onboard_response, + request_body=request_body + ) + + def send_command(self, onboard_response: SoftwareOnboardingResponse, request_body=None): + return self.send( + method="GET", + uri=onboard_response.get_connection_criteria().get_commands(), + onboard_response=onboard_response, + request_body=request_body + ) + + def send(self, method: str, uri: str, onboard_response: SoftwareOnboardingResponse, request_body=None): certificate_file_path = create_certificate_file_from_pen(onboard_response) try: - connection = self.make_connection(certificate_file_path, onboard_response) + connection = self.make_connection(certificate_file_path, uri, onboard_response) if request_body is not None: connection.request( method=method, - url=self.get_path(onboard_response.get_connection_criteria().get_measures()), + url=self.get_path(uri), headers=self.headers, body=json.dumps(request_body.json_serialize()) ) else: connection.request( method=method, - url=self.get_path(onboard_response.get_connection_criteria().get_measures()), + url=self.get_path(uri), headers=self.headers, ) response = connection.getresponse() diff --git a/agrirouter/messaging/decode.py b/agrirouter/messaging/decode.py index dd9672b0..02387153 100644 --- a/agrirouter/messaging/decode.py +++ b/agrirouter/messaging/decode.py @@ -9,6 +9,7 @@ from agrirouter.generated.messaging.response.payload.feed.feed_response_pb2 import HeaderQueryResponse, \ MessageQueryResponse from agrirouter.generated.messaging.response.response_pb2 import ResponseEnvelope, ResponsePayloadWrapper +from agrirouter.messaging.exceptions import DecodeMessageException from agrirouter.messaging.messages import DecodedMessage from agrirouter.utils.type_url import TypeUrl @@ -31,8 +32,10 @@ def decode_response(message: bytes) -> DecodedMessage: input_stream = base64.b64decode(message) response_envelope_buffer, response_payload_buffer = read_properties_buffers_from_input_stream(input_stream) - envelope = ResponseEnvelope().MergeFromString(response_envelope_buffer) - payload = ResponsePayloadWrapper().MergeFromString(response_payload_buffer) + envelope = ResponseEnvelope() + envelope.ParseFromString(response_envelope_buffer) + payload = ResponsePayloadWrapper() + payload.ParseFromString(response_payload_buffer) message = DecodedMessage(envelope, payload) @@ -41,10 +44,20 @@ def decode_response(message: bytes) -> DecodedMessage: def decode_details(details: Any): if details.type_url == TypeUrl.get_type_url(Messages): - return Messages().MergeFromString(details.value) + messages = Messages() + messages.MergeFromString(details.value) + return messages elif details.type_url == TypeUrl.get_type_url(ListEndpointsResponse): - return ListEndpointsResponse().MergeFromString(details.value) + list_endpoints_response = ListEndpointsResponse() + list_endpoints_response.MergeFromString(details.value) + return list_endpoints_response elif details.type_url == TypeUrl.get_type_url(HeaderQueryResponse): - return HeaderQueryResponse().MergeFromString(details.value) + header_query_response = HeaderQueryResponse() + header_query_response.MergeFromString(details.value) + return header_query_response elif details.type_url == TypeUrl.get_type_url(MessageQueryResponse): - return MessageQueryResponse().MergeFromString(details.value) + message_query_response = MessageQueryResponse() + message_query_response.MergeFromString(details.value) + return message_query_response + else: + raise DecodeMessageException(f"Could not handle type {details.type_url} while decoding details.") diff --git a/agrirouter/messaging/exceptions.py b/agrirouter/messaging/exceptions.py index 3df29d4c..2bc8b9a7 100644 --- a/agrirouter/messaging/exceptions.py +++ b/agrirouter/messaging/exceptions.py @@ -7,3 +7,11 @@ class TypeUrlNotFoundError(AgriRouuterBaseException): class WrongFieldError(AgriRouuterBaseException): _message = "Unknown field" + + +class DecodeMessageException(AgriRouuterBaseException): + _message = "Can't decode message" + + +class OutboxException(AgriRouuterBaseException): + _message = "Can't fetch outbox message" diff --git a/agrirouter/messaging/messages.py b/agrirouter/messaging/messages.py index 2d5d1671..1655d2a7 100644 --- a/agrirouter/messaging/messages.py +++ b/agrirouter/messaging/messages.py @@ -82,15 +82,17 @@ def __init__(self, self.sensor_alternate_id = sensor_alternate_id self.command = command - def json_deserialize(self, data: Union[list, str]): - data = data if type(data) == list else json.loads(data) + def json_deserialize(self, data: Union[dict, str]): + data = data if type(data) == dict else json.loads(data) for key, value in data.keys(): if key == self.CAPABILITY_ALTERNATE_ID: self.capability_alternate_id = value elif key == self.SENSOR_ALTERNATE_ID: self.sensor_alternate_id = value elif key == self.COMMAND: - self.command = Command.json_deserialize(value) + command = Command() + command.json_deserialize(value) + self.command = command else: raise WrongFieldError(f"Unknown field `{key}` for {self.__class__}") diff --git a/agrirouter/messaging/result.py b/agrirouter/messaging/result.py index ba2df495..165469ac 100644 --- a/agrirouter/messaging/result.py +++ b/agrirouter/messaging/result.py @@ -26,7 +26,13 @@ def __init__(self, def json_deserialize(self, data: Union[list, str]): messages = data if type(data) == list else json.loads(data) - self.set_messages([OutboxMessage.json_deserialize(message) for message in messages]) + outbox_messages = [] + for message in messages: + outbox_message = OutboxMessage() + outbox_message.json_deserialize(message) + outbox_messages.append(outbox_message) + + self.set_messages(outbox_messages) def get_status_code(self) -> int: return self.status_code diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index 84717719..10518340 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -40,12 +40,8 @@ def __init__(self): def send(self, parameters) -> MessagingResult: request = self.create_message_request(parameters) - response = self.client.send( - "POST", - parameters.get_onboarding_response(), - request - ) - if response.status in [400, 401, 403, 404, 500]: + response = self.client.send_measure(parameters.get_onboarding_response(), request) + if response.status != 200: raise BadMessagingResult(f"Messaging Request failed with status code {response.status}") result = MessagingResult([parameters.get_application_message_id()]) return result diff --git a/agrirouter/messaging/services/http/outbox.py b/agrirouter/messaging/services/http/outbox.py index 72328072..13db25cf 100644 --- a/agrirouter/messaging/services/http/outbox.py +++ b/agrirouter/messaging/services/http/outbox.py @@ -1,8 +1,11 @@ +import json import os import requests from agrirouter.messaging.clients.http import HttpClient +from agrirouter.messaging.exceptions import OutboxException +from agrirouter.messaging.messages import OutboxMessage from agrirouter.messaging.result import OutboxResponse from agrirouter.messaging.certification import create_certificate_file_from_pen @@ -10,18 +13,18 @@ class OutboxService: - def __init__(self, on_message_callback, timeout): - self.client = HttpClient(on_message_callback=on_message_callback, timeout=timeout) + def __init__(self): + self.client = HttpClient() def fetch(self, onboarding_response) -> OutboxResponse: - response = self.client.send( - "GET", - onboarding_response, - None - ) - - outbox_response = OutboxResponse(status_code=response.status_code) - outbox_response.json_deserialize(response.json()["contents"]) + response = self.client.send_command(onboarding_response, None) + + if response.status == 200: + outbox_response = OutboxResponse(status_code=response.status) + response_body = response.read() + outbox_response.json_deserialize(response_body) + else: + raise OutboxException(f"Could not fetch messages from outbox. Status code was {response.status}") return outbox_response diff --git a/agrirouter/utils/type_url.py b/agrirouter/utils/type_url.py index 71f4c6df..7d256bf3 100644 --- a/agrirouter/utils/type_url.py +++ b/agrirouter/utils/type_url.py @@ -17,29 +17,29 @@ class TypeUrl: @classmethod def get_type_url(cls, class_): - if class_.__name__ == Messages.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == ListEndpointsResponse.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == HeaderQueryResponse.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == MessageQueryResponse.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == MessageDelete.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == MessageConfirm.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == OnboardingResponse.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == OnboardingRequest.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == CapabilitySpecification.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == Subscription.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == MessageQuery.__name__: - return cls.prefix + class_.__name__ - elif class_.__name__ == ListEndpointsQuery.__name__: - return cls.prefix + class_.__name__ + if class_ == Messages: + return cls.prefix + Messages.DESCRIPTOR.full_name + elif class_ == ListEndpointsResponse: + return cls.prefix + ListEndpointsResponse.DESCRIPTOR.full_name + elif class_ == HeaderQueryResponse: + return cls.prefix + HeaderQueryResponse.DESCRIPTOR.full_name + elif class_ == MessageQueryResponse: + return cls.prefix + MessageQueryResponse.DESCRIPTOR.full_name + elif class_ == MessageDelete: + return cls.prefix + MessageDelete.DESCRIPTOR.full_name + elif class_ == MessageConfirm: + return cls.prefix + MessageConfirm.DESCRIPTOR.full_name + elif class_ == OnboardingResponse: + return cls.prefix + OnboardingResponse.DESCRIPTOR.full_name + elif class_ == OnboardingRequest: + return cls.prefix + OnboardingRequest.DESCRIPTOR.full_name + elif class_ == CapabilitySpecification: + return cls.prefix + CapabilitySpecification.DESCRIPTOR.full_name + elif class_ == Subscription: + return cls.prefix + Subscription.DESCRIPTOR.full_name + elif class_ == MessageQuery: + return cls.prefix + MessageQuery.DESCRIPTOR.full_name + elif class_ == ListEndpointsQuery: + return cls.prefix + ListEndpointsQuery..DESCRIPTOR.full_name else: raise TypeUrlNotFoundError(f"The {class_} type url not found") From 4c9e6d156100c98c6f8053542bf2c9b7af1e7cdd Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Wed, 27 Oct 2021 20:32:26 +0300 Subject: [PATCH 18/55] Fix TypeUrl --- agrirouter/utils/type_url.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agrirouter/utils/type_url.py b/agrirouter/utils/type_url.py index 7d256bf3..2457b9da 100644 --- a/agrirouter/utils/type_url.py +++ b/agrirouter/utils/type_url.py @@ -40,6 +40,6 @@ def get_type_url(cls, class_): elif class_ == MessageQuery: return cls.prefix + MessageQuery.DESCRIPTOR.full_name elif class_ == ListEndpointsQuery: - return cls.prefix + ListEndpointsQuery..DESCRIPTOR.full_name + return cls.prefix + ListEndpointsQuery.DESCRIPTOR.full_name else: raise TypeUrlNotFoundError(f"The {class_} type url not found") From 0a3afaad7fde4809bc878a9ee27e4368a1251d54 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Wed, 27 Oct 2021 22:00:49 +0300 Subject: [PATCH 19/55] Fix OutboxMessage --- agrirouter/messaging/messages.py | 6 +++--- agrirouter/messaging/result.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/agrirouter/messaging/messages.py b/agrirouter/messaging/messages.py index 1655d2a7..dab8e2e9 100644 --- a/agrirouter/messaging/messages.py +++ b/agrirouter/messaging/messages.py @@ -53,8 +53,8 @@ def __init__(self, message: str = None): self.message = message def json_deserialize(self, data: Union[Dict[str, str], str]): - messages = data if type(data) == list else json.loads(data) - for key, value in messages.keys(): + messages = data if type(data) == dict else json.loads(data) + for key, value in messages.items(): if key == self.MESSAGE: self.message = value else: @@ -84,7 +84,7 @@ def __init__(self, def json_deserialize(self, data: Union[dict, str]): data = data if type(data) == dict else json.loads(data) - for key, value in data.keys(): + for (key, value) in data.items(): if key == self.CAPABILITY_ALTERNATE_ID: self.capability_alternate_id = value elif key == self.SENSOR_ALTERNATE_ID: diff --git a/agrirouter/messaging/result.py b/agrirouter/messaging/result.py index 165469ac..d08c7f4b 100644 --- a/agrirouter/messaging/result.py +++ b/agrirouter/messaging/result.py @@ -26,13 +26,13 @@ def __init__(self, def json_deserialize(self, data: Union[list, str]): messages = data if type(data) == list else json.loads(data) - outbox_messages = [] + outbox_message_list = [] for message in messages: outbox_message = OutboxMessage() outbox_message.json_deserialize(message) - outbox_messages.append(outbox_message) + outbox_message_list.append(outbox_message) - self.set_messages(outbox_messages) + self.set_messages(outbox_message_list) def get_status_code(self) -> int: return self.status_code From 2c9c247b534d57c12d7dd79229e066ab77d1fa80 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Wed, 27 Oct 2021 22:01:22 +0300 Subject: [PATCH 20/55] Fix OutboxMessage --- agrirouter/messaging/messages.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/agrirouter/messaging/messages.py b/agrirouter/messaging/messages.py index dab8e2e9..da475631 100644 --- a/agrirouter/messaging/messages.py +++ b/agrirouter/messaging/messages.py @@ -113,6 +113,3 @@ def get_command(self) -> Command: def set_command(self, command: Command) -> None: self.command = command - - def json_deserialize(self): - pass From 565c69aee9ef7cd73bbd9f8c3771f0afb4b74c9f Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 00:48:57 +0300 Subject: [PATCH 21/55] Implement SubscriptionItemBuilder, CapabilityBuilder --- agrirouter/messaging/builders.py | 201 +++++++++++++++++++++++++++++++ agrirouter/messaging/enums.py | 2 +- 2 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 agrirouter/messaging/builders.py diff --git a/agrirouter/messaging/builders.py b/agrirouter/messaging/builders.py new file mode 100644 index 00000000..94e56ce8 --- /dev/null +++ b/agrirouter/messaging/builders.py @@ -0,0 +1,201 @@ +from typing import List + +from agrirouter.generated.messaging.request.payload.endpoint.capabilities_pb2 import CapabilitySpecification +from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription +from agrirouter.messaging.enums import CapabilityType + + +class SubscriptionItemBuilder: + + def __init__(self): + self._subscription_items = [] + + def build(self): + return self._subscription_items + + def clear(self): + self._subscription_items = [] + + def with_task_data(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_device_description(self, ddis: List[int]=None, position: bool=None): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.ISO_11783_DEVICE_DESCRIPTION_PROTOBUF.value, + ddis=ddis, + position=position + ) + self._subscription_items.append(subscription_item) + return self + + def with_time_log(self, ddis: List[int] = None, position: bool = None): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.ISO_11783_TIMELOG_PROTOBUF.value, + ddis=ddis, + position=position + ) + self._subscription_items.append(subscription_item) + return self + + def with_bmp(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.IMG_BMP.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_jpg(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.IMG_JPEG.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_png(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.IMG_PNG.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_shape(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.SHP_SHAPE_ZIP.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_pdf(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.DOC_PDF.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_avi(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.VID_AVI.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_mp4(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.VID_MP4.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_wmv(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.VID_WMV.value + ) + self._subscription_items.append(subscription_item) + return self + + def with_gps_info(self): + subscription_item = Subscription.MessageTypeSubscriptionItem( + technical_message_type=CapabilityType.GPS_INFO.value + ) + self._subscription_items.append(subscription_item) + return self + + +class CapabilityBuilder: + + def __init__(self): + self._capabilities = [] + + def build(self) -> list: + return self._capabilities + + def clear(self): + self._capabilities = [] + + def with_task_data(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.ISO_11783_TASKDATA_ZIP.value + self._capabilities.append(capability) + return self + + def with_device_description(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.ISO_11783_DEVICE_DESCRIPTION_PROTOBUF.value + self._capabilities.append(capability) + return self + + def with_time_log(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.ISO_11783_TIMELOG_PROTOBUF.value + self._capabilities.append(capability) + return self + + def with_bmp(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.IMG_BMP.value + self._capabilities.append(capability) + return self + + def with_jpg(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.IMG_JPEG.value + self._capabilities.append(capability) + return self + + def with_png(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.IMG_PNG.value + self._capabilities.append(capability) + return self + + def with_shape(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.SHP_SHAPE_ZIP.value + self._capabilities.append(capability) + return self + + def with_pdf(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.DOC_PDF.value + self._capabilities.append(capability) + return self + + def with_avi(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.VID_AVI.value + self._capabilities.append(capability) + return self + + def with_mp4(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.VID_MP4.value + self._capabilities.append(capability) + return self + + def with_wmv(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.VID_WMV.value + self._capabilities.append(capability) + return self + + def with_gps_info(self, direction: int): + capability = CapabilitySpecification.Capability() + capability.direction = direction + capability.technical_message_type = CapabilityType.GPS_INFO.value + self._capabilities.append(capability) + return self diff --git a/agrirouter/messaging/enums.py b/agrirouter/messaging/enums.py index 0b9354cf..53265ec5 100644 --- a/agrirouter/messaging/enums.py +++ b/agrirouter/messaging/enums.py @@ -15,7 +15,7 @@ class TechnicalMessageType(BaseEnum): CLOUD_OFFBOARD_ENDPOINTS = "dke:cloud_offboard_endpoints" -class CapabilityTypeDefinitions(BaseEnum): +class CapabilityType(BaseEnum): ISO_11783_TASKDATA_ZIP = "iso:11783:-10:taskdata:zip" ISO_11783_DEVICE_DESCRIPTION_PROTOBUF = "iso:11783:-10:device_description:protobuf" ISO_11783_TIMELOG_PROTOBUF = "iso:11783:-10:time_log:protobuf" From 26d8971c1baa26bfcf116abe042b4a19d589697d Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 03:10:06 +0300 Subject: [PATCH 22/55] Refactor encode_header method --- agrirouter/messaging/encode.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/agrirouter/messaging/encode.py b/agrirouter/messaging/encode.py index 5ad1361c..f1f40513 100644 --- a/agrirouter/messaging/encode.py +++ b/agrirouter/messaging/encode.py @@ -34,7 +34,10 @@ def encode_header(header_parameters: MessageHeaderParameters) -> RequestEnvelope request_envelope.application_message_seq_no = header_parameters.get_application_message_seq_no() request_envelope.technical_message_type = header_parameters.get_technical_message_type() request_envelope.mode = header_parameters.get_mode() + if header_parameters.get_team_set_context_id() is not None: + request_envelope.team_set_context_id = header_parameters.get_team_set_context_id() request_envelope.timestamp.FromDatetime(now_as_utc_timestamp()) + return request_envelope From f01f8417e786b7b8cfaa8b595ad182821b6ac0aa Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 03:10:54 +0300 Subject: [PATCH 23/55] Fix CapabilityService --- agrirouter/messaging/services/messaging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agrirouter/messaging/services/messaging.py b/agrirouter/messaging/services/messaging.py index 5a6cdb80..685934ba 100644 --- a/agrirouter/messaging/services/messaging.py +++ b/agrirouter/messaging/services/messaging.py @@ -52,7 +52,7 @@ def encode(parameters: CapabilityParameters) -> EncodedMessage: enable_push_notifications=parameters.get_enable_push_notification() ) if parameters.get_capability_parameters(): - capability_specification.capabilities = parameters.get_capability_parameters() + capability_specification.capabilities.extend(parameters.get_capability_parameters()) message_payload_parameters = MessagePayloadParameters( type_url=TypeUrl.get_type_url(CapabilitySpecification), From ddacb852d60ba9d95a3773829ca11fc0f26a264f Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 03:11:29 +0300 Subject: [PATCH 24/55] Refactor CapabilityParameters --- agrirouter/messaging/parameters/service.py | 52 ++++++++++++++++------ 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/agrirouter/messaging/parameters/service.py b/agrirouter/messaging/parameters/service.py index 48ccbd59..e26167e6 100644 --- a/agrirouter/messaging/parameters/service.py +++ b/agrirouter/messaging/parameters/service.py @@ -3,6 +3,7 @@ from typing import List from agrirouter.generated.commons.chunk_pb2 import ChunkComponent +from agrirouter.generated.messaging.request.payload.endpoint.capabilities_pb2 import CapabilitySpecification from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription from agrirouter.generated.messaging.request.payload.feed.feed_requests_pb2 import ValidityPeriod from agrirouter.messaging.parameters.dto import MessageParameters, Parameters @@ -16,7 +17,7 @@ def __init__(self, technical_message_type: str = None, mode: str = None, team_set_context_id: str = None, - application_message_seq_no: str = None, + application_message_seq_no: int = None, recipients: list = None, chunk_component: ChunkComponent = None, application_message_id: str = None, @@ -66,13 +67,20 @@ def get_value(self) -> str: class CloudOnboardParameters(MessageParameters): def __init__(self, - # List[EndpointRegistrationDetails] - must be defined in generated by by proto schemes, - # but they are not + *, onboarding_requests: list = None, - **kwargs + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse ): self.onboarding_requests = onboarding_requests if onboarding_requests else [] - super(CloudOnboardParameters, self).__init__(**kwargs) + super(CloudOnboardParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_onboarding_requests(self) -> list: return self.onboarding_requests @@ -90,11 +98,20 @@ def extend_onboarding_requests(self, onboarding_requests: list) -> None: class CloudOffboardParameters(MessageParameters): def __init__(self, + *, endpoints: List[str] = None, - **kwargs + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse ): self.endpoints = endpoints if endpoints else [] - super(CloudOffboardParameters, self).__init__(**kwargs) + super(CloudOffboardParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_endpoints(self) -> List[str]: return self.endpoints @@ -112,17 +129,26 @@ def extend_endpoints(self, endpoints: List[str]) -> None: class CapabilityParameters(MessageParameters): def __init__(self, - application_id, - certification_version_id, - enable_push_notification, - capability_parameters: list = None, - **kwargs + *, + application_id: str, + certification_version_id: str, + enable_push_notification: int = CapabilitySpecification.PushNotification.Value("DISABLED"), + capability_parameters: List[CapabilitySpecification.Capability] = None, + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse ): self.application_id = application_id self.certification_version_id = certification_version_id self.enable_push_notification = enable_push_notification self.capability_parameters = capability_parameters if capability_parameters else [] - super(CapabilityParameters, self).__init__(**kwargs) + super(CapabilityParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_application_id(self): return self.application_id From 6f62a2bc891a3121325501d0e1162021cea43ed9 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 10:41:14 +0300 Subject: [PATCH 25/55] Refactor Parameters --- agrirouter/messaging/parameters/service.py | 72 +++++++++++++++++++--- 1 file changed, 62 insertions(+), 10 deletions(-) diff --git a/agrirouter/messaging/parameters/service.py b/agrirouter/messaging/parameters/service.py index e26167e6..631188ae 100644 --- a/agrirouter/messaging/parameters/service.py +++ b/agrirouter/messaging/parameters/service.py @@ -182,9 +182,21 @@ def extend_capability_parameters(self, capability_parameters: list): class FeedConfirmParameters(MessageParameters): - def __init__(self, message_ids: list = None, **kwargs): + def __init__(self, + *, + message_ids: list = None, + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse + ): self.message_ids = message_ids if message_ids else [] - super(FeedConfirmParameters, self).__init__(**kwargs) + super(FeedConfirmParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_message_ids(self): return deepcopy(self.message_ids) @@ -201,14 +213,24 @@ def extend_message_ids(self, message_ids): class FeedDeleteParameters(MessageParameters): def __init__(self, + *, message_ids: list = None, receivers: list = None, validity_period: ValidityPeriod = None, - **kwargs): + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse, + ): self.message_ids = message_ids if message_ids else [] self.receivers = receivers if receivers else [] self.validity_period = validity_period - super(FeedDeleteParameters, self).__init__(**kwargs) + super(FeedDeleteParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_message_ids(self): return deepcopy(self.message_ids) @@ -243,14 +265,24 @@ def set_validity_period(self, validity_period: ValidityPeriod): class ListEndpointsParameters(MessageParameters): def __init__(self, + *, technical_message_type: str = None, direction: int = None, filtered: bool = False, - **kwargs): + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse, + ): self.technical_message_type = technical_message_type self.direction = direction self.filtered = filtered - super(ListEndpointsParameters, self).__init__(**kwargs) + super(ListEndpointsParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_technical_message_type(self) -> str: return self.technical_message_type @@ -273,14 +305,24 @@ def set_filtered(self, filtered: bool): class QueryMessageParameters(MessageParameters): def __init__(self, + *, senders: list = None, message_ids: list = None, validity_period: ValidityPeriod = None, - **kwargs): + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse, + ): self.senders = senders self.message_ids = message_ids self.validity_period = validity_period - super(QueryMessageParameters, self).__init__(**kwargs) + super(QueryMessageParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_senders(self) -> list: return self.senders @@ -315,14 +357,24 @@ def set_validity_period(self, validity_period: list) -> None: class QueryHeaderParameters(MessageParameters): def __init__(self, + *, senders: list = None, message_ids: list = None, validity_period: ValidityPeriod = None, - **kwargs): + application_message_seq_no: int, + application_message_id: str, + team_set_context_id: str = None, + onboarding_response: BaseOnboardingResonse, + ): self.senders = senders self.message_ids = message_ids self.validity_period = validity_period - super(QueryHeaderParameters, self).__init__(**kwargs) + super(QueryHeaderParameters, self).__init__( + application_message_seq_no=application_message_seq_no, + application_message_id=application_message_id, + team_set_context_id=team_set_context_id, + onboarding_response=onboarding_response + ) def get_senders(self) -> list: return self.senders From c711db362bf41f984e1a0974f6543e6813fe3b45 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 15:47:01 +0300 Subject: [PATCH 26/55] Remove redundant methods in HttpMessagingService --- agrirouter/messaging/services/commons.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index 10518340..425aa976 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -46,12 +46,6 @@ def send(self, parameters) -> MessagingResult: result = MessagingResult([parameters.get_application_message_id()]) return result - def subscribe(self): - pass - - def unsubscribe(self): - pass - class MqttMessagingService(AbstractMessagingClient): From ba81a1c5c09a8d588f00180ee11a50a6a0c57d6f Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 16:37:09 +0300 Subject: [PATCH 27/55] Move AuthorizationResultUrl, AuthorizationToken, AuthorizationResult to auth/dto.py --- agrirouter/auth/dto.py | 110 +++++++++++++++++++++++++++++++++++ agrirouter/onboarding/dto.py | 106 --------------------------------- 2 files changed, 110 insertions(+), 106 deletions(-) create mode 100644 agrirouter/auth/dto.py diff --git a/agrirouter/auth/dto.py b/agrirouter/auth/dto.py new file mode 100644 index 00000000..fec51c0a --- /dev/null +++ b/agrirouter/auth/dto.py @@ -0,0 +1,110 @@ +import json +from typing import Union + +from agrirouter.messaging.exceptions import WrongFieldError + + +class AuthorizationResultUrl: + def __init__(self, + *, + state: str = None, + signature: str = None, + token: str = None, + error: str = None + ): + self.state = state + self.signature = signature + self.token = token + self.error = error + + def get_state(self) -> str: + return self.state + + def set_state(self, state: str) -> None: + self.state = state + + def get_signature(self) -> str: + return self.signature + + def set_signature(self, signature: str) -> None: + self.signature = signature + + def get_token(self) -> str: + return self.token + + def set_token(self, token: str) -> None: + self.token = token + + def get_error(self) -> str: + return self.error + + def set_error(self, error: str) -> None: + self.error = error + + +class AuthorizationToken: + ACCOUNT = 'account' + REGISTRATION_CODE = 'regcode' + EXPIRES = 'expires' + + def __init__(self, + *, + account: str = None, + regcode: str = None, + expires: str = None + ): + self.account = account + self.regcode = regcode + self.expires = expires + + def json_deserialize(self, data: Union[str, dict]) -> None: + data = data if type(data) == dict else json.loads(data) + for key, value in data.items(): + if key == self.ACCOUNT: + self.account = value + elif key == self.REGISTRATION_CODE: + self.regcode = value + elif key == self.EXPIRES: + self.expires = value + else: + raise WrongFieldError(f"Unknown field {key} for AuthorizationToken class") + + def get_account(self) -> str: + return self.account + + def set_account(self, account: str) -> None: + self.account = account + + def get_regcode(self) -> str: + return self.regcode + + def set_regcode(self, regcode: str) -> None: + self.regcode = regcode + + def get_expires(self) -> str: + return self.expires + + def set_expires(self, expires: str) -> None: + self.expires = expires + + +class AuthorizationResult: + def __init__(self, + *, + authorization_url: str = None, + state: str = None, + ): + self.authorization_url = authorization_url + self.state = state + + def get_authorization_url(self) -> str: + return self.authorization_url + + def set_authorization_url(self, authorization_url: str) -> None: + self.authorization_url = authorization_url + + def get_state(self) -> str: + return self.state + + def set_state(self, state: str) -> None: + self.state = state \ No newline at end of file diff --git a/agrirouter/onboarding/dto.py b/agrirouter/onboarding/dto.py index 233c907e..c4cbc314 100644 --- a/agrirouter/onboarding/dto.py +++ b/agrirouter/onboarding/dto.py @@ -145,112 +145,6 @@ def set_certificate(self, certificate: str) -> None: self.certificate = certificate -class AuthorizationResultUrl: - def __init__(self, - *, - state: str = None, - signature: str = None, - token: str = None, - error: str = None - ): - self.state = state - self.signature = signature - self.token = token - self.error = error - - def get_state(self) -> str: - return self.state - - def set_state(self, state: str) -> None: - self.state = state - - def get_signature(self) -> str: - return self.signature - - def set_signature(self, signature: str) -> None: - self.signature = signature - - def get_token(self) -> str: - return self.token - - def set_token(self, token: str) -> None: - self.token = token - - def get_error(self) -> str: - return self.error - - def set_error(self, error: str) -> None: - self.error = error - - -class AuthorizationToken: - ACCOUNT = 'account' - REGISTRATION_CODE = 'regcode' - EXPIRES = 'expires' - - def __init__(self, - *, - account: str = None, - regcode: str = None, - expires: str = None - ): - self.account = account - self.regcode = regcode - self.expires = expires - - def json_deserialize(self, data: Union[str, dict]) -> None: - data = data if type(data) == dict else json.loads(data) - for key, value in data.items(): - if key == self.ACCOUNT: - self.account = value - elif key == self.REGISTRATION_CODE: - self.regcode = value - elif key == self.EXPIRES: - self.expires = value - else: - raise WrongFieldError(f"Unknown field {key} for AuthorizationToken class") - - def get_account(self) -> str: - return self.account - - def set_account(self, account: str) -> None: - self.account = account - - def get_regcode(self) -> str: - return self.regcode - - def set_regcode(self, regcode: str) -> None: - self.regcode = regcode - - def get_expires(self) -> str: - return self.expires - - def set_expires(self, expires: str) -> None: - self.expires = expires - - -class AuthorizationResult: - def __init__(self, - *, - authorization_url: str = None, - state: str = None, - ): - self.authorization_url = authorization_url - self.state = state - - def get_authorization_url(self) -> str: - return self.authorization_url - - def set_authorization_url(self, authorization_url: str) -> None: - self.authorization_url = authorization_url - - def get_state(self) -> str: - return self.state - - def set_state(self, state: str) -> None: - self.state = state - - class ErrorResponse: def __init__(self, *, From 8c29477294d76cbdd6a2e01328406b1700eb74fc Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 16:37:45 +0300 Subject: [PATCH 28/55] Create helper method let_agrirouter_process_the_message --- tests/sleeper.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 tests/sleeper.py diff --git a/tests/sleeper.py b/tests/sleeper.py new file mode 100644 index 00000000..73eb1d28 --- /dev/null +++ b/tests/sleeper.py @@ -0,0 +1,5 @@ +import time + + +def let_agrirouter_process_the_message(seconds: int = 3): + time.sleep(3) From 4c4927bccb20d5d10a53c9786d396cc9f6bd35a3 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 16:39:09 +0300 Subject: [PATCH 29/55] Remove redundant CU-classes in onboarding --- agrirouter/onboarding/headers.py | 25 +------------ agrirouter/onboarding/onboarding.py | 49 ++++--------------------- agrirouter/onboarding/parameters.py | 52 +-------------------------- agrirouter/onboarding/request.py | 22 +++--------- agrirouter/onboarding/request_body.py | 39 +------------------- agrirouter/onboarding/response.py | 8 ----- 6 files changed, 14 insertions(+), 181 deletions(-) diff --git a/agrirouter/onboarding/headers.py b/agrirouter/onboarding/headers.py index a19fe008..fb598310 100644 --- a/agrirouter/onboarding/headers.py +++ b/agrirouter/onboarding/headers.py @@ -1,28 +1,9 @@ import base64 -from abc import ABC, abstractmethod from agrirouter.constants.media_types import ContentTypes -class BaseOnboardingHeader(ABC): - @abstractmethod - def __init__(self, *args, **kwargs): - self._set_params(*args, **kwargs) - - @abstractmethod - def get_header(self) -> dict: - ... - - @abstractmethod - def _set_params(self, *args, **kwargs): - ... - - @abstractmethod - def sign(self, *args, **kwargs): - ... - - -class SoftwareOnboardingHeader(BaseOnboardingHeader): +class SoftwareOnboardingHeader: def __init__(self, reg_code, application_id, @@ -46,7 +27,3 @@ def _set_params(self, reg_code: str, application_id: str, signature: str, conten header["X-Agrirouter-Signature"] = signature if signature else "" self.params = header - - -class CUOnboardingHeader(BaseOnboardingHeader): - pass diff --git a/agrirouter/onboarding/onboarding.py b/agrirouter/onboarding/onboarding.py index 306dba00..74ded694 100644 --- a/agrirouter/onboarding/onboarding.py +++ b/agrirouter/onboarding/onboarding.py @@ -4,12 +4,11 @@ from agrirouter.environments.environmental_services import EnvironmentalService from agrirouter.onboarding.exceptions import RequestNotSigned -from agrirouter.onboarding.headers import SoftwareOnboardingHeader, CUOnboardingHeader -from agrirouter.onboarding.parameters import SoftwareOnboardingParameter, BaseOnboardingParameter, CUOnboardingParameter -from agrirouter.onboarding.request import SoftwareOnboardingRequest, BaseOnboardingRequest, CUOnboardingRequest -from agrirouter.onboarding.request_body import SoftwareOnboardingBody, CUOnboardingBody -from agrirouter.onboarding.response import SoftwareVerifyOnboardingResponse, SoftwareOnboardingResponse, \ - CUOnboardingResponse +from agrirouter.onboarding.headers import SoftwareOnboardingHeader +from agrirouter.onboarding.parameters import SoftwareOnboardingParameter +from agrirouter.onboarding.request import SoftwareOnboardingRequest +from agrirouter.onboarding.request_body import SoftwareOnboardingBody +from agrirouter.onboarding.response import SoftwareVerifyOnboardingResponse, SoftwareOnboardingResponse class SoftwareOnboarding(EnvironmentalService): @@ -19,7 +18,7 @@ def __init__(self, *args, **kwargs): self._private_key = kwargs.pop("private_key") super(SoftwareOnboarding, self).__init__(*args, **kwargs) - def _create_request(self, params: BaseOnboardingParameter, url: str) -> SoftwareOnboardingRequest: + def _create_request(self, params: SoftwareOnboardingParameter, url: str) -> SoftwareOnboardingRequest: body_params = params.get_body_params() request_body = SoftwareOnboardingBody(**body_params) @@ -28,7 +27,7 @@ def _create_request(self, params: BaseOnboardingParameter, url: str) -> Software return SoftwareOnboardingRequest(header=request_header, body=request_body, url=url) - def _perform_request(self, params: BaseOnboardingParameter, url: str) -> requests.Response: + def _perform_request(self, params: SoftwareOnboardingParameter, url: str) -> requests.Response: request = self._create_request(params, url) request.sign(self._private_key, self._public_key) if request.is_signed: @@ -50,37 +49,3 @@ def onboard(self, params: SoftwareOnboardingParameter) -> SoftwareOnboardingResp http_response = self._perform_request(params=params, url=url) return SoftwareOnboardingResponse(http_response) - - -class CUOnboarding(EnvironmentalService): - - def __init__(self, *args, **kwargs): - self._public_key = kwargs.pop("public_key") - self._private_key = kwargs.pop("private_key") - super(CUOnboarding, self).__init__(*args, **kwargs) - - def _create_request(self, params: CUOnboardingParameter, url: str) -> CUOnboardingRequest: - body_params = params.get_body_params() - request_body = CUOnboardingBody(**body_params) - - header_params = params.get_header_params() - request_header = CUOnboardingHeader(**header_params) - - return CUOnboardingRequest(header=request_header, body=request_body, url=url) - - def _perform_request(self, params: CUOnboardingParameter, url: str) -> requests.Response: - request = self._create_request(params, url) - request.sign(self._private_key, self._public_key) - if request.is_signed: - return requests.post( - url=request.get_url(), - data=request.get_body_content(), - headers=request.get_header() - ) - raise RequestNotSigned - - def onboard(self, params: CUOnboardingParameter) -> CUOnboardingResponse: - url = self._environment.get_onboard_url() - http_response = self._perform_request(params=params, url=url) - - return CUOnboardingResponse(http_response) diff --git a/agrirouter/onboarding/parameters.py b/agrirouter/onboarding/parameters.py index 1ca75312..ba5e01e3 100644 --- a/agrirouter/onboarding/parameters.py +++ b/agrirouter/onboarding/parameters.py @@ -1,25 +1,10 @@ -from abc import ABC, abstractmethod from datetime import datetime from agrirouter.constants.media_types import ContentTypes from agrirouter.onboarding.enums import CertificateTypes -class BaseOnboardingParameter(ABC): - @abstractmethod - def __init__(self, *args, **kwargs): - ... - - @abstractmethod - def get_header_params(self, *args, **kwargs): - ... - - @abstractmethod - def get_body_params(self, *args, **kwargs): - ... - - -class SoftwareOnboardingParameter(BaseOnboardingParameter): +class SoftwareOnboardingParameter: def __init__(self, *, id_, @@ -61,38 +46,3 @@ def get_body_params(self): "utc_timestamp": self.utc_timestamp, "time_zone": self.time_zone, } - - -class CUOnboardingParameter(BaseOnboardingParameter): - def __init__(self, - id_, - application_id, - certification_version_id, - gateway_id, - reg_code, - content_type=ContentTypes.APPLICATION_JSON.value, - certificate_type=CertificateTypes.P12.value, - ): - - self.id_ = id_ - self.application_id = application_id - self.content_type = content_type - self.certification_version_id = certification_version_id - self.gateway_id = gateway_id - self.certificate_type = certificate_type - self.reg_code = reg_code - - def get_header_params(self): - return { - "content_type": self.content_type, - "reg_code": self.reg_code, - } - - def get_body_params(self): - return { - "id_": self.id_, - "application_id": self.application_id, - "certification_version_id": self.certification_version_id, - "gateway_id": self.gateway_id, - "certificate_type": self.certificate_type, - } diff --git a/agrirouter/onboarding/request.py b/agrirouter/onboarding/request.py index c39d9d7a..0728f149 100644 --- a/agrirouter/onboarding/request.py +++ b/agrirouter/onboarding/request.py @@ -1,10 +1,10 @@ -from agrirouter.onboarding.headers import SoftwareOnboardingHeader, BaseOnboardingHeader -from agrirouter.onboarding.request_body import SoftwareOnboardingBody, BaseOnboardingBody +from agrirouter.onboarding.headers import SoftwareOnboardingHeader +from agrirouter.onboarding.request_body import SoftwareOnboardingBody from agrirouter.onboarding.signature import create_signature, verify_signature -class BaseOnboardingRequest: - def __init__(self, header: BaseOnboardingHeader, body: BaseOnboardingBody, url: str): +class SoftwareOnboardingRequest: + def __init__(self, header: SoftwareOnboardingHeader, body: SoftwareOnboardingBody, url: str): self.header = header self.body = body self.url = url @@ -33,17 +33,3 @@ def is_signed(self): if header_has_signature: return True return False - - -class SoftwareOnboardingRequest(BaseOnboardingRequest): - """ - Request must be used to onboard Farming Software or Telemetry Platform - """ - pass - - -class CUOnboardingRequest(BaseOnboardingRequest): - """ - Request must be used to onboard CUs - """ - pass diff --git a/agrirouter/onboarding/request_body.py b/agrirouter/onboarding/request_body.py index 045c46a8..a730cf6e 100644 --- a/agrirouter/onboarding/request_body.py +++ b/agrirouter/onboarding/request_body.py @@ -1,30 +1,11 @@ import json -from abc import ABC, abstractmethod from datetime import datetime from agrirouter.onboarding.enums import CertificateTypes, GateWays from agrirouter.onboarding.exceptions import WrongCertificationType, WrongGateWay -class BaseOnboardingBody(ABC): - @abstractmethod - def __init__(self, *args, **kwargs): - ... - - @abstractmethod - def get_parameters(self, *args, **kwargs) -> dict: - ... - - @abstractmethod - def _set_params(self, *args, **kwargs): - ... - - @abstractmethod - def json(self, *args, **kwargs): - ... - - -class SoftwareOnboardingBody(BaseOnboardingBody): +class SoftwareOnboardingBody: def __init__(self, *, id_, @@ -86,21 +67,3 @@ def _validate_certificate_type(certificate_type: str) -> None: def _validate_gateway_id(gateway_id: str) -> None: if gateway_id not in GateWays.values_list(): raise WrongGateWay - - -class CUOnboardingBody(BaseOnboardingBody): - - def __init__(self, *args, **kwargs): - ... - - def get_parameters(self, *args, **kwargs) -> dict: - ... - - def _set_params(self, *args, **kwargs): - ... - - def json(self, new_lines: bool = True) -> str: - result = json.dumps(self.get_parameters(), indent="") - if not new_lines: - return result.replace("\n", "") - return result diff --git a/agrirouter/onboarding/response.py b/agrirouter/onboarding/response.py index d62ce45f..367879d6 100644 --- a/agrirouter/onboarding/response.py +++ b/agrirouter/onboarding/response.py @@ -91,11 +91,3 @@ def get_device_alternate_id(self) -> str: def get_capability_alternate_id(self) -> str: return self.capability_alternate_id - - -class CUOnboardingResponse(BaseOnboardingResonse): - """ - Response from onboarding request used for CUs - """ - pass - From 4ce546863d668ec9f9bf37667bb70142fab78359 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 17:57:55 +0300 Subject: [PATCH 30/55] Refactor BaseEnvieonment --- agrirouter/environments/environments.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/agrirouter/environments/environments.py b/agrirouter/environments/environments.py index 503ecd6b..5c688c7a 100644 --- a/agrirouter/environments/environments.py +++ b/agrirouter/environments/environments.py @@ -6,7 +6,7 @@ class BaseEnvironment: _MQTT_URL_TEMPLATE = "ssl://{host}:{port}" _SECURED_ONBOARDING_AUTHORIZATION_LINK_TEMPLATE = \ "/application/{application_id}/authorize" \ - "?response_type={response_type}&state={state}&redirect_uri={redirect_uri}" + "?response_type={response_type}&state={state}" _ENV_BASE_URL = "" _API_PREFIX = "" @@ -38,13 +38,13 @@ def get_revoke_url(self) -> str: def get_agrirouter_login_url(self) -> str: return self.get_base_url() + self._AGRIROUTER_LOGIN_URL - def get_secured_onboarding_authorization_url(self, application_id, response_type, state, redirect_uri) -> str: - return self.get_base_url() + self._SECURED_ONBOARDING_AUTHORIZATION_LINK_TEMPLATE.format( - application_id=application_id, - response_type=response_type, - state=state, - redirect_uri=redirect_uri - ) + def get_secured_onboarding_authorization_url(self, application_id, response_type, state, redirect_uri=None) -> str: + auth_url = self.get_base_url() + self._SECURED_ONBOARDING_AUTHORIZATION_LINK_TEMPLATE.format( + application_id=application_id, + response_type=response_type, + state=state + ) + return auth_url + f"&redirect_uri={redirect_uri}" if redirect_uri is not None else auth_url def get_mqtt_server_url(self, host, port) -> str: return self._MQTT_URL_TEMPLATE.format(host=host, port=port) From 819c60c01c91bc03310bd7ae8db7d249d21c5d8f Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 18:01:31 +0300 Subject: [PATCH 31/55] Refactor Authorization.__init__ --- agrirouter/auth/auth.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/agrirouter/auth/auth.py b/agrirouter/auth/auth.py index 6cf1902b..f1e2f634 100644 --- a/agrirouter/auth/auth.py +++ b/agrirouter/auth/auth.py @@ -12,10 +12,10 @@ class Authorization(EnvironmentalService): TOKEN_KEY = "token" ERROR_KEY = "error" - def __init__(self, *args, **kwargs): - self._public_key = kwargs.pop("public_key") - self._private_key = kwargs.pop("private_key") - super(Authorization, self).__init__(*args, **kwargs) + def __init__(self, env, public_key, private_key): + self._public_key = public_key + self._private_key = private_key + super(Authorization, self).__init__(env) def get_auth_request_url(self, parameters: AuthUrlParameter) -> str: auth_parameters = parameters.get_parameters() From 4aac735bb14485e608a536c701397e4333cc3657 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 18:16:25 +0300 Subject: [PATCH 32/55] Refactor auth --- agrirouter/auth/response.py | 50 ++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/agrirouter/auth/response.py b/agrirouter/auth/response.py index 9aac71b3..b5a73d48 100644 --- a/agrirouter/auth/response.py +++ b/agrirouter/auth/response.py @@ -5,6 +5,7 @@ from cryptography.exceptions import InvalidSignature +from agrirouter.auth.dto import AuthorizationToken from agrirouter.onboarding.signature import verify_signature @@ -17,11 +18,11 @@ class AuthResponse: CRED_KEY = "credentials" def __init__(self, query_params): - self._state = query_params.get(self.STATE_KEY, None) - self._signature = query_params.get(self.SIGNATURE_KEY, None) - self._token = query_params.get(self.TOKEN_KEY, None) - self._error = query_params.get(self.ERROR_KEY, None) - self.is_successful = not bool(self._error) + self.state = query_params.get(self.STATE_KEY, None) + self.signature = query_params.get(self.SIGNATURE_KEY, None) + self.token = query_params.get(self.TOKEN_KEY, None) + self.error = query_params.get(self.ERROR_KEY, None) + self.is_successful = not bool(self.error) self._was_verified = False self._is_valid = False @@ -43,8 +44,8 @@ def verify(self, public_key) -> None: :return: """ - encoded_data = self._state + self._token - unquoted_signature = unquote(self._signature) + encoded_data = self.state + self.token + unquoted_signature = unquote(self.signature) encoded_signature = base64.b64decode(unquoted_signature.encode("utf-8")) try: @@ -58,20 +59,41 @@ def verify(self, public_key) -> None: self._is_valid = True @staticmethod - def decode_token(token: Union[str, bytes]) -> dict: + def decode_token(token: Union[str, bytes]) -> AuthorizationToken: if type(token) == str: token = token.encode("utf-8") base_64_decoded_token = base64.b64decode(token) decoded_token = base_64_decoded_token.decode("utf-8") - return json.loads(decoded_token) + + auth_token = AuthorizationToken() + auth_token.json_deserialize(json.loads(decoded_token)) + return auth_token def get_auth_result(self) -> dict: if not self.is_successful: - return {self.ERROR_KEY: self._error} - decoded_token = self.decode_token(self._token) + return {self.ERROR_KEY: self.error} + decoded_token = self.decode_token(self.token) return { - self.SIGNATURE_KEY: self._signature, - self.STATE_KEY: self._state, - self.TOKEN_KEY: self._token, + self.SIGNATURE_KEY: self.signature, + self.STATE_KEY: self.state, + self.TOKEN_KEY: self.token, self.CRED_KEY: decoded_token } + + def get_signature(self): + return self.signature + + def set_signature(self, signature): + self.signature = signature + + def get_state(self): + return self.state + + def set_state(self, state): + self.state = state + + def get_token(self): + return self.token + + def set_token(self, token): + self.token = token From 8005b5063651d620754f32968a4a2063c695fc38 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 19:13:31 +0300 Subject: [PATCH 33/55] Refactor SoftwareOnboardingResponse --- agrirouter/onboarding/response.py | 78 ++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 6 deletions(-) diff --git a/agrirouter/onboarding/response.py b/agrirouter/onboarding/response.py index 367879d6..1b9c6591 100644 --- a/agrirouter/onboarding/response.py +++ b/agrirouter/onboarding/response.py @@ -1,7 +1,9 @@ import json +from typing import Union from requests import Response +from agrirouter.messaging.exceptions import WrongFieldError from agrirouter.onboarding.dto import ErrorResponse, ConnectionCriteria, Authentication @@ -26,9 +28,14 @@ class SoftwareVerifyOnboardingResponse(BaseOnboardingResonse): Response from verify request used for Farming Software or Telemetry Platform before onboarding """ - def __init__(self, http_response: Response): - super(SoftwareVerifyOnboardingResponse, self).__init__(http_response) - response_body = http_response.json() + def __init__(self, http_response: Response = None): + if http_response: + super(SoftwareVerifyOnboardingResponse, self).__init__(http_response) + response_body = http_response.json() + else: + self._text = None + self._status_code = None + response_body = {} self.account_id = response_body.get("accountId", None) @@ -42,14 +49,29 @@ def __init__(self, http_response: Response): def get_account_id(self) -> str: return self.account_id + def set_account_id(self, account_id: str): + self.account_id = account_id + class SoftwareOnboardingResponse(BaseOnboardingResonse): """ Response from onboarding request used for Farming Software or Telemetry Platform """ - def __init__(self, http_response: Response): - super(SoftwareOnboardingResponse, self).__init__(http_response) - response_body = http_response.json() + + DEVICE_ALTERNATE_ID = "deviceAlternateId" + CAPABILITY_ALTERNATE_ID = "capabilityAlternateId" + SENSOR_ALTERNATE_ID = "sensorAlternateId" + CONNECTION_CRITERIA = "connectionCriteria" + AUTHENTICATION = "authentication" + + def __init__(self, http_response: Response = None): + if http_response: + super(SoftwareOnboardingResponse, self).__init__(http_response) + response_body = http_response.json() + else: + self._text = None + self._status_code = None + response_body = {} self.connection_criteria = ConnectionCriteria( gateway_id=response_body.get("connectionCriteria").get("gatewayId"), @@ -80,14 +102,58 @@ def __init__(self, http_response: Response): def get_connection_criteria(self) -> ConnectionCriteria: return self.connection_criteria + def set_connection_criteria(self, connection_criteria: ConnectionCriteria): + self.connection_criteria = connection_criteria + def get_authentication(self) -> Authentication: return self.authentication + def set_authentication(self, authentication: Authentication): + self.authentication = authentication + def get_sensor_alternate_id(self) -> str: return self.sensor_alternate_id + def set_sensor_alternate_id(self, sensor_alternate_id: str): + self.sensor_alternate_id = sensor_alternate_id + def get_device_alternate_id(self) -> str: return self.device_alternate_id + def set_device_alternate_id(self, device_alternate_id: str): + self.device_alternate_id = device_alternate_id + def get_capability_alternate_id(self) -> str: return self.capability_alternate_id + + def set_capability_alternate_id(self, capability_alternate_id: str): + self.capability_alternate_id = capability_alternate_id + + def json_serialize(self): + return { + self.DEVICE_ALTERNATE_ID: self.device_alternate_id, + self.CAPABILITY_ALTERNATE_ID: self.capability_alternate_id, + self.SENSOR_ALTERNATE_ID: self.sensor_alternate_id, + self.CONNECTION_CRITERIA: self.connection_criteria, + self.AUTHENTICATION: self.authentication + } + + def json_deserialize(self, data: Union[dict, str]): + data_dict = data if type(data) == dict else json.loads(data) + for (key, value) in data_dict.items(): + if key == self.DEVICE_ALTERNATE_ID: + self.device_alternate_id = value + if key == self.CAPABILITY_ALTERNATE_ID: + self.capability_alternate_id = value + if key == self.SENSOR_ALTERNATE_ID: + self.sensor_alternate_id = value + if key == self.CONNECTION_CRITERIA: + connection_criteria = ConnectionCriteria() + connection_criteria.json_deserialize(value) + self.connection_criteria = connection_criteria + if key == self.AUTHENTICATION: + authentication = Authentication() + authentication.json_deserialize(value) + self.authentication = authentication + else: + raise WrongFieldError(f"Unknown field `{key}` for {self.__class__}") From 55ecf8fbf1cc0df9cdda8b182714e07ef56df4c4 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 19:46:22 +0300 Subject: [PATCH 34/55] Refactor SoftwareOnboardingParameter --- agrirouter/onboarding/parameters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agrirouter/onboarding/parameters.py b/agrirouter/onboarding/parameters.py index ba5e01e3..bb62ae2e 100644 --- a/agrirouter/onboarding/parameters.py +++ b/agrirouter/onboarding/parameters.py @@ -2,6 +2,7 @@ from agrirouter.constants.media_types import ContentTypes from agrirouter.onboarding.enums import CertificateTypes +from agrirouter.utils.utc_time_util import now_as_utc_str class SoftwareOnboardingParameter: @@ -24,8 +25,7 @@ def __init__(self, self.certification_version_id = certification_version_id self.gateway_id = str(gateway_id) self.certificate_type = certificate_type - self.utc_timestamp = str(utc_timestamp) if utc_timestamp \ - else datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ") + self.utc_timestamp = str(utc_timestamp) if utc_timestamp else now_as_utc_str() self.time_zone = str(time_zone) self.reg_code = reg_code From 3658d118da691d8a0c51faa7aaa9606823c9b7d3 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 28 Oct 2021 20:14:45 +0300 Subject: [PATCH 35/55] Implement tests for --- .../messaging_test/test_messaging_services.py | 237 ++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 tests/messaging_test/test_messaging_services.py diff --git a/tests/messaging_test/test_messaging_services.py b/tests/messaging_test/test_messaging_services.py new file mode 100644 index 00000000..3ca06ea7 --- /dev/null +++ b/tests/messaging_test/test_messaging_services.py @@ -0,0 +1,237 @@ +import pytest +from google.protobuf.timestamp_pb2 import Timestamp + +from agrirouter import CapabilityParameters, CapabilityService, QueryHeaderService, QueryHeaderParameters +from agrirouter.generated.messaging.request.payload.feed.feed_requests_pb2 import ValidityPeriod + +from agrirouter.messaging.builders import CapabilityBuilder +from agrirouter.messaging.decode import decode_response, decode_details +from agrirouter.messaging.services.commons import HttpMessagingService +from agrirouter.messaging.services.http.outbox import OutboxService +from agrirouter.onboarding.response import SoftwareOnboardingResponse +from agrirouter.utils.uuid_util import new_uuid +from tests.sleeper import let_agrirouter_process_the_message + +onboarding_response = SoftwareOnboardingResponse() +onboarding_response.json_deserialize('{"deviceAlternateId":"e2b512f3-9930-4461-b35f-2cdcc7f017fd","capabilityAlternateId":"523e4623-68d2-43d4-a0cc-e2ada2f68b5e","sensorAlternateId":"185cd97b-ed0b-4e75-a6e2-6be1cdd38a06","connectionCriteria":{"gatewayId":"3","measures":"https://dke-qa.eu10.cp.iot.sap/iot/gateway/rest/measures/e2b512f3-9930-4461-b35f-2cdcc7f017fd","commands":"https://dke-qa.eu10.cp.iot.sap/iot/gateway/rest/commands/e2b512f3-9930-4461-b35f-2cdcc7f017fd"},"authentication":{"type":"PEM","secret":"VXHfXV7g#3COYT1Q5YkTxrRXo1IjdN8xSZ3O","certificate":"-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIE6zAdBgoqhkiG9w0BDAEDMA8ECHlX0q4qf0tSAgMCAAAEggTI4XIOXehAN02J\nwUrHPe2BCGEqXvyA2QzWMBkqYRm7dCmhL/ay1JzJkkDf2afS/1FE0Sl3So1AMKBe\nhpqZUpaz5g7tN1ktl9BReXC+pptJPpy3XSpZ/8Bg3Jje4eYn2aCWMSNbnw0SO9Sf\nZnUZrNwQH9GSR35fGHYPaYHmAIM9axaUSn6/LfsrifdORY38r9EKvrcuIYFJ18ai\ngHoCAboaPLY52m/UkaM0WNnpTrHm/72G8978jpZKYZmbmp7/qdB7+aQ+WZWs4u/V\nCR6vzgkyWaFzQK5GMKCMBgHteq5900FY9Iz/ZBS/gyoHoVdMsRKRBSXfNebdvADC\nkksZBfaYqMI58CFEuVODi7gD+YKcu7/5BjX8DJ72eDFaYa1ZIC+na24gel8x85UF\n+TwFSQ4NgHmqcUkZJOyRtcnMREP79ZkdGXi4l6eZk4hG9KhfM66HgcvIzGT6e1SF\nJrKcLTVYUdYSyhLZmk7DflgI5VoCKX1/P1O0iiyWqsbqhjfsnfTpaCaveMb+c/2z\nSaw3tc5G6th/QdN72wZTM1Vqb4562JEpxvkxY4i2PW1Ky9HNY0M+jy0DL/KhfnM9\nm8abxdgILTu3WcxnfH7f8uiK2R9zgnIf+CCDtGGWftOGgV5MY4t0XLAsGSadNdqH\nrpTguI4XMcQET0ZEE7fTbkvJ2+QVWz3vD8w4/ryZx12ZzQCoewd5nPQD/JgLoo2R\n8Pdp2TQDYrn0PYZHus1GnPL7kAs34gl1zFNxFF8nYSSP0hITc2zWm8XuLyMc8fgs\nymXZPdTz9LQZPqs5t+P0gL04xegaXiYWWAhZFkMx9/0+zeoNK+w28cTsLRC4oQcJ\nkaq69f7gHIg3ar75Zzzc1xUEgz50oJI2BCDVLllHzoAWFjl5t/9hGcBirP3QOKwg\nCKfHKnbLkSS+2omhp3zBecX7moDS6+RcMMIUvMbHHLx8l4uv/cDtWIh++I21hzQu\ntSaIK6gglGE0OZJN3tPmy3CdHqbapBWvfCMGD2J2xISJCoSwmR58fni4pyniTydt\nJH040YpDUuUxIDlVTFAsmMRm5vh2KkE4DVr1UqA+JJgHRuQiuVbprm/QU2bumTG+\n4vhOKcaSgbQW3++NtvfVSymz5/IJXOMQIzprokvC+6GwiRUBIov7angWwUB9X5iC\nN0rfJ5MbNkabXsFRQpbXYV7z/P+t/9A8A5LBr+DiPLib9i3WI4sHV5aLZapJ3R4u\neBCVB+VIGbcTM19t51h4ohZhY2Q9CYXVnle7ol0Nz/mUiX0Az9oF6GQCngMFdqMh\nftl2XdCq43AZA7hHP+wqKkjOo7i5Lr0IRWw5F7IexV8mHDwx560DJkp0bZM9+UxA\nu1JcTLDJR+a/aOsx5CDSWig+W3XNCQfC4kVhNUlWZ1yQ8Heh+NB3kD+Krvby40DL\nzNOe6VSkFCjKn2st1yJkdcdhLs8mPE1DEk7WRFS+AMaIgXIGpdoBIlUwHpwP7djd\n8gC9e3kFTAazudZkTCi8QnhAeH1coxVhB6+WbzVnFzJDZuy65DiVDSmOjlHmxF7A\nZc5LCo1vmtER3d08bjuz+33dCGS2yKg2Q7Nd3rymUlGzPEx+dPFmvXaDWNXghdbj\nc9PfhpiX5tt83tHe7E2C\n-----END ENCRYPTED PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\nMIIEaTCCA1GgAwIBAgIQAPktOgtD/4tlEAEHAL6qbTANBgkqhkiG9w0BAQsFADBW\nMQswCQYDVQQGEwJERTEjMCEGA1UEChMaU0FQIElvVCBUcnVzdCBDb21tdW5pdHkg\nSUkxIjAgBgNVBAMTGVNBUCBJbnRlcm5ldCBvZiBUaGluZ3MgQ0EwHhcNMjExMDI1\nMTk0MzE1WhcNMjIxMDI1MTk0MzE1WjCBtTELMAkGA1UEBhMCREUxHDAaBgNVBAoT\nE1NBUCBUcnVzdCBDb21tdW5pdHkxFTATBgNVBAsTDElvVCBTZXJ2aWNlczFxMG8G\nA1UEAxRoZGV2aWNlQWx0ZXJuYXRlSWQ6ZTJiNTEyZjMtOTkzMC00NDYxLWIzNWYt\nMmNkY2M3ZjAxN2ZkfGdhdGV3YXlJZDozfHRlbmFudElkOjExMTY5MDM0OTB8aW5z\ndGFuY2VJZDpka2UtcWEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCY\nRAKTeVe2Eo4EtV1QJi1m3gZDpAAXYYhGo8905yw4XZD9M2fCyMbUVcoAm/5lGN+W\nsMk/GsfNeBRmd80SLv6/Z7342tVryhslkGL0TVw2MHMw+1cEAPsH6EthrvH6poTs\ngGDtUB4ad2BOvBwveTPpHwdWxyDUvb74mXwXZ9XgIo/VJKSEr6DlO+zv52BUXRh9\nS70m4dgM0aTM/iRYITrPPXHZfY91M9lsypp64m1dHzDXiQaWvFqaiyIOw/IO2V+O\nMmz3U1Q6L/8ai4WNeTTX69hprOPDTCG5WLdnDviK9hx1w6tOyRdKun7LpklZ14Rv\nApZXATxFwxrNQm2iiFbfAgMBAAGjgdIwgc8wSAYDVR0fBEEwPzA9oDugOYY3aHR0\ncHM6Ly90Y3MubXlzYXAuY29tL2NybC9UcnVzdENvbW11bml0eUlJL1NBUElvVENB\nLmNybDAMBgNVHRMBAf8EAjAAMCUGA1UdEgQeMByGGmh0dHA6Ly9zZXJ2aWNlLnNh\ncC5jb20vVENTMA4GA1UdDwEB/wQEAwIGwDAdBgNVHQ4EFgQUDG1OiFv4Ohku4sG+\n6vC9+x3nCGQwHwYDVR0jBBgwFoAUlbez9Vje1bSzWEbg8qbJeE69LXUwDQYJKoZI\nhvcNAQELBQADggEBADgzaWG5+ch1iQ6tHOHO8/sVBpQJ0kEnHCxDeJ1WRL6Qas/n\nMZPMwzmllsUhQv93fVElGosy2sMjCanCLnCh8qwb85vq7exEZBccbmUo6Epqz9XO\n/NJ4Fr1OWLtE9svRM5s0QEB6B9oQ1OjZtdjeGI9/uQSJgmzYKdI/HAFkTTugokRU\nkyr+rM6Rv9KCNbkzoNTRS6xDNs64FxEw53FBYitmtnsgXAdWPjHpkoZFIntstuFr\nVwpdxeH1TZmdvwhtImibcqGHgUqa7r1lySbK+sEdFzQcf7Ea1dRJR3r1ZfG1/ALn\nRInsXoCBNxyllk6ExpQWiczLiOY5jXnQulX51+k=\n-----END CERTIFICATE-----\n"}}') +account_id = "fb2921de-592a-49ba-be5e-94044430bc96" +certification_version_id = "edd5d6b7-45bb-4471-898e-ff9c2a7bf56f" +application_id = "8c947a45-c57d-42d2-affc-206e21d63a50" + + +def test_given_task_data_capabilities_capability_service_http(): + messaging_service = HttpMessagingService() + capability_parameters = CapabilityParameters( + application_id=application_id, + certification_version_id=certification_version_id, + enable_push_notification=1, + capability_parameters=CapabilityBuilder().with_task_data(2).build(), + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + capability_service = CapabilityService(messaging_service) + messaging_result = capability_service.send(capability_parameters) + assert messaging_result.get_messages_ids() + assert 1 == len(messaging_result.get_messages_ids()) + + +# def test_list_endpoint_service_http(): +# messaging_service = HttpMessagingService() +# list_endpoint_parameters = ListEndpointsParameters( +# technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, +# direction=ListEndpointsQuery.Direction.Value("SEND_RECEIVE"), +# filtered=False, +# onboarding_response=onboarding_response, +# application_message_id=new_uuid(), +# application_message_seq_no=1, +# ) +# list_endpoint_service = ListEndpointsService(messaging_service) +# messaging_result = list_endpoint_service.send(list_endpoint_parameters) +# return messaging_result +# +# +# def test_list_endpoint_service_mqtt(): +# client = MqttClient(on_message_callback=foo, client_id="fb2921de-592a-49ba-be5e-94044430bc96") +# messaging_service = MqttMessagingService(onboarding_response, client) +# list_endpoint_parameters = ListEndpointsParameters( +# technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, +# direction=ListEndpointsQuery.Direction.Value("SEND_RECEIVE"), +# filtered=False, +# onboarding_response=onboarding_response, +# application_message_id=new_uuid(), +# application_message_seq_no=1, +# ) +# list_endpoint_service = ListEndpointsService(messaging_service) +# messaging_result = list_endpoint_service.send(list_endpoint_parameters) +# return messaging_result +# +# +# def test_valid_subscription_service_http(): +# messaging_service = HttpMessagingService() +# subscription_service = SubscriptionService(messaging_service) +# items = [] +# for tmt in [CapabilityType.DOC_PDF.value, CapabilityType.ISO_11783_TASKDATA_ZIP.value]: +# subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) +# items.append(subscription_item) +# subscription_parameters = SubscriptionParameters( +# subscription_items=items, +# onboarding_response=onboarding_response, +# application_message_id=new_uuid(), +# application_message_seq_no=1, +# ) +# messaging_result = subscription_service.send(subscription_parameters) +# return messaging_result + + +def test_query_header_message_http(): + messaging_service = HttpMessagingService() + query_header_service = QueryHeaderService(messaging_service) + sent_from = Timestamp() + sent_to = Timestamp() + validity_period = ValidityPeriod(sent_from=sent_from, sent_to=sent_to) + query_header_parameters = QueryHeaderParameters( + message_ids=[new_uuid(), new_uuid()], + senders=[new_uuid(), new_uuid()], + validity_period=validity_period, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + return messaging_result + + +def test_given_validity_and_missing_messages_query_messages_service_http(): + messaging_service = HttpMessagingService() + query_header_service = QueryHeaderService(messaging_service) + sent_from = Timestamp() + sent_to = Timestamp() + validity_period = ValidityPeriod(sent_from=sent_from, sent_to=sent_to) + query_header_parameters = QueryHeaderParameters( + validity_period=validity_period, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + assert messaging_result.get_messages_ids() + assert 1 == len(messaging_result.get_messages_ids()) + + let_agrirouter_process_the_message() + + outbox_service = OutboxService() + outbox_response = outbox_service.fetch(onboarding_response) + assert 200 == outbox_response.status_code + + messages = outbox_response.messages + assert len(messages) == 1 + assert messages[0].command.message + + decoded_message = decode_response(outbox_response.messages[0].command.message) + assert 204 == decoded_message.response_envelope.response_code + + decoded_details = decode_details(decoded_message.response_payload.details) + assert decoded_details + + query_metrics = decoded_details.query_metrics + assert 0 == query_metrics.total_messages_in_query + + +def test_given_invalid_sender_id_query_messages_service_http(): + messaging_service = HttpMessagingService() + query_header_service = QueryHeaderService(messaging_service) + query_header_parameters = QueryHeaderParameters( + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + assert messaging_result.get_messages_ids() + assert 1 == len(messaging_result.get_messages_ids()) + + let_agrirouter_process_the_message() + + outbox_service = OutboxService() + outbox_response = outbox_service.fetch(onboarding_response) + assert 200 == outbox_response.status_code + + messages = outbox_response.messages + assert len(messages) == 1 + assert messages[0].command.message + + decoded_message = decode_response(outbox_response.messages[0].command.message) + assert 204 == decoded_message.response_envelope.response_code + + decoded_details = decode_details(decoded_message.response_payload.details) + assert decoded_details + + query_metrics = decoded_details.query_metrics + assert 0 == query_metrics.total_messages_in_query + + +def test_given_invalid_message_id_query_messages_service_http(): + messaging_service = HttpMessagingService() + query_header_service = QueryHeaderService(messaging_service) + query_header_parameters = QueryHeaderParameters( + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + assert messaging_result.get_messages_ids() + assert 1 == len(messaging_result.get_messages_ids()) + + let_agrirouter_process_the_message() + + outbox_service = OutboxService() + outbox_response = outbox_service.fetch(onboarding_response) + assert 200 == outbox_response.status_code + + messages = outbox_response.messages + assert len(messages) == 1 + assert messages[0].command.message + + decoded_message = decode_response(outbox_response.messages[0].command.message) + assert 204 == decoded_message.response_envelope.response_code + + decoded_details = decode_details(decoded_message.response_payload.details) + assert decoded_details + + query_metrics = decoded_details.query_metrics + assert 0 == query_metrics.total_messages_in_query + + +def test_given_missing_filter_criteria_id_query_messages_service_http(): + messaging_service = HttpMessagingService() + query_header_service = QueryHeaderService(messaging_service) + query_header_parameters = QueryHeaderParameters( + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + assert messaging_result.get_messages_ids() + assert 1 == len(messaging_result.get_messages_ids()) + + let_agrirouter_process_the_message() + + outbox_service = OutboxService() + outbox_response = outbox_service.fetch(onboarding_response) + assert 200 == outbox_response.status_code + + messages = outbox_response.messages + assert len(messages) == 1 + assert messages[0].command.message + + decoded_message = decode_response(outbox_response.messages[0].command.message) + assert 400 == decoded_message.response_envelope.response_code + + decoded_details = decode_details(decoded_message.response_payload.details) + assert decoded_details + + assert 1 == len(decoded_details.messages) + + for message in decoded_details.messages: + assert "VAL_000017" == message.message_code + assert message.message == "Query does not contain any filtering criteria: messageIds, senders or validityPeriod. Information required to process message is missing or malformed." From a11821b9666bb6f5d00b05a5465d8dd9494b928c Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Fri, 29 Oct 2021 00:03:24 +0300 Subject: [PATCH 36/55] Refactor messaging test --- tests/messaging_test/test_messaging_services.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/messaging_test/test_messaging_services.py b/tests/messaging_test/test_messaging_services.py index 3ca06ea7..4d5a5e15 100644 --- a/tests/messaging_test/test_messaging_services.py +++ b/tests/messaging_test/test_messaging_services.py @@ -4,6 +4,7 @@ from agrirouter import CapabilityParameters, CapabilityService, QueryHeaderService, QueryHeaderParameters from agrirouter.generated.messaging.request.payload.feed.feed_requests_pb2 import ValidityPeriod +from agrirouter.generated.messaging.request.payload.endpoint.capabilities_pb2 import CapabilitySpecification from agrirouter.messaging.builders import CapabilityBuilder from agrirouter.messaging.decode import decode_response, decode_details from agrirouter.messaging.services.commons import HttpMessagingService @@ -25,7 +26,9 @@ def test_given_task_data_capabilities_capability_service_http(): application_id=application_id, certification_version_id=certification_version_id, enable_push_notification=1, - capability_parameters=CapabilityBuilder().with_task_data(2).build(), + capability_parameters=CapabilityBuilder().with_task_data( + CapabilitySpecification.Direction.Value("SEND_RECEIVE") + ).build(), onboarding_response=onboarding_response, application_message_id=new_uuid(), application_message_seq_no=1, From 5707874982885f3954124d5f34ec4fe9030284f0 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 9 Nov 2021 18:33:42 +0300 Subject: [PATCH 37/55] Refactor Dtos --- agrirouter/auth/dto.py | 84 ++++++++++++++++++++----------------- agrirouter/auth/response.py | 20 ++++----- 2 files changed, 56 insertions(+), 48 deletions(-) diff --git a/agrirouter/auth/dto.py b/agrirouter/auth/dto.py index fec51c0a..8e4be05e 100644 --- a/agrirouter/auth/dto.py +++ b/agrirouter/auth/dto.py @@ -4,44 +4,6 @@ from agrirouter.messaging.exceptions import WrongFieldError -class AuthorizationResultUrl: - def __init__(self, - *, - state: str = None, - signature: str = None, - token: str = None, - error: str = None - ): - self.state = state - self.signature = signature - self.token = token - self.error = error - - def get_state(self) -> str: - return self.state - - def set_state(self, state: str) -> None: - self.state = state - - def get_signature(self) -> str: - return self.signature - - def set_signature(self, signature: str) -> None: - self.signature = signature - - def get_token(self) -> str: - return self.token - - def set_token(self, token: str) -> None: - self.token = token - - def get_error(self) -> str: - return self.error - - def set_error(self, error: str) -> None: - self.error = error - - class AuthorizationToken: ACCOUNT = 'account' REGISTRATION_CODE = 'regcode' @@ -88,6 +50,52 @@ def set_expires(self, expires: str) -> None: self.expires = expires +class AuthorizationResultUrl: + def __init__(self, + *, + state: str = None, + signature: str = None, + token: str = None, + decoded_token: AuthorizationToken = None, + error: str = None + ): + self.state = state + self.signature = signature + self.token = token + self.decoded_token = decoded_token + self.error = error + + def get_state(self) -> str: + return self.state + + def set_state(self, state: str) -> None: + self.state = state + + def get_signature(self) -> str: + return self.signature + + def set_signature(self, signature: str) -> None: + self.signature = signature + + def get_token(self) -> str: + return self.token + + def set_token(self, token: str) -> None: + self.token = token + + def get_error(self) -> str: + return self.error + + def set_error(self, error: str) -> None: + self.error = error + + def get_decoded_token(self) -> AuthorizationToken: + return self.decoded_token + + def set_decoded_token(self, decoded_token: AuthorizationToken) -> None: + self.decoded_token = decoded_token + + class AuthorizationResult: def __init__(self, *, diff --git a/agrirouter/auth/response.py b/agrirouter/auth/response.py index b5a73d48..a4b87f7f 100644 --- a/agrirouter/auth/response.py +++ b/agrirouter/auth/response.py @@ -5,7 +5,7 @@ from cryptography.exceptions import InvalidSignature -from agrirouter.auth.dto import AuthorizationToken +from agrirouter.auth.dto import AuthorizationToken, AuthorizationResultUrl from agrirouter.onboarding.signature import verify_signature @@ -69,16 +69,16 @@ def decode_token(token: Union[str, bytes]) -> AuthorizationToken: auth_token.json_deserialize(json.loads(decoded_token)) return auth_token - def get_auth_result(self) -> dict: - if not self.is_successful: - return {self.ERROR_KEY: self.error} + def get_auth_result(self) -> AuthorizationResultUrl: decoded_token = self.decode_token(self.token) - return { - self.SIGNATURE_KEY: self.signature, - self.STATE_KEY: self.state, - self.TOKEN_KEY: self.token, - self.CRED_KEY: decoded_token - } + + return AuthorizationResultUrl( + signature=self.signature, + state=self.state, + token=self.token, + decoded_token=decoded_token, + error=self.error + ) def get_signature(self): return self.signature From 9a4edb48cb9126891debf9778567f32693f46aa2 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Tue, 9 Nov 2021 18:34:31 +0300 Subject: [PATCH 38/55] [WIP] Fix MqttClient --- agrirouter/messaging/clients/mqtt.py | 64 +++++----- agrirouter/messaging/services/commons.py | 11 +- example_script.py | 142 ++++++++++++++--------- examples.txt | 2 +- 4 files changed, 129 insertions(+), 90 deletions(-) diff --git a/agrirouter/messaging/clients/mqtt.py b/agrirouter/messaging/clients/mqtt.py index 72ff399d..cfc4b90c 100644 --- a/agrirouter/messaging/clients/mqtt.py +++ b/agrirouter/messaging/clients/mqtt.py @@ -1,13 +1,18 @@ +import ssl from typing import Any, List, Tuple -from paho.mqtt import client as mqtt_client +import paho.mqtt.client as mqtt_client from paho.mqtt.client import MQTTv31, MQTTMessageInfo +from agrirouter.messaging.certification import create_certificate_file_from_pen + class MqttClient: def __init__(self, - client_id, + onboard_response, + + client_id: str, on_message_callback: callable = None, userdata: Any = None, clean_session: bool = True @@ -22,18 +27,29 @@ def __init__(self, protocol=MQTTv31, transport="tcp" ) + self.mqtt_client.on_message = on_message_callback if on_message_callback else self._get_on_message_callback() self.mqtt_client.on_connect = self._get_on_connect_callback() self.mqtt_client.on_disconnect = self._get_on_disconnect_callback() + self.mqtt_client.on_connect = self._get_on_connect_callback() self.mqtt_client.on_subscribe = self._get_on_subscribe_callback() self.mqtt_client.on_unsubscribe = self._get_on_unsubscribe_callback() + certificate_file_path = create_certificate_file_from_pen(onboard_response) + context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + context.load_cert_chain( + certfile=certificate_file_path, + keyfile=certificate_file_path, + password=onboard_response.get_authentication().get_secret(), + ) + self.mqtt_client.tls_set_context() + def connect(self, host: str, port: str) -> None: - self.mqtt_client.connect_async( + self.mqtt_client.connect( host=host, - port=port + port=int(port) ) - self.mqtt_client.loop_start() + self.mqtt_client.loop_forever() def disconnect(self): self.mqtt_client.loop_stop() @@ -41,7 +57,6 @@ def disconnect(self): def publish(self, topic, payload, qos=0) -> MQTTMessageInfo: """ - :param topic: str representing unique name of the topic that the message should be published on :param payload: The actual message to send :param qos: int representing the quality of service level to use. May be [0, 1, 2] @@ -88,19 +103,9 @@ def unsubscribe(self, topics: List[str]) -> tuple: def _get_on_connect_callback() -> callable: def on_connect(client, userdata, flags, rc, properties=None): - print("Connection started") - with open("connection.txt", "w") as file: - file.write("Connection started") - if rc == 0: - file.write("Connected!!") - else: - file.write("Do not Connected!!") - if rc == 0: - print("Connected to MQTT Broker!") - else: - print(f"Failed to connect, return code: {rc}") - - return client, userdata, flags, rc, properties + print("On_connect func start...") + print(f"Connection with response code: {rc}, flags: {flags}") + print("-"*50) return on_connect @@ -108,7 +113,7 @@ def on_connect(client, userdata, flags, rc, properties=None): def _get_on_message_callback() -> callable: def on_message(client, userdata, msg): - # print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic") + print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic") return client, userdata, msg @@ -117,29 +122,30 @@ def on_message(client, userdata, msg): @staticmethod def _get_on_subscribe_callback() -> callable: - def on_subscribe(client, userdata, mid, granted_qos, properties=None): - # print(f"Subscribed {userdata} to `{properties}`") + # def on_subscribe(client, userdata, mid, granted_qos, properties=None): + def on_subscribe(*args, **kwargs): + print(f"Subscribed. Args: `{args}`, Kwargs: `{kwargs}`") - return client, userdata, mid, granted_qos, properties + return args, kwargs return on_subscribe @staticmethod def _get_on_disconnect_callback() -> callable: - def on_disconnect(client, userdata, rc): - # print(f"Disconnected from from `{properties}`") + def on_disconnect(*args, **kwargs): + print(f"Disconnected. Args: `{args}`, Kwargs: `{kwargs}`") - return client, userdata, rc + return args, kwargs return on_disconnect @staticmethod def _get_on_unsubscribe_callback() -> callable: - def on_unsubscribe(client, userdata, mid): - # print(f"Unsubscribed `{userdata}` from `{properties}`") + def on_unsubscribe(*args, **kwargs): + print(f"Unsubscribed. Args: `{args}`, Kwargs: `{kwargs}`") - return client, userdata, mid + return args, kwargs return on_unsubscribe diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index 425aa976..dd41491d 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -11,6 +11,7 @@ from agrirouter.messaging.request import MessageRequest from agrirouter.messaging.result import MessagingResult from agrirouter.onboarding.exceptions import BadMessagingResult +from agrirouter.onboarding.response import SoftwareOnboardingResponse class AbstractMessagingClient(ABC): @@ -50,14 +51,15 @@ def send(self, parameters) -> MessagingResult: class MqttMessagingService(AbstractMessagingClient): def __init__(self, - client_id, - onboarding_response, + onboarding_response: SoftwareOnboardingResponse, on_message_callback: callable = None, ): self.onboarding_response = onboarding_response self.client = MqttClient( - client_id=client_id, + onboard_response=onboarding_response, + + client_id=onboarding_response.get_connection_criteria().get_client_id(), on_message_callback=on_message_callback, ) self.client.connect( @@ -69,7 +71,8 @@ def send(self, parameters, qos: int = 0) -> MessagingResult: message_request = self.create_message_request(parameters) mqtt_payload = message_request.json_serialize() self.client.publish( - self.onboarding_response.get_connection_criteria().get_measures(), json.dumps(mqtt_payload), + topic=self.onboarding_response.get_connection_criteria().get_measures(), + payload=json.dumps(mqtt_payload), qos=qos ) result = MessagingResult([parameters.get_application_message_id()]) diff --git a/example_script.py b/example_script.py index 0995c898..b6c05d67 100644 --- a/example_script.py +++ b/example_script.py @@ -1,3 +1,5 @@ +from agrirouter.generated.messaging.request.payload.account.endpoints_pb2 import ListEndpointsQuery + public_key = """-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzGt41/+kSOTlO1sJvLIN 6RAFaOn6GiCNX/Ju0oVT4VMDHfuQMI5t9+ZgBxFmUhtY5/eykQfYJVGac/cy5xyk @@ -38,93 +40,121 @@ -----END PRIVATE KEY-----""" -application_id = "8c947a45-c57d-42d2-affc-206e21d63a50" # # store here your application id. You can find it in AR UI +import agrirouter as ar +from agrirouter.onboarding.enums import GateWays +from agrirouter.messaging.enums import CapabilityType +from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription +from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService +from agrirouter import ListEndpointsParameters, ListEndpointsService, SubscriptionService, SubscriptionParameters +from agrirouter.utils.uuid_util import new_uuid -######################################################## -# Authorization -print("Authorization...\n") +application_id = "8c947a45-c57d-42d2-affc-206e21d63a50" # # store here your application id. You can find it in AR UI -import agrirouter as ar -auth_params = ar.AuthUrlParameter(application_id=application_id, response_type="onboard") -auth_client = ar.Authorization("QA", public_key=public_key, private_key=private_key) -auth_url = auth_client.get_auth_request_url(auth_params) # use this url to authorize the user as described at https://docs.my-agrirouter.com/agrirouter-interface-documentation/latest/integration/authorization.html#perform-authorization -print(f"auth_url={auth_url}") +def test_auth(): + print("Authorization...\n") -auth_result_url = input("Enter auth_url (the url the user was redirected to after his authorization, see above): ") # the url the user was redirected to after his authorization. -auth_response = auth_client.extract_auth_response(auth_result_url) # auth_response contains the results of the auth process -auth_client.verify_auth_response(auth_response) # you may verify auth_response to ensure answer was from AR + auth_params = ar.AuthUrlParameter(application_id=application_id, response_type="onboard") + auth_client = ar.Authorization("QA", public_key=public_key, private_key=private_key) + auth_url = auth_client.get_auth_request_url( + auth_params) # use this url to authorize the user as described at https://docs.my-agrirouter.com/agrirouter-interface-documentation/latest/integration/authorization.html#perform-authorization + print(f"auth_url={auth_url}") -print(f"auth_response is successful: {auth_response.is_successful}") # True if user accepted application, False if he rejected + auth_result_url = input( + "Enter auth_url (the url the user was redirected to after his authorization, see above): ") # the url the user was redirected to after his authorization. + auth_response = auth_client.extract_auth_response( + auth_result_url) # auth_response contains the results of the auth process + auth_client.verify_auth_response(auth_response) # you may verify auth_response to ensure answer was from AR -print(f"auth_response is valid: {auth_response.is_valid}") # Result of verification, if False, response was not validated by public key. Doesn't indicate the auth was successfull. Accessible only after response verifying + print( + f"auth_response is successful: {auth_response.is_successful}") # True if user accepted application, False if he rejected + print( + f"auth_response is valid: {auth_response.is_valid}") # Result of verification, if False, response was not validated by public key. Doesn't indicate the auth was successfull. Accessible only after response verifying -# Get dict containing data from auth process you will use for futher communication. -# If auth was rejected, contains {"error"} key. -# If auth was accepted, contains {signature, state, token, credentials{account, expires, regcode}} keys -# Even if response verifying was not processed or failed, the results will be returned. But in that case you act on your risk. -auth_data = auth_response.get_auth_result() -print(f"auth_data: {auth_data}") + # Get dict containing data from auth process you will use for futher communication. + # If auth was rejected, contains {"error"} key. + # If auth was accepted, contains {signature, state, token, credentials{account, expires, regcode}} keys + # Even if response verifying was not processed or failed, the results will be returned. But in that case you act on your risk. + auth_data = auth_response.get_auth_result() + print(f"auth_data: {auth_data}") -######################################################## + return auth_data -# Onboarding -print("Onboarding...\n") +def test_onboarding(gateway_id): -from agrirouter.onboarding.enums import GateWays + auth_data = test_auth() -id_ = "urn:myapp:snr00003234" # just unique -certification_version_id = "edd5d6b7-45bb-4471-898e-ff9c2a7bf56f" # get from AR UI -time_zone = "+03:00" + print("Onboarding...\n") -onboarding_client = ar.SoftwareOnboarding("QA", public_key=public_key, private_key=private_key) -onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, certification_version_id=certification_version_id, gateway_id=GateWays.REST.value, time_zone=time_zone, reg_code=auth_data["credentials"]["regcode"]) -onboarding_verifying_response = onboarding_client.verify(onboarding_parameters) -print(f"onboarding_verifying_response.status_code: {onboarding_verifying_response.status_code}") -print(f"onboarding_verifying_response.text: {onboarding_verifying_response.text}") -onboarding_response = onboarding_client.onboard(onboarding_parameters) -print(f"onboarding_response.status_code: {onboarding_response.status_code}") -print(f"onboarding_response.text: {onboarding_response.text}") + id_ = "urn:myapp:snr00003234" # just unique + certification_version_id = "edd5d6b7-45bb-4471-898e-ff9c2a7bf56f" # get from AR UI + time_zone = "+03:00" + onboarding_client = ar.SoftwareOnboarding("QA", public_key=public_key, private_key=private_key) + onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, + certification_version_id=certification_version_id, + gateway_id=gateway_id, time_zone=time_zone, + reg_code=auth_data.get_decoded_token().regcode) + onboarding_verifying_response = onboarding_client.verify(onboarding_parameters) + print(f"onboarding_verifying_response.status_code: {onboarding_verifying_response.status_code}") + print(f"onboarding_verifying_response.text: {onboarding_verifying_response.text}") + onboarding_response = onboarding_client.onboard(onboarding_parameters) + print(f"onboarding_response.status_code: {onboarding_response.status_code}") + print(f"onboarding_response.text: {onboarding_response.text}") -########################## -# Messaging + return onboarding_response -from agrirouter.messaging.enums import CapabilityTypeDefinitions -from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription -from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService -from agrirouter import ListEndpointsParameters, ListEndpointsService, SubscriptionService, SubscriptionParameters -from agrirouter.utils.uuid_util import new_uuid +def test_list_endpoints_mqtt(onboarding_response): + messaging_service = MqttMessagingService( + onboarding_response=onboarding_response + ) + list_endpoint_parameters = ListEndpointsParameters( + technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, + direction=ListEndpointsQuery.Direction.Value("SEND_RECEIVE"), + filtered=False, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + list_endpoint_service = ListEndpointsService(messaging_service) + messaging_result = list_endpoint_service.send(list_endpoint_parameters) + return messaging_result -# List Endpoints -messaging_service = HttpMessagingService() -list_endpoint_parameters = ListEndpointsParameters( - technical_message_type=CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value, +def test_list_endpoint_http(onboarding_response): + messaging_service = HttpMessagingService() + list_endpoint_parameters = ListEndpointsParameters( + technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, direction=2, filtered=False, onboarding_response=onboarding_response, application_message_id=new_uuid(), application_message_seq_no=1, ) -list_endpoint_service = ListEndpointsService(messaging_service) -list_endpoint_service.send(list_endpoint_parameters) + list_endpoint_service = ListEndpointsService(messaging_service) + messaging_result = list_endpoint_service.send(list_endpoint_parameters) + return messaging_result -# Subscription -messaging_service = HttpMessagingService() -subscription_service = SubscriptionService(messaging_service) +def test_subscription_http(onboarding_response): + messaging_service = HttpMessagingService() + subscription_service = SubscriptionService(messaging_service) -tmt = CapabilityTypeDefinitions.ISO_11783_TASKDATA_ZIP.value -subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) -subscription_parameters = SubscriptionParameters( + tmt = CapabilityType.ISO_11783_TASKDATA_ZIP.value + subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) + subscription_parameters = SubscriptionParameters( subscription_items=[subscription_item], onboarding_response=onboarding_response, application_message_id=new_uuid(), application_message_seq_no=1, -) -subscription_service.send(subscription_parameters) + ) + messaging_result = subscription_service.send(subscription_parameters) + return messaging_result + + +if __name__ == "__main__": + test_list_endpoints_mqtt(test_onboarding(GateWays.MQTT.value)) diff --git a/examples.txt b/examples.txt index fcb13cd4..3ca0f065 100644 --- a/examples.txt +++ b/examples.txt @@ -89,7 +89,7 @@ True >>> time_zone = "+03:00" >>> onboarding_client = ar.SoftwareOnboarding("QA", public_key=public_key, private_key=private_key) ->>> onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, certification_version_id=certification_version_id, gateway_id=GateWays.REST.value, time_zone=time_zone, reg_code=auth_data["credentials"]["regcode"]) +>>> onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, certification_version_id=certification_version_id, gateway_id=GateWays.REST.value, time_zone=time_zone, reg_code=auth_data.get_decoded_token().regcode) >>> onboarding_verifying_response = onboarding_client.verify(onboarding_parameters) >>> onboarding_verifying_response.status_code >>> onboarding_verifying_response.text From 344f7358332bf5af89538488b12bf5d6b97480ef Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 11 Nov 2021 13:25:09 +0300 Subject: [PATCH 39/55] Fix SoftwareOnboardingResponse --- agrirouter/onboarding/response.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agrirouter/onboarding/response.py b/agrirouter/onboarding/response.py index 1b9c6591..e42e14b2 100644 --- a/agrirouter/onboarding/response.py +++ b/agrirouter/onboarding/response.py @@ -79,7 +79,7 @@ def __init__(self, http_response: Response = None): commands=response_body.get("connectionCriteria").get("commands"), host=response_body.get("connectionCriteria").get("host"), port=response_body.get("connectionCriteria").get("port"), - client_id=response_body.get("connectionCriteria").get("client_id") + client_id=response_body.get("connectionCriteria").get("clientId") ) if response_body.get("connectionCriteria", None) else None self.authentication = Authentication( From dbdeb6fe00d477390fb3e92227ed4b122230b621 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 11 Nov 2021 14:21:08 +0300 Subject: [PATCH 40/55] Fix MqttClient.__init__ method --- agrirouter/messaging/clients/mqtt.py | 2 +- connection.txt | 0 my_test.py | 265 +++++++++++++++++++++++++++ tests/messaging_test/test_decode.py | 21 +++ 4 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 connection.txt create mode 100644 my_test.py create mode 100644 tests/messaging_test/test_decode.py diff --git a/agrirouter/messaging/clients/mqtt.py b/agrirouter/messaging/clients/mqtt.py index cfc4b90c..c2de3c4e 100644 --- a/agrirouter/messaging/clients/mqtt.py +++ b/agrirouter/messaging/clients/mqtt.py @@ -42,7 +42,7 @@ def __init__(self, keyfile=certificate_file_path, password=onboard_response.get_authentication().get_secret(), ) - self.mqtt_client.tls_set_context() + self.mqtt_client.tls_set_context(context) def connect(self, host: str, port: str) -> None: self.mqtt_client.connect( diff --git a/connection.txt b/connection.txt new file mode 100644 index 00000000..e69de29b diff --git a/my_test.py b/my_test.py new file mode 100644 index 00000000..27e9f4a2 --- /dev/null +++ b/my_test.py @@ -0,0 +1,265 @@ +import os +import uuid +from datetime import datetime + +from google.protobuf.timestamp_pb2 import Timestamp + +from agrirouter import ListEndpointsParameters, ListEndpointsService, SubscriptionParameters, SubscriptionService, \ + RevokingParameter, Revoking, CapabilityService, CapabilityParameters, QueryHeaderService, QueryHeaderParameters +from agrirouter.auth.auth import Authorization +from agrirouter.auth.parameters import AuthUrlParameter +from agrirouter.constants.media_types import ContentTypes +from agrirouter.generated.messaging.request.payload.account.endpoints_pb2 import ListEndpointsQuery +from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription +from agrirouter.generated.messaging.request.payload.feed.feed_requests_pb2 import ValidityPeriod +from agrirouter.messaging.builders import CapabilityBuilder +from agrirouter.messaging.clients.http import HttpClient +from agrirouter.messaging.clients.mqtt import MqttClient +from agrirouter.messaging.decode import decode_response, decode_details +from agrirouter.messaging.enums import CapabilityType +from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService +from agrirouter.messaging.services.http.outbox import OutboxService +from agrirouter.onboarding.onboarding import SoftwareOnboarding +from agrirouter.onboarding.parameters import SoftwareOnboardingParameter +from agrirouter.utils.utc_time_util import now_as_utc_str +from agrirouter.utils.uuid_util import new_uuid + +application_id = os.getenv("APPLICATION_ID", "8c947a45-c57d-42d2-affc-206e21d63a50") +ENV = os.getenv("ENVIRONMENT", "QA") +auth_result_url = "http://fuf.me/?state=99298901-2cbc-4f51-a0c2-3905e277e921&token=eyJhY2NvdW50IjoiZmIyOTIxZGUtNTkyYS00OWJhLWJlNWUtOTQwNDQ0MzBiYzk2IiwicmVnY29kZSI6IjBkMDE3NmU4NmYiLCJleHBpcmVzIjoiMjAyMS0xMC0xMlQxMzo0MjozNy40ODZaIn0%3D&signature=qeHHDV9sE6DxqcwoXm453Hbq7pJ92pWGUXwp3A13xtaNftjLX%2Ffcu8iiGV6%2Fu22g856Bw21Bxlrtyj2wCIm%2FJSmztYeitsfd99o5oSzPQ3zqm4tdDLH8qvnONRJqck7OWChVU4mArk14uVQv2ofxANGogspp1T1k51WLtPtoHBxFu6XAS3Cbm%2FpkakqalR%2FnWjAsCIlf9vGvX6oQnSX2lUDYmMCNNvzLwHYPCQgwF1vusbXE%2BAxwpBAFnZHFHPmjbTVUPXF6Hxb%2B3plwwm4tfXK9%2B7dwkZvUbKgIkcq2AYKFSTKwAns7hBjFSt6uhWK17%2Brzc%2FsarW30CZ%2BIGuuQeQ%3D%3D" +private_key = os.getenv("PRIVATE_KEY", + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDMa3jX/6RI5OU7\n" + "Wwm8sg3pEAVo6foaII1f8m7ShVPhUwMd+5Awjm335mAHEWZSG1jn97KRB9glUZpz\n" + "9zLnHKQX/XGkzOW0rul+jtdml8eOQcs9Q55odbTMT6Da+ilA4BEoTktKC4IFSF21\n" + "8W6KF5Eg/QRT4y0RAOGV+KpgsAmQC9keW4If2rqFrCfS38bzveOXOytcGoiApuhe\n" + "rLPWVERK8zKpfThfBqrUKABmMdcFVa9u46uIJHQ/afXf+eQKAGAt385HBWKVvgZA\n" + "NeSQJKC45VxFTez8ob85UlOicdWUu8PMEDkYuulkynMLgR/NcEz3yzhWPog4AhB5\n" + "jzhKmmOrAgMBAAECggEAEEr6mUCzb+nqiWYSqxsH980CmV+Yww9YJU8V3SqqSlnK\n" + "9E9SKUSY6DrQ6Y9N9/pdBjQcY+nbpPHRnS+VO41xWMYnEisQneuZCbDJ40/ypFiD\n" + "IfFrRUkobWZlXD63Hggd5fgDkTXEmbYwXemN1WzWcOopt6PyOho3YLQupEEzqerb\n" + "XkzBFWwWO9589fbWnlaSoJPtgA8gFxeJJkU3kG10Epj6wV17yo6DuyVZpemGPTUL\n" + "uVl7yNx9O/Lp8UXRlBtSEEBQqoJaGy9mzVZyobXNKvdlZxwlkbJQpZB/m4dzqbyn\n" + "Wv+lSJdmbOnOzc67FfRqHf/irIdg6aInJd6WxZ3rPQKBgQDlxrcePlzpNwJOWtXb\n" + "sneHU50Lx73u183q5dtKlH/FudhOgP4aot6+q1KDu3b9rRakGJUKQYoLgGNwhl/7\n" + "5CF0iKQE+5JZ5R9YpwFoDuALjPfic5vFN76G851ccz5pfThLjCMV1NgKJskaefP0\n" + "OdV+UW9qOIxR8UAMntWTTrQzFwKBgQDjv+2Kz1/KsXSPaw+mJKsmUnC2YbqeAr+9\n" + "Dwm7Hr0RZWkkS2EjqcMxvq0D8bYcuJvrlZFmB/r6Ly0MKlfsUT+64LAQnKHhlCUi\n" + "vlE7VuDOR16lC4ZCPeWtjrL45fpj+Lhe54m7rCT8F+Ocdxv2yNQrSBbQ6epOVuDz\n" + "XJaSRt/AjQKBgQCrBZPIS+yFnO73eP6SLixvKhnK6dmBi1h1zK3CvfK4LZJFJBd9\n" + "pdoampOo/wAa4hjm/HD6GDvyQZZB65JHfs4z2XwTRVfx1urU5kDSvbeegUcDYr7/\n" + "NHV4JpzqcdBzXcNn359BoZFHRQUL0tdz4RP5mA1QR1SRrPnaKuKWaM8Q8wKBgQC5\n" + "mY9br+PAqxzyQ61dGETh1g1ElCAg5NyclcS4WTR7GMm2ajefeJk50MnujOx8O3XV\n" + "Zu422AoQGKH9aAR+8Teec70HzJ2f17rrtW09jm9lq4PVvK6NDSQ/bCst6z1Ce07F\n" + "CKuV5ZO+XTmAKREA7Gj7XKQ7XGU1sldf+/Q5AMkXgQKBgQC4lXL9zLV/vfWUTPSR\n" + "qlGcS2+WYtjWPapDZa+7zlxGdPgOTri4nJO69Bs9ReLlzsYKSBihfpWPxcl9sS65\n" + "KFrlBkR/vzKYjCFXB6cmMP61mUrgGQRoYJQBetAyEiXZL3zjt1R/Dndk0kHkVmHr\n" + "HjmgzBRxXFy5uph6Ue6dxyszaA==\n" + "-----END PRIVATE KEY-----" + ) + +public_key = "-----BEGIN PUBLIC KEY-----\n" \ + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzGt41/+kSOTlO1sJvLIN\n" \ + "6RAFaOn6GiCNX/Ju0oVT4VMDHfuQMI5t9+ZgBxFmUhtY5/eykQfYJVGac/cy5xyk\n" \ + "F/1xpMzltK7pfo7XZpfHjkHLPUOeaHW0zE+g2vopQOARKE5LSguCBUhdtfFuiheR\n" \ + "IP0EU+MtEQDhlfiqYLAJkAvZHluCH9q6hawn0t/G873jlzsrXBqIgKboXqyz1lRE\n" \ + "SvMyqX04Xwaq1CgAZjHXBVWvbuOriCR0P2n13/nkCgBgLd/ORwVilb4GQDXkkCSg\n" \ + "uOVcRU3s/KG/OVJTonHVlLvDzBA5GLrpZMpzC4EfzXBM98s4Vj6IOAIQeY84Sppj\n" \ + "qwIDAQAB\n" \ + "-----END PUBLIC KEY-----" + +AR_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy8xF9661acn+iS+QS+9Y\n3HvTfUVcismzbuvxHgHA7YeoOUFxyj3lkaTnXm7hzQe4wDEDgwpJSGAzxIIYSUXe\n8EsWLorg5O0tRexx5SP3+kj1i83DATBJCXP7k+bAF4u2FVJphC1m2BfLxelGLjzx\nVAS/v6+EwvYaT1AI9FFqW/a2o92IsVPOh9oM9eds3lBOAbH/8XrmVIeHofw+XbTH\n1/7MLD6IE2+HbEeY0F96nioXArdQWXcjUQsTch+p0p9eqh23Ak4ef5oGcZhNd4yp\nY8M6ppvIMiXkgWSPJevCJjhxRJRmndY+ajYGx7CLePx7wNvxXWtkng3yh+7WiZ/Y\nqwIDAQAB\n-----END PUBLIC KEY-----" + +account_id = "fb2921de-592a-49ba-be5e-94044430bc96" +certification_version_id = "edd5d6b7-45bb-4471-898e-ff9c2a7bf56f" + + +verifying_response = '{"accountId":"fb2921de-592a-49ba-be5e-94044430bc96"}' +onboarding_response = '{"deviceAlternateId":"e2b512f3-9930-4461-b35f-2cdcc7f017fd","capabilityAlternateId":"523e4623-68d2-43d4-a0cc-e2ada2f68b5e","sensorAlternateId":"185cd97b-ed0b-4e75-a6e2-6be1cdd38a06","connectionCriteria":{"gatewayId":"3","measures":"https://dke-qa.eu10.cp.iot.sap/iot/gateway/rest/measures/e2b512f3-9930-4461-b35f-2cdcc7f017fd","commands":"https://dke-qa.eu10.cp.iot.sap/iot/gateway/rest/commands/e2b512f3-9930-4461-b35f-2cdcc7f017fd"},"authentication":{"type":"PEM","secret":"VXHfXV7g#3COYT1Q5YkTxrRXo1IjdN8xSZ3O","certificate":"-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIE6zAdBgoqhkiG9w0BDAEDMA8ECHlX0q4qf0tSAgMCAAAEggTI4XIOXehAN02J\nwUrHPe2BCGEqXvyA2QzWMBkqYRm7dCmhL/ay1JzJkkDf2afS/1FE0Sl3So1AMKBe\nhpqZUpaz5g7tN1ktl9BReXC+pptJPpy3XSpZ/8Bg3Jje4eYn2aCWMSNbnw0SO9Sf\nZnUZrNwQH9GSR35fGHYPaYHmAIM9axaUSn6/LfsrifdORY38r9EKvrcuIYFJ18ai\ngHoCAboaPLY52m/UkaM0WNnpTrHm/72G8978jpZKYZmbmp7/qdB7+aQ+WZWs4u/V\nCR6vzgkyWaFzQK5GMKCMBgHteq5900FY9Iz/ZBS/gyoHoVdMsRKRBSXfNebdvADC\nkksZBfaYqMI58CFEuVODi7gD+YKcu7/5BjX8DJ72eDFaYa1ZIC+na24gel8x85UF\n+TwFSQ4NgHmqcUkZJOyRtcnMREP79ZkdGXi4l6eZk4hG9KhfM66HgcvIzGT6e1SF\nJrKcLTVYUdYSyhLZmk7DflgI5VoCKX1/P1O0iiyWqsbqhjfsnfTpaCaveMb+c/2z\nSaw3tc5G6th/QdN72wZTM1Vqb4562JEpxvkxY4i2PW1Ky9HNY0M+jy0DL/KhfnM9\nm8abxdgILTu3WcxnfH7f8uiK2R9zgnIf+CCDtGGWftOGgV5MY4t0XLAsGSadNdqH\nrpTguI4XMcQET0ZEE7fTbkvJ2+QVWz3vD8w4/ryZx12ZzQCoewd5nPQD/JgLoo2R\n8Pdp2TQDYrn0PYZHus1GnPL7kAs34gl1zFNxFF8nYSSP0hITc2zWm8XuLyMc8fgs\nymXZPdTz9LQZPqs5t+P0gL04xegaXiYWWAhZFkMx9/0+zeoNK+w28cTsLRC4oQcJ\nkaq69f7gHIg3ar75Zzzc1xUEgz50oJI2BCDVLllHzoAWFjl5t/9hGcBirP3QOKwg\nCKfHKnbLkSS+2omhp3zBecX7moDS6+RcMMIUvMbHHLx8l4uv/cDtWIh++I21hzQu\ntSaIK6gglGE0OZJN3tPmy3CdHqbapBWvfCMGD2J2xISJCoSwmR58fni4pyniTydt\nJH040YpDUuUxIDlVTFAsmMRm5vh2KkE4DVr1UqA+JJgHRuQiuVbprm/QU2bumTG+\n4vhOKcaSgbQW3++NtvfVSymz5/IJXOMQIzprokvC+6GwiRUBIov7angWwUB9X5iC\nN0rfJ5MbNkabXsFRQpbXYV7z/P+t/9A8A5LBr+DiPLib9i3WI4sHV5aLZapJ3R4u\neBCVB+VIGbcTM19t51h4ohZhY2Q9CYXVnle7ol0Nz/mUiX0Az9oF6GQCngMFdqMh\nftl2XdCq43AZA7hHP+wqKkjOo7i5Lr0IRWw5F7IexV8mHDwx560DJkp0bZM9+UxA\nu1JcTLDJR+a/aOsx5CDSWig+W3XNCQfC4kVhNUlWZ1yQ8Heh+NB3kD+Krvby40DL\nzNOe6VSkFCjKn2st1yJkdcdhLs8mPE1DEk7WRFS+AMaIgXIGpdoBIlUwHpwP7djd\n8gC9e3kFTAazudZkTCi8QnhAeH1coxVhB6+WbzVnFzJDZuy65DiVDSmOjlHmxF7A\nZc5LCo1vmtER3d08bjuz+33dCGS2yKg2Q7Nd3rymUlGzPEx+dPFmvXaDWNXghdbj\nc9PfhpiX5tt83tHe7E2C\n-----END ENCRYPTED PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\nMIIEaTCCA1GgAwIBAgIQAPktOgtD/4tlEAEHAL6qbTANBgkqhkiG9w0BAQsFADBW\nMQswCQYDVQQGEwJERTEjMCEGA1UEChMaU0FQIElvVCBUcnVzdCBDb21tdW5pdHkg\nSUkxIjAgBgNVBAMTGVNBUCBJbnRlcm5ldCBvZiBUaGluZ3MgQ0EwHhcNMjExMDI1\nMTk0MzE1WhcNMjIxMDI1MTk0MzE1WjCBtTELMAkGA1UEBhMCREUxHDAaBgNVBAoT\nE1NBUCBUcnVzdCBDb21tdW5pdHkxFTATBgNVBAsTDElvVCBTZXJ2aWNlczFxMG8G\nA1UEAxRoZGV2aWNlQWx0ZXJuYXRlSWQ6ZTJiNTEyZjMtOTkzMC00NDYxLWIzNWYt\nMmNkY2M3ZjAxN2ZkfGdhdGV3YXlJZDozfHRlbmFudElkOjExMTY5MDM0OTB8aW5z\ndGFuY2VJZDpka2UtcWEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCY\nRAKTeVe2Eo4EtV1QJi1m3gZDpAAXYYhGo8905yw4XZD9M2fCyMbUVcoAm/5lGN+W\nsMk/GsfNeBRmd80SLv6/Z7342tVryhslkGL0TVw2MHMw+1cEAPsH6EthrvH6poTs\ngGDtUB4ad2BOvBwveTPpHwdWxyDUvb74mXwXZ9XgIo/VJKSEr6DlO+zv52BUXRh9\nS70m4dgM0aTM/iRYITrPPXHZfY91M9lsypp64m1dHzDXiQaWvFqaiyIOw/IO2V+O\nMmz3U1Q6L/8ai4WNeTTX69hprOPDTCG5WLdnDviK9hx1w6tOyRdKun7LpklZ14Rv\nApZXATxFwxrNQm2iiFbfAgMBAAGjgdIwgc8wSAYDVR0fBEEwPzA9oDugOYY3aHR0\ncHM6Ly90Y3MubXlzYXAuY29tL2NybC9UcnVzdENvbW11bml0eUlJL1NBUElvVENB\nLmNybDAMBgNVHRMBAf8EAjAAMCUGA1UdEgQeMByGGmh0dHA6Ly9zZXJ2aWNlLnNh\ncC5jb20vVENTMA4GA1UdDwEB/wQEAwIGwDAdBgNVHQ4EFgQUDG1OiFv4Ohku4sG+\n6vC9+x3nCGQwHwYDVR0jBBgwFoAUlbez9Vje1bSzWEbg8qbJeE69LXUwDQYJKoZI\nhvcNAQELBQADggEBADgzaWG5+ch1iQ6tHOHO8/sVBpQJ0kEnHCxDeJ1WRL6Qas/n\nMZPMwzmllsUhQv93fVElGosy2sMjCanCLnCh8qwb85vq7exEZBccbmUo6Epqz9XO\n/NJ4Fr1OWLtE9svRM5s0QEB6B9oQ1OjZtdjeGI9/uQSJgmzYKdI/HAFkTTugokRU\nkyr+rM6Rv9KCNbkzoNTRS6xDNs64FxEw53FBYitmtnsgXAdWPjHpkoZFIntstuFr\nVwpdxeH1TZmdvwhtImibcqGHgUqa7r1lySbK+sEdFzQcf7Ea1dRJR3r1ZfG1/ALn\nRInsXoCBNxyllk6ExpQWiczLiOY5jXnQulX51+k=\n-----END CERTIFICATE-----\n"}}' + + +message_result = b'[{"sensorAlternateId":"185cd97b-ed0b-4e75-a6e2-6be1cdd38a06","capabilityAlternateId":"bbe9f361-b551-48d9-9fca-1b4dc768287c","command":{"message":"XwjIARAKGiQ5NWUzNWE0Zi1jNWM4LTQ1NDEtODE4OS03NmJlMzM0OTc0NDUiJDUzNzYyM2ZjLWY2NmYtNDc5Yi1hMmJhLWVjZjNlNWM3ZjhlMCoMCNTV5YsGEICI8LIDzQIKygIKTnR5cGVzLmFncmlyb3V0ZXIuY29tL2Fncmlyb3V0ZXIucmVzcG9uc2UucGF5bG9hZC5hY2NvdW50Lkxpc3RFbmRwb2ludHNSZXNwb25zZRL3AQp4CiRkNzA0YTQ0My05OWY3LTQ3YjQtYmU1NS1lMmZhMDk2ODllYmUSJFB5dGhvblNES19kZXYgLSAyMDIxLTEwLTI1LCAxMDo1MToxOBoLYXBwbGljYXRpb24iBmFjdGl2ZTIVdXJuOm15YXBwOnNucjAwMDAzMjM0CnsKJDE4NWNkOTdiLWVkMGItNGU3NS1hNmUyLTZiZTFjZGQzOGEwNhIkUHl0aG9uU0RLX2RldiAtIDIwMjEtMTAtMjEsIDIxOjQxOjI0GgthcHBsaWNhdGlvbiIGYWN0aXZlMhh1cm46bXlhcHA6c25yMDAwMDMyMzRzZGY="}}]' + + + +def test_onboarding(): + from agrirouter.onboarding.enums import GateWays + import agrirouter as ar + auth_params = ar.AuthUrlParameter(application_id=application_id, response_type="onboard") + auth_client = ar.Authorization("QA", public_key=public_key, private_key=private_key) + auth_url = auth_client.get_auth_request_url(auth_params) + + print(auth_url) + + auth_result_url = input("Entrer url: ") # the url the user was redirected after his authorization. + + auth_response = auth_client.extract_auth_response(auth_result_url) + auth_client.verify_auth_response(auth_response) + auth_response.is_successful + auth_response.is_valid + auth_data = auth_response.get_auth_result() + + id_ = "urn:myapp:snr00003234sdf" + certification_version_id = "edd5d6b7-45bb-4471-898e-ff9c2a7bf56f" + time_zone = "+03:00" + onboarding_client = ar.SoftwareOnboarding("QA", public_key=public_key, private_key=private_key) + onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, + certification_version_id=certification_version_id, + gateway_id=GateWays.MQTT.value, + time_zone=time_zone, + reg_code=auth_data.decoded_token.regcode, + utc_timestamp=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z") + onboarding_verifying_response = onboarding_client.verify(onboarding_parameters) + onboarding_verifying_response.status_code + onboarding_verifying_response.text + print(onboarding_verifying_response.text) + onboarding_response = onboarding_client.onboard(onboarding_parameters) + print(onboarding_response.text) + + return onboarding_response + + +def test_capability_service_http(onboarding_response): + messaging_service = HttpMessagingService() + capability_parameters = CapabilityParameters( + application_id=application_id, + certification_version_id=certification_version_id, + enable_push_notification=1, + capability_parameters=CapabilityBuilder().with_task_data(2).build(), + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + capability_service = CapabilityService(messaging_service) + messaging_result = capability_service.send(capability_parameters) + return messaging_result + + +def test_list_endpoint_service_http(onboarding_response): + messaging_service = HttpMessagingService() + list_endpoint_parameters = ListEndpointsParameters( + technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, + direction=ListEndpointsQuery.Direction.Value("SEND_RECEIVE"), + filtered=False, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + list_endpoint_service = ListEndpointsService(messaging_service) + messaging_result = list_endpoint_service.send(list_endpoint_parameters) + return messaging_result + + +def foo(client, userdata, message): + print(client, userdata, message) + return client, userdata, message + + +def test_list_endpoint_service_mqtt(onboarding_response): + messaging_service = MqttMessagingService( + on_message_callback=foo, + onboarding_response=onboarding_response + ) + list_endpoint_parameters = ListEndpointsParameters( + technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, + direction=ListEndpointsQuery.Direction.Value("SEND_RECEIVE"), + filtered=False, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + list_endpoint_service = ListEndpointsService(messaging_service) + messaging_result = list_endpoint_service.send(list_endpoint_parameters) + return messaging_result + + +def test_valid_subscription_service_http(onboarding_response): + messaging_service = HttpMessagingService() + subscription_service = SubscriptionService(messaging_service) + items = [] + for tmt in [CapabilityType.DOC_PDF.value, CapabilityType.ISO_11783_TASKDATA_ZIP.value]: + subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) + items.append(subscription_item) + subscription_parameters = SubscriptionParameters( + subscription_items=items, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = subscription_service.send(subscription_parameters) + return messaging_result + + +def test_query_header_message_http(onboarding_response): + messaging_service = HttpMessagingService() + query_header_service = QueryHeaderService(messaging_service) + sent_from = Timestamp() + sent_to = Timestamp() + validity_period = ValidityPeriod(sent_from=sent_from, sent_to=sent_to) + query_header_parameters = QueryHeaderParameters( + message_ids=[new_uuid(), new_uuid()], + senders=[new_uuid(), new_uuid()], + validity_period=validity_period, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + return messaging_result + + +def get_outbox(onboarding_response): + outbox_service = OutboxService() + outbox_response = outbox_service.fetch(onboarding_response) + # assert 200 == outbox_response.status_code + + messages = outbox_response.messages + # assert len(messages) == 1 + # assert messages[0].command.message + + decoded_message = decode_response(outbox_response.messages[0].command.message) + # assert 204 == decoded_message.response_envelope.response_code + + decoded_details = decode_details(decoded_message.response_payload.details) + # assert decoded_details + + # query_metrics = decoded_details.query_metrics + # assert 0 == query_metrics.total_messages_in_query + + return messages, decoded_message, decoded_details + + +def test_outbox_service_http(onboarding_response): + outbox_service = OutboxService() + result = outbox_service.fetch(onboarding_response) + return result + + +def test_revoke(): + params = RevokingParameter( + application_id=application_id, + account_id=account_id, + endpoint_ids=[ + "849ff5b9-6b3a-418e-9394-931e19acb8ec", + "b2157913-013f-486c-bf25-a591ffca451b" + ], + utc_timestamp=now_as_utc_str(), + time_zone="+03:00", + content_type=ContentTypes.APPLICATION_JSON.value + ) + service = Revoking("QA", public_key=public_key, private_key=private_key) + return service.revoke(params) + + +def test(): + onboarding_response = test_onboarding() + # print(test_list_endpoint_service_http(onboarding_response).get_messages_ids()) + # print(test_valid_subscription_service_http(onboarding_response).get_messages_ids()) + # print(test_capability_service_http(onboarding_response).get_messages_ids()) + # print(test_list_endpoint_service_http(onboarding_response).get_messages_ids()) + messaging_result = test_list_endpoint_service_mqtt(onboarding_response) + return messaging_result + + +if __name__ == "__main__": + test() diff --git a/tests/messaging_test/test_decode.py b/tests/messaging_test/test_decode.py new file mode 100644 index 00000000..77d62143 --- /dev/null +++ b/tests/messaging_test/test_decode.py @@ -0,0 +1,21 @@ +import json + +import pytest + +from agrirouter.messaging.decode import decode_response +from agrirouter.messaging.decode import decode_details + + +MESSAGING_RESULT = b'[{"sensorAlternateId":"185cd97b-ed0b-4e75-a6e2-6be1cdd38a06","capabilityAlternateId":"bbe9f361-b551-48d9-9fca-1b4dc768287c","command":{"message":"XwjIARAKGiQ5NWUzNWE0Zi1jNWM4LTQ1NDEtODE4OS03NmJlMzM0OTc0NDUiJDUzNzYyM2ZjLWY2NmYtNDc5Yi1hMmJhLWVjZjNlNWM3ZjhlMCoMCNTV5YsGEICI8LIDzQIKygIKTnR5cGVzLmFncmlyb3V0ZXIuY29tL2Fncmlyb3V0ZXIucmVzcG9uc2UucGF5bG9hZC5hY2NvdW50Lkxpc3RFbmRwb2ludHNSZXNwb25zZRL3AQp4CiRkNzA0YTQ0My05OWY3LTQ3YjQtYmU1NS1lMmZhMDk2ODllYmUSJFB5dGhvblNES19kZXYgLSAyMDIxLTEwLTI1LCAxMDo1MToxOBoLYXBwbGljYXRpb24iBmFjdGl2ZTIVdXJuOm15YXBwOnNucjAwMDAzMjM0CnsKJDE4NWNkOTdiLWVkMGItNGU3NS1hNmUyLTZiZTFjZGQzOGEwNhIkUHl0aG9uU0RLX2RldiAtIDIwMjEtMTAtMjEsIDIxOjQxOjI0GgthcHBsaWNhdGlvbiIGYWN0aXZlMhh1cm46bXlhcHA6c25yMDAwMDMyMzRzZGY="}}]' + + +def test_decode_response(): + pass + + +def test_decode_details(): + json_response = json.loads(MESSAGING_RESULT) + message = decode_response(json_response[0]["command"]["message"].encode()) + decoded_details = decode_details(message.response_payload.details) + print(decoded_details) + assert False From 8558c45b96fa1a73b732b67b98cda4e65b6b3108 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Thu, 11 Nov 2021 15:33:42 +0300 Subject: [PATCH 41/55] Remove redundant files --- agrirouter/messaging/clients/mqtt.py | 1 - connection.txt | 0 my_test.py | 265 --------------------------- 3 files changed, 266 deletions(-) delete mode 100644 connection.txt delete mode 100644 my_test.py diff --git a/agrirouter/messaging/clients/mqtt.py b/agrirouter/messaging/clients/mqtt.py index 1b5183f8..8c0ccaf9 100644 --- a/agrirouter/messaging/clients/mqtt.py +++ b/agrirouter/messaging/clients/mqtt.py @@ -121,7 +121,6 @@ def on_message(client, userdata, msg): @staticmethod def _get_on_subscribe_callback() -> callable: - # def on_subscribe(client, userdata, mid, granted_qos, properties=None): def on_subscribe(*args, **kwargs): print(f"Subscribed. Args: `{args}`, Kwargs: `{kwargs}`") diff --git a/connection.txt b/connection.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/my_test.py b/my_test.py deleted file mode 100644 index 27e9f4a2..00000000 --- a/my_test.py +++ /dev/null @@ -1,265 +0,0 @@ -import os -import uuid -from datetime import datetime - -from google.protobuf.timestamp_pb2 import Timestamp - -from agrirouter import ListEndpointsParameters, ListEndpointsService, SubscriptionParameters, SubscriptionService, \ - RevokingParameter, Revoking, CapabilityService, CapabilityParameters, QueryHeaderService, QueryHeaderParameters -from agrirouter.auth.auth import Authorization -from agrirouter.auth.parameters import AuthUrlParameter -from agrirouter.constants.media_types import ContentTypes -from agrirouter.generated.messaging.request.payload.account.endpoints_pb2 import ListEndpointsQuery -from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription -from agrirouter.generated.messaging.request.payload.feed.feed_requests_pb2 import ValidityPeriod -from agrirouter.messaging.builders import CapabilityBuilder -from agrirouter.messaging.clients.http import HttpClient -from agrirouter.messaging.clients.mqtt import MqttClient -from agrirouter.messaging.decode import decode_response, decode_details -from agrirouter.messaging.enums import CapabilityType -from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService -from agrirouter.messaging.services.http.outbox import OutboxService -from agrirouter.onboarding.onboarding import SoftwareOnboarding -from agrirouter.onboarding.parameters import SoftwareOnboardingParameter -from agrirouter.utils.utc_time_util import now_as_utc_str -from agrirouter.utils.uuid_util import new_uuid - -application_id = os.getenv("APPLICATION_ID", "8c947a45-c57d-42d2-affc-206e21d63a50") -ENV = os.getenv("ENVIRONMENT", "QA") -auth_result_url = "http://fuf.me/?state=99298901-2cbc-4f51-a0c2-3905e277e921&token=eyJhY2NvdW50IjoiZmIyOTIxZGUtNTkyYS00OWJhLWJlNWUtOTQwNDQ0MzBiYzk2IiwicmVnY29kZSI6IjBkMDE3NmU4NmYiLCJleHBpcmVzIjoiMjAyMS0xMC0xMlQxMzo0MjozNy40ODZaIn0%3D&signature=qeHHDV9sE6DxqcwoXm453Hbq7pJ92pWGUXwp3A13xtaNftjLX%2Ffcu8iiGV6%2Fu22g856Bw21Bxlrtyj2wCIm%2FJSmztYeitsfd99o5oSzPQ3zqm4tdDLH8qvnONRJqck7OWChVU4mArk14uVQv2ofxANGogspp1T1k51WLtPtoHBxFu6XAS3Cbm%2FpkakqalR%2FnWjAsCIlf9vGvX6oQnSX2lUDYmMCNNvzLwHYPCQgwF1vusbXE%2BAxwpBAFnZHFHPmjbTVUPXF6Hxb%2B3plwwm4tfXK9%2B7dwkZvUbKgIkcq2AYKFSTKwAns7hBjFSt6uhWK17%2Brzc%2FsarW30CZ%2BIGuuQeQ%3D%3D" -private_key = os.getenv("PRIVATE_KEY", - "-----BEGIN PRIVATE KEY-----\n" - "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDMa3jX/6RI5OU7\n" - "Wwm8sg3pEAVo6foaII1f8m7ShVPhUwMd+5Awjm335mAHEWZSG1jn97KRB9glUZpz\n" - "9zLnHKQX/XGkzOW0rul+jtdml8eOQcs9Q55odbTMT6Da+ilA4BEoTktKC4IFSF21\n" - "8W6KF5Eg/QRT4y0RAOGV+KpgsAmQC9keW4If2rqFrCfS38bzveOXOytcGoiApuhe\n" - "rLPWVERK8zKpfThfBqrUKABmMdcFVa9u46uIJHQ/afXf+eQKAGAt385HBWKVvgZA\n" - "NeSQJKC45VxFTez8ob85UlOicdWUu8PMEDkYuulkynMLgR/NcEz3yzhWPog4AhB5\n" - "jzhKmmOrAgMBAAECggEAEEr6mUCzb+nqiWYSqxsH980CmV+Yww9YJU8V3SqqSlnK\n" - "9E9SKUSY6DrQ6Y9N9/pdBjQcY+nbpPHRnS+VO41xWMYnEisQneuZCbDJ40/ypFiD\n" - "IfFrRUkobWZlXD63Hggd5fgDkTXEmbYwXemN1WzWcOopt6PyOho3YLQupEEzqerb\n" - "XkzBFWwWO9589fbWnlaSoJPtgA8gFxeJJkU3kG10Epj6wV17yo6DuyVZpemGPTUL\n" - "uVl7yNx9O/Lp8UXRlBtSEEBQqoJaGy9mzVZyobXNKvdlZxwlkbJQpZB/m4dzqbyn\n" - "Wv+lSJdmbOnOzc67FfRqHf/irIdg6aInJd6WxZ3rPQKBgQDlxrcePlzpNwJOWtXb\n" - "sneHU50Lx73u183q5dtKlH/FudhOgP4aot6+q1KDu3b9rRakGJUKQYoLgGNwhl/7\n" - "5CF0iKQE+5JZ5R9YpwFoDuALjPfic5vFN76G851ccz5pfThLjCMV1NgKJskaefP0\n" - "OdV+UW9qOIxR8UAMntWTTrQzFwKBgQDjv+2Kz1/KsXSPaw+mJKsmUnC2YbqeAr+9\n" - "Dwm7Hr0RZWkkS2EjqcMxvq0D8bYcuJvrlZFmB/r6Ly0MKlfsUT+64LAQnKHhlCUi\n" - "vlE7VuDOR16lC4ZCPeWtjrL45fpj+Lhe54m7rCT8F+Ocdxv2yNQrSBbQ6epOVuDz\n" - "XJaSRt/AjQKBgQCrBZPIS+yFnO73eP6SLixvKhnK6dmBi1h1zK3CvfK4LZJFJBd9\n" - "pdoampOo/wAa4hjm/HD6GDvyQZZB65JHfs4z2XwTRVfx1urU5kDSvbeegUcDYr7/\n" - "NHV4JpzqcdBzXcNn359BoZFHRQUL0tdz4RP5mA1QR1SRrPnaKuKWaM8Q8wKBgQC5\n" - "mY9br+PAqxzyQ61dGETh1g1ElCAg5NyclcS4WTR7GMm2ajefeJk50MnujOx8O3XV\n" - "Zu422AoQGKH9aAR+8Teec70HzJ2f17rrtW09jm9lq4PVvK6NDSQ/bCst6z1Ce07F\n" - "CKuV5ZO+XTmAKREA7Gj7XKQ7XGU1sldf+/Q5AMkXgQKBgQC4lXL9zLV/vfWUTPSR\n" - "qlGcS2+WYtjWPapDZa+7zlxGdPgOTri4nJO69Bs9ReLlzsYKSBihfpWPxcl9sS65\n" - "KFrlBkR/vzKYjCFXB6cmMP61mUrgGQRoYJQBetAyEiXZL3zjt1R/Dndk0kHkVmHr\n" - "HjmgzBRxXFy5uph6Ue6dxyszaA==\n" - "-----END PRIVATE KEY-----" - ) - -public_key = "-----BEGIN PUBLIC KEY-----\n" \ - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzGt41/+kSOTlO1sJvLIN\n" \ - "6RAFaOn6GiCNX/Ju0oVT4VMDHfuQMI5t9+ZgBxFmUhtY5/eykQfYJVGac/cy5xyk\n" \ - "F/1xpMzltK7pfo7XZpfHjkHLPUOeaHW0zE+g2vopQOARKE5LSguCBUhdtfFuiheR\n" \ - "IP0EU+MtEQDhlfiqYLAJkAvZHluCH9q6hawn0t/G873jlzsrXBqIgKboXqyz1lRE\n" \ - "SvMyqX04Xwaq1CgAZjHXBVWvbuOriCR0P2n13/nkCgBgLd/ORwVilb4GQDXkkCSg\n" \ - "uOVcRU3s/KG/OVJTonHVlLvDzBA5GLrpZMpzC4EfzXBM98s4Vj6IOAIQeY84Sppj\n" \ - "qwIDAQAB\n" \ - "-----END PUBLIC KEY-----" - -AR_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy8xF9661acn+iS+QS+9Y\n3HvTfUVcismzbuvxHgHA7YeoOUFxyj3lkaTnXm7hzQe4wDEDgwpJSGAzxIIYSUXe\n8EsWLorg5O0tRexx5SP3+kj1i83DATBJCXP7k+bAF4u2FVJphC1m2BfLxelGLjzx\nVAS/v6+EwvYaT1AI9FFqW/a2o92IsVPOh9oM9eds3lBOAbH/8XrmVIeHofw+XbTH\n1/7MLD6IE2+HbEeY0F96nioXArdQWXcjUQsTch+p0p9eqh23Ak4ef5oGcZhNd4yp\nY8M6ppvIMiXkgWSPJevCJjhxRJRmndY+ajYGx7CLePx7wNvxXWtkng3yh+7WiZ/Y\nqwIDAQAB\n-----END PUBLIC KEY-----" - -account_id = "fb2921de-592a-49ba-be5e-94044430bc96" -certification_version_id = "edd5d6b7-45bb-4471-898e-ff9c2a7bf56f" - - -verifying_response = '{"accountId":"fb2921de-592a-49ba-be5e-94044430bc96"}' -onboarding_response = '{"deviceAlternateId":"e2b512f3-9930-4461-b35f-2cdcc7f017fd","capabilityAlternateId":"523e4623-68d2-43d4-a0cc-e2ada2f68b5e","sensorAlternateId":"185cd97b-ed0b-4e75-a6e2-6be1cdd38a06","connectionCriteria":{"gatewayId":"3","measures":"https://dke-qa.eu10.cp.iot.sap/iot/gateway/rest/measures/e2b512f3-9930-4461-b35f-2cdcc7f017fd","commands":"https://dke-qa.eu10.cp.iot.sap/iot/gateway/rest/commands/e2b512f3-9930-4461-b35f-2cdcc7f017fd"},"authentication":{"type":"PEM","secret":"VXHfXV7g#3COYT1Q5YkTxrRXo1IjdN8xSZ3O","certificate":"-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIE6zAdBgoqhkiG9w0BDAEDMA8ECHlX0q4qf0tSAgMCAAAEggTI4XIOXehAN02J\nwUrHPe2BCGEqXvyA2QzWMBkqYRm7dCmhL/ay1JzJkkDf2afS/1FE0Sl3So1AMKBe\nhpqZUpaz5g7tN1ktl9BReXC+pptJPpy3XSpZ/8Bg3Jje4eYn2aCWMSNbnw0SO9Sf\nZnUZrNwQH9GSR35fGHYPaYHmAIM9axaUSn6/LfsrifdORY38r9EKvrcuIYFJ18ai\ngHoCAboaPLY52m/UkaM0WNnpTrHm/72G8978jpZKYZmbmp7/qdB7+aQ+WZWs4u/V\nCR6vzgkyWaFzQK5GMKCMBgHteq5900FY9Iz/ZBS/gyoHoVdMsRKRBSXfNebdvADC\nkksZBfaYqMI58CFEuVODi7gD+YKcu7/5BjX8DJ72eDFaYa1ZIC+na24gel8x85UF\n+TwFSQ4NgHmqcUkZJOyRtcnMREP79ZkdGXi4l6eZk4hG9KhfM66HgcvIzGT6e1SF\nJrKcLTVYUdYSyhLZmk7DflgI5VoCKX1/P1O0iiyWqsbqhjfsnfTpaCaveMb+c/2z\nSaw3tc5G6th/QdN72wZTM1Vqb4562JEpxvkxY4i2PW1Ky9HNY0M+jy0DL/KhfnM9\nm8abxdgILTu3WcxnfH7f8uiK2R9zgnIf+CCDtGGWftOGgV5MY4t0XLAsGSadNdqH\nrpTguI4XMcQET0ZEE7fTbkvJ2+QVWz3vD8w4/ryZx12ZzQCoewd5nPQD/JgLoo2R\n8Pdp2TQDYrn0PYZHus1GnPL7kAs34gl1zFNxFF8nYSSP0hITc2zWm8XuLyMc8fgs\nymXZPdTz9LQZPqs5t+P0gL04xegaXiYWWAhZFkMx9/0+zeoNK+w28cTsLRC4oQcJ\nkaq69f7gHIg3ar75Zzzc1xUEgz50oJI2BCDVLllHzoAWFjl5t/9hGcBirP3QOKwg\nCKfHKnbLkSS+2omhp3zBecX7moDS6+RcMMIUvMbHHLx8l4uv/cDtWIh++I21hzQu\ntSaIK6gglGE0OZJN3tPmy3CdHqbapBWvfCMGD2J2xISJCoSwmR58fni4pyniTydt\nJH040YpDUuUxIDlVTFAsmMRm5vh2KkE4DVr1UqA+JJgHRuQiuVbprm/QU2bumTG+\n4vhOKcaSgbQW3++NtvfVSymz5/IJXOMQIzprokvC+6GwiRUBIov7angWwUB9X5iC\nN0rfJ5MbNkabXsFRQpbXYV7z/P+t/9A8A5LBr+DiPLib9i3WI4sHV5aLZapJ3R4u\neBCVB+VIGbcTM19t51h4ohZhY2Q9CYXVnle7ol0Nz/mUiX0Az9oF6GQCngMFdqMh\nftl2XdCq43AZA7hHP+wqKkjOo7i5Lr0IRWw5F7IexV8mHDwx560DJkp0bZM9+UxA\nu1JcTLDJR+a/aOsx5CDSWig+W3XNCQfC4kVhNUlWZ1yQ8Heh+NB3kD+Krvby40DL\nzNOe6VSkFCjKn2st1yJkdcdhLs8mPE1DEk7WRFS+AMaIgXIGpdoBIlUwHpwP7djd\n8gC9e3kFTAazudZkTCi8QnhAeH1coxVhB6+WbzVnFzJDZuy65DiVDSmOjlHmxF7A\nZc5LCo1vmtER3d08bjuz+33dCGS2yKg2Q7Nd3rymUlGzPEx+dPFmvXaDWNXghdbj\nc9PfhpiX5tt83tHe7E2C\n-----END ENCRYPTED PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\nMIIEaTCCA1GgAwIBAgIQAPktOgtD/4tlEAEHAL6qbTANBgkqhkiG9w0BAQsFADBW\nMQswCQYDVQQGEwJERTEjMCEGA1UEChMaU0FQIElvVCBUcnVzdCBDb21tdW5pdHkg\nSUkxIjAgBgNVBAMTGVNBUCBJbnRlcm5ldCBvZiBUaGluZ3MgQ0EwHhcNMjExMDI1\nMTk0MzE1WhcNMjIxMDI1MTk0MzE1WjCBtTELMAkGA1UEBhMCREUxHDAaBgNVBAoT\nE1NBUCBUcnVzdCBDb21tdW5pdHkxFTATBgNVBAsTDElvVCBTZXJ2aWNlczFxMG8G\nA1UEAxRoZGV2aWNlQWx0ZXJuYXRlSWQ6ZTJiNTEyZjMtOTkzMC00NDYxLWIzNWYt\nMmNkY2M3ZjAxN2ZkfGdhdGV3YXlJZDozfHRlbmFudElkOjExMTY5MDM0OTB8aW5z\ndGFuY2VJZDpka2UtcWEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCY\nRAKTeVe2Eo4EtV1QJi1m3gZDpAAXYYhGo8905yw4XZD9M2fCyMbUVcoAm/5lGN+W\nsMk/GsfNeBRmd80SLv6/Z7342tVryhslkGL0TVw2MHMw+1cEAPsH6EthrvH6poTs\ngGDtUB4ad2BOvBwveTPpHwdWxyDUvb74mXwXZ9XgIo/VJKSEr6DlO+zv52BUXRh9\nS70m4dgM0aTM/iRYITrPPXHZfY91M9lsypp64m1dHzDXiQaWvFqaiyIOw/IO2V+O\nMmz3U1Q6L/8ai4WNeTTX69hprOPDTCG5WLdnDviK9hx1w6tOyRdKun7LpklZ14Rv\nApZXATxFwxrNQm2iiFbfAgMBAAGjgdIwgc8wSAYDVR0fBEEwPzA9oDugOYY3aHR0\ncHM6Ly90Y3MubXlzYXAuY29tL2NybC9UcnVzdENvbW11bml0eUlJL1NBUElvVENB\nLmNybDAMBgNVHRMBAf8EAjAAMCUGA1UdEgQeMByGGmh0dHA6Ly9zZXJ2aWNlLnNh\ncC5jb20vVENTMA4GA1UdDwEB/wQEAwIGwDAdBgNVHQ4EFgQUDG1OiFv4Ohku4sG+\n6vC9+x3nCGQwHwYDVR0jBBgwFoAUlbez9Vje1bSzWEbg8qbJeE69LXUwDQYJKoZI\nhvcNAQELBQADggEBADgzaWG5+ch1iQ6tHOHO8/sVBpQJ0kEnHCxDeJ1WRL6Qas/n\nMZPMwzmllsUhQv93fVElGosy2sMjCanCLnCh8qwb85vq7exEZBccbmUo6Epqz9XO\n/NJ4Fr1OWLtE9svRM5s0QEB6B9oQ1OjZtdjeGI9/uQSJgmzYKdI/HAFkTTugokRU\nkyr+rM6Rv9KCNbkzoNTRS6xDNs64FxEw53FBYitmtnsgXAdWPjHpkoZFIntstuFr\nVwpdxeH1TZmdvwhtImibcqGHgUqa7r1lySbK+sEdFzQcf7Ea1dRJR3r1ZfG1/ALn\nRInsXoCBNxyllk6ExpQWiczLiOY5jXnQulX51+k=\n-----END CERTIFICATE-----\n"}}' - - -message_result = b'[{"sensorAlternateId":"185cd97b-ed0b-4e75-a6e2-6be1cdd38a06","capabilityAlternateId":"bbe9f361-b551-48d9-9fca-1b4dc768287c","command":{"message":"XwjIARAKGiQ5NWUzNWE0Zi1jNWM4LTQ1NDEtODE4OS03NmJlMzM0OTc0NDUiJDUzNzYyM2ZjLWY2NmYtNDc5Yi1hMmJhLWVjZjNlNWM3ZjhlMCoMCNTV5YsGEICI8LIDzQIKygIKTnR5cGVzLmFncmlyb3V0ZXIuY29tL2Fncmlyb3V0ZXIucmVzcG9uc2UucGF5bG9hZC5hY2NvdW50Lkxpc3RFbmRwb2ludHNSZXNwb25zZRL3AQp4CiRkNzA0YTQ0My05OWY3LTQ3YjQtYmU1NS1lMmZhMDk2ODllYmUSJFB5dGhvblNES19kZXYgLSAyMDIxLTEwLTI1LCAxMDo1MToxOBoLYXBwbGljYXRpb24iBmFjdGl2ZTIVdXJuOm15YXBwOnNucjAwMDAzMjM0CnsKJDE4NWNkOTdiLWVkMGItNGU3NS1hNmUyLTZiZTFjZGQzOGEwNhIkUHl0aG9uU0RLX2RldiAtIDIwMjEtMTAtMjEsIDIxOjQxOjI0GgthcHBsaWNhdGlvbiIGYWN0aXZlMhh1cm46bXlhcHA6c25yMDAwMDMyMzRzZGY="}}]' - - - -def test_onboarding(): - from agrirouter.onboarding.enums import GateWays - import agrirouter as ar - auth_params = ar.AuthUrlParameter(application_id=application_id, response_type="onboard") - auth_client = ar.Authorization("QA", public_key=public_key, private_key=private_key) - auth_url = auth_client.get_auth_request_url(auth_params) - - print(auth_url) - - auth_result_url = input("Entrer url: ") # the url the user was redirected after his authorization. - - auth_response = auth_client.extract_auth_response(auth_result_url) - auth_client.verify_auth_response(auth_response) - auth_response.is_successful - auth_response.is_valid - auth_data = auth_response.get_auth_result() - - id_ = "urn:myapp:snr00003234sdf" - certification_version_id = "edd5d6b7-45bb-4471-898e-ff9c2a7bf56f" - time_zone = "+03:00" - onboarding_client = ar.SoftwareOnboarding("QA", public_key=public_key, private_key=private_key) - onboarding_parameters = ar.SoftwareOnboardingParameter(id_=id_, application_id=application_id, - certification_version_id=certification_version_id, - gateway_id=GateWays.MQTT.value, - time_zone=time_zone, - reg_code=auth_data.decoded_token.regcode, - utc_timestamp=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z") - onboarding_verifying_response = onboarding_client.verify(onboarding_parameters) - onboarding_verifying_response.status_code - onboarding_verifying_response.text - print(onboarding_verifying_response.text) - onboarding_response = onboarding_client.onboard(onboarding_parameters) - print(onboarding_response.text) - - return onboarding_response - - -def test_capability_service_http(onboarding_response): - messaging_service = HttpMessagingService() - capability_parameters = CapabilityParameters( - application_id=application_id, - certification_version_id=certification_version_id, - enable_push_notification=1, - capability_parameters=CapabilityBuilder().with_task_data(2).build(), - onboarding_response=onboarding_response, - application_message_id=new_uuid(), - application_message_seq_no=1, - ) - capability_service = CapabilityService(messaging_service) - messaging_result = capability_service.send(capability_parameters) - return messaging_result - - -def test_list_endpoint_service_http(onboarding_response): - messaging_service = HttpMessagingService() - list_endpoint_parameters = ListEndpointsParameters( - technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, - direction=ListEndpointsQuery.Direction.Value("SEND_RECEIVE"), - filtered=False, - onboarding_response=onboarding_response, - application_message_id=new_uuid(), - application_message_seq_no=1, - ) - list_endpoint_service = ListEndpointsService(messaging_service) - messaging_result = list_endpoint_service.send(list_endpoint_parameters) - return messaging_result - - -def foo(client, userdata, message): - print(client, userdata, message) - return client, userdata, message - - -def test_list_endpoint_service_mqtt(onboarding_response): - messaging_service = MqttMessagingService( - on_message_callback=foo, - onboarding_response=onboarding_response - ) - list_endpoint_parameters = ListEndpointsParameters( - technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, - direction=ListEndpointsQuery.Direction.Value("SEND_RECEIVE"), - filtered=False, - onboarding_response=onboarding_response, - application_message_id=new_uuid(), - application_message_seq_no=1, - ) - list_endpoint_service = ListEndpointsService(messaging_service) - messaging_result = list_endpoint_service.send(list_endpoint_parameters) - return messaging_result - - -def test_valid_subscription_service_http(onboarding_response): - messaging_service = HttpMessagingService() - subscription_service = SubscriptionService(messaging_service) - items = [] - for tmt in [CapabilityType.DOC_PDF.value, CapabilityType.ISO_11783_TASKDATA_ZIP.value]: - subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) - items.append(subscription_item) - subscription_parameters = SubscriptionParameters( - subscription_items=items, - onboarding_response=onboarding_response, - application_message_id=new_uuid(), - application_message_seq_no=1, - ) - messaging_result = subscription_service.send(subscription_parameters) - return messaging_result - - -def test_query_header_message_http(onboarding_response): - messaging_service = HttpMessagingService() - query_header_service = QueryHeaderService(messaging_service) - sent_from = Timestamp() - sent_to = Timestamp() - validity_period = ValidityPeriod(sent_from=sent_from, sent_to=sent_to) - query_header_parameters = QueryHeaderParameters( - message_ids=[new_uuid(), new_uuid()], - senders=[new_uuid(), new_uuid()], - validity_period=validity_period, - onboarding_response=onboarding_response, - application_message_id=new_uuid(), - application_message_seq_no=1, - ) - messaging_result = query_header_service.send(query_header_parameters) - return messaging_result - - -def get_outbox(onboarding_response): - outbox_service = OutboxService() - outbox_response = outbox_service.fetch(onboarding_response) - # assert 200 == outbox_response.status_code - - messages = outbox_response.messages - # assert len(messages) == 1 - # assert messages[0].command.message - - decoded_message = decode_response(outbox_response.messages[0].command.message) - # assert 204 == decoded_message.response_envelope.response_code - - decoded_details = decode_details(decoded_message.response_payload.details) - # assert decoded_details - - # query_metrics = decoded_details.query_metrics - # assert 0 == query_metrics.total_messages_in_query - - return messages, decoded_message, decoded_details - - -def test_outbox_service_http(onboarding_response): - outbox_service = OutboxService() - result = outbox_service.fetch(onboarding_response) - return result - - -def test_revoke(): - params = RevokingParameter( - application_id=application_id, - account_id=account_id, - endpoint_ids=[ - "849ff5b9-6b3a-418e-9394-931e19acb8ec", - "b2157913-013f-486c-bf25-a591ffca451b" - ], - utc_timestamp=now_as_utc_str(), - time_zone="+03:00", - content_type=ContentTypes.APPLICATION_JSON.value - ) - service = Revoking("QA", public_key=public_key, private_key=private_key) - return service.revoke(params) - - -def test(): - onboarding_response = test_onboarding() - # print(test_list_endpoint_service_http(onboarding_response).get_messages_ids()) - # print(test_valid_subscription_service_http(onboarding_response).get_messages_ids()) - # print(test_capability_service_http(onboarding_response).get_messages_ids()) - # print(test_list_endpoint_service_http(onboarding_response).get_messages_ids()) - messaging_result = test_list_endpoint_service_mqtt(onboarding_response) - return messaging_result - - -if __name__ == "__main__": - test() From 0c6da941308b81321d6bf978ced5860504cc6443 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Fri, 12 Nov 2021 01:14:38 +0300 Subject: [PATCH 42/55] Fix SoftwareOnboardingResponse.json_deserialize method --- agrirouter/onboarding/response.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/agrirouter/onboarding/response.py b/agrirouter/onboarding/response.py index e42e14b2..5d2382c2 100644 --- a/agrirouter/onboarding/response.py +++ b/agrirouter/onboarding/response.py @@ -143,15 +143,15 @@ def json_deserialize(self, data: Union[dict, str]): for (key, value) in data_dict.items(): if key == self.DEVICE_ALTERNATE_ID: self.device_alternate_id = value - if key == self.CAPABILITY_ALTERNATE_ID: + elif key == self.CAPABILITY_ALTERNATE_ID: self.capability_alternate_id = value - if key == self.SENSOR_ALTERNATE_ID: + elif key == self.SENSOR_ALTERNATE_ID: self.sensor_alternate_id = value - if key == self.CONNECTION_CRITERIA: + elif key == self.CONNECTION_CRITERIA: connection_criteria = ConnectionCriteria() connection_criteria.json_deserialize(value) self.connection_criteria = connection_criteria - if key == self.AUTHENTICATION: + elif key == self.AUTHENTICATION: authentication = Authentication() authentication.json_deserialize(value) self.authentication = authentication From 869b77499551da7cacb8a0e60d99e5c7d29625a1 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Fri, 12 Nov 2021 12:24:31 +0300 Subject: [PATCH 43/55] Fix let_agrirouter_process_the_message method --- tests/sleeper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sleeper.py b/tests/sleeper.py index 73eb1d28..9d4179ac 100644 --- a/tests/sleeper.py +++ b/tests/sleeper.py @@ -2,4 +2,4 @@ def let_agrirouter_process_the_message(seconds: int = 3): - time.sleep(3) + time.sleep(seconds) From 9915d36615ce4df44cc834d698f4d9ea22830fa0 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Fri, 12 Nov 2021 18:07:58 +0300 Subject: [PATCH 44/55] Refactor MqttMessagingService --- agrirouter/messaging/services/commons.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index ea3b2e3e..f610bca3 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -53,6 +53,7 @@ class MqttMessagingService(AbstractMessagingClient): def __init__(self, onboarding_response: SoftwareOnboardingResponse, on_message_callback: callable = None, + client_async: bool = True ): self.onboarding_response = onboarding_response @@ -61,10 +62,16 @@ def __init__(self, client_id=onboarding_response.get_connection_criteria().get_client_id(), on_message_callback=on_message_callback, ) - self.client.connect( - self.onboarding_response.get_connection_criteria().get_host(), - self.onboarding_response.get_connection_criteria().get_port() - ) + if client_async: + self.client.connect_async( + self.onboarding_response.get_connection_criteria().get_host(), + self.onboarding_response.get_connection_criteria().get_port() + ) + else: + self.client.connect( + self.onboarding_response.get_connection_criteria().get_host(), + self.onboarding_response.get_connection_criteria().get_port() + ) def send(self, parameters, qos: int = 0) -> MessagingResult: message_request = self.create_message_request(parameters) @@ -76,9 +83,3 @@ def send(self, parameters, qos: int = 0) -> MessagingResult: ) result = MessagingResult([parameters.get_application_message_id()]) return result - - def subscribe(self): - pass - - def unsubscribe(self): - pass From 972ccdb7afecee136846e2cbddbca6bf3ed5fe76 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Fri, 12 Nov 2021 18:08:49 +0300 Subject: [PATCH 45/55] Implement __str__ and __repr__ magic methods for dtos --- agrirouter/onboarding/dto.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/agrirouter/onboarding/dto.py b/agrirouter/onboarding/dto.py index c4cbc314..89d84806 100644 --- a/agrirouter/onboarding/dto.py +++ b/agrirouter/onboarding/dto.py @@ -91,6 +91,12 @@ def get_client_id(self) -> str: def set_client_id(self, client_id: str) -> None: self.client_id = client_id + def __str__(self): + return str(self.json_serialize()) + + def __repr__(self): + return str(self.json_serialize()) + class Authentication: TYPE = 'type' @@ -144,6 +150,12 @@ def get_certificate(self) -> str: def set_certificate(self, certificate: str) -> None: self.certificate = certificate + def __str__(self): + return str(self.json_serialize()) + + def __repr__(self): + return str(self.json_serialize()) + class ErrorResponse: def __init__(self, From 9067872c3d43c66e259490a391a92b1fbd1765fa Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Fri, 12 Nov 2021 18:12:48 +0300 Subject: [PATCH 46/55] Refactor MqttClient --- agrirouter/messaging/clients/constants.py | 2 + agrirouter/messaging/clients/mqtt.py | 46 ++++++++++++++++++----- 2 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 agrirouter/messaging/clients/constants.py diff --git a/agrirouter/messaging/clients/constants.py b/agrirouter/messaging/clients/constants.py new file mode 100644 index 00000000..78de8628 --- /dev/null +++ b/agrirouter/messaging/clients/constants.py @@ -0,0 +1,2 @@ +ASYNC = "ASYNC" +SYNC = "SYNC" diff --git a/agrirouter/messaging/clients/mqtt.py b/agrirouter/messaging/clients/mqtt.py index 8c0ccaf9..e5ce0da8 100644 --- a/agrirouter/messaging/clients/mqtt.py +++ b/agrirouter/messaging/clients/mqtt.py @@ -1,3 +1,4 @@ +import time import ssl from typing import Any, List, Tuple @@ -5,6 +6,7 @@ from paho.mqtt.client import MQTTv31, MQTTMessageInfo from agrirouter.messaging.certification import create_certificate_file_from_pen +from agrirouter.messaging.clients.constants import SYNC, ASYNC class MqttClient: @@ -14,7 +16,7 @@ def __init__(self, client_id: str, on_message_callback: callable = None, userdata: Any = None, - clean_session: bool = True + clean_session: bool = False ): # TODO: Implement on_message_callback parameter validation: # must take params as described at https://pypi.org/project/paho-mqtt/#callbacks @@ -28,9 +30,8 @@ def __init__(self, ) self.mqtt_client.on_message = on_message_callback if on_message_callback else self._get_on_message_callback() - self.mqtt_client.on_connect = self._get_on_connect_callback() + self.mqtt_client.on_connect = self._get_on_connect_callback(onboard_response) self.mqtt_client.on_disconnect = self._get_on_disconnect_callback() - self.mqtt_client.on_connect = self._get_on_connect_callback() self.mqtt_client.on_subscribe = self._get_on_subscribe_callback() self.mqtt_client.on_unsubscribe = self._get_on_unsubscribe_callback() @@ -43,18 +44,37 @@ def __init__(self, ) self.mqtt_client.tls_set_context(context) + self._mode = None + def connect(self, host: str, port: str) -> None: self.mqtt_client.connect( host=host, port=int(port) ) - self.mqtt_client.loop_forever() + self.mqtt_client.loop() + + self._mode = SYNC + + def connect_async(self, host: str, port: str): + self.mqtt_client.connect_async( + host=host, + port=int(port) + ) + self.mqtt_client.loop_start() + + self._mode = ASYNC + + while self.mqtt_client._state == 0: + time.sleep(1) def disconnect(self): self.mqtt_client.loop_stop() self.mqtt_client.disconnect() - def publish(self, topic, payload, qos=0) -> MQTTMessageInfo: + def receive_outbox_messages(self): + self.mqtt_client.loop() + + def publish(self, topic, payload, qos=2) -> MQTTMessageInfo: """ :param topic: str representing unique name of the topic that the message should be published on :param payload: The actual message to send @@ -66,6 +86,10 @@ def publish(self, topic, payload, qos=0) -> MQTTMessageInfo: payload=payload, qos=qos ) + if self._mode == SYNC: + self.mqtt_client.loop() + time.sleep(3) + self.mqtt_client.loop() return message_info def subscribe(self, topics: List[Tuple[str, int]]) -> tuple: @@ -81,7 +105,7 @@ def subscribe(self, topics: List[Tuple[str, int]]) -> tuple: :return: tuple """ - result, mid = self.mqtt_client.subscribe(topics) + result, mid = self.mqtt_client.subscribe(topics, qos=2) return result, mid def unsubscribe(self, topics: List[str]) -> tuple: @@ -99,12 +123,14 @@ def unsubscribe(self, topics: List[str]) -> tuple: return result, mid @staticmethod - def _get_on_connect_callback() -> callable: + def _get_on_connect_callback(onboard_response) -> callable: - def on_connect(client, userdata, flags, rc, properties=None): - print("On_connect func start...") + def on_connect(client: mqtt_client.Client, userdata, flags, rc, properties=None): print(f"Connection with response code: {rc}, flags: {flags}") - print("-"*50) + if rc == 0: + print(f"Subscribing to {onboard_response.connection_criteria.commands}") + client.subscribe(topic=onboard_response.connection_criteria.commands) + time.sleep(3) return on_connect From 2d06436fcc3c6afd4a78277c27fb70be8b4f6f04 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Fri, 12 Nov 2021 18:17:18 +0300 Subject: [PATCH 47/55] Remove redundant prints --- agrirouter/messaging/clients/mqtt.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/agrirouter/messaging/clients/mqtt.py b/agrirouter/messaging/clients/mqtt.py index e5ce0da8..423897d0 100644 --- a/agrirouter/messaging/clients/mqtt.py +++ b/agrirouter/messaging/clients/mqtt.py @@ -126,9 +126,7 @@ def unsubscribe(self, topics: List[str]) -> tuple: def _get_on_connect_callback(onboard_response) -> callable: def on_connect(client: mqtt_client.Client, userdata, flags, rc, properties=None): - print(f"Connection with response code: {rc}, flags: {flags}") if rc == 0: - print(f"Subscribing to {onboard_response.connection_criteria.commands}") client.subscribe(topic=onboard_response.connection_criteria.commands) time.sleep(3) @@ -138,8 +136,6 @@ def on_connect(client: mqtt_client.Client, userdata, flags, rc, properties=None) def _get_on_message_callback() -> callable: def on_message(client, userdata, msg): - print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic") - return client, userdata, msg return on_message @@ -148,8 +144,6 @@ def on_message(client, userdata, msg): def _get_on_subscribe_callback() -> callable: def on_subscribe(*args, **kwargs): - print(f"Subscribed. Args: `{args}`, Kwargs: `{kwargs}`") - return args, kwargs return on_subscribe @@ -158,8 +152,6 @@ def on_subscribe(*args, **kwargs): def _get_on_disconnect_callback() -> callable: def on_disconnect(*args, **kwargs): - print(f"Disconnected. Args: `{args}`, Kwargs: `{kwargs}`") - return args, kwargs return on_disconnect @@ -168,8 +160,6 @@ def on_disconnect(*args, **kwargs): def _get_on_unsubscribe_callback() -> callable: def on_unsubscribe(*args, **kwargs): - print(f"Unsubscribed. Args: `{args}`, Kwargs: `{kwargs}`") - return args, kwargs return on_unsubscribe From 8de531a29540fb76a93f8bd48bed04175b13b6db Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Sat, 13 Nov 2021 23:14:37 +0300 Subject: [PATCH 48/55] Refactor SoftwareOnboardingResponse --- agrirouter/onboarding/response.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agrirouter/onboarding/response.py b/agrirouter/onboarding/response.py index 5d2382c2..99723f96 100644 --- a/agrirouter/onboarding/response.py +++ b/agrirouter/onboarding/response.py @@ -134,8 +134,8 @@ def json_serialize(self): self.DEVICE_ALTERNATE_ID: self.device_alternate_id, self.CAPABILITY_ALTERNATE_ID: self.capability_alternate_id, self.SENSOR_ALTERNATE_ID: self.sensor_alternate_id, - self.CONNECTION_CRITERIA: self.connection_criteria, - self.AUTHENTICATION: self.authentication + self.CONNECTION_CRITERIA: self.connection_criteria.json_serialize(), + self.AUTHENTICATION: self.authentication.json_serialize() } def json_deserialize(self, data: Union[dict, str]): From 8e1d24e9ff2abe34cab1bf65e5e9b9bbbd016176 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Sat, 13 Nov 2021 23:15:33 +0300 Subject: [PATCH 49/55] Update examp,es script --- example_script.py | 118 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 108 insertions(+), 10 deletions(-) diff --git a/example_script.py b/example_script.py index b6c05d67..2c8b667c 100644 --- a/example_script.py +++ b/example_script.py @@ -1,4 +1,8 @@ +from pprint import pprint + from agrirouter.generated.messaging.request.payload.account.endpoints_pb2 import ListEndpointsQuery +from agrirouter.onboarding.response import SoftwareOnboardingResponse +import time public_key = """-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzGt41/+kSOTlO1sJvLIN @@ -40,6 +44,28 @@ -----END PRIVATE KEY-----""" +onboarding_response_mqtt = { + "deviceAlternateId": "2145df0e-3451-46cb-bf23-23191af66fce", + "capabilityAlternateId": "523e4623-68d2-43d4-a0cc-e2ada2f68b5e", + "sensorAlternateId": "1489638c-7bed-4205-ad77-8d11efdc779f", + "connectionCriteria": { + "gatewayId": "2", + "host": "dke-qa.eu10.cp.iot.sap", + "port": 8883, + "clientId": "2145df0e-3451-46cb-bf23-23191af66fce", + "measures": "measures/2145df0e-3451-46cb-bf23-23191af66fce", + "commands": "commands/2145df0e-3451-46cb-bf23-23191af66fce" + }, + "authentication": { + "type": "PEM", + "secret": "JNKdNg8R0lwmFgvrUfOCc7inebr0h?!7Z9wL", + "certificate": "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIE6zAdBgoqhkiG9w0BDAEDMA8ECMkL85F+LbPbAgMCAAAEggTI1CmRlnDUStBv\nTycvaRVFMCk1OuynhiOYRF6HBFFXBCxWKZa3WqTShLdf9iCel/NgtdZIiQsoD1LL\nMxVyh8pWAfLQ+pDJLvM6suQjHALt8dW5iTeCZ7R1gzFvPJ+xnDGFFytN7HmGSvHM\nQbcCOuEeIu8U6ENa6/+WmUwK9/ZMkLNqDHVKEGpI+lSJs8JWEE+S3Klmsxuq0dvz\nh6o3V7RKFwMfUZOQLHezGBDjLfEBdP+d2G87CY+LSzinL8pFhLwyrXFKfYWYoT0m\n5PkDdjfiVq3SJIUoQWnGrjaVVw4TV3WSxmhQnWbDwOQydr8DAiBxDMYoeK3rePpC\nwh6KATnBrovq1icqjonYDE0T+3Rs2SUbG+3+m9Zj4j46L2Sh9bUB6qxdw74Ck2/z\nAzJ1N+tB+RL7UvOpMOhmndMBl5qpx9dFFy8Z/N7w4YTQLZLN7chD8ApeFhCgvppt\nAGh8/VeWO54OC9ZOSHpxEl7sJz97jaHYNbw/lGbDk7cOZezwpA0NCWZ/Bb1vRDzy\n8EDX9s1hOA3jiy2T1RSyk2Rj/12pWdKtdSO8lMhMKC0B32Zr1F8rBJKDVzqFWuTt\nn+pXOKedyOA/ggyvYJdsltP8O4XB2oBN3WBdFK7Y1FG/tN30LsaqcnFTxab5v1Pp\ngq2dHu6Xy0TCMAw/DH3RmGXlGnDDWu86Zad7TjjrEZvpSIv4TTSCqqTvc4IN0xFX\nbKZCrY6JSkJWWnDMKrsRYOijUDvpAbYwZuTV9PAljYbt5YX778qxV9O0fNBQdaww\nNlfxU93jgr4g3E9nIzRxLu9S98hPbxKUnVYiQmYvP7vJUcUSo5F0LmUU/nvHY1pi\nr4tZDp8Xu1aZy7cOd3sTbf/68IjiZMZlF5/PVlOFOo40yGqW600j/qEqXoY/492h\nONXUCpHKaG/Pkjtg9THuYoaw1773gxYYsYLt+c6NkQCCsydOr2BMZQ4Qy4bZV67D\n2RNDeZzSBY6jEX6dnfY0FJqIsSiw28Ek5NXx0HTEGN8txPkx/1dfu3RfZnzUqT/0\nmS9xcWVYRmlip3vm48fMecqP/DNIHyjVLC39SsFdeXa+De76z/S3+or0t7HGlUim\nNVkIcWqm/sD2ia8hYberaRRTbUQ1iObNToIg8dA/xna6D61sYK8jkf1GVPpKsCTA\nOVW5u9XrE1f5YQEovE9kFgvtzs0u6jSeI9edqVadH1u6hX4QWQSTrcTb3raqAKpK\nl67cQ96eXI1WQPSdPhQPTjqzOPZDbot3qMkGFijHar7FdQjDx/cNhqhvxv0LWsvl\njgep1czUFoo1BS3wTUiO0qyloNGOQdgmlTOHbMFk1wgoNyAohfZtfn6LH/zlJnE3\nQ0YkUKgAG+1N/PmkQFO0k5qAflUV7h+HAzT1ZAZcscjHNbQFDc0Zjq9nE9sfhxE8\nOFpnF9Jp3fQVekyyC/dsCxtJdYfhxqYe+BzZu0SlsLCmc1JoK5lkiXQwv6+cFpKW\nwfHMTTrCoOetJyiF7oJX+t4adzmLmnujiw5izxObWQJ7avHC1oYNHfRejrOtlu34\n0nDPRFiSDyEbDCBXPe9dIafqjJVLQGFOeXC8/VN9cGSZp2JV8rqumWOr9E+Wd5zU\n8MRZpevo0i3rPgdyFRpw\n-----END ENCRYPTED PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\nMIIEaDCCA1CgAwIBAgIPANHZYxYlOc+wEAEDDWDpMA0GCSqGSIb3DQEBCwUAMFYx\nCzAJBgNVBAYTAkRFMSMwIQYDVQQKExpTQVAgSW9UIFRydXN0IENvbW11bml0eSBJ\nSTEiMCAGA1UEAxMZU0FQIEludGVybmV0IG9mIFRoaW5ncyBDQTAeFw0yMTExMTIw\nNzMyMjNaFw0yMjExMTIwNzMyMjNaMIG1MQswCQYDVQQGEwJERTEcMBoGA1UEChMT\nU0FQIFRydXN0IENvbW11bml0eTEVMBMGA1UECxMMSW9UIFNlcnZpY2VzMXEwbwYD\nVQQDFGhkZXZpY2VBbHRlcm5hdGVJZDoyMTQ1ZGYwZS0zNDUxLTQ2Y2ItYmYyMy0y\nMzE5MWFmNjZmY2V8Z2F0ZXdheUlkOjJ8dGVuYW50SWQ6MTExNjkwMzQ5MHxpbnN0\nYW5jZUlkOmRrZS1xYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJeF\naxjV7Xk1R2dFjadN6WsUkrmcVu44vZRCJEbR7Chkg1xcXT6cgIlokO/V4lTgaD6i\neCMKMFegjXzEJQy0dyIWncozcmt6HJFxpdjVQtdCtDtCWykGscNDgvv5ukykOOKI\nMzWJ4d2cJRlostpNe4FYZoPp6cArSHTl9DvfYqjZ/ykeTa1w157dgVxPxezHrJMl\n+z2XgO37mq6CJLw8J6W8RBHbCADgB8c6qGHgJnBURyxnoHHi/yqdIKC6cOs8NAnc\nyVmnvLDu8RUWu9pWkqFHhMvSqdkUCTYORZ9mUTm/Kmv6ss2NaYT4uUBZTskwnAa9\nFLdj+DV2NG0OQl3NYr8CAwEAAaOB0jCBzzBIBgNVHR8EQTA/MD2gO6A5hjdodHRw\nczovL3Rjcy5teXNhcC5jb20vY3JsL1RydXN0Q29tbXVuaXR5SUkvU0FQSW9UQ0Eu\nY3JsMAwGA1UdEwEB/wQCMAAwJQYDVR0SBB4wHIYaaHR0cDovL3NlcnZpY2Uuc2Fw\nLmNvbS9UQ1MwDgYDVR0PAQH/BAQDAgbAMB0GA1UdDgQWBBSRf8DUjowgQ+6amVIs\njd7zM7VWqjAfBgNVHSMEGDAWgBSVt7P1WN7VtLNYRuDypsl4Tr0tdTANBgkqhkiG\n9w0BAQsFAAOCAQEARzSc9GLpSU3pRJPIfgadHrZ+2KQsPsQ1/fLlASlt4V1Rlxn7\n/tn0gk3sP0X5/TrkO+N0kx1qrLarxWSDiVfaXoPa6Lit30SBPnPLUPPPZeTJOz5r\nTW9PkPPuC39GlM1biVoil2cLZrTr9DMSUoBvR4IVKQoJveQsLwn7Ea+SDPE0uvZV\nbDN6UPGZ2yIiCXO1MODJ6r3A4EDD2MArGgfhGdbvJNAY36ShFJhzfzi0t8linEAA\nxh0vcaEEIkVeEiwiguyGWB69X88cjZ0Q5cCf0r6iu3oQnB57uM5TW12OwXQN1NpQ\neK3EMFSoM6BYJu/3B8TXhNmpNBvD7KYozw9XaA==\n-----END CERTIFICATE-----\n" + } +} + + + + import agrirouter as ar from agrirouter.onboarding.enums import GateWays from agrirouter.messaging.enums import CapabilityType @@ -52,7 +78,7 @@ application_id = "8c947a45-c57d-42d2-affc-206e21d63a50" # # store here your application id. You can find it in AR UI -def test_auth(): +def example_auth(): print("Authorization...\n") auth_params = ar.AuthUrlParameter(application_id=application_id, response_type="onboard") @@ -83,9 +109,9 @@ def test_auth(): return auth_data -def test_onboarding(gateway_id): +def example_onboarding(gateway_id): - auth_data = test_auth() + auth_data = example_auth() print("Onboarding...\n") @@ -108,9 +134,14 @@ def test_onboarding(gateway_id): return onboarding_response -def test_list_endpoints_mqtt(onboarding_response): +def example_list_endpoints_mqtt(onboarding_response_data, foo): + onboarding_response = SoftwareOnboardingResponse() + onboarding_response.json_deserialize(onboarding_response_data) + messaging_service = MqttMessagingService( - onboarding_response=onboarding_response + onboarding_response=onboarding_response, + on_message_callback=foo + ) list_endpoint_parameters = ListEndpointsParameters( technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, @@ -121,11 +152,19 @@ def test_list_endpoints_mqtt(onboarding_response): application_message_seq_no=1, ) list_endpoint_service = ListEndpointsService(messaging_service) + messaging_result = list_endpoint_service.send(list_endpoint_parameters) - return messaging_result + print("Sent message: ", messaging_result) + + # Is needed for waiting of messaging responses from outbox + while True: + time.sleep(1) -def test_list_endpoint_http(onboarding_response): +def example_list_endpoints_http(onboarding_response_data): + onboarding_response = SoftwareOnboardingResponse() + onboarding_response.json_deserialize(onboarding_response_data) + messaging_service = HttpMessagingService() list_endpoint_parameters = ListEndpointsParameters( technical_message_type=CapabilityType.ISO_11783_TASKDATA_ZIP.value, @@ -136,14 +175,19 @@ def test_list_endpoint_http(onboarding_response): application_message_seq_no=1, ) list_endpoint_service = ListEndpointsService(messaging_service) + messaging_result = list_endpoint_service.send(list_endpoint_parameters) + print("Sent message: ", messaging_result) + return messaging_result -def test_subscription_http(onboarding_response): +def example_subscription_http(onboarding_response_data): + onboarding_response = SoftwareOnboardingResponse() + onboarding_response.json_deserialize(onboarding_response_data) + messaging_service = HttpMessagingService() subscription_service = SubscriptionService(messaging_service) - tmt = CapabilityType.ISO_11783_TASKDATA_ZIP.value subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) subscription_parameters = SubscriptionParameters( @@ -152,9 +196,63 @@ def test_subscription_http(onboarding_response): application_message_id=new_uuid(), application_message_seq_no=1, ) + messaging_result = subscription_service.send(subscription_parameters) + print("Sent message: ", messaging_result) + return messaging_result +def example_subscription_mqtt(onboarding_response_data, on_msg_callback): + onboarding_response = SoftwareOnboardingResponse() + onboarding_response.json_deserialize(onboarding_response_data) + + messaging_service = MqttMessagingService(onboarding_response, on_message_callback=on_msg_callback) + subscription_service = SubscriptionService(messaging_service) + tmt = CapabilityType.ISO_11783_TASKDATA_ZIP.value + subscription_item = Subscription.MessageTypeSubscriptionItem(technical_message_type=tmt) + subscription_parameters = SubscriptionParameters( + subscription_items=[subscription_item], + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + + messaging_result = subscription_service.send(subscription_parameters) + print("Sent message: ", messaging_result) + + # Is needed for waiting of messaging responses from outbox + while True: + time.sleep(1) + + +def on_message_callback(client, userdata, msg): + + # Define here the way receiving messages will be processed + + from agrirouter.messaging.decode import decode_response + from agrirouter.messaging.decode import decode_details + from agrirouter.messaging.messages import OutboxMessage + + outbox_message = OutboxMessage() + outbox_message.json_deserialize(msg.payload.decode().replace("'", '"')) + + print(outbox_message.command.message) + + decoded_message = decode_response(outbox_message.command.message) + print(decoded_message.response_envelope) + + try: + decoded_details = decode_details(decoded_message.response_payload.details) + print(decoded_details) + except: + pass + + if __name__ == "__main__": - test_list_endpoints_mqtt(test_onboarding(GateWays.MQTT.value)) + onboarding_response_mqtt = example_onboarding(GateWays.MQTT.value) + example_list_endpoints_mqtt(onboarding_response_mqtt.json_serialize(), on_message_callback) + + # of for http + onboarding_response_mqtt = example_onboarding(GateWays.REST.value) + example_list_endpoints_http(onboarding_response_mqtt.json_serialize()) From 65a8c8cc0124c9524412dd051bef88c58e605dba Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Sat, 13 Nov 2021 23:32:35 +0300 Subject: [PATCH 50/55] Add example functions for QueryHeaderService to example_script --- example_script.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/example_script.py b/example_script.py index 2c8b667c..a4752230 100644 --- a/example_script.py +++ b/example_script.py @@ -1,6 +1,9 @@ from pprint import pprint +from google.protobuf.timestamp_pb2 import Timestamp + from agrirouter.generated.messaging.request.payload.account.endpoints_pb2 import ListEndpointsQuery +from agrirouter.generated.messaging.request.payload.feed.feed_requests_pb2 import ValidityPeriod from agrirouter.onboarding.response import SoftwareOnboardingResponse import time @@ -71,7 +74,8 @@ from agrirouter.messaging.enums import CapabilityType from agrirouter.generated.messaging.request.payload.endpoint.subscription_pb2 import Subscription from agrirouter.messaging.services.commons import HttpMessagingService, MqttMessagingService -from agrirouter import ListEndpointsParameters, ListEndpointsService, SubscriptionService, SubscriptionParameters +from agrirouter import ListEndpointsParameters, ListEndpointsService, SubscriptionService, SubscriptionParameters, \ + QueryHeaderService, QueryHeaderParameters from agrirouter.utils.uuid_util import new_uuid @@ -226,6 +230,52 @@ def example_subscription_mqtt(onboarding_response_data, on_msg_callback): time.sleep(1) +def example_query_header_message_http(onboarding_response_data): + onboarding_response = SoftwareOnboardingResponse() + onboarding_response.json_deserialize(onboarding_response_data) + + messaging_service = HttpMessagingService() + query_header_service = QueryHeaderService(messaging_service) + sent_from = Timestamp() + sent_to = Timestamp() + validity_period = ValidityPeriod(sent_from=sent_from, sent_to=sent_to) + query_header_parameters = QueryHeaderParameters( + message_ids=[new_uuid(), new_uuid()], + senders=[new_uuid(), new_uuid()], + validity_period=validity_period, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + print("Sent message: ", messaging_result) + + return messaging_result + + +def example_query_header_message_mqtt(onboarding_response_data, on_msg_callback): + onboarding_response = SoftwareOnboardingResponse() + onboarding_response.json_deserialize(onboarding_response_data) + + messaging_service = MqttMessagingService(onboarding_response, on_message_callback=on_msg_callback) + query_header_service = QueryHeaderService(messaging_service) + sent_from = Timestamp() + sent_to = Timestamp() + validity_period = ValidityPeriod(sent_from=sent_from, sent_to=sent_to) + query_header_parameters = QueryHeaderParameters( + message_ids=[new_uuid(), new_uuid()], + senders=[new_uuid(), new_uuid()], + validity_period=validity_period, + onboarding_response=onboarding_response, + application_message_id=new_uuid(), + application_message_seq_no=1, + ) + messaging_result = query_header_service.send(query_header_parameters) + print("Sent message: ", messaging_result) + + return messaging_result + + def on_message_callback(client, userdata, msg): # Define here the way receiving messages will be processed From 368689d1c6da3c99d9a8d8f9b5bcb8643e3cfef3 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Sat, 13 Nov 2021 23:33:18 +0300 Subject: [PATCH 51/55] Fix example_script --- example_script.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/example_script.py b/example_script.py index a4752230..e6b33a91 100644 --- a/example_script.py +++ b/example_script.py @@ -273,7 +273,9 @@ def example_query_header_message_mqtt(onboarding_response_data, on_msg_callback) messaging_result = query_header_service.send(query_header_parameters) print("Sent message: ", messaging_result) - return messaging_result + # Is needed for waiting of messaging responses from outbox + while True: + time.sleep(1) def on_message_callback(client, userdata, msg): From 88d78ba26af0d65e48843313f2d93cac5f775db8 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Mon, 15 Nov 2021 14:29:13 +0300 Subject: [PATCH 52/55] Refactor example_script --- example_script.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/example_script.py b/example_script.py index e6b33a91..50e4ae3f 100644 --- a/example_script.py +++ b/example_script.py @@ -47,7 +47,7 @@ -----END PRIVATE KEY-----""" -onboarding_response_mqtt = { +onboarding_response_mqtt_data = { "deviceAlternateId": "2145df0e-3451-46cb-bf23-23191af66fce", "capabilityAlternateId": "523e4623-68d2-43d4-a0cc-e2ada2f68b5e", "sensorAlternateId": "1489638c-7bed-4205-ad77-8d11efdc779f", @@ -297,14 +297,14 @@ def on_message_callback(client, userdata, msg): try: decoded_details = decode_details(decoded_message.response_payload.details) print(decoded_details) - except: - pass + except Exception as exc: + print("Error in decoding details: ", exc) if __name__ == "__main__": onboarding_response_mqtt = example_onboarding(GateWays.MQTT.value) example_list_endpoints_mqtt(onboarding_response_mqtt.json_serialize(), on_message_callback) - # of for http - onboarding_response_mqtt = example_onboarding(GateWays.REST.value) - example_list_endpoints_http(onboarding_response_mqtt.json_serialize()) + # or for http + # onboarding_response_mqtt = example_onboarding(GateWays.REST.value) + # example_list_endpoints_http(onboarding_response_mqtt.json_serialize()) From 1181acb9c4b433ec57fd2b7a6776bacf7ee6fb42 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Mon, 15 Nov 2021 14:30:15 +0300 Subject: [PATCH 53/55] Fix ConnectionCriteria dto --- agrirouter/onboarding/dto.py | 1 + 1 file changed, 1 insertion(+) diff --git a/agrirouter/onboarding/dto.py b/agrirouter/onboarding/dto.py index 89d84806..d2b34bed 100644 --- a/agrirouter/onboarding/dto.py +++ b/agrirouter/onboarding/dto.py @@ -33,6 +33,7 @@ def json_serialize(self) -> dict: self.GATEWAY_ID: self.gateway_id, self.MEASURES: self.measures, self.COMMANDS: self.commands, + self.HOST: self.host, self.PORT: self.port, self.CLIENT_ID: self.client_id } From ab4f189b2e64da8f8476c1f53dd3de9340aa5fd2 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Mon, 15 Nov 2021 14:30:57 +0300 Subject: [PATCH 54/55] Add __str__ and __repr__ methods for SoftwareOnboardingResponse --- agrirouter/onboarding/response.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/agrirouter/onboarding/response.py b/agrirouter/onboarding/response.py index 99723f96..44a4f5b5 100644 --- a/agrirouter/onboarding/response.py +++ b/agrirouter/onboarding/response.py @@ -157,3 +157,9 @@ def json_deserialize(self, data: Union[dict, str]): self.authentication = authentication else: raise WrongFieldError(f"Unknown field `{key}` for {self.__class__}") + + def __str__(self): + return str(self.json_serialize()) + + def __repr__(self): + return str(self.json_serialize()) From e6f35c3391780c64b92ec153be9533ac1e009b97 Mon Sep 17 00:00:00 2001 From: Alexey Petrovsky Date: Mon, 15 Nov 2021 17:12:32 +0300 Subject: [PATCH 55/55] Fix flake8 warnings --- agrirouter/auth/auth.py | 1 - agrirouter/auth/dto.py | 2 +- agrirouter/environments/environments.py | 8 ++++---- agrirouter/messaging/builders.py | 2 +- agrirouter/messaging/certification.py | 3 --- agrirouter/messaging/clients/http.py | 1 - agrirouter/messaging/decode.py | 1 - agrirouter/messaging/messages.py | 3 +-- agrirouter/messaging/parameters/service.py | 1 - agrirouter/messaging/request.py | 2 -- agrirouter/messaging/services/commons.py | 4 ---- agrirouter/messaging/services/http/outbox.py | 9 --------- agrirouter/messaging/services/messaging.py | 2 +- agrirouter/onboarding/headers.py | 2 -- agrirouter/onboarding/onboarding.py | 2 -- agrirouter/onboarding/parameters.py | 2 -- agrirouter/revoking/headers.py | 2 +- 17 files changed, 9 insertions(+), 38 deletions(-) diff --git a/agrirouter/auth/auth.py b/agrirouter/auth/auth.py index f1e2f634..b33527c0 100644 --- a/agrirouter/auth/auth.py +++ b/agrirouter/auth/auth.py @@ -34,4 +34,3 @@ def verify_auth_response(self, response, public_key=None): def _extract_query_params(query_params: str) -> dict: qp_pairs = parse_qs(query_params) return {k: v[0] for k, v in qp_pairs.items()} - diff --git a/agrirouter/auth/dto.py b/agrirouter/auth/dto.py index 8e4be05e..55d1bbbb 100644 --- a/agrirouter/auth/dto.py +++ b/agrirouter/auth/dto.py @@ -115,4 +115,4 @@ def get_state(self) -> str: return self.state def set_state(self, state: str) -> None: - self.state = state \ No newline at end of file + self.state = state diff --git a/agrirouter/environments/environments.py b/agrirouter/environments/environments.py index 5c688c7a..6b05b99e 100644 --- a/agrirouter/environments/environments.py +++ b/agrirouter/environments/environments.py @@ -40,10 +40,10 @@ def get_agrirouter_login_url(self) -> str: def get_secured_onboarding_authorization_url(self, application_id, response_type, state, redirect_uri=None) -> str: auth_url = self.get_base_url() + self._SECURED_ONBOARDING_AUTHORIZATION_LINK_TEMPLATE.format( - application_id=application_id, - response_type=response_type, - state=state - ) + application_id=application_id, + response_type=response_type, + state=state + ) return auth_url + f"&redirect_uri={redirect_uri}" if redirect_uri is not None else auth_url def get_mqtt_server_url(self, host, port) -> str: diff --git a/agrirouter/messaging/builders.py b/agrirouter/messaging/builders.py index 94e56ce8..d7815042 100644 --- a/agrirouter/messaging/builders.py +++ b/agrirouter/messaging/builders.py @@ -23,7 +23,7 @@ def with_task_data(self): self._subscription_items.append(subscription_item) return self - def with_device_description(self, ddis: List[int]=None, position: bool=None): + def with_device_description(self, ddis: List[int] = None, position: bool = None): subscription_item = Subscription.MessageTypeSubscriptionItem( technical_message_type=CapabilityType.ISO_11783_DEVICE_DESCRIPTION_PROTOBUF.value, ddis=ddis, diff --git a/agrirouter/messaging/certification.py b/agrirouter/messaging/certification.py index 50de6778..c87afc83 100644 --- a/agrirouter/messaging/certification.py +++ b/agrirouter/messaging/certification.py @@ -1,7 +1,4 @@ -import json import os -import pathlib -from pathlib import Path import tempfile diff --git a/agrirouter/messaging/clients/http.py b/agrirouter/messaging/clients/http.py index 429be1e7..cf1e400f 100644 --- a/agrirouter/messaging/clients/http.py +++ b/agrirouter/messaging/clients/http.py @@ -5,7 +5,6 @@ from urllib.parse import urlparse from agrirouter.messaging.certification import create_certificate_file_from_pen -from agrirouter.onboarding.dto import ConnectionCriteria from agrirouter.onboarding.response import SoftwareOnboardingResponse diff --git a/agrirouter/messaging/decode.py b/agrirouter/messaging/decode.py index 02387153..0d1a4e12 100644 --- a/agrirouter/messaging/decode.py +++ b/agrirouter/messaging/decode.py @@ -1,5 +1,4 @@ import base64 -from ctypes import Union from google.protobuf.any_pb2 import Any from google.protobuf.internal.decoder import _DecodeVarint diff --git a/agrirouter/messaging/messages.py b/agrirouter/messaging/messages.py index da475631..7db4e461 100644 --- a/agrirouter/messaging/messages.py +++ b/agrirouter/messaging/messages.py @@ -1,6 +1,5 @@ import json -from datetime import datetime, timezone -from typing import Union, List, Dict +from typing import Union, Dict from agrirouter.messaging.exceptions import WrongFieldError from agrirouter.utils.utc_time_util import now_as_utc_str diff --git a/agrirouter/messaging/parameters/service.py b/agrirouter/messaging/parameters/service.py index 631188ae..758df834 100644 --- a/agrirouter/messaging/parameters/service.py +++ b/agrirouter/messaging/parameters/service.py @@ -1,4 +1,3 @@ -from abc import ABC, abstractmethod from copy import deepcopy from typing import List diff --git a/agrirouter/messaging/request.py b/agrirouter/messaging/request.py index 9cc9507e..14766318 100644 --- a/agrirouter/messaging/request.py +++ b/agrirouter/messaging/request.py @@ -1,7 +1,5 @@ from typing import List -from agrirouter.messaging.messages import Message - class MessageRequest: SENSOR_ALTERNATE_ID = "sensorAlternateId" diff --git a/agrirouter/messaging/services/commons.py b/agrirouter/messaging/services/commons.py index f610bca3..8f4f6db5 100644 --- a/agrirouter/messaging/services/commons.py +++ b/agrirouter/messaging/services/commons.py @@ -1,10 +1,6 @@ import json -import os from abc import ABC, abstractmethod -import requests - -from agrirouter.messaging.certification import create_certificate_file_from_pen from agrirouter.messaging.clients.http import HttpClient from agrirouter.messaging.clients.mqtt import MqttClient from agrirouter.messaging.messages import Message diff --git a/agrirouter/messaging/services/http/outbox.py b/agrirouter/messaging/services/http/outbox.py index 13db25cf..7985f914 100644 --- a/agrirouter/messaging/services/http/outbox.py +++ b/agrirouter/messaging/services/http/outbox.py @@ -1,15 +1,7 @@ -import json -import os - -import requests - from agrirouter.messaging.clients.http import HttpClient from agrirouter.messaging.exceptions import OutboxException -from agrirouter.messaging.messages import OutboxMessage from agrirouter.messaging.result import OutboxResponse -from agrirouter.messaging.certification import create_certificate_file_from_pen - class OutboxService: @@ -27,4 +19,3 @@ def fetch(self, onboarding_response) -> OutboxResponse: raise OutboxException(f"Could not fetch messages from outbox. Status code was {response.status}") return outbox_response - diff --git a/agrirouter/messaging/services/messaging.py b/agrirouter/messaging/services/messaging.py index 685934ba..81f60d6f 100644 --- a/agrirouter/messaging/services/messaging.py +++ b/agrirouter/messaging/services/messaging.py @@ -6,7 +6,7 @@ from agrirouter.messaging.encode import encode_message from agrirouter.messaging.enums import TechnicalMessageType from agrirouter.messaging.messages import EncodedMessage -from agrirouter.messaging.parameters.dto import MessageParameters, MessagingParameters +from agrirouter.messaging.parameters.dto import MessagingParameters from agrirouter.messaging.parameters.service import MessageHeaderParameters, MessagePayloadParameters, \ CapabilityParameters, FeedConfirmParameters, FeedDeleteParameters, ListEndpointsParameters, \ SubscriptionParameters, QueryHeaderParameters, QueryMessageParameters diff --git a/agrirouter/onboarding/headers.py b/agrirouter/onboarding/headers.py index fb598310..7843e2ad 100644 --- a/agrirouter/onboarding/headers.py +++ b/agrirouter/onboarding/headers.py @@ -1,5 +1,3 @@ -import base64 - from agrirouter.constants.media_types import ContentTypes diff --git a/agrirouter/onboarding/onboarding.py b/agrirouter/onboarding/onboarding.py index 74ded694..3119e181 100644 --- a/agrirouter/onboarding/onboarding.py +++ b/agrirouter/onboarding/onboarding.py @@ -1,5 +1,3 @@ -import json - import requests from agrirouter.environments.environmental_services import EnvironmentalService diff --git a/agrirouter/onboarding/parameters.py b/agrirouter/onboarding/parameters.py index bb62ae2e..1895c9fe 100644 --- a/agrirouter/onboarding/parameters.py +++ b/agrirouter/onboarding/parameters.py @@ -1,5 +1,3 @@ -from datetime import datetime - from agrirouter.constants.media_types import ContentTypes from agrirouter.onboarding.enums import CertificateTypes from agrirouter.utils.utc_time_util import now_as_utc_str diff --git a/agrirouter/revoking/headers.py b/agrirouter/revoking/headers.py index 0c1770f2..50652d5a 100644 --- a/agrirouter/revoking/headers.py +++ b/agrirouter/revoking/headers.py @@ -23,4 +23,4 @@ def _set_params(self, application_id: str, signature: str, content_type: str): if signature: header["X-Agrirouter-Signature"] = signature - self.params = header \ No newline at end of file + self.params = header