diff --git a/.env.example b/.env.example index 2299911..1c8e0b3 100644 --- a/.env.example +++ b/.env.example @@ -16,6 +16,17 @@ # those checks fail rather than skip when `CI` is set. A skip reads in a summary line # exactly like a pass, and that is how a stale vendored capture went unnoticed for # nine days. See DEVELOPMENT.md, "A skip here is not a pass". +# +# These answer one question of two. A sibling checkout answers "do our bytes still +# match the pin?" -- a byte comparison against a producer sitting where spec_lock.json +# says it sits. It cannot answer "has the producer moved past the pin?", because what +# is on your disk knows nothing about what has been pushed or released since. +# `scripts/peer_drift.py` asks that one, over the network and needing no checkout at +# all, and the pre-commit hook runs it on every local commit -- so a producer release +# is noticed on the commit that should have moved the pin, rather than by a scheduled +# job the next morning. CI skips that hook deliberately: a producer's push must not +# fail somebody's pull request. Nothing below configures the check either way -- it +# reads every pin, path and repository out of spec_lock.json. # A checkout of the eBus specification. # diff --git a/.github/actions/peer-checkouts/action.yml b/.github/actions/peer-checkouts/action.yml index 1814ce8..6610973 100644 --- a/.github/actions/peer-checkouts/action.yml +++ b/.github/actions/peer-checkouts/action.yml @@ -29,31 +29,25 @@ inputs: required: false default: pin +# Only what a workflow step cannot read for itself. Everything else a job needs out of +# the lockfile — the emitter's distribution and released version, each peer's repository +# — is read straight from `spec_lock.json` by `scripts/peer_drift.py`, which asks the +# producers whether they have moved. Exposing those here as well would put a second +# reader between a job and the pin for no gain. What is left is the pair a `git` command +# in a checkout needs: which commit, and which ref was cloned. outputs: panelbench-pin: description: The commit spec_lock.json pins for panelbench, whichever ref was cloned. value: ${{ steps.pins.outputs.panelbench-commit }} - panelbench-repo: - description: The panelbench repository, as owner/name. - value: ${{ steps.pins.outputs.panelbench-repo }} panelbench-checkout: description: The ref actually cloned — the pinned commit, or the producer's branch. value: ${{ steps.pins.outputs.panelbench-checkout }} panel-sim-pin: description: The commit spec_lock.json pins for ebus-panel-sim. value: ${{ steps.pins.outputs.panel-sim-commit }} - panel-sim-repo: - description: The emitter repository, as owner/name. - value: ${{ steps.pins.outputs.panel-sim-repo }} panel-sim-checkout: description: The ref actually cloned — the pinned commit, or the producer's branch. value: ${{ steps.pins.outputs.panel-sim-checkout }} - panel-sim-distribution: - description: The PyPI distribution the emitter releases as. - value: ${{ steps.pins.outputs.panel-sim-distribution }} - panel-sim-version: - description: The released version the reference tree was captured from. - value: ${{ steps.pins.outputs.panel-sim-version }} runs: using: composite @@ -91,10 +85,6 @@ runs: print(f"{prefix}-repo={slug(peer['repo'])}") print(f"{prefix}-commit={peer['commit']}") print(f"{prefix}-checkout={peer['commit'] if mode == 'pin' else peer['ref']}") - - emitter = peers["ebus-panel-sim"] - print(f"panel-sim-distribution={emitter['distribution']}") - print(f"panel-sim-version={emitter['version']}") PY # All three are public, so no token is involved. If any ever goes private this is diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f530d1..e0e87ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,7 +58,12 @@ jobs: - name: Install dependencies run: uv sync --all-packages + # SKIP because `peer-drift` asks whether a *producer* has moved, and that answer + # changes because somebody else pushed — peer-drift.yml is where it is asked, on + # a schedule, so no pull request fails for a push its author did not make. - name: Run pre-commit hooks + env: + SKIP: peer-drift run: | uv run pre-commit run --all-files diff --git a/.github/workflows/peer-drift.yml b/.github/workflows/peer-drift.yml index 296c2d1..a2b25ba 100644 --- a/.github/workflows/peer-drift.yml +++ b/.github/workflows/peer-drift.yml @@ -9,11 +9,13 @@ name: Peer drift # commits we claim they came from -- against the pinned commits, where the answer is # deterministic and blocking a merge on it is fair. # -# Two producers, one job each, and they are not the same shape. panelbench is a -# repository we byte-copy captures out of, so "has it moved" is a commit count. The -# eBus emitter is a released PyPI distribution that we *run* to generate a capture, so -# it has moved in two senses -- commits on main, and a newer release -- and only the -# second is a reason to regenerate. +# Both jobs run `scripts/peer_drift.py --strict`, which is where the comparisons and +# what they mean now live -- including why the two producers are not the same shape, +# and what to do about either one. The same script runs from `.pre-commit-config.yaml` +# on a local commit, non-strict, so drift is usually caught on the commit that should +# have moved the pin rather than by this job the next morning; ci.yml skips that hook +# (`SKIP: peer-drift`) so the paragraph above holds for pull requests too. What stays +# here is the one thing a script with no clone cannot do: name the commits. on: schedule: # Daily. The drift this exists to catch took nine days to be noticed by hand. @@ -43,43 +45,43 @@ jobs: with: peer-ref: default - # Ahead of the comparison, so the summary names the distance whichever way the job - # goes. "N commits behind" with the subjects is what makes the result actionable; - # a red check with no names is a chore. - - name: Report the distance from the pin + # Needs none of the checkouts above -- it asks GitHub what changed between the pin + # and the branch. Red only when the answer names a file this repository vendors, so + # panelbench advancing with a change we do not copy stays green. GITHUB_TOKEN for + # the API rate limit; the script sends it to no other host. + - name: Ask panelbench whether it moved past the pin + env: + GH_TOKEN: ${{ github.token }} + run: | + python3 scripts/peer_drift.py --strict --peer panelbench >> "$GITHUB_STEP_SUMMARY" + + # `if: always()`, so the list lands under the verdict whichever way it went. Naming + # the commits is what makes a red run actionable, and it is the part the script has + # no way to do -- counting or listing commits needs a clone, and this job has one. + - name: List the commits since the pin + if: always() env: PIN: ${{ steps.peers.outputs.panelbench-pin }} - REPO: ${{ steps.peers.outputs.panelbench-repo }} BRANCH: ${{ steps.peers.outputs.panelbench-checkout }} run: | git() { command git -C "$GITHUB_WORKSPACE/peers/panelbench" "$@"; } - head="$(git rev-parse HEAD)" if ! git merge-base --is-ancestor "$PIN" HEAD 2>/dev/null; then - { - echo "## $REPO" - echo - echo "\`$PIN\` is not an ancestor of \`$BRANCH\` (\`${head:0:12}\`)." - echo - echo "The pin names a commit this branch does not contain — a branch that was" - echo "rebased, squash-merged or deleted. \`peers.panelbench.ref\` in \`spec_lock.json\` needs" - echo "to name a ref the pinned commit is actually on." - } >> "$GITHUB_STEP_SUMMARY" + echo "The pinned commit is not on \`$BRANCH\`, so there is no list of commits since it." >> "$GITHUB_STEP_SUMMARY" exit 0 fi behind="$(git rev-list --count "$PIN"..HEAD)" - { - echo "## $REPO" - echo - echo "\`$BRANCH\` is at \`${head:0:12}\`, we pin \`${PIN:0:12}\` — **$behind commits behind**." - if [ "$behind" -gt 0 ]; then + if [ "$behind" -gt 0 ]; then + { + echo + echo "The $behind commits, which only a clone can name:" echo echo '```' git log --oneline --no-decorate "$PIN"..HEAD echo '```' - fi - } >> "$GITHUB_STEP_SUMMARY" + } >> "$GITHUB_STEP_SUMMARY" + fi - name: Install uv uses: astral-sh/setup-uv@v7 @@ -94,55 +96,19 @@ jobs: # byte comparison means, it means the same thing in both jobs, and there is no # second definition of "the captures match" to drift. # - # So a red run means the producer changed something we vendor, not merely that it - # advanced -- a README commit leaves this green while still being reported above. + # It runs after the drift verdict rather than instead of it, and the two cannot + # disagree: the compare API reports the net diff between the pin and the branch, so + # a vendored file it names is a vendored file whose bytes differ. The script says + # so first and cheaply, with the instructions attached; this is the comparison that + # settles it against the bytes themselves. - name: Compare the vendored captures against panelbench's branch run: | uv run pytest tests/test_schema_one_conformance.py -v -rs - - name: Say what a failure means - if: failure() - env: - PIN: ${{ steps.peers.outputs.panelbench-pin }} - run: | - { - echo - echo "### We have fallen behind the producer" - echo - echo "panelbench has changed something this repository keeps a copy of, and the copy" - echo "still reflects \`${PIN:0:12}\`. Read the failing assertion above for which:" - echo "a capture, or the specification commit the producer itself pins." - echo - echo "For a capture, re-vendor and re-pin together, in one change:" - echo - echo '```bash' - echo "cp \$PANELBENCH_DIR/tests/conformance/fixtures/golden_tree.json \\" - echo " packages/schema-1/spec/fixtures/simulator_tree.json" - echo "cp \$PANELBENCH_DIR/tests/conformance/fixtures/golden_wire.json \\" - echo " packages/schema-1/spec/fixtures/simulator_wire.json" - echo '```' - echo - echo "then set \`peers.panelbench.commit\` in \`packages/schema-1/src/span_panel_api_schema_1/spec_lock.json\`" - echo "to the commit you copied from. A capture without a commit bump records where the" - echo "bytes came from as a guess." - echo - echo "For the specification commit, the two sides are reading different vocabularies" - echo "until \`synced_commit\` and the vendored catalogs move together." - } >> "$GITHUB_STEP_SUMMARY" - emitter: name: Has the eBus emitter released past the pin? runs-on: ubuntu-latest - # Two answers, and the release is the one that matters. `ebus-panel-sim` is a - # published distribution, and `scripts/capture_parent_child_reference.py` refuses - # to write a capture taken from any version other than the one spec_lock.json - # records -- so regenerating the reference tree is gated on a *release*, not on a - # commit. Commits on main are reported too, because they are what a release will - # be made of and seeing them early is free, but they are not a call to action. - # - # So the release comparison is the verdict and fails the job; the commit distance - # runs either way and only ever reports. steps: - name: Checkout code uses: actions/checkout@v7 @@ -153,103 +119,42 @@ jobs: with: peer-ref: default - # The question that decides whether to recapture. PyPI's JSON API is public and - # unauthenticated; a failure to reach it reports as unknown rather than as drift, - # because "we could not ask" and "there is a new release" are different facts and - # only one of them is actionable. - - name: Compare the pinned release against PyPI - env: - DISTRIBUTION: ${{ steps.peers.outputs.panel-sim-distribution }} - PINNED: ${{ steps.peers.outputs.panel-sim-version }} + # The release is the verdict and the commits are context -- the script says why, and + # says it in the summary. Red, deliberately, when the emitter has released past the + # pin: a scheduled run that succeeds notifies nobody, and this job exists because + # the last drift of this kind took nine days to be noticed by hand. It gates no pull + # request, so failing costs a notification and nothing else, and it stays red until + # the capture is regenerated or the pin is moved -- which is the honest state of a + # reference tree that describes a superseded producer. + - name: Ask the emitter whether it released past the pin run: | - python3 - <<'PY' >> "$GITHUB_STEP_SUMMARY" - import json - import os - import urllib.error - import urllib.request - - distribution = os.environ["DISTRIBUTION"] - pinned = os.environ["PINNED"] - print(f"## {distribution}\n") - - try: - with urllib.request.urlopen( - f"https://pypi.org/pypi/{distribution}/json", timeout=30 - ) as response: - latest = json.load(response)["info"]["version"] - except (urllib.error.URLError, TimeoutError, KeyError, json.JSONDecodeError) as error: - print(f"Could not ask PyPI for the latest release (`{error}`).\n") - print("Reported as unknown rather than as drift: not having asked is not the") - print("same fact as there being nothing new.") - raise SystemExit(0) - - if latest == pinned: - print(f"Latest release is `{latest}`, which is what we pin. The reference tree is current.") - raise SystemExit(0) - - print(f"Latest release is `{latest}`, we pin `{pinned}`.\n") - print("The reference tree was captured from the pinned release, so it now describes a") - print("producer that has been superseded. To follow:\n") - print("```bash") - print(f"# in a checkout of the emitter at v{latest}, from its own environment") - print("uv run python ../span-panel-api/scripts/capture_parent_child_reference.py \\") - print(" ../span-panel-api/tests/reference_payloads/parent_child_tree.json") - print("```\n") - print("The script refuses until `peers.ebus-panel-sim.version` and `.commit` in") - print("`spec_lock.json` name the release you captured from, which is what keeps the") - print("bytes and the claim about them from drifting apart. Read the emitter's") - print("CHANGELOG for the wire changes before accepting the new capture: a diff") - print("confined to each `$description`'s `version` means nothing moved.") - - # Red, deliberately. A scheduled run that succeeds notifies nobody, and this - # job exists because the last drift of this kind "took nine days to be noticed - # by hand" -- a step summary nobody is told to read would repeat that. It gates - # no pull request, so failing costs a notification and nothing else. It stays - # red until the capture is regenerated or the pin is moved, which is the honest - # state of a reference tree that describes a superseded producer. - raise SystemExit(1) - PY - - # Reported after the release comparison because it is context for it, not the - # verdict. "N commits behind" with the subjects is what makes the result readable; - # a red check with no names is a chore. - - name: Report the distance from the pinned commit + python3 scripts/peer_drift.py --strict --peer ebus-panel-sim >> "$GITHUB_STEP_SUMMARY" + + # After the verdict because it is context for it, and `if: always()` because + # unreleased commits are worth reading on exactly the day the release comparison + # went red. The script reports that the branch moved; only a clone can say by how + # much and which commits, so that part stays here. + - name: List the commits since the pin if: always() env: PIN: ${{ steps.peers.outputs.panel-sim-pin }} - REPO: ${{ steps.peers.outputs.panel-sim-repo }} BRANCH: ${{ steps.peers.outputs.panel-sim-checkout }} run: | git() { command git -C "$GITHUB_WORKSPACE/peers/panel-sim" "$@"; } - head="$(git rev-parse HEAD)" if ! git merge-base --is-ancestor "$PIN" HEAD 2>/dev/null; then - { - echo - echo "### $REPO commits" - echo - echo "\`$PIN\` is not an ancestor of \`$BRANCH\` (\`${head:0:12}\`)." - echo - echo "The pin names a commit this branch does not contain — a branch that was" - echo "rebased, squash-merged or deleted. \`peers.ebus-panel-sim.ref\` in" - echo "\`spec_lock.json\` needs to name a ref the pinned commit is actually on." - } >> "$GITHUB_STEP_SUMMARY" + echo "The pinned commit is not on \`$BRANCH\`, so there is no list of commits since it." >> "$GITHUB_STEP_SUMMARY" exit 0 fi behind="$(git rev-list --count "$PIN"..HEAD)" - { - echo - echo "### $REPO commits" - echo - echo "\`$BRANCH\` is at \`${head:0:12}\`, we pin \`${PIN:0:12}\` — **$behind commits behind**." - if [ "$behind" -gt 0 ]; then + if [ "$behind" -gt 0 ]; then + { echo - echo "Unreleased work, so not itself a reason to recapture — the release comparison" - echo "above is." + echo "The $behind unreleased commits, which only a clone can name:" echo echo '```' git log --oneline --no-decorate "$PIN"..HEAD echo '```' - fi - } >> "$GITHUB_STEP_SUMMARY" + } >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7c8e6b6..d9513fd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,12 +32,17 @@ repos: rev: v0.11.13 hooks: # Run formatter first - exclude tests to avoid conflicts with black + # + # `scripts/` is excluded except for `peer_drift.py` and `_lock.py`, which run + # on every commit and in CI and are held to the library's standard; the + # hand-run tools beside them are not. `pyproject.toml` names the same two + # exclusions for a bare `ruff check .`. - id: ruff-format - exclude: '^src/span_panel_api/generated_client/.*|generate_client\.py|scripts/.*|tests/.*|\..*_cache/.*|dist/.*|venv/.*|\.venv/.*|^examples/.*' - # Then linter - exclude tests and scripts from strict linting + exclude: '^src/span_panel_api/generated_client/.*|generate_client\.py|scripts/(?!peer_drift\.py$|_lock\.py$).*|tests/.*|\..*_cache/.*|dist/.*|venv/.*|\.venv/.*|^examples/.*' + # Then linter - exclude tests and the hand-run scripts from strict linting - id: ruff-check args: ['--fix'] - exclude: '^src/span_panel_api/generated_client/.*|generate_client\.py|scripts/.*|tests/.*|\..*_cache/.*|dist/.*|venv/.*|\.venv/.*|^examples/.*' + exclude: '^src/span_panel_api/generated_client/.*|generate_client\.py|scripts/(?!peer_drift\.py$|_lock\.py$).*|tests/.*|\..*_cache/.*|dist/.*|venv/.*|\.venv/.*|^examples/.*' # Black for line length handling and test file formatting - repo: https://github.com/psf/black @@ -93,7 +98,12 @@ repos: # have quietly cost them `--strict`. `pyproject.toml`'s own `exclude` still # names tests/, which is fine — that flag governs directory discovery, and # a file passed by name is checked regardless. - exclude: '^src/span_panel_api/generated_client/.*|scripts/.*|tests/(?!reference_payloads/).*|docs/.*|examples/.*|\..*_cache/.*|dist/.*|venv/.*' + # + # `scripts/peer_drift.py` and `scripts/_lock.py` are checked for the same + # reason: they decide whether a commit goes through, which is not the place + # for the one part of the tree nothing type-checks. The hand-run scripts + # beside them stay out — see `pyproject.toml`, which names them one by one. + exclude: '^src/span_panel_api/generated_client/.*|scripts/(?!peer_drift\.py$|_lock\.py$).*|tests/(?!reference_payloads/).*|docs/.*|examples/.*|\..*_cache/.*|dist/.*|venv/.*' # Pylint for code quality - repo: https://github.com/pycqa/pylint @@ -142,6 +152,36 @@ repos: pass_filenames: false files: ^pyproject\.toml$|^uv\.lock$ + # Have the producers moved past the pins in spec_lock.json? + # + # On a local commit, because CI finding this out is finding it out too late: the + # drift this catches is a producer release nobody here noticed until a scheduled + # job went red the next morning, by which time the commits that should have moved + # the pin were already in. + # + # `stages: [pre-commit]` and `SKIP: peer-drift` in ci.yml keep it off pull + # requests, and that is not an oversight to tidy up later: this question's answer + # changes because somebody else pushed, so asking it of an author's unrelated + # change would fail their build for a thing they did not do. peer-drift.yml asks + # it daily, with --strict, where failing costs a notification and nothing else. + # + # It asks PyPI and GitHub, so it is the one hook that needs the network — and + # deliberately non-strict, so it does not. A producer that cannot be reached + # reports as unknown and the commit goes through; only a producer that has + # actually moved past a pin stops one. Five calls at a ten-second timeout, so the + # worst case is under a minute and only against a network that accepts + # connections and then says nothing; a refused connection or a failed lookup + # comes back at once. + - repo: local + hooks: + - id: peer-drift + name: peers still at their pins + entry: uv run python scripts/peer_drift.py + language: system + pass_filenames: false + always_run: true + stages: [pre-commit] + # Quick coverage check (total only, no details) - repo: local hooks: diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 9f63545..48d4887 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -74,6 +74,25 @@ It cost us a second time on 2026-08-20, in the other vendored capture. `tests/fi lower-case, which is the flat half of a change panelbench made on the v1.0 side the same week. Nothing compared the capture to its source, so for nine days the two vendored captures named the same charger differently. `scripts/capture_flat_reference.py` now records the simulator commit its output came from, for the same reason `spec_lock.json` records the other two. +### The other question: has the producer moved? + +A sibling checkout answers _do our bytes still match the pin?_ It cannot answer _has the producer moved past it?_, because what is on your disk knows nothing about what has been pushed or released since. `scripts/peer_drift.py` asks that one — PyPI for the +emitter's latest release, GitHub's compare API for what panelbench changed, `git ls-remote` for where a branch is — reading every pin, path and repository out of `spec_lock.json`, and needing no checkout at all: + +```bash +uv run python scripts/peer_drift.py # all three producers +uv run python scripts/peer_drift.py --peer panelbench # one of them +``` + +It runs as a pre-commit hook so that drift is caught on the commit that should have moved the pin. Non-strict there: a producer that cannot be reached reports as **UNKNOWN** and the commit still goes through, because not having asked is not the same fact +as there being nothing new, and a laptop with no network still has to be able to commit. Only a producer that has actually moved past a pin stops one. + +**Local commits ask; pull requests do not.** `ci.yml` runs the same hooks with `SKIP: peer-drift`, and the hook is `stages: [pre-commit]`, so a producer that moved cannot fail somebody's unrelated pull request — the answer changes because a third party +pushed, and failing an author for that is how a check gets ignored. `peer-drift.yml` asks it daily with `--strict`, where being unable to reach a producer is a broken run rather than bad wifi, and where a red result costs a notification and nothing else. + +Two comparisons are verdicts, and both are verdicts about bytes here having gone stale: the emitter's **release**, because the reference tree is a capture of one, and a **panelbench commit that touches a file we vendor**. Everything else is reported and +stays green — unreleased commits on the emitter's branch, panelbench commits that change nothing we copy, and the specification itself, which says what a device class may publish rather than what one does. + ### Regenerating a vendored capture Two scripts, one per producer, and neither is run automatically — a capture is a deliberate act. @@ -94,7 +113,7 @@ Its input is committed too, as `scripts/reference_panel.yaml`, pinned as `peers. panel that mirrors the emitter's own `examples/forty_tab_minimal.yaml` key for key and marks its two deliberate divergences at the head of the file: spec-legal shed priorities in place of a value the emitter degrades to `UNKNOWN` (electrification-bus/distribution-enclosure-simulator#51), and the identity properties a real panel publishes. Read that file before assuming ours has drifted from theirs. -Expect a recapture to move every `$description`'s `version`, which is minted from the wall clock. A diff confined to those thirteen lines means the producer did not move. +Expect a recapture to move every `$description`'s `version`, which is minted from the wall clock. A diff confined to those fourteen lines means the producer did not move. ### The producer is the specification, executable @@ -111,8 +130,12 @@ The peer checks answer a question whose shape depends on which producer revision - **`.github/workflows/ci.yml`** clones both peers at the commits `spec_lock.json` pins, via the `.github/actions/peer-checkouts` composite action, and runs the whole suite against them. The question is _do our vendored bytes match the commit we claim they came from?_ — deterministic, answerable on any commit, and fair to block a merge on. It catches an accidental local edit to a vendored file. -- **`.github/workflows/peer-drift.yml`** runs on a schedule, never on a pull request, and clones each producer at its `ref` — the branch that producer develops on. The question is _has the producer moved past the pin?_ Its answer changes because someone - else pushed, so it must not fail an author's unrelated change. It reports the distance from the pin in the job summary either way, and goes red only when the comparison itself fails, so panelbench advancing with a change we do not vendor stays green. +- **`.github/workflows/peer-drift.yml`** runs on a schedule, never on a pull request. The question is _has the producer moved past the pin?_ Its answer changes because someone else pushed, so it must not fail an author's unrelated change. It runs + `scripts/peer_drift.py --strict`, which asks the producers directly and needs no clone; the clones it still takes are there to name the commits, which is the one thing that script cannot do. It goes red only on a verdict — a new emitter release, or a + panelbench commit touching a file we vendor — so panelbench advancing with a change we do not copy stays green. + +The second question is also asked on every local commit, by the `peer-drift` pre-commit hook running the same script non-strict — and skipped in `ci.yml`, so it stays off pull requests for the same reason the workflow does. The workflow is the backstop +rather than the check: a producer that moved is best found by the commit that should have moved the pin with it. All three repositories are public, so no checkout needs a token. If either ever goes private, the checkout step in the composite action is what starts failing, and the fix is a read-scoped PAT in its `token:` — the pin is not involved. diff --git a/RELEASE.md b/RELEASE.md index 9ff1a45..4d59c60 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -207,3 +207,8 @@ Versions like `3.0.0b1` are pre-releases in both places that matter: - **GitHub** should have "Set as a pre-release" ticked, which keeps them out of the repository's "Latest release" slot. The publish workflow itself does not care — `on: release: published` fires either way. + +## When a producer releases + +Releasing the eBus emitter past the version we pin, or landing a panelbench commit that touches a capture we vendor, leaves this repository's `peers` block describing a producer that has been superseded — so the next commit here fails the `peer-drift` +pre-commit hook until `spec_lock.json` moves with it. That is the intended order: re-vendor or re-capture and re-pin in one change, then commit. `gh workflow run peer-drift.yml` asks the same question on demand rather than waiting for the daily run. diff --git a/packages/schema-1/CHANGELOG.md b/packages/schema-1/CHANGELOG.md index 7ab35d2..2f198ec 100644 --- a/packages/schema-1/CHANGELOG.md +++ b/packages/schema-1/CHANGELOG.md @@ -9,6 +9,15 @@ number. A release here means this parser changed, never that the panel did. Pre-releases are not listed separately. A beta is a step towards the next public version, so its changes are folded into that version's entry as they land and are described against the last public release, never against the beta before it. +## [1.1.2] + +A refreshed peer pin: `spec_lock.json` ships inside this wheel, so the reference capture's producer moving is a release here even though the parser did not change. + +### Changed + +- **The reference capture and the peer pins move to `ebus-panel-sim` 0.8.0 and panelbench 2.4.0, and the reference tree now carries a circuit commissioned never-backup** — so the priority lock this parser reads is a case a conforming producer emits rather + than one a test builds by editing a declaration. + ## [1.1.1] A correctness fix to how this parser reads write authorisation off a `$description`. No API change, and the floor on `span-panel-api` stays at **3.1.0**: nothing here asks anything new of the bootstrap. diff --git a/packages/schema-1/pyproject.toml b/packages/schema-1/pyproject.toml index 029d039..e0cedbd 100644 --- a/packages/schema-1/pyproject.toml +++ b/packages/schema-1/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "span-panel-api-schema-1" -version = "1.1.1" +version = "1.1.2" description = "Parent/child schema (data-model-version 1.x) parser for span-panel-api" authors = [ {name = "SpanPanel"} diff --git a/packages/schema-1/spec/fixtures/simulator_tree.json b/packages/schema-1/spec/fixtures/simulator_tree.json index 8546434..ee75fd6 100644 --- a/packages/schema-1/spec/fixtures/simulator_tree.json +++ b/packages/schema-1/spec/fixtures/simulator_tree.json @@ -24,10 +24,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -135,7 +131,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627511 + "version": 1787808834687 }, "1bfdc7ecebb0547bbe87a3696cddb0c0": { "children": [], @@ -162,10 +158,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -273,7 +265,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627514 + "version": 1787808834690 }, "1eeeb748eeaa58edb7e9b7e9dbbdeca7": { "children": [], @@ -300,10 +292,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -411,7 +399,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627513 + "version": 1787808834689 }, "2140a7e253ed54e3bc90a959081df615": { "children": [], @@ -438,10 +426,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -530,8 +514,7 @@ "relay": { "datatype": "enum", "format": "UNKNOWN,OPEN,CLOSED", - "name": "Circuit relay state", - "settable": true + "name": "Circuit relay state" }, "relay-controllable": { "datatype": "boolean", @@ -549,7 +532,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627511 + "version": 1787808834688 }, "249a2f59782e5f1ab317c4632e79afad": { "children": [], @@ -576,10 +559,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -687,7 +666,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627514 + "version": 1787808834690 }, "3d9d86f303cc50d1827be57d4c667e53": { "children": [], @@ -714,10 +693,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -825,7 +800,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627509 + "version": 1787808834686 }, "3eeb0eb1605e5a7eadac41994b7a096c": { "children": [], @@ -852,10 +827,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -963,7 +934,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627510 + "version": 1787808834686 }, "43a0521737db516f99f14a9964ea4af0": { "children": [], @@ -990,10 +961,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -1101,7 +1068,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627512 + "version": 1787808834688 }, "4aeb08c46c2c5905a944166413f2f1ef": { "children": [], @@ -1128,10 +1095,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -1239,7 +1202,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627512 + "version": 1787808834689 }, "4ce8b30e8d3f5c49b9e0ab0c8caf4832": { "children": [], @@ -1266,10 +1229,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -1377,7 +1336,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627514 + "version": 1787808834690 }, "4d1deb6acb065746b13207b1358f8ca7": { "children": [], @@ -1404,10 +1363,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -1515,7 +1470,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627512 + "version": 1787808834688 }, "516694a326a35cd88600b3520e8a981a": { "children": [], @@ -1542,10 +1497,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -1653,7 +1604,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627512 + "version": 1787808834689 }, "6fcb352679ad5bfb8c8a8eab06829b9f": { "children": [], @@ -1680,10 +1631,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -1772,8 +1719,7 @@ "relay": { "datatype": "enum", "format": "UNKNOWN,OPEN,CLOSED", - "name": "Circuit relay state", - "settable": true + "name": "Circuit relay state" }, "relay-controllable": { "datatype": "boolean", @@ -1791,7 +1737,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627514 + "version": 1787808834690 }, "770e2de52c33508a8a9ee8878064b46f": { "children": [], @@ -1818,10 +1764,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -1929,7 +1871,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627509 + "version": 1787808834685 }, "80a4fada833156ab8112f9d50e252b8f": { "children": [], @@ -1956,10 +1898,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -2067,7 +2005,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627510 + "version": 1787808834687 }, "9429f828509e58d59cb5f0f9f5fee523": { "children": [], @@ -2094,10 +2032,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -2205,7 +2139,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627509 + "version": 1787808834686 }, "948dea7788aa5c959b99df0edfabead2": { "children": [], @@ -2232,10 +2166,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -2343,7 +2273,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627513 + "version": 1787808834689 }, "af731c49a6785a4cb2ea5549fb8bce7e": { "children": [], @@ -2370,10 +2300,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -2481,7 +2407,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627513 + "version": 1787808834689 }, "afe90839f2725e3e962fb05afa2b6d43": { "children": [], @@ -2508,10 +2434,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -2600,8 +2522,7 @@ "relay": { "datatype": "enum", "format": "UNKNOWN,OPEN,CLOSED", - "name": "Circuit relay state", - "settable": true + "name": "Circuit relay state" }, "relay-controllable": { "datatype": "boolean", @@ -2619,7 +2540,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627512 + "version": 1787808834688 }, "b24483358d29589d8e91d3bf11113269": { "children": [], @@ -2646,10 +2567,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -2757,7 +2674,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627511 + "version": 1787808834687 }, "b9fa08f1eaaf5d129bd5c78e1d5d937f": { "children": [], @@ -2784,10 +2701,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -2895,7 +2808,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627514 + "version": 1787808834691 }, "be7742043a06554aab2a1e38cc776603": { "children": [], @@ -2922,10 +2835,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -3033,7 +2942,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627513 + "version": 1787808834690 }, "c058aa11287f50f9b81e5160a0678869": { "children": [], @@ -3060,10 +2969,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -3171,7 +3076,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627510 + "version": 1787808834686 }, "c339ec7ce7ff521ca7646f9606baff9f": { "children": [], @@ -3198,10 +3103,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -3309,7 +3210,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627511 + "version": 1787808834688 }, "d1ff145887a05b839ede89409c27b398": { "children": [], @@ -3336,10 +3237,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -3447,7 +3344,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627511 + "version": 1787808834687 }, "e0ac90e169e6550ea83fe0b1942f1d0e": { "children": [], @@ -3474,10 +3371,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -3585,7 +3478,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627510 + "version": 1787808834687 }, "e0bc156c85015a609d4132084dfcd6fe": { "children": [], @@ -3612,10 +3505,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -3723,7 +3612,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627512 + "version": 1787808834688 }, "edee3425d50d51ffb022ee999053b2b4": { "children": [], @@ -3750,10 +3639,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -3861,7 +3746,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627511 + "version": 1787808834687 }, "ef972f063451539e8b2ad88e831d87b6": { "children": [], @@ -3888,10 +3773,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -3999,7 +3880,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627513 + "version": 1787808834689 }, "f515a0f43b6555b1a196fbb62728c24e": { "children": [], @@ -4026,10 +3907,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated downstream (e.g. microinverters, packs)" - }, "feeds-device-id": { "datatype": "string", "name": "Homie device-id of the downstream device fed by this circuit" @@ -4137,7 +4014,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.circuit", - "version": 1786424627510 + "version": 1787808834686 }, "sim-40t-001": { "children": [ @@ -4443,7 +4320,7 @@ } }, "type": "energy.ebus.device.distribution-enclosure", - "version": 1786424627515 + "version": 1787808834691 }, "sim-40t-001-SIM-BESS-40T-001": { "children": [ @@ -4526,7 +4403,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.bess", - "version": 1786424627515 + "version": 1787808834691 }, "sim-40t-001-SIM-BESS-40T-001-mid": { "children": [], @@ -4584,183 +4461,7 @@ "parent": "sim-40t-001-SIM-BESS-40T-001", "root": "sim-40t-001", "type": "energy.ebus.device.mid", - "version": 1786424627515 - }, - "sim-40t-001-sim-evse-sim-40t-001": { - "children": [], - "extensions": [], - "homie": "5.0", - "name": "SPAN Drive - Garage", - "nodes": { - "config": { - "name": "config", - "properties": { - "max-charge-current": { - "datatype": "integer", - "name": "Commissioned maximum EVSE charge current (installer-configured)", - "unit": "A" - }, - "user-max-charge-current": { - "datatype": "integer", - "name": "User-configured maximum EVSE charge current (ceiling)", - "settable": true, - "unit": "A" - } - }, - "type": "energy.ebus.capability.config" - }, - "info": { - "name": "info", - "properties": { - "firmware-version": { - "datatype": "string", - "name": "Firmware version" - }, - "model": { - "datatype": "string", - "name": "Model" - }, - "part-number": { - "datatype": "string", - "name": "Part number" - }, - "serial-number": { - "datatype": "string", - "name": "Serial number" - }, - "vendor-name": { - "datatype": "string", - "name": "Vendor name" - } - }, - "type": "energy.ebus.capability.info" - }, - "meter": { - "name": "meter", - "properties": { - "advertised-current": { - "datatype": "float", - "name": "Current EVSE is advertising to the EV", - "unit": "A" - } - }, - "type": "energy.ebus.capability.meter" - }, - "status": { - "name": "status", - "properties": { - "status": { - "datatype": "enum", - "format": "AVAILABLE,PREPARING,CHARGING,UNAVAILABLE", - "name": "Status" - } - }, - "type": "energy.ebus.capability.status" - }, - "switch": { - "name": "switch", - "properties": { - "lock-state": { - "datatype": "enum", - "format": "UNLOCKED,LOCKED", - "name": "Lock state" - } - }, - "type": "energy.ebus.capability.switch" - } - }, - "parent": "sim-40t-001", - "root": "sim-40t-001", - "type": "energy.ebus.device.evse", - "version": 1786424627514 - }, - "sim-40t-001-sim-evse-sim-40t-001-2": { - "children": [], - "extensions": [], - "homie": "5.0", - "name": "SPAN Drive - Driveway", - "nodes": { - "config": { - "name": "config", - "properties": { - "max-charge-current": { - "datatype": "integer", - "name": "Commissioned maximum EVSE charge current (installer-configured)", - "unit": "A" - }, - "user-max-charge-current": { - "datatype": "integer", - "name": "User-configured maximum EVSE charge current (ceiling)", - "settable": true, - "unit": "A" - } - }, - "type": "energy.ebus.capability.config" - }, - "info": { - "name": "info", - "properties": { - "firmware-version": { - "datatype": "string", - "name": "Firmware version" - }, - "model": { - "datatype": "string", - "name": "Model" - }, - "part-number": { - "datatype": "string", - "name": "Part number" - }, - "serial-number": { - "datatype": "string", - "name": "Serial number" - }, - "vendor-name": { - "datatype": "string", - "name": "Vendor name" - } - }, - "type": "energy.ebus.capability.info" - }, - "meter": { - "name": "meter", - "properties": { - "advertised-current": { - "datatype": "float", - "name": "Current EVSE is advertising to the EV", - "unit": "A" - } - }, - "type": "energy.ebus.capability.meter" - }, - "status": { - "name": "status", - "properties": { - "status": { - "datatype": "enum", - "format": "AVAILABLE,PREPARING,CHARGING,UNAVAILABLE", - "name": "Status" - } - }, - "type": "energy.ebus.capability.status" - }, - "switch": { - "name": "switch", - "properties": { - "lock-state": { - "datatype": "enum", - "format": "UNLOCKED,LOCKED", - "name": "Lock state" - } - }, - "type": "energy.ebus.capability.switch" - } - }, - "parent": "sim-40t-001", - "root": "sim-40t-001", - "type": "energy.ebus.device.evse", - "version": 1786424627515 + "version": 1787808834691 }, "sim-40t-001-lugs-dn": { "children": [], @@ -4771,10 +4472,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated up/downstream" - }, "fed-by-device-id": { "datatype": "string", "name": "Homie device-id of the upstream device feeding this lugs" @@ -4850,7 +4547,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.lugs", - "version": 1786424627515 + "version": 1787808834691 }, "sim-40t-001-lugs-up": { "children": [], @@ -4861,10 +4558,6 @@ "connection": { "name": "connection", "properties": { - "count": { - "datatype": "integer", - "name": "Number of physical units aggregated up/downstream" - }, "fed-by-device-id": { "datatype": "string", "name": "Homie device-id of the upstream device feeding this lugs" @@ -4940,7 +4633,7 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.lugs", - "version": 1786424627515 + "version": 1787808834691 }, "sim-40t-001-pv-1": { "children": [], @@ -4979,6 +4672,182 @@ "parent": "sim-40t-001", "root": "sim-40t-001", "type": "energy.ebus.device.pv", - "version": 1786424627515 + "version": 1787808834691 + }, + "sim-40t-001-sim-evse-sim-40t-001": { + "children": [], + "extensions": [], + "homie": "5.0", + "name": "SPAN Drive - Garage", + "nodes": { + "config": { + "name": "config", + "properties": { + "max-charge-current": { + "datatype": "integer", + "name": "Commissioned maximum EVSE charge current (installer-configured)", + "unit": "A" + }, + "user-max-charge-current": { + "datatype": "integer", + "name": "User-configured maximum EVSE charge current (ceiling)", + "settable": true, + "unit": "A" + } + }, + "type": "energy.ebus.capability.config" + }, + "info": { + "name": "info", + "properties": { + "firmware-version": { + "datatype": "string", + "name": "Firmware version" + }, + "model": { + "datatype": "string", + "name": "Model" + }, + "part-number": { + "datatype": "string", + "name": "Part number" + }, + "serial-number": { + "datatype": "string", + "name": "Serial number" + }, + "vendor-name": { + "datatype": "string", + "name": "Vendor name" + } + }, + "type": "energy.ebus.capability.info" + }, + "meter": { + "name": "meter", + "properties": { + "advertised-current": { + "datatype": "float", + "name": "Current EVSE is advertising to the EV", + "unit": "A" + } + }, + "type": "energy.ebus.capability.meter" + }, + "status": { + "name": "status", + "properties": { + "status": { + "datatype": "enum", + "format": "AVAILABLE,PREPARING,CHARGING,UNAVAILABLE", + "name": "Status" + } + }, + "type": "energy.ebus.capability.status" + }, + "switch": { + "name": "switch", + "properties": { + "lock-state": { + "datatype": "enum", + "format": "UNLOCKED,LOCKED", + "name": "Lock state" + } + }, + "type": "energy.ebus.capability.switch" + } + }, + "parent": "sim-40t-001", + "root": "sim-40t-001", + "type": "energy.ebus.device.evse", + "version": 1787808834691 + }, + "sim-40t-001-sim-evse-sim-40t-001-2": { + "children": [], + "extensions": [], + "homie": "5.0", + "name": "SPAN Drive - Driveway", + "nodes": { + "config": { + "name": "config", + "properties": { + "max-charge-current": { + "datatype": "integer", + "name": "Commissioned maximum EVSE charge current (installer-configured)", + "unit": "A" + }, + "user-max-charge-current": { + "datatype": "integer", + "name": "User-configured maximum EVSE charge current (ceiling)", + "settable": true, + "unit": "A" + } + }, + "type": "energy.ebus.capability.config" + }, + "info": { + "name": "info", + "properties": { + "firmware-version": { + "datatype": "string", + "name": "Firmware version" + }, + "model": { + "datatype": "string", + "name": "Model" + }, + "part-number": { + "datatype": "string", + "name": "Part number" + }, + "serial-number": { + "datatype": "string", + "name": "Serial number" + }, + "vendor-name": { + "datatype": "string", + "name": "Vendor name" + } + }, + "type": "energy.ebus.capability.info" + }, + "meter": { + "name": "meter", + "properties": { + "advertised-current": { + "datatype": "float", + "name": "Current EVSE is advertising to the EV", + "unit": "A" + } + }, + "type": "energy.ebus.capability.meter" + }, + "status": { + "name": "status", + "properties": { + "status": { + "datatype": "enum", + "format": "AVAILABLE,PREPARING,CHARGING,UNAVAILABLE", + "name": "Status" + } + }, + "type": "energy.ebus.capability.status" + }, + "switch": { + "name": "switch", + "properties": { + "lock-state": { + "datatype": "enum", + "format": "UNLOCKED,LOCKED", + "name": "Lock state" + } + }, + "type": "energy.ebus.capability.switch" + } + }, + "parent": "sim-40t-001", + "root": "sim-40t-001", + "type": "energy.ebus.device.evse", + "version": 1787808834691 } } diff --git a/packages/schema-1/spec/fixtures/simulator_wire.json b/packages/schema-1/spec/fixtures/simulator_wire.json index 7bd1bdb..919837d 100644 --- a/packages/schema-1/spec/fixtures/simulator_wire.json +++ b/packages/schema-1/spec/fixtures/simulator_wire.json @@ -1,14 +1,14 @@ { "13044bfbcbe5554b8f3dba126bce828f": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617685, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Kitchen Outlets (Island)\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834687, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Kitchen Outlets (Island)\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "20", "info/name": "Kitchen Outlets (Island)", "info/spaces": "10", "load-shed/priority": "NEVER", - "meter/active-power": "-295.52615631181357", - "meter/current": "2.462717969265113", + "meter/active-power": "-316.2382093849783", + "meter/current": "2.6353184115414856", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -18,7 +18,7 @@ "switch/relay-requester": "NONE" }, "1bfdc7ecebb0547bbe87a3696cddb0c0": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617688, \"type\": \"energy.ebus.device.circuit\", \"name\": \"SPAN Drive - Driveway\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834690, \"type\": \"energy.ebus.device.circuit\", \"name\": \"SPAN Drive - Driveway\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "2", "breaker/rating": "50", @@ -39,15 +39,15 @@ "switch/relay-requester": "NONE" }, "1eeeb748eeaa58edb7e9b7e9dbbdeca7": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617687, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Smoke Detectors\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834689, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Smoke Detectors\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "15", "info/name": "Smoke Detectors", "info/spaces": "40", "load-shed/priority": "NEVER", - "meter/active-power": "-4.79282953433586", - "meter/current": "0.0399402461194655", + "meter/active-power": "-5.062257438750594", + "meter/current": "0.04218547865625495", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -57,25 +57,25 @@ "switch/relay-requester": "NONE" }, "2140a7e253ed54e3bc90a959081df615": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617686, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Refrigerator\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834688, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Refrigerator\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\"}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "20", "info/name": "Refrigerator", "info/spaces": "15", "load-shed/priority": "NEVER", - "meter/active-power": "-124.32888886191921", - "meter/current": "1.0360740738493268", + "meter/active-power": "-125.72371105321349", + "meter/current": "1.0476975921101124", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "false", "pcs/priority": "14", "switch/relay": "CLOSED", "switch/relay-controllable": "false", - "switch/relay-requester": "NONE" + "switch/relay-requester": "CONFIGURATION" }, "249a2f59782e5f1ab317c4632e79afad": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617688, \"type\": \"energy.ebus.device.circuit\", \"name\": \"SPAN Drive - Garage\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834690, \"type\": \"energy.ebus.device.circuit\", \"name\": \"SPAN Drive - Garage\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "2", "breaker/rating": "50", @@ -96,15 +96,15 @@ "switch/relay-requester": "NONE" }, "3d9d86f303cc50d1827be57d4c667e53": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617684, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Bedroom Lights\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834686, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Bedroom Lights\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "15", "info/name": "Bedroom Lights", "info/spaces": "4", "load-shed/priority": "NEVER", - "meter/active-power": "-32.54905694719264", - "meter/current": "0.27124214122660534", + "meter/active-power": "-56.71304901954643", + "meter/current": "0.4726087418295536", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -114,15 +114,15 @@ "switch/relay-requester": "NONE" }, "3eeb0eb1605e5a7eadac41994b7a096c": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617684, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Master Bedroom Outlets\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834686, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Master Bedroom Outlets\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "15", "info/name": "Master Bedroom Outlets", "info/spaces": "7", "load-shed/priority": "NEVER", - "meter/active-power": "-156.07339750944152", - "meter/current": "1.3006116459120127", + "meter/active-power": "-156.67040842581636", + "meter/current": "1.305586736881803", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -132,15 +132,15 @@ "switch/relay-requester": "NONE" }, "43a0521737db516f99f14a9964ea4af0": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617686, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Washing Machine\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834688, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Washing Machine\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "20", "info/name": "Washing Machine", "info/spaces": "17", "load-shed/priority": "OFF_GRID", - "meter/active-power": "-738.3921387303916", - "meter/current": "6.153267822753263", + "meter/active-power": "0.0", + "meter/current": "0.0", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -150,7 +150,7 @@ "switch/relay-requester": "NONE" }, "4aeb08c46c2c5905a944166413f2f1ef": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617686, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Garbage Disposal\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834689, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Garbage Disposal\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "15", @@ -168,15 +168,15 @@ "switch/relay-requester": "NONE" }, "4ce8b30e8d3f5c49b9e0ab0c8caf4832": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617688, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Water Heater\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834690, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Water Heater\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "2", "breaker/rating": "30", "info/name": "Water Heater", "info/spaces": "31,33", "load-shed/priority": "OFF_GRID", - "meter/active-power": "-4500.0", - "meter/current": "18.75", + "meter/active-power": "-2440.8522238207183", + "meter/current": "10.170217599252993", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -186,7 +186,7 @@ "switch/relay-requester": "NONE" }, "4d1deb6acb065746b13207b1358f8ca7": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617686, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Dishwasher\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834688, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Dishwasher\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "20", @@ -204,15 +204,15 @@ "switch/relay-requester": "NONE" }, "516694a326a35cd88600b3520e8a981a": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617687, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Pool Pump\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834689, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Pool Pump\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "20", "info/name": "Pool Pump", "info/spaces": "39", "load-shed/priority": "OFF_GRID", - "meter/active-power": "-217.26021119423217", - "meter/current": "1.8105017599519349", + "meter/active-power": "0.0", + "meter/current": "0.0", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -222,7 +222,7 @@ "switch/relay-requester": "NONE" }, "6fcb352679ad5bfb8c8a8eab06829b9f": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617688, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Solar Inverter\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834690, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Solar Inverter\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\"}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "2", "breaker/rating": "30", @@ -232,26 +232,26 @@ "info/name": "Solar Inverter", "info/spaces": "36,38", "load-shed/priority": "NEVER", - "meter/active-power": "2029.544536896322", - "meter/current": "8.456435570401341", + "meter/active-power": "0.0", + "meter/current": "0.0", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "false", "pcs/priority": "29", "switch/relay": "CLOSED", "switch/relay-controllable": "false", - "switch/relay-requester": "NONE" + "switch/relay-requester": "CONFIGURATION" }, "770e2de52c33508a8a9ee8878064b46f": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617683, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Master Bedroom Lights\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834685, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Master Bedroom Lights\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "15", "info/name": "Master Bedroom Lights", "info/spaces": "1", "load-shed/priority": "NEVER", - "meter/active-power": "-15.079182336548172", - "meter/current": "0.1256598528045681", + "meter/active-power": "-30.32221262652881", + "meter/current": "0.2526851052210734", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -261,15 +261,15 @@ "switch/relay-requester": "NONE" }, "80a4fada833156ab8112f9d50e252b8f": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617684, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Kitchen Outlets (Counter)\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834687, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Kitchen Outlets (Counter)\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "20", "info/name": "Kitchen Outlets (Counter)", "info/spaces": "9", "load-shed/priority": "NEVER", - "meter/active-power": "-313.35079337973053", - "meter/current": "2.6112566114977542", + "meter/active-power": "-325.34125309928953", + "meter/current": "2.711177109160746", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -279,15 +279,15 @@ "switch/relay-requester": "NONE" }, "9429f828509e58d59cb5f0f9f5fee523": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617683, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Living Room Lights\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834686, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Living Room Lights\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "15", "info/name": "Living Room Lights", "info/spaces": "2", "load-shed/priority": "NEVER", - "meter/active-power": "-20.77555195401851", - "meter/current": "0.17312959961682092", + "meter/active-power": "-34.472444900751626", + "meter/current": "0.2872703741729302", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -297,15 +297,15 @@ "switch/relay-requester": "NONE" }, "948dea7788aa5c959b99df0edfabead2": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617687, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Heat Pump\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834689, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Heat Pump\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "2", "breaker/rating": "30", "info/name": "Heat Pump", "info/spaces": "27,29", "load-shed/priority": "OFF_GRID", - "meter/active-power": "-1263.514205767015", - "meter/current": "5.264642524029229", + "meter/active-power": "-965.0519069091841", + "meter/current": "4.0210496121216", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -315,15 +315,15 @@ "switch/relay-requester": "NONE" }, "af731c49a6785a4cb2ea5549fb8bce7e": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617687, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Main HVAC\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834689, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Main HVAC\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "2", "breaker/rating": "30", "info/name": "Main HVAC", "info/spaces": "23,25", "load-shed/priority": "NEVER", - "meter/active-power": "-618.5089366700175", - "meter/current": "2.5771205694584065", + "meter/active-power": "-320.48691088550527", + "meter/current": "1.3353621286896054", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -333,33 +333,33 @@ "switch/relay-requester": "NONE" }, "afe90839f2725e3e962fb05afa2b6d43": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617686, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Chest Freezer\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834688, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Chest Freezer\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\"}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "20", "info/name": "Chest Freezer", "info/spaces": "19", "load-shed/priority": "NEVER", - "meter/active-power": "-86.21815758174944", - "meter/current": "0.7184846465145787", + "meter/active-power": "-89.07854876022438", + "meter/current": "0.7423212396685365", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "false", "pcs/priority": "18", "switch/relay": "CLOSED", "switch/relay-controllable": "false", - "switch/relay-requester": "NONE" + "switch/relay-requester": "CONFIGURATION" }, "b24483358d29589d8e91d3bf11113269": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617685, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Office Outlets\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834687, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Office Outlets\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "15", "info/name": "Office Outlets", "info/spaces": "11", "load-shed/priority": "NEVER", - "meter/active-power": "-259.11072766424627", - "meter/current": "2.159256063868719", + "meter/active-power": "-309.05471791239904", + "meter/current": "2.575455982603325", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -369,15 +369,15 @@ "switch/relay-requester": "NONE" }, "b9fa08f1eaaf5d129bd5c78e1d5d937f": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617688, \"type\": \"energy.ebus.device.circuit\", \"name\": \"kitchen Lights\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834691, \"type\": \"energy.ebus.device.circuit\", \"name\": \"kitchen Lights\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "15", "info/name": "kitchen Lights", "info/spaces": "3", "load-shed/priority": "NEVER", - "meter/active-power": "-141.27576756242206", - "meter/current": "1.177298063020184", + "meter/active-power": "-127.52464717836219", + "meter/current": "1.0627053931530182", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -387,15 +387,15 @@ "switch/relay-requester": "NONE" }, "be7742043a06554aab2a1e38cc776603": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617687, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Electric Oven/Range\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834690, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Electric Oven/Range\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "2", "breaker/rating": "40", "info/name": "Electric Oven/Range", "info/spaces": "28,30", "load-shed/priority": "OFF_GRID", - "meter/active-power": "-5000.0", - "meter/current": "20.833333333333332", + "meter/active-power": "0.0", + "meter/current": "0.0", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -405,15 +405,15 @@ "switch/relay-requester": "NONE" }, "c058aa11287f50f9b81e5160a0678869": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617684, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Bathroom Lights\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834686, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Bathroom Lights\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "15", "info/name": "Bathroom Lights", "info/spaces": "5", "load-shed/priority": "NEVER", - "meter/active-power": "-11.460658123756456", - "meter/current": "0.09550548436463714", + "meter/active-power": "-21.07568035928572", + "meter/current": "0.17563066966071433", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -423,15 +423,15 @@ "switch/relay-requester": "NONE" }, "c339ec7ce7ff521ca7646f9606baff9f": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617685, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Guest Room Outlets\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834688, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Guest Room Outlets\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "15", "info/name": "Guest Room Outlets", "info/spaces": "14", "load-shed/priority": "NEVER", - "meter/active-power": "-137.99326566949193", - "meter/current": "1.1499438805790994", + "meter/active-power": "-142.31988939201733", + "meter/current": "1.185999078266811", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -441,15 +441,15 @@ "switch/relay-requester": "NONE" }, "d1ff145887a05b839ede89409c27b398": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617685, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Garage Outlets\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834687, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Garage Outlets\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "15", "info/name": "Garage Outlets", "info/spaces": "12", "load-shed/priority": "NEVER", - "meter/active-power": "-163.20433875732755", - "meter/current": "1.360036156311063", + "meter/active-power": "-171.67427934793716", + "meter/current": "1.430618994566143", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -459,15 +459,15 @@ "switch/relay-requester": "NONE" }, "e0ac90e169e6550ea83fe0b1942f1d0e": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617684, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Living Room Outlets\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834687, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Living Room Outlets\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "15", "info/name": "Living Room Outlets", "info/spaces": "8", "load-shed/priority": "NEVER", - "meter/active-power": "-244.30939312922507", - "meter/current": "2.035911609410209", + "meter/active-power": "-276.1586544850663", + "meter/current": "2.301322120708886", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -477,15 +477,15 @@ "switch/relay-requester": "NONE" }, "e0bc156c85015a609d4132084dfcd6fe": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617686, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Microwave\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834688, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Microwave\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "20", "info/name": "Microwave", "info/spaces": "18", "load-shed/priority": "NEVER", - "meter/active-power": "-1500.0", - "meter/current": "12.5", + "meter/active-power": "0.0", + "meter/current": "0.0", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -495,15 +495,15 @@ "switch/relay-requester": "NONE" }, "edee3425d50d51ffb022ee999053b2b4": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617685, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Laundry Room Outlets\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834687, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Laundry Room Outlets\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "15", "info/name": "Laundry Room Outlets", "info/spaces": "13", "load-shed/priority": "NEVER", - "meter/active-power": "-129.62101242313324", - "meter/current": "1.0801751035261102", + "meter/active-power": "-146.5534394043421", + "meter/current": "1.2212786617028508", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -513,15 +513,15 @@ "switch/relay-requester": "NONE" }, "ef972f063451539e8b2ad88e831d87b6": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617687, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Electric Dryer\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834689, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Electric Dryer\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "2", "breaker/rating": "30", "info/name": "Electric Dryer", "info/spaces": "20,22", "load-shed/priority": "OFF_GRID", - "meter/active-power": "-5000.0", - "meter/current": "20.833333333333332", + "meter/active-power": "0.0", + "meter/current": "0.0", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -531,15 +531,15 @@ "switch/relay-requester": "NONE" }, "f515a0f43b6555b1a196fbb62728c24e": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617684, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Exterior Lights\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated downstream (e.g. microinverters, packs)\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834686, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Exterior Lights\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "15", "info/name": "Exterior Lights", "info/spaces": "6", "load-shed/priority": "OFF_GRID", - "meter/active-power": "0.0", - "meter/current": "0.0", + "meter/active-power": "-62.39139331008993", + "meter/current": "0.5199282775840828", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0", "pcs/managed": "true", @@ -549,7 +549,7 @@ "switch/relay-requester": "NONE" }, "sim-40t-001": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617689, \"type\": \"energy.ebus.device.distribution-enclosure\", \"name\": \"Span Panel\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"enum\", \"format\": \"MAIN_16,MLO_24,MAIN_32,MAIN_40,MLO_48\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"hardware-version\": {\"name\": \"Hardware version\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}, \"data-model-version\": {\"name\": \"eBus data-model version (parent/child schema discriminator)\", \"datatype\": \"string\"}}}, \"door\": {\"name\": \"door\", \"type\": \"energy.ebus.capability.door\", \"properties\": {\"state\": {\"name\": \"Door state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"voltage-a\": {\"name\": \"L1 voltage\", \"datatype\": \"float\", \"unit\": \"V\"}, \"voltage-b\": {\"name\": \"L2 voltage\", \"datatype\": \"float\", \"unit\": \"V\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Main breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"enabled\": {\"name\": \"PCS system enabled\", \"datatype\": \"boolean\"}, \"active\": {\"name\": \"PCS system actively controlling one (or more) loads\", \"datatype\": \"boolean\"}, \"import-limit\": {\"name\": \"The power import limit currently being managed to\", \"datatype\": \"float\", \"unit\": \"A\"}, \"binding-constraint\": {\"name\": \"Which constraint class currently sets the import limit\", \"datatype\": \"enum\", \"format\": \"FSR,DOE,VOLTAGE,OFF_GRID,REQUESTED,OPERATOR,NONE,UNKNOWN\"}, \"feed-import-limit\": {\"name\": \"Limit of maximum power feeding the distribution enclosure\", \"datatype\": \"float\", \"unit\": \"A\"}, \"feed-import-limit-enablement\": {\"name\": \"Enablement status of the feed-import-limit\", \"datatype\": \"enum\", \"format\": \"UNSPECIFIED,UNCONFIGURED,DISABLED,ENABLED\"}, \"feed-import-limit-active\": {\"name\": \"Is feed-import-limit currently being enforced?\", \"datatype\": \"boolean\"}, \"operator-import-limit\": {\"name\": \"Operator-imposed maximum import limit\", \"datatype\": \"float\", \"unit\": \"A\"}, \"operator-import-limit-enablement\": {\"name\": \"Enablement status of the operator-import-limit\", \"datatype\": \"enum\", \"format\": \"UNSPECIFIED,UNCONFIGURED,DISABLED,ENABLED\"}, \"operator-import-limit-active\": {\"name\": \"Is operator-import-limit currently being enforced?\", \"datatype\": \"boolean\"}, \"off-grid-import-limit\": {\"name\": \"Off-Grid limit maximum import power\", \"datatype\": \"float\", \"unit\": \"A\"}, \"off-grid-import-limit-enablement\": {\"name\": \"Enablement status of the off-grid-import-limit\", \"datatype\": \"enum\", \"format\": \"UNSPECIFIED,UNCONFIGURED,DISABLED,ENABLED\"}, \"off-grid-import-limit-active\": {\"name\": \"Is off-grid-import-limit currently being enforced?\", \"datatype\": \"boolean\"}, \"requested-import-limit\": {\"name\": \"Requested limit maximum import power\", \"datatype\": \"float\", \"unit\": \"A\"}, \"requested-import-limit-enablement\": {\"name\": \"Enablement status of the requested-import-limit\", \"datatype\": \"enum\", \"format\": \"UNSPECIFIED,UNCONFIGURED,DISABLED,ENABLED\"}, \"requested-import-limit-active\": {\"name\": \"Is requested-import-limit currently being enforced?\", \"datatype\": \"boolean\"}}}, \"shed-forecast\": {\"name\": \"shed-forecast\", \"type\": \"energy.ebus.capability.shed-forecast\", \"properties\": {\"total-time-remaining\": {\"name\": \"Estimated total time before all sheddable circuits are shed (off-grid runtime)\", \"datatype\": \"integer\", \"unit\": \"min\"}, \"time-to-priority-shed\": {\"name\": \"Estimated time before the next priority tier is shed\", \"datatype\": \"integer\", \"unit\": \"min\"}, \"full-charge-total-time-remaining\": {\"name\": \"Estimated total time assuming BESS starts at full charge\", \"datatype\": \"integer\", \"unit\": \"min\"}, \"full-charge-time-to-priority-shed\": {\"name\": \"Estimated time to next priority shed assuming BESS starts at full charge\", \"datatype\": \"integer\", \"unit\": \"min\"}, \"confidence\": {\"name\": \"Confidence of the shed-forecast estimate\", \"datatype\": \"enum\", \"format\": \"LOW,MEDIUM,HIGH\"}}}, \"shed\": {\"name\": \"shed\", \"type\": \"energy.ebus.capability.shed\", \"properties\": {\"asserted-islanding-state\": {\"name\": \"Consumer-asserted islanding-state (grid-state override during MID/BESS comm-loss)\", \"datatype\": \"enum\", \"format\": \"NONE,ON_GRID,OFF_GRID\", \"settable\": true}, \"policy\": {\"name\": \"Shed policy (algorithm and parameters)\", \"datatype\": \"json\", \"format\": \"{\\\"$id\\\":\\\"soc-priority.v1\\\",\\\"type\\\":\\\"object\\\",\\\"required\\\":[\\\"algorithm\\\",\\\"parameters\\\"],\\\"additionalProperties\\\":false,\\\"properties\\\":{\\\"algorithm\\\":{\\\"const\\\":\\\"soc-priority.v1\\\"},\\\"parameters\\\":{\\\"type\\\":\\\"object\\\",\\\"required\\\":[\\\"soc-threshold-shed\\\",\\\"soc-threshold-release\\\"],\\\"additionalProperties\\\":false,\\\"properties\\\":{\\\"soc-threshold-shed\\\":{\\\"type\\\":\\\"integer\\\",\\\"minimum\\\":0,\\\"maximum\\\":100,\\\"description\\\":\\\"SoC percent below which SOC_THRESHOLD circuits shed\\\"},\\\"soc-threshold-release\\\":{\\\"type\\\":\\\"integer\\\",\\\"minimum\\\":0,\\\"maximum\\\":100,\\\"description\\\":\\\"SoC percent above which shed SOC_THRESHOLD circuits restore\\\"}}}}}\"}}}, \"power-flows\": {\"name\": \"power-flows\", \"type\": \"energy.ebus.capability.power-flows\", \"properties\": {\"pv\": {\"name\": \"PV power flow\", \"datatype\": \"float\", \"unit\": \"W\"}, \"battery\": {\"name\": \"Battery/BESS power flow\", \"datatype\": \"float\", \"unit\": \"W\"}, \"grid\": {\"name\": \"Grid power flow\", \"datatype\": \"float\", \"unit\": \"W\"}, \"site\": {\"name\": \"Site power flow\", \"datatype\": \"float\", \"unit\": \"W\"}}}, \"status\": {\"name\": \"status\", \"type\": \"energy.ebus.capability.status\", \"properties\": {\"relay\": {\"name\": \"Main relay\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\"}, \"ethernet\": {\"name\": \"Is Ethernet network interface operational?\", \"datatype\": \"boolean\"}, \"wifi\": {\"name\": \"Is Wi-Fi network interface operational?\", \"datatype\": \"boolean\"}, \"wifi-ssid\": {\"name\": \"SSID to which Wi-Fi network interface is connected\", \"datatype\": \"string\"}, \"cloud-connection\": {\"name\": \"Device connected to vendor cloud?\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,UNCONNECTED,CONNECTED\"}, \"postal-code\": {\"name\": \"Postal (Zip) code\", \"datatype\": \"string\"}, \"time-zone\": {\"name\": \"Time zone\", \"datatype\": \"string\"}}}}, \"children\": [\"sim-40t-001-SIM-BESS-40T-001\", \"770e2de52c33508a8a9ee8878064b46f\", \"9429f828509e58d59cb5f0f9f5fee523\", \"3d9d86f303cc50d1827be57d4c667e53\", \"c058aa11287f50f9b81e5160a0678869\", \"f515a0f43b6555b1a196fbb62728c24e\", \"3eeb0eb1605e5a7eadac41994b7a096c\", \"e0ac90e169e6550ea83fe0b1942f1d0e\", \"80a4fada833156ab8112f9d50e252b8f\", \"13044bfbcbe5554b8f3dba126bce828f\", \"b24483358d29589d8e91d3bf11113269\", \"d1ff145887a05b839ede89409c27b398\", \"edee3425d50d51ffb022ee999053b2b4\", \"c339ec7ce7ff521ca7646f9606baff9f\", \"2140a7e253ed54e3bc90a959081df615\", \"4d1deb6acb065746b13207b1358f8ca7\", \"43a0521737db516f99f14a9964ea4af0\", \"e0bc156c85015a609d4132084dfcd6fe\", \"afe90839f2725e3e962fb05afa2b6d43\", \"4aeb08c46c2c5905a944166413f2f1ef\", \"516694a326a35cd88600b3520e8a981a\", \"1eeeb748eeaa58edb7e9b7e9dbbdeca7\", \"ef972f063451539e8b2ad88e831d87b6\", \"af731c49a6785a4cb2ea5549fb8bce7e\", \"948dea7788aa5c959b99df0edfabead2\", \"be7742043a06554aab2a1e38cc776603\", \"4ce8b30e8d3f5c49b9e0ab0c8caf4832\", \"249a2f59782e5f1ab317c4632e79afad\", \"1bfdc7ecebb0547bbe87a3696cddb0c0\", \"6fcb352679ad5bfb8c8a8eab06829b9f\", \"b9fa08f1eaaf5d129bd5c78e1d5d937f\", \"sim-40t-001-sim-evse-sim-40t-001\", \"sim-40t-001-sim-evse-sim-40t-001-2\", \"sim-40t-001-lugs-up\", \"sim-40t-001-lugs-dn\", \"sim-40t-001-pv-1\"], \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834691, \"type\": \"energy.ebus.device.distribution-enclosure\", \"name\": \"Span Panel\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"enum\", \"format\": \"MAIN_16,MLO_24,MAIN_32,MAIN_40,MLO_48\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"hardware-version\": {\"name\": \"Hardware version\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}, \"data-model-version\": {\"name\": \"eBus data-model version (parent/child schema discriminator)\", \"datatype\": \"string\"}}}, \"door\": {\"name\": \"door\", \"type\": \"energy.ebus.capability.door\", \"properties\": {\"state\": {\"name\": \"Door state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"voltage-a\": {\"name\": \"L1 voltage\", \"datatype\": \"float\", \"unit\": \"V\"}, \"voltage-b\": {\"name\": \"L2 voltage\", \"datatype\": \"float\", \"unit\": \"V\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Main breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"enabled\": {\"name\": \"PCS system enabled\", \"datatype\": \"boolean\"}, \"active\": {\"name\": \"PCS system actively controlling one (or more) loads\", \"datatype\": \"boolean\"}, \"import-limit\": {\"name\": \"The power import limit currently being managed to\", \"datatype\": \"float\", \"unit\": \"A\"}, \"binding-constraint\": {\"name\": \"Which constraint class currently sets the import limit\", \"datatype\": \"enum\", \"format\": \"FSR,DOE,VOLTAGE,OFF_GRID,REQUESTED,OPERATOR,NONE,UNKNOWN\"}, \"feed-import-limit\": {\"name\": \"Limit of maximum power feeding the distribution enclosure\", \"datatype\": \"float\", \"unit\": \"A\"}, \"feed-import-limit-enablement\": {\"name\": \"Enablement status of the feed-import-limit\", \"datatype\": \"enum\", \"format\": \"UNSPECIFIED,UNCONFIGURED,DISABLED,ENABLED\"}, \"feed-import-limit-active\": {\"name\": \"Is feed-import-limit currently being enforced?\", \"datatype\": \"boolean\"}, \"operator-import-limit\": {\"name\": \"Operator-imposed maximum import limit\", \"datatype\": \"float\", \"unit\": \"A\"}, \"operator-import-limit-enablement\": {\"name\": \"Enablement status of the operator-import-limit\", \"datatype\": \"enum\", \"format\": \"UNSPECIFIED,UNCONFIGURED,DISABLED,ENABLED\"}, \"operator-import-limit-active\": {\"name\": \"Is operator-import-limit currently being enforced?\", \"datatype\": \"boolean\"}, \"off-grid-import-limit\": {\"name\": \"Off-Grid limit maximum import power\", \"datatype\": \"float\", \"unit\": \"A\"}, \"off-grid-import-limit-enablement\": {\"name\": \"Enablement status of the off-grid-import-limit\", \"datatype\": \"enum\", \"format\": \"UNSPECIFIED,UNCONFIGURED,DISABLED,ENABLED\"}, \"off-grid-import-limit-active\": {\"name\": \"Is off-grid-import-limit currently being enforced?\", \"datatype\": \"boolean\"}, \"requested-import-limit\": {\"name\": \"Requested limit maximum import power\", \"datatype\": \"float\", \"unit\": \"A\"}, \"requested-import-limit-enablement\": {\"name\": \"Enablement status of the requested-import-limit\", \"datatype\": \"enum\", \"format\": \"UNSPECIFIED,UNCONFIGURED,DISABLED,ENABLED\"}, \"requested-import-limit-active\": {\"name\": \"Is requested-import-limit currently being enforced?\", \"datatype\": \"boolean\"}}}, \"shed-forecast\": {\"name\": \"shed-forecast\", \"type\": \"energy.ebus.capability.shed-forecast\", \"properties\": {\"total-time-remaining\": {\"name\": \"Estimated total time before all sheddable circuits are shed (off-grid runtime)\", \"datatype\": \"integer\", \"unit\": \"min\"}, \"time-to-priority-shed\": {\"name\": \"Estimated time before the next priority tier is shed\", \"datatype\": \"integer\", \"unit\": \"min\"}, \"full-charge-total-time-remaining\": {\"name\": \"Estimated total time assuming BESS starts at full charge\", \"datatype\": \"integer\", \"unit\": \"min\"}, \"full-charge-time-to-priority-shed\": {\"name\": \"Estimated time to next priority shed assuming BESS starts at full charge\", \"datatype\": \"integer\", \"unit\": \"min\"}, \"confidence\": {\"name\": \"Confidence of the shed-forecast estimate\", \"datatype\": \"enum\", \"format\": \"LOW,MEDIUM,HIGH\"}}}, \"shed\": {\"name\": \"shed\", \"type\": \"energy.ebus.capability.shed\", \"properties\": {\"asserted-islanding-state\": {\"name\": \"Consumer-asserted islanding-state (grid-state override during MID/BESS comm-loss)\", \"datatype\": \"enum\", \"format\": \"NONE,ON_GRID,OFF_GRID\", \"settable\": true}, \"policy\": {\"name\": \"Shed policy (algorithm and parameters)\", \"datatype\": \"json\", \"format\": \"{\\\"$id\\\":\\\"soc-priority.v1\\\",\\\"type\\\":\\\"object\\\",\\\"required\\\":[\\\"algorithm\\\",\\\"parameters\\\"],\\\"additionalProperties\\\":false,\\\"properties\\\":{\\\"algorithm\\\":{\\\"const\\\":\\\"soc-priority.v1\\\"},\\\"parameters\\\":{\\\"type\\\":\\\"object\\\",\\\"required\\\":[\\\"soc-threshold-shed\\\",\\\"soc-threshold-release\\\"],\\\"additionalProperties\\\":false,\\\"properties\\\":{\\\"soc-threshold-shed\\\":{\\\"type\\\":\\\"integer\\\",\\\"minimum\\\":0,\\\"maximum\\\":100,\\\"description\\\":\\\"SoC percent below which SOC_THRESHOLD circuits shed\\\"},\\\"soc-threshold-release\\\":{\\\"type\\\":\\\"integer\\\",\\\"minimum\\\":0,\\\"maximum\\\":100,\\\"description\\\":\\\"SoC percent above which shed SOC_THRESHOLD circuits restore\\\"}}}}}\"}}}, \"power-flows\": {\"name\": \"power-flows\", \"type\": \"energy.ebus.capability.power-flows\", \"properties\": {\"pv\": {\"name\": \"PV power flow\", \"datatype\": \"float\", \"unit\": \"W\"}, \"battery\": {\"name\": \"Battery/BESS power flow\", \"datatype\": \"float\", \"unit\": \"W\"}, \"grid\": {\"name\": \"Grid power flow\", \"datatype\": \"float\", \"unit\": \"W\"}, \"site\": {\"name\": \"Site power flow\", \"datatype\": \"float\", \"unit\": \"W\"}}}, \"status\": {\"name\": \"status\", \"type\": \"energy.ebus.capability.status\", \"properties\": {\"relay\": {\"name\": \"Main relay\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\"}, \"ethernet\": {\"name\": \"Is Ethernet network interface operational?\", \"datatype\": \"boolean\"}, \"wifi\": {\"name\": \"Is Wi-Fi network interface operational?\", \"datatype\": \"boolean\"}, \"wifi-ssid\": {\"name\": \"SSID to which Wi-Fi network interface is connected\", \"datatype\": \"string\"}, \"cloud-connection\": {\"name\": \"Device connected to vendor cloud?\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,UNCONNECTED,CONNECTED\"}, \"postal-code\": {\"name\": \"Postal (Zip) code\", \"datatype\": \"string\"}, \"time-zone\": {\"name\": \"Time zone\", \"datatype\": \"string\"}}}}, \"children\": [\"sim-40t-001-SIM-BESS-40T-001\", \"770e2de52c33508a8a9ee8878064b46f\", \"9429f828509e58d59cb5f0f9f5fee523\", \"3d9d86f303cc50d1827be57d4c667e53\", \"c058aa11287f50f9b81e5160a0678869\", \"f515a0f43b6555b1a196fbb62728c24e\", \"3eeb0eb1605e5a7eadac41994b7a096c\", \"e0ac90e169e6550ea83fe0b1942f1d0e\", \"80a4fada833156ab8112f9d50e252b8f\", \"13044bfbcbe5554b8f3dba126bce828f\", \"b24483358d29589d8e91d3bf11113269\", \"d1ff145887a05b839ede89409c27b398\", \"edee3425d50d51ffb022ee999053b2b4\", \"c339ec7ce7ff521ca7646f9606baff9f\", \"2140a7e253ed54e3bc90a959081df615\", \"4d1deb6acb065746b13207b1358f8ca7\", \"43a0521737db516f99f14a9964ea4af0\", \"e0bc156c85015a609d4132084dfcd6fe\", \"afe90839f2725e3e962fb05afa2b6d43\", \"4aeb08c46c2c5905a944166413f2f1ef\", \"516694a326a35cd88600b3520e8a981a\", \"1eeeb748eeaa58edb7e9b7e9dbbdeca7\", \"ef972f063451539e8b2ad88e831d87b6\", \"af731c49a6785a4cb2ea5549fb8bce7e\", \"948dea7788aa5c959b99df0edfabead2\", \"be7742043a06554aab2a1e38cc776603\", \"4ce8b30e8d3f5c49b9e0ab0c8caf4832\", \"249a2f59782e5f1ab317c4632e79afad\", \"1bfdc7ecebb0547bbe87a3696cddb0c0\", \"6fcb352679ad5bfb8c8a8eab06829b9f\", \"b9fa08f1eaaf5d129bd5c78e1d5d937f\", \"sim-40t-001-sim-evse-sim-40t-001\", \"sim-40t-001-sim-evse-sim-40t-001-2\", \"sim-40t-001-lugs-up\", \"sim-40t-001-lugs-dn\", \"sim-40t-001-pv-1\"], \"extensions\": []}", "$state": "ready", "breaker/rating": "200", "door/state": "CLOSED", @@ -577,10 +577,10 @@ "pcs/requested-import-limit": "0.0", "pcs/requested-import-limit-active": "false", "pcs/requested-import-limit-enablement": "UNCONFIGURED", - "power-flows/battery": "3500.0", - "power-flows/grid": "15443.800133211684", - "power-flows/pv": "2029.544536896322", - "power-flows/site": "20973.344670108007", + "power-flows/battery": "-3500.0", + "power-flows/grid": "-2622.765837714007", + "power-flows/pv": "0", + "power-flows/site": "6122.765837714007", "shed-forecast/confidence": "HIGH", "shed-forecast/full-charge-time-to-priority-shed": "3038", "shed-forecast/full-charge-total-time-remaining": "4320", @@ -597,7 +597,7 @@ "status/wifi-ssid": "sim-wifi" }, "sim-40t-001-SIM-BESS-40T-001": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617689, \"type\": \"energy.ebus.device.bess\", \"name\": \"Battery\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"string\"}, \"part-number\": {\"name\": \"Part number\", \"datatype\": \"string\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}, \"nameplate-capacity\": {\"name\": \"Nameplate capacity\", \"datatype\": \"float\", \"unit\": \"kWh\"}}}, \"soc\": {\"name\": \"soc\", \"type\": \"energy.ebus.capability.soc\", \"properties\": {\"soc\": {\"name\": \"State of charge\", \"datatype\": \"float\", \"unit\": \"%\"}, \"soe\": {\"name\": \"State of energy\", \"datatype\": \"float\", \"unit\": \"kWh\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"active-power\": {\"name\": \"Active power\", \"datatype\": \"float\", \"unit\": \"W\"}}}, \"status\": {\"name\": \"status\", \"type\": \"energy.ebus.capability.status\", \"properties\": {\"communication-state\": {\"name\": \"Communication state\", \"datatype\": \"enum\", \"format\": \"OK,DEGRADED,LOST,UNKNOWN\"}}}}, \"children\": [\"sim-40t-001-SIM-BESS-40T-001-mid\"], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834691, \"type\": \"energy.ebus.device.bess\", \"name\": \"Battery\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"string\"}, \"part-number\": {\"name\": \"Part number\", \"datatype\": \"string\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}, \"nameplate-capacity\": {\"name\": \"Nameplate capacity\", \"datatype\": \"float\", \"unit\": \"kWh\"}}}, \"soc\": {\"name\": \"soc\", \"type\": \"energy.ebus.capability.soc\", \"properties\": {\"soc\": {\"name\": \"State of charge\", \"datatype\": \"float\", \"unit\": \"%\"}, \"soe\": {\"name\": \"State of energy\", \"datatype\": \"float\", \"unit\": \"kWh\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"active-power\": {\"name\": \"Active power\", \"datatype\": \"float\", \"unit\": \"W\"}}}, \"status\": {\"name\": \"status\", \"type\": \"energy.ebus.capability.status\", \"properties\": {\"communication-state\": {\"name\": \"Communication state\", \"datatype\": \"enum\", \"format\": \"OK,DEGRADED,LOST,UNKNOWN\"}}}}, \"children\": [\"sim-40t-001-SIM-BESS-40T-001-mid\"], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "info/firmware-version": "sim-bess/v0.1.0", "info/model": "SPAN Battery", @@ -605,13 +605,13 @@ "info/part-number": "SPN-BESS-001", "info/serial-number": "SIM-BESS-40T-001", "info/vendor-name": "Span", - "meter/active-power": "3500.0", + "meter/active-power": "-3500.0", "soc/soc": "50.0", "soc/soe": "6.75", "status/communication-state": "OK" }, "sim-40t-001-SIM-BESS-40T-001-mid": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617689, \"type\": \"energy.ebus.device.mid\", \"name\": \"Microgrid Interconnect Device\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}, \"hardware-version\": {\"name\": \"Hardware version\", \"datatype\": \"string\"}}}, \"grid\": {\"name\": \"grid\", \"type\": \"energy.ebus.capability.grid\", \"properties\": {\"islanding-state\": {\"name\": \"Islanding state of the BESS-integrated grid-forming device\", \"datatype\": \"enum\", \"format\": \"ON_GRID,OFF_GRID,UNKNOWN\"}, \"grid-state\": {\"name\": \"Sensed grid condition\", \"datatype\": \"enum\", \"format\": \"UP,DOWN,DEGRADED,UNKNOWN\"}, \"grid-forming-entity\": {\"name\": \"Identity of the currently grid-forming entity\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001-SIM-BESS-40T-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834691, \"type\": \"energy.ebus.device.mid\", \"name\": \"Microgrid Interconnect Device\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}, \"hardware-version\": {\"name\": \"Hardware version\", \"datatype\": \"string\"}}}, \"grid\": {\"name\": \"grid\", \"type\": \"energy.ebus.capability.grid\", \"properties\": {\"islanding-state\": {\"name\": \"Islanding state of the BESS-integrated grid-forming device\", \"datatype\": \"enum\", \"format\": \"ON_GRID,OFF_GRID,UNKNOWN\"}, \"grid-state\": {\"name\": \"Sensed grid condition\", \"datatype\": \"enum\", \"format\": \"UP,DOWN,DEGRADED,UNKNOWN\"}, \"grid-forming-entity\": {\"name\": \"Identity of the currently grid-forming entity\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001-SIM-BESS-40T-001\", \"extensions\": []}", "$state": "ready", "grid/grid-forming-entity": "GRID", "grid/grid-state": "UP", @@ -623,30 +623,30 @@ "info/vendor-name": "Span" }, "sim-40t-001-lugs-dn": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617689, \"type\": \"energy.ebus.device.lugs\", \"name\": \"Downstream lugs\", \"nodes\": {\"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current-a\": {\"name\": \"L1 current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"current-b\": {\"name\": \"L2 current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Imported energy\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Exported energy\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"fed-by-device-id\": {\"name\": \"Homie device-id of the upstream device feeding this lugs\", \"datatype\": \"string\"}, \"fed-by-device-type\": {\"name\": \"Homie $type of the upstream device\", \"datatype\": \"string\"}, \"fed-by-device-status\": {\"name\": \"Panel's view of comm health to the upstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this lugs\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated up/downstream\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"direction\": {\"name\": \"Lugs feed direction: upstream or downstream\", \"datatype\": \"enum\", \"format\": \"UPSTREAM,DOWNSTREAM\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834691, \"type\": \"energy.ebus.device.lugs\", \"name\": \"Downstream lugs\", \"nodes\": {\"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current-a\": {\"name\": \"L1 current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"current-b\": {\"name\": \"L2 current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Imported energy\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Exported energy\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"fed-by-device-id\": {\"name\": \"Homie device-id of the upstream device feeding this lugs\", \"datatype\": \"string\"}, \"fed-by-device-type\": {\"name\": \"Homie $type of the upstream device\", \"datatype\": \"string\"}, \"fed-by-device-status\": {\"name\": \"Panel's view of comm health to the upstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this lugs\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"direction\": {\"name\": \"Lugs feed direction: upstream or downstream\", \"datatype\": \"enum\", \"format\": \"UPSTREAM,DOWNSTREAM\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "info/direction": "DOWNSTREAM", "meter/active-power": "0", "meter/current-a": "0.0", "meter/current-b": "0.0", - "meter/exported-energy": "0", - "meter/imported-energy": "0" + "meter/exported-energy": "0.0", + "meter/imported-energy": "0.0" }, "sim-40t-001-lugs-up": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617689, \"type\": \"energy.ebus.device.lugs\", \"name\": \"Upstream lugs\", \"nodes\": {\"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current-a\": {\"name\": \"L1 current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"current-b\": {\"name\": \"L2 current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Imported energy\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Exported energy\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"fed-by-device-id\": {\"name\": \"Homie device-id of the upstream device feeding this lugs\", \"datatype\": \"string\"}, \"fed-by-device-type\": {\"name\": \"Homie $type of the upstream device\", \"datatype\": \"string\"}, \"fed-by-device-status\": {\"name\": \"Panel's view of comm health to the upstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this lugs\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"count\": {\"name\": \"Number of physical units aggregated up/downstream\", \"datatype\": \"integer\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"direction\": {\"name\": \"Lugs feed direction: upstream or downstream\", \"datatype\": \"enum\", \"format\": \"UPSTREAM,DOWNSTREAM\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834691, \"type\": \"energy.ebus.device.lugs\", \"name\": \"Upstream lugs\", \"nodes\": {\"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current-a\": {\"name\": \"L1 current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"current-b\": {\"name\": \"L2 current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Imported energy\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Exported energy\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"fed-by-device-id\": {\"name\": \"Homie device-id of the upstream device feeding this lugs\", \"datatype\": \"string\"}, \"fed-by-device-type\": {\"name\": \"Homie $type of the upstream device\", \"datatype\": \"string\"}, \"fed-by-device-status\": {\"name\": \"Panel's view of comm health to the upstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this lugs\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"direction\": {\"name\": \"Lugs feed direction: upstream or downstream\", \"datatype\": \"enum\", \"format\": \"UPSTREAM,DOWNSTREAM\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "connection/fed-by-device-id": "sim-40t-001-SIM-BESS-40T-001", "connection/fed-by-device-status": "OK", "connection/fed-by-device-type": "energy.ebus.device.bess", "info/direction": "UPSTREAM", - "meter/active-power": "18943.800133211684", - "meter/current-a": "94.98295645861873", - "meter/current-b": "96.70778693308402", + "meter/active-power": "6122.765837714007", + "meter/current-a": "26.621167830226376", + "meter/current-b": "24.401880817390346", "meter/exported-energy": "0.0", "meter/imported-energy": "0.0" }, "sim-40t-001-pv-1": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617689, \"type\": \"energy.ebus.device.pv\", \"name\": \"Solar\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"string\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}, \"nominal-power\": {\"name\": \"Nominal power\", \"datatype\": \"float\", \"unit\": \"W\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834691, \"type\": \"energy.ebus.device.pv\", \"name\": \"Solar\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"string\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}, \"nominal-power\": {\"name\": \"Nominal power\", \"datatype\": \"float\", \"unit\": \"W\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "info/firmware-version": "sim-pv/v0.1.0", "info/model": "IQ8PLUS-72-2-US", @@ -654,7 +654,7 @@ "info/vendor-name": "Enphase" }, "sim-40t-001-sim-evse-sim-40t-001": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617689, \"type\": \"energy.ebus.device.evse\", \"name\": \"SPAN Drive - Garage\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"string\"}, \"part-number\": {\"name\": \"Part number\", \"datatype\": \"string\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}}}, \"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"lock-state\": {\"name\": \"Lock state\", \"datatype\": \"enum\", \"format\": \"UNLOCKED,LOCKED\"}}}, \"status\": {\"name\": \"status\", \"type\": \"energy.ebus.capability.status\", \"properties\": {\"status\": {\"name\": \"Status\", \"datatype\": \"enum\", \"format\": \"AVAILABLE,PREPARING,CHARGING,UNAVAILABLE\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"advertised-current\": {\"name\": \"Current EVSE is advertising to the EV\", \"datatype\": \"float\", \"unit\": \"A\"}}}, \"config\": {\"name\": \"config\", \"type\": \"energy.ebus.capability.config\", \"properties\": {\"user-max-charge-current\": {\"name\": \"User-configured maximum EVSE charge current (ceiling)\", \"datatype\": \"integer\", \"settable\": true, \"unit\": \"A\"}, \"max-charge-current\": {\"name\": \"Commissioned maximum EVSE charge current (installer-configured)\", \"datatype\": \"integer\", \"unit\": \"A\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834691, \"type\": \"energy.ebus.device.evse\", \"name\": \"SPAN Drive - Garage\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"string\"}, \"part-number\": {\"name\": \"Part number\", \"datatype\": \"string\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}}}, \"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"lock-state\": {\"name\": \"Lock state\", \"datatype\": \"enum\", \"format\": \"UNLOCKED,LOCKED\"}}}, \"status\": {\"name\": \"status\", \"type\": \"energy.ebus.capability.status\", \"properties\": {\"status\": {\"name\": \"Status\", \"datatype\": \"enum\", \"format\": \"AVAILABLE,PREPARING,CHARGING,UNAVAILABLE\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"advertised-current\": {\"name\": \"Current EVSE is advertising to the EV\", \"datatype\": \"float\", \"unit\": \"A\"}}}, \"config\": {\"name\": \"config\", \"type\": \"energy.ebus.capability.config\", \"properties\": {\"user-max-charge-current\": {\"name\": \"User-configured maximum EVSE charge current (ceiling)\", \"datatype\": \"integer\", \"settable\": true, \"unit\": \"A\"}, \"max-charge-current\": {\"name\": \"Commissioned maximum EVSE charge current (installer-configured)\", \"datatype\": \"integer\", \"unit\": \"A\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "config/max-charge-current": "32", "config/user-max-charge-current": "32", @@ -668,7 +668,7 @@ "switch/lock-state": "UNLOCKED" }, "sim-40t-001-sim-evse-sim-40t-001-2": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787186617689, \"type\": \"energy.ebus.device.evse\", \"name\": \"SPAN Drive - Driveway\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"string\"}, \"part-number\": {\"name\": \"Part number\", \"datatype\": \"string\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}}}, \"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"lock-state\": {\"name\": \"Lock state\", \"datatype\": \"enum\", \"format\": \"UNLOCKED,LOCKED\"}}}, \"status\": {\"name\": \"status\", \"type\": \"energy.ebus.capability.status\", \"properties\": {\"status\": {\"name\": \"Status\", \"datatype\": \"enum\", \"format\": \"AVAILABLE,PREPARING,CHARGING,UNAVAILABLE\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"advertised-current\": {\"name\": \"Current EVSE is advertising to the EV\", \"datatype\": \"float\", \"unit\": \"A\"}}}, \"config\": {\"name\": \"config\", \"type\": \"energy.ebus.capability.config\", \"properties\": {\"user-max-charge-current\": {\"name\": \"User-configured maximum EVSE charge current (ceiling)\", \"datatype\": \"integer\", \"settable\": true, \"unit\": \"A\"}, \"max-charge-current\": {\"name\": \"Commissioned maximum EVSE charge current (installer-configured)\", \"datatype\": \"integer\", \"unit\": \"A\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787808834691, \"type\": \"energy.ebus.device.evse\", \"name\": \"SPAN Drive - Driveway\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"string\"}, \"part-number\": {\"name\": \"Part number\", \"datatype\": \"string\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}}}, \"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"lock-state\": {\"name\": \"Lock state\", \"datatype\": \"enum\", \"format\": \"UNLOCKED,LOCKED\"}}}, \"status\": {\"name\": \"status\", \"type\": \"energy.ebus.capability.status\", \"properties\": {\"status\": {\"name\": \"Status\", \"datatype\": \"enum\", \"format\": \"AVAILABLE,PREPARING,CHARGING,UNAVAILABLE\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"advertised-current\": {\"name\": \"Current EVSE is advertising to the EV\", \"datatype\": \"float\", \"unit\": \"A\"}}}, \"config\": {\"name\": \"config\", \"type\": \"energy.ebus.capability.config\", \"properties\": {\"user-max-charge-current\": {\"name\": \"User-configured maximum EVSE charge current (ceiling)\", \"datatype\": \"integer\", \"settable\": true, \"unit\": \"A\"}, \"max-charge-current\": {\"name\": \"Commissioned maximum EVSE charge current (installer-configured)\", \"datatype\": \"integer\", \"unit\": \"A\"}}}}, \"children\": [], \"root\": \"sim-40t-001\", \"parent\": \"sim-40t-001\", \"extensions\": []}", "$state": "ready", "config/max-charge-current": "32", "config/user-max-charge-current": "32", diff --git a/packages/schema-1/src/span_panel_api_schema_1/adapter.py b/packages/schema-1/src/span_panel_api_schema_1/adapter.py index cece256..ea1636e 100644 --- a/packages/schema-1/src/span_panel_api_schema_1/adapter.py +++ b/packages/schema-1/src/span_panel_api_schema_1/adapter.py @@ -431,7 +431,7 @@ def _child(self, device_id: str) -> DiscoveredDevice | None: Searched rather than indexed because the tree is repopulated on every reconnect and a cached map would answer for a panel that is being - replaced. Thirteen devices on a full enclosure makes the linear scan + replaced. Fourteen devices on a full enclosure makes the linear scan irrelevant beside a broker round trip. """ for device in self._children(): diff --git a/packages/schema-1/src/span_panel_api_schema_1/circuits.py b/packages/schema-1/src/span_panel_api_schema_1/circuits.py index 24a86eb..e2bda7a 100644 --- a/packages/schema-1/src/span_panel_api_schema_1/circuits.py +++ b/packages/schema-1/src/span_panel_api_schema_1/circuits.py @@ -163,17 +163,18 @@ def priority_is_settable(device: DiscoveredDevice) -> bool: **A lock is announced by omission, not by ``settable: false``.** Homie 5 defaults the attribute to false, and a conforming publisher emits it only where it is true — the eBus SDK's description builder does exactly that, and - the vendored capture shows the same hand on ``switch/relay``, where - ``$settable`` is present on every controllable circuit and absent on the one - commissioned otherwise. `description.declared_settable` gives the rule and - the evidence for it. + the vendored capture shows the same hand twice: ``switch/relay`` carries + ``$settable`` on every controllable circuit and not on the one commissioned + non-controllable, and ``load-shed/priority`` carries it on every circuit but + the one commissioned never-backup. `description.declared_settable` gives the + rule and the evidence for it. Reading omission the other way — as permission — is what this corrects. It offered a priority control on precisely the circuits commissioned not to accept one, and the panel refuses that write however the declaration last - read. Every circuit in the capture announces ``settable: true`` explicitly, - so no producer we have seen ever needed the permissive default that the - misreading existed to provide. + read. No producer we have seen has ever published ``settable: false`` for the + permissive default to rescue: the capture's never-backup circuit announces + its lock by saying nothing, which is what the misreading read as a yes. Absence of the *property* answers the same way and for a plainer reason: a device carrying no ``load-shed`` node, or one with no ``priority`` on it, has diff --git a/packages/schema-1/src/span_panel_api_schema_1/spec_lock.json b/packages/schema-1/src/span_panel_api_schema_1/spec_lock.json index a46d775..2271280 100644 --- a/packages/schema-1/src/span_panel_api_schema_1/spec_lock.json +++ b/packages/schema-1/src/span_panel_api_schema_1/spec_lock.json @@ -15,7 +15,7 @@ "repo": "https://github.com/SpanPanel/panelbench", "ref": "main", "role": "publisher", - "commit": "e7579104a04dfb381ba89bfa1cbbe76e2c2d2058", + "commit": "8d1895025a6e239ab0778dede3f20c7eb5e8853d", "synced_commit": "7ee7ca93b19c3de3d61be44f01887ba9557dd803", "firmware_range": "r202633+", "fixtures": { @@ -27,10 +27,10 @@ "repo": "https://github.com/electrification-bus/distribution-enclosure-simulator", "ref": "main", "role": "publisher", - "commit": "156b6ef14fbd00ca9e79ca2fc4bcd2ca4a6348f3", - "tag": "v0.7.0", + "commit": "171bb94f0960ccd2f62282c83ec203017bd6aa7f", + "tag": "v0.8.0", "distribution": "ebus-panel-sim", - "version": "0.7.0", + "version": "0.8.0", "synced_commit": "7ee7ca93b19c3de3d61be44f01887ba9557dd803", "capture_script": "scripts/capture_parent_child_reference.py", "manifest": "scripts/reference_panel.yaml", diff --git a/pyproject.toml b/pyproject.toml index cecf23a..23a4e8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -153,8 +153,20 @@ include = [ [tool.ruff] line-length = 125 +# `scripts/` is excluded file by file rather than as a directory, so that a new +# script is linted by default and only the ones written before that was true are +# grandfathered out. `peer_drift.py` and `_lock.py` are the ones in scope: they +# run on every commit and in CI, and the rest are hand-run tools. exclude = [ - "scripts/", + "scripts/capture_flat_reference.py", + "scripts/capture_live_flat.py", + "scripts/capture_parent_child_reference.py", + "scripts/coverage.py", + "scripts/format_markdown.py", + "scripts/test_live_auth.py", + "scripts/validate_lug_derivation/", + "scripts/verify_adapterless_install.py", + "scripts/verify_reconnect.py", ".*_cache/", "dist/", "venv/", @@ -164,6 +176,10 @@ exclude = [ [tool.ruff.lint.per-file-ignores] # Exclude tests from ALL linting checks (formatting still applies) "tests/**/*.py" = ["ALL"] +# Its whole output is a markdown summary on stdout, which the workflow appends to +# the job summary and the hook shows on failure. T20 is right for the library and +# wrong for a reporter. +"scripts/peer_drift.py" = ["T201"] [tool.ruff.format] quote-style = "double" @@ -217,8 +233,19 @@ warn_unused_configs = true disallow_untyped_defs = true explicit_package_bases = true mypy_path = "src" +# Excluded by name for the reason `[tool.ruff]` gives above: the two scripts the +# drift check is made of are checked like the library, and the hand-run tools +# written before that was expected are not. exclude = [ - "scripts/", + "scripts/capture_flat_reference.py", + "scripts/capture_live_flat.py", + "scripts/capture_parent_child_reference.py", + "scripts/coverage.py", + "scripts/format_markdown.py", + "scripts/test_live_auth.py", + "scripts/validate_lug_derivation/", + "scripts/verify_adapterless_install.py", + "scripts/verify_reconnect.py", "tests/", "docs/", ".*_cache/", diff --git a/scripts/_lock.py b/scripts/_lock.py new file mode 100644 index 0000000..6f86c47 --- /dev/null +++ b/scripts/_lock.py @@ -0,0 +1,72 @@ +"""`spec_lock.json`, and the shape checks that reading it honestly needs. + +Two scripts ask this file for a pin, and they ask different questions of it. +`capture_parent_child_reference.py` asks what release the reference tree is a +capture of, so it can refuse to write one taken from any other. `peer_drift.py` +asks what every producer was pinned at, so it can ask that producer whether it +has moved. Neither restates a pin — and the path to the lockfile is not a third +place to get it wrong either, so it is named here once and imported. + +`json.load` returns `object`, and a reader under strict typing is not allowed to +pretend otherwise. The helpers below are how that stays true without a `cast`: +each one narrows exactly one shape and says what it found when the shape is +wrong. A malformed lockfile is fatal in both callers — it is a file this +repository owns, and a broken one makes every pin in it a guess — so they exit +with a sentence naming the key rather than raising for a caller that has nothing +useful to do about it. +""" + +from __future__ import annotations + +from collections.abc import Mapping +import json +import pathlib + +REPO = pathlib.Path(__file__).resolve().parent.parent +LOCK = REPO / "packages" / "schema-1" / "src" / "span_panel_api_schema_1" / "spec_lock.json" + + +def mapping(value: object, where: str) -> dict[str, object]: + if not isinstance(value, Mapping): + raise SystemExit(f"{where} must be a mapping, got {type(value).__name__}") + return {str(key): item for key, item in value.items()} + + +def required(source: Mapping[str, object], key: str, where: str) -> object: + """One key that has to be there, reported the way everything else here is. + + Indexing straight into the mapping says the same thing as a bare `KeyError` + traceback, which is the one failure mode that makes a reader work out what + the caller wanted. Every other malformed input exits with a sentence naming + it. + """ + if key not in source: + raise SystemExit(f"{where} has no {key!r} entry") + return source[key] + + +def string(source: Mapping[str, object], key: str, where: str) -> str: + """A required key whose value the lockfile promises is a string.""" + value = required(source, key, where) + if not isinstance(value, str): + raise SystemExit(f"{where}.{key} must be a string, got {type(value).__name__}") + return value + + +def load() -> dict[str, object]: + with LOCK.open(encoding="utf-8") as handle: + document: object = json.load(handle) + return mapping(document, LOCK.name) + + +def peer(name: str, lock: Mapping[str, object] | None = None) -> dict[str, object]: + """One producer's block, by name. + + Keyed rather than positional, for the reason `spec_lock.json` says at + `peers`: every reader wants a specific producer, never "the first one". + Callers that have already read the lockfile pass it in rather than reading + it again; the ones that want a single pin and nothing else do not have to. + """ + document = load() if lock is None else lock + peers = mapping(required(document, "peers", LOCK.name), "peers") + return mapping(required(peers, name, "peers"), f"peers.{name}") diff --git a/scripts/capture_parent_child_reference.py b/scripts/capture_parent_child_reference.py index 8d6d7a5..f060304 100644 --- a/scripts/capture_parent_child_reference.py +++ b/scripts/capture_parent_child_reference.py @@ -57,9 +57,9 @@ `spec_lock.json` as `peers.ebus-panel-sim.manifest`. **Shape-stable, not byte-stable.** Every `$description` carries a `version` -minted from the wall clock when its device is built, so all thirteen move on +minted from the wall clock when its device is built, so all fourteen move on every run. Nothing here reads it — it is Homie's own change counter — but it -does mean a recapture always shows thirteen diffs, and that a diff confined to +does mean a recapture always shows fourteen diffs, and that a diff confined to those lines says the producer did not move. """ @@ -73,6 +73,13 @@ import sys _REPO = pathlib.Path(__file__).resolve().parent.parent +# Run as a file — which is the only way this is run, and from the emitter's +# working directory at that — the interpreter puts *this* directory on the path +# and not the repository above it, so `scripts._lock` is not importable until we +# say where the repository is. Appended rather than prepended: this process is +# somebody else's, and the emitter's own imports get to resolve first. +sys.path.append(str(_REPO)) + SIM = pathlib.Path(os.environ.get("PANEL_SIM_DIR", _REPO.parent / "distribution-enclosure-simulator")) if not (SIM / "src").is_dir(): raise SystemExit(f"no emitter checkout at {SIM}; set PANEL_SIM_DIR") @@ -80,6 +87,8 @@ import yaml # noqa: E402 +from scripts._lock import mapping as _mapping, peer, string # noqa: E402 + from ebus_panel_sim import ( # noqa: E402 BESSConfig, ChargeMode, @@ -92,7 +101,6 @@ __version__ as PRODUCER_VERSION, ) -LOCK = _REPO / "packages" / "schema-1" / "src" / "span_panel_api_schema_1" / "spec_lock.json" MANIFEST = _REPO / "scripts" / "reference_panel.yaml" PEER = "ebus-panel-sim" @@ -105,15 +113,14 @@ # --------------------------------------------------------------------------- # Reading YAML without giving up on types +# +# The mapping check itself is `scripts/_lock.py`'s, because reading the lockfile +# needs the same one and two copies of it would be two things to keep honest. +# What is below is the rest of the manifest's vocabulary, which only this script +# reads. # --------------------------------------------------------------------------- -def _mapping(value: object, where: str) -> dict[str, object]: - if not isinstance(value, Mapping): - raise SystemExit(f"{where} must be a mapping, got {type(value).__name__}") - return {str(key): item for key, item in value.items()} - - def _optional_mapping(value: object) -> dict[str, object]: return _mapping(value, "") if isinstance(value, Mapping) else {} @@ -128,19 +135,6 @@ def _mappings(value: object, where: str) -> list[dict[str, object]]: return [_mapping(item, f"{where}[{index}]") for index, item in enumerate(_sequence(value, where))] -def _required(source: Mapping[str, object], key: str, where: str) -> object: - """One key that has to be there, reported the way everything else here is. - - Indexing straight into the mapping says the same thing as a bare `KeyError` - traceback, which is the one failure mode in this file that makes a reader - work out what the script wanted. Every other malformed input exits with a - sentence naming it. - """ - if key not in source: - raise SystemExit(f"{where} has no {key!r} entry") - return source[key] - - def _text(source: Mapping[str, object], key: str, default: str) -> str: value = source.get(key) return default if value is None else str(value) @@ -189,11 +183,7 @@ def pinned_release() -> str: until somebody recaptured and updated only one -- which is the failure this whole change exists to make impossible. """ - with LOCK.open(encoding="utf-8") as handle: - lock: object = json.load(handle) - document = _mapping(lock, "spec_lock.json") - peers = _mapping(_required(document, "peers", "spec_lock.json"), "peers") - return _text(_mapping(_required(peers, PEER, "peers"), f"peers.{PEER}"), "version", "") + return string(peer(PEER), "version", f"peers.{PEER}") # --------------------------------------------------------------------------- @@ -245,6 +235,12 @@ def circuit_instance(profile: Profile, circuit: Mapping[str, object], pcs_priori "relay-behavior": behavior, "placement": _text(circuit, "placement", "downstream-of-lugs"), "always-on": _bool_str(behavior == "always-on"), + # The other commissioning lock (ebus-panel-sim 0.8.0): read by the + # emitter's `manifest_physics.never_backup`, which pins the priority + # and drops `$settable` from `load-shed/priority`. + "never-backup": _bool_str( + _flag(template, "never_backup") if "never_backup" in template else _flag(circuit, "never_backup") + ), "pcs-priority": str(pcs_priority), }, ) diff --git a/scripts/peer_drift.py b/scripts/peer_drift.py new file mode 100644 index 0000000..9289de0 --- /dev/null +++ b/scripts/peer_drift.py @@ -0,0 +1,856 @@ +"""Has a producer moved past the pin? + +`spec_lock.json` records three producers this repository copies from or is +generated by: the eBus **specification**, whose capability catalogs are vendored +under `packages/schema-1/spec/`; **panelbench**, whose captures are byte-copied +into that same directory; and the **eBus emitter**, a released distribution that +produced the reference tree twenty test modules replay. Any of them can move +without a file here being touched, and when one does the pin stops describing a +producer and starts describing a moment. + +The provenance tests answer the other half of the question — do our bytes still +match the commit we claim they came from — and they answer it against sibling +checkouts, which says nothing about what a producer has *published* since. This +asks the producers themselves, over the network and with no checkout at all: +PyPI for the emitter's latest release, GitHub's compare API for what panelbench +changed, `git ls-remote` for where a branch is. + +It is a script rather than a workflow step because it runs in two places. +`.github/workflows/peer-drift.yml` runs it daily under `--strict`, which is the +backstop. `.pre-commit-config.yaml` runs it on every commit, which is where it is +meant to catch things: the drift this exists to prevent was a producer release +that nobody here noticed until a scheduled job went red, and a check only CI runs +is a check that finds out late. + +**A verdict, or context.** Two comparisons are verdicts, and both are verdicts +about bytes in this repository having gone stale: + +- the emitter's *release*, because the reference tree is a capture of one and + `capture_parent_child_reference.py` refuses to write a capture from any other + version. Commits on the emitter's branch are unreleased work, reported because + they are what a release will be made of, never a call to action. +- a panelbench commit that touches a file we vendor. Commits that touch nothing + we copy are reported and stay green: a README commit there is not a defect + here. + +The specification is context only and never a verdict. It says what a device +class *may* publish, the vendored catalogs are byte-compared against +`synced_commit` by `ci.yml`, and a specification that has moved is something to +read rather than something to fix. + +**Not having asked is not the same fact as there being nothing new.** A producer +that could not be reached is reported as UNKNOWN, loudly, and exits 0 — a laptop +on a plane still has to be able to commit. `--strict` turns unknown into a +failure, because in CI there is no such excuse. + +Nothing here restates a pin, a path or a repository. Every one of them is read +from `spec_lock.json`, which is their single home; a constant in this file would +be a second one, agreeing right up until the day somebody moved only one. +""" + +from __future__ import annotations + +import argparse +from collections.abc import Callable, Mapping, Sequence +from dataclasses import dataclass +import json +import os +import pathlib +import subprocess +import sys +from typing import Literal, Protocol, TextIO +import urllib.error +import urllib.parse +import urllib.request + +_REPO = pathlib.Path(__file__).resolve().parent.parent +# Run as a file (`python scripts/peer_drift.py`) the interpreter puts *this* +# directory on the path, not the repository, so the sibling module holding the +# lockfile reader is not importable as `scripts._lock` until we say where the +# repository is. Imported as `scripts.peer_drift`, which is how the tests read +# it, the entry is already there and this is a no-op. +if str(_REPO) not in sys.path: + sys.path.append(str(_REPO)) + +from scripts._lock import LOCK, REPO, load, mapping, peer as pinned_peer, required, string # noqa: E402 + +PANELBENCH = "panelbench" +"""The SPAN-side publisher this parser is developed against, and the one whose +captures are byte-copied into this repository.""" + +PANEL_SIM = "ebus-panel-sim" +"""The eBus specification's own executable publisher, and the producer of the +reference tree. A released distribution, which is why its release is the +verdict.""" + +SPECIFICATION = "specification" +"""Not a peer in `spec_lock.json` — it is the document both peers implement, and +it is pinned at the top level as `spec_repo` / `synced_commit`.""" + +TIMEOUT = 10.0 +"""Seconds per network call. Short deliberately: this runs in a pre-commit hook, +and a producer that has not answered in ten seconds is a producer we will report +as unknown rather than one worth waiting for.""" + +COMPARE_FILE_LIMIT = 300 +"""GitHub's compare API lists at most this many changed files. A comparison that +hits the limit cannot be read as "nothing we vendor changed", because the file +that did may be the one the API declined to name — so it is reported as unknown, +not as current.""" + +EBUS_SPEC_FILE = ".ebus-spec.json" +"""The producer's own record of which specification commit it implements. +Not vendored, but read by `test_the_peer_record_matches_the_simulator_lockfile`, +so a change to it is a change to something this repository depends on.""" + +VENDORED = { + "tree": "packages/schema-1/spec/fixtures/simulator_tree.json", + "wire": "packages/schema-1/spec/fixtures/simulator_wire.json", +} +"""Where each panelbench capture is copied *to*. The paths it is copied *from* +are the pin's own (`peers.panelbench.fixtures`) and are read from the lockfile; +these are paths inside this repository, which that peer's block deliberately does +not record — see `spec_lock.json`'s note on why `fixtures` and `produces` are two +keys rather than one.""" + +GITHUB_API = "https://api.github.com" +PYPI_JSON = "https://pypi.org/pypi" + +Verdict = Literal["current", "drift", "unknown"] + + +class Unreachable(Exception): + """A producer could not be asked. + + Deliberately not an answer. Every caller turns this into `unknown` and says + so in the summary, because the alternative — treating silence as agreement — + is how a check reports green on the day it had nothing to report at all. + """ + + +class HttpGet(Protocol): + """Fetch a URL, or raise `Unreachable`. Injected so tests never leave the machine.""" + + def __call__(self, url: str) -> str: ... + + +class LsRemote(Protocol): + """Resolve a ref in a remote repository to a commit, or raise `Unreachable`.""" + + def __call__(self, repo: str, ref: str) -> str: ... + + +@dataclass(frozen=True) +class Producer: + """One producer, asked and answered. + + `pinned` and `observed` are the same fact as `detail`, in the currency the + comparison was made in — a release for the emitter, a commit for the other + two. The prose is what a person reads in a job summary; these are what a test + can assert on without matching sentences. + """ + + name: str + pinned: str + observed: str + verdict: Verdict + detail: str + advisory: bool = False + """Reported, never counted. Set for the specification, whose movement is + context for reading a disagreement rather than a reason to change anything + here, and so must not fail a commit or a job.""" + + def section(self) -> str: + return f"## {self.name}\n\n{self.detail.rstrip()}\n" + + +@dataclass(frozen=True) +class Comparison: + """GitHub's answer to `compare/...`, narrowed to what we read. + + `behind_by` rather than the API's own `status` word: what we need to know is + whether the pin is an ancestor of the branch, and a non-zero count of commits + the base has and the head does not is exactly that question answered. + """ + + ahead_by: int + behind_by: int + files: tuple[str, ...] + + @property + def capped(self) -> bool: + return len(self.files) >= COMPARE_FILE_LIMIT + + +@dataclass(frozen=True) +class Index: + """What PyPI says about a distribution, narrowed to the two things we ask. + + `latest` alone was not enough. It answers "is there something newer?" only if + the pin is still a release PyPI serves, and it is not the same question as + "is what we pinned still installable" — a yanked or withdrawn release leaves + `latest` looking perfectly ordinary while the reference tree describes + something nobody can get. + """ + + latest: str + pinned_files: int | None + """How many files the index lists for the pinned release; `None` when it does + not list that release at all.""" + pinned_yanked: bool + """Every file of the pinned release withdrawn. PyPI yanks per file, so this + is the whole release only when they all carry the flag.""" + + +# --------------------------------------------------------------------------- +# Asking, over the network +# --------------------------------------------------------------------------- + + +def _headers(url: str) -> dict[str, str]: + """Authenticate to the GitHub API when a token is around, and nowhere else. + + CI has one and wants the higher rate limit; a developer machine usually does + not, and 60 unauthenticated requests an hour is ample for a hook that makes + at most one such request per commit. The host check is not decoration: a + credential sent to a service that did not ask for it is a credential + disclosed, and PyPI has no use for a GitHub token. + """ + if not url.startswith(GITHUB_API): + return {} + headers = {"Accept": "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28"} + token = os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN") or "" + if token: + headers["Authorization"] = f"Bearer {token}" + return headers + + +def http_get(url: str) -> str: + request = urllib.request.Request(url, headers=_headers(url)) + try: + with urllib.request.urlopen(request, timeout=TIMEOUT) as response: + body: bytes = response.read() + except urllib.error.HTTPError as error: + if error.code in (403, 429) and error.headers.get("x-ratelimit-remaining") == "0": + raise Unreachable( + "GitHub's rate limit is spent; set GH_TOKEN to raise it from the unauthenticated 60/hr" + ) from error + raise Unreachable(f"HTTP {error.code} {error.reason}") from error + except (urllib.error.URLError, TimeoutError, OSError) as error: + raise Unreachable(str(error)) from error + return body.decode("utf-8", errors="replace") + + +def ls_remote(repo: str, ref: str) -> str: + """The commit a remote ref names, without cloning anything. + + `GIT_TERMINAL_PROMPT=0` because this runs in a pre-commit hook: a repository + that has gone private must report as unreachable rather than stop a commit at + a password prompt nobody is watching for. + """ + environment = dict(os.environ, GIT_TERMINAL_PROMPT="0") + try: + completed = subprocess.run( + ["git", "ls-remote", repo, ref], + capture_output=True, + text=True, + timeout=TIMEOUT, + check=False, + env=environment, + ) + except subprocess.TimeoutExpired as error: + raise Unreachable(f"git ls-remote did not answer within {TIMEOUT:.0f}s") from error + except OSError as error: + raise Unreachable(str(error)) from error + if completed.returncode != 0: + detail = completed.stderr.strip().splitlines() + raise Unreachable(detail[-1] if detail else f"git ls-remote exited {completed.returncode}") + fields = completed.stdout.split() + if not fields: + raise Unreachable(f"{repo} has no {ref}") + return fields[0] + + +# --------------------------------------------------------------------------- +# Reading what came back, which is somebody else's JSON +# --------------------------------------------------------------------------- + + +def _answer(body: str, who: str) -> dict[str, object]: + try: + loaded: object = json.loads(body) + except json.JSONDecodeError as error: + raise Unreachable(f"{who} did not answer with JSON ({error})") from error + if not isinstance(loaded, Mapping): + raise Unreachable(f"{who} answered with {type(loaded).__name__}, not an object") + return {str(key): value for key, value in loaded.items()} + + +def _member(source: Mapping[str, object], key: str, who: str) -> dict[str, object]: + value = source.get(key) + if not isinstance(value, Mapping): + raise Unreachable(f"{who} answered with no {key!r} object") + return {str(inner): item for inner, item in value.items()} + + +def _field(source: Mapping[str, object], key: str, who: str) -> str: + value = source.get(key) + if not isinstance(value, str): + raise Unreachable(f"{who} answered with no {key!r} string") + return value + + +def _count(source: Mapping[str, object], key: str, who: str) -> int: + value = source.get(key) + if isinstance(value, bool) or not isinstance(value, int): + raise Unreachable(f"{who} answered with no {key!r} count") + return value + + +def _objects(listed: object, key: str, who: str) -> list[dict[str, object]]: + """A list of JSON objects, which is the shape both producers answer files in.""" + if not isinstance(listed, Sequence) or isinstance(listed, str | bytes): + raise Unreachable(f"{who} answered with a {key!r} that is not a list") + entries: list[dict[str, object]] = [] + for entry in listed: + if not isinstance(entry, Mapping): + raise Unreachable(f"{who} answered with a {key!r} entry that is not an object") + entries.append({str(inner): value for inner, value in entry.items()}) + return entries + + +def _filenames(source: Mapping[str, object], who: str) -> tuple[str, ...]: + """The changed files, which the API omits entirely when there are none.""" + return tuple(_field(entry, "filename", who) for entry in _objects(source.get("files", []), "files", who)) + + +# --------------------------------------------------------------------------- +# Comparing, which is pure +# --------------------------------------------------------------------------- + + +def slug(url: str) -> str: + """owner/name, which is what the GitHub API addresses a repository by.""" + return url.removeprefix("https://github.com/").removesuffix(".git") + + +def short(commit: str) -> str: + return commit[:12] + + +def fixtures(peer: Mapping[str, object]) -> dict[str, str]: + """The captures byte-copied out of panelbench, by kind, as the pin names them. + + Paths *inside panelbench*: `spec_lock.json` records this peer's artifacts + under `fixtures` for exactly that reason, and the other peer's under + `produces`, whose paths are inside this repository. + """ + listed = mapping(required(peer, "fixtures", f"peers.{PANELBENCH}"), f"peers.{PANELBENCH}.fixtures") + return {str(kind): str(path) for kind, path in listed.items()} + + +def watched(peer: Mapping[str, object]) -> tuple[str, ...]: + """The files in panelbench whose movement is our problem. + + The captures we byte-copy, named by the pin, plus the producer's own record + of the specification commit it implements. Everything else in that repository + may change freely. + """ + return (*sorted(fixtures(peer).values()), EBUS_SPEC_FILE) + + +def touched(changed: Sequence[str], watch: Sequence[str]) -> tuple[str, ...]: + """Those of `watch` the producer changed, in the order `watch` names them.""" + changes = set(changed) + return tuple(path for path in watch if path in changes) + + +def _release_numbers(version: str) -> tuple[int, ...] | None: + """The dotted integers of a version, or `None` when it is not only those. + + No `packaging` here, and not from thrift: the emitter job runs this under the + runner's system `python3`, before uv has installed anything, so the standard + library is the whole toolbox. Dotted integers are the part of a version this + can order correctly; anything with a pre-release, post or local suffix is + handed back as unorderable rather than guessed at. + """ + parts = version.split(".") + if not all(part.isdigit() for part in parts): + return None + return tuple(int(part) for part in parts) + + +def newer_release(candidate: str, than: str) -> bool | None: + """Whether `candidate` is a later release than `than`; `None` if unanswerable. + + Three-valued on purpose. "I cannot order these two" is a different fact from + "this one is not newer", and only one of them is a reason to leave the pin + alone quietly. + """ + left = _release_numbers(candidate) + right = _release_numbers(than) + if left is None or right is None: + return None + width = max(len(left), len(right)) + return _padded(left, width) > _padded(right, width) + + +def _padded(numbers: tuple[int, ...], width: int) -> tuple[int, ...]: + """A two-segment version and a three-segment one can name the same release, + so both are compared at the wider of the two.""" + return numbers + (0,) * (width - len(numbers)) + + +def pin_problem(index: Index, pinned: str) -> tuple[str, str] | None: + """Why the pinned release cannot be compared against, headline and reason. + + Checked before `latest` is looked at, because these hold whatever `latest` + says: a pin PyPI does not serve is not a pin that "has not drifted", and + reporting it as current would be the check quietly agreeing with a release + that is no longer there. + """ + if index.pinned_files is None: + return ( + f"PyPI lists no release `{pinned}` at all", + "The pin names a version this project has not published, or one that has since " + "been removed from the index. Nothing can be compared against it, and the " + "reference tree claims to be a capture of it.", + ) + if index.pinned_files == 0: + return ( + f"PyPI lists release `{pinned}` with no files", + "The release exists in the index with nothing left to install under it, so the " + "version the reference tree names cannot be obtained or re-captured from.", + ) + if index.pinned_yanked: + return ( + f"the release we pin (`{pinned}`) has been yanked", + "The producer withdrew it. A capture taken from a yanked release describes a " + "producer nobody should install, whether or not anything newer exists — read the " + "emitter's reason for the yank before deciding which release to move the pin to.", + ) + return None + + +def released_tag(peer: Mapping[str, object], version: str) -> str: + """`version` spelled the way that producer tags a release. + + Derived from the pinned pair rather than assumed: the lockfile records both + `tag` and `version`, so whatever prefix the producer puts on one is readable + from the other. + """ + tag = string(peer, "tag", f"peers.{PANEL_SIM}") + pinned = string(peer, "version", f"peers.{PANEL_SIM}") + return f"{tag.removesuffix(pinned)}{version}" if tag.endswith(pinned) else version + + +def unasked(lock: Mapping[str, object]) -> tuple[str, ...]: + """Producers the lockfile pins that this script has no way to ask about. + + A new peer added to `spec_lock.json` and not here would be silently exempt + from the only check that notices it moving, which is the failure this whole + script exists to prevent, one level up. + """ + peers = mapping(required(lock, "peers", LOCK.name), "peers") + return tuple(sorted(set(peers) - set(BUILDERS))) + + +def unvendored(lock: Mapping[str, object]) -> tuple[str, ...]: + """Capture kinds the pin names that this script knows no destination for. + + `VENDORED` is the one thing here that is not read from the lockfile, because + the destinations are paths in this repository and that peer's block records + only its sources. A kind in one and not the other would still be *detected* + as drift — `watched` reads the pin — but the summary would go quiet about + where to copy it, which is the half a reader actually acts on. + """ + return tuple(sorted(set(fixtures(pinned_peer(PANELBENCH, lock))) - set(VENDORED))) + + +def _unknown(who: str, error: Exception) -> str: + return ( + f"**UNKNOWN — could not ask {who}:** `{error}`\n\n" + "Not having asked is not the same fact as there being nothing new, so this is " + "reported rather than passed over. It does not fail a commit; `--strict`, which " + "CI passes, makes it fail there, where being unable to reach a producer is a " + "problem with the run rather than with the network in a coffee shop." + ) + + +def _inconclusive(headline: str, why: str) -> str: + """Asked, answered, and the answer is not a verdict. + + A different shape of unknown from `_unknown`: the producer replied, and what + it said cannot be read as either "the capture is current" or "the capture is + stale". Reporting it as drift would attach instructions that make things + worse — re-capturing from a release that is older, withdrawn or absent. + """ + return ( + f"**UNKNOWN — {headline}.**\n\n{why}\n\n" + "Neither current nor drift, so it is reported rather than acted on. `--strict`, " + "which CI passes, fails on it; a commit is not stopped by it." + ) + + +# --------------------------------------------------------------------------- +# The producers, one shape each +# --------------------------------------------------------------------------- + + +def emitter(lock: Mapping[str, object], get: HttpGet, ls: LsRemote) -> Producer: + """The released distribution the reference tree was captured from. + + The release is the verdict and the branch is context, because the capture + script gates on a release: it refuses to write a tree taken from any version + other than the pinned one, so unreleased commits cannot produce a capture we + would accept even if we wanted one. + """ + peer = pinned_peer(PANEL_SIM, lock) + distribution = string(peer, "distribution", f"peers.{PANEL_SIM}") + pinned = string(peer, "version", f"peers.{PANEL_SIM}") + context = _emitter_commits(peer, ls) + who = f"PyPI for {distribution}" + + try: + index = _index(get(f"{PYPI_JSON}/{urllib.parse.quote(distribution)}/json"), pinned, who) + except Unreachable as error: + return Producer(PANEL_SIM, pinned, "", "unknown", f"{_unknown(who, error)}\n\n{context}") + + verdict, detail = _release_reading(peer, pinned, index) + return Producer(PANEL_SIM, pinned, index.latest, verdict, f"{detail}\n\n{context}") + + +def _index(body: str, pinned: str, who: str) -> Index: + """PyPI's answer, read once for both questions we ask of it.""" + answer = _answer(body, who) + latest = _field(_member(answer, "info", who), "version", who) + listed = _member(answer, "releases", who).get(pinned) + if listed is None: + return Index(latest, None, pinned_yanked=False) + files = _objects(listed, f"releases.{pinned}", who) + return Index(latest, len(files), pinned_yanked=bool(files) and all(entry.get("yanked") is True for entry in files)) + + +def _release_reading(peer: Mapping[str, object], pinned: str, index: Index) -> tuple[Verdict, str]: + """The verdict on the pinned release, and what to say about it. + + Only one branch here is drift, and it is the narrow one: a release that + exists, is not withdrawn, and is genuinely later than ours. Everything else + the index can say — a pin it does not serve, a pin it has yanked, a `latest` + that is older or that carries a suffix this cannot order — is a disagreement + we have no safe instruction for, so it is reported and not acted on. + """ + problem = pin_problem(index, pinned) + if problem is not None: + return "unknown", _inconclusive(*problem) + if index.latest == pinned: + return "current", f"Latest release is `{index.latest}`, which is what we pin. The reference tree is current." + + newer = newer_release(index.latest, pinned) + if newer is None: + return "unknown", _inconclusive( + f"PyPI's latest (`{index.latest}`) cannot be ordered against the pin (`{pinned}`)", + "One of them carries a pre-release, post-release or local suffix. Ordering dotted " + "integers is what the standard library gives us, and guessing at the rest would make " + "a verdict out of a guess — read the emitter's releases and move the pin by hand.", + ) + if not newer: + return "unknown", _inconclusive( + f"PyPI's latest (`{index.latest}`) is not newer than the pin (`{pinned}`)", + "The index serves an older release than the one we pin — a newer release withdrawn " + "after we pinned it, or an index that has not caught up. Re-capturing would take the " + "reference tree backwards, which is why this is not reported as drift.", + ) + return "drift", _emitter_drift(peer, pinned, index.latest) + + +def _emitter_drift(peer: Mapping[str, object], pinned: str, latest: str) -> str: + """What to do about a reference tree captured from a superseded release.""" + capture = string(peer, "capture_script", f"peers.{PANEL_SIM}") + produces = mapping(required(peer, "produces", f"peers.{PANEL_SIM}"), f"peers.{PANEL_SIM}.produces") + tree = str(required(produces, "tree", f"peers.{PANEL_SIM}.produces")) + return ( + f"Latest release is `{latest}`, we pin `{pinned}`.\n\n" + "The reference tree was captured from the pinned release, so it now describes a\n" + "producer that has been superseded. To follow:\n\n" + "```bash\n" + f"# in a checkout of the emitter at {released_tag(peer, latest)}, from its own environment\n" + f"uv run python ../{REPO.name}/{capture} \\\n" + f" ../{REPO.name}/{tree}\n" + "```\n\n" + f"The script refuses until `peers.{PANEL_SIM}.version` and `.commit` in\n" + f"`{LOCK.name}` name the release you captured from, which is what keeps the\n" + "bytes and the claim about them from drifting apart. Read the emitter's CHANGELOG\n" + "for the wire changes before accepting the new capture: a diff confined to each\n" + "`$description`'s `version` means nothing moved." + ) + + +def _emitter_commits(peer: Mapping[str, object], ls: LsRemote) -> str: + """Where the emitter's branch is, which is context and says so.""" + repo = string(peer, "repo", f"peers.{PANEL_SIM}") + ref = string(peer, "ref", f"peers.{PANEL_SIM}") + commit = string(peer, "commit", f"peers.{PANEL_SIM}") + heading = f"### {slug(repo)} commits" + try: + head = ls(repo, f"refs/heads/{ref}") + except Unreachable as error: + return f"{heading}\n\nCould not ask where `{ref}` is (`{error}`)." + if head == commit: + return f"{heading}\n\n`{ref}` is at `{short(commit)}`, the commit we pin." + return ( + f"{heading}\n\n" + f"`{ref}` is at `{short(head)}`, we pin `{short(commit)}` — the branch head has\n" + "moved past the pin. Unreleased work, so not itself a reason to recapture; the\n" + "release comparison above is. How many commits, and which, needs a clone — the\n" + "scheduled `peer-drift.yml` run has one and lists them." + ) + + +def panelbench(lock: Mapping[str, object], get: HttpGet, ls: LsRemote) -> Producer: + """The repository whose captures are byte-copied here. + + Two questions, cheapest first. `git ls-remote` says whether the branch has + moved at all, and on the ordinary day — the pin at the branch head — that is + the whole answer and no API request is made. Only a branch that has moved is + worth spending a compare on, and only then does *what* it changed matter. + """ + peer = pinned_peer(PANELBENCH, lock) + repo = string(peer, "repo", f"peers.{PANELBENCH}") + ref = string(peer, "ref", f"peers.{PANELBENCH}") + commit = string(peer, "commit", f"peers.{PANELBENCH}") + name = slug(repo) + who = f"GitHub what {name} changed" + + try: + head = ls(repo, f"refs/heads/{ref}") + except Unreachable as error: + return Producer(PANELBENCH, commit, "", "unknown", _unknown(f"{name} where `{ref}` is", error)) + if head == commit: + return Producer( + PANELBENCH, + commit, + head, + "current", + f"`{ref}` is at `{short(commit)}`, the commit we pin. Nothing to re-vendor.", + ) + + try: + # Against the commit `ls-remote` just resolved, not against the branch + # name: the branch could move between the two calls, and a summary that + # names one commit while comparing another is two facts pretending to be + # one. + comparison = compare(repo, commit, head, get, who) + except Unreachable as error: + return Producer(PANELBENCH, commit, head, "unknown", _unknown(who, error)) + + moved = f"`{ref}` is at `{short(head)}`, we pin `{short(commit)}`" + verdict, detail = _panelbench_reading(comparison, moved, watched(peer), fixtures(peer)) + return Producer(PANELBENCH, commit, head, verdict, detail) + + +def _panelbench_reading( + comparison: Comparison, moved: str, watch: Sequence[str], sources: Mapping[str, str] +) -> tuple[Verdict, str]: + """What the comparison means, once it has been obtained.""" + if comparison.behind_by: + detail = ( + f"**UNKNOWN — {moved}, and the pin is not an ancestor of it.**\n\n" + "The pin names a commit this branch does not contain — a branch that was\n" + f"rebased, squash-merged or deleted. `peers.{PANELBENCH}.ref` in `{LOCK.name}`\n" + "needs to name a ref the pinned commit is actually on, and until it does\n" + "nothing here can say whether what we vendor is current." + ) + return "unknown", detail + + changed = touched(comparison.files, watch) + if not changed and comparison.capped: + # Asked in this order because a file we vendor appearing in a truncated + # list is still a file we vendor having changed. Only the *absence* of one + # is unsafe to conclude from a list the API cut short. + detail = ( + f"**UNKNOWN — {moved}, {comparison.ahead_by} commits ahead, and the comparison\n" + f"named {COMPARE_FILE_LIMIT} changed files, which is all the API will list.**\n\n" + 'A truncated list cannot be read as "nothing we vendor changed", because the\n' + "file that did may be one it declined to name. Compare against a checkout — the\n" + "byte comparison in `tests/test_schema_one_conformance.py` is the answer that\n" + "does not truncate." + ) + return "unknown", detail + + if not changed: + detail = ( + f"{moved} — {comparison.ahead_by} commits ahead, nothing we copy changed.\n\n" + "Green deliberately: this repository vendors two captures and reads one\n" + "lockfile out of that branch, and a commit touching none of them is the\n" + "producer's business rather than ours." + ) + return "current", detail + + return "drift", _panelbench_drift(moved, comparison, changed, sources) + + +def _panelbench_drift(moved: str, comparison: Comparison, changed: Sequence[str], sources: Mapping[str, str]) -> str: + """What to do about a producer that changed something we keep a copy of.""" + lines = [ + f"**{moved} — {comparison.ahead_by} commits ahead, and it changed what we vendor:**", + "", + *(f"- `{path}`" for path in changed), + "", + ] + captures = sorted((source, VENDORED[kind]) for kind, source in sources.items() if source in changed) + if captures: + lines += [ + "Re-vendor and re-pin together, in one change:", + "", + "```bash", + *(f"cp $PANELBENCH_DIR/{source} \\\n {destination}" for source, destination in captures), + "```", + "", + f"then set `peers.{PANELBENCH}.commit` in `{LOCK.relative_to(REPO)}` to the", + "commit you copied from. A capture without a commit bump records where the bytes", + "came from as a guess.", + "", + ] + if EBUS_SPEC_FILE in changed: + lines += [ + f"`{EBUS_SPEC_FILE}` is the producer's own record of the specification commit it", + f"implements. The two sides are reading different vocabularies until `peers.{PANELBENCH}`'s", + "`synced_commit` and the vendored catalogs move together.", + "", + ] + lines += [ + "The byte comparison in `tests/test_schema_one_conformance.py` is what settles it,", + "and it is the same one `ci.yml` runs — this only says the producer moved first.", + ] + return "\n".join(lines) + + +def compare(repo: str, base: str, head: str, get: HttpGet, who: str) -> Comparison: + """`base...head`, as GitHub answers it, needing no clone of either.""" + url = f"{GITHUB_API}/repos/{slug(repo)}/compare/{urllib.parse.quote(base)}...{urllib.parse.quote(head)}" + answer = _answer(get(url), who) + return Comparison( + ahead_by=_count(answer, "ahead_by", who), + behind_by=_count(answer, "behind_by", who), + files=_filenames(answer, who), + ) + + +def specification(lock: Mapping[str, object], ls: LsRemote) -> Producer: + """The document both producers implement. Context, never a verdict. + + A specification that has moved does not make anything here wrong: it says + what a device class *may* publish, and what this repository vendors out of it + is byte-compared against `synced_commit` by `ci.yml`. Reported so that a + disagreement with a producer can be read against the right revision of the + document, and marked advisory so it can never fail a commit. + """ + repo = string(lock, "spec_repo", LOCK.name) + pinned = string(lock, "synced_commit", LOCK.name) + name = slug(repo) + try: + head = ls(repo, "HEAD") + except Unreachable as error: + return Producer(SPECIFICATION, pinned, "", "unknown", _unknown(f"{name} where HEAD is", error), advisory=True) + if head == pinned: + return Producer( + SPECIFICATION, + pinned, + head, + "current", + f"`{name}` is at `{short(head)}`, the commit we sync to.", + advisory=True, + ) + detail = ( + f"`{name}` is at `{short(head)}`, we sync to `{short(pinned)}`.\n\n" + "Context, not a verdict, and it fails nothing. The specification says what a device\n" + "class may publish rather than what one does, and the catalogs vendored from it are\n" + "byte-compared against the pinned commit by `ci.yml`. Worth reading when a producer\n" + "and this parser disagree — the two sides may be reading different revisions of it." + ) + return Producer(SPECIFICATION, pinned, head, "drift", detail, advisory=True) + + +BUILDERS: dict[str, Callable[[Mapping[str, object], HttpGet, LsRemote], Producer]] = { + PANELBENCH: panelbench, + PANEL_SIM: emitter, + SPECIFICATION: lambda lock, get, ls: specification(lock, ls), +} +"""One builder per producer, because they are not the same shape. Keyed by the +name the lockfile uses, so `--peer` names what `spec_lock.json` names.""" + + +# --------------------------------------------------------------------------- +# Saying it +# --------------------------------------------------------------------------- + + +def render(found: Sequence[Producer]) -> str: + return "\n".join(producer.section() for producer in found) + + +def exit_code(found: Sequence[Producer], *, strict: bool) -> int: + """1 when a producer we depend on has moved past its pin, 0 when none has. + + Unknown is 0 unless `--strict`: a network this script could not reach is not + evidence about a producer, and a commit is not the place to insist on one. + Advisory producers count for neither — see `Producer.advisory`. + """ + decisive = [producer for producer in found if not producer.advisory] + if any(producer.verdict == "drift" for producer in decisive): + return 1 + if strict and any(producer.verdict == "unknown" for producer in decisive): + return 1 + return 0 + + +def main( + argv: Sequence[str] | None = None, + *, + get: HttpGet = http_get, + ls: LsRemote = ls_remote, + out: TextIO | None = None, +) -> int: + parser = argparse.ArgumentParser( + prog="peer_drift.py", + description="Ask each producer pinned in spec_lock.json whether it has moved past the pin.", + ) + parser.add_argument( + "--strict", + action="store_true", + help="fail when a producer could not be asked, not only when one has drifted (CI passes this)", + ) + parser.add_argument( + "--peer", + choices=sorted(BUILDERS), + help="ask one producer instead of all of them", + ) + namespace = parser.parse_args(argv) + strict = bool(namespace.strict) + only = None if namespace.peer is None else str(namespace.peer) + + lock = load() + missing = unasked(lock) + if missing: + raise SystemExit( + f"{LOCK.name} pins {', '.join(missing)}, which this script has no way to ask. " + "Add a builder to scripts/peer_drift.py: a producer nobody asks about is a " + "producer that moves unnoticed, which is the whole thing this check exists to stop." + ) + unplaced = unvendored(lock) + if unplaced: + raise SystemExit( + f"peers.{PANELBENCH}.fixtures names a {', '.join(unplaced)} capture and " + "scripts/peer_drift.py records no path in this repository it is copied to. " + "Add it to VENDORED, or a drift report will name the file and not what to do about it." + ) + + asked = [name for name in (PANELBENCH, PANEL_SIM, SPECIFICATION) if only is None or name == only] + found = [BUILDERS[name](lock, get, ls) for name in asked] + print(render(found), end="", file=sys.stdout if out is None else out) + return exit_code(found, strict=strict) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/reference_panel.yaml b/scripts/reference_panel.yaml index 06fafe7..ce2fdcb 100644 --- a/scripts/reference_panel.yaml +++ b/scripts/reference_panel.yaml @@ -84,6 +84,15 @@ circuit_templates: priority: OFF_GRID breaker_rating: 50 device_type: evse + pool_pump: + # Commissioned to receive no backup power: the priority is locked at OFF_GRID, + # so `load-shed/priority` publishes no `$settable` and refuses a `/set`. The + # other way a circuit can be locked, distinct from `solar` below, whose NEVER + # is an ordinary settable value meaning "never shed". + relay_behavior: controllable + priority: OFF_GRID + never_backup: true + breaker_rating: 20 solar: # The locked circuit, and the reason this capture can test a refusal at all: a # non-controllable relay publishes no `$settable` and reports requester @@ -113,6 +122,10 @@ circuits: name: SPAN Drive - Driveway template: span_drive tabs: [35, 37] + - id: pool_pump + name: Pool Pump + template: pool_pump + tabs: [3] - id: solar_inverter name: Solar Inverter template: solar @@ -132,6 +145,7 @@ ticks: circuits: kitchen_lights: 121.0 garage_outlet: 122.0 + pool_pump: 1230.0 span_drive_garage: 2410.0 span_drive_driveway: 2420.0 solar_inverter: 0.0 @@ -141,6 +155,7 @@ ticks: circuits: kitchen_lights: 121.0 garage_outlet: 122.0 + pool_pump: 1230.0 span_drive_garage: 2410.0 span_drive_driveway: 2420.0 solar_inverter: -2430.0 @@ -150,6 +165,7 @@ ticks: circuits: kitchen_lights: 121.0 garage_outlet: 122.0 + pool_pump: 1230.0 span_drive_garage: 2410.0 span_drive_driveway: 0.0 solar_inverter: -8500.0 diff --git a/tests/reference_payloads/README.md b/tests/reference_payloads/README.md index 33efe0f..c92379a 100644 --- a/tests/reference_payloads/README.md +++ b/tests/reference_payloads/README.md @@ -27,7 +27,7 @@ fails when the two diverge. ## `parent_child_tree.json` -Retained topics captured off the eBus emitter's parent/child tree for a 40-space panel: 13 devices — the panel, both lugs, a BESS with its MID, a PV, an EVSE, and the circuits. +Retained topics captured off the eBus emitter's parent/child tree for a 40-space panel: 14 devices — the panel, both lugs, a BESS with its MID, a PV, an EVSE, and the circuits. Shape is `{device_id: {topic: payload}}`, every value a string, exactly as the broker retains them. `$description` is therefore a **JSON string**, not a nested object; `device_from_topics` replays it the way the transport does. `bess-mid` is typed `energy.ebus.device.mid`, not `.bess` — a reader filtering the tree by type marker has to expect the MID to survive a BESS filter. @@ -41,9 +41,9 @@ Recorded machine-readably in `spec_lock.json` as `peers.ebus-panel-sim`, which i | | | | -------------- | --------------------------------------------------------- | -| Producer | `ebus-panel-sim` 0.7.0 | +| Producer | `ebus-panel-sim` 0.8.0 | | Repository | electrification-bus/distribution-enclosure-simulator | -| Commit | `156b6ef14fbd00ca9e79ca2fc4bcd2ca4a6348f3` (tag `v0.7.0`) | +| Commit | `171bb94f0960ccd2f62282c83ec203017bd6aa7f` (tag `v0.8.0`) | | Capture script | `scripts/capture_parent_child_reference.py` | | Manifest | `scripts/reference_panel.yaml` | @@ -64,4 +64,4 @@ producer is not written down. It mirrors `examples/forty_tab_minimal.yaml` key f The cost of that choice is real: the capture is no longer reproducible by running an example anyone can find in the emitter. The committed manifest is what buys it back. -**Shape-stable, not byte-stable.** Each `$description` carries a `version` minted from the wall clock, so all thirteen move on every recapture. Nothing reads it; a diff confined to those lines means the producer did not move. +**Shape-stable, not byte-stable.** Each `$description` carries a `version` minted from the wall clock, so all fourteen move on every recapture. Nothing reads it; a diff confined to those lines means the producer did not move. diff --git a/tests/reference_payloads/parent_child_tree.json b/tests/reference_payloads/parent_child_tree.json index c6700de..42a5e79 100644 --- a/tests/reference_payloads/parent_child_tree.json +++ b/tests/reference_payloads/parent_child_tree.json @@ -1,6 +1,6 @@ { "0ab966b95f92a6a51ec548485aa85f54": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787706163063, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Kitchen Lights\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787869298458, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Kitchen Lights\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "15", @@ -18,7 +18,7 @@ "switch/relay-requester": "NONE" }, "573066aaddd7b75114c4563ce3af18c4": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787706163064, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Solar Inverter\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\"}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787869298459, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Solar Inverter\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\"}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "2", "breaker/rating": "30", @@ -33,13 +33,13 @@ "meter/exported-energy": "0.0", "meter/imported-energy": "182.16666666666666", "pcs/managed": "false", - "pcs/priority": "5", + "pcs/priority": "6", "switch/relay": "CLOSED", "switch/relay-controllable": "false", "switch/relay-requester": "CONFIGURATION" }, "62d0e03897b337b57101aae82f1e9ba2": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787706163063, \"type\": \"energy.ebus.device.circuit\", \"name\": \"SPAN Drive - Garage\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787869298458, \"type\": \"energy.ebus.device.circuit\", \"name\": \"SPAN Drive - Garage\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "2", "breaker/rating": "50", @@ -59,8 +59,26 @@ "switch/relay-controllable": "true", "switch/relay-requester": "NONE" }, + "acf35888f35522c721501d35e66503e6": { + "$description": "{\"homie\": \"5.0\", \"version\": 1787869298459, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Pool Pump\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\"}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", + "$state": "ready", + "breaker/poles": "1", + "breaker/rating": "20", + "info/name": "Pool Pump", + "info/spaces": "3", + "load-shed/priority": "OFF_GRID", + "meter/active-power": "-1230.0", + "meter/current": "10.25", + "meter/exported-energy": "41.0", + "meter/imported-energy": "0.0", + "pcs/managed": "true", + "pcs/priority": "5", + "switch/relay": "CLOSED", + "switch/relay-controllable": "true", + "switch/relay-requester": "NONE" + }, "bess": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787706163064, \"type\": \"energy.ebus.device.bess\", \"name\": \"Battery\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"string\"}, \"part-number\": {\"name\": \"Part number\", \"datatype\": \"string\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}, \"nameplate-capacity\": {\"name\": \"Nameplate capacity\", \"datatype\": \"float\", \"unit\": \"kWh\"}}}, \"soc\": {\"name\": \"soc\", \"type\": \"energy.ebus.capability.soc\", \"properties\": {\"soc\": {\"name\": \"State of charge\", \"datatype\": \"float\", \"unit\": \"%\"}, \"soe\": {\"name\": \"State of energy\", \"datatype\": \"float\", \"unit\": \"kWh\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"active-power\": {\"name\": \"Active power\", \"datatype\": \"float\", \"unit\": \"W\"}}}, \"status\": {\"name\": \"status\", \"type\": \"energy.ebus.capability.status\", \"properties\": {\"communication-state\": {\"name\": \"Communication state\", \"datatype\": \"enum\", \"format\": \"OK,DEGRADED,LOST,UNKNOWN\"}}}}, \"children\": [\"bess-mid\"], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787869298460, \"type\": \"energy.ebus.device.bess\", \"name\": \"Battery\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"string\"}, \"part-number\": {\"name\": \"Part number\", \"datatype\": \"string\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}, \"nameplate-capacity\": {\"name\": \"Nameplate capacity\", \"datatype\": \"float\", \"unit\": \"kWh\"}}}, \"soc\": {\"name\": \"soc\", \"type\": \"energy.ebus.capability.soc\", \"properties\": {\"soc\": {\"name\": \"State of charge\", \"datatype\": \"float\", \"unit\": \"%\"}, \"soe\": {\"name\": \"State of energy\", \"datatype\": \"float\", \"unit\": \"kWh\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"active-power\": {\"name\": \"Active power\", \"datatype\": \"float\", \"unit\": \"W\"}}}, \"status\": {\"name\": \"status\", \"type\": \"energy.ebus.capability.status\", \"properties\": {\"communication-state\": {\"name\": \"Communication state\", \"datatype\": \"enum\", \"format\": \"OK,DEGRADED,LOST,UNKNOWN\"}}}}, \"children\": [\"bess-mid\"], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", "$state": "ready", "info/firmware-version": "example-bess/v0.1.0", "info/model": "Example BESS", @@ -74,7 +92,7 @@ "status/communication-state": "OK" }, "bess-mid": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787706163064, \"type\": \"energy.ebus.device.mid\", \"name\": \"Microgrid Interconnect Device\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}, \"hardware-version\": {\"name\": \"Hardware version\", \"datatype\": \"string\"}}}, \"grid\": {\"name\": \"grid\", \"type\": \"energy.ebus.capability.grid\", \"properties\": {\"islanding-state\": {\"name\": \"Islanding state of the BESS-integrated grid-forming device\", \"datatype\": \"enum\", \"format\": \"ON_GRID,OFF_GRID,UNKNOWN\"}, \"grid-state\": {\"name\": \"Sensed grid condition\", \"datatype\": \"enum\", \"format\": \"UP,DOWN,DEGRADED,UNKNOWN\"}, \"grid-forming-entity\": {\"name\": \"Identity of the currently grid-forming entity\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"bess\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787869298460, \"type\": \"energy.ebus.device.mid\", \"name\": \"Microgrid Interconnect Device\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}, \"hardware-version\": {\"name\": \"Hardware version\", \"datatype\": \"string\"}}}, \"grid\": {\"name\": \"grid\", \"type\": \"energy.ebus.capability.grid\", \"properties\": {\"islanding-state\": {\"name\": \"Islanding state of the BESS-integrated grid-forming device\", \"datatype\": \"enum\", \"format\": \"ON_GRID,OFF_GRID,UNKNOWN\"}, \"grid-state\": {\"name\": \"Sensed grid condition\", \"datatype\": \"enum\", \"format\": \"UP,DOWN,DEGRADED,UNKNOWN\"}, \"grid-forming-entity\": {\"name\": \"Identity of the currently grid-forming entity\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"bess\", \"extensions\": []}", "$state": "ready", "grid/grid-forming-entity": "GRID", "grid/grid-state": "UP", @@ -86,7 +104,7 @@ "info/vendor-name": "Span" }, "d3724e0d660ba506aa79c1cafe5d1181": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787706163063, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Garage Outlet\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787869298458, \"type\": \"energy.ebus.device.circuit\", \"name\": \"Garage Outlet\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "1", "breaker/rating": "20", @@ -104,7 +122,7 @@ "switch/relay-requester": "NONE" }, "evse": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787706163064, \"type\": \"energy.ebus.device.evse\", \"name\": \"SPAN Drive - Garage\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"string\"}, \"part-number\": {\"name\": \"Part number\", \"datatype\": \"string\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}}}, \"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"lock-state\": {\"name\": \"Lock state\", \"datatype\": \"enum\", \"format\": \"UNLOCKED,LOCKED\"}}}, \"status\": {\"name\": \"status\", \"type\": \"energy.ebus.capability.status\", \"properties\": {\"status\": {\"name\": \"Status\", \"datatype\": \"enum\", \"format\": \"AVAILABLE,PREPARING,CHARGING,UNAVAILABLE\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"advertised-current\": {\"name\": \"Current EVSE is advertising to the EV\", \"datatype\": \"float\", \"unit\": \"A\"}}}, \"config\": {\"name\": \"config\", \"type\": \"energy.ebus.capability.config\", \"properties\": {\"user-max-charge-current\": {\"name\": \"User-configured maximum EVSE charge current (ceiling)\", \"datatype\": \"integer\", \"settable\": true, \"unit\": \"A\"}, \"max-charge-current\": {\"name\": \"Commissioned maximum EVSE charge current (installer-configured)\", \"datatype\": \"integer\", \"unit\": \"A\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787869298459, \"type\": \"energy.ebus.device.evse\", \"name\": \"SPAN Drive - Garage\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"string\"}, \"part-number\": {\"name\": \"Part number\", \"datatype\": \"string\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}}}, \"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"lock-state\": {\"name\": \"Lock state\", \"datatype\": \"enum\", \"format\": \"UNLOCKED,LOCKED\"}}}, \"status\": {\"name\": \"status\", \"type\": \"energy.ebus.capability.status\", \"properties\": {\"status\": {\"name\": \"Status\", \"datatype\": \"enum\", \"format\": \"AVAILABLE,PREPARING,CHARGING,UNAVAILABLE\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"advertised-current\": {\"name\": \"Current EVSE is advertising to the EV\", \"datatype\": \"float\", \"unit\": \"A\"}}}, \"config\": {\"name\": \"config\", \"type\": \"energy.ebus.capability.config\", \"properties\": {\"user-max-charge-current\": {\"name\": \"User-configured maximum EVSE charge current (ceiling)\", \"datatype\": \"integer\", \"settable\": true, \"unit\": \"A\"}, \"max-charge-current\": {\"name\": \"Commissioned maximum EVSE charge current (installer-configured)\", \"datatype\": \"integer\", \"unit\": \"A\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", "$state": "ready", "config/max-charge-current": "32", "config/user-max-charge-current": "32", @@ -118,7 +136,7 @@ "switch/lock-state": "LOCKED" }, "evse-2": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787706163064, \"type\": \"energy.ebus.device.evse\", \"name\": \"SPAN Drive - Driveway\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"string\"}, \"part-number\": {\"name\": \"Part number\", \"datatype\": \"string\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}}}, \"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"lock-state\": {\"name\": \"Lock state\", \"datatype\": \"enum\", \"format\": \"UNLOCKED,LOCKED\"}}}, \"status\": {\"name\": \"status\", \"type\": \"energy.ebus.capability.status\", \"properties\": {\"status\": {\"name\": \"Status\", \"datatype\": \"enum\", \"format\": \"AVAILABLE,PREPARING,CHARGING,UNAVAILABLE\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"advertised-current\": {\"name\": \"Current EVSE is advertising to the EV\", \"datatype\": \"float\", \"unit\": \"A\"}}}, \"config\": {\"name\": \"config\", \"type\": \"energy.ebus.capability.config\", \"properties\": {\"user-max-charge-current\": {\"name\": \"User-configured maximum EVSE charge current (ceiling)\", \"datatype\": \"integer\", \"settable\": true, \"unit\": \"A\"}, \"max-charge-current\": {\"name\": \"Commissioned maximum EVSE charge current (installer-configured)\", \"datatype\": \"integer\", \"unit\": \"A\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787869298459, \"type\": \"energy.ebus.device.evse\", \"name\": \"SPAN Drive - Driveway\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"string\"}, \"part-number\": {\"name\": \"Part number\", \"datatype\": \"string\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}}}, \"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"lock-state\": {\"name\": \"Lock state\", \"datatype\": \"enum\", \"format\": \"UNLOCKED,LOCKED\"}}}, \"status\": {\"name\": \"status\", \"type\": \"energy.ebus.capability.status\", \"properties\": {\"status\": {\"name\": \"Status\", \"datatype\": \"enum\", \"format\": \"AVAILABLE,PREPARING,CHARGING,UNAVAILABLE\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"advertised-current\": {\"name\": \"Current EVSE is advertising to the EV\", \"datatype\": \"float\", \"unit\": \"A\"}}}, \"config\": {\"name\": \"config\", \"type\": \"energy.ebus.capability.config\", \"properties\": {\"user-max-charge-current\": {\"name\": \"User-configured maximum EVSE charge current (ceiling)\", \"datatype\": \"integer\", \"settable\": true, \"unit\": \"A\"}, \"max-charge-current\": {\"name\": \"Commissioned maximum EVSE charge current (installer-configured)\", \"datatype\": \"integer\", \"unit\": \"A\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", "$state": "ready", "config/max-charge-current": "32", "config/user-max-charge-current": "32", @@ -132,7 +150,7 @@ "switch/lock-state": "UNLOCKED" }, "example-40t-001": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787706163064, \"type\": \"energy.ebus.device.distribution-enclosure\", \"name\": \"Example 40-tab Panel\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"enum\", \"format\": \"MAIN_16,MLO_24,MAIN_32,MAIN_40,MLO_48\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"hardware-version\": {\"name\": \"Hardware version\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}, \"data-model-version\": {\"name\": \"eBus data-model version (parent/child schema discriminator)\", \"datatype\": \"string\"}}}, \"door\": {\"name\": \"door\", \"type\": \"energy.ebus.capability.door\", \"properties\": {\"state\": {\"name\": \"Door state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"voltage-a\": {\"name\": \"L1 voltage\", \"datatype\": \"float\", \"unit\": \"V\"}, \"voltage-b\": {\"name\": \"L2 voltage\", \"datatype\": \"float\", \"unit\": \"V\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Main breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"enabled\": {\"name\": \"PCS system enabled\", \"datatype\": \"boolean\"}, \"active\": {\"name\": \"PCS system actively controlling one (or more) loads\", \"datatype\": \"boolean\"}, \"import-limit\": {\"name\": \"The power import limit currently being managed to\", \"datatype\": \"float\", \"unit\": \"A\"}, \"binding-constraint\": {\"name\": \"Which constraint class currently sets the import limit\", \"datatype\": \"enum\", \"format\": \"FSR,DOE,VOLTAGE,OFF_GRID,REQUESTED,OPERATOR,NONE,UNKNOWN\"}, \"feed-import-limit\": {\"name\": \"Limit of maximum power feeding the distribution enclosure\", \"datatype\": \"float\", \"unit\": \"A\"}, \"feed-import-limit-enablement\": {\"name\": \"Enablement status of the feed-import-limit\", \"datatype\": \"enum\", \"format\": \"UNSPECIFIED,UNCONFIGURED,DISABLED,ENABLED\"}, \"feed-import-limit-active\": {\"name\": \"Is feed-import-limit currently being enforced?\", \"datatype\": \"boolean\"}, \"operator-import-limit\": {\"name\": \"Operator-imposed maximum import limit\", \"datatype\": \"float\", \"unit\": \"A\"}, \"operator-import-limit-enablement\": {\"name\": \"Enablement status of the operator-import-limit\", \"datatype\": \"enum\", \"format\": \"UNSPECIFIED,UNCONFIGURED,DISABLED,ENABLED\"}, \"operator-import-limit-active\": {\"name\": \"Is operator-import-limit currently being enforced?\", \"datatype\": \"boolean\"}, \"off-grid-import-limit\": {\"name\": \"Off-Grid limit maximum import power\", \"datatype\": \"float\", \"unit\": \"A\"}, \"off-grid-import-limit-enablement\": {\"name\": \"Enablement status of the off-grid-import-limit\", \"datatype\": \"enum\", \"format\": \"UNSPECIFIED,UNCONFIGURED,DISABLED,ENABLED\"}, \"off-grid-import-limit-active\": {\"name\": \"Is off-grid-import-limit currently being enforced?\", \"datatype\": \"boolean\"}, \"requested-import-limit\": {\"name\": \"Requested limit maximum import power\", \"datatype\": \"float\", \"unit\": \"A\"}, \"requested-import-limit-enablement\": {\"name\": \"Enablement status of the requested-import-limit\", \"datatype\": \"enum\", \"format\": \"UNSPECIFIED,UNCONFIGURED,DISABLED,ENABLED\"}, \"requested-import-limit-active\": {\"name\": \"Is requested-import-limit currently being enforced?\", \"datatype\": \"boolean\"}}}, \"shed-forecast\": {\"name\": \"shed-forecast\", \"type\": \"energy.ebus.capability.shed-forecast\", \"properties\": {\"total-time-remaining\": {\"name\": \"Estimated total time before all sheddable circuits are shed (off-grid runtime)\", \"datatype\": \"integer\", \"unit\": \"min\"}, \"time-to-priority-shed\": {\"name\": \"Estimated time before the next priority tier is shed\", \"datatype\": \"integer\", \"unit\": \"min\"}, \"full-charge-total-time-remaining\": {\"name\": \"Estimated total time assuming BESS starts at full charge\", \"datatype\": \"integer\", \"unit\": \"min\"}, \"full-charge-time-to-priority-shed\": {\"name\": \"Estimated time to next priority shed assuming BESS starts at full charge\", \"datatype\": \"integer\", \"unit\": \"min\"}, \"confidence\": {\"name\": \"Confidence of the shed-forecast estimate\", \"datatype\": \"enum\", \"format\": \"LOW,MEDIUM,HIGH\"}}}, \"shed\": {\"name\": \"shed\", \"type\": \"energy.ebus.capability.shed\", \"properties\": {\"asserted-islanding-state\": {\"name\": \"Consumer-asserted islanding-state (grid-state override during MID/BESS comm-loss)\", \"datatype\": \"enum\", \"format\": \"NONE,ON_GRID,OFF_GRID\", \"settable\": true}, \"policy\": {\"name\": \"Shed policy (algorithm and parameters)\", \"datatype\": \"json\", \"format\": \"{\\\"$id\\\":\\\"soc-priority.v1\\\",\\\"type\\\":\\\"object\\\",\\\"required\\\":[\\\"algorithm\\\",\\\"parameters\\\"],\\\"additionalProperties\\\":false,\\\"properties\\\":{\\\"algorithm\\\":{\\\"const\\\":\\\"soc-priority.v1\\\"},\\\"parameters\\\":{\\\"type\\\":\\\"object\\\",\\\"required\\\":[\\\"soc-threshold-shed\\\",\\\"soc-threshold-release\\\"],\\\"additionalProperties\\\":false,\\\"properties\\\":{\\\"soc-threshold-shed\\\":{\\\"type\\\":\\\"integer\\\",\\\"minimum\\\":0,\\\"maximum\\\":100,\\\"description\\\":\\\"SoC percent below which SOC_THRESHOLD circuits shed\\\"},\\\"soc-threshold-release\\\":{\\\"type\\\":\\\"integer\\\",\\\"minimum\\\":0,\\\"maximum\\\":100,\\\"description\\\":\\\"SoC percent above which shed SOC_THRESHOLD circuits restore\\\"}}}}}\"}}}, \"power-flows\": {\"name\": \"power-flows\", \"type\": \"energy.ebus.capability.power-flows\", \"properties\": {\"pv\": {\"name\": \"PV power flow\", \"datatype\": \"float\", \"unit\": \"W\"}, \"battery\": {\"name\": \"Battery/BESS power flow\", \"datatype\": \"float\", \"unit\": \"W\"}, \"grid\": {\"name\": \"Grid power flow\", \"datatype\": \"float\", \"unit\": \"W\"}, \"site\": {\"name\": \"Site power flow\", \"datatype\": \"float\", \"unit\": \"W\"}}}, \"status\": {\"name\": \"status\", \"type\": \"energy.ebus.capability.status\", \"properties\": {\"relay\": {\"name\": \"Main relay\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\"}, \"ethernet\": {\"name\": \"Is Ethernet network interface operational?\", \"datatype\": \"boolean\"}, \"wifi\": {\"name\": \"Is Wi-Fi network interface operational?\", \"datatype\": \"boolean\"}, \"wifi-ssid\": {\"name\": \"SSID to which Wi-Fi network interface is connected\", \"datatype\": \"string\"}, \"cloud-connection\": {\"name\": \"Device connected to vendor cloud?\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,UNCONNECTED,CONNECTED\"}, \"postal-code\": {\"name\": \"Postal (Zip) code\", \"datatype\": \"string\"}, \"time-zone\": {\"name\": \"Time zone\", \"datatype\": \"string\"}}}}, \"children\": [\"bess\", \"0ab966b95f92a6a51ec548485aa85f54\", \"d3724e0d660ba506aa79c1cafe5d1181\", \"62d0e03897b337b57101aae82f1e9ba2\", \"fe8b85c15bc9610c1b8b4ebc6f82488d\", \"573066aaddd7b75114c4563ce3af18c4\", \"evse\", \"evse-2\", \"lugs-upstream\", \"lugs-downstream\", \"pv\"], \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787869298460, \"type\": \"energy.ebus.device.distribution-enclosure\", \"name\": \"Example 40-tab Panel\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"enum\", \"format\": \"MAIN_16,MLO_24,MAIN_32,MAIN_40,MLO_48\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"hardware-version\": {\"name\": \"Hardware version\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}, \"data-model-version\": {\"name\": \"eBus data-model version (parent/child schema discriminator)\", \"datatype\": \"string\"}}}, \"door\": {\"name\": \"door\", \"type\": \"energy.ebus.capability.door\", \"properties\": {\"state\": {\"name\": \"Door state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"voltage-a\": {\"name\": \"L1 voltage\", \"datatype\": \"float\", \"unit\": \"V\"}, \"voltage-b\": {\"name\": \"L2 voltage\", \"datatype\": \"float\", \"unit\": \"V\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Main breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"enabled\": {\"name\": \"PCS system enabled\", \"datatype\": \"boolean\"}, \"active\": {\"name\": \"PCS system actively controlling one (or more) loads\", \"datatype\": \"boolean\"}, \"import-limit\": {\"name\": \"The power import limit currently being managed to\", \"datatype\": \"float\", \"unit\": \"A\"}, \"binding-constraint\": {\"name\": \"Which constraint class currently sets the import limit\", \"datatype\": \"enum\", \"format\": \"FSR,DOE,VOLTAGE,OFF_GRID,REQUESTED,OPERATOR,NONE,UNKNOWN\"}, \"feed-import-limit\": {\"name\": \"Limit of maximum power feeding the distribution enclosure\", \"datatype\": \"float\", \"unit\": \"A\"}, \"feed-import-limit-enablement\": {\"name\": \"Enablement status of the feed-import-limit\", \"datatype\": \"enum\", \"format\": \"UNSPECIFIED,UNCONFIGURED,DISABLED,ENABLED\"}, \"feed-import-limit-active\": {\"name\": \"Is feed-import-limit currently being enforced?\", \"datatype\": \"boolean\"}, \"operator-import-limit\": {\"name\": \"Operator-imposed maximum import limit\", \"datatype\": \"float\", \"unit\": \"A\"}, \"operator-import-limit-enablement\": {\"name\": \"Enablement status of the operator-import-limit\", \"datatype\": \"enum\", \"format\": \"UNSPECIFIED,UNCONFIGURED,DISABLED,ENABLED\"}, \"operator-import-limit-active\": {\"name\": \"Is operator-import-limit currently being enforced?\", \"datatype\": \"boolean\"}, \"off-grid-import-limit\": {\"name\": \"Off-Grid limit maximum import power\", \"datatype\": \"float\", \"unit\": \"A\"}, \"off-grid-import-limit-enablement\": {\"name\": \"Enablement status of the off-grid-import-limit\", \"datatype\": \"enum\", \"format\": \"UNSPECIFIED,UNCONFIGURED,DISABLED,ENABLED\"}, \"off-grid-import-limit-active\": {\"name\": \"Is off-grid-import-limit currently being enforced?\", \"datatype\": \"boolean\"}, \"requested-import-limit\": {\"name\": \"Requested limit maximum import power\", \"datatype\": \"float\", \"unit\": \"A\"}, \"requested-import-limit-enablement\": {\"name\": \"Enablement status of the requested-import-limit\", \"datatype\": \"enum\", \"format\": \"UNSPECIFIED,UNCONFIGURED,DISABLED,ENABLED\"}, \"requested-import-limit-active\": {\"name\": \"Is requested-import-limit currently being enforced?\", \"datatype\": \"boolean\"}}}, \"shed-forecast\": {\"name\": \"shed-forecast\", \"type\": \"energy.ebus.capability.shed-forecast\", \"properties\": {\"total-time-remaining\": {\"name\": \"Estimated total time before all sheddable circuits are shed (off-grid runtime)\", \"datatype\": \"integer\", \"unit\": \"min\"}, \"time-to-priority-shed\": {\"name\": \"Estimated time before the next priority tier is shed\", \"datatype\": \"integer\", \"unit\": \"min\"}, \"full-charge-total-time-remaining\": {\"name\": \"Estimated total time assuming BESS starts at full charge\", \"datatype\": \"integer\", \"unit\": \"min\"}, \"full-charge-time-to-priority-shed\": {\"name\": \"Estimated time to next priority shed assuming BESS starts at full charge\", \"datatype\": \"integer\", \"unit\": \"min\"}, \"confidence\": {\"name\": \"Confidence of the shed-forecast estimate\", \"datatype\": \"enum\", \"format\": \"LOW,MEDIUM,HIGH\"}}}, \"shed\": {\"name\": \"shed\", \"type\": \"energy.ebus.capability.shed\", \"properties\": {\"asserted-islanding-state\": {\"name\": \"Consumer-asserted islanding-state (grid-state override during MID/BESS comm-loss)\", \"datatype\": \"enum\", \"format\": \"NONE,ON_GRID,OFF_GRID\", \"settable\": true}, \"policy\": {\"name\": \"Shed policy (algorithm and parameters)\", \"datatype\": \"json\", \"format\": \"{\\\"$id\\\":\\\"soc-priority.v1\\\",\\\"type\\\":\\\"object\\\",\\\"required\\\":[\\\"algorithm\\\",\\\"parameters\\\"],\\\"additionalProperties\\\":false,\\\"properties\\\":{\\\"algorithm\\\":{\\\"const\\\":\\\"soc-priority.v1\\\"},\\\"parameters\\\":{\\\"type\\\":\\\"object\\\",\\\"required\\\":[\\\"soc-threshold-shed\\\",\\\"soc-threshold-release\\\"],\\\"additionalProperties\\\":false,\\\"properties\\\":{\\\"soc-threshold-shed\\\":{\\\"type\\\":\\\"integer\\\",\\\"minimum\\\":0,\\\"maximum\\\":100,\\\"description\\\":\\\"SoC percent below which SOC_THRESHOLD circuits shed\\\"},\\\"soc-threshold-release\\\":{\\\"type\\\":\\\"integer\\\",\\\"minimum\\\":0,\\\"maximum\\\":100,\\\"description\\\":\\\"SoC percent above which shed SOC_THRESHOLD circuits restore\\\"}}}}}\"}}}, \"power-flows\": {\"name\": \"power-flows\", \"type\": \"energy.ebus.capability.power-flows\", \"properties\": {\"pv\": {\"name\": \"PV power flow\", \"datatype\": \"float\", \"unit\": \"W\"}, \"battery\": {\"name\": \"Battery/BESS power flow\", \"datatype\": \"float\", \"unit\": \"W\"}, \"grid\": {\"name\": \"Grid power flow\", \"datatype\": \"float\", \"unit\": \"W\"}, \"site\": {\"name\": \"Site power flow\", \"datatype\": \"float\", \"unit\": \"W\"}}}, \"status\": {\"name\": \"status\", \"type\": \"energy.ebus.capability.status\", \"properties\": {\"relay\": {\"name\": \"Main relay\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\"}, \"ethernet\": {\"name\": \"Is Ethernet network interface operational?\", \"datatype\": \"boolean\"}, \"wifi\": {\"name\": \"Is Wi-Fi network interface operational?\", \"datatype\": \"boolean\"}, \"wifi-ssid\": {\"name\": \"SSID to which Wi-Fi network interface is connected\", \"datatype\": \"string\"}, \"cloud-connection\": {\"name\": \"Device connected to vendor cloud?\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,UNCONNECTED,CONNECTED\"}, \"postal-code\": {\"name\": \"Postal (Zip) code\", \"datatype\": \"string\"}, \"time-zone\": {\"name\": \"Time zone\", \"datatype\": \"string\"}}}}, \"children\": [\"bess\", \"0ab966b95f92a6a51ec548485aa85f54\", \"d3724e0d660ba506aa79c1cafe5d1181\", \"62d0e03897b337b57101aae82f1e9ba2\", \"fe8b85c15bc9610c1b8b4ebc6f82488d\", \"acf35888f35522c721501d35e66503e6\", \"573066aaddd7b75114c4563ce3af18c4\", \"evse\", \"evse-2\", \"lugs-upstream\", \"lugs-downstream\", \"pv\"], \"extensions\": []}", "$state": "ready", "breaker/rating": "200", "door/state": "CLOSED", @@ -161,9 +179,9 @@ "pcs/requested-import-limit-active": "false", "pcs/requested-import-limit-enablement": "UNCONFIGURED", "power-flows/battery": "3500.0", - "power-flows/grid": "2347.0", + "power-flows/grid": "1117.0", "power-flows/pv": "-8500.0", - "power-flows/site": "2653.0", + "power-flows/site": "3883.0", "shed-forecast/confidence": "HIGH", "shed-forecast/full-charge-time-to-priority-shed": "3038", "shed-forecast/full-charge-total-time-remaining": "4320", @@ -180,7 +198,7 @@ "status/wifi-ssid": "example-wifi" }, "fe8b85c15bc9610c1b8b4ebc6f82488d": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787706163063, \"type\": \"energy.ebus.device.circuit\", \"name\": \"SPAN Drive - Driveway\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787869298458, \"type\": \"energy.ebus.device.circuit\", \"name\": \"SPAN Drive - Driveway\", \"nodes\": {\"switch\": {\"name\": \"switch\", \"type\": \"energy.ebus.capability.switch\", \"properties\": {\"relay\": {\"name\": \"Circuit relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OPEN,CLOSED\", \"settable\": true}, \"relay-requester\": {\"name\": \"Actor requesting the relay state\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,NONE,LOAD_SHED,USER,PCS,CONFIGURATION,FAULT\"}, \"relay-controllable\": {\"name\": \"Can the circuit's relay be commanded by the user?\", \"datatype\": \"boolean\"}}}, \"breaker\": {\"name\": \"breaker\", \"type\": \"energy.ebus.capability.breaker\", \"properties\": {\"rating\": {\"name\": \"Circuit breaker rating\", \"datatype\": \"integer\", \"unit\": \"A\"}, \"poles\": {\"name\": \"Number of breaker poles\", \"datatype\": \"integer\", \"format\": \"1:4:1\"}}}, \"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current\": {\"name\": \"Measured current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Measured active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Measured energy imported\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Measured energy exported\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"load-shed\": {\"name\": \"load-shed\", \"type\": \"energy.ebus.capability.load-shed\", \"properties\": {\"priority\": {\"name\": \"Configured priority of circuit shedding when off-grid (dominant-power-source != GRID)\", \"datatype\": \"enum\", \"format\": \"UNKNOWN,OFF_GRID,SOC_THRESHOLD,NEVER\", \"settable\": true}}}, \"pcs\": {\"name\": \"pcs\", \"type\": \"energy.ebus.capability.pcs\", \"properties\": {\"managed\": {\"name\": \"Is circuit managed by PCS?\", \"datatype\": \"boolean\"}, \"priority\": {\"name\": \"Circuit PCS priority ranking\", \"datatype\": \"integer\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this circuit\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"name\": {\"name\": \"Circuit name\", \"datatype\": \"string\"}, \"spaces\": {\"name\": \"Circuit breaker space number(s) within the load center (comma-separated for multi-pole)\", \"datatype\": \"string\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", "$state": "ready", "breaker/poles": "2", "breaker/rating": "50", @@ -201,30 +219,30 @@ "switch/relay-requester": "NONE" }, "lugs-downstream": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787706163064, \"type\": \"energy.ebus.device.lugs\", \"name\": \"Downstream lugs\", \"nodes\": {\"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current-a\": {\"name\": \"L1 current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"current-b\": {\"name\": \"L2 current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Imported energy\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Exported energy\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"fed-by-device-id\": {\"name\": \"Homie device-id of the upstream device feeding this lugs\", \"datatype\": \"string\"}, \"fed-by-device-type\": {\"name\": \"Homie $type of the upstream device\", \"datatype\": \"string\"}, \"fed-by-device-status\": {\"name\": \"Panel's view of comm health to the upstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this lugs\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"direction\": {\"name\": \"Lugs feed direction: upstream or downstream\", \"datatype\": \"enum\", \"format\": \"UPSTREAM,DOWNSTREAM\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787869298460, \"type\": \"energy.ebus.device.lugs\", \"name\": \"Downstream lugs\", \"nodes\": {\"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current-a\": {\"name\": \"L1 current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"current-b\": {\"name\": \"L2 current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Imported energy\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Exported energy\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"fed-by-device-id\": {\"name\": \"Homie device-id of the upstream device feeding this lugs\", \"datatype\": \"string\"}, \"fed-by-device-type\": {\"name\": \"Homie $type of the upstream device\", \"datatype\": \"string\"}, \"fed-by-device-status\": {\"name\": \"Panel's view of comm health to the upstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this lugs\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"direction\": {\"name\": \"Lugs feed direction: upstream or downstream\", \"datatype\": \"enum\", \"format\": \"UPSTREAM,DOWNSTREAM\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", "$state": "ready", "info/direction": "DOWNSTREAM", - "meter/active-power": "-5847.0", - "meter/current-a": "46.46666666666666", + "meter/active-power": "-4617.0", + "meter/current-a": "56.71666666666666", "meter/current-b": "46.474999999999994", - "meter/exported-energy": "97.45", - "meter/imported-energy": "44.05" + "meter/exported-energy": "76.95", + "meter/imported-energy": "64.55" }, "lugs-upstream": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787706163064, \"type\": \"energy.ebus.device.lugs\", \"name\": \"Upstream lugs\", \"nodes\": {\"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current-a\": {\"name\": \"L1 current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"current-b\": {\"name\": \"L2 current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Imported energy\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Exported energy\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"fed-by-device-id\": {\"name\": \"Homie device-id of the upstream device feeding this lugs\", \"datatype\": \"string\"}, \"fed-by-device-type\": {\"name\": \"Homie $type of the upstream device\", \"datatype\": \"string\"}, \"fed-by-device-status\": {\"name\": \"Panel's view of comm health to the upstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this lugs\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"direction\": {\"name\": \"Lugs feed direction: upstream or downstream\", \"datatype\": \"enum\", \"format\": \"UPSTREAM,DOWNSTREAM\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787869298459, \"type\": \"energy.ebus.device.lugs\", \"name\": \"Upstream lugs\", \"nodes\": {\"meter\": {\"name\": \"meter\", \"type\": \"energy.ebus.capability.meter\", \"properties\": {\"current-a\": {\"name\": \"L1 current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"current-b\": {\"name\": \"L2 current\", \"datatype\": \"float\", \"unit\": \"A\"}, \"active-power\": {\"name\": \"Active power\", \"datatype\": \"float\", \"unit\": \"W\"}, \"imported-energy\": {\"name\": \"Imported energy\", \"datatype\": \"float\", \"unit\": \"Wh\"}, \"exported-energy\": {\"name\": \"Exported energy\", \"datatype\": \"float\", \"unit\": \"Wh\"}}}, \"connection\": {\"name\": \"connection\", \"type\": \"energy.ebus.capability.connection\", \"properties\": {\"fed-by-device-id\": {\"name\": \"Homie device-id of the upstream device feeding this lugs\", \"datatype\": \"string\"}, \"fed-by-device-type\": {\"name\": \"Homie $type of the upstream device\", \"datatype\": \"string\"}, \"fed-by-device-status\": {\"name\": \"Panel's view of comm health to the upstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}, \"feeds-device-id\": {\"name\": \"Homie device-id of the downstream device fed by this lugs\", \"datatype\": \"string\"}, \"feeds-device-type\": {\"name\": \"Homie $type of the downstream device\", \"datatype\": \"string\"}, \"feeds-device-status\": {\"name\": \"Panel's view of comm health to the downstream device\", \"datatype\": \"enum\", \"format\": \"OK,LOST,DEGRADED\"}}}, \"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"direction\": {\"name\": \"Lugs feed direction: upstream or downstream\", \"datatype\": \"enum\", \"format\": \"UPSTREAM,DOWNSTREAM\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", "$state": "ready", "connection/fed-by-device-id": "bess", "connection/fed-by-device-status": "OK", "connection/fed-by-device-type": "energy.ebus.device.bess", "info/direction": "UPSTREAM", - "meter/active-power": "-5847.0", - "meter/current-a": "46.46666666666666", + "meter/active-power": "-4617.0", + "meter/current-a": "56.71666666666666", "meter/current-b": "46.474999999999994", - "meter/exported-energy": "97.45", - "meter/imported-energy": "44.05" + "meter/exported-energy": "76.95", + "meter/imported-energy": "64.55" }, "pv": { - "$description": "{\"homie\": \"5.0\", \"version\": 1787706163064, \"type\": \"energy.ebus.device.pv\", \"name\": \"Solar\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"string\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}, \"nominal-power\": {\"name\": \"Nominal power\", \"datatype\": \"float\", \"unit\": \"W\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", + "$description": "{\"homie\": \"5.0\", \"version\": 1787869298460, \"type\": \"energy.ebus.device.pv\", \"name\": \"Solar\", \"nodes\": {\"info\": {\"name\": \"info\", \"type\": \"energy.ebus.capability.info\", \"properties\": {\"vendor-name\": {\"name\": \"Vendor name\", \"datatype\": \"string\"}, \"model\": {\"name\": \"Model\", \"datatype\": \"string\"}, \"serial-number\": {\"name\": \"Serial number\", \"datatype\": \"string\"}, \"firmware-version\": {\"name\": \"Firmware version\", \"datatype\": \"string\"}, \"nominal-power\": {\"name\": \"Nominal power\", \"datatype\": \"float\", \"unit\": \"W\"}}}}, \"children\": [], \"root\": \"example-40t-001\", \"parent\": \"example-40t-001\", \"extensions\": []}", "$state": "ready", "info/firmware-version": "example-pv/v0.1.0", "info/model": "IQ8PLUS-72-2-US", diff --git a/tests/reference_payloads/schema_one.py b/tests/reference_payloads/schema_one.py index e3ce37a..d6b9d3e 100644 --- a/tests/reference_payloads/schema_one.py +++ b/tests/reference_payloads/schema_one.py @@ -38,7 +38,7 @@ def parent_child_tree() -> RetainedTopicTree: """The captured retained topics of a full 40-space panel. - Thirteen devices: the panel, both lugs, a BESS with its MID, a PV, an EVSE + Fourteen devices: the panel, both lugs, a BESS with its MID, a PV, an EVSE and the circuits — enough that a test can check what each device class does and does not declare, including the absences. """ diff --git a/tests/test_adoption.py b/tests/test_adoption.py index b797ddc..8de87ca 100644 --- a/tests/test_adoption.py +++ b/tests/test_adoption.py @@ -86,7 +86,7 @@ def _adopted(snapshot: SpanPanelSnapshot) -> dict[str, AdoptedDevice]: def test_a_tree_of_modelled_devices_adopts_nothing() -> None: - """The captured tree is thirteen devices this adapter reads, so it adopts none. + """The captured tree is fourteen devices this adapter reads, so it adopts none. The baseline the rest of this module measures against: anything that shows up here is a modelled device leaking into adoption, which is the failure that diff --git a/tests/test_peer_drift.py b/tests/test_peer_drift.py new file mode 100644 index 0000000..f61a8cf --- /dev/null +++ b/tests/test_peer_drift.py @@ -0,0 +1,545 @@ +"""`scripts/peer_drift.py` — the check that asks a producer whether it moved. + +Every test here hands the script fake fetchers, so nothing in this file touches +the network: the point of `HttpGet` / `LsRemote` being injectable is that the +comparisons can be exercised on a laptop with no connection, and that a suite +which passes today passes on the day panelbench cuts a release. + +What is deliberately *not* faked is the lockfile. `main` reads the real +`spec_lock.json`, because "the pins live in exactly one place" is the property +these tests are here to hold, and a fixture standing in for it would test a copy. +""" + +from __future__ import annotations + +from collections.abc import Callable, Mapping, Sequence +import email.message +import io +import json +from pathlib import Path +import re +import subprocess +import urllib.error +import urllib.request + +import pytest + +from span_panel_api_schema_1 import const + +from scripts.peer_drift import ( + EBUS_SPEC_FILE, + PANEL_SIM, + PANELBENCH, + SPECIFICATION, + VENDORED, + HttpGet, + LsRemote, + Unreachable, + _headers, + emitter, + exit_code, + fixtures, + http_get, + ls_remote, + main, + newer_release, + panelbench, + specification, + unasked, + unvendored, +) + +_LOCK = Path(const.__file__).parent / "spec_lock.json" +_SCRIPTS = Path(__file__).parent.parent / "scripts" + +_HEAD = "a" * 40 +"""A branch head that is not any pinned commit, and obviously synthetic.""" + + +def _lock() -> dict[str, object]: + """The real lockfile, read here rather than through the code under test.""" + with _LOCK.open(encoding="utf-8") as handle: + document: dict[str, object] = json.load(handle) + return document + + +def _peer(name: str) -> dict[str, object]: + peers = _lock()["peers"] + assert isinstance(peers, dict) + block = peers[name] + assert isinstance(block, dict) + return block + + +def _pinned(name: str, key: str) -> str: + value = _peer(name)[key] + assert isinstance(value, str) + return value + + +def _files(*, yanked: bool = False) -> list[dict[str, object]]: + return [{"filename": "example-0.whl", "yanked": yanked}] + + +def _pypi(latest: str, releases: Mapping[str, Sequence[Mapping[str, object]]] | None = None) -> str: + """PyPI's answer, with the pinned release healthy unless a test says otherwise.""" + listed: Mapping[str, Sequence[Mapping[str, object]]] = ( + {_pinned(PANEL_SIM, "version"): _files(), latest: _files()} if releases is None else releases + ) + return json.dumps({"info": {"version": latest}, "releases": listed}) + + +def _comparison(ahead: int, behind: int, files: Sequence[str]) -> str: + return json.dumps( + { + "status": "diverged" if behind else "ahead", + "ahead_by": ahead, + "behind_by": behind, + "files": [{"filename": name} for name in files], + } + ) + + +def _http(answers: Mapping[str, str]) -> HttpGet: + """Answer by URL fragment; anything unmatched is unreachable, loudly.""" + + def get(url: str) -> str: + for fragment, body in answers.items(): + if fragment in url: + return body + raise Unreachable(f"no fake answer for {url}") + + return get + + +def _refuses_http() -> HttpGet: + def get(url: str) -> str: + raise AssertionError(f"asked {url}, which this comparison should not need") + + return get + + +def _remote(heads: Mapping[str, str]) -> LsRemote: + def resolve(repo: str, ref: str) -> str: + for fragment, commit in heads.items(): + if fragment in repo: + return commit + raise Unreachable(f"no fake head for {repo} {ref}") + + return resolve + + +def _at_their_pins() -> LsRemote: + """Every producer sitting exactly where the lockfile says it is.""" + return _remote( + { + "panelbench": _pinned(PANELBENCH, "commit"), + "distribution-enclosure-simulator": _pinned(PANEL_SIM, "commit"), + "specification": str(_lock()["synced_commit"]), + } + ) + + +def _unreachable() -> LsRemote: + def resolve(repo: str, ref: str) -> str: + raise Unreachable("Name or service not known") + + return resolve + + +# --------------------------------------------------------------------------- +# The pins have one home +# --------------------------------------------------------------------------- + + +def test_the_script_states_no_pin_of_its_own() -> None: + """No commit and no version literal in either module. + + The whole failure this check exists to catch is a pin that has two homes and + stops agreeing with itself. A script that hardcoded either half would be that + failure, in the tool meant to find it. + """ + commit = re.compile(r"\b[0-9a-f]{40}\b") + version = re.compile(r"\b\d+\.\d+\.\d+\b") + for module in ("peer_drift.py", "_lock.py"): + source = (_SCRIPTS / module).read_text(encoding="utf-8") + assert not commit.findall(source), f"{module} names a commit; read it from spec_lock.json" + assert not version.findall(source), f"{module} names a version; read it from spec_lock.json" + + +def test_every_producer_it_reports_is_pinned_where_it_says() -> None: + lock = _lock() + reported = [ + (panelbench(lock, _refuses_http(), _at_their_pins()).pinned, _pinned(PANELBENCH, "commit")), + (emitter(lock, _all_current(), _at_their_pins()).pinned, _pinned(PANEL_SIM, "version")), + (specification(lock, _at_their_pins()).pinned, str(lock["synced_commit"])), + ] + assert [pair for pair in reported if pair[0] != pair[1]] == [] + + +def test_every_pinned_producer_can_be_asked() -> None: + """A peer added to the lockfile and not here would be silently exempt.""" + assert unasked(_lock()) == () + assert unvendored(_lock()) == () + + +def test_a_token_reaches_github_and_nowhere_else(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("GH_TOKEN", "not-a-real-token") + assert "Authorization" in _headers("https://api.github.com/repos/o/n/compare/a...b") + assert _headers("https://pypi.org/pypi/anything/json") == {} + + +# --------------------------------------------------------------------------- +# panelbench: a commit is only drift if it touched what we copy +# --------------------------------------------------------------------------- + + +def test_a_branch_at_the_pin_asks_github_nothing() -> None: + """The ordinary day, and it costs one `ls-remote` and no API request.""" + found = panelbench(_lock(), _refuses_http(), _at_their_pins()) + assert found.verdict == "current" + assert "the commit we pin" in found.detail + + +def test_commits_that_change_nothing_we_copy_stay_green() -> None: + found = panelbench( + _lock(), + _http({"compare": _comparison(3, 0, ["README.md", "docs/index.md"])}), + _remote({"panelbench": _HEAD}), + ) + assert found.verdict == "current" + assert found.observed == _HEAD + assert "3 commits ahead, nothing we copy changed" in found.detail + + +def test_a_changed_capture_is_drift_and_says_where_to_copy_it() -> None: + source = fixtures(_peer(PANELBENCH))["tree"] + # Answered only for the resolved head, so a comparison against the branch + # name -- a later push away from what the summary reports -- goes unanswered. + since_the_pin = f"compare/{_pinned(PANELBENCH, 'commit')}...{_HEAD}" + found = panelbench( + _lock(), + _http({since_the_pin: _comparison(1, 0, [source, "README.md"])}), + _remote({"panelbench": _HEAD}), + ) + assert found.verdict == "drift" + assert f"- `{source}`" in found.detail + assert f"cp $PANELBENCH_DIR/{source}" in found.detail + assert VENDORED["tree"] in found.detail + assert VENDORED["wire"] not in found.detail + + +def test_a_changed_producer_lockfile_is_drift() -> None: + found = panelbench( + _lock(), + _http({"compare": _comparison(1, 0, [EBUS_SPEC_FILE])}), + _remote({"panelbench": _HEAD}), + ) + assert found.verdict == "drift" + assert "reading different vocabularies" in found.detail + assert "cp $PANELBENCH_DIR" not in found.detail + + +def test_a_truncated_file_list_is_unknown_not_current() -> None: + """300 files is the API's ceiling, not a statement that the 301st is safe.""" + found = panelbench( + _lock(), + _http({"compare": _comparison(400, 0, [f"src/file_{index}.py" for index in range(300)])}), + _remote({"panelbench": _HEAD}), + ) + assert found.verdict == "unknown" + assert "all the API will list" in found.detail + + +def test_a_truncated_list_that_names_a_capture_is_still_drift() -> None: + """The ceiling makes an absence unreliable, not a presence.""" + source = fixtures(_peer(PANELBENCH))["wire"] + padding = [f"src/file_{index}.py" for index in range(299)] + found = panelbench( + _lock(), + _http({"compare": _comparison(400, 0, [source, *padding])}), + _remote({"panelbench": _HEAD}), + ) + assert found.verdict == "drift" + assert VENDORED["wire"] in found.detail + + +def test_a_pin_the_branch_does_not_contain_is_unknown() -> None: + found = panelbench( + _lock(), + _http({"compare": _comparison(4, 2, [])}), + _remote({"panelbench": _HEAD}), + ) + assert found.verdict == "unknown" + assert "not an ancestor" in found.detail + assert f"peers.{PANELBENCH}.ref" in found.detail + + +def test_a_producer_that_cannot_be_reached_is_unknown() -> None: + found = panelbench(_lock(), _refuses_http(), _unreachable()) + assert found.verdict == "unknown" + assert found.observed == "" + assert "not having asked is not the same fact" in found.detail.lower() + + +# --------------------------------------------------------------------------- +# the emitter: the release is the verdict, the branch is context +# --------------------------------------------------------------------------- + + +def test_a_newer_release_is_drift_and_names_the_capture_script() -> None: + found = emitter(_lock(), _http({"pypi.org": _pypi("99.0.0")}), _at_their_pins()) + assert found.verdict == "drift" + assert found.observed == "99.0.0" + assert _pinned(PANEL_SIM, "capture_script") in found.detail + produces = _peer(PANEL_SIM)["produces"] + assert isinstance(produces, dict) + assert str(produces["tree"]) in found.detail + assert "at v99.0.0" in found.detail, "the tag prefix comes from the pinned tag, not from a guess" + + +def test_the_emitters_branch_moving_is_reported_and_not_a_verdict() -> None: + found = emitter( + _lock(), + _http({"pypi.org": _pypi(_pinned(PANEL_SIM, "version"))}), + _remote({"distribution-enclosure-simulator": _HEAD}), + ) + assert found.verdict == "current" + assert "the branch head has" in found.detail + assert "not itself a reason to recapture" in found.detail + + +def test_asking_the_emitter_never_reaches_the_github_api() -> None: + """PyPI answers the verdict and `ls-remote` the context; neither is the API. + + Worth holding: the unauthenticated GitHub limit is the scarce thing here, and + the emitter has no claim on it. + """ + + def get(url: str) -> str: + assert "api.github.com" not in url, f"asked the GitHub API for {url}" + return _pypi(_pinned(PANEL_SIM, "version")) + + code, report = _run(strict=True, get=get, ls=_at_their_pins(), peer=PANEL_SIM) + assert code == 0 + assert "The reference tree is current." in report + + +@pytest.mark.parametrize( + ("body", "expected"), + [ + pytest.param( + lambda pin: _pypi(pin, {pin: _files(yanked=True)}), + "has been yanked", + id="the pinned release was withdrawn", + ), + pytest.param( + lambda pin: _pypi("99.0.0", {"99.0.0": _files()}), + "lists no release", + id="the index does not carry the pin at all", + ), + pytest.param( + lambda pin: _pypi(pin, {pin: []}), + "with no files", + id="the pinned release has nothing left under it", + ), + pytest.param( + lambda pin: _pypi("0.0.1", {pin: _files(), "0.0.1": _files()}), + "is not newer than the pin", + id="the index serves something older than the pin", + ), + pytest.param( + lambda pin: _pypi("99.0.0b1", {pin: _files(), "99.0.0b1": _files()}), + "cannot be ordered against the pin", + id="the latest is a pre-release this cannot order", + ), + ], +) +def test_a_release_that_is_not_plainly_newer_is_never_drift(body: Callable[[str], str], expected: str) -> None: + """Five ways PyPI can disagree with the pin without the capture being stale. + + Every one of them used to read as drift, whose instructions say to re-capture + from `info.version` — which for a yanked, absent or older release is worse + than doing nothing. + """ + pinned = _pinned(PANEL_SIM, "version") + found = emitter(_lock(), _http({"pypi.org": body(pinned)}), _at_their_pins()) + assert found.verdict == "unknown" + assert expected in found.detail + assert "capture_parent_child_reference" not in found.detail, "no instructions for a non-verdict" + + +@pytest.mark.parametrize( + ("candidate", "than", "expected"), + [ + ("1.9.0", "1.10.0", False), + ("1.10.0", "1.9.0", True), + ("2.0", "2.0.0", False), + ("2.0.1", "2.0", True), + ("2.0.0rc1", "2.0.0", None), + ("2.0.0", "2.0.0.dev1", None), + ], +) +def test_versions_are_ordered_as_numbers_or_not_at_all(candidate: str, than: str, expected: bool | None) -> None: + """Segment by segment as integers, and `None` the moment a suffix appears.""" + assert newer_release(candidate, than) is expected + + +def test_pypi_being_unreachable_still_reports_the_branch() -> None: + found = emitter(_lock(), _http({}), _at_their_pins()) + assert found.verdict == "unknown" + assert "UNKNOWN" in found.detail + assert "commits" in found.detail, "the context is still worth printing when the verdict is not" + + +# --------------------------------------------------------------------------- +# the specification: reported, never decisive +# --------------------------------------------------------------------------- + + +def test_the_specification_moving_decides_nothing() -> None: + found = specification(_lock(), _remote({"specification": _HEAD})) + assert found.advisory + assert found.verdict == "drift" + assert exit_code([found], strict=True) == 0 + + +# --------------------------------------------------------------------------- +# the fetchers themselves, which every other test replaces +# --------------------------------------------------------------------------- + + +def _http_error(code: int, reason: str, *, spent: bool = False) -> urllib.error.HTTPError: + headers = email.message.Message() + if spent: + headers["x-ratelimit-remaining"] = "0" + return urllib.error.HTTPError("https://api.github.com/repos/o/n", code, reason, headers, None) + + +@pytest.mark.parametrize( + ("failure", "expected"), + [ + pytest.param(_http_error(404, "Not Found"), "HTTP 404", id="a repository or release that is not there"), + pytest.param(_http_error(403, "Forbidden", spent=True), "rate limit", id="the unauthenticated limit spent"), + pytest.param(_http_error(429, "Too Many Requests", spent=True), "rate limit", id="asked too fast"), + pytest.param(urllib.error.URLError("Name or service not known"), "Name or service", id="no network"), + ], +) +def test_http_get_turns_every_failure_into_unreachable( + monkeypatch: pytest.MonkeyPatch, failure: Exception, expected: str +) -> None: + """No failure mode of the real fetcher may escape as anything but `Unreachable`. + + Everything above this line runs against fakes, so this is the only test that + holds the seam between them: an exception the fetcher does not convert would + reach `main` as a traceback and fail a commit for a producer that is merely + unreachable. + """ + + def raise_it(request: object, timeout: float) -> object: + raise failure + + monkeypatch.setattr(urllib.request, "urlopen", raise_it) + with pytest.raises(Unreachable) as caught: + http_get("https://api.github.com/repos/o/n/compare/a...b") + assert expected in str(caught.value) + + +def test_http_get_returns_the_body_it_was_given(monkeypatch: pytest.MonkeyPatch) -> None: + class Response: + def __enter__(self) -> Response: + return self + + def __exit__(self, *_: object) -> bool: + return False + + def read(self) -> bytes: + return b'{"info": {}}' + + def answer(request: object, timeout: float) -> Response: + return Response() + + monkeypatch.setattr(urllib.request, "urlopen", answer) + assert http_get("https://pypi.org/pypi/example/json") == '{"info": {}}' + + +def _repository(tmp_path: Path) -> tuple[str, str]: + """A real repository with one commit on a branch, to resolve refs against.""" + path = tmp_path / "producer" + path.mkdir() + _git(path, "init", "-b", "probe") + _git(path, "-c", "user.email=nobody@example.invalid", "-c", "user.name=Nobody", "commit", "--allow-empty", "-m", "one") + return str(path), _git(path, "rev-parse", "HEAD") + + +def _git(path: Path, *arguments: str) -> str: + completed = subprocess.run(["git", *arguments], cwd=path, check=True, capture_output=True, text=True) + return completed.stdout.strip() + + +def test_ls_remote_resolves_a_ref_that_exists(tmp_path: Path) -> None: + repo, head = _repository(tmp_path) + assert ls_remote(repo, "refs/heads/probe") == head + + +def test_ls_remote_reports_a_ref_that_does_not(tmp_path: Path) -> None: + """A branch renamed or deleted answers cleanly, and `git` exits 0 saying nothing.""" + repo, _ = _repository(tmp_path) + with pytest.raises(Unreachable) as caught: + ls_remote(repo, "refs/heads/renamed-away") + assert "has no refs/heads/renamed-away" in str(caught.value) + + +def test_ls_remote_reports_a_repository_that_is_not_there(tmp_path: Path) -> None: + with pytest.raises(Unreachable) as caught: + ls_remote(str(tmp_path / "nowhere"), "HEAD") + assert str(caught.value), "git's own last line, rather than an empty report" + + +# --------------------------------------------------------------------------- +# exit codes +# --------------------------------------------------------------------------- + + +def _run(*, strict: bool, get: HttpGet, ls: LsRemote, peer: str | None = None) -> tuple[int, str]: + out = io.StringIO() + argv = ["--strict"] if strict else [] + if peer is not None: + argv += ["--peer", peer] + return main(argv, get=get, ls=ls, out=out), out.getvalue() + + +def _all_current() -> HttpGet: + return _http({"pypi.org": _pypi(_pinned(PANEL_SIM, "version"))}) + + +def test_a_run_where_nothing_moved_passes() -> None: + code, report = _run(strict=True, get=_all_current(), ls=_at_their_pins()) + assert code == 0 + assert [line for line in report.splitlines() if line.startswith("## ")] == [ + f"## {PANELBENCH}", + f"## {PANEL_SIM}", + f"## {SPECIFICATION}", + ] + + +def test_drift_fails_a_commit_and_a_ci_run_alike() -> None: + drifted = _http({"pypi.org": _pypi("99.0.0")}) + assert _run(strict=False, get=drifted, ls=_at_their_pins(), peer=PANEL_SIM)[0] == 1 + assert _run(strict=True, get=drifted, ls=_at_their_pins(), peer=PANEL_SIM)[0] == 1 + + +def test_unknown_passes_a_commit_and_fails_ci() -> None: + """A laptop on a plane still commits; a CI run with no answer does not pass.""" + code, report = _run(strict=False, get=_http({}), ls=_unreachable()) + assert code == 0 + assert "UNKNOWN" in report + + assert _run(strict=True, get=_http({}), ls=_unreachable())[0] == 1 + + +def test_peer_asks_one_producer() -> None: + code, report = _run(strict=True, get=_refuses_http(), ls=_at_their_pins(), peer=PANELBENCH) + assert code == 0 + assert report.count("## ") == 1 + assert report.startswith(f"## {PANELBENCH}") diff --git a/tests/test_reference_tree_values.py b/tests/test_reference_tree_values.py index 6c6d4d5..278ea0e 100644 --- a/tests/test_reference_tree_values.py +++ b/tests/test_reference_tree_values.py @@ -17,12 +17,12 @@ thing nobody does twice — hence this. **Compared at device-type granularity, and it has to be.** The two artifacts -describe different panels: this one is a five-circuit synthetic enclosure with +describe different panels: this one is a six-circuit synthetic enclosure with `example-*` identifiers, panelbench's is a twenty-eight-circuit one, so a per-device comparison would fail on the names rather than on a value. Type -granularity is also the granularity the question is asked at: five circuits -declare the same properties, and the same one going unvalued on all five is one -gap, not five. The same choice the integration's `test_declared_but_unread` +granularity is also the granularity the question is asked at: six circuits +declare the same properties, and the same one going unvalued on all six is one +gap, not six. The same choice the integration's `test_declared_but_unread` makes, for the same reason. The reduction loses nothing here, and that is measured rather than assumed: diff --git a/tests/test_schema_one_adapter.py b/tests/test_schema_one_adapter.py index f67933c..29ab036 100644 --- a/tests/test_schema_one_adapter.py +++ b/tests/test_schema_one_adapter.py @@ -212,7 +212,7 @@ def test_snapshot_is_built_from_the_discovered_tree(adapter: SchemaOneAdapter) - assert snapshot.panel_size == 40 assert snapshot.circuits[SOLAR_CIRCUIT].name == "Solar Inverter" assert snapshot.battery.soe_percentage == pytest.approx(50.4104, rel=1e-4) - # 5 circuits occupying 8 positions (two are multi-pole), so 32 remain. + # 6 circuits occupying 9 positions (three are multi-pole), so 31 remain. assert len(snapshot.circuits) == 37 diff --git a/tests/test_schema_one_circuits.py b/tests/test_schema_one_circuits.py index d5113d8..3886fda 100644 --- a/tests/test_schema_one_circuits.py +++ b/tests/test_schema_one_circuits.py @@ -27,9 +27,11 @@ _TREE = parent_child_tree() _CATALOGS = Path(__file__).parent.parent / "packages" / "schema-1" / "spec" / "catalogs" -# From the fixture: a 1-pole load, and a 2-pole backfeeding PV breaker. +# From the fixture: a 1-pole load, a 2-pole backfeeding PV breaker, and the +# 1-pole load commissioned never-backup. KITCHEN_LIGHTS = "0ab966b95f92a6a51ec548485aa85f54" SOLAR_INVERTER = "573066aaddd7b75114c4563ce3af18c4" +POOL_PUMP = "acf35888f35522c721501d35e66503e6" def _device(device_id: str) -> DiscoveredDevice: @@ -46,6 +48,11 @@ def _solar() -> DiscoveredDevice: return _device(SOLAR_INVERTER) +@pytest.fixture(name="pump") +def _pump() -> DiscoveredDevice: + return _device(POOL_PUMP) + + def test_identity_and_name(kitchen: DiscoveredDevice) -> None: circuit = build_circuit(kitchen) @@ -177,6 +184,31 @@ def test_an_unannounced_settable_means_never_backup(kitchen: DiscoveredDevice) - assert build_circuit(kitchen).is_never_backup is True +def test_the_captured_never_backup_circuit_is_locked_and_still_controllable(pump: DiscoveredDevice) -> None: + """The same lock read off a real circuit rather than an edited declaration. + + The two tests above build a never-backup circuit by editing Kitchen + Lights, which is what proves the reader keys on `$settable` and on nothing + else about the device. This proves the other half — that a conforming + producer announces the lock that way: `ebus-panel-sim` 0.8.0 commissions Pool + Pump never-backup and publishes `load-shed/priority` carrying no `$settable` + at all, never `settable: false`. + + Its relay stays controllable, and that is not incidental. The two + commissioning flags are scoped separately, so deriving either from the other + would take the switch away from every never-backup circuit on every panel — + here that inference would be invisible, because the derived answer and the + published one differ. + """ + assert "settable" not in pump.get_node_properties("load-shed")["priority"] + assert pump.get_property("switch", "relay-controllable") == "true" + + circuit = build_circuit(pump) + + assert circuit.is_never_backup is True + assert circuit.is_user_controllable is True + + def _catalogued_priorities() -> list[str]: """Every value `load-shed` 0.3 declares for `priority`, in catalog order. diff --git a/tests/test_schema_one_connection_health.py b/tests/test_schema_one_connection_health.py index 8bf24d4..fca3f31 100644 --- a/tests/test_schema_one_connection_health.py +++ b/tests/test_schema_one_connection_health.py @@ -9,7 +9,7 @@ **Three things make that easy to get wrong, and every test here is aimed at one of them.** -*Absence is a value.* Two of the capture's five circuits publish no connection +*Absence is a value.* Three of the capture's six circuits publish no connection record at all — the spec calls that normal for a mixed-load or unsurveyed circuit — and the enum firmware does publish is `OK,LOST,DEGRADED`, with no UNKNOWN member. So "nobody has said" can only be expressed by the property not @@ -139,7 +139,7 @@ def test_the_status_enum_has_no_unknown_member() -> None: def test_only_the_circuits_feeding_a_der_publish_a_connection_record() -> None: """The negative case is in the capture, not manufactured by a test. - Five circuits, three of which feed a commissioned DER. The other two feed + Six circuits, three of which feed a commissioned DER. The other three feed ordinary loads and publish neither half of the record — which `distribution-enclosure.md` describes as the normal state for a mixed-load circuit, and which is exactly the shape a mapper must not read as a fault. diff --git a/tests/test_schema_one_control_refusal.py b/tests/test_schema_one_control_refusal.py index 3f35169..247daf5 100644 --- a/tests/test_schema_one_control_refusal.py +++ b/tests/test_schema_one_control_refusal.py @@ -15,12 +15,20 @@ a circuit is re-commissioned in place. Driven from the shipped capture wherever the capture has the case, and from a -mutated copy of it where it does not. The distinction matters: the locked relay -below is what `ebus-panel-sim` 0.7.0 -- the specification's own executable -publisher -- emits for a circuit commissioned `non-controllable`, so those tests -are evidence that the catalogued rule and a conforming producer agree. The -never-backup circuit is a mutation, because no circuit in the capture is -commissioned that way, and it says so. +mutated copy of it where it does not. The distinction matters, and the capture +now carries both locks: `ebus-panel-sim` 0.8.0 -- the specification's own +executable publisher -- emits a `non-controllable` relay on Solar Inverter and a +never-backup priority on Pool Pump. So every refusal asserted against a device id +below is evidence that the catalogued rule and a conforming producer agree, +rather than evidence about an edit made here. The two locks land on different +circuits, which is what makes their independence provable from the capture as +well as arguable from the catalogs. + +What stays mutated is what this producer will not emit: a declaration that +disagrees with its own value (the firmware defect SPAN reports), a `settable: +false` spelled out where a conforming publisher says nothing, a `$settable` +dropped from a circuit that publishes one, and a `load-shed` node with no +`priority` property in it. """ from __future__ import annotations @@ -44,6 +52,11 @@ CONTROLLABLE_CIRCUIT = "0ab966b95f92a6a51ec548485aa85f54" """Kitchen Lights, its ordinary sibling.""" +NEVER_BACKUP_CIRCUIT = "acf35888f35522c721501d35e66503e6" +"""Pool Pump, commissioned never-backup: `load-shed/priority` carries no +`$settable`. Its relay is controllable, so it is the other lock and only the +other lock.""" + def _schema() -> V2HomieSchema: return V2HomieSchema( @@ -222,7 +235,11 @@ def test_a_relay_target_exists_exactly_where_relay_controllable_is_true(adapter: for device_id, topics in tree.items() if json.loads(topics["$description"])["type"].endswith(".circuit") } - assert len(circuits) == 5 + assert len(circuits) == 6 + assert circuits[NEVER_BACKUP_CIRCUIT]["switch/relay-controllable"] == "true", ( + "the priority-locked circuit's relay is no longer controllable, so the sweep below " + "no longer carries the case that proves the two commissioning locks are independent" + ) for device_id, topics in circuits.items(): controllable = topics["switch/relay-controllable"] == "true" @@ -255,7 +272,7 @@ def test_a_circuit_the_tree_does_not_carry_yields_no_target(adapter: SchemaOneAd # --------------------------------------------------------------------------- -# The priority, which the capture has no case for +# The priority, which the capture carries a case for too # --------------------------------------------------------------------------- @@ -274,14 +291,18 @@ def test_priority_stays_settable_on_a_locked_relay(adapter: SchemaOneAdapter) -> assert target.topic == f"ebus/5/{LOCKED_CIRCUIT}/load-shed/priority/set" -def test_a_never_backup_circuit_yields_no_priority_target() -> None: - """Mutated, because no circuit in the capture is commissioned never-backup.""" +def test_a_never_backup_circuit_yields_no_priority_target(adapter: SchemaOneAdapter) -> None: + """The mirror of the test above, and both halves come off real circuits now. - adapter = _adapter(_redeclared(CONTROLLABLE_CIRCUIT, "load-shed", "priority", settable=False)) - - assert adapter.set_circuit_priority_target(CONTROLLABLE_CIRCUIT) is None - # And only the priority: the relay is a separate commissioning flag. - assert adapter.set_circuit_relay_target(CONTROLLABLE_CIRCUIT) is not None + Pool Pump is commissioned never-backup and its relay is controllable, so this + is the other direction of the same independence: a locked priority takes + nothing away from the relay beside it, exactly as a locked relay takes nothing + away from the priority. Conflating the two would strip a control from every + never-backup circuit on every panel, and the pair of circuits is what makes + that a claim about commissioning rather than about a mutation of mine. + """ + assert adapter.set_circuit_priority_target(NEVER_BACKUP_CIRCUIT) is None + assert adapter.set_circuit_relay_target(NEVER_BACKUP_CIRCUIT) is not None def test_an_unannounced_priority_settable_yields_no_target() -> None: @@ -293,6 +314,13 @@ def test_an_unannounced_priority_settable_yields_no_target() -> None: permission resolved a write topic for precisely the circuits commissioned not to accept one, and the panel would have refused the publish. + Mutated even though the capture now carries a never-backup circuit, because + the two ask different questions. Pool Pump proves the producer announces the + lock this way; this proves the *reader* keys on the attribute and on nothing + else about that circuit -- the omission is applied to a circuit whose + declaration otherwise announces the priority settable, so every other + difference between the two circuits is held constant. + Only the priority: the relay is a separate commissioning flag and this circuit's is untouched. """ @@ -303,18 +331,41 @@ def test_an_unannounced_priority_settable_yields_no_target() -> None: assert adapter.set_circuit_relay_target(CONTROLLABLE_CIRCUIT) is not None -def test_every_circuit_in_the_capture_announces_its_priority_settable(adapter: SchemaOneAdapter) -> None: - """The premise of the two mutations above, asserted rather than assumed. +def test_a_priority_declared_settable_false_is_a_lock_too() -> None: + """The spelling no conforming publisher uses, refused all the same. + + Homie 5 defaults `$settable` to false and the eBus SDK emits the attribute + only where it is true, so the capture's never-backup circuit announces its + lock by saying nothing and `settable: false` appears nowhere in it. A reader + that recognised only the omission would grant a priority control to firmware + that spells the same lock out in words, which is the one publisher that could + not be accused of ambiguity. + """ + + adapter = _adapter(_redeclared(CONTROLLABLE_CIRCUIT, "load-shed", "priority", settable=False)) + + assert adapter.set_circuit_priority_target(CONTROLLABLE_CIRCUIT) is None + assert adapter.set_circuit_relay_target(CONTROLLABLE_CIRCUIT) is not None + + +def test_exactly_one_circuit_in_the_capture_locks_its_priority(adapter: SchemaOneAdapter) -> None: + """The premise of everything above, asserted rather than assumed. - Both build a locked priority by editing a declaration that the producer - published as settable. A capture that stopped announcing the attribute would - make the mutations indistinguishable from the shipped state and quietly turn - `test_a_priority_target_exists_exactly_on_the_circuits` into an assertion - that no circuit has a priority target at all. + Two things at once, and each guards a different set of tests. That every + circuit but one announces `settable: true` is what makes the mutations above + mutations: they build a lock by editing a declaration the producer published + as settable, and a capture that stopped announcing the attribute would make + them indistinguishable from the shipped state while quietly turning + `test_a_priority_target_exists_exactly_where_the_priority_is_announced_settable` + into an assertion that no circuit has a priority target at all. That the remaining one is + `NEVER_BACKUP_CIRCUIT` is what makes the capture-driven refusal a refusal + rather than a lookup of an absent device. It is also the standing answer to the argument the permissive default was - built on -- firmware that publishes no attribute at all. Every circuit here - announces one, so no producer we hold a capture from ever needed it. + built on -- firmware that publishes no attribute at all. This producer omits + it, and it omits it in precisely one place: the circuit commissioned not to + accept the write. Silence here is not a gap in what the panel said, it is + what the panel said. """ tree = parent_child_tree() circuits = { @@ -322,11 +373,17 @@ def test_every_circuit_in_the_capture_announces_its_priority_settable(adapter: S for device_id, topics in tree.items() if json.loads(topics["$description"])["type"].endswith(".circuit") } - assert len(circuits) == 5, "the capture's circuit set has moved; this premise is no longer about what it was" + assert len(circuits) == 6, "the capture's circuit set has moved; this premise is no longer about what it was" + locked: set[str] = set() for device_id, topics in circuits.items(): definition = json.loads(topics["$description"])["nodes"]["load-shed"]["properties"]["priority"] - assert definition.get("settable") is True, device_id + if "settable" in definition: + assert definition["settable"] is True, device_id + else: + locked.add(device_id) + + assert locked == {NEVER_BACKUP_CIRCUIT} # --------------------------------------------------------------------------- @@ -361,18 +418,24 @@ def test_a_device_that_declares_no_priority_yields_no_priority_target(adapter: S assert adapter.set_circuit_relay_target(device_id) is None -def test_a_priority_target_exists_exactly_on_the_circuits(adapter: SchemaOneAdapter) -> None: +def test_a_priority_target_exists_exactly_where_the_priority_is_announced_settable(adapter: SchemaOneAdapter) -> None: """Over the whole capture at once, the way the relay invariant is asserted. Every device in the tree is addressable by id, and only the circuits declare a shed priority — so a target on anything else is a target for a control that device never offered, whatever its `$settable` would have defaulted to. + Being a circuit is necessary and not sufficient: the never-backup one + declares the property and withholds the write, so the predicate is the + declaration itself. Asserting the device type instead would pass a reader + that never looked at a declaration at all. """ tree = parent_child_tree() for device_id, topics in tree.items(): - is_circuit = json.loads(topics["$description"])["type"].endswith(".circuit") - assert (adapter.set_circuit_priority_target(device_id) is not None) is is_circuit, device_id + nodes = json.loads(topics["$description"])["nodes"] + declared = nodes.get("load-shed", {}).get("properties", {}).get("priority", {}) + announced = declared.get("settable") is True + assert (adapter.set_circuit_priority_target(device_id) is not None) is announced, device_id def test_a_declared_priority_missing_from_its_node_is_undeclared_too(adapter: SchemaOneAdapter) -> None: @@ -405,6 +468,7 @@ def test_has_circuit_answers_for_the_circuits_and_nothing_else(adapter: SchemaOn """ assert adapter.has_circuit(CONTROLLABLE_CIRCUIT) is True assert adapter.has_circuit(LOCKED_CIRCUIT) is True, "locked is not absent" + assert adapter.has_circuit(NEVER_BACKUP_CIRCUIT) is True, "nor is a locked priority" for device_id in _NON_CIRCUITS: assert adapter.has_circuit(device_id) is False, device_id diff --git a/tests/test_schema_one_discovery.py b/tests/test_schema_one_discovery.py index d753710..65c4cae 100644 --- a/tests/test_schema_one_discovery.py +++ b/tests/test_schema_one_discovery.py @@ -176,7 +176,7 @@ def _moved() -> Mapping[Declaration, frozenset[str]]: """Republish each declared property once; return the snapshot fields it moved. One rebuild per declaring *device*, unioned: the two lugs devices and the - five circuits declare the same properties and are read differently, so a + six circuits declare the same properties and are read differently, so a single probe against whichever came first would answer for both. """ tree = _tree() diff --git a/tests/test_schema_one_panel.py b/tests/test_schema_one_panel.py index 125d28b..09156d4 100644 --- a/tests/test_schema_one_panel.py +++ b/tests/test_schema_one_panel.py @@ -114,8 +114,8 @@ def test_power_flows(fields: PanelFields) -> None: negates none of them, and this is what says so.""" assert fields.power_flow_pv == -8500.0 assert fields.power_flow_battery == 3500.0 - assert fields.power_flow_grid == 2347.0 - assert fields.power_flow_site == 2653.0 + assert fields.power_flow_grid == 1117.0 + assert fields.power_flow_site == 3883.0 # --------------------------------------------------------------------------- @@ -128,32 +128,32 @@ def test_grid_power_is_not_negated(fields: PanelFields) -> None: is what consumption means there. Applying the circuit rule would invert every grid figure while leaving it entirely plausible.""" raw = _device("lugs-upstream").get_property("meter", "active-power") - assert raw == "-5847.0" + assert raw == "-4617.0" - assert fields.instant_grid_power_w == -5847.0 + assert fields.instant_grid_power_w == -4617.0 def test_main_meter_energy_maps_imported_to_consumed(fields: PanelFields) -> None: """Opposite of a circuit: the panel imports from the grid, so imported energy is what the house consumed.""" upstream = _device("lugs-upstream") - assert upstream.get_property("meter", "imported-energy") == "44.05" + assert upstream.get_property("meter", "imported-energy") == "64.55" - assert fields.main_meter_energy_consumed_wh == pytest.approx(44.05, rel=1e-4) - assert fields.main_meter_energy_produced_wh == pytest.approx(97.45, rel=1e-4) + assert fields.main_meter_energy_consumed_wh == pytest.approx(64.55, rel=1e-4) + assert fields.main_meter_energy_produced_wh == pytest.approx(76.95, rel=1e-4) def test_per_phase_currents(fields: PanelFields) -> None: """Lugs expose `current-a`/`current-b`; circuits expose a single `current`. Same capability type, different property set.""" - assert fields.upstream_l1_current_a == pytest.approx(46.4666, rel=1e-4) + assert fields.upstream_l1_current_a == pytest.approx(56.7166, rel=1e-4) assert fields.upstream_l2_current_a == pytest.approx(46.4749, rel=1e-4) - assert fields.downstream_l1_current_a == pytest.approx(46.4666, rel=1e-4) + assert fields.downstream_l1_current_a == pytest.approx(56.7166, rel=1e-4) def test_feedthrough_comes_from_the_downstream_lugs(fields: PanelFields) -> None: - assert fields.feedthrough_power_w == -5847.0 - assert fields.feedthrough_energy_consumed_wh == pytest.approx(44.05, rel=1e-4) + assert fields.feedthrough_power_w == -4617.0 + assert fields.feedthrough_energy_consumed_wh == pytest.approx(64.55, rel=1e-4) def test_lugs_are_found_by_declared_direction_not_device_id() -> None: diff --git a/tests/test_schema_one_snapshot.py b/tests/test_schema_one_snapshot.py index a54a04b..9521f54 100644 --- a/tests/test_schema_one_snapshot.py +++ b/tests/test_schema_one_snapshot.py @@ -34,7 +34,7 @@ def test_roles_are_sorted_by_declared_type_not_device_id() -> None: what the schema defines.""" roles = TreeRoles(_children()) - assert len(roles.circuits) == 5 + assert len(roles.circuits) == 6 assert len(roles.lugs) == 2 assert len(roles.evse) == 2 assert roles.bess is not None and roles.bess.device_id == "bess" @@ -51,7 +51,7 @@ def test_snapshot_carries_panel_identity(snapshot: SpanPanelSnapshot) -> None: def test_every_real_circuit_is_present(snapshot: SpanPanelSnapshot) -> None: real = {cid for cid in snapshot.circuits if not cid.startswith("unmapped_tab_")} - assert len(real) == 5 + assert len(real) == 6 assert SOLAR_CIRCUIT in real assert snapshot.circuits[SOLAR_CIRCUIT].name == "Solar Inverter" @@ -89,7 +89,7 @@ def test_der_snapshots_are_populated(snapshot: SpanPanelSnapshot) -> None: def test_panel_and_lugs_values_reach_the_snapshot(snapshot: SpanPanelSnapshot) -> None: - assert snapshot.instant_grid_power_w == -5847.0 + assert snapshot.instant_grid_power_w == -4617.0 assert snapshot.power_flow_pv == -8500.0 assert snapshot.grid_state == "ON_GRID" assert snapshot.l1_voltage == 120.0 @@ -126,7 +126,7 @@ def test_an_unsizable_panel_yields_no_unmapped_positions() -> None: assert snapshot.panel_size == 0 assert not [cid for cid in snapshot.circuits if cid.startswith("unmapped_tab_")] # Real circuits survive — only the synthesised ones depend on the total. - assert len(snapshot.circuits) == 5 + assert len(snapshot.circuits) == 6 def test_a_panel_with_no_children_still_builds() -> None: diff --git a/uv.lock b/uv.lock index 930116a..4f97670 100644 --- a/uv.lock +++ b/uv.lock @@ -1043,7 +1043,7 @@ requires-dist = [{ name = "span-panel-api", editable = "." }] [[package]] name = "span-panel-api-schema-1" -version = "1.1.1" +version = "1.1.2" source = { editable = "packages/schema-1" } dependencies = [ { name = "ebus-sdk" },