solarman-devices
v1.0.4
Published
Library for communicating with Solarman V5 compatible devices (inverters, loggers) via Modbus RTU over TCP
Maintainers
Readme
solarman-devices
Lightweight Node.js library for talking to Solarman V5 compatible devices - typically a WiFi/LAN logger stick (e.g. an LSW3) in front of a Deye/V-TAC hybrid inverter - over TCP, reading and writing its Modbus RTU registers.
This project is used by the Node-RED node node-red-contrib-solarman-devices and can be incorporated into other Node.js projects.
Status
Active development. The protocol layer (SolarmanV5, ModbusRtu) and the TCP client (Device) are implemented and verified against real hardware, not just synthetic test data - see test/ for regression tests built from captured device traffic.
Features
- SolarmanV5 protocol framing - wraps/unwraps Modbus RTU frames into the Solarman V5 TCP envelope (start/length/control/sequence/serial header, checksum trailer).
- Modbus RTU - read holding registers (0x03), read input registers (0x04), write single register (0x06), write multiple registers (0x10), with proper CRC16 and Modbus exception decoding.
Deviceclient - manages the TCP connection lifecycle (connect, auto-reconnect, timeouts, one request in flight at a time), exposed as a small promise-based API.- MAC-based IP resolution - if a MAC address is configured, the current IP is looked up via ARP on every connect/reconnect, so the device keeps working across DHCP reassignments. Disable via
disableArpon systems without ARP support. - Network discovery -
Scanner/scan()broadcast the Hi-Flying-style UDP discovery probe and collect replies (IP, MAC, logger serial number) from Solarman V5 devices on the local network - useful for first-time setup when none of those are known yet.
Installation
npm install solarman-devicesQuick start
const { Device, scan } = require('solarman-devices')
// Discover devices on the local network (optional - skip if you already know
// the host/mac/serialNumber):
const found = await scan({ timeout: 2000 })
console.log(found) // [{ ip, mac, serialNumber }, ...]
const device = new Device({
host: '192.168.1.100', // fallback/initial IP - only used if mac is unset or ARP fails
port: 8899, // Solarman V5 default port
serialNumber: '1234567890', // logger serial number (device sticker / web UI)
unitAddress: 1, // Modbus unit/slave address of the inverter
mac: 'AA:BB:CC:DD:EE:FF', // optional - keeps working across DHCP IP changes
disableArp: false, // set true on systems without arp/ip neighbor support
timeout: 5000
})
device.on('status', (status) => console.log('status:', status))
device.connect()
// once status is 'connected':
const registers = await device.readHoldingRegisters(0, 2)
console.log(registers)
await device.writeSingleRegister(104, 500)
device.disconnect()API overview
SolarmanV5- the protocol envelope.wrapModbusFrame(modbusFrame)builds a request frame;unwrapModbusFrame(solarmanFrame)extracts the embedded Modbus RTU frame from a response.ModbusRtu- frame builder/parser for one unit address.requestReadHoldingRegisters/requestReadInputRegisters/requestWriteSingleRegister/requestWriteMultipleRegistersbuild requests; the matchingfetchXxxmethods parse responses and throwModbusErroron a real Modbus exception.Device- the TCP client tying both together:connect(),disconnect(),readHoldingRegisters(first, quantity),readInputRegisters(first, quantity),writeSingleRegister(register, value),writeMultipleRegisters(first, values). Emitsstatus(connecting/connected/disconnected/error).Scanner/scan()-Scanneris anEventEmitter(start()/stop(), emitsfindper device);scan(options)is a convenience one-shot wrapper that resolves with an array of{ ip, mac, serialNumber }afteroptions.timeout(default 2000ms).
Known limitations
- The Modbus RTU response sequence number is compared for diagnostics but not enforced - real V-TAC/LSW3 traffic has been observed with a checksum- and control-code-valid response that still didn't echo back the exact request sequence number, on nearly every single response. A mismatch is accepted rather than rejected, and logged only via
debugLog(the device node's Debug checkbox) rather than always-onwarn- on this kind of hardware it isn't an exceptional event worth flagging every time, just the normal case. - The exact byte layout of a response frame's body prefix (as opposed to the request side, which mirrors the real pysolarmanv5 construction field-for-field) is only known empirically, not derived from a documented spec - see the comment at the top of
lib/solarman_v5.js.
Development & Tests
npm testContributing
PRs welcome. Please follow the existing code style: catch and else should start on their own line, and use multi-line if/else blocks.
Changelog
Disclaimer
The software is provided as-is under the MIT license. The author cannot be held responsible for any unintended behaviours.
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
