HotNoob / PythonProtocolGateway

Python Protocol Gateway reads data via Modbus RTU or other protocols and translates the data for MQTT. In the long run, Python Protocol Gateway will become a general purpose protocol gateway to translate between more than just modbus and mqtt. Growatt, EG4, Sigineer, SOK, PACE
Apache License 2.0
4 stars 0 forks source link

EG4 18kPV Support #29

Closed utdrmac closed 2 months ago

utdrmac commented 2 months ago

Hello. I have an EG4 18Kpv and I would love to help this project in testing/creating compatibility to read the inverter/battery data locally. I have the RS232 eth -> USB cable that came with it. Do I need a different cable?

HotNoob commented 2 months ago

hi. welcome.

reading the manual: https://eg4electronics.com/backend/wp-content/uploads/2023/04/EG4-18KPV-12LV-Manual.pdf

there appears to be a lot of options for hooking up for communication. it doesn't specify what protocol is running on that INV485 port. doesnt hurt to try tho.

there are a few rs485 ports and looks like there are some screw terminals for rs485 ( mod bus rtu ). so if the usb cable doesn't work, you'd just need to a grab a rs485 usb adapter.

i'm personally using theese: https://www.aliexpress.us/item/2251832830864445.html

any silicon labs (ie CP2102) or prolific rs485 adapter should be a safe bet.


there is also a usb-a port in the pics. dont see anything about in the docs. but id be it might work as well. ( just use a usb-a to usb-a cable -- most cables should be wired so you don't have to worry about usb-a to usb-a power pins )

utdrmac commented 2 months ago

These two pins can be used to communicate with the inverter using the RS485 Modbus protocol.

Does that not help? I ordered one of those USB->RS485 adapters since it was only $1.50 :)

HotNoob commented 2 months ago

if that's for the usb cable you have, than should work...

for the software config. ''' [transport.0] protocol_version = eg4_v58 '''

and hopefully it works :)

utdrmac commented 2 months ago

Didn't work. Ugh. I found out why. The included RJ45->USB cable says "RS232". I just noticed this. I'll try to get the supplier to send me one, or I'll just modify this one and try again this weekend. Sorry for the noise.

HotNoob commented 2 months ago

was worth a try. the rs232 protocol is sometimes the same as the rs485...

utdrmac commented 2 months ago

Finally got the proper cable. Still no joy. What's the best way to debug?

[2024-05-02 08:58:02,765]  {protocol_gateway.py:110}  INFO - Loading...
[2024-05-02 08:58:05,420]  {protocol_gateway.py:150}  INFO - Connecting to modbus_rtu:transport.0...
INFO:invertermodbustomqqt_log:Connecting to modbus_rtu:transport.0...
[2024-05-02 08:58:05,436]  {protocol_gateway.py:150}  INFO - Connecting to mqtt:transport.1...
INFO:invertermodbustomqqt_log:Connecting to mqtt:transport.1...
mqtt connect
INFO:classes.transports.transport_base:Connected with result code 0

get registers(0): 7 to 44 (38)
DEBUG:pymodbus.transaction:Current transaction state - IDLE
DEBUG:pymodbus.transaction:Running transaction 1
DEBUG:pymodbus.transaction:SEND: 0x1 0x3 0x0 0x7 0x0 0x26 0x75 0xd1
DEBUG:pymodbus.client.sync:New Transaction state 'SENDING'
WARNING:pymodbus.client.sync:Cleanup recv buffer before send: 0x0 0xfb
DEBUG:pymodbus.transaction:Changing transaction state from 'SENDING' to 'WAITING FOR REPLY'
DEBUG:pymodbus.transaction:Transaction failed. (Modbus Error: [Invalid Message] Incomplete message received, expected at least 2 bytes (0 received))
DEBUG:pymodbus.framer.rtu_framer:Frame - [b''] not ready
DEBUG:pymodbus.transaction:Getting transaction 1
DEBUG:pymodbus.transaction:Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
ERROR:classes.transports.transport_base:<bound method ModbusException.__str__ of ModbusIOException(InvalidMessageReceivedException('Incomplete message received, expected at least 2 bytes (0 received)'), 3)>
Retry(1 - (1)) range(0)
get registers(0): 7 to 44 (38)
DEBUG:pymodbus.transaction:Current transaction state - TRANSACTION_COMPLETE
DEBUG:pymodbus.transaction:Running transaction 2
DEBUG:pymodbus.transaction:SEND: 0x1 0x3 0x0 0x7 0x0 0x26 0x75 0xd1
DEBUG:pymodbus.framer.rtu_framer:Changing state to IDLE - Last Frame End - None, Current Time stamp - 1714658290.022049
DEBUG:pymodbus.client.sync:New Transaction state 'SENDING'
DEBUG:pymodbus.transaction:Changing transaction state from 'SENDING' to 'WAITING FOR REPLY'
DEBUG:pymodbus.transaction:Incomplete message received, Expected 81 bytes Recieved 0 bytes !!!!
DEBUG:pymodbus.transaction:Changing transaction state from 'WAITING FOR REPLY' to 'PROCESSING REPLY'
DEBUG:pymodbus.transaction:RECV:
DEBUG:pymodbus.framer.rtu_framer:Frame - [b''] not ready
DEBUG:pymodbus.transaction:Getting transaction 1
DEBUG:pymodbus.transaction:Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
ERROR:classes.transports.transport_base:<bound method ModbusException.__str__ of ModbusIOException('No Response received from the remote unit/Unable to decode response', 3)>
Retry(2 - (2)) range(0)
HotNoob commented 2 months ago

assuming the cable is correct, and the port on the inverter is correct, and the "serial port" on the computer is correct.

ensure you have the correct baudrate, probably 9600 or 115200. check manual and/or your inverter could have a setting for it.

just as importantly, ensure the address is correct. just a number between 0-255. modbus wont response unless the address is correct.

based on debug messages: DEBUG:pymodbus.transaction:Transaction failed. (Modbus Error: [Invalid Message] Incomplete message received, expected at least 2 bytes (0 received)) ERROR:classes.transports.transport_base:<bound method ModbusException.str of ModbusIOException('No Response received from the remote unit/Unable to decode response', 3)>

are basically the same, stating nothing was recieved. when you no longer get the no response recieved error, were in business.

utdrmac commented 2 months ago

I tried doing analyze_protocol=true and it crashed

[2024-05-02 09:11:44,608]  {protocol_gateway.py:110}  INFO - Loading...
=== PROTOCOL ANALYZER ===
sigineer_v0.11
pace_bms_v1.3
v0.14
hdhk_16ch_ac_module
growatt_2020_v1.24
pylon_rs485_v3.3
eg4_v58
WARNING Invalid Name : discharging_oc-2_protection reg: 82 doc name: discharging_oc-2_protection path: protocols/pace_bms_v1.3.holding_registry_map.csv
WARNING Invalid Name : discharging_oc-2_protection_delay_time reg: 83 doc name: discharging_oc-2_protection_delay_time path: protocols/pace_bms_v1.3.holding_registry_map.csv
WARNING Invalid Name : pack_full-charge_voltage reg: 107 doc name: pack_full-charge_voltage path: protocols/pace_bms_v1.3.holding_registry_map.csv
WARNING Invalid Name : pack_full-charge_current reg: 108 doc name: pack_full-charge_current path: protocols/pace_bms_v1.3.holding_registry_map.csv
WARNING Invalid Name : charging_oc-2_protection reg: 113 doc name: charging_oc-2_protection path: protocols/pace_bms_v1.3.holding_registry_map.csv
WARNING Invalid Name : charging_oc-2_protection_delay_time reg: 114 doc name: charging_oc-2_protection_delay_time path: protocols/pace_bms_v1.3.holding_registry_map.csv
WARNING Invalid Name : channel_a-h_current reg: 1 doc name: channel_a-h_current path: protocols/hdhk_16ch_ac_module.holding_registry_map.csv
WARNING Invalid Name : channel_i-p_current reg: 2 doc name: channel_i-p_current path: protocols/hdhk_16ch_ac_module.holding_registry_map.csv
Traceback (most recent call last):
  File "/root/PythonProtocolGateway/protocol_gateway.py", line 233, in <module>
    main()
  File "/root/PythonProtocolGateway/protocol_gateway.py", line 220, in main
    ppg = Protocol_Gateway(args.config)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/PythonProtocolGateway/protocol_gateway.py", line 143, in __init__
    transport : transport_base = cls(transport_cfg)
                                 ^^^^^^^^^^^^^^^^^^
  File "/root/PythonProtocolGateway/classes/transports/modbus_rtu.py", line 32, in __init__
    super().__init__(settings, protocolSettings=protocolSettings)
  File "/root/PythonProtocolGateway/classes/transports/modbus_base.py", line 32, in __init__
    self.analyze_protocol()
  File "/root/PythonProtocolGateway/classes/transports/modbus_base.py", line 162, in analyze_protocol
    protocols[name] = protocol_settings(name)
                      ^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/PythonProtocolGateway/classes/protocol_settings.py", line 207, in __init__
    self.load_registry_map(registry_type)
  File "/root/PythonProtocolGateway/classes/protocol_settings.py", line 561, in load_registry_map
    self.registry_map[registry_type] = self.load__registry(path, registry_type)
                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/PythonProtocolGateway/classes/protocol_settings.py", line 425, in load__registry
    register = strtoint(row['register'])
               ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/PythonProtocolGateway/defs/common.py", line 16, in strtoint
    return int(val)
           ^^^^^^^^
ValueError: invalid literal for int() with base 10: ''
utdrmac commented 2 months ago

I called EG4 and they sent me this document. It says baudrate is 19200 so I changed that in the config. I think I'm getting data now.

EG4-18kPV-12LV - MODBUS Communication Protocol - Copy (1).pdf

utdrmac commented 2 months ago
[2024-05-02 09:15:51,244]  {protocol_gateway.py:110}  INFO - Loading...
[2024-05-02 09:15:53,897]  {protocol_gateway.py:150}  INFO - Connecting to modbus_rtu:transport.0...
INFO:invertermodbustomqqt_log:Connecting to modbus_rtu:transport.0...
[2024-05-02 09:15:53,914]  {protocol_gateway.py:150}  INFO - Connecting to mqtt:transport.1...
INFO:invertermodbustomqqt_log:Connecting to mqtt:transport.1...
mqtt connect
INFO:classes.transports.transport_base:Connected with result code 0

get registers(0): 7 to 44 (38)
DEBUG:pymodbus.transaction:Current transaction state - IDLE
DEBUG:pymodbus.transaction:Running transaction 1
DEBUG:pymodbus.transaction:SEND: 0x1 0x3 0x0 0x7 0x0 0x26 0x75 0xd1
DEBUG:pymodbus.client.sync:New Transaction state 'SENDING'
WARNING:pymodbus.client.sync:Cleanup recv buffer before send: 0x0 0x0 0x0 0x0 0x1 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x1f 0x0 0x1f 0x0 0x16 0x0 0x15 0x0 0x0 0x0 0xb 0x0 0x9e 0xf 0x4f 0xc 0x92 0x2 0x0 0x0 0x16 0x1 0x0 0x0 0x76 0x0 0x0 0x0 0x1e 0x0 0x0 0x0 0xf6 0x2 0x0 0x0 0x1e 0x6 0x0 0x0 0xb4 0x3 0x0 0x0 0xf9 0x3 0x0 0x0 0x0 0x0 0x0 0x0 0x4e 0x2 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x28 0x0 0x35 0x0 0x2d 0x0 0x2 0x0 0x0 0x0 0xf 0x25 0x1d 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x1 0x0 0x1 0x0 0x2 0x0 0xd0 0x7 0xd0 0x7 0x30 0x2 0xc2 0x1 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0xc0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x3 0x0 0x1 0x0 0x18 0x1 0xbd 0xff 0x0 0x0 0x0 0x0 0xc8 0xc 0xc6 0xc 0x18 0x1 0x18 0x1 0x0 0x0 0x21 0x0 0x9 0x2 0xa7 0x1 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x5 0x1 0x0 0x0 0x33 0x31 0x36 0x32 0x36 0x37 0x30 0x31 0x33 0x31 0xd5 0x7 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x6e 0x85 0x0 0x4 0x30 0x30 0x30 0x30 0x30 0x30 0x30 0x30 0x30 0x30 0x7f 0x0 0x7f 0x0 0x6f 0x8a 0x1 0x4 0x33 0x31 0x36 0x32 0x36 0x37 0x30 0x31 0x33 0x31 0x7f 0x0 0xfe 0xad 0x4 0xae 0x4 0x5 0x1 0x35 0x0 0x69 0x1 0x8d 0x0 0x15 0x0 0x15 0x0 0xf9 0x3 0x0 0x0 0xf9 0x3 0x0 0x0 0x0 0x0 0xb1 0x4 0xb6 0x4 0x0 0x0 0x2d 0x0 0x0 0x0 0x40 0x18 0x80 0x6 0xb 0x8 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x3b 0x1 0x15 0x0 0x83 0x4 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x2c 0x1 0x0 0x0 0x2c 0x0 0x0 0x0 0x32 0x0 0x64 0x0 0x64 0x0 0x14 0x0 0x90 0x1 0x30 0x2 0xa 0x0 0x64 0x0 0x3c 0x0 0x0 0x0 0xb 0x8 0x30 0x2 0x90 0x1 0x0 0x0 0xb0 0x4 0x0 0x0 0x0 0x0 0x32 0x0 0x8 0x2 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x1c 0x2 0xe0 0x1 0x5a 0x0 0x3c 0x0 0x5 0x0 0x32 0x0 0x8 0x2 0x64 0x0 0xff 0x0 0x53 0x2 0x20 0x3 0xb 0x1 0x0 0x0 0x80 0x0 0x65 0x0 0x53 0x2 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0xe5 0xe7
DEBUG:pymodbus.transaction:Changing transaction state from 'SENDING' to 'WAITING FOR REPLY'
DEBUG:pymodbus.transaction:Changing transaction state from 'WAITING FOR REPLY' to 'PROCESSING REPLY'
DEBUG:pymodbus.transaction:RECV: 0x1 0x3 0x4c 0x41 0x46 0x42 0x41 0x17 0x3 0x1 0x17 0x0 0x1 0x5 0x18 0x9 0x2 0x13 0xf 0x0 0x1 0x0 0x1 0x0 0x0 0x0 0x0 0x0 0x2c 0x0 0x7 0x72 0xd5 0x5 0x78 0x1 0x2c 0x1 0x2c 0x8 0x98 0x9 0xd8 0x17 0x3e 0x17 0x7a 0x8 0x40 0xa 0x50 0x8 0x34 0x5 0x14 0x4 0xb0 0xb 0x40 0x0 0xc8 0x0 0x10 0x4 0xb0 0xb 0x40 0x0 0xc8 0x0 0x10 0xa 0x50 0x16 0xda 0x17 0xe8 0x75 0x30 0x24 0x46
DEBUG:pymodbus.framer.rtu_framer:Getting Frame - 0x3 0x4c 0x41 0x46 0x42 0x41 0x17 0x3 0x1 0x17 0x0 0x1 0x5 0x18 0x9 0x2 0x13 0xf 0x0 0x1 0x0 0x1 0x0 0x0 0x0 0x0 0x0 0x2c 0x0 0x7 0x72 0xd5 0x5 0x78 0x1 0x2c 0x1 0x2c 0x8 0x98 0x9 0xd8 0x17 0x3e 0x17 0x7a 0x8 0x40 0xa 0x50 0x8 0x34 0x5 0x14 0x4 0xb0 0xb 0x40 0x0 0xc8 0x0 0x10 0x4 0xb0 0xb 0x40 0x0 0xc8 0x0 0x10 0xa 0x50 0x16 0xda 0x17 0xe8 0x75 0x30
DEBUG:pymodbus.factory:Factory Response[ReadHoldingRegistersResponse: 3]
DEBUG:pymodbus.framer.rtu_framer:Frame advanced, resetting header!!
DEBUG:pymodbus.transaction:Adding transaction 1
DEBUG:pymodbus.transaction:Getting transaction 1
DEBUG:pymodbus.transaction:Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
get registers(1): 45 to 89 (45)
DEBUG:pymodbus.transaction:Current transaction state - TRANSACTION_COMPLETE
DEBUG:pymodbus.transaction:Running transaction 2
DEBUG:pymodbus.transaction:SEND: 0x1 0x3 0x0 0x2d 0x0 0x2d 0x15 0xde
DEBUG:pymodbus.framer.rtu_framer:Changing state to IDLE - Last Frame End - 1714659355.922102, Current Time stamp - 1714659356.804119
DEBUG:pymodbus.client.sync:New Transaction state 'SENDING'
HotNoob commented 2 months ago

yes! there is data there!

you should see some json in the output after it finishes reading all the registers.

utdrmac commented 2 months ago

Ohh. I see stuff in MQTT!

/ # mosquitto_sub -t '/home/inverter/#' -v
/home/inverter/availability online
/home/inverter/fwcode0 70
/home/inverter/fwcode1 65
...
/home/inverter/pv1_voltage 157.5
/home/inverter/pv2_voltage 198.3
/home/inverter/pv3_voltage 11.9
...
utdrmac commented 2 months ago

I'm seeing this error at the end all the time:

DEBUG:pymodbus.transaction:Current transaction state - TRANSACTION_COMPLETE
DEBUG:pymodbus.transaction:Running transaction 115
DEBUG:pymodbus.transaction:SEND: 0x1 0x4 0x3 0xe2 0x0 0x12 0xd0 0x75
DEBUG:pymodbus.framer.rtu_framer:Changing state to IDLE - Last Frame End - 1714659663.899823, Current Time stamp - 1714659667.225088
DEBUG:pymodbus.client.sync:New Transaction state 'SENDING'
DEBUG:pymodbus.transaction:Changing transaction state from 'SENDING' to 'WAITING FOR REPLY'
DEBUG:pymodbus.transaction:Changing transaction state from 'WAITING FOR REPLY' to 'PROCESSING REPLY'
DEBUG:pymodbus.transaction:RECV: 0x1 0x84 0x3 0x3 0x1
DEBUG:pymodbus.framer.rtu_framer:Getting Frame - 0x84 0x3
DEBUG:pymodbus.factory:Factory Response[132]
DEBUG:pymodbus.framer.rtu_framer:Frame advanced, resetting header!!
DEBUG:pymodbus.transaction:Adding transaction 1
DEBUG:pymodbus.transaction:Getting transaction 1
DEBUG:pymodbus.transaction:Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
ERROR:classes.transports.transport_base:<bound method ExceptionResponse.__str__ of <pymodbus.pdu.ExceptionResponse object at 0xb6524210>>
HotNoob commented 2 months ago

ok. 2 possibilities, the read rate is too high, causing the inverter's modbus to freeze a bit. or there are a set of registers that aren't responding; my sigineer inverters does this for the "bms feature" when there is no bms comm to the inverter.

if it's the later, ie some stuff you don't need you can disable them via the .csv or i think i have a "screen" feature on the todo. shouldn't effect functionality other than being a little slower to update.

utdrmac commented 2 months ago

My next question was going to be 'how can I filter stuff I don't care about' :) I just need to edit the csv and remove/comment out what I don't want? There's only about 8 data points I'm interested in. It looks like I'm only getting data pushed to mqtt every minute, which is about the same lag as using the wifi dongle/cloud UI. I was hoping for much more frequent updates, like every 10s or so, looking at much less data.

HotNoob commented 2 months ago

https://github.com/HotNoob/PythonProtocolGateway/blob/main/variable_mask.example.txt

mask and on the todo screen.

add the documented name to the txt file, one per line. when the variable_mask.txt file is not empty it will only scan / send those specified entries. then you don't have to worry about the csv being rewritten when updating.

alternatively, you can make your own "protocol" and name it .custom.csv, then git will ignore that.

HotNoob commented 2 months ago

oh and you can also adjust the read_interval on the modbus transport (probably transport.0), to a lower number.

if your only reading a few entries you might be able to push it below the 1 second minimum specified in inverter docs.

read_interval is in seconds, and can be a decimal.

utdrmac commented 2 months ago

Requested pictures:

RaspberryPi_Model1_B EG4_18K_Diagram

HotNoob commented 2 months ago

awesome, running on a pi. ty.