Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
194 commits
Select commit Hold shift + click to select a range
733ef40
refactor: migrate from tox/PyScaffold to uv for dependency management…
Feb 19, 2026
d661056
fix: fix whitespace issues
Feb 23, 2026
cef07f3
feat: move from pre-commit git repos to using uv for pre-commit scripts
Feb 23, 2026
d63afe5
dev: try using the _sending_queue instead
Flix6x Feb 25, 2026
564b8f1
Revert "dev: try using the _sending_queue instead"
Flix6x Feb 25, 2026
2db7a4f
fix: give the sending task enough time to flush the queue before the …
Flix6x Feb 25, 2026
35cc9ad
dev: use DEBUG logging level
Flix6x Feb 25, 2026
375b49c
feat: await message sent rather than message queued
Flix6x Feb 25, 2026
14f7792
fix: also close WS if closing CEM
Flix6x Feb 25, 2026
4799585
feat: skip setting up the toy account
Flix6x Feb 25, 2026
89d4338
feat: post prices in background task
Flix6x Feb 25, 2026
60ff6af
Revert "feat: skip setting up the toy account"
Flix6x Feb 25, 2026
404ed6b
chore: use modern method name
Flix6x Feb 25, 2026
5d4de10
fix: fall back on now in case FRBCActuatorStatus.transition_timestamp…
Flix6x Feb 25, 2026
f8ea658
fix: actuator status unit
Flix6x Feb 25, 2026
d59f340
feat: skip setting up the toy account
Flix6x Feb 25, 2026
6abd2a1
fix: copy-paste mistake
Flix6x Feb 25, 2026
5328de9
fix: discharge unit
Flix6x Feb 25, 2026
eae232b
docs: update instruction to run docker-compose stack
Flix6x Feb 25, 2026
0e72c25
Revert "fix: discharge unit"
Flix6x Feb 25, 2026
abb0a74
fix: schedule power sensor instead of dimensionless discharge sensor
Flix6x Feb 25, 2026
bbaac73
feat: trigger schedule with each storage status (not yet rate limited…
Flix6x Feb 25, 2026
374e44a
feat: post 1 year of data in background tasks
Flix6x Feb 25, 2026
a1d6ba7
Revert "feat: post 1 year of data in background tasks"
Flix6x Feb 25, 2026
f8e0d62
fix: set prior knowledge of prices and test with now
Flix6x Feb 25, 2026
be6d8d3
fix: floor the schedule start
Flix6x Feb 25, 2026
5334c0c
fix: messages should now be routed through cem.send_message
Flix6x Feb 25, 2026
2845036
fix: messages should now be routed through cem.send_message; update h…
Flix6x Feb 25, 2026
e01176d
feat: roll 3 days of test prices
Flix6x Feb 25, 2026
c780f98
style: black, isort
Flix6x Feb 25, 2026
26f4778
style: black
Flix6x Feb 25, 2026
c691cf2
Merge remote-tracking branch 'origin/main' into dev/fix-handshake-han…
Flix6x Feb 26, 2026
5fb3a26
feat: make schedules appear with consumption on the positive axis
Flix6x Feb 26, 2026
508611b
fix: update _sending_queue.put to send_message in FillRateBasedContro…
Flix6x Feb 26, 2026
07b55c5
feat: port send_fill_level_target_profile
Flix6x Feb 27, 2026
872224c
feat: save scheduled state-of-charge, too
Flix6x Feb 27, 2026
0b96cc5
feat: set sensors_to_show on CEM asset
Flix6x Feb 27, 2026
5ef6f64
fix: type annotation
Flix6x Feb 27, 2026
010d584
fix: flex-model soc-unit
Flix6x Feb 27, 2026
c389510
fix: get rid of valid_from_shift
Flix6x Feb 27, 2026
e5ad8a0
dev: log used SystemDescription
Flix6x Feb 27, 2026
54dd188
style: black
Flix6x Feb 27, 2026
2819e4f
feat: port send_usage_forecast
Flix6x Feb 27, 2026
6cf71ad
feat: relax constraints
Flix6x Feb 27, 2026
b78fbc8
feat: we already remove scheduled power values that are not a change …
Flix6x Feb 27, 2026
baac3bb
dev: debug log JSON instructions
Flix6x Feb 27, 2026
0577d53
dev: speed up polling for simulations
Flix6x Feb 27, 2026
b99b39b
docs: add instruction to create an admin user
Flix6x Feb 27, 2026
7ce4c35
refactor: rename variable
Flix6x Feb 27, 2026
806dae0
style: isort
Flix6x Feb 27, 2026
5be4b64
fix: mypy
Flix6x Feb 27, 2026
abb68e0
chore: resolve implicit todo
Flix6x Feb 27, 2026
59108c0
fix: obvious typo
Flix6x Feb 27, 2026
92fd551
fix: debug log instead of error log
Flix6x Feb 27, 2026
1b8d4c9
feat: port send_leakage_behaviour
Flix6x Feb 27, 2026
fe02d33
feat: CEM supports setting simulation time, by wrapping the S2 messag…
Flix6x Mar 2, 2026
fd91a62
style: isort, flake8
Flix6x Mar 2, 2026
e8e6006
feat: record data in FlexMeasures as if it was recorded at simulation…
Flix6x Mar 2, 2026
14bf5b8
style: silence mypy on overwriting a method with a lambda function
Flix6x Mar 2, 2026
7e6b29a
feat: force new scheduling job creation when using a prior
Flix6x Mar 9, 2026
a89a41e
fix: upgrade timely-beliefs to fix scheduler bug with resampling from…
Flix6x Mar 9, 2026
31bddec
fix: pass prior to trigger_and_get_schedule
Flix6x Mar 10, 2026
906a7f5
fix: start schedule from the time of the most recent storage status
Flix6x Mar 10, 2026
f61fd60
style: black
Flix6x Mar 10, 2026
2d5b0fe
fix: still make sure to run `flexmeasures add toy-account` once
Flix6x Mar 10, 2026
eda6b28
feat: add ability to customize fill_level_scale
Flix6x Mar 10, 2026
1abdd71
dev: remove todo (soc-at-start is now actually coming from the latest…
Flix6x Mar 10, 2026
9f20ab4
feat: derive consumption-capacity and production-capacity from operat…
Flix6x Mar 10, 2026
ea68aa0
docs: update docstring
Flix6x Mar 10, 2026
605980a
feat: move to J and W as default energy unit and power unit, respecti…
Flix6x Mar 10, 2026
c2bab5d
fix: wrong conversion
Flix6x Mar 10, 2026
00f363a
docs: clarify inline note
Flix6x Mar 10, 2026
80b2f61
refactor: prepare for more params
Flix6x Mar 10, 2026
2cc3b75
feat: support getting a schedule in a given unit
Flix6x Mar 10, 2026
3a547b1
feat: require minimum version for getting a schedule in a given unit
Flix6x Mar 10, 2026
d29e64c
fix: get schedule in the assumed power unit
Flix6x Mar 10, 2026
fca2c5b
feat: note the current FM server version
Flix6x Mar 10, 2026
fb6d947
fix: actually requires v0.32.0
Flix6x Mar 10, 2026
7d4c0c0
docs: clarify that the server ignores the parameter, not the client
Flix6x Mar 10, 2026
cc28d77
style: black
Flix6x Mar 10, 2026
0760825
fix: mistake while refactoring (or from running black?)
Flix6x Mar 10, 2026
67cbd0b
fix: misinterpreted the usage forecast scale
Flix6x Mar 11, 2026
6ba2337
fix: CEM should only relax soc-constraints (not capacity-constraints …
Flix6x Mar 11, 2026
2711aff
fix: stop flipping the values from the actuator status (now that we u…
Flix6x Mar 11, 2026
fbe2461
feat: use local flexmeasures repo in server and worker
Flix6x Mar 12, 2026
9a2b131
Merge remote-tracking branch 'origin/main' into refactor/uv-migration
Flix6x Mar 13, 2026
a273788
fix: release step was split off to separate release.yml
Flix6x Mar 13, 2026
d4a72fe
feat: update github workflows. Clean up .pre-commit-config.yaml.
Mar 16, 2026
06406c9
fix: fix s2 tests in CI. Use uv for s2 tests.
Mar 16, 2026
274bbc6
feat: use uv for readthedocs
Mar 16, 2026
0de29b8
refactor: remove coverage for s2. Add coverage to general test
Mar 16, 2026
cd7b732
fix: fix issues in S2 client and server examples. Use Pydantic V2 met…
Mar 18, 2026
1ec5a87
feat: update docs to use new UV setup
Mar 18, 2026
3afc4af
fix: fix docker compose CEM not connecting to flexmeasures server. Fi…
Mar 18, 2026
db0c676
feat: rewrite CEM docs to use new UV setup and new docker compose set…
Mar 18, 2026
030c6bf
fix: trigger_schedule retreiving outdated schedule
Mar 18, 2026
b78bacf
dev: expose dev-db on local port
Flix6x Apr 13, 2026
5664e2c
Merge branch 'refactor/uv-migration' into dev/fix-handshake-handler
Flix6x Apr 13, 2026
0e4489a
fix: missing import
Flix6x Apr 13, 2026
f3114cc
dev: fix db port exposure
Flix6x Apr 13, 2026
da7268a
dev: expose queue-db port, too
Flix6x Apr 13, 2026
21d28a1
fix: install missing packages for CEM
Flix6x Apr 13, 2026
78b87d5
feat: add charging-efficiency sensor
Flix6x Apr 13, 2026
5e2e4ce
refactor: rename variable
Flix6x Apr 13, 2026
72321d4
docs: update developer note
Flix6x Apr 13, 2026
7a87b91
feat: add production price sensor
Flix6x Apr 14, 2026
e700c22
fix: get existing charging_efficiency_sensor
Flix6x Apr 14, 2026
1af2a1d
Merge remote-tracking branch 'origin/main' into refactor/uv-migration
Flix6x Apr 14, 2026
642bd45
Merge remote-tracking branch 'stijn/refactor/uv-migration' into dev/f…
Flix6x Apr 14, 2026
fa88ad7
Merge remote-tracking branch 'origin/main' into dev/fix-handshake-han…
Flix6x Apr 14, 2026
347268d
fix: repeated keyword (prior comes from start, not now)
Flix6x Apr 14, 2026
655ba10
fix: use new internal method
Flix6x Apr 14, 2026
74fc885
dev: allow using dev server version
Flix6x Apr 14, 2026
a5b382f
fix: parse two more JSON fields
Flix6x Apr 14, 2026
5f62c41
feat: add chart with power values
Flix6x Apr 14, 2026
69e671e
style: black, flake8, isort (run pre-commit hooks)
Flix6x Apr 20, 2026
0e4bfed
Merge remote-tracking branch 'origin/main' into dev/fix-handshake-han…
Flix6x Apr 21, 2026
6a3b2e9
Merge remote-tracking branch 'origin/main' into dev/fix-handshake-han…
Flix6x Apr 21, 2026
01ccccb
fix: hanging future
Flix6x Apr 21, 2026
a37c0df
fix: FRBC init
Flix6x Apr 21, 2026
c4b2718
Merge remote-tracking branch 'origin/main' into dev/fix-handshake-han…
Flix6x May 18, 2026
094624c
fix: move sensor ID into flex-model
Flix6x May 18, 2026
a21c5e4
feat: move to kebab-case field name
Flix6x May 18, 2026
2b0eb2e
Merge remote-tracking branch 'origin/main' into dev/fix-handshake-han…
Flix6x Jun 1, 2026
e8c4a88
fix: prevent using dev version
Flix6x Jun 1, 2026
eff597a
feat: use ingestion queue for flexmeasures v0.33.0
Flix6x Jun 1, 2026
62b89bc
fix: prioritize ingestion jobs
Flix6x Jun 3, 2026
ef3ce3c
feat: make latitude and longitude optional
Flix6x Jun 8, 2026
f68a261
feat: map resource to asset when receiving RM details
Flix6x Jun 8, 2026
543f3c4
Merge remote-tracking branch 'origin/main' into dev/fix-handshake-han…
Flix6x Jun 8, 2026
c20c2f3
fix: allow parsing JSON fields when updating a sensor or asset
Flix6x Jun 16, 2026
8cc82d8
fix: race condition in handler registration
Flix6x Jun 18, 2026
a09c19f
test: fix CEM tests to use ControlContext and cleanup background tasks
Flix6x Jun 18, 2026
d251fb9
refactor: set handler_ready automatically in register_control_type
Flix6x Jun 18, 2026
771b978
fix: initialize ControlContext as instance attribute instead of class…
Flix6x Jun 18, 2026
61b89c6
fix: remove stale _control_type class attribute and update websockets…
Flix6x Jun 18, 2026
d9dc817
feat: add conversion efficiency posting for FRBC handlers
Flix6x Jun 19, 2026
cb604f2
dev: default to 24 hour scheduling
Flix6x Jun 22, 2026
8f465ac
dev: fix charging-efficiency
Flix6x Jun 22, 2026
70065df
fix: set belief_time to simulation now, when POSTing charging-efficiency
Flix6x Jun 22, 2026
0982462
refactor: introduce extra variables for better debugging
Flix6x Jun 22, 2026
6cfe972
feat: infer next schedule_duration from previous usage_forecast duration
Flix6x Jun 22, 2026
a6b0c1d
Revert "feat: infer next schedule_duration from previous usage_foreca…
Flix6x Jun 22, 2026
14d21d1
dev: add debugging statements
Flix6x Jun 16, 2026
ad1cf67
feat: support running multiple CEM instances on different ports
Flix6x Jul 6, 2026
a230540
feat: load the seita_ems plugin into the server and worker containers
Flix6x Jul 6, 2026
04b3999
fix: match returning resource managers to assets by name when externa…
Flix6x Jul 6, 2026
dedfe9f
fix: use more gunicorn worker processes instead of threads, to isolat…
Flix6x Jul 7, 2026
51346d6
feat: add trace logging around asset reuse and schedule triggering; s…
Flix6x Jul 7, 2026
bc709df
chore: scale gunicorn workers to match the number of CEM instances
Flix6x Jul 7, 2026
c0ddc15
fix: relax all site-capacity constraints, not just SOC, so infeasibil…
Flix6x Jul 7, 2026
c19cf8d
fix: place back command for running a worker against the ingestion queue
Flix6x Jul 7, 2026
52843ff
fix: revert worker to the scheduling|forecasting queue list (ingestio…
Flix6x Jul 7, 2026
d2805b6
fix: raise the CEM's schedule-polling budget to match FlexMeasures' l…
Flix6x Jul 7, 2026
2902705
fix: relax site capacity specifically instead of the broad relax-cons…
Flix6x Jul 7, 2026
2d2d97c
revert: drop the CEM polling-timeout increase, no longer needed now t…
Flix6x Jul 7, 2026
9aa318d
dev: let py-spy attach to the RQ worker subprocess for live debugging
Flix6x Jul 7, 2026
b3540dd
feat: scale RQ workers to 3, one per concurrently-simulated apartment…
Flix6x Jul 7, 2026
117cdac
fix: retry a transient scheduling-job failure instead of permanently …
Flix6x Jul 7, 2026
52bfa82
refactor: move the seita_ems plugin mounts to an overlay in the ems repo
Flix6x Jul 10, 2026
292b646
feat: pass S2 operation-mode power ranges to FlexMeasures as power bands
Flix6x Jul 10, 2026
f924543
fix: tear down the CEM session when its websocket closes
Flix6x Jul 10, 2026
7df1a4b
fix: retry schedule triggers that race the initial battery-SoC post
Flix6x Jul 12, 2026
aae8c23
feat: route S2 PowerMeasurements to a measured-power sensor at simula…
Flix6x Jul 13, 2026
e44619f
fix: give control-type handlers a default close() so CEM teardown can…
Flix6x Jul 13, 2026
80a7e51
fix: install and run the CEM with one and the same Python interpreter
Flix6x Jul 14, 2026
d409b51
fix: stop deriving a phantom production capacity from S2 operation modes
Flix6x Jul 14, 2026
a7f154a
Fix CEM co-sim belief time, timezone anchor, and live aggregate-power…
Flix6x Jul 14, 2026
cfd562e
fix: stamp power measurements with zero belief horizon
Flix6x Jul 15, 2026
89b5893
fix: hard-bound the FRBC heat pump against production
Flix6x Jul 15, 2026
ec0b103
fix: cap exponential polling backoff at MAX_POLLING_SLEEP
Flix6x Jul 24, 2026
edac914
fix: raise MAX_POLLING_STEPS so polling_timeout governs patience
Flix6x Jul 24, 2026
65849df
fix: widen hard soc bounds to include the current state
Flix6x Jul 24, 2026
19ff075
fix: raise schedule polling budget to 360s
Flix6x Jul 24, 2026
6adb15e
feat: optional 'metadata' field on schedule triggers
Flix6x Jul 27, 2026
ba7f865
fix: activate FRBC implicitly when the RM sends FRBC messages
Flix6x Jul 27, 2026
c519d59
feat(FRBC): supersession, sysdesc dedupe, transition-filtered instruc…
Flix6x Jul 27, 2026
3081698
fix(config): fit the State-of-charge subplot y-axis to the data
Flix6x Jul 27, 2026
47419db
fix(FRBC): never retry deterministic scheduling failures
Flix6x Jul 27, 2026
a5ccebe
perf(s2): post realized-power measurements concurrently
Flix6x Jul 27, 2026
5e862a2
perf(s2): deduplicate re-sent realized-power measurements
Flix6x Jul 27, 2026
e51d628
feat(s2): log an error when FlexMeasures used its fallback scheduler
Flix6x Jul 28, 2026
0ce28b2
fix(s2): make measurement-post dedupe survive RM reconnections
Flix6x Jul 28, 2026
5316b12
perf(s2): lean site-asset lookup in configure_site
Flix6x Jul 28, 2026
8d6c09c
perf(s2): cache the site-asset id across RM reconnections
Flix6x Jul 28, 2026
75a7b0c
feat(frbc): treat the declared fill-level range as a soft comfort band
Flix6x Jul 28, 2026
f94a9bc
fix(build): fall back to a static version when git is unavailable
Flix6x Jul 29, 2026
1412e64
feat: await asynchronous server-side sensor-data ingestion
Flix6x Jul 29, 2026
8299d0a
fix: workers handle ingestion, too
Flix6x Sep 7, 2026
51ad714
Merge remote-tracking branch 'origin/main' into dev/fix-handshake-han…
Flix6x Sep 8, 2026
d2a1a5f
feat(FRBC): map S2 power ranges onto FlexMeasures' signed operation m…
Flix6x Sep 8, 2026
2ff470f
fix(FRBC): choose the power-band shape from the server version
Flix6x Sep 9, 2026
3a4c28c
Merge branch 'feat/v1-operation-mode-ranges' into dev/fix-handshake-h…
Flix6x Sep 9, 2026
b32402f
fix(compose): ingest sensor data synchronously, and clear stale inges…
Flix6x Sep 10, 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
4 changes: 4 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ repos:
- id: check-ast
- id: check-json
- id: check-merge-conflict
# RST section adornments are rows of "=", which this hook reads as conflict
# markers. It scans the whole tree while a merge is in progress, so without
# this exclusion every merge commit is blocked by docs/license.rst.
exclude: ^docs/license\.rst$
- id: check-xml
- id: check-yaml
- id: debug-statements
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@
Changelog
=========

Unreleased
==========

- ``post_sensor_data()`` now awaits asynchronous server-side ingestion by
default (``await_ingestion=True``), restoring read-your-writes semantics
after FlexMeasures PR #2101 made ``POST .../sensors/data`` (and the file
upload endpoint) return ``202 Accepted`` with a background job id instead
of processing synchronously. The client polls the job-status endpoint
(exponential backoff from 0.25s up to a 2s cap, ``ingestion_polling_timeout``
seconds total, default 60s) until the job finishes. A failed job raises the
new ``IngestionFailedError``; a job still pending when polling times out
logs an ERROR and returns normally, so callers with their own downstream
safety nets are not blocked indefinitely. Pass ``await_ingestion=False`` to
opt out and return as soon as the POST is acknowledged, matching the old
(PR #2101) behavior.

Version 0.1.1
=============

Expand Down
253 changes: 253 additions & 0 deletions docker-compose.override.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
# ------------------------------------------------------------------
# This allow to run the S2 CEM from your local FlexMeasures Client code in a docker compose stack.
# Assuming you have flexmeasures the repo next to your flexmeasures-client repo,
# run this from the flexmeasures folder (which contains the Dockerfile):
# docker compose \
# -f docker-compose.yml \
# -f ../flexmeasures-client/docker-compose.override.yml \
# up
# ------------------------------------------------------------------

# Shared definition for all CEM instances. Each instance keeps listening on
# port 8080 inside its own container; only the host port mapping differs,
# so RMs connect to ws://localhost:8080/ws, ws://localhost:8081/ws, etc.
x-cem: &cem
build:
context: .
dockerfile: Dockerfile
image: flexmeasures-client-cem
depends_on:
- server
restart: always
environment:
FLEXMEASURES_BASE_URL: http://server:5000
FLEXMEASURES_USER: toy-user@flexmeasures.io
FLEXMEASURES_PASSWORD: toy-password
LOGGING_LEVEL: DEBUG
SETUPTOOLS_SCM_PRETEND_VERSION_FOR_FLEXMEASURES_CLIENT: "0.0.0"
SETUPTOOLS_SCM_PRETEND_VERSION: "0.0.0"
volumes:
# If flexmeasures_client lives in your repo and you want live edits
- ../flexmeasures-client:/app/flexmeasures-client:rw
entrypoint: ["/bin/sh", "-c"]
command:
- |
# Install and run with the SAME interpreter (`python3 -m pip`, not bare `pip`).
# In images built from FlexMeasures' uv-based Dockerfile, `python3` resolves to
# the /app/.venv virtualenv (which ships without pip and cannot see system
# site-packages) while bare `pip` is the system one under /usr/local - so
# packages installed with `pip` are invisible at runtime (ModuleNotFoundError:
# aiohttp). Older cached images had a single system Python, masking this.
# The ensurepip guard bootstraps pip into the venv on fresh images and is a
# no-op wherever `python3 -m pip` already works.
python3 -m pip --version >/dev/null 2>&1 || python3 -m ensurepip --upgrade
python3 -m pip install --break-system-packages -e "/app/flexmeasures-client[s2]"
python3 -m pip install --break-system-packages aiohttp pytz s2-python==0.8.2
exec python3 /app/flexmeasures-client/src/flexmeasures_client/s2/script/websockets_server.py

# Shared definition for all RQ worker instances. Scaled one-per-concurrently-
# simulated-apartment, same as the cem-* services and the server's gunicorn
# worker count below, since otherwise every apartment's scheduling jobs funnel
# through a single worker process and serialize even though gunicorn/CEM are
# already scaled per apartment. Bump alongside those when scaling further.
x-worker: &worker
# worker-2/worker-3 are new services (not present in the base docker-compose.yml,
# unlike "worker" itself), so this anchor must be self-contained rather than
# relying on a base-file merge for build/depends_on/restart/base environment.
build:
context: .
dockerfile: Dockerfile
depends_on:
- dev-db
- queue-db
- mailhog
restart: on-failure
cap_add:
# SYS_PTRACE: lets py-spy attach to the RQ worker subprocess for live debugging
# of slow/stuck scheduling jobs (py-spy itself isn't preinstalled here; install
# it ad hoc with `docker exec <container> pip install --break-system-packages py-spy`).
- SYS_PTRACE
volumes:
# a place for config and plugin code, and custom requirements.txt
- ./flexmeasures-instance/:/usr/var/flexmeasures-instance/:rw
- ../flexmeasures/flexmeasures:/app/flexmeasures:rw
- ./flexmeasures-instance/:/app/instance/:rw
# The client repo (flexmeasures_client + examples/HEMS). The seita_ems
# community plugin is mounted by the ems repo's docker-compose.community.yml
# overlay instead, so this stack also runs without access to that repo.
- ../flexmeasures-client:/app/flexmeasures-client:rw
entrypoint: ["/bin/sh", "-c"]
environment:
SQLALCHEMY_DATABASE_URI: "postgresql://fm-dev-db-user:fm-dev-db-pass@dev-db:5432/fm-dev-db"
FLEXMEASURES_REDIS_URL: queue-db
FLEXMEASURES_REDIS_PASSWORD: fm-redis-pass
SECRET_KEY: notsecret
SECURITY_TOTP_SECRETS: '{"1": "something-secret"}'
FLEXMEASURES_ENV: development
MAIL_SERVER: mailhog
MAIL_PORT: 1025
LOGGING_LEVEL: INFO

services:
dev-db:
ports:
- "5433:5432"
queue-db:
ports:
- "6380:6379"
server:
# SYS_PTRACE: lets py-spy attach to the gunicorn workers for live debugging
# of the CEM-asset-reuse hang (see projects/009 HANDOFF.md in pps_flexed).
cap_add:
- SYS_PTRACE
volumes:
# A place for config and plugin code, and custom requirements.txt
# The 1st mount point is for running the FlexMeasures CLI, the 2nd for gunicorn
# We use :rw so flexmeasures CLI commands can write log files
- ./flexmeasures-instance/:/usr/var/flexmeasures-instance/:rw
- ./flexmeasures-instance/:/app/instance/:rw
- ../flexmeasures/flexmeasures:/app/flexmeasures:rw
# The client repo (flexmeasures_client + examples/HEMS). The seita_ems
# community plugin is mounted by the ems repo's docker-compose.community.yml
# overlay instead, so this stack also runs without access to that repo.
- ../flexmeasures-client:/app/flexmeasures-client:rw
command:
- |
pip install --break-system-packages -e /app
pip install --break-system-packages -r /usr/var/flexmeasures-instance/requirements.txt
pip install timely-beliefs -U --break-system-packages
pip install --break-system-packages -e /app/flexmeasures-client[s2]
pip install --break-system-packages py-spy
flexmeasures db upgrade
if ! flexmeasures show accounts | grep -q "Docker Toy Account"; then
flexmeasures add toy-account --name 'Docker Toy Account'
fi
# More worker PROCESSES, fewer threads per worker: a slow CPU-bound
# request (see projects/009 HANDOFF.md - belief-dedup in
# flexmeasures/data/services/time_series.py can be slow) previously
# monopolized the GIL of its gunicorn worker under --threads 4,
# starving unrelated concurrent requests until they hit client-side
# timeouts. Separate OS processes aren't GIL-bound, so this isolates
# a slow request to its own worker instead of blocking the others.
# Worker count matches the number of CEM instances below (one per
# concurrently-simulated apartment), since each apartment's RM/CEM
# can be mid-request at the same time. Bump both together when
# scaling to more apartments (see the cem-* services' own comment).
gunicorn --bind 0.0.0.0:5000 --worker-tmp-dir /dev/shm --workers 5 --threads 1 --timeout 120 wsgi:application
# One RQ worker per concurrently-simulated apartment (see x-worker comment
# above). Add or remove instances alongside the cem-* services.
worker:
<<: *worker
command:
- |
pip install --break-system-packages -e /app
pip install --break-system-packages -r /usr/var/flexmeasures-instance/requirements.txt
pip install timely-beliefs -U --break-system-packages
pip install --break-system-packages -e /app/flexmeasures-client[s2]
# Drop any lingering ingestion registration before this worker starts.
# FlexMeasures decides synchronous vs asynchronous sensor-data ingestion by asking
# Redis which workers are registered for the ingestion queue (PR #2101), and STALE
# registrations count: if a dead worker's entry survives, posts are enqueued to a
# queue nobody serves and the run hangs with no error. Removing only entries that
# name the ingestion queue is safe to run on every start - no worker here registers
# for it - and it is idempotent, so all three workers may do it concurrently.
python3 - <<'PYGUARD'
import os, redis
r = redis.Redis(host=os.environ.get("FLEXMEASURES_REDIS_URL", "queue-db"),
port=int(os.environ.get("FLEXMEASURES_REDIS_PORT", 6379)),
password=os.environ.get("FLEXMEASURES_REDIS_PASSWORD"))
try:
for name in r.smembers("rq:workers"):
key = name.decode()
queues = (r.hget(key, "queues") or b"").decode()
if "ingestion" in queues or not queues:
r.srem("rq:workers", name); r.delete(key)
r.delete("rq:workers:ingestion")
except Exception as exc:
print(f"ingestion-registration guard skipped: {exc}")
PYGUARD
flexmeasures jobs run-worker --name flexmeasures-worker-1 --queue scheduling\|forecasting
worker-2:
<<: *worker
command:
- |
pip install --break-system-packages -e /app
pip install --break-system-packages -r /usr/var/flexmeasures-instance/requirements.txt
pip install timely-beliefs -U --break-system-packages
pip install --break-system-packages -e /app/flexmeasures-client[s2]
# Drop any lingering ingestion registration before this worker starts.
# FlexMeasures decides synchronous vs asynchronous sensor-data ingestion by asking
# Redis which workers are registered for the ingestion queue (PR #2101), and STALE
# registrations count: if a dead worker's entry survives, posts are enqueued to a
# queue nobody serves and the run hangs with no error. Removing only entries that
# name the ingestion queue is safe to run on every start - no worker here registers
# for it - and it is idempotent, so all three workers may do it concurrently.
python3 - <<'PYGUARD'
import os, redis
r = redis.Redis(host=os.environ.get("FLEXMEASURES_REDIS_URL", "queue-db"),
port=int(os.environ.get("FLEXMEASURES_REDIS_PORT", 6379)),
password=os.environ.get("FLEXMEASURES_REDIS_PASSWORD"))
try:
for name in r.smembers("rq:workers"):
key = name.decode()
queues = (r.hget(key, "queues") or b"").decode()
if "ingestion" in queues or not queues:
r.srem("rq:workers", name); r.delete(key)
r.delete("rq:workers:ingestion")
except Exception as exc:
print(f"ingestion-registration guard skipped: {exc}")
PYGUARD
flexmeasures jobs run-worker --name flexmeasures-worker-2 --queue scheduling\|forecasting
worker-3:
<<: *worker
command:
- |
pip install --break-system-packages -e /app
pip install --break-system-packages -r /usr/var/flexmeasures-instance/requirements.txt
pip install timely-beliefs -U --break-system-packages
pip install --break-system-packages -e /app/flexmeasures-client[s2]
# Drop any lingering ingestion registration before this worker starts.
# FlexMeasures decides synchronous vs asynchronous sensor-data ingestion by asking
# Redis which workers are registered for the ingestion queue (PR #2101), and STALE
# registrations count: if a dead worker's entry survives, posts are enqueued to a
# queue nobody serves and the run hangs with no error. Removing only entries that
# name the ingestion queue is safe to run on every start - no worker here registers
# for it - and it is idempotent, so all three workers may do it concurrently.
python3 - <<'PYGUARD'
import os, redis
r = redis.Redis(host=os.environ.get("FLEXMEASURES_REDIS_URL", "queue-db"),
port=int(os.environ.get("FLEXMEASURES_REDIS_PORT", 6379)),
password=os.environ.get("FLEXMEASURES_REDIS_PASSWORD"))
try:
for name in r.smembers("rq:workers"):
key = name.decode()
queues = (r.hget(key, "queues") or b"").decode()
if "ingestion" in queues or not queues:
r.srem("rq:workers", name); r.delete(key)
r.delete("rq:workers:ingestion")
except Exception as exc:
print(f"ingestion-registration guard skipped: {exc}")
PYGUARD
flexmeasures jobs run-worker --name flexmeasures-worker-3 --queue scheduling\|forecasting
# One CEM per simulated household. Add or remove instances as needed;
# scale by copying an entry and bumping the host port.
cem:
<<: *cem
ports:
- "8080:8080"
cem-2:
<<: *cem
ports:
- "8081:8080"
cem-3:
<<: *cem
ports:
- "8082:8080"
cem-4:
<<: *cem
ports:
- "8083:8080"
cem-5:
<<: *cem
ports:
- "8084:8080"
27 changes: 27 additions & 0 deletions docs/CEM.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,39 @@ Then point your Resource Managers (RMs) to ``http://localhost:8080/ws`` and run:

uv run src/flexmeasures_client/s2/script/websockets_server.py

We also included a ``docker-compose.override.yaml`` that can be used to set up the CEM including the FlexMeasures server, creating a fully self-hosted HEMS.
Assuming your ``flexmeasures`` and ``flexmeasures-client`` repo folders are located side by side, run this from your flexmeasures folder:

.. code-block:: bash

docker compose \
-f docker-compose.yml \
-f ../flexmeasures-client/docker-compose.override.yml \
up


This creates the following containers for the CEM:

- a WebSocket server (FlexMeasures Client)
- web and worker servers (FlexMeasures)
- a database server (Postgres)
- a queue server (Redis)
- a mail server (MailHog)

To test, run the included example RM:

.. code-block:: bash

uv run src/flexmeasures_client/s2/script/websockets_client.py

For full access via the UI, create an admin user for the Docker Toy Account (here, we assume it has ID 1):

.. code-block:: bash

docker exec -it flexmeasures-server-1 bash
flexmeasures show accounts
flexmeasures add user --roles admin --account 1 --email <email> --username <username>

Disclaimer
==========

Expand Down
29 changes: 29 additions & 0 deletions examples/HEMS/configs/aggregate_reporter_param.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"input": [
{
"name": "aggregate-1",
"sensor": 3616,
"exclude_source_types": [
"scheduler",
"forecaster"
]
},
{
"name": "aggregate-2",
"sensor": 3645,
"exclude_source_types": [
"scheduler",
"forecaster"
]
}
],
"output": [
{
"sensor": 3613
}
],
"start": "2030-01-15T20:00:00+01:00",
"end": "2030-01-16T00:00:00+01:00",
"belief_horizon": "PT0H",
"check_output_resolution": false
}
Loading
Loading