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, 0, 2: 19785, 20272
- 221, 21, 16: "SY2310534552F0174"
- 221, 42, 3: 1, 8, 11
- 222, 0, 2: 19785, 20272
- 222, 21, 16: "SY2249534552F1581"
- 222, 42, 3: 1, 8, 11
- 223, 0, 2: Fails
- 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 XWPro(s) at id(s) 10 (and 11…) aren't accessible on this bus. They are accessible via ModbusTCP on the PVS-6/InsightFacility network.