Perform the modbus packet capture experiment from @koleson's PVS-6 Notes Gist
- A *nix computer. I'm using a Raspberry Pi and the setup reflects that.
- Waveshare RS-486/USB bridge
- RJ-45 3-way splitter
- 2 network patch cords.
- Set up the Raspberry Pi according to the getting started guide. You can install the extra software now or later.
- You'll be working inside the dead front of the Hub+. It's an electrical panel, so take the usual precautions like turning off the power. All of the power, including the PV panels and the Sunvaults using the shutdown proceedure on the sticker on the right side of the deadfront.
- Open the Hub+ and remove the PVS-6 cover and deadfront.
- Set up the RPi close to or inside the Hub+ cabinet. You'll need to arrange for power and either a network connection or a keyboard and display to control it. One option is to put it inside the Hub+ and power it from one of the PVS-6's USB ports. If you're going to connect to it with WiFi you'll need the antenna to be in the plastic cover at the top of the Hub+ with the PVS-6 and its antennae. In my installation it was convenient to place it on the other side of the wall to which the Hub+ is mounted.
- Using patch cords connect the MIDC and PVS-6 RS-485 ports with the 3-way splitter. Depending on where you place things you may use the quite short existing patch cable here.
- Cut one end off of a network patch cable, strip it back an inch or two, and untwist the blue and brown pairs. Strip and connect the solid brown to the Waveshare's ground terminal, the solid blue to the A or + terminal, and the blue-white to the B or - terminal. Connect the Waveshare to one of the RPi's USB ports. Note that RS-485 is designed to be a single branchless bus so to minimize noise on the line you should keep the cable length between the splitter and the waveshare as short as possible. Take care to ensure that you get the wires securely in the terminal clamps.
- Note: The cables between the MIDC, PVDR, and MIO carry 12VDC on the brown pair. This is used to power the MIO and the InsightFacility, the latter via the MIO's RJ11 jack that also feeds the Insight Facility's CANbus input.
- Connect the Waveshare to one of the RPi's USB ports and make the other connections you need for the RPi.
- Put the Hub+ back together and restore power.
- SSH/log in to the Raspberry Pi.
- If you haven't already, clone modbus-sniffer and build it. You may want to move the
sniffer
executable somewhere convenient to run from. Install PyModbus and PySerial. You may want to set up a virtenv for PyModbus. - Wireshark is a GUI program so if you're connecting to the RPi with SSH you'll want it on the computer you're connecting from, not the RPi.
- Start the modbus sniffer:
/path/to/sniffer -p /dev/ttyUSB0 -b 9600 -t 2000 -o modbus.pcap
- Note that the port might be different if you have another serial device using USB.
- You can of course use any output file you like; the output is binary so you must either use
-o
or a redirect to a file name.sniffer
will refuse to write to the console. - I found
-t 2000
to be the minimum needed to ensure that whole packets were captured. Anything smaller yielded truncated packets and CRC failures. sniffer
writes per-packet capture status to stderr. You may want to redirect that to a file or/dev/null
.sniffer
writes Wireshark pcap files, so after collecting data for a while kill it with ctrl-C and load the file into Wireshark for analysis.- Wireshark doesn't load its modbus frame interpreter by default, so after loading the capture file select one of the frames and right-click on it. From the context menu select
Protocol Preferences>DLT_User>Encapsulations Table…
(near the bottom). Add an entry and entermbrtu
in thePayload dissector
field. Save it and clickOK
to accept the change.
My results are going to be a bit unusual: My PVS-6 stopped charging the battery on New Year's Eve 2024 and subsequent efforts by technicians to revive it resulted in its being decommissioned. Consequently the dl_CGI
device list shows only the Hub+ and its two directly-connected power meters and there is no ModbusTCP traffic on the network, though the PVS-6 connects to the Insight every 5 minutes via https, does one request, and hangs up. I ran two ~45-minute captures, one with the Sunvault battery off and one with it on. I didn't see any difference between the two.
Notation: ID, beginning register, number of registers: response. Responses enclosed in quotes are ASCII strings, otherwise the response is numeric.
On the RS-485 Modbus the PVS-6 sends the following once a minute except the first one that is done every two minutes before the others.
- 220, 60000, 17: "SY2328850-005506.30812" Repeats 4 times every two minutes
- 220, 0, 3: "HUB+"
- 220, 21, 16: "SY2328850-005506.30812"
- 220, 68, 3: 0, 8, 6
- 220, 200, 2: 2, 0
- 220, 111, 2: 1253, 1249 This response changes slightly over time
- 220, 83, 1: 0
- 221, 60000, 17: "SY2310534552F0174" Repeats 6 times every 5 minutes
- 221, 0, 2: 19785, 20272
- 221, 21, 16: "SY2310534552F0174"
- 221, 42, 3: 1, 8, 11
- 222, 60000, 17: "SY2249534552F1581" Repeats 6 times every 5 minutes
- 222, 0, 2: 19785, 20272
- 222, 21, 16: "SY2249534552F1581"
- 222, 42, 3: 1, 8, 11
- 223, 0, 2: Fails
- 230, 60000, 17: "SY2314536749B1538" Repeats 5 times every 5 minutes
- 230, 0, 2: 20594, 21058
- 230, 21, 16: "SY2314536749B1538"
- 230, 41, 3: 0, 3, 16
- 231, 0, 2: Fails
Disconnect the PVS-6 cable from the 3-way splitter so you can use the PyModbus client without the PVS-6's traffic clogging up the input buffer. Start a python shell on the RPi and connect to the Waveshare:
>>> from pymodbus.client import ModbusSerialClient
>>> client = ModbusSerialClient(port="/dev/ttyUSB0", baudrate=9600, name="modbus")
>>> client.connect()
True
Repeat the register 60000 query to confirm that it's working:
>>> from pymodbus.client import ModbusSerialClient
>>> client = ModbusSerialClient(port="/dev/ttyUSB0", baudrate=9600, name="modbus")
>>> client.connect()
True
>>> resp = client.read_holding_registers(address=60000, count=17, slave=220)
>>> resp.isError()
False
>>> resp.registers
[21337, 12851, 12856, 14389, 12333, 12336, 13621, 12342, 11827, 12344, 12594, 0, 0, 0, 0, 0, 0]
Now try to read the Sunspec identification tags in the 40000 and 40001 or 50000 and 50001 holding registers:
>>> resp = client.read_holding_registers(address=40000, count=2, slave=220)
>>> resp.isError()
True
>>> resp = client.read_holding_registers(address=50000, count=2, slave=220)
>>> resp.isError()
True
I got the same for ids 221, 222, and 230, so none of them are Sunspec compliant. Not too surprising, Sunspec is mostly about inverters. Note: The InsightFacility at id 1 XWPro(s) at id(s) 10 (and 11…) aren't accessible on this bus, they're on the modbus-over-tcp connected to the InsightFacility; you can access them with pymodbus.client.ModbusTCPClient.
I scanned each of the modbus servers with sunvault-modbus-probe.py and distilled the successful responses into sunvault_register_data.py, guessing a bit at the number sizes, then used that with sunvault-modbus-probe-2.py to retrieve the following tables.
As noted my current situation is that the PVS6 is decommissioned and it's also disconnected from both the internal (172.27.153.0/24) network and the RS485 bus so that it doesn't interfere with my experimentation. I've set modbus register 40250 to 2 (autonomous mode when sunspec controller is disconnected). The BMS shuts down after 12 hours or so, so I ran the scan with the batteries off, then turned them on and scanned again. InsightLocal showed me that the XWPros decided to top off the batteries so I scanned again while they charged and a third time after the charge stopped. Finally I made a 4th scan at 20:30 after the PV array had shut down (the batteries had not shut themselves off yet). A diff
showed what lines changed and I've included the changed lines with a note of which scan each is from.
220:1: HUB+
220:3: SunPower MIDC
220:19: [458777, 1528665]
220:22: SY2328850-005506.30812
220:38: [1, 6, 3]
220:41: [327680, 0]
220:69: [8, 393451] (All 3 Daytime)
220:69: [8, 393452] (Night-PV Off)
220:72: [15400960, 4] (All 3 Daytime)
220:72: [15466496, 4] (Night-PV Off)
220:75: [4, 1371, 1, 0, 121, 2, 0, 0, 0, 0, 0, 6002, 6000, 1802, 1800, 100, 1989, 2007, 0, 0, 123, 124, 0, 0, 0, 40, 20, 6798, 66, 11298, 11375, 5, 247, 2, 13, 1247, 1246, 1248, 1245, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0] (Battery off)
220:75: [4, 1371, 1, 0, 121, 2, 0, 0, 0, 0, 0, 6000, 5999, 1802, 1802, 100, 664, 909, 0, 0, -32, -45, 0, 0, 0, 37, 20, 6215, 11331, 11375, 11375, 5, 242, 2, 13, 1237, 1236, 1238, 1235, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0] (Battery charging)
220:75: [4, 1371, 1, 0, 121, 2, 0, 0, 0, 0, 0, 5999, 5999, 1802, 1801, 100, 1885, 1790, 0, 0, 116, 109, 0, 0, 0, 36, 21, 5896, 11331, 11386, 11375, 0, 242, 2, 13, 1248, 1248, 1249, 1247, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0] (Battery stable
220:75: [4, 1371, 1, 0, 121, 2, 0, 0, 0, 0, 0, 6002, 6002, 1801, 1800, 100, 1308, 1467, 0, 0, -64, -69, 0, 0, 0, 29, 29, 3751, 11320, 11386, 11364, 0, 242, 2, 13, 1233, 1231, 1234, 1230, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0] (Night-PV Off)
220:196: [0, 23, 1359, 631, 0, 2, 0, 0] (Battery off)
220:196: [1495, 1476, 2383, 325, 0, 2, 0, 0] (Battery charging)
220:196: [48, 91, 353, 1909, 0, 2, 0, 0] (Battery stable)
220:196: [1456, 1419, 397, 2668, 0, 2, 0, 0] (Night-PV Off)
220:300: [1247, 1247, 1247, 1247, 1247, 1247, 1247, 1247] (Battery off)
220:300: [1238, 1237, 1238, 1238, 1237, 1237, 1237, 1238] (Battery charging)
220:300: [1247, 1247, 1248, 1248, 1248, 1247, 1248, 1248] (Battery stable)
220:300: [1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233] (Night-PV Off)
220:308: [1246, 1246, 1246, 1246, 1246, 1246, 1246, 1246] (Battery off)
220:308: [1237, 1237, 1236, 1236, 1237, 1236, 1236, 1236] (Battery charging)
220:308: [1246, 1247, 1247, 1247, 1247, 1247, 1248, 1248] (Battery stable)
220:308: [1231, 1231, 1231, 1231, 1231, 1231, 1231, 1231] (Night-PV Off)
220:316: [1248, 1248, 1248, 1248, 1248, 1248, 1248, 1248] (Battery off)
220:316: [1238, 1238, 1238, 1238, 1238, 1238, 1238, 1238] (Battery charging)
220:316: [1248, 1249, 1249, 1248, 1248, 1249, 1249, 1249] (Battery stable)
220:316: [1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234] (Night-PV Off)
220:324: [1245, 1245, 1245, 1245, 1245, 1245, 1245, 1245] (Battery off)
220:324: [1236, 1235, 1235, 1236, 1235, 1235, 1235, 1235] (Battery charging)
220:324: [1246, 1246, 1246, 1246, 1246, 1247, 1247, 1247] (Battery stable)
220:324: [1230, 1230, 1230, 1230, 1230, 1230, 1230, 1230] (Night-PV Off)
220:1000:
220:8999: [0, 0, 0, 0, 1, 2, 2, 0, 0]
220:9075: [0, 0]
220:10001: [0, 0]
220:60001: SY2328850-005506.30812
220:60606:
220:65501:
221:1: MIO0
221:3: SunPower MIO
221:19: [196616, 1528665]
221:22: SY2310534552F0174
221:38: [0, 6, 0]
221:41: [131072, 65544]
221:43: [1, 8, 11]
221:46: [1, 0, 30, 320, 23, 44, 4031, 9658, 9658, 2026, 181, 9691, 0, 0, 0, 0, 0, 0, 0, 0, 0] (Battery Off)
221:46: [1, 0, 30, 320, 24, 41, 4031, 11760, 11782, 2148, 177, 11804, 0, 0, 0, 0, 0, 0, 0, 0, 0] (Battery Charging)
221:46: [1, 0, 30, 320, 26, 38, 4031, 11771, 11793, 2267, 171, 11793, 0, 0, 0, 0, 0, 0, 0, 0, 0] (Battery Stable)
221:46: [1, 0, 30, 320, 23, 40, 4031, 11782, 11815, 2264, 63, 11782, 0, 0, 0, 0, 0, 0, 0, 0, 0] (Night-PV Off)
221:64: [0, 0]
221:80: [0, 1, 0, 0]
221:95: [0, 1, 0, 0, 0, 0, 0, 0, 0, 47, 2]
221:8999: [0, 0, 0, 0, 1, 1, 1, 0, 0]
221:9075: [0, 0]
221:10001: [0, 0]
221:60001: SY2310534552F0174
221:65501:
222:1: MIO0
222:3: SunPower MIO
222:19: [786441, 1463129]
222:22: SY2249534552F1581
222:38: [0, 6, 0]
222:41: [131072, 65544]
222:43: [1, 8, 11]
222:46: [1, 0, 31, 320, 25, 37, 4031, 10055, 10011, 2026, 153, 10022, 0, 0, 0, 0, 0, 0, 0, 0, 0] (Battery Off)
222:46: [1, 0, 31, 320, 26, 36, 4031, 11848, 11859, 2263, 144, 11925, 0, 0, 0, 0, 0, 0, 0, 0, 0] (Battery Charging)
222:46: [1, 0, 31, 320, 27, 33, 4031, 11892, 11826, 2266, 139, 11925, 0, 0, 0, 0, 0, 0, 0, 0, 0] (Battery Stable)
222:46: [1, 0, 31, 320, 24, 37, 4031, 11870, 11870, 2262, 67, 11903, 0, 0, 0, 0, 0, 0, 0, 0, 0] (Night-PV Off)
222:64: [0, 0]
222:80: [0, 1, 0, 0]
222:95: [0, 1, 0, 0, 0, 0, 0, 0, 0, 47, 2]
222:8999: [0, 0, 0, 0, 1, 1, 1, 0, 0]
222:9075: [0, 0]
222:10001: [0, 0]
222:60001: SY2249534552F1581
222:65501:
230:1: PrRB
230:3: SunPower PV Disconnect Relay
230:19: [262148, 1528665]
230:22: SY2314536749B1538
230:38: [0, 2, 0]
230:41: [0, 196624]
230:46: [288, 1, 0, 0, 0, 0, 1, 1, 0, 0, 2, 1, 0, 0, 2, 600, 0, 0, 0, 0, 1306, 1296, 0, 0, 0, 0, 0] (Battery off)
230:46: [288, 1, 0, 0, 0, 0, 1, 1, 0, 0, 2, 1, 0, 0, 2, 600, 0, 0, 0, 0, 1306, 1296, 0, 0, 0, 0, 0] (Battery charging)
230:46: [288, 1, 0, 0, 0, 0, 1, 1, 0, 0, 2, 0, 0, 0, 2, 600, 0, 0, 0, 0, 1306, 1296, 0, 0, 0, 0, 0] (Battery Stable)
230:46: [288, 1, 0, 0, 0, 0, 1, 1, 0, 0, 2, 1, 0, 0, 2, 600, 0, 0, 0, 0, 1306, 1296, 0, 0, 0, 0, 0] (Night-PV Off)
230:98: [1774, 1776, 1237, 1243, 1246, 1240] (Battery off)
230:98: [1774, 1776, 1234, 1234, 1234, 1225] (Battery charging)
230:98: [1774, 1776, 1243, 1246, 1246, 1243] (Battery Stable)
230:98: [1774, 1776, 1228, 1228, 1228, 1219] (Night-PV Off)
230:104: [0, 0, 935920320, 935920320, 16843009, 935920320, 935920320, 935920320, 935920320, 0] (Battery off)
230:104: [0, 0, 2233008832, 2233008832, 16843009, 2233008832, 2233008832, 2233008832 2233008832, 0] (Battery charging)
230:104: [0, 0, 2826306240, 2826306240, 16843009, 2826306240, 2826306240, 2826306240, 2826306240, 0] (Battery Stable)
230:104: [0, 0, 3520660163, 3520660163, 16843009, 3520660163, 3520660163, 3520660163, 3520660163, 0] (Night-PV Off)
230:156: 12289
230:172: 935985856 (Battery off)
230:172: 2233074368 (Battery charging)
230:172: 2826371776 (Battery Stable)
230:172: 3520725699 (Night-PV Off)
230:200: [81462491, 81462491, 81462491, 81462491, 81462491, 81462491, 81462491, 81462491] (Battery off)
230:200: [80872658, 80872658, 80872658, 80872658, 80872658, 80872658, 80872658, 80872658] (Battery charging)
230:200: [81462491, 81462491, 81462491, 81462491, 81462491, 81462491, 81462491, 81462491] (Battery stable)
230:200: [80479436, 80479436, 80479436, 80479436, 80479436, 80479436, 80479436, 80479436] (Night-PV Off)
230:216: [81462491, 81462491, 81462491, 81462491, 81462491, 81462491, 81462491, 81462491] (Battery Off)
230:216: [80872658, 80872658, 80872658, 80872658, 80872658, 80872658, 80872658, 80872658] (Battery charging)
230:216: [81462491, 81462491, 81462491, 81462491, 81462491, 81462491, 81462491, 81462491] (Battery Stable)
230:216: [80282825, 80282825, 80282825, 80282825, 80282825, 80282825, 80282825, 80282825] (Night-PV Off)
230:232: [81659102, 81659102, 81659102, 81659102, 81659102, 81659102, 81659102, 81659102] (Battery off)
230:232: [80872658, 80872658, 80872658, 80872658, 80872658, 80872658, 80872658, 80872658] (Battery charging)
230:232: [81069269, 81069269, 81069269, 81069269, 81069269, 81069269, 81069269, 81069278] (Battery Stable)
230:232: [80479436, 80479436, 80479436, 80479436, 80479436, 80479436, 80479436, 80479436] (Night-PV Off)
230:248: [80872658, 80872658, 80872658, 80872658, 80872658, 80872658, 80872658, 80872658] (Battery off)
230:248: [80676047, 80676047, 80676047, 80676047, 80676047, 80676047, 80676047, 80676047] (Battery charging)
230:248: [80872658, 80872658, 80872658, 80872658, 80872658, 80872658, 80872658, 80872658] (Battery Stable)
230:248: [80282825, 80282825, 80282825, 80282825, 80282825, 80282825, 80282825, 80282825] (Night-PV Off)
230:8999: [0, 0, 0, 0, 1, 1, 1, 0, 0]
230:9075: [0, 0]
230:10001: [0, 0]
230:60001: SY2314536749B1538
230:65501: