Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
43bb171
fix(TUNES): record a leakage behaviour instead of refusing it
Flix6x Nov 5, 2026
30dc81e
chore(types): check every source file, including untyped function bodies
Flix6x Nov 5, 2026
ff1304c
ci: measure the coverage of the S2 tests
Flix6x Nov 5, 2026
372a39a
chore(lint): no print() in the S2 code that runs unattended
Flix6x Nov 5, 2026
9ae5a5c
docs(changelog): FillRateBasedControlTUNES records a leakage behaviour
Flix6x Nov 5, 2026
f791bdb
chore: close the gaps an adversarial review found in the instruments
Flix6x Nov 5, 2026
5ff631d
fix(FRBC): answer by the element that applies, whatever order S2 list…
Flix6x Nov 5, 2026
d0b1c1d
docs(changelog): the element that applies, and what a standby mode st…
Flix6x Nov 5, 2026
b572a86
fix(FRBC): plan with the element where the schedule starts, not the l…
Flix6x Nov 5, 2026
9587c94
fix(TUNES): an operation mode without a label no longer stops planning
Flix6x Nov 5, 2026
4585f2f
fix(client): drop a content-type check that could not fire, nor be pr…
Flix6x Nov 5, 2026
c341c4a
chore(types): type check against what the code imports, S2 messages i…
Flix6x Nov 5, 2026
1928d56
docs(changelog): an unlabelled TUNES mode, and two helpers that never…
Flix6x Nov 5, 2026
2a644e6
fix: correct what this branch claimed, and test what review found unt…
Flix6x Nov 5, 2026
4d5435e
fix(FRBC): every rate per second, as S2 states it, under the instruct…
Flix6x Nov 5, 2026
d0ae8ce
docs(changelog): every rate per second under the instructions
Flix6x Nov 5, 2026
2387288
fix(FRBC): follow each step as a resource manager does, which review …
Flix6x Nov 5, 2026
2f8902c
fix(example): a heat pump with a buffer that is planned for, instruct…
Flix6x Nov 5, 2026
fa3dc59
docs(changelog): the example, and the planning triggers it needed
Flix6x Nov 5, 2026
10f1cc7
fix(FRBC): a state of charge in any supported energy unit reaches Fle…
Flix6x Nov 5, 2026
5ba8ee2
docs(changelog): which energy units a handler takes, and what FlexMea…
Flix6x Nov 5, 2026
324e7f2
fix(FRBC): follow each step exactly, between the moments anything cha…
Flix6x Nov 5, 2026
40479ae
Merge remote-tracking branch 'origin/dev/s2-frbc-complete' into fix/s…
Flix6x Nov 5, 2026
392ba50
fix(example): follow only the latest plan, keep the forecasts current…
Flix6x Nov 5, 2026
e3a5567
fix(example, FRBC): what the second review of the example round found
Flix6x Nov 5, 2026
c7fe0a7
docs(changelog): describe the example round as it now stands
Flix6x Nov 5, 2026
41a8f63
fix(example, FRBC): what the third review of the example round found
Flix6x Nov 5, 2026
bc90b2b
fix(FRBC): plan from the next boundary, not the start of the step und…
Flix6x Nov 5, 2026
a5c0dcb
Merge remote-tracking branch 'origin/fix/s2-example-heat-pump' into f…
Flix6x Nov 5, 2026
9824ea5
style(FRBC): keep the soc-at-start TODO off the line it annotates
Flix6x Nov 5, 2026
93e3ae3
feat(FRBC): a production price of its own, an opt-in relaxed site cap…
Flix6x Nov 5, 2026
506e7ec
Merge remote-tracking branch 'origin/fix/frbc-energy-unit' into feat/…
Flix6x Nov 5, 2026
6408b6f
fix(FRBC): what the review of the energy-unit change found
Flix6x Nov 5, 2026
fd45a98
Merge remote-tracking branch 'origin/dev/s2-frbc-complete' into feat/…
Flix6x Nov 5, 2026
11e9a39
test(FRBC): an overtaken plan sends nothing, whether dropped before o…
Flix6x Nov 5, 2026
7c8ae45
docs(changelog): the handler options, the dropped translation, and as…
Flix6x Nov 5, 2026
47d295d
fix(FRBC): send relax_site_capacity either way, or leave it to FlexMe…
Flix6x Nov 5, 2026
bcdbd79
feat(example): the site's graphs show the buffer, its comfort band, a…
Flix6x Nov 5, 2026
cc54bb6
docs(changelog): the example site's graphs
Flix6x Nov 5, 2026
c466752
fix(example): set the site graphs in the form every FlexMeasures reads
Flix6x Nov 5, 2026
0028494
fix(S2): a lost confirmation does not stall a session, and a closed s…
Flix6x Nov 5, 2026
1cc6261
test(S2): a message without a type activates nothing
Flix6x Nov 5, 2026
9053b86
docs(changelog): implicit activation and session teardown
Flix6x Nov 5, 2026
6b1d3ea
fix(S2): take only the control type being selected as confirmed, and …
Flix6x Nov 5, 2026
69f5dbe
test(S2): a typeless message activates nothing while a selection is p…
Flix6x Nov 5, 2026
80ad945
feat(S2): a clock the CEM and its handlers go by, which a co-simulati…
Flix6x Nov 5, 2026
5b9793e
docs(changelog): the clock and the simulation metadata
Flix6x Nov 5, 2026
47c695a
fix(S2): what the review of the clock found
Flix6x Nov 5, 2026
6a12ea4
feat(S2): a site of its own for each resource manager, and the co-sim…
Flix6x Nov 5, 2026
bffe6c9
docs(changelog): sites per resource manager and the co-simulation stack
Flix6x Nov 5, 2026
65b359f
fix(S2): what the review of the sites per resource manager found
Flix6x Nov 5, 2026
ccab11b
feat(S2): record measured power by when it was measured, in watts, on…
Flix6x Nov 5, 2026
809b136
docs(changelog): the measurement recorder
Flix6x Nov 5, 2026
ab56606
fix(S2): what the review of the measurement recorder found
Flix6x Nov 5, 2026
b8aff1d
fix(examples): the co-simulation stack runs on a current FlexMeasures…
Flix6x Nov 5, 2026
40288e8
docs(changelog): the co-simulation stack on FlexMeasures 1.1
Flix6x Nov 5, 2026
4807388
docs(examples): what the co-simulation stack runs, and how to build i…
Flix6x Nov 5, 2026
f0c4d4e
fix(FRBC): a state of charge is known as of the handler's clock
Flix6x Nov 5, 2026
2b5d481
docs(changelog): the state of charge on the clock
Flix6x Nov 5, 2026
16a0b24
Merge remote-tracking branch 'origin/dev/s2-frbc-complete' into fix/s…
Flix6x Nov 5, 2026
00d3676
feat(FRBC): plan one schedule at a time, for the newest request
Flix6x Nov 5, 2026
77de62c
Merge remote-tracking branch 'origin/dev/s2-frbc-complete' into feat/…
Flix6x Nov 5, 2026
401599b
docs(changelog): one schedule at a time
Flix6x Nov 5, 2026
29845ea
fix(FRBC): a request out of date makes no further attempt, so the new…
Flix6x Nov 5, 2026
5564983
docs(examples): the instance settings a co-simulation needs
Flix6x Nov 5, 2026
ad9a12b
Merge remote-tracking branch 'origin/dev/s2-frbc-complete' into docs/…
Flix6x Nov 5, 2026
309e283
fix(FRBC): the declared fill-level range reaches the server, as a ban…
Flix6x Nov 5, 2026
e8f14bc
Merge remote-tracking branch 'origin/dev/s2-frbc-complete' into fix/s…
Flix6x Nov 5, 2026
c34155a
fix(FRBC): the flex model offers only what the actuator may use, and …
Flix6x Nov 5, 2026
9a1ddf6
fix(FRBC): what the review of the declared band found
Flix6x Nov 5, 2026
fe65dcd
test(FRBC): an empty profile covers no step
Flix6x Nov 5, 2026
f8c46b1
fix(FRBC): a mode for abnormal conditions does not set the charging e…
Flix6x Nov 5, 2026
c96e137
Merge remote-tracking branch 'origin/dev/s2-frbc-complete' into fix/s…
Flix6x Nov 5, 2026
7c45746
feat(client): post_sensor_data(confirm_ingestion=True) raises where i…
Flix6x Nov 5, 2026
f32b44d
fix(FRBC): a failure is answered or said out loud
Flix6x Nov 5, 2026
1eb62eb
fix(FRBC): what the review of "a failure is answered or said out loud…
Flix6x Nov 5, 2026
2557896
fix(S2): housekeeping, and tests that pin what they claim to
Flix6x Nov 5, 2026
9cf4baf
Merge remote-tracking branch 'origin/dev/s2-frbc-complete' into fix/s…
Flix6x Nov 6, 2026
e2d5e8d
fix(FRBC): guard the display-only band posts; say a negative top need…
Flix6x Nov 6, 2026
ab2b0dc
fix(S2): a message set again is the latest; what the review of housek…
Flix6x Nov 6, 2026
f15b9e2
fix(S2): what the sweep of the whole branch found
Flix6x Nov 6, 2026
fa18fdd
docs(changelog): say once what the S2 integration ships since v0.9.0
Flix6x Nov 6, 2026
96c81ce
Merge remote-tracking branch 'origin/dev/s2-frbc-complete' into docs/…
Flix6x Nov 6, 2026
ea0b1d0
fix(FRBC): comments that later changes made false, and the unguarded …
Flix6x Nov 6, 2026
2f89b72
fix(FRBC): warn about transitions only where they rule a switch out
Flix6x Nov 6, 2026
e35ffd3
docs(changelog): say each S2 change plainly
Flix6x Nov 8, 2026
95cda29
docs(changelog): keep what the plain rewrite lost or shifted
Flix6x Nov 8, 2026
00a6136
test(S2): cover what the handler base, the CEM and the example server…
Flix6x Nov 8, 2026
4816024
test(FRBC): time the long-schedule translation in CPU time
Flix6x Nov 8, 2026
e1c9e64
docs(S2): say docstrings and comments plainly
Flix6x Nov 8, 2026
9f03188
docs(S2): keep what the plain rewrite of the comments got wrong
Flix6x Nov 8, 2026
a2fa1b8
Merge remote-tracking branch 'origin/dev/s2-frbc-complete' into docs/…
Flix6x Nov 8, 2026
2e62cf2
Merge origin/main (#278, squash-merged) into dev/s2-frbc-complete
Flix6x Nov 8, 2026
ef5739f
fix(FRBC): record an actuator status that arrived before any system d…
Flix6x Nov 9, 2026
d7d9f6c
docs(changelog): link #287
Flix6x Nov 9, 2026
58f9a15
docs(changelog): say that one sensor holds the charging efficiency of…
Flix6x Nov 9, 2026
8299241
docs(changelog): split the charging efficiency entry into short sente…
Flix6x Nov 9, 2026
d38f63c
Merge dev/s2-frbc-complete (#287) into docs/s2-changelog-efficiency-s…
Flix6x Nov 9, 2026
ebdc615
feat(FRBC): record rates of fill level in a fill_rate_unit, by default W
Flix6x Nov 9, 2026
c983c0e
feat(site): create a site's power and rate sensors in W
Flix6x Nov 9, 2026
e102532
docs(changelog): fill_rate_unit and the site sensors' units (#290)
Flix6x Nov 9, 2026
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
208 changes: 127 additions & 81 deletions CHANGELOG-S2.rst

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,20 @@ Added

* ``server_version_at_least``, which answers whether the server is of a given version or newer, looking its version up where that is not known yet, for a caller that sends a field or calls an endpoint only where the server has it. Release generations are compared, so a development build such as ``0.33.0.dev26`` counts as ``0.33.0`` [`#248 <https://github.com/FlexMeasures/flexmeasures-client/pull/248>`__]
* ``ingestion_polling_interval``, ``ingestion_polling_max_interval`` and ``ingestion_polling_timeout``, which govern how the ingestion of posted sensor data is waited for, on a tighter profile than the ``job_polling_*`` settings give other background jobs [`#242 <https://github.com/FlexMeasures/flexmeasures-client/pull/242>`__]
* ``post_sensor_data`` takes ``confirm_ingestion``. With ``True``, it raises an error if it cannot confirm that FlexMeasures ingested the posted data, instead of logging this and carrying on. Use it if your code remembers what it has recorded, and would otherwise never record the data again [`#273 <https://github.com/FlexMeasures/flexmeasures-client/pull/273>`__]
* Optional ``rate_limit_notifier`` and ``job_status_notifier`` callbacks, to report rate limits and the progress of jobs being waited for, instead of logging them [`#225 <https://github.com/FlexMeasures/flexmeasures-client/pull/225>`__]
* ``get_automations``, ``get_automation``, ``add_automation``, ``update_automation``, ``delete_automation``, ``trigger_automation`` and ``trigger_and_await_automation``, to manage and run automations through the API on FlexMeasures v1.1.0 and up [`#230 <https://github.com/FlexMeasures/flexmeasures-client/pull/230>`__]

Changed
-------

* ``add_asset`` no longer requires a ``latitude`` and ``longitude``, which FlexMeasures does not require either, for an asset with no location, such as one standing for a community. ``generic_asset_type_id`` stays required, and half a location is refused [`#261 <https://github.com/FlexMeasures/flexmeasures-client/pull/261>`__]
* ``add_asset`` no longer requires a ``latitude`` and ``longitude``, just like FlexMeasures. This allows assets without a location, such as one that represents a community. ``generic_asset_type_id`` is still required, and a latitude without a longitude (or the other way around) is refused [`#261 <https://github.com/FlexMeasures/flexmeasures-client/pull/261>`__]
* ``post_sensor_data``, and the deprecated ``post_measurements`` it was renamed from, wait for a server of FlexMeasures 0.33.0 or above that ingests the posted data asynchronously, answering ``202 Accepted`` with a job id, to finish doing so, so that a caller can read the data back or schedule over it; a failed job raises ``JobFailedError``, while a job still pending at the timeout, or one that cannot be looked up, is logged as an error and leaves the post successful. Pass ``await_ingestion=False`` to return as soon as the data is accepted [`#242 <https://github.com/FlexMeasures/flexmeasures-client/pull/242>`__]

Removed
-------

* ``response_handling.check_content_type``, which refused an answer in a JSON variant that aiohttp parses, such as ``application/problem+json``, with an error that raised an ``AttributeError`` of its own when printed. Such an answer is now read like any other. A content type that is not JSON at all is still reported as a ``ConnectionError``, by aiohttp's own check [`#257 <https://github.com/FlexMeasures/flexmeasures-client/pull/257>`__]
* ``response_handling.check_content_type``. It refused responses in JSON variants that aiohttp can parse, such as ``application/problem+json``, and the error it raised caused an ``AttributeError`` of its own when printed. Such responses are now read like any other. A response that is not JSON at all is still reported as a ``ConnectionError``, by aiohttp's own check [`#257 <https://github.com/FlexMeasures/flexmeasures-client/pull/257>`__]

Fixed
-----
Expand Down
84 changes: 53 additions & 31 deletions src/flexmeasures_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,11 +459,11 @@ def report_rate_limit(message: str) -> None:
"Request retry timeout while connecting to the API."
) from exception

# request_once has already parsed this answer with aiohttp's json(),
# which refuses a content type that is not JSON, and the loop above reports that as a ConnectionError.
# A check that used to follow here refused the JSON variants aiohttp accepts too, such as application/problem+json,
# with an error that could not even be printed, so it is gone.
# A body that is not JSON under a JSON content type is refused by json() itself, as a JSONDecodeError.
# request_once has already parsed this response with aiohttp's json().
# json() refuses a content type that is not JSON, and the loop above reports that as a ConnectionError.
# A check used to follow here, but it also refused JSON variants that aiohttp accepts, such as application/problem+json.
# Its error could not even be printed, so the check was removed.
# A body that is not JSON under a JSON content type makes json() itself raise a JSONDecodeError.
return await response.json(), response.status

async def request_once(
Expand Down Expand Up @@ -664,6 +664,7 @@ async def post_sensor_data(
file_path: str | None = None,
belief_time_measured_instantly: bool = False,
await_ingestion: bool = True,
confirm_ingestion: bool = False,
):
"""
Post sensor data for the given time range.
Expand All @@ -676,19 +677,24 @@ async def post_sensor_data(

The method automatically chooses the appropriate API endpoint based on the provided parameters.

Since FlexMeasures 0.33.0, a server with an ingestion worker available answers ``202 Accepted`` with the id of a background job,
rather than ``200 OK``, and ingests the data after the request has returned.
By default this method then waits for that job to finish,
so that a caller which reads the data back, or asks for a schedule over it, sees what it just posted.
Since FlexMeasures 0.33.0, a server with an available ingestion worker responds ``202 Accepted`` instead of ``200 OK``,
with the id of a background job.
The server then ingests the data after the request has returned.
By default, this method waits for that job to finish.
This way, a caller that reads the data back, or asks for a schedule over it, sees the data it just posted.

:param await_ingestion: whether to wait for asynchronous ingestion to finish,
when the server answered ``202 Accepted`` with a job id.
Waiting is polled on the ``ingestion_polling_*`` profile, which is tighter than the one other jobs are polled on.
when the server responded ``202 Accepted`` with a job id.
The job is polled on the ``ingestion_polling_*`` profile, which is tighter than the profile for other jobs.
A job that fails raises :class:`JobFailedError`.
A job still pending when ``self.ingestion_polling_timeout`` is reached is logged as an error and otherwise ignored,
since the data may well land moments later, and a caller that has waited that long is better served by carrying on than by failing.
A job that is still pending when ``self.ingestion_polling_timeout`` is reached is logged as an error and otherwise ignored.
The data may well land moments later, and a caller that has waited that long is better off carrying on than failing.
Pass ``False`` to return as soon as the server has accepted the data.
A synchronous ``200 OK`` response is unaffected either way.
Either way, a synchronous ``200 OK`` response is unaffected.
:param confirm_ingestion: whether to raise when ingestion could not be confirmed while waiting for it, instead of logging and ignoring that.
If the job was still pending at the timeout, this raises :class:`JobTimeoutError`.
If looking the job up failed, this raises the error from the lookup.
Use this if the caller remembers having recorded the data, and would otherwise never record it again.

This function raises a ValueError when an unhandled status code is returned.
"""
Expand Down Expand Up @@ -740,6 +746,7 @@ async def post_sensor_data(
unit=unit,
prior=prior,
await_ingestion=await_ingestion,
confirm_ingestion=confirm_ingestion,
)
else:
# Type assertion to help the type checker understand that file_path is not None
Expand All @@ -752,6 +759,7 @@ async def post_sensor_data(
belief_time_measured_instantly=belief_time_measured_instantly,
unit=unit,
await_ingestion=await_ingestion,
confirm_ingestion=confirm_ingestion,
)

@staticmethod
Expand All @@ -767,20 +775,26 @@ def _ingestion_job_id(response) -> str | None:
job_id = response.get("job") or response.get("job_id")
return job_id if isinstance(job_id, str) else None

async def _await_sensor_data_ingestion(self, job_id: str) -> None:
async def _await_sensor_data_ingestion(
self, job_id: str, confirm_ingestion: bool = False
) -> None:
"""Wait for an ingestion job to finish, so that the data it carries can be read back.

Waiting is left to :func:`wait_for_job`, so ingestion is backed off like every other job this client waits for,
and reported through the same ``job_status_notifier``, on the ``ingestion_polling_*`` intervals rather than the ``job_polling_*`` ones.

A failed job propagates as :class:`JobFailedError`, for the caller to handle like any other failed post.
Nothing else does.
A timeout is logged as an error and otherwise ignored, for the reason given in :func:`post_sensor_data`,
and so is a failure to look the job up at all, which says nothing about the data:
the server accepted that, and a job record can be gone, or answered for by something in front of the server, by the time it is asked about.
Raising there would report a post that succeeded as a post that failed.
A lookup that 404s is reported as a ``ConnectionError`` by :func:`request`, which wraps every client error that way,
so that is among the ones caught here, alongside the ``ValueError`` an unexpected status code raises.
Waiting is delegated to :func:`wait_for_job`.
So ingestion is backed off like every other job this client waits for,
and reported through the same ``job_status_notifier``.
The only difference is that it uses the ``ingestion_polling_*`` intervals, instead of the ``job_polling_*`` ones.

A failed job raises :class:`JobFailedError`, which the caller handles like any other failed post.
By default, no other error is raised.
A timeout is logged as an error and otherwise ignored, for the reason given in :func:`post_sensor_data`.
The same goes for a failure to look the job up at all, which says nothing about the data.
After all, the server did accept the data.
By the time the job is looked up, its record may be gone, or something in front of the server may respond instead.
Raising in these cases would report a successful post as a failed post.
If the caller asked for ingestion to be confirmed, both cases are logged and then raised.
A lookup that returns 404 is reported as a ``ConnectionError`` by :func:`request`, which wraps every client error that way.
So ``ConnectionError`` is among the errors caught here, alongside the ``ValueError`` that an unexpected status code raises.
"""
try:
await self.wait_for_job(
Expand All @@ -793,6 +807,8 @@ async def _await_sensor_data_ingestion(self, job_id: str) -> None:
self.logger.error(
f"Ingestion of the data posted as job {job_id} is unconfirmed, so a read of it may come back without it. {e}"
)
if confirm_ingestion:
raise
except (
ConnectionError,
TimeoutError,
Expand All @@ -803,6 +819,8 @@ async def _await_sensor_data_ingestion(self, job_id: str) -> None:
self.logger.error(
f"Ingestion of the data posted as job {job_id} is unconfirmed, because looking that job up failed, so a read of it may come back without it. {e}"
)
if confirm_ingestion:
raise

async def _post_sensor_data_json(
self,
Expand All @@ -813,6 +831,7 @@ async def _post_sensor_data_json(
unit: str,
prior: str | datetime | None = None,
await_ingestion: bool = True,
confirm_ingestion: bool = False,
):
"""
Post sensor data using JSON payload.
Expand Down Expand Up @@ -849,7 +868,7 @@ async def _post_sensor_data_json(
self.logger.info("Sensor data sent successfully via JSON.")

if await_ingestion and status == 202 and job_id:
await self._await_sensor_data_ingestion(job_id)
await self._await_sensor_data_ingestion(job_id, confirm_ingestion)

return response, status

Expand All @@ -860,6 +879,7 @@ async def _post_sensor_data_file(
belief_time_measured_instantly: bool = False,
unit: str | None = None,
await_ingestion: bool = True,
confirm_ingestion: bool = False,
):
"""
Post sensor data using file upload.
Expand Down Expand Up @@ -981,7 +1001,7 @@ async def _post_sensor_data_file(
# Wait for ingestion outside the request timeout above, and outside the error handler that names this an upload failure:
# a job takes as long as self.ingestion_polling_timeout allows, which is no concern of the request that queued it.
if await_ingestion and status == 202 and upload_job_id:
await self._await_sensor_data_ingestion(upload_job_id)
await self._await_sensor_data_ingestion(upload_job_id, confirm_ingestion)

return response_data, status

Expand All @@ -995,6 +1015,7 @@ async def post_measurements(
unit: str,
prior: str | datetime | None = None,
await_ingestion: bool = True,
confirm_ingestion: bool = False,
):
"""
Post sensor data for the given time range.
Expand All @@ -1019,6 +1040,7 @@ async def post_measurements(
unit=unit,
prior=prior,
await_ingestion=await_ingestion,
confirm_ingestion=confirm_ingestion,
)

async def get_schedule(
Expand Down Expand Up @@ -1538,10 +1560,10 @@ async def add_asset(
}

This function raises a ValueError when an unhandled status code is returned,
when no generic_asset_type_id is given, and when only one of latitude and longitude is.
when no generic_asset_type_id is given, and when only one of latitude and longitude is given.
"""
if generic_asset_type_id is None:
# Optional in the signature only so that latitude and longitude, before it, can be left out,
# The signature makes this optional only so that latitude and longitude, which come before it, can be left out,
# while every argument keeps its position.
raise ValueError("add_asset needs a generic_asset_type_id.")
if (latitude is None) != (longitude is None):
Expand All @@ -1553,7 +1575,7 @@ async def add_asset(
account_id=account_id,
generic_asset_type_id=generic_asset_type_id,
)
# A location is optional to FlexMeasures, which many assets have no use for, such as one standing for a community.
# FlexMeasures does not require a location, and many assets have no use for one, such as an asset that represents a community.
if latitude is not None:
asset["latitude"] = latitude
if longitude is not None:
Expand Down
4 changes: 2 additions & 2 deletions src/flexmeasures_client/s2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def __init__(self, max_size: int = 100, timezone: str = "UTC") -> None:
self.outgoing_messages_status = SizeLimitOrderedDict(max_size=max_size)

self._timezone = ZoneInfo(timezone)
#: The time this handler goes by; a CEM gives each handler it registers its own clock.
#: The clock this handler reads the time from. A CEM sets this to its own clock for each handler it registers.
self._clock: Clock = WallClock()

self.discover()
Expand Down Expand Up @@ -253,5 +253,5 @@ def handle_revoke_object(self, message: RevokeObject) -> ReceptionStatus:
)

def now(self) -> datetime:
"""The current moment by this handler's clock, in its time zone."""
"""Return the current moment by this handler's clock, in the handler's timezone."""
return self._clock.now().astimezone(self._timezone)
Loading
Loading