Skip to content

Add Depth of Discharge configuration support for Venus devices#307

Merged
tomquist merged 6 commits into
developfrom
claude/focused-noether-b2toc
Jun 2, 2026
Merged

Add Depth of Discharge configuration support for Venus devices#307
tomquist merged 6 commits into
developfrom
claude/focused-noether-b2toc

Conversation

@tomquist
Copy link
Copy Markdown
Owner

@tomquist tomquist commented Jun 2, 2026

Summary

This PR adds support for reading and configuring the Depth of Discharge (DoD) setting on Venus devices through a new discharge-depth command entity. The feature allows users to adjust the battery's maximum depth of discharge between 30-88% via Home Assistant.

Key Changes

  • New CommandType: Added DEPTH_OF_DISCHARGE = 56 command type to the Venus device command enum
  • DoD Constants: Defined DOD_MIN = 30 and DOD_MAX = 88 based on Marstek app limits with a note that these may change over time
  • Message Parsing: Implemented bidirectional translation for the DoD value:
    • Device encodes the maximum (88%) as 0, which is translated back to 88% when reading
    • When sending commands, 88% is encoded as 0 for the device
  • Home Assistant Integration:
    • Added depthOfDischarge field to VenusDeviceData type
    • Created a number component with min/max constraints (30-88%, step 1)
    • Implemented discharge-depth command handler with input validation
    • Entity is only advertised when the device reports the DoD value (conditional discovery)
  • Documentation: Updated venus.md with new section describing the DoD command protocol and parameters
  • Test Coverage: Added comprehensive test cases covering:
    • Valid DoD values (30%, 70%, 86%, 88%)
    • Invalid values (below minimum, above maximum)
    • Sentinel value handling (0 → 88%)
    • Parser tests for both normal and sentinel DoD values
    • Discovery config gating based on data presence

Implementation Details

  • Input validation ensures values are within the 30-88% range before sending to device
  • The special case where DoD_MAX (88%) is encoded as 0 is handled transparently in both directions
  • Discovery config uses a conditional gate to only advertise the entity when depthOfDischarge is present in device state, preventing unnecessary Home Assistant entities for devices that don't support this feature

https://claude.ai/code/session_01TWXXbfjrDmatHtpZKUfktK

Summary by CodeRabbit

  • New Features

    • Added Depth of Discharge support for Venus battery devices: read and configure DOD (30–88%) via MQTT; advertised only for devices that report it.
  • Documentation

    • Updated user docs and README with new "discharge-depth" command details and usage.
  • Tests

    • Added unit tests covering parsing, command handling, discovery config generation, and input validation for DOD.

claude added 3 commits June 2, 2026 07:24
Venus devices report a depth-of-discharge (dod) value in their runtime
info but it was neither published nor configurable. Add a Depth of
Discharge number entity and a discharge-depth command (cd=56,dod=%d),
matching the Jupiter implementation. The entity is only advertised on
devices that actually report the value and is limited to the 30-88%
range exposed by the Marstek app.

Fixes #306
Sniffing the app traffic (issue #306) confirmed the command is
cd=56,dod=%d with a minimum of 30, but the maximum of 88% is sent to
the device as dod=0 rather than dod=88. Translate the sentinel in both
directions: send 0 when the requested value is the maximum, and report
a received dod=0 back as 88%.
Return undefined (defer) instead of false from the enabled predicate
when the dod field is absent, so the entity is only published once the
device actually reports a depth of discharge value, matching the
HMI inverter PV3/PV4 gating convention. Returning false would have
actively cleared the entity instead.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 73e5eb6f-c73c-4bf3-a25f-d9adfaa1b5ea

📥 Commits

Reviewing files that changed from the base of the PR and between a291a2e and 58af33e.

📒 Files selected for processing (5)
  • src/device/venus.ts
  • src/deviceCommands.test.ts
  • src/deviceDefinition.ts
  • src/generateDiscoveryConfigs.test.ts
  • src/parser.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/generateDiscoveryConfigs.test.ts
  • src/device/venus.ts

Walkthrough

This PR adds Venus depth-of-discharge control by introducing a depthOfDischarge field to device state, implementing parsing with special device encoding (where 0 represents 88%), publishing a Home Assistant sensor, and adding a discharge-depth command with range validation (30–88%) and inverse encoding for device transmission.

Changes

Venus Depth-of-Discharge Support

Layer / File(s) Summary
Data types and command definitions
src/types.ts, src/device/venus.ts
VenusDeviceData extends with optional depthOfDischarge field; CommandType enum gains DEPTH_OF_DISCHARGE with DOD_MIN/DOD_MAX constants (30–88%).
Device implementation and state management
src/device/venus.ts
Venus publishes dod sensor reading depthOfDischarge, maps device encoding where raw 0 = 88%; discharge-depth command handler validates integer range, updates state, and encodes 88% back to 0 for device transmission.
Transform typedef update
src/deviceDefinition.ts
Adds deprecated exported TransformFn<Args,R> alias and updates TransformSpec to use it for legacy function-based transforms.
Command generation and encoding tests
src/deviceCommands.test.ts
Tests validate discharge-depth MQTT payload generation for valid inputs (30/70/86/88, with 88 -> dod=0) and assert no output for out-of-range or malformed inputs.
Input parsing and value mapping tests
src/parser.test.ts
Parser tests verify dod=88 parses to depthOfDischarge=88 and dod=0 is treated as unknown (no depthOfDischarge).
Home Assistant discovery configuration test
src/generateDiscoveryConfigs.test.ts
Test ensures depth_of_discharge discovery config is only generated when depthOfDischarge exists and that the config has { min: 30, max: 88, step: 1 }.
Documentation and changelog
CHANGELOG.md, README.md, docs/venus.md
Changelog and README document the discharge-depth command; docs/venus.md adds section "Set depth of discharge" with publish topic, payload cd=56,dod=%d, parameter table, and dod device info entry.

Sequence Diagram

sequenceDiagram
  participant Device as Battery Device
  participant Parser as MQTT Parser
  participant VenusImpl as Venus Implementation
  participant HA as Home Assistant
  participant User as Command Source

  Device->>Parser: publishes "dod=0" or "dod=88"
  Parser->>VenusImpl: parsed message (dod -> depthOfDischarge)
  VenusImpl->>HA: publish dod sensor (depthOfDischarge%)

  User->>VenusImpl: discharge-depth command (e.g. 75)
  VenusImpl->>VenusImpl: validate 30≤value≤88
  VenusImpl->>VenusImpl: update depthOfDischarge
  VenusImpl->>Device: publish "cd=56,dod=75" (or "dod=0" if 88)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • tomquist/hm2mqtt#233: Related Venus parsing/transform changes and prior discharge-depth handling adjustments.
  • tomquist/hm2mqtt#241: Similar Venus device-layer range validation changes for other numeric fields.
  • tomquist/hm2mqtt#234: Overlapping device-commands test additions covering discharge-depth.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and clearly summarizes the main change: adding Depth of Discharge (DoD) configuration support for Venus devices, which is the primary feature across all modified files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/focused-noether-b2toc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@tomquist tomquist marked this pull request as ready for review June 2, 2026 09:33
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/generateDiscoveryConfigs.test.ts (1)

357-363: ⚡ Quick win

Assert the advertised DoD range contract too.

This verifies gating, but not the 30..88 / step: 1 number metadata that Home Assistant uses to constrain the control. A drift there would let the UI offer values that the Venus handler rejects.

Small assertion add-on
     const withDod = dodConfigs({ depthOfDischarge: 88 });
     expect(withDod).toHaveLength(1);
     expect(withDod[0].config).not.toBeNull();
+    expect(withDod[0].config).toMatchObject({
+      min: 30,
+      max: 88,
+      step: 1,
+    });
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/generateDiscoveryConfigs.test.ts` around lines 357 - 363, Add an
assertion that the advertised DoD range contract carries the expected numeric
bounds and step: after creating withDod = dodConfigs({ depthOfDischarge: 88 }),
inspect withDod[0].config (the advertised DoD control/contract) and assert its
metadata contains minimum 30, maximum 88 and step 1 (e.g., contract.range or
properties.range fields that describe the 30..88 step:1 constraint) so the test
verifies both presence and the exact numeric constraints used by Home Assistant.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/venus.md`:
- Around line 423-430: Add language identifiers to the two fenced code blocks
containing the MQTT topic "hame_energy/{type}/App/{uid or mac}/ctrl" and the
payload "cd=56,dod=%d" in docs/venus.md to satisfy markdownlint MD040; update
both opening backtick fences to include a language tag (e.g., "text") so they
become fenced code blocks like ```text.

In `@src/device/venus.ts`:
- Around line 1226-1235: The transform for the 'dod' field currently maps the
sentinel 0 to DOD_MAX but otherwise accepts any integer; update the transform
for the field with key 'dod' (path ['depthOfDischarge']) to first parse the
value and map 0 to DOD_MAX, then validate that the resulting dod lies within
DOD_MIN..DOD_MAX and return undefined for parse failures or out-of-range values;
in short: decode sentinel 0, then drop (return undefined) any dod < DOD_MIN or >
DOD_MAX.

In `@src/deviceCommands.test.ts`:
- Around line 744-787: Add test cases to src/deviceCommands.test.ts for
malformed discharge-depth payloads (e.g., "88foo", "30.5", "", " 70 ") and
update the Venus handler in src/device/venus.ts to reject any non-exact-numeric
payload rather than using parseInt(message, 10). In the Venus handler function
that processes the "discharge-depth" command, validate the message with a strict
numeric check (e.g., match full string to an integer pattern), then enforce the
30–88 range and still map 88 to 0 before publishing; return null/ignore when
validation fails so the new tests pass.

---

Nitpick comments:
In `@src/generateDiscoveryConfigs.test.ts`:
- Around line 357-363: Add an assertion that the advertised DoD range contract
carries the expected numeric bounds and step: after creating withDod =
dodConfigs({ depthOfDischarge: 88 }), inspect withDod[0].config (the advertised
DoD control/contract) and assert its metadata contains minimum 30, maximum 88
and step 1 (e.g., contract.range or properties.range fields that describe the
30..88 step:1 constraint) so the test verifies both presence and the exact
numeric constraints used by Home Assistant.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bbce137b-0638-4417-82bc-708cf010a74f

📥 Commits

Reviewing files that changed from the base of the PR and between e58d538 and a291a2e.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • README.md
  • docs/venus.md
  • src/device/venus.ts
  • src/deviceCommands.test.ts
  • src/generateDiscoveryConfigs.test.ts
  • src/parser.test.ts
  • src/types.ts

Comment thread docs/venus.md
Comment on lines +423 to +430
```
hame_energy/{type}/App/{uid or mac}/ctrl
```

Payload:
```
cd=56,dod=%d
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add fenced code block language identifiers to satisfy markdownlint (MD040).

Line 423 and Line 428 use fenced blocks without language tags; this triggers current static-analysis warnings.

Suggested doc-only fix
 Topic:
-```
+```text
 hame_energy/{type}/App/{uid or mac}/ctrl

Payload:
- +text
cd=56,dod=%d

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 423-423: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 428-428: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/venus.md` around lines 423 - 430, Add language identifiers to the two
fenced code blocks containing the MQTT topic "hame_energy/{type}/App/{uid or
mac}/ctrl" and the payload "cd=56,dod=%d" in docs/venus.md to satisfy
markdownlint MD040; update both opening backtick fences to include a language
tag (e.g., "text") so they become fenced code blocks like ```text.

Comment thread src/device/venus.ts Outdated
Comment thread src/deviceCommands.test.ts
claude added 3 commits June 2, 2026 09:58
- Reject out-of-range values when decoding the dod field (after mapping
  the 0 sentinel to the maximum), returning undefined instead of
  publishing bogus percentages.
- Require an exact integer payload for the discharge-depth command so
  malformed payloads (e.g. "88foo", "30.5", "", " 70 ") are ignored
  rather than silently coerced by parseInt.
- Extend tests to cover the malformed payloads and assert the advertised
  number entity carries the 30..88 step:1 range.
Replace the function-based dod transform with the declarative
chain(number(), inRange(...)), matching the existing mcp_w field. The
device reports the actual percentage in its runtime data, so the read
path only needs numeric parsing and range validation; the 0 sentinel
for the maximum applies only to the write direction, which stays in the
discharge-depth command handler. Declarative transforms are also
introspectable for Home Assistant Jinja2 template generation.
Extract the function form of TransformSpec into a named TransformFn type
annotated with @deprecated, so the compiler and IDEs flag any
function-based field transforms. Declarative transforms should be used
instead since they can be introspected for Home Assistant Jinja2
template generation and serialized.
@tomquist tomquist merged commit 17c4f92 into develop Jun 2, 2026
13 checks passed
@tomquist tomquist deleted the claude/focused-noether-b2toc branch June 2, 2026 10:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants