From b8443f8b6a83281105e1c808d133a35fbe727990 Mon Sep 17 00:00:00 2001 From: Maurice Hendrix Date: Fri, 14 Mar 2025 09:43:21 +0100 Subject: [PATCH 1/2] started getting pairing details and decrypring challenge --- examples/example_frbc_rm.py | 60 ++++++++++++++---------- examples/example_with_pairing_frbc_rm.py | 37 +++++++++++++++ src/s2python/generated/gen_s2_pairing.py | 14 +++--- src/s2python/s2_pairing.py | 16 +++---- 4 files changed, 87 insertions(+), 40 deletions(-) create mode 100644 examples/example_with_pairing_frbc_rm.py diff --git a/examples/example_frbc_rm.py b/examples/example_frbc_rm.py index 774d936..8c2390e 100644 --- a/examples/example_frbc_rm.py +++ b/examples/example_frbc_rm.py @@ -1,3 +1,6 @@ +import argparse +import re +from functools import partial import logging import sys import uuid @@ -150,30 +153,35 @@ def activate(self, conn: S2Connection) -> None: def deactivate(self, conn: S2Connection) -> None: print("The control type NoControl is now deactivated.") - -s2_conn = S2Connection( - url="ws://localhost:8080/backend/rm/s2python-frbc/cem/dummy_model/ws", - role=EnergyManagementRole.RM, - control_types=[MyFRBCControlType(), MyNoControlControlType()], - asset_details=AssetDetails( - resource_id=str(uuid.uuid4()), - name="Some asset", - instruction_processing_delay=Duration.from_milliseconds(20), - roles=[Role(role=RoleType.ENERGY_CONSUMER, commodity=Commodity.ELECTRICITY)], - currency=Currency.EUR, - provides_forecast=False, - provides_power_measurements=[CommodityQuantity.ELECTRIC_POWER_L1], - ), - reconnect=True, -) - - -def stop(signal_num, _current_stack_frame): +def stop(s2_connection, signal_num, _current_stack_frame): print(f"Received signal {signal_num}. Will stop S2 connection.") - s2_conn.stop() - - -signal.signal(signal.SIGINT, stop) -signal.signal(signal.SIGTERM, stop) - -s2_conn.start_as_rm() + s2_connection.stop() + +def start_s2_session(url, client_node_id=str(uuid.uuid4())): + s2_conn = S2Connection( + url=url, + role=EnergyManagementRole.RM, + control_types=[MyFRBCControlType(), MyNoControlControlType()], + asset_details=AssetDetails( + resource_id=client_node_id, + name="Some asset", + instruction_processing_delay=Duration.from_milliseconds(20), + roles=[Role(role=RoleType.ENERGY_CONSUMER, commodity=Commodity.ELECTRICITY)], + currency=Currency.EUR, + provides_forecast=False, + provides_power_measurements=[CommodityQuantity.ELECTRIC_POWER_L1] + ), + reconnect=True, + verify_certificate=False + ) + signal.signal(signal.SIGINT, partial(stop, s2_conn)) + signal.signal(signal.SIGTERM, partial(stop, s2_conn)) + + s2_conn.start_as_rm() + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="A simple S2 reseource manager example.") + parser.add_argument('endpoint', type=str, help="WebSocket endpoint uri for the server (CEM) e.h. ws://localhost:8080/websocket/s2/my-first-websocket-rm") + args = parser.parse_args() + + start_s2_session(args.endpoint) diff --git a/examples/example_with_pairing_frbc_rm.py b/examples/example_with_pairing_frbc_rm.py new file mode 100644 index 0000000..3cc579c --- /dev/null +++ b/examples/example_with_pairing_frbc_rm.py @@ -0,0 +1,37 @@ +import argparse +import re +import uuid +import logging + +from example_frbc_rm import start_s2_session +from s2python.s2_pairing import PairingDetails, S2Pairing +from s2python.generated.gen_s2_pairing import S2NodeDescription, Deployment +from s2python.generated.gen_s2 import EnergyManagementRole + +logger = logging.getLogger("s2python") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="A simple S2 reseource manager example.") + parser.add_argument('endpoint', type=str, help="Rest endpoint to start S2 pairing. E.g. https://localhost/requestPairing") + parser.add_argument('pairing_token', type=str, help="The pairing toekn for teh endpoint. You should get this from the S2 server e.g. ca14fda4") + args = parser.parse_args() + + nodeDescription: S2NodeDescription = S2NodeDescription(brand="TNO", + logoUri = "https://www.tno.nl/publish/pages/5604/tno-logo-1484x835_003_.jpg", + type = "demo frbc example", + modelName = "S2 pairing example stub", + userDefinedName = "TNO S2 pairing example for frbc", + role = EnergyManagementRole.RM, + deployment = Deployment.LAN) + client_node_id: str = str(uuid.uuid4()) + + pairing: S2Pairing = S2Pairing(request_pairing_endpoint = args.endpoint, + token = args.pairing_token, + s2_client_node_description = nodeDescription, + client_node_id = client_node_id ) + + logger.info(f'Pairing details: \n{pairing.pairing_details}') + #print(pairing.pairing_details) + + start_s2_session(pairing.pairing_details.connection_details.connectionUri) \ No newline at end of file diff --git a/src/s2python/generated/gen_s2_pairing.py b/src/s2python/generated/gen_s2_pairing.py index 7ed825d..df45a08 100644 --- a/src/s2python/generated/gen_s2_pairing.py +++ b/src/s2python/generated/gen_s2_pairing.py @@ -8,16 +8,18 @@ from typing import List from pydantic import BaseModel, ConfigDict, Field -from s2python.common import EnergyManagementRole as S2Role - -class Deployment(Enum): +class S2Role(str, Enum): + CEM = 'CEM' + RM = 'RM' + +class Deployment(str, Enum): WAN = 'WAN' LAN = 'LAN' -class Protocols(Enum): +class Protocols(str, Enum): WebSocketSecure = 'WebSocketSecure' @@ -50,7 +52,7 @@ class PairingRequest(BaseModel): token: str publicKey: str s2ClientNodeId: str - s2ClientNodeDescription: str + s2ClientNodeDescription: S2NodeDescription supportedProtocols: List[Protocols] @@ -59,7 +61,7 @@ class PairingResponse(BaseModel): extra='forbid', ) s2ServerNodeId: str - serverNodeDescription: str + serverNodeDescription: S2NodeDescription requestConnectionUri: str diff --git a/src/s2python/s2_pairing.py b/src/s2python/s2_pairing.py index c28f8fd..d5570bf 100644 --- a/src/s2python/s2_pairing.py +++ b/src/s2python/s2_pairing.py @@ -5,7 +5,7 @@ from typing import Tuple, Union import requests -from jwskate import JweCompact, Jwk +from jwskate import JweCompact, Jwk, Jwt from binapy.binapy import BinaPy from s2python.generated.gen_s2_pairing import (Protocols, @@ -77,7 +77,6 @@ def _pair(self) -> None: self._paring_timestamp = datetime.datetime.now() rsa_key_pair = Jwk.generate_for_alg(KEY_ALGORITHM).with_kid_thumbprint() - pairing_request: PairingRequest = PairingRequest(token=self._token, publicKey=rsa_key_pair.public_jwk().to_pem(), s2ClientNodeId=self._client_node_id, @@ -85,11 +84,11 @@ def _pair(self) -> None: supportedProtocols=self._supported_protocols) response = requests.post(self._request_pairing_endpoint, - json=pairing_request.model_dump_json(), - timeout=REQTEST_TIMEOUT, + json = pairing_request.dict(), + timeout = REQTEST_TIMEOUT, verify = self._verify_certificate) response.raise_for_status() - pairing_response: PairingResponse = PairingResponse.parse_raw(response.json()) + pairing_response: PairingResponse = PairingResponse.parse_raw(response.text) connection_request: ConnectionRequest = ConnectionRequest(s2ClientNodeId=self._client_node_id, supportedProtocols=self._supported_protocols) @@ -102,12 +101,13 @@ def _pair(self) -> None: 'requestConnection') response = requests.post(restest_pairing_uri, - json=connection_request.model_dump_json(), - timeout=REQTEST_TIMEOUT, + json = connection_request.dict(), + timeout = REQTEST_TIMEOUT, verify = self._verify_certificate) response.raise_for_status() - connection_details: ConnectionDetails = ConnectionDetails.parse_raw(response.json()) + connection_details: ConnectionDetails = ConnectionDetails.parse_raw(response.text) challenge = JweCompact(connection_details.challenge).decrypt(rsa_key_pair) + challenge = Jwt.unprotected(JweCompact(connection_details.challenge).decrypt(rsa_key_pair)) self._pairing_details = PairingDetails(pairing_response, connection_details, challenge) From 6421621b0310bf643addcdb8e09b2df4efeaa219 Mon Sep 17 00:00:00 2001 From: Maurice Hendrix Date: Fri, 14 Mar 2025 09:46:40 +0100 Subject: [PATCH 2/2] catch up with changes in main to catch_and_convert_exceptions --- src/s2python/validate_values_mixin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/s2python/validate_values_mixin.py b/src/s2python/validate_values_mixin.py index cc9c6fd..fa4a8d7 100644 --- a/src/s2python/validate_values_mixin.py +++ b/src/s2python/validate_values_mixin.py @@ -59,7 +59,10 @@ def inner(*args: List[Any], **kwargs: Dict[str, Any]) -> Any: return inner -def catch_and_convert_exceptions(input_class: Type[S2MessageComponent[B_co]]) -> Type[S2MessageComponent[B_co]]: +S = TypeVar("S", bound=S2MessageComponent) + + +def catch_and_convert_exceptions(input_class: Type[S]) -> Type[S]: input_class.__init__ = convert_to_s2exception(input_class.__init__) # type: ignore[method-assign] input_class.__setattr__ = convert_to_s2exception(input_class.__setattr__) # type: ignore[method-assign] input_class.model_validate_json = convert_to_s2exception( # type: ignore[method-assign]