Skip to content

Fix: CEM work session#188

Draft
Flix6x wants to merge 170 commits into
mainfrom
dev/fix-handshake-handler
Draft

Fix: CEM work session#188
Flix6x wants to merge 170 commits into
mainfrom
dev/fix-handshake-handler

Conversation

@Flix6x

@Flix6x Flix6x commented Feb 25, 2026

Copy link
Copy Markdown
Member

Follow-up to #187.

  • Fix handshake handler now sending back two responses against a queue that is periodically flushed (every 0.3 seconds)
  • Docker-compose stack skips running toy tutorial every time -> this is actually just for devs at the moment, because the toy account is actually needed for the CEM to run; specifically, the CEM uses the toy user credentials
  • Ported the FRBC.FillLevelTargetProfile and FRBC.UsageForecast to the FRBCSimple handler.
  • Test CEM now configures its own data dashboard in FlexMeasures
  • Test CEM now polls FlexMeasures much faster, which is more suitable for simulations.
  • A few minor fixes to the SimpleFRBC handler to get to the point that FRBCInstructions are sent back.
  • Ported the FRBC.LeakageBehaviour to the FRBCSimple handler.
  • Support wrapped S2 messages in an envelope together with metadata containing the simulation time.
  • Made the start of the schedule equal to the most recent storage status

Stijn van Houwelingen and others added 29 commits February 19, 2026 16:44
…, consolidating all dependencies into pyproject.toml and a single uv.lock file.

This simplifies and speeds up the development setup greatly.

Changes:
- Switch build backend from setuptools to hatchling
- Remove setup.cfg, tox.ini, .isort.cfg and setup.py in favour of .flake8 and pyproject.toml
- Add Poethepoet tasks
- Upgrade main Python version (CI/CD, .python-version, etc.) to 3.12
- Deprecate Python 3.9
- Add .python-version for consistent Python version management
- Create separate Dockerfile for flexmeasures-client
- Replace pip-tools with uv in all CI/CD workflows
- Remove ci/run_mypy.sh in favour of Poethepoet task
- Update documentation

Signed-off-by: Stijn van Houwelingen <teadrinkingprogrammer@github.io>
Signed-off-by: Stijn van Houwelingen <teadrinkingprogrammer@github.io>
Signed-off-by: Stijn van Houwelingen <teadrinkingprogrammer@github.io>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
…connection shuts down

Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
… is missing

Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
…) and make system_description_id optional

Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
…andlers accordingly

Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
@Flix6x Flix6x self-assigned this Feb 25, 2026
Flix6x and others added 30 commits June 22, 2026 19:12
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
…st duration"; it works after the first schedule, but I fear it may sometimes lead to a very short schedule duration; I prefer not to make this dependent on the usage forecast duration

This reverts commit 10fa1556532e789c42e81f2fa26af18ade611257.
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
…l_id is unavailable

Signed-off-by: F.N. Claessen <claessen@seita.nl>
…e slow requests

Signed-off-by: F.N. Claessen <claessen@seita.nl>
…top silently swallowing exceptions in the fire-and-forget storage-status task

Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
…traints flag, which pushed HiGHS into 10+ minute solves
Signed-off-by: F.N. Claessen <claessen@seita.nl>
…, so scheduling jobs run in parallel instead of serializing through a single worker
…stalling the timestep

Observed a pandas 'cannot reindex on an axis with duplicate labels' error from a
scheduling job during a multi-day, frequently-re-triggered (implementation_horizon_hours=3)
run; re-running the exact same job manually succeeded immediately, confirming it's
transient. Without a retry, trigger_schedule's fire-and-forget task just logs and
drops the exception, so the RM never receives an instruction for that timestep and
polls forever. Retries up to 3 times on a ValueError containing 'Scheduling job
failed' before giving up for real.
The base override now runs without access to the private ems repo (needed
for single-house simulations by collaborators without that repo); community
runs add ../ems/docker-compose.community.yml as a third compose file.
Builds the new flex-model "operation-modes" field from the FRBC system
description's operation-mode element power ranges (only when they actually
restrict the range), so FlexMeasures schedules only power values the device
can run at. Requires flexmeasures feat/2113-operation-mode-power-bands.
A plain disconnect (RMs reconnect at every replan boundary) previously left
the producer and watchdog tasks and the CEM itself running forever; the old
session lived on as a zombie able to keep posting SoC and triggering
schedules for an asset the RM no longer implements.
At startup, the CEM's first trigger_schedule can beat the controller's
initial battery-SoC post (a separate client writing to the flex-model's
state-of-charge sensor). FlexMeasures then rejects the trigger with a
422 "No recent state-of-charge value found", and since this whole call
runs as a fire-and-forget task, that single failure permanently stalled
the house at its barrier. Treat it as transient alongside "Scheduling
job failed" (5 attempts, 2s backoff) - it resolves itself once the SoC
post lands moments later. Heavier stacks lose this race more often.
…ted time

The CEM now creates a dedicated 'measured-power' sensor (kW,
consumption-positive) per site, separate from the StorageScheduler 'power'
schedule sensor, and maps ELECTRIC.POWER.L1 measurements to it (replacing a
hardcoded fallback sensor id). Measurements are binned and posted at their
own SIMULATED timestamp instead of wall-clock time, and posted directly
per incoming value (the RM sends one aggregated value per interval, so the
old 5-minute wall-clock buffered-averaging would have suppressed nearly
every post in a co-simulation). Values are posted in Watts (S2
PowerMeasurement convention) and converted to the kW sensor by FlexMeasures.
…'t crash

CEM.close() calls close() on every registered control-type handler when a
websocket tears down, but only FRBCTunes implemented it - FRBCSimple (and any
other handler) raised AttributeError, which killed the CEM's request handler
and left it unable to serve the next RM connection (houses then hang forever
waiting for a schedule). Add a default no-op close() on the ControlTypeHandler
base class.
Images built from FlexMeasures' uv-based Dockerfile put a pip-less
/app/.venv first on PATH while bare `pip` is the system one under
/usr/local, so packages installed at container start were invisible to
the runtime interpreter (ModuleNotFoundError: aiohttp) on any freshly
built image - notably Samuel's Windows setups - while long-cached
single-Python images kept working. Use `python3 -m pip` throughout
(with an ensurepip bootstrap for pip-less venvs) and exec the server
with that same python3. Also set the generic
SETUPTOOLS_SCM_PRETEND_VERSION alongside the client-specific one for
the git-less editable install.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQFkeGxDwErydUV5ZmHmki
An S2 PowerRange runs from start_of_range (lower bound) to end_of_range
(upper bound), but the capacity derivation introduced in 9f20ab4 (March
2026) read them the other way around: production-capacity became the
smallest upper bound across operation modes (e.g. 2.5 kW for a
consumption-only heat pump) and consumption-capacity the largest lower
bound. The scheduler consequently discharged the heat pump's thermal
storage as if it were an electric battery, and capped its consumption
below the device's real maximum. Derive the overall [min, max] power
range instead, with production only where ranges extend below zero.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQFkeGxDwErydUV5ZmHmki
… realizations

Belief time (defect 4b): PowerMeasurements posted by the CEM now carry a
simulated prior (the binned interval's end), so realized apartment power
is stamped at simulation time instead of wall-clock 2026, and the UI
horizon view no longer shows data "recorded in 2026".

Timezone (defect 2): naive S2 measurement timestamps are now localized as
Europe/Amsterdam (was UTC), matching the community orchestrator, RM and
controller which all localize the same naive sim times as Europe/Amsterdam.

Live apartment realizations (defect 4a): besides the dedicated
measured-power sensor, the CEM now also mirrors the realized apartment
aggregate onto the apartment's flex-context "aggregate-power" sensor,
resolved lazily from the asset's flex_context (it is attached by the
community runner only after the CEM connects, so it appears from step 1).
Realized posts stay distinguishable from the StorageScheduler schedule
data on that same sensor by their own source and simulated belief time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQFkeGxDwErydUV5ZmHmki
The RM sends one PowerMeasurement per 15-minute interval (stamped at
interval start), but the posting path binned and stamped priors with
the 5-minute _minimum_measurement_period, yielding belief times 10
minutes before each event's end - an ex-ante horizon on measurements.
Bin and stamp at the measured-power sensor's 15-minute event resolution
instead, so each realized value is believed exactly when its interval
elapses (zero horizon).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQFkeGxDwErydUV5ZmHmki
An explicit production-capacity of 0 kW gets softened by FlexMeasures'
relax-constraints defaults (100 EUR/kW device breach price), so under a
tight community capacity the scheduler chose to 'produce' 1.764 kW from
a heat pump rather than pay the 10000 EUR/kW site breach. Setting
is_strictly_non_positive on the FRBC power sensor keeps production
hard-bounded at zero regardless of relaxation settings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQFkeGxDwErydUV5ZmHmki
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants