Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
7630c86
feat: create FillRateBasedControlTUNES which implements an ad-hoc FRB…
Aug 7, 2024
3b3ca0b
make session optional
Aug 7, 2024
473ca06
docs: fix typo
Flix6x Sep 30, 2024
8f8bbab
docs: fix typo
Flix6x Oct 29, 2024
8f7d369
fix: pin pandas
Flix6x Nov 5, 2024
768f4fd
docs: comment on tests
Flix6x Nov 5, 2024
bd9fcdf
feature: expose translation strategy further up the chain of methods
Flix6x Nov 5, 2024
5e96ea5
Merge remote-tracking branch 'refs/remotes/origin/main' into feature/…
Flix6x Nov 5, 2024
f32d392
style: black
Flix6x Nov 5, 2024
050eb7d
style: update black
Flix6x Nov 5, 2024
f920294
apply pre-commit
Nov 5, 2024
6f78a2d
per-comit on setup
Nov 5, 2024
3c14ef7
chore: use (dev) released s2-python version
Flix6x Nov 5, 2024
dfe3cdc
Merge remote-tracking branch 'refs/remotes/origin/main' into feature/…
Flix6x Nov 12, 2024
ce7bd07
Merge remote-tracking branch 'origin/feature/TUNES/send-system-descri…
Flix6x Nov 12, 2024
f894198
fix: update s2-python
Flix6x Dec 6, 2024
5f1ea21
fix: relax Pandas constraint
Flix6x Dec 10, 2024
5f61680
fix: fetch whole history, incl. previous dev tags
Flix6x Dec 10, 2024
43fe6ac
rename start_session() to more fitting ensure_session(); fix black/fl…
nhoening Oct 2, 2024
50f889e
do not require session on client.init; fix tests for recent port conf…
nhoening Oct 2, 2024
36ce4f6
add deployment by tags to developer docs
nhoening Oct 2, 2024
133d2b9
Move comments in Readme examples to end of lines
nhoening Oct 8, 2024
24bf44b
chore: pin s2-python to dev release that uses Pydantic 1 (#89)
Flix6x Nov 12, 2024
42266aa
Update README.rst
VladIftime Nov 29, 2024
d00a882
client.get_accout()
VladIftime Dec 10, 2024
e62f991
fix: mock user authentication by setting a dummy access_token
Flix6x Dec 10, 2024
9fbefd4
feat: lacking a dedicated API endpoint, we fetch all accessible users…
Flix6x Dec 10, 2024
adc97f4
PPBC
VladIftime Dec 13, 2024
b33b406
applied black formatting
VladIftime Dec 13, 2024
fd34109
applied black formatting test_client.py
VladIftime Dec 13, 2024
9b65284
Adding PPBC importer and CEM test
VladIftime Dec 17, 2024
e5e2647
feat: create FillRateBasedControlTUNES which implements an ad-hoc FRB…
Aug 7, 2024
dac5f6a
make session optional
Aug 7, 2024
d90c5f9
docs: fix typo
Flix6x Sep 30, 2024
d32efd2
docs: fix typo
Flix6x Oct 29, 2024
963ea53
fix: pin pandas
Flix6x Nov 5, 2024
1cdc071
docs: comment on tests
Flix6x Nov 5, 2024
a29adbc
feature: expose translation strategy further up the chain of methods
Flix6x Nov 5, 2024
6a01983
style: black
Flix6x Nov 5, 2024
cdac191
style: update black
Flix6x Nov 5, 2024
29138fb
apply pre-commit
Nov 5, 2024
5bea6b4
chore: use (dev) released s2-python version
Flix6x Nov 5, 2024
551ff51
per-comit on setup
Nov 5, 2024
d94c42b
fix: update s2-python
Flix6x Dec 6, 2024
ff11ab9
fix: relax Pandas constraint
Flix6x Dec 10, 2024
e6f7a44
Created tests for PPBC. Passed all the tests. One test left.
VladIftime Dec 18, 2024
f842424
chore: close test clients at the end of each relevant async test (#90)
Flix6x Dec 5, 2024
38a9f36
Get client feature request (#94)
VladIftime Dec 13, 2024
bdf0684
Revert unwanted parts of "Get client feature request (#94)"
Flix6x Dec 13, 2024
170fe72
Update readme (#96)
VladIftime Dec 25, 2024
4579ba7
refactor: simplify assignment in PPBC class
VladIftime Dec 25, 2024
d8f131f
FRBC individual tests
VladIftime Dec 25, 2024
ee9e83d
PPBC RM test
VladIftime Dec 25, 2024
faab9d8
PPBC RM test
VladIftime Dec 25, 2024
63cd6ed
PPBC activation controll type by RM
VladIftime Dec 25, 2024
e2ab228
PPBC message handlers skeleton
VladIftime Dec 25, 2024
8a7c23c
PPBC controll type handler rounting implemented
VladIftime Dec 25, 2024
7bc201c
Removed accidental public key
VladIftime Dec 25, 2024
d6bce53
"Updated PPBC module in flexmeasures client: improved code readabilit…
VladIftime Jan 2, 2025
9b7dc34
Merge branch 'feature/TUNES/send-system-description-to-fm' into VladI…
VladIftime Jan 10, 2025
c8d7e26
reset to right commit
VladIftime Jan 10, 2025
ff79b06
Merge remote-tracking branch 'refs/remotes/origin/main' into feature/…
Flix6x Jan 11, 2025
f4dd0c8
Merge remote-tracking branch 'refs/remotes/origin/feature/TUNES/send-…
Flix6x Jan 11, 2025
ea31300
revert: linebreaks
Flix6x Jan 11, 2025
79d4d96
dev: try installing s2-python from branch containing required imports
Flix6x Jan 11, 2025
2923aa9
chore: point to s2-python release with PPBC scaffolding
Flix6x Jan 13, 2025
bb427ad
chore: add reason for minimum version
Flix6x Jan 13, 2025
a71f1fa
dev: unpin pydantic (let it go to v2)
Flix6x Jan 13, 2025
72a0c65
Removed irrelevant file
VladIftime Jan 13, 2025
1210eec
Removed irrelevant file
VladIftime Jan 13, 2025
29a09b6
fix: test fixture
Flix6x Jan 15, 2025
852ea26
fix: client returns timezone aware datetimes
Flix6x Jan 15, 2025
78176dc
fix: use uuid4
Flix6x Jan 15, 2025
e774e47
fix: fet correct operation mode ID representing that the initial oper…
Flix6x Jan 15, 2025
ff242d8
style: black
Flix6x Jan 15, 2025
38b730d
style: isort
Flix6x Jan 15, 2025
475d094
refactor: use get_unique_id()
Flix6x Jan 15, 2025
44f884b
fix: test should rely on exact IDs that were meant, not new IDs
Flix6x Jan 15, 2025
36b3bdc
refactor: prefer timezone.utc (shorter, clearer)
Flix6x Jan 15, 2025
0b2ad47
docs: fix typos
Flix6x Jan 15, 2025
e38cbb1
docs: add todo
Flix6x Jan 15, 2025
0843602
docs: add todo
Flix6x Jan 15, 2025
90a4f72
fix: call to FM scheduler in FRBCSimple.trigger_schedule
Flix6x Jan 15, 2025
eea9807
docs: fix typo
Flix6x Jan 15, 2025
1015e16
Merge remote-tracking branch 'refs/remotes/origin/feature/TUNES/send-…
Flix6x Jan 15, 2025
33be489
Restored the wrapper and wrapper_test for s2 (used in simulations). S…
VladIftime Jan 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ jobs:
id-token: write
steps:
- uses: actions/checkout@v3
with: {fetch-depth: 0} # deep clone for setuptools-scm
- uses: actions/setup-python@v4
with: {python-version: "3.11"}
- name: Retrieve pre-built distribution files
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ repos:
- id: isort

- repo: https://github.com/psf/black
rev: 23.3.0
rev: 24.8.0
hooks:
- id: black
language_version: python3
Expand Down
8 changes: 4 additions & 4 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ python_requires >= 3.9
# For more information, check out https://semver.org/.
install_requires =
importlib-metadata; python_version<"3.8"
aiohttp
pandas
pydantic>=1.10.8,<2.0
s2-python==0.2.0.dev2
aiohttp<=3.9.1
pandas>=2.1.4
pydantic>=1.10.8
s2-python>=0.3.1 # minimum version adding PPBC classes
async_timeout

[options.packages.find]
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
PyScaffold helps you to put up the scaffold of your new Python project.
Learn more under: https://pyscaffold.org/
"""

from setuptools import setup

if __name__ == "__main__":
Expand Down
9 changes: 6 additions & 3 deletions src/flexmeasures_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import socket
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Any
from typing import Any, cast

import async_timeout
import pandas as pd
Expand Down Expand Up @@ -59,6 +59,9 @@ class FlexMeasuresClient:
session: ClientSession | None = None

def __post_init__(self):
if self.session is None:
self.session = ClientSession()

if not re.match(r".+\@.+\..+", self.email):
raise EmailValidationError(
f"{self.email} is not an email address format string"
Expand Down Expand Up @@ -102,7 +105,7 @@ def determine_port(self):

async def close(self):
"""Function to close FlexMeasuresClient session when all requests are done"""
await self.session.close()
await cast(ClientSession, self.session).close()

async def request(
self,
Expand Down Expand Up @@ -198,7 +201,7 @@ async def request_once(

"""Sends a single request to FlexMeasures and checks the response"""
self.ensure_session()
response = await self.session.request( # type: ignore
response = await cast(ClientSession, self.session).request(
method=method,
url=url,
params=params,
Expand Down
14 changes: 8 additions & 6 deletions src/flexmeasures_client/s2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ def wrap(*args, **kwargs):
# TODO: implement function __hash__ in ID that returns
# the value of __root__, this way we would be able to use
# the ID as key directly
self.incoming_messages[
get_message_id(incoming_message)
] = incoming_message
self.incoming_messages[get_message_id(incoming_message)] = (
incoming_message
)

outgoing_message = func(self, incoming_message)

self.outgoing_messages[
get_message_id(outgoing_message)
] = outgoing_message
self.outgoing_messages[get_message_id(outgoing_message)] = (
outgoing_message
)

return outgoing_message

Expand Down Expand Up @@ -80,6 +80,8 @@ class Handler:

outgoing_messages_status: SizeLimitOrderedDict

background_tasks: set

def __init__(self, max_size: int = 100) -> None:
"""
Handler
Expand Down
15 changes: 11 additions & 4 deletions src/flexmeasures_client/s2/cem.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,13 @@ def __init__(
def supports_control_type(self, control_type: ControlType):
return control_type in self._resource_manager_details.available_control_types

def close(self):
async def close(self):
self._is_closed = True

for control_type, handler in self._control_types_handlers.items():
print(control_type, handler)
await handler.close()

def is_closed(self):
return self._is_closed

Expand All @@ -92,9 +96,9 @@ def register_control_type(self, control_type_handler: ControlTypeHandler):
control_type_handler._sending_queue = self._sending_queue

# store control_type_handler
self._control_types_handlers[
control_type_handler._control_type
] = control_type_handler
self._control_types_handlers[control_type_handler._control_type] = (
control_type_handler
)

async def handle_message(self, message: Dict | pydantic.BaseModel | str):
"""
Expand Down Expand Up @@ -273,3 +277,6 @@ def handle_revoke_object(self, message: RevokeObject):
)

return get_reception_status(message, ReceptionStatusValues.OK)

async def send_message(self, message):
await self._sending_queue.put(message)
86 changes: 68 additions & 18 deletions src/flexmeasures_client/s2/control_types/FRBC/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class FRBC(ControlTypeHandler):
_timer_status_history: SizeLimitOrderedDict[str, FRBCTimerStatus]
_actuator_status_history: SizeLimitOrderedDict[str, FRBCActuatorStatus]
_storage_status_history: SizeLimitOrderedDict[str, FRBCStorageStatus]
background_tasks: set

def __init__(self, max_size: int = 100) -> None:
super().__init__(max_size)
Expand All @@ -51,6 +52,7 @@ def __init__(self, max_size: int = 100) -> None:
self._system_description_history = SizeLimitOrderedDict(max_size=max_size)
self._leakage_behaviour_history = SizeLimitOrderedDict(max_size=max_size)
self._usage_forecast_history = SizeLimitOrderedDict(max_size=max_size)
self.background_tasks = set()

@register(FRBCSystemDescription)
def handle_system_description(
Expand All @@ -62,24 +64,37 @@ def handle_system_description(
self._system_description_history[system_description_id] = message

# schedule trigger_schedule to run soon concurrently
asyncio.create_task(self.trigger_schedule(system_description_id))

task = asyncio.create_task(self.trigger_schedule(system_description_id))
self.background_tasks.add(
task
) # important to avoid a task disappearing mid-execution.
task.add_done_callback(self.background_tasks.discard)
return get_reception_status(message, status=ReceptionStatusValues.OK)

async def send_storage_status(self, status: FRBCStorageStatus):
raise NotImplementedError()
@register(FRBCUsageForecast)
def handle_usage_forecast(self, message: FRBCUsageForecast) -> pydantic.BaseModel:
message_id = str(message.message_id)

async def send_actuator_status(self, status: FRBCActuatorStatus):
raise NotImplementedError()
self._usage_forecast_history[message_id] = message

task = asyncio.create_task(self.send_usage_forecast(message))
self.background_tasks.add(
task
) # important to avoid a task disappearing mid-execution.
task.add_done_callback(self.background_tasks.discard)
return get_reception_status(message, status=ReceptionStatusValues.OK)

@register(FRBCStorageStatus)
def handle_storage_status(self, message: FRBCStorageStatus) -> pydantic.BaseModel:
message_id = str(message.message_id)

self._storage_status_history[message_id] = message

asyncio.create_task(self.send_storage_status(message))

task = asyncio.create_task(self.send_storage_status(message))
self.background_tasks.add(
task
) # important to avoid a task disappearing mid-execution.
task.add_done_callback(self.background_tasks.discard)
return get_reception_status(message, status=ReceptionStatusValues.OK)

@register(FRBCActuatorStatus)
Expand All @@ -88,29 +103,64 @@ def handle_actuator_status(self, message: FRBCActuatorStatus) -> pydantic.BaseMo

self._actuator_status_history[message_id] = message

asyncio.create_task(self.send_actuator_status(message))

task = asyncio.create_task(self.send_actuator_status(message))
self.background_tasks.add(
task
) # important to avoid a task disappearing mid-execution.
task.add_done_callback(self.background_tasks.discard)
return get_reception_status(message, status=ReceptionStatusValues.OK)

@register(FRBCLeakageBehaviour)
def handle_leakage_behaviour(
self, message: FRBCLeakageBehaviour
) -> pydantic.BaseModel:
# return get_reception_status(message, status=ReceptionStatusValues.OK)
raise NotImplementedError()
message_id = str(message.message_id)

@register(FRBCUsageForecast)
def handle_usage_forecast(self, message: FRBCUsageForecast) -> pydantic.BaseModel:
# return get_reception_status(message, status=ReceptionStatusValues.OK)
raise NotImplementedError()
self._leakage_behaviour_history[message_id] = message

async def trigger_schedule(self, system_description_id: str):
raise NotImplementedError()
task = asyncio.create_task(self.send_leakage_behaviour(message))
self.background_tasks.add(
task
) # important to avoid a task disappearing mid-execution.
task.add_done_callback(self.background_tasks.discard)
return get_reception_status(message, status=ReceptionStatusValues.OK)

@register(FRBCFillLevelTargetProfile)
def handle_fill_level_target_profile(
self, message: FRBCFillLevelTargetProfile
) -> pydantic.BaseModel:
message_id = str(message.message_id)

self._fill_level_target_profile_history[message_id] = message

task = asyncio.create_task(self.send_fill_level_target_profile(message))
self.background_tasks.add(
task
) # important to avoid a task disappearing mid-execution.
task.add_done_callback(self.background_tasks.discard)
return get_reception_status(message, status=ReceptionStatusValues.OK)

@register(FRBCTimerStatus)
def handle_frbc_timer_status(self, message: FRBCTimerStatus) -> pydantic.BaseModel:
return get_reception_status(message, status=ReceptionStatusValues.OK)

async def send_storage_status(self, status: FRBCStorageStatus):
raise NotImplementedError()

async def send_actuator_status(self, status: FRBCActuatorStatus):
raise NotImplementedError()

async def send_leakage_behaviour(self, leakage_behaviour: FRBCLeakageBehaviour):
raise NotImplementedError()

async def send_usage_forecast(self, usage_forecast: FRBCUsageForecast):
raise NotImplementedError()

async def send_fill_level_target_profile(
self, fill_level_target_profile: FRBCFillLevelTargetProfile
):
raise NotImplementedError()


class FRBCTest(FRBC):
"""Dummy class to simulate the triggering of a schedule."""
Expand Down
40 changes: 10 additions & 30 deletions src/flexmeasures_client/s2/control_types/FRBC/frbc_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,23 +75,6 @@ async def send_actuator_status(self, status: FRBCActuatorStatus):
duration=timedelta(minutes=15),
)

# await self._fm_client.post_measurements(
# self._soc_sensor_id
# )

# system_description = self.find_system_description_from_actuator()

# if system_description is None:
# return

# #for a
# if system_description is not None:

# self._system_description_history[]
# status.active_operation_mode_id
# status.actuator_id
# status.operation_mode_factor

async def trigger_schedule(self, system_description_id: str):
"""Translates S2 System Description into FM API calls"""

Expand All @@ -108,24 +91,21 @@ async def trigger_schedule(self, system_description_id: str):
return

# call schedule
schedule_id = await self._fm_client.trigger_storage_schedule(
schedule = await self._fm_client.trigger_and_get_schedule(
start=system_description.valid_from
+ self._valid_from_shift, # TODO: localize datetime
sensor_id=self._power_sensor_id,
production_price_sensor=self._price_sensor_id,
consumption_price_sensor=self._price_sensor_id,
soc_unit="MWh",
soc_at_start=soc_at_start, # TODO: use forecast of the SOC instead
flex_context=dict(
production_price_sensor=self._price_sensor_id,
consumption_price_sensor=self._price_sensor_id,
),
flex_model=dict(
soc_unit="MWh",
soc_at_start=soc_at_start, # TODO: use forecast of the SOC instead
),
duration=self._schedule_duration, # next 12 hours
# TODO: add SOC MAX AND SOC MIN FROM fill_level_range,
# this needs chages on the client
)

# wait for the schedule to finish
schedule = await self._fm_client.get_schedule(
sensor_id=self._power_sensor_id,
schedule_id=schedule_id,
duration=self._schedule_duration,
# this needs changes on the client
)

# translate FlexMeasures schedule into instructions. SOC -> Power -> PowerFactor
Expand Down
Loading