Skip to content

Add Venus A (VNSA) PV input support and fix BMS scaling#309

Merged
tomquist merged 4 commits into
developfrom
claude/adoring-dirac-cEVOb
Jun 2, 2026
Merged

Add Venus A (VNSA) PV input support and fix BMS scaling#309
tomquist merged 4 commits into
developfrom
claude/adoring-dirac-cEVOb

Conversation

@tomquist
Copy link
Copy Markdown
Owner

@tomquist tomquist commented Jun 2, 2026

Summary

This PR adds support for per-string PV input monitoring on Venus A (VNSA) devices and fixes incorrect scaling of BMS voltage and temperature values across all Venus variants.

Key Changes

  • Venus A PV Input Support: Added exposure of per-string PV input power (PV1–PV4) and their connection status, plus a combined Total PV Power sensor for VNSA devices

    • Implemented new venusPvField transform to parse Venus PV input format ("POWER|CONNECTED")
    • Added sum transform with optional scale parameter to calculate total PV power across all inputs
    • PV power values are converted from deciwatts to watts
  • BMS Voltage Scaling: Fixed battery voltage and charge voltage to be reported in volts instead of raw centivolt/decivolt values

    • Battery voltage: centivolts → volts (e.g., 4328 → 43.28 V)
    • Charge voltage: decivolts → volts (e.g., 468 → 46.8 V)
  • Venus A BMS Temperature Scaling: Fixed cell and MOSFET temperatures for VNSA to be scaled by factor of 10 (they were previously 10× too high)

    • Applied only to VNSA devices; other Venus variants already report correct values
  • Device Definition Refactoring: Separated VNSA device registration from other Venus variants (HMG, VNSE3, VNSD) to support variant-specific field configurations

Implementation Details

  • Added optional parameters to registerRuntimeInfoMessage() and registerBMSInfoMessage() to enable variant-specific field registration
  • Extended SumTransform interface to support optional scale divisor
  • Implemented executeVenusPvField() and generateVenusPvFieldJinja2() for transform execution and template generation
  • Added comprehensive test coverage for new transforms and parsing of real VNSA device data

https://claude.ai/code/session_01L5QLHLxrBV8zqDip55aKow

Summary by CodeRabbit

  • New Features

    • Added Venus A PV input power sensors (per-string PV1–PV4) and a Total PV Power sensor
    • Added PV input connection status sensors
    • Added Venus Depth of Discharge number entity
  • Bug Fixes

    • Venus A temperature values now correctly scaled to °C
    • Venus voltage values now correctly scaled to volts
  • Documentation

    • Updated changelog with Venus device and PV sensor details

@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: 58c6764a-8e3e-49c0-bfdd-0853254befbd

📥 Commits

Reviewing files that changed from the base of the PR and between 81bf944 and b02f14e.

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

Walkthrough

Adds Venus A (VNSA) device variant parsing with per-string PV input sensors, aggregated total PV power, and conditional BMS temperature scaling. Extends transforms with venusPvField and scaled sum, updates device registration wiring, adds types and tests, and updates CHANGELOG.

Changes

Venus A (VNSA) variant with PV input and temperature scaling support

Layer / File(s) Summary
Transform contract for PV fields and scaled sums
src/transforms.ts
Adds VenusPvFieldTransform interface and venusPvField() factory; extends SumTransform with scale?: number and updates sum() to accept an optional scale.
Transform execution and Jinja2 wiring
src/transforms.ts
Implements runtime executeTransform/executeMultiKeyTransform branches and Jinja2 generation for venusPvField and scaled sum. Adds executeVenusPvField and generateVenusPvFieldJinja2.
Data type extensions for PV properties
src/types.ts
Extends VenusDeviceData with optional pv1Powerpv4Power, pv1Connectedpv4Connected, and totalPvPower.
Device registration split and runtime options
src/device/venus.ts
Splits registration: HMG/VNSE3/VNSD default vs VNSA-specific registration. registerRuntimeInfoMessage gains { withPvInputs?: boolean }; registerBMSInfoMessage gains { scaleTemperatures?: boolean }.
PV sensor and BMS temperature scaling registration
src/device/venus.ts
When withPvInputs is enabled registers per-PV power/connectivity sensors using venusPvField and a totalPvPower sensor using sum(10). Rewrites BMS field descriptors to allow per-field transforms and conditionally applies divide(10) to temperature fields and MOSFET when scaleTemperatures is enabled.
Transform and Venus parser test coverage
src/transforms.test.ts, src/parser.test.ts
Adds tests for venusPvField behavior and Jinja2 templates, extends sum tests for sum(10) scaling, and adds parser tests validating VNSA PV exposure and BMS temperature/voltage scaling differences between VNSA and VNSE3.
Changelog documentation
CHANGELOG.md
Documents Venus A PV input sensors (PV1–PV4 power, connection status, total power) and fixes for BMS/charge voltage scaling and Venus A temperature scaling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • tomquist/hm2mqtt#233: Main PR extends the declarative transform framework from #233, building on the sum transform and adding venusPvField that plug into the same execution and Jinja2-generation paths.
  • tomquist/hm2mqtt#182: Both PRs extend src/device/venus.ts to handle Venus device variants, with the main PR adding VNSA-specific runtime PV parsing and BMS scaling on top of VNSE3 support from #182.
  • tomquist/hm2mqtt#213: Main PR builds on VNSA variant routing from #213 by adding VNSA-specific runtime/PV sensor registration and BMS temperature scaling logic.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% 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 summarizes the two main changes in the PR: adding Venus A PV input support and fixing BMS scaling issues.
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/adoring-dirac-cEVOb

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.

claude added 3 commits June 2, 2026 13:13
Venus A (VNSA) reports per-string PV input power (pv1-pv4 in the form
power|connected) that was never parsed, so no PV sensors appeared in
MQTT/Home Assistant. Add PV1-PV4 power sensors, per-input connection
binary sensors and a combined Total PV Power sensor for VNSA.

The BMS battery voltage and charge voltage were published as raw
centivolt/decivolt values (e.g. 4328 instead of 43.28 V); scale them to
volts for all Venus variants. Venus A additionally encodes the cell and
MOSFET temperatures 10x too high (164 instead of 16.4 C); scale those
down for VNSA only, since the other Venus variants already report whole
degrees.

Fixes #218

https://claude.ai/code/session_01L5QLHLxrBV8zqDip55aKow
Replace the function-based field transforms added for the Venus A PV
inputs with declarative transforms, which are introspectable and
serializable to Jinja2 (function transforms are legacy).

Add a declarative venusPvField('power' | 'connected') transform that
extracts a field from the Venus PV input string ("<power>|<connected>"),
mirroring the existing mpptPvField transform, and use sum() for the
combined Total PV Power sensor.

https://claude.ai/code/session_01L5QLHLxrBV8zqDip55aKow
Confirmed against the decompiled Marstek app (v1.6.64,
VenusPVModel in venus_realTime_controller.dart): each PV input value
"<power>|<connected>" stores parts[0] * 0.1 as pvNInverterValue, i.e.
the power is reported in deciwatts. So pv1=1076 is 107.6 W, not 1076 W.

- venusPvField('power') now divides the leading value by 10
- add an optional scale divisor to the sum() transform and use sum(10)
  for the Total PV Power sensor

https://claude.ai/code/session_01L5QLHLxrBV8zqDip55aKow
@tomquist tomquist force-pushed the claude/adoring-dirac-cEVOb branch from 25de1a9 to 81bf944 Compare June 2, 2026 13:14
@tomquist tomquist marked this pull request as ready for review June 2, 2026 13:21
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.

🧹 Nitpick comments (2)
src/parser.test.ts (1)

695-710: 💤 Low value

Optional: assert b_tem (battery temperature) stays unscaled for VNSA.

b_tem is the one temperature field without scaleTemperatures, so it diverges from b_tp*/b_mot. Adding an explicit assertion (e.g. b_tem=16temperature 16, not 1.6) would lock in that intentional distinction and guard against an accidental future blanket-scaling change.

🤖 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/parser.test.ts` around lines 695 - 710, Add an assertion to lock in that
the raw b_tem field remains unscaled: after parsing with parseMessage in the
'scales Venus A (VNSA) BMS...' test, assert that the battery temperature coming
from b_tem equals 16 (e.g. expect(result.bms?.temperature).toBe(16)), ensuring
the b_tem value is not divided by any temperature scaling applied to
b_tp*/b_mot.
src/device/venus.ts (1)

234-323: 💤 Low value

Consider extracting PV field registration into a loop.

The per-string PV power and connectivity registrations repeat the same pattern for pv1–pv4. A loop similar to the cell voltage registration (lines 1418–1431) would reduce duplication and make future additions easier.

♻️ Optional refactor using a loop
     // PV / solar input information
     if (withPvInputs) {
-      // Per-string PV input power (pv1..pv4 = "<power>|<connected>")
-      field({ key: 'pv1', path: ['pv1Power'], transform: venusPvField('power') });
-      advertise(
-        ['pv1Power'],
-        sensorComponent<number>({
-          id: 'pv1_power',
-          name: 'PV1 Power',
-          device_class: 'power',
-          unit_of_measurement: 'W',
-          state_class: 'measurement',
-        }),
-      );
-      field({ key: 'pv2', path: ['pv2Power'], transform: venusPvField('power') });
-      // ... similar for pv2, pv3, pv4 power and connected ...
+      // Per-string PV input power and connection status (pv1..pv4 = "<power>|<connected>")
+      for (let i = 1; i <= 4; i++) {
+        const key = `pv${i}`;
+        field({ key, path: [`pv${i}Power`], transform: venusPvField('power') });
+        advertise(
+          [`pv${i}Power`],
+          sensorComponent<number>({
+            id: `pv${i}_power`,
+            name: `PV${i} Power`,
+            device_class: 'power',
+            unit_of_measurement: 'W',
+            state_class: 'measurement',
+          }),
+        );
+        field({ key, path: [`pv${i}Connected`], transform: venusPvField('connected') });
+        advertise(
+          [`pv${i}Connected`],
+          binarySensorComponent({
+            id: `pv${i}_connected`,
+            name: `PV${i} Connected`,
+            device_class: 'connectivity',
+            icon: 'mdi:solar-power',
+            enabled_by_default: false,
+          }),
+        );
+      }
🤖 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/device/venus.ts` around lines 234 - 323, The PV power and connectivity
registrations are duplicated for pv1–pv4; refactor by iterating over an array of
PV identifiers (e.g., ['pv1','pv2','pv3','pv4']) and perform the repeated calls
to field(..., transform: venusPvField('power'/'connected')) and advertise(...)
inside that loop, using sensorComponent for power (id: `${pv}_power`) and
binarySensorComponent for connectivity (id: `${pv}_connected`); update
references to the advertised state keys (e.g., `${pv}Power`, `${pv}Connected`)
so the existing field and advertise calls (function names: field, advertise,
venusPvField, sensorComponent, binarySensorComponent) are created
programmatically instead of repeated.
🤖 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.

Nitpick comments:
In `@src/device/venus.ts`:
- Around line 234-323: The PV power and connectivity registrations are
duplicated for pv1–pv4; refactor by iterating over an array of PV identifiers
(e.g., ['pv1','pv2','pv3','pv4']) and perform the repeated calls to field(...,
transform: venusPvField('power'/'connected')) and advertise(...) inside that
loop, using sensorComponent for power (id: `${pv}_power`) and
binarySensorComponent for connectivity (id: `${pv}_connected`); update
references to the advertised state keys (e.g., `${pv}Power`, `${pv}Connected`)
so the existing field and advertise calls (function names: field, advertise,
venusPvField, sensorComponent, binarySensorComponent) are created
programmatically instead of repeated.

In `@src/parser.test.ts`:
- Around line 695-710: Add an assertion to lock in that the raw b_tem field
remains unscaled: after parsing with parseMessage in the 'scales Venus A (VNSA)
BMS...' test, assert that the battery temperature coming from b_tem equals 16
(e.g. expect(result.bms?.temperature).toBe(16)), ensuring the b_tem value is not
divided by any temperature scaling applied to b_tp*/b_mot.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cbc7c0f8-a13d-4042-a838-4ee94e5b4054

📥 Commits

Reviewing files that changed from the base of the PR and between 17c4f92 and 81bf944.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • src/device/venus.ts
  • src/parser.test.ts
  • src/transforms.test.ts
  • src/transforms.ts
  • src/types.ts

- Refactor the repeated pv1-pv4 power/connection field+advertise calls
  into a loop over a typed list of PV inputs (behaviour unchanged: same
  ids, names, paths and transforms).
- Add an assertion to the Venus A BMS test that the battery temperature
  (b_tem) stays unscaled at 16, guarding against the cell/MOSFET
  temperature scaling being applied to it.

https://claude.ai/code/session_01L5QLHLxrBV8zqDip55aKow
@tomquist tomquist merged commit a9e825a into develop Jun 2, 2026
13 checks passed
@tomquist tomquist deleted the claude/adoring-dirac-cEVOb branch June 2, 2026 15:00
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