diy solar

diy solar

Setting registers in Epever controller with python

bobself

New Member
Joined
Jun 16, 2021
Messages
8
I have a Epever 40 amp charge controller and using python + minimalmodbus to communicate to it on the rs485 bus. I can read
all of the over 100 registers OK. I can set the 'battery type' to 'user'. I'm trying a test to set the boost reconnect voltage.
This is what I'm doing but, I'm getting an exception:
decimals = 0
val = instrument.read_register(0x900, decimals) # 0x9000 is the battery type register
assert val == 0 # 0 means that battery type is 'user'
# try to set the boost reconnect voltage
decimals = 2
val = c.instrument.read_register(0x9009, decimals) # returns 13.2 volts
instrument.debug = True
instrument.write_register(0x9009, 13.30, decimals) # set to 13.3 volts

debug output:
MinimalModbus debug mode. Will write to instrument (expecting 8 bytes back): '\x01\x10\x90\t\x00\x01\x02\x052´E' (01 10 90 09 00 01 02 05 32 B4 45)
MinimalModbus debug mode. Clearing serial buffers for port /dev/ttyUSB0
MinimalModbus debug mode. Sleeping 0.80 ms before sending. Minimum silent period: 1.75 ms, time since read: 0.95 ms.
MinimalModbus debug mode. Response from instrument: '\x01\x90\x04MÃ' (01 90 04 4D C3) (5 bytes), roundtrip time: 500.7 ms. Timeout for reading: 500.0 ms.
exception:
SlaveReportedException('Slave reported device failure')
Does anyone know what might be wrong?
thanks
 
Make sure you're writing to the right register, with the right function code.

(I don't have the modbus manual for these, I'll be happy to give some more pointers if you can post a link to it here)
 
I tried these. Nothing works so far
instrument.write_register(0x9009, .133, 2) # 'Slave reported device failure' exception
instrument.write_register(0x9009, 1330, 2) # 'ValueError...' (133,000 is too big?) exception
instrument.write_register(0x9009, 1330, 0) # 'Slave reported device failure' exception

I'm also verifying that the battery type is 'user':
val = instrument.read_register(reg, decimals)
assert val == 0 # 0 means that battery type is 'user'
 
I decided to try pymodbus to see if I could get more info.

r = client.read_holding_registers(0x9009, 1, unit=1) # https://pymodbus.readthedocs.io/en/latest/source/library/pymodbus.client.html
val = r.registers[0]
assert r.registers[0] == 1320

This works

then I try to change this value:
r = client.write_register(0x9009, 1330, unit=1)

This fails. r.exception_code is 4 which means

Slave Device Failure​
An unrecoverable error occurred while the slave was attempting to perform the requested action.​



I'm starting to think that this charge controller simply does not allow this. But why can I write to 0x9000?
 
Well, it's definitely writable since I just tested and the MT50 can change that parameter.

I have a way to proxy requests between the MT50 and the Tracer unit through my computer. Trying that now to see whether I can capture what modbus commands it is using...
 
I ended up (for now) setting the battery parameters with the display so I agree that register 0x9009 is writable with the MT50. Can you try it with pymodbus?

from pymodbus.client.sync import ModbusSerialClient as ModbusClient

client = ModbusClient( method='rtu', port='/dev/ttyUSB0', baudrate=115200, timeout=1)
client.connect()

r = client.read_holding_registers(0x9009, 1, unit=1) # https://pymodbus.readthedocs.io/en/latest/source/library/pymodbus.client.html
val = r.registers[0]
assert r.registers[0] == 1320

r = client.write_register(0x9009, 1310, unit=1)
assert r.function_code < 0x80
 
I also found that I could not trivially write address 0x9009;

Code:
from pymodbus.client.sync import ModbusSerialClient as ModbusClient

client = ModbusClient(method='rtu', port='/dev/ttyUSB0', timeout=1, stopbits=1, bytesize=8, parity='N', baudrate=115200)
client.connect()

rr = client.read_holding_registers(address=0x9000, count=1, unit=1)
print(rr.registers)

rr = client.write_register(address=0x9009, value=1310, unit=1)
print(rr)




When I intercept the request from the MT50, it looks like it is writing a block of 16 registers;

Code:
2021-07-13 08:24:00,374 MainThread      DEBUG    factory        :137      Factory Request[WriteMultipleRegistersRequest: 16]
2021-07-13 08:24:00,374 MainThread      DEBUG    rtu_framer     :107      Frame advanced, resetting header!!
2021-07-13 08:24:00,375 Thread-1        DEBUG    transaction    :139      Current transaction state - TRANSACTION_COMPLETE
2021-07-13 08:24:00,375 Thread-1        DEBUG    transaction    :144      Running transaction 32
2021-07-13 08:24:00,375 Thread-1        DEBUG    transaction    :272      SEND: 0x1 0x10 0x90 0x0 0x0 0xf 0x1e 0x0 0x0 0x0 0x22 0x1 0x2c 0x6 0x4a 0x5 0xdc 0x5 0xdc 0x5 0xb4 0x5 0xa0 0x5 0x64 0x5 0x28 0x4 0xec 0x4 0xc4 0x4 0xb0 0x4 0x56 0x4 0x24 0x10 0x4f

Code:
0x1 (unit)
0x10 (Function code = 0x10 Write Multiple registers)
0x90 0x0 (Starting Address = 0x9000)
0x0 0xf (Quantity of Registers

0x1e (30 bytes follow)

0x0 0x0 (0x9000 = 0000H - User defined)

0x0 0x22 (0x9001 = 34AH - Battery Capacity)

0x1 0x2c (0x9002 = 3.00mV/°C/2V - Temperature compensationcoefficient)

0x6 0x4a (0x9003 = 16.10V - High Volt.disconnect)

0x5 0xdc (0x9004 = 15.00V - Charging limit voltage)

0x5 0xdc (0x9005 = 15.00V - Over voltage reconnect)

0x5 0xb4 (0x9006 = 14.60V - Equalization voltage)

0x5 0xa0 (0x9007 = 14.40V - Boost voltage)

0x5 0x64 (0x9008 = 13.80V - Float voltage)

0x5 0x28 (0x9009 = 13.20V - Boost reconnect voltage)

0x4 0xec (0x900A = 12.60V - Low voltage reconnect)

0x4 0xc4 (0x900B = 12.20V - Under voltage recover)

0x4 0xb0 (0x900C = 12.00V - Under voltage warning)

0x4 0x56 (0x900D = 11.10V - Low voltage disconnect)

0x4 0x24 (0x900E = 10.60V - Discharging limit voltage)

0x10 0x4f (CRC)

I think the next step would be to try and reproduce that request from pymodbus...
 
Oh, those absolute dicks.

I've seen that shit on other modbus devices too. Read a block of 100 registers? Sure! Read one in the middle? fuck off!

I would do the following:

1. Read the registers into an array
2. Inject new values from user program into the register array.
3. Write the now altered register array back to the MPPT controller.
 
Well, writing all the registers at once does work...

Python:
import logging
FORMAT = ('%(asctime)-15s %(threadName)-15s'
          ' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.WARNING)

import time
from pymodbus.client.sync import ModbusSerialClient as ModbusClient

client = ModbusClient(method='rtu', port='/dev/ttyUSB2', timeout=1, stopbits=1, bytesize=8, parity='N', baudrate=115200)
client.connect()

rr = client.read_holding_registers(address=0x9000, count=1, unit=1)
print(rr.registers)

rr = client.write_registers(address=0x9000, values=[
    0x0,  # (0x9000 = 0000H - User defined)
    0x22, # (0x9001 = 34AH - Battery Capacity)
    0x12c, # (0x9002 = 3.00mV/°C/2V - Temperature compensationcoefficient)
    1620, # (0x9003 = 16.20V - High Volt.disconnect)
    0x5dc, # (0x9004 = 15.00V - Charging limit voltage)
    0x5dc, # (0x9005 = 15.00V - Over voltage reconnect)
    0x5b4, # (0x9006 = 14.60V - Equalization voltage)
    0x5a0, # (0x9007 = 14.40V - Boost voltage)
    0x564, # (0x9008 = 13.80V - Float voltage)
    1630, # (0x9009 = 16.30V - Boost reconnect voltage)
    0x4ec, # (0x900A = 12.60V - Low voltage reconnect)
    0x4c4, # (0x900B = 12.20V - Under voltage recover)
    0x4b0, # (0x900C = 12.00V - Under voltage warning)
    0x456, # (0x900D = 11.10V - Low voltage disconnect)
    0x424, # (0x900E = 10.60V - Discharging limit voltage)
], unit=1)
print(rr)

rr = client.read_holding_registers(address=0x9009, count=1, unit=1)

if hasattr(rr, 'registers'):
    print(rr.registers)
else:
    print(rr)

You can see the ones I've modified which are in non-hex form.
 
I've seen that shit on other modbus devices too. Read a block of 100 registers? Sure! Read one in the middle? fudge off!

Yeah, looks like that's the case here.

It works when writing all 15 registers, but fails if I remove the last one and try and write just 14 registers. I could experiment further, but my guess is that the device can only write the memory that backs these values all at once and doesn't internally implement the logic to read/modify/write them - perhaps because that takes too long for the modbus spec or introduces race conditions...

I think @Wibla's suggestion of reading, modifying, and writing the values back is probably the only option unless you want to hard-code values for all these registers into your program.
 
For reference, something like this should work...

Python:
from pymodbus.client.sync import ModbusSerialClient as ModbusClient

client = ModbusClient(method='rtu', port='/dev/ttyUSB2', timeout=1, stopbits=1, bytesize=8, parity='N', baudrate=115200)
client.connect()

# Read the whole block of registers
rr = client.read_holding_registers(address=0x9000, count=15, unit=1)
print(rr.registers)

# Copy the values that were read and modify them
values = rr.registers[:]
values[0x9] = 1640 # Boost reconnect voltage

# Write them back
rr = client.write_registers(address=0x9000, values=values, unit=1)
print(rr)

# Check that it got written
rr = client.read_holding_registers(address=0x9009, count=1, unit=1)
if hasattr(rr, 'registers'):
    print(rr.registers)
else:
    print(rr)
 
Note that the manual actually says:
"Only when the battery type is User, the customer can set the other parameters (9003~900E the parameters need to be set at the same time)"

The charge controller must want to check all these rules at once:

vp.PNG
 

Attachments

  • A or BSeriesControllerProtocolv2.6.pdf
    711.9 KB · Views: 28
Back
Top