Repository navigation
feat(S2): #188 finished, reconciled onto main (1 of 2) #278
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
Merged
Changes from all commits
Commits
Show all changes
66 commits
Select commit
Hold shift + click to select a range
43bb171
fix(TUNES): record a leakage behaviour instead of refusing it
Flix6x 30dc81e
chore(types): check every source file, including untyped function bodies
Flix6x ff1304c
ci: measure the coverage of the S2 tests
Flix6x 372a39a
chore(lint): no print() in the S2 code that runs unattended
Flix6x 9ae5a5c
docs(changelog): FillRateBasedControlTUNES records a leakage behaviour
Flix6x f791bdb
chore: close the gaps an adversarial review found in the instruments
Flix6x 5ff631d
fix(FRBC): answer by the element that applies, whatever order S2 list…
Flix6x d0b1c1d
docs(changelog): the element that applies, and what a standby mode st…
Flix6x b572a86
fix(FRBC): plan with the element where the schedule starts, not the l…
Flix6x 9587c94
fix(TUNES): an operation mode without a label no longer stops planning
Flix6x 4585f2f
fix(client): drop a content-type check that could not fire, nor be pr…
Flix6x c341c4a
chore(types): type check against what the code imports, S2 messages i…
Flix6x 1928d56
docs(changelog): an unlabelled TUNES mode, and two helpers that never…
Flix6x 2a644e6
fix: correct what this branch claimed, and test what review found unt…
Flix6x 4d5435e
fix(FRBC): every rate per second, as S2 states it, under the instruct…
Flix6x d0ae8ce
docs(changelog): every rate per second under the instructions
Flix6x 2387288
fix(FRBC): follow each step as a resource manager does, which review …
Flix6x 2f8902c
fix(example): a heat pump with a buffer that is planned for, instruct…
Flix6x fa3dc59
docs(changelog): the example, and the planning triggers it needed
Flix6x 10f1cc7
fix(FRBC): a state of charge in any supported energy unit reaches Fle…
Flix6x 5ba8ee2
docs(changelog): which energy units a handler takes, and what FlexMea…
Flix6x 324e7f2
fix(FRBC): follow each step exactly, between the moments anything cha…
Flix6x 40479ae
Merge remote-tracking branch 'origin/dev/s2-frbc-complete' into fix/s…
Flix6x 392ba50
fix(example): follow only the latest plan, keep the forecasts current…
Flix6x e3a5567
fix(example, FRBC): what the second review of the example round found
Flix6x c7fe0a7
docs(changelog): describe the example round as it now stands
Flix6x 41a8f63
fix(example, FRBC): what the third review of the example round found
Flix6x bc90b2b
fix(FRBC): plan from the next boundary, not the start of the step und…
Flix6x a5c0dcb
Merge remote-tracking branch 'origin/fix/s2-example-heat-pump' into f…
Flix6x 9824ea5
style(FRBC): keep the soc-at-start TODO off the line it annotates
Flix6x 93e3ae3
feat(FRBC): a production price of its own, an opt-in relaxed site cap…
Flix6x 506e7ec
Merge remote-tracking branch 'origin/fix/frbc-energy-unit' into feat/…
Flix6x 6408b6f
fix(FRBC): what the review of the energy-unit change found
Flix6x fd45a98
Merge remote-tracking branch 'origin/dev/s2-frbc-complete' into feat/…
Flix6x 11e9a39
test(FRBC): an overtaken plan sends nothing, whether dropped before o…
Flix6x 7c8ae45
docs(changelog): the handler options, the dropped translation, and as…
Flix6x 47d295d
fix(FRBC): send relax_site_capacity either way, or leave it to FlexMe…
Flix6x bcdbd79
feat(example): the site's graphs show the buffer, its comfort band, a…
Flix6x cc54bb6
docs(changelog): the example site's graphs
Flix6x c466752
fix(example): set the site graphs in the form every FlexMeasures reads
Flix6x 0028494
fix(S2): a lost confirmation does not stall a session, and a closed s…
Flix6x 1cc6261
test(S2): a message without a type activates nothing
Flix6x 9053b86
docs(changelog): implicit activation and session teardown
Flix6x 6b1d3ea
fix(S2): take only the control type being selected as confirmed, and …
Flix6x 69f5dbe
test(S2): a typeless message activates nothing while a selection is p…
Flix6x 80ad945
feat(S2): a clock the CEM and its handlers go by, which a co-simulati…
Flix6x 5b9793e
docs(changelog): the clock and the simulation metadata
Flix6x 47c695a
fix(S2): what the review of the clock found
Flix6x 6a12ea4
feat(S2): a site of its own for each resource manager, and the co-sim…
Flix6x bffe6c9
docs(changelog): sites per resource manager and the co-simulation stack
Flix6x 65b359f
fix(S2): what the review of the sites per resource manager found
Flix6x ccab11b
feat(S2): record measured power by when it was measured, in watts, on…
Flix6x 809b136
docs(changelog): the measurement recorder
Flix6x ab56606
fix(S2): what the review of the measurement recorder found
Flix6x b8aff1d
fix(examples): the co-simulation stack runs on a current FlexMeasures…
Flix6x 40288e8
docs(changelog): the co-simulation stack on FlexMeasures 1.1
Flix6x 4807388
docs(examples): what the co-simulation stack runs, and how to build i…
Flix6x f0c4d4e
fix(FRBC): a state of charge is known as of the handler's clock
Flix6x 2b5d481
docs(changelog): the state of charge on the clock
Flix6x 16a0b24
Merge remote-tracking branch 'origin/dev/s2-frbc-complete' into fix/s…
Flix6x 00d3676
feat(FRBC): plan one schedule at a time, for the newest request
Flix6x 77de62c
Merge remote-tracking branch 'origin/dev/s2-frbc-complete' into feat/…
Flix6x 401599b
docs(changelog): one schedule at a time
Flix6x 29845ea
fix(FRBC): a request out of date makes no further attempt, so the new…
Flix6x 5564983
docs(examples): the instance settings a co-simulation needs
Flix6x ad9a12b
Merge remote-tracking branch 'origin/dev/s2-frbc-complete' into docs/…
Flix6x 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
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
Large diffs are not rendered by default.
Oops, something went wrong.
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,71 @@ | ||
| # A FlexMeasures stack for an S2 co-simulation | ||
|
|
||
| `docker-compose.cem.yml` runs FlexMeasures with one S2 CEM per simulated home, | ||
| for a co-simulation whose resource managers connect over websockets, such as a | ||
| community of homes steered at once. How to start it is at the top of the file. | ||
|
|
||
| Each CEM is the example server (`src/flexmeasures_client/s2/script/websockets_server.py`) | ||
| with two switches on: | ||
|
|
||
| - `S2_ACCEPT_SIMULATION_METADATA`: a resource manager wraps each message with the | ||
| simulated time, `{"message": ..., "metadata": {"dt": ...}}`, and the CEM, and every | ||
| handler it registers, go by that time (see `flexmeasures_client.s2.clock`). | ||
| `dt` has to name its time zone. Without the switch, a wrapped message is refused | ||
| as invalid, from the handshake on. | ||
| - `S2_SITE_PER_RESOURCE_MANAGER`: each resource manager gets a site of its own, | ||
| named after it, with the sensors in `flexmeasures_client.s2.site.FRBC_SITE_SENSORS`. | ||
| Messages are answered with a temporary error until its site is set up, which a | ||
| resource manager retries. Its schedules start from the simulated now and cover | ||
| 24 hours. The site's prices are for whoever runs the simulation to post. | ||
|
|
||
| FlexMeasures reads three settings a co-simulation needs from its instance config | ||
| (`flexmeasures-instance/flexmeasures.cfg` in the flexmeasures checkout), not from the environment: | ||
|
|
||
| ```python | ||
| # A steering round replans a window at the same simulated moment, and that plan replaces the one before. | ||
| # Without this, the scheduler cannot save the second plan, and the home gets no instructions for its window. | ||
| FLEXMEASURES_ALLOW_DATA_OVERWRITE = True | ||
| # Every home triggers a schedule each round, all from one account, far above the default of 10 per 5 minutes, | ||
| # and every trigger past the limit waits minutes before it is retried. | ||
| FLEXMEASURES_API_TRIGGER_RATE_LIMIT = "1000 per minute" | ||
| # A bound on each solve, since a tail of degenerate problems otherwise proves optimality past the job timeout. | ||
| FLEXMEASURES_LP_SOLVER_OPTIONS = {"time_limit": "15"} | ||
| ``` | ||
|
|
||
| A co-simulation reads the sites by sensor name: `price`, `production price`, | ||
| `power` (the schedule), `measured-power` (what the home measured) and | ||
| `state of charge`. | ||
|
|
||
| The stack uses FlexMeasures' own development database service, `dev-db`, | ||
| whose data FlexMeasures' compose file keeps in `docker-compose-data/dev-db` | ||
| inside the flexmeasures checkout it runs from. | ||
| A compose project name of its own does not separate that. | ||
| To keep a development database you care about out of it, | ||
| run it from a folder of its own holding checkouts named `flexmeasures` and `flexmeasures-client`: | ||
| the overlay mounts `../flexmeasures` and `../flexmeasures-client`, so those names are what it runs. | ||
| Git worktrees will do, with one thing to add: FlexMeasures' image reads its version from git, which it cannot through a worktree. | ||
| Give it as the build argument `FLEXMEASURES_VERSION` to each service built from FlexMeasures' Dockerfile, in one more overlay: | ||
|
|
||
| ```yaml | ||
| x-version: &version | ||
| build: | ||
| context: . | ||
| dockerfile: Dockerfile | ||
| args: | ||
| FLEXMEASURES_VERSION: "1.1.0" | ||
|
|
||
| services: | ||
| server: | ||
| <<: *version | ||
| worker: | ||
| <<: *version | ||
| # and likewise worker-2, worker-3, automation-runner and each cem | ||
| ``` | ||
|
|
||
| Its host ports, 5000, 5433, 6380, 1025, 8025 and 8080 to 8084, have to be free too; | ||
| where they are not, an extra overlay can move them with compose's `!override`. | ||
|
|
||
| It runs FlexMeasures from the flexmeasures checkout, which is mounted over the code the image installed, | ||
| with this client installed into the image's virtualenv, which is what FlexMeasures and its plugins run from. | ||
| The UI's third-party libraries are fetched into the installed package when the image is built, so a UI served from the mounted checkout may lack them; the API does not need them. | ||
| The server trusts the name the CEMs reach it by, `server`, alongside the loopback names a development server trusts by default. |
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,175 @@ | ||
| # ------------------------------------------------------------------ | ||
| # A FlexMeasures stack with one S2 CEM per simulated home, | ||
| # for a co-simulation whose resource managers connect over websockets, | ||
| # such as the community simulations that steer several homes at once. | ||
| # | ||
| # It is an overlay on FlexMeasures' own docker-compose.yml, | ||
| # and runs FlexMeasures from the flexmeasures checkout and the CEMs from this one, both mounted. | ||
| # With checkouts named flexmeasures and flexmeasures-client side by side, | ||
| # run it from the flexmeasures folder, which holds the Dockerfile: | ||
| # | ||
| # docker compose \ | ||
| # -f docker-compose.yml \ | ||
| # -f ../flexmeasures-client/examples/S2-co-simulation/docker-compose.cem.yml \ | ||
| # up | ||
| # | ||
| # Resource managers connect to ws://localhost:8080/ws, ws://localhost:8081/ws and so on, one per home. | ||
| # Each gets a site of its own, named after it, | ||
| # and the CEM goes by the simulated time its messages carry (see README.md). | ||
| # ------------------------------------------------------------------ | ||
|
|
||
| # Shared definition for every CEM. | ||
| # Each listens on port 8080 inside its own container; only the host port differs. | ||
| 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: INFO | ||
| # A resource manager in a co-simulation wraps each message with the simulated time. | ||
| S2_ACCEPT_SIMULATION_METADATA: "1" | ||
| # And each home is a site of its own, rather than all of them sharing one. | ||
| S2_SITE_PER_RESOURCE_MANAGER: "1" | ||
| volumes: | ||
| - ../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` is the /app/.venv virtualenv, | ||
| # which may come without pip, and cannot see what the system pip installs. | ||
| python3 -m pip --version >/dev/null 2>&1 || python3 -m ensurepip --upgrade | ||
| # s2-python as this repo locks it, and as the co-simulation's resource managers use it. | ||
| # The checkout may be mounted without git history to read its version from, as a git worktree is, | ||
| # so a version is given, in the plain variable, since hatch-vcs cannot match the per-distribution one. | ||
| SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 python3 -m pip install --break-system-packages -e "/app/flexmeasures-client[s2]" "s2-python==0.8.1" | ||
| exec python3 /app/flexmeasures-client/src/flexmeasures_client/s2/script/websockets_server.py | ||
|
|
||
| # Shared definition for every RQ worker: one per home simulated at once, | ||
| # like the CEMs and the server's gunicorn workers below, | ||
| # since every home's scheduling jobs otherwise queue behind one worker process. | ||
| x-worker: &worker | ||
| # Self-contained, since worker-2 and worker-3 do not exist in the base file to merge with. | ||
| build: | ||
| context: . | ||
| dockerfile: Dockerfile | ||
| depends_on: | ||
| - dev-db | ||
| - queue-db | ||
| - mailhog | ||
| restart: on-failure | ||
| volumes: | ||
| - ./flexmeasures-instance/:/usr/var/flexmeasures-instance/:rw | ||
| - ../flexmeasures/flexmeasures:/app/flexmeasures:rw | ||
| - ./flexmeasures-instance/:/app/instance/:rw | ||
| - ../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 | ||
| command: | ||
| - | | ||
| # Into the image's virtualenv, which FlexMeasures and its plugins run from, with that interpreter's own pip. | ||
| python3 -m pip --version >/dev/null 2>&1 || python3 -m ensurepip --upgrade | ||
| python3 -m pip install -r /usr/var/flexmeasures-instance/requirements.txt | ||
| # The checkout may be mounted without git history to read its version from, as a git worktree is, | ||
| # so a version is given, in the plain variable, since hatch-vcs cannot match the per-distribution one. | ||
| SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 python3 -m pip install -e "/app/flexmeasures-client[s2]" | ||
| # Drop any lingering registration for the ingestion queue before this worker starts. | ||
| # FlexMeasures ingests posted sensor data asynchronously when Redis lists a worker for that queue, | ||
| # and a dead worker's entry counts too: posts then wait on a queue nobody serves, and a run hangs without an error. | ||
| # No worker here serves that queue, so removing those entries on every start is safe, and idempotent. | ||
| 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 redis.RedisError as exc: | ||
| print(f"ingestion-registration guard skipped: {exc}") | ||
| PYGUARD | ||
| # Named after the container, which is unique per worker. | ||
| # Every queue the base file's worker serves but ingestion, which is left to the server to do as it receives the data. | ||
| flexmeasures jobs run-worker --name "flexmeasures-worker-$$(hostname)" --queue scheduling\|forecasting\|reporting | ||
|
|
||
| services: | ||
| dev-db: | ||
| ports: | ||
| - "5433:5432" | ||
| queue-db: | ||
| ports: | ||
| - "6380:6379" | ||
| server: | ||
| environment: | ||
| # The CEMs reach the server by its service name, which a development server does not trust by default. | ||
| TRUSTED_HOSTS: "localhost,.localhost,127.0.0.1,[::1],server" | ||
| volumes: | ||
| - ./flexmeasures-instance/:/usr/var/flexmeasures-instance/:rw | ||
| - ./flexmeasures-instance/:/app/instance/:rw | ||
| - ../flexmeasures/flexmeasures:/app/flexmeasures:rw | ||
| - ../flexmeasures-client:/app/flexmeasures-client:rw | ||
| command: | ||
| - | | ||
| # Into the image's virtualenv, which FlexMeasures and its plugins run from, with that interpreter's own pip. | ||
| python3 -m pip --version >/dev/null 2>&1 || python3 -m ensurepip --upgrade | ||
| python3 -m pip install -r /usr/var/flexmeasures-instance/requirements.txt | ||
| # The checkout may be mounted without git history to read its version from, as a git worktree is, | ||
| # so a version is given, in the plain variable, since hatch-vcs cannot match the per-distribution one. | ||
| SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 python3 -m pip install -e "/app/flexmeasures-client[s2]" | ||
| flexmeasures db upgrade | ||
| if ! flexmeasures show accounts | grep -q "Docker Toy Account"; then | ||
| flexmeasures add toy-account --name 'Docker Toy Account' | ||
| fi | ||
| # Worker processes rather than threads: a slow, CPU-bound request held its worker's GIL, | ||
| # and under several threads that starved the other requests into client-side timeouts. | ||
| # One worker per CEM below, since every home's CEM can be mid-request at once; | ||
| # scale the two together. | ||
| gunicorn --bind 0.0.0.0:5000 --worker-tmp-dir /dev/shm --workers 5 --threads 1 --timeout 120 wsgi:application | ||
| # One worker per home simulated at once; add or remove them along with the CEMs. | ||
| worker: | ||
| <<: *worker | ||
| worker-2: | ||
| <<: *worker | ||
| worker-3: | ||
| <<: *worker | ||
| # One CEM per simulated home; add one by copying an entry and taking the next 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" |
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.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A short section in the PR description explaining what including the CEM does to the coverage reporting on the fm-client repo. I think the readme banner can report only one number, but it feels as if we should be making a distinction in the coverage reporting over the different installation types. Now it's just S2, but there may be other client flavours in the future.