-
Notifications
You must be signed in to change notification settings - Fork 6
139: Split up S2Connection into a sync and async interface as well as decoupling the underlying medium e.g. websockets, mqtt and d-bus interfaces #144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
lfse-slafleur
merged 18 commits into
main
from
139-s2connection-should-have-an-async-capable-interface-next-to-the-sync-interface
Mar 19, 2026
Merged
Changes from 12 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
198acb7
139: Moving big chunks of code places.
lfse-slafleur 7af8771
139: Finish up first draft before executing it.
lfse-slafleur 69ebb06
139: Get async mostly to work, started on getting sync to work.
lfse-slafleur 8578468
Merge branch 'main' into 139-s2connection-should-have-an-async-capabl…
lfse-slafleur e1c98b8
139: Add a bunch of stuff. Threading/task management is now clear for…
lfse-slafleur bfaaab0
139: Some fixes.
lfse-slafleur 18b8919
139: Fix all linting issues.
lfse-slafleur ad677fc
139: Fix all linting and typing issues.
lfse-slafleur 6b4fa58
Merge branch 'main' into 139-s2connection-should-have-an-async-capabl…
lfse-slafleur fb15159
139: Propagate asset details from ResourceManagerHandler to all under…
lfse-slafleur d1d2231
139: Fix missing return value in func sig.
lfse-slafleur 2e43cb9
Merge branch 'main' into 139-s2connection-should-have-an-async-capabl…
lfse-slafleur fb96895
139: Add examples on how to perform another task after the connection…
lfse-slafleur 9aff094
139: Add functionality to let ws_medium disconnect.
lfse-slafleur 4831386
139: websockets import should be behind the try import.
lfse-slafleur a2ce80d
139: Fix typing and linting issues and add docs regarding the verify_…
lfse-slafleur 6537af3
139: Document the send_and_await_reception_status functions for both …
lfse-slafleur 5c0f1dd
139: Add DDBC control type handlers in class based approach.
lfse-slafleur File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,202 @@ | ||
| import argparse | ||
| import asyncio | ||
| import logging | ||
| import sys | ||
| import uuid | ||
| import signal | ||
| import datetime | ||
|
|
||
| from s2python.connection.types import S2ConnectionEventsAndMessages, SendOkayRunAsync | ||
| from s2python.common import ( | ||
| Duration, | ||
| Role, | ||
| RoleType, | ||
| Commodity, | ||
| Currency, | ||
| NumberRange, | ||
| PowerRange, | ||
| CommodityQuantity, | ||
| ) | ||
| from s2python.frbc import ( | ||
| FRBCInstruction, | ||
| FRBCSystemDescription, | ||
| FRBCActuatorDescription, | ||
| FRBCStorageDescription, | ||
| FRBCOperationMode, | ||
| FRBCOperationModeElement, | ||
| FRBCFillLevelTargetProfile, | ||
| FRBCFillLevelTargetProfileElement, | ||
| FRBCStorageStatus, | ||
| FRBCActuatorStatus, | ||
| ) | ||
| from s2python.connection import AssetDetails | ||
| from s2python.connection.async_ import S2AsyncConnection, WebsocketClientMedium | ||
| from s2python.connection.async_.control_type.class_based import ( | ||
| FRBCControlType, | ||
| NoControlControlType, | ||
| ResourceManagerHandler, | ||
| ) | ||
|
|
||
| logger = logging.getLogger("s2python") | ||
| logger.addHandler(logging.StreamHandler(sys.stdout)) | ||
| logger.setLevel(logging.DEBUG) | ||
|
|
||
|
|
||
| class MyFRBCControlType(FRBCControlType): | ||
| async def handle_instruction( | ||
| self, | ||
| connection: S2AsyncConnection, | ||
| msg: S2ConnectionEventsAndMessages, | ||
| send_okay: SendOkayRunAsync, | ||
| ) -> None: | ||
| if not isinstance(msg, FRBCInstruction): | ||
| raise RuntimeError( | ||
| f"Expected an FRBCInstruction but received a message of type {type(msg)}." | ||
| ) | ||
| print(f"I have received the message {msg} from {connection}") | ||
|
|
||
| async def activate(self, connection: S2AsyncConnection) -> None: | ||
| print("The control type FRBC is now activated.") | ||
|
|
||
| print("Time to send a FRBC SystemDescription") | ||
| actuator_id = uuid.uuid4() | ||
| operation_mode_id = uuid.uuid4() | ||
| await connection.send_msg_and_await_reception_status( | ||
| FRBCSystemDescription( | ||
| message_id=uuid.uuid4(), | ||
| valid_from=datetime.datetime.now(tz=datetime.timezone.utc), | ||
| actuators=[ | ||
| FRBCActuatorDescription( | ||
| id=actuator_id, | ||
| operation_modes=[ | ||
| FRBCOperationMode( | ||
| id=operation_mode_id, | ||
| elements=[ | ||
| FRBCOperationModeElement( | ||
| fill_level_range=NumberRange( | ||
| start_of_range=0.0, end_of_range=100.0 | ||
| ), | ||
| fill_rate=NumberRange( | ||
| start_of_range=-5.0, end_of_range=5.0 | ||
| ), | ||
| power_ranges=[ | ||
| PowerRange( | ||
| start_of_range=-200.0, | ||
| end_of_range=200.0, | ||
| commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, | ||
| ) | ||
| ], | ||
| ) | ||
| ], | ||
| diagnostic_label="Load & unload battery", | ||
| abnormal_condition_only=False, | ||
| ) | ||
| ], | ||
| transitions=[], | ||
| timers=[], | ||
| supported_commodities=[Commodity.ELECTRICITY], | ||
| ) | ||
| ], | ||
| storage=FRBCStorageDescription( | ||
| fill_level_range=NumberRange(start_of_range=0.0, end_of_range=100.0), | ||
| fill_level_label="%", | ||
| diagnostic_label="Imaginary battery", | ||
| provides_fill_level_target_profile=True, | ||
| provides_leakage_behaviour=False, | ||
| provides_usage_forecast=False, | ||
| ), | ||
| ) | ||
| ) | ||
| print("Also send the target profile") | ||
|
|
||
| await connection.send_msg_and_await_reception_status( | ||
| FRBCFillLevelTargetProfile( | ||
| message_id=uuid.uuid4(), | ||
| start_time=datetime.datetime.now(tz=datetime.timezone.utc), | ||
| elements=[ | ||
| FRBCFillLevelTargetProfileElement( | ||
| duration=Duration.from_milliseconds(30_000), | ||
| fill_level_range=NumberRange(start_of_range=20.0, end_of_range=30.0), | ||
| ), | ||
| FRBCFillLevelTargetProfileElement( | ||
| duration=Duration.from_milliseconds(300_000), | ||
| fill_level_range=NumberRange(start_of_range=40.0, end_of_range=50.0), | ||
| ), | ||
| ], | ||
| ) | ||
| ) | ||
|
|
||
| print("Also send the storage status.") | ||
| await connection.send_msg_and_await_reception_status( | ||
| FRBCStorageStatus(message_id=uuid.uuid4(), present_fill_level=10.0) | ||
| ) | ||
|
|
||
| print("Also send the actuator status.") | ||
| await connection.send_msg_and_await_reception_status( | ||
| FRBCActuatorStatus( | ||
| message_id=uuid.uuid4(), | ||
| actuator_id=actuator_id, | ||
| active_operation_mode_id=operation_mode_id, | ||
| operation_mode_factor=0.5, | ||
| ) | ||
| ) | ||
|
|
||
| async def deactivate(self, connection: S2AsyncConnection) -> None: | ||
| print("The control type FRBC is now deactivated.") | ||
|
|
||
|
|
||
| class MyNoControlControlType(NoControlControlType): | ||
| async def activate(self, connection: S2AsyncConnection) -> None: | ||
| print("The control type NoControl is now activated.") | ||
|
|
||
| async def deactivate(self, connection: S2AsyncConnection) -> None: | ||
| print("The control type NoControl is now deactivated.") | ||
|
|
||
|
|
||
| async def start_s2_session(url, rm_id: uuid.UUID): | ||
| # Configure a resource manager | ||
| rm_handler = ResourceManagerHandler( | ||
| asset_details=AssetDetails( | ||
| resource_id=rm_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], | ||
| ), | ||
| control_types=[MyFRBCControlType(), MyNoControlControlType()], | ||
| ) | ||
|
|
||
| # Setup the underlying websocket connection | ||
| ws_medium = WebsocketClientMedium(url=url, verify_certificate=False) | ||
| await ws_medium.connect() | ||
|
|
||
| # Configure the S2 connection on top of the websocket connection | ||
| s2_conn = S2AsyncConnection(medium=ws_medium) | ||
| rm_handler.register_handlers(s2_conn) | ||
|
|
||
| eventloop = asyncio.get_running_loop() | ||
|
|
||
| async def stop(): | ||
| print("Received signal. Will stop S2 connection.") | ||
| await s2_conn.stop() | ||
|
|
||
| eventloop.add_signal_handler(signal.SIGINT, lambda: eventloop.create_task(stop())) | ||
| eventloop.add_signal_handler(signal.SIGTERM, lambda: eventloop.create_task(stop())) | ||
| await s2_conn.run() | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| parser = argparse.ArgumentParser(description="A simple S2 reseource manager example.") | ||
| RM_ID = uuid.uuid4() | ||
| parser.add_argument( | ||
| "--endpoint", | ||
| type=str, | ||
| required=False, | ||
| help=f"WebSocket endpoint uri for the server (CEM) e.g. ws://localhost:8003/ws/{RM_ID}", | ||
| default=f"ws://localhost:8003/ws/{RM_ID}", | ||
| ) | ||
| args = parser.parse_args() | ||
|
|
||
| asyncio.run(start_s2_session(args.endpoint, RM_ID)) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.