node-red-contrib-solarman-devices 1.0.4
Use Solarman V5 compatible devices (inverters, loggers) local in NodeRed
node-red-contrib-solarman-devices
Support for Solarman V5 compatible devices in Node-RED.
The goal of the project is to read and write Modbus registers on local Solarman V5 devices - typically a WiFi/LAN logger stick (e.g. an LSW3) in front of a Deye/V-TAC hybrid inverter - directly over the local network, without going through the manufacturer's cloud.
Getting Started
Prerequisites: Node-RED installation. For details see here.
Install via npm
$ cd ~/.node-red
$ npm install node-red-contrib-solarman-devices
then restart node-red
The device is represented by a config node (solarman-device, one per physical device). Two flow node types reference it: solarman-register for raw, ad-hoc register access (good for discovering/scanning a device's registers), and solarman-proxy for named, schema-driven access once you know which registers you need (good for dashboards and automations).
First steps
Add a solarman-register node from the Solarman palette group and open it. Under "Device", add a new solarman-device config node. If you don't know the device's IP, MAC or logger serial number yet, click Scan network for Solarman devices first and pick it from the discovered list - otherwise fill in:
- Host - the device's current IP address (e.g.
192.168.1.100) - Port -
8899(Solarman V5 default) - Logger serial - the logger's serial number, from its sticker or web UI
- Unit address - the Modbus unit/slave address of the inverter (commonly
1) - MAC - optional; if left empty while Host has a value, it is looked up automatically from the ARP table when you open the dialog. Once set, the device's current IP is re-resolved via ARP on every connect/reconnect, so it keeps working across DHCP reassignments. Enable "Disable ARP-based IP resolution" on systems without ARP support (e.g. containers without an
arp/ip neighborcommand) to always use the fixed Host instead.
Back in the solarman-register node, choose the Operation (readHolding, readInput, writeSingle, writeMultiple), the Register address and Quantity. All of these can be overridden per message via msg.operation, msg.register, msg.quantity, msg.value/msg.values.
Usage
The node's status reflects the underlying solarman-devices Device connection: green (connected), yellow (connecting), red (error/disconnected). On msg input, it performs the configured (or overridden) read/write and sends the result on msg.payload; on a real Modbus exception (e.g. "Illegal Data Address" for a register that doesn't exist on this device) the node reports an error instead of crashing the flow.
Since the exact register map differs between manufacturers/models, discovering which registers are actually implemented on your specific device is normally a matter of scanning a range of candidate addresses with both readHolding and readInput - a function node driving a loop of msg.register/msg.operation values into a solarman-register node works well for this.
Once you know which registers you care about, open the solarman-device config node's Registers tab. This is backed by a reusable model file under resources/models/ (a JSON array of {name, register, registerType, access, quantity, description, typeSpec}) - only that file's path is saved on the node, the register list itself is always read fresh from it. The table is locked (read-only) whenever the dialog opens - tick Edit above it to change a field, add/remove a row, or reorder rows; this only guards against accidental changes and doesn't affect Customize, Save as..., Export or Import.
- No model file yet for your device? Just add rows by hand (the tab's own add/remove controls), then click Save as... to write them out as a new file under
resources/models/and point this node at it - that's how a model gets created in the first place. - Already have (or found) a model file? Pick it from the Model dropdown - it loads immediately, no separate Load step. Models can only ever be saved under
resources/models/, so there's no free-text path to type or paste. Export/Import are a lower-friction alternative for a one-off file that doesn't need to live underresources/models/yet. - Click Save as... any time to write the current table out as a new file (it always refuses to overwrite an existing name, so it's a safe way to save a variant under a new name without touching the original) and switch the node to it.
- Click Customize to test the currently loaded/edited table against the live device automatically (needs the device node already deployed and connected): every register is read individually, deliberately not bulk-read in address blocks - a single unsupported/reserved register would otherwise be able to take a whole block of otherwise-good registers down with it (a real case: a documented block containing the battery SoC register, among many others, failed entirely because of one unrelated bad register elsewhere in the same block). Survivors are re-read a few times to tell genuinely live data apart from a register that merely answers without erroring but never actually changes. Registers that don't respond are removed from the table; if the scan meaningfully narrowed things down, you're prompted to save the result as its own model. Shows real progress (phase + N/total, and a progress bar) since a full-model scan can take a while; reopening the Registers tab resumes watching a scan still running from an earlier session, or reports a result that finished while nobody was watching.
- Editing the table without an explicit Save (manual or Customize-triggered) is not persisted - closing the dialog reverts to whatever the model file currently contains.
A register's Type (holding/input) can be left (unknown) instead of guessed - useful when your source (a manufacturer PDF, a name-based guess) only tells you read/write access, not the actual Modbus function code. Customize determines it for real: readwrite/write registers are always holding (input registers can't be written, so no test is needed), and read-only registers are tested against both function codes, keeping whichever one actually responds (or flagging it if, unusually, both do).
A register's typeSpec (edited via the pencil button) isn't just documentation - solarman-proxy applies it to every read: a 'value' typeSpec combines multi-register 32-bit values (S32/U32) into a real signed/unsigned number and applies scale (e.g. a 2-register 0.1W-resolution reading arrives as a proper Watt number, not a raw [highWord, lowWord] pair), 'bool'/'enum'/'bitmap'/'string' decode accordingly. A register with no typeSpec (or typeSpec 'raw') is unaffected - still the plain register value, or the raw array for a multi-register one.
A solarman-proxy node referencing that device can then read/write registers by name: send { topic: 'zeroExportPower' } to read, or { topic: 'zeroExportPower', payload: 500 } to write - no register numbers in your flow logic. Each solarman-proxy node independently picks, from a compact name/access list of the device's registers, which ones get their own dedicated output in "dedicated output per selected register" mode (different proxy nodes on the same device can make different choices) - useful for wiring straight into dashboard nodes without a switch node in between.
solarman-device also has an optional, off-by-default system resource diagnostics toggle (Connection tab): watches the whole host's CPU/memory, not just this Node-RED process, and - only once usage stays above a configurable threshold for several consecutive checks in a row, not on a single spike - logs it and shows it on every solarman-register/solarman-proxy node referencing that device (a config node has no status ring of its own). Useful on a resource-constrained host (e.g. a Victron GX device) to catch a heavy scan/test session before it starves the host's own other functionality, with a record of when it happened for later analysis.
Each solarman-proxy node can also define its own Poll timers - a named, reusable poll cadence (e.g. "fast" -> 5000ms) rather than a raw number per register, so a handful of cadences cover any number of registers - and assign one per register in the checklist's Poll column. This lives entirely on the proxy, not on the device/model: how often a register gets auto-polled is a property of this specific output, not of what the register means, so different proxy nodes on the same device can poll the same register at different rates (or not at all). A register left without one is only ever read on command; independently of, and in addition to, the proxy's own pollInterval field (a single interval that polls every readable register).
Changelog
Disclaimer
The software is provided as-is under the MIT license. The author cannot be held responsible for any unintended behaviours.
The register model files under resources/models/ were compiled independently (through protocol/bus analysis and reverse engineering for interoperability purposes) and are provided as examples only, without any claim to correctness or completeness - verify against your own device before relying on them. This project is not affiliated with, endorsed by, or associated with Solarman or any inverter/logger manufacturer; any such names are used purely descriptively, to identify device compatibility.
Thanks
If you like our ideas and want to support further development, you can donate here:
Please understand that requests from those who support us will be given priority