// ============================================================ // SIGEN RS485 MONITOR -- Shelly device with Modbus RS485 addon // // Reads live Sigenergy solar data directly from the gateway // via Modbus RS485 -- fully local, no internet, no laptop. // // Prerequisites: // 1. Shelly Modbus RS485 addon physically wired to the // Sigenergy gateway RS485 port (A+ -> A+, B- -> B-, GND -> GND) // 2. RS485 addon configured in Shelly Web UI: // Settings -> Addon -> RS485 // Baud rate, parity, stop bits: match gateway spec sheet // (typically 9600 or 19200 baud, 8N1 or 8E1) // 3. Five Virtual Components (number type) created on this device: // VC 200 PV Power (W) VC 203 Grid Power (W) // VC 201 Battery SOC (%) VC 204 Load Power (W) // VC 202 Battery Power (W) // // Modbus register map (input registers, FC04, Unit ID 247): // Signal Register PDU addr Count Type Scale // gridPower 30005 4 2 int32 1 W (+import / -export) // batterySoc 30014 13 1 uint16 0.1 % // pvPower 30035 34 2 int32 1 W // batteryPower 30037 36 2 int32 1 W (+charging) // loadPower 30284 283 2 int32 1 W // // PDU address = register number - 30001 (0-indexed for FC04) // ============================================================ var UNIT_ID = 247; var POLL_MS = 30000; // poll every 30 seconds // VC IDs var VC_PV = 200; var VC_SOC = 201; var VC_BAT = 202; var VC_GRID = 203; var VC_LOAD = 204; // Collected values (filled sequentially, pushed to VCs when all ready) var vals = {}; // Convert two uint16 Modbus registers to a signed int32 function to_int32(hi, lo) { var u = hi * 65536 + lo; if (u > 2147483647) u = u - 4294967296; return u; } function setVC(id, value) { Virtual.getHandle("number:" + id).setValue(value); } // ── Step 5: write all values to VCs ────────────────────────────────────────── function pushToVCs() { setVC(VC_PV, vals.pvW); setVC(VC_SOC, vals.socPc); setVC(VC_BAT, vals.batW); setVC(VC_GRID, vals.griW); setVC(VC_LOAD, vals.loadW); print("[rs485] PV=" + vals.pvW + "W SOC=" + vals.socPc + "% Bat=" + vals.batW + "W Grid=" + vals.griW + "W Load=" + vals.loadW + "W"); } // ── Step 4: read loadPower (30284, addr 283, 2 regs, int32) ────────────────── function readLoad() { Shelly.call("Modbus.Read", { device_id: UNIT_ID, type: "input_registers", addr: 283, count: 2 }, function(res, err) { if (err !== 0 || !res) { print("[rs485] err readLoad:", err); return; } vals.loadW = to_int32(res.values[0], res.values[1]); pushToVCs(); }); } // ── Step 3: read batteryPower (30037, addr 36, 2 regs, int32) ──────────────── function readBatteryPower() { Shelly.call("Modbus.Read", { device_id: UNIT_ID, type: "input_registers", addr: 36, count: 2 }, function(res, err) { if (err !== 0 || !res) { print("[rs485] err readBatteryPower:", err); return; } vals.batW = to_int32(res.values[0], res.values[1]); readLoad(); }); } // ── Step 2b: read pvPower (30035, addr 34, 2 regs, int32) ──────────────────── function readPV() { Shelly.call("Modbus.Read", { device_id: UNIT_ID, type: "input_registers", addr: 34, count: 2 }, function(res, err) { if (err !== 0 || !res) { print("[rs485] err readPV:", err); return; } vals.pvW = to_int32(res.values[0], res.values[1]); readBatteryPower(); }); } // ── Step 2a: read batterySoc (30014, addr 13, 1 reg, uint16 x0.1) ──────────── function readSOC() { Shelly.call("Modbus.Read", { device_id: UNIT_ID, type: "input_registers", addr: 13, count: 1 }, function(res, err) { if (err !== 0 || !res) { print("[rs485] err readSOC:", err); return; } vals.socPc = res.values[0] * 0.1; readPV(); }); } // ── Step 1: read gridPower (30005, addr 4, 2 regs, int32) ──────────────────── // Grid sign: Modbus + = import from grid, VC 203 + = export -> negate function readGrid() { Shelly.call("Modbus.Read", { device_id: UNIT_ID, type: "input_registers", addr: 4, count: 2 }, function(res, err) { if (err !== 0 || !res) { print("[rs485] err readGrid:", err); return; } vals.griW = -to_int32(res.values[0], res.values[1]); // negate for VC sign readSOC(); }); } // ── Poll loop ───────────────────────────────────────────────────────────────── function poll() { vals = {}; readGrid(); } Timer.set(POLL_MS, true, poll, null); poll(); print("Sigen RS485 monitor started, polling every", POLL_MS / 1000, "s");