Repository navigation
Add read-only SAJ eManager C1 Pro driver - #210
Merged
Merged
Conversation
The community register sheet does not state sign, word order, or whether phase voltage and frequency are summed. Control stays off until a named eManager proves a held zero. Signed-off-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: David Mozart <davmoz@users.noreply.github.com>
Generation stays negative. Battery charge is positive and discharge is negative; the H2 wire is the other way round, so 0x8491 is negated. Grid import stays positive. Vendor-only words are not DER fields. Signed-off-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: David Mozart <davmoz@users.noreply.github.com>
This was referenced Nov 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
saj-emanager0.1.1, a community, read-only driver for a SAJ eManager C1 Pro sitting in front of 1..N CHS2/CH2 C&I hybrids (the intended first site is Castellum Örebro, 3x CHS2 50 kW).It publishes aggregate PV, battery and grid telemetry over Modbus TCP. It does not write. Nothing in this change has been run on a controller.
Emitted signs match the rest of this repository (
docs/WRITING-A-DRIVER.md,drivers/lua/saj.lua):FTW's bundled copy does not move with this commit. Bump the pin and run
scripts/sync-bundled-drivers.shafter merge.Register sources
Decoded from
Modbus_registers_eManager.xlsx(eManager + SEC-C), posted by remkoteselle on stanus74/home-assistant-saj-h2-modbus discussion #182. SAJ-formatted, not an official SAJ publication. Sheets used:protocol: modbusbecause that is the only Modbus value the catalog allows; the description, connection default (port 502) and notes state TCP only.0x4A00device count, then per inverter SN (10 words) + id, stride 12, up to 21.0x848A–0x849Dsystem power,0x849E–0x84B5grid per phase,0x8500–0x854Fenergy counters.0x8400–0x8405, documented and not read or written.Cross-checks, not the map this driver reads:
xeno27/openemsfeat: bind first public-source package candidates #2), from SAJ "Protocol for CH2 remote EMS control". Same0x8400–0x8405block. Direct CH2 realtime is0x7B04–0x7C88, which this map does not use. Not hardware-validated there either.drivers/lua/saj.lua1.2.0, for H2 wire signs and high-word-first 32-bit values.What it emits
Holding FC 0x03. A failed or short read leaves the field out. It is not reported as 0.
TotalPVPower(0x848F, uint32 W). The register is a magnitude. The driver negates it, so generation is negative, as in the other PV drivers. Lifetime generation from0x8506(×0.01 kWh → Wh).TotalBatteryPower(0x8491). The H2 wire is discharge-positive, so this driver negates it: charging is positive, discharging is negative. Emitted only when capacity, SoC or battery power says a pack is there. SoC is ×0.01% → a 0..1 fraction (7325→ 0.7325). Charge/discharge totals from0x850E/0x8516.TotalGridPowerWatt(0x8496) plus R/S/T voltage, current, frequency and power at0x849E. The H2 grid word is already import-positive, so these watts are not negated: import is positive, export is negative. Phase current uses the same direction (positive into the site).0x848D), load (0x8493) and generator (0x8498) words have no stated sign. They are*_vendormetrics, not DER fields.DeviceNumis0x4A00; only that many slots are read (stride 12, max 21). The ID15 reserved decimal 19234 disagrees with hex4AB4(19124); the stride follows the hex.0x8550overlaps the reserve at0x8551and is not read.Assumptions
0x8491is discharge-positive (H20x40A6, and the0x8405setpoint text). One negation produces the site sign. Unverified on this aggregate register.0x8496and the phase watt registers is import-positive (H2SysGridPowerWall). No negation. Unverified on this aggregate register.TotalPVPoweris an unsigned magnitude.FeedIn(0x852E) andSell(0x8536) are vendor metrics, not meter import/export. SAJ uses both words both ways.DeviceNumonly when the sheet's own scale is implausible for one phase (above ~320 V or ~70 Hz) and the quotient lands in a normal band (~180..280 V, ~45..65 Hz). A 230 V / 50 Hz reading is left alone even with three inverters. A 690 V / 150 Hz reading withDeviceNum= 3 becomes 230 V / 50 Hz. Power, current, energy and rated power are never divided. The sheet does not document a sum; Eniris does.Control — not implemented
0x8400EMSEnable,0x8401EMSKeepTime (heartbeat, default 10 s, timeout outputs 0),0x8402EMSINVPRef,0x8403/0x8404current limits (sheet: "Temporarily Closed"),0x8405EMSBATPRef.Setpoint scaling is unverified. Range on the sheet is ±11000, magnification -4, unit %. ×10^-4 makes 10000 = 1.00 and 11000 = 1.10, i.e. ±110% of rated at 0.01 percentage points per step. The device text and OpenEMS CH2 both say a positive setpoint discharges (the opposite of the site sign). This driver does not write, so that device sign is not applied to telemetry. SEC-C reuses
0x8400–0x8404as an anti-backflow mode with different meanings.Open questions to verify at Castellum Örebro
3x CHS2 50 kW behind one eManager C1 Pro. Compare against the site meter and the elekeeper/app figures.
saj_emanager_phase_scale_divisoris 1 when the sheet scale was published and 3 when it was divided.0x8496positive on the wire? This driver publishes that word unchanged, so a positive wire word is an import.0x8491negative on the wire? This driver negates it, so a negative wire word is published as a positive charge. If the wire is already positive while charging, the negation is wrong.paralltotalcap(0x849A) non-zero, and what does unit "K" actually mean? A PV-only eManager must stay silent on the battery DER.FeedInandSellis import?DeviceNum3?0x848B: 150 kW (sum) or 50 kW (one inverter)?0x8400until held zero, charge, discharge, heartbeat refresh and a release back to 0 are shown on this firmware. Confirm it is the EMS map and not the SEC-C anti-backflow map. A future write has to translate site charge-positive into the device's discharge-positive setpoint.Driver evidence
make test-driver ID=saj-emanager,make check)Safety
Control evidence
Not applicable.
driver_commandrefuses every action exceptinit/deinit, anddriver_default_modedoes not write.0x8400is not read.required: not applicable (read-only)Checks
Signed-off-by.make test-driver ID=saj-emanagermake check