If you are installing one of these then firstly read this set of notes, it's very useful. I'll confirm/add a few things here.
Firstly, upon install you absolutely MUST 'freeze' (lock) the direction of the main import/export CT clamp. If you don't do this, then it will look like it's all working ok, but when the sun comes out the inverter may detect the solar energy being exported as an import and attempt to match it, and which ends up basically discharging your battery into the grid at maximum power (plus the solar being exported on top).
As mentioned on the page above the UI is a little confusing though. If the screen says "FREEZE" under the CT list then the direction is frozen. If the screen says "UNFREEZE" then you haven't done it right, or if you are checking it later then it has somehow become unfrozen.
Secondly, connecting the PVsolar CT clamp appears to be optional (the inverter should be able to just charge until the export CT goes to zero), but I have just experienced a problem where the inverter will click it's relays, start a charge and then flick off and cycle though checking/charging (and clicking the relays). I fixed it by connecting the solar CT, which I had disconnected at some point over the winter while tidying the cabling. (Side note: I also had a buzzing from my solar consumer unit, I thought it was an RCBO but it was actually the CT 'ringing' because it wasn't connected!)
Thirdly, don't blindly update the firmware unless you need to. Usually it's a good idea to update software regularly, but I've read about people experiencing various problems with newer versions, when they had no reason to upgrade in the first place. If it ain't broken, don't fix it!
ESPHome Integration
I couldn't find a native ESPHome configuration for the ME3000SP, so I wrote this, based on work for other similar Sofar devices. Hopefully it might be useful to others! This is specifically intended for Passive (manual) work mode (which needs to be selected on the inverter). The mode will appear as a Select dropdown in Home Assistant, which can be manipulated either in the UI or through automations. The charge rate appears as a numeric field - set the default to whatever value you need.
substitutions:
device_name: me3000sp
name: ${device_name}
device_verbose_name: "ME3000SP"
ip: 192.168.1.110
esphome:
name: ${device_name}
platform: ESP32
board: esp32dev
logger:
level: DEBUG
web_server:
port: 80
packages:
wifi: !include includes/wifi.yaml
config: !include includes/config.yaml
ha: !include includes/ha.yaml
uart:
- id: mod_bus
tx_pin: GPIO26
rx_pin: GPIO27
baud_rate: 9600
stop_bits: 1
debug:
direction: BOTH
modbus:
id: modbus_me3000sp
uart_id: mod_bus
send_wait_time: 200ms
modbus_controller:
- id: me3000sp
address: 0x01
modbus_id: modbus_me3000sp
update_interval: 30s
setup_priority: -10
text_sensor:
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} Run state
id: run_state
register_type: holding
address: 0x0200
lambda: |-
auto z = "Unknown";
char d = data[item->offset+1];
if (d == 0) z = "Wait";
else if (d == 1) z = "Checking";
else if (d == 2) z = "Charging";
else if (d == 3) z = "Checking Discharge";
else if (d == 4) z = "Discharging";
else if (d == 5) z = "EPS State";
else if (d == 6) z = "Fault";
else if (d == 7) z = "Permanent Fault";
return {z};
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} Fault Message
id: inverter_fault_message
register_type: holding
address: 0x0201
response_size: 10
lambda: |-
std::string z = "";
int idx = item->offset;
//byte[0]
if ((data[idx] & 0x1) != 0) z += "GridOVP,";
if ((data[idx] & 0x2) != 0) z += "GridUVP,";
if ((data[idx] & 0x4) != 0) z += "GridOFP,";
if ((data[idx] & 0x8) != 0) z += "GridUFP,";
if ((data[idx] & 0x10) != 0) z += "PVUVP,";
if ((data[idx] & 0x20) != 0) z += "GridLVRT,";
if ((data[idx] & 0x40) != 0) z += "reserve-ID7,";
if ((data[idx] & 0x80) != 0) z += "reserve-ID8,";
//byte[1]
idx++;
if ((data[idx] & 0x1) != 0) z += "PVOVP,";
if ((data[idx] & 0x2) != 0) z += "IpvUnbalance,";
if ((data[idx] & 0x4) != 0) z += "PvConfigSetWrong,";
if ((data[idx] & 0x8) != 0) z += "GFCIFault,";
if ((data[idx] & 0x10) != 0) z += "PhaseSequenceFault,";
if ((data[idx] & 0x20) != 0) z += "HwBoostOCP,";
if ((data[idx] & 0x40) != 0) z += "HwAcOCP,";
if ((data[idx] & 0x80) != 0) z += "AcRmsOCP,";
//byte[2]
idx++;
if ((data[idx] & 0x1) != 0) z += "HwADFaultIGrid,";
if ((data[idx] & 0x2) != 0) z += "HwADFaultDCI,";
if ((data[idx] & 0x4) != 0) z += "HwADFaultVGrid,";
if ((data[idx] & 0x8) != 0) z += "GFCIDeviceFault,";
if ((data[idx] & 0x10) != 0) z += "MChip_Fault,";
if ((data[idx] & 0x20) != 0) z += "HwAuxPowerFault,";
if ((data[idx] & 0x40) != 0) z += "BusVoltZeroFault,";
if ((data[idx] & 0x80) != 0) z += "IacRmsUnbalance,";
//byte[3]
idx++;
if ((data[idx] & 0x1) != 0) z += "BusUVP,";
if ((data[idx] & 0x2) != 0) z += "BusOVP,";
if ((data[idx] & 0x4) != 0) z += "VbusUnbalance,";
if ((data[idx] & 0x8) != 0) z += "DciOCP,";
if ((data[idx] & 0x10) != 0) z += "SwOCPInstant,";
if ((data[idx] & 0x20) != 0) z += "SwBOCPInstant,";
if ((data[idx] & 0x40) != 0) z += "reserved-ID31,";
if ((data[idx] & 0x80) != 0) z += "reserved-ID32,";
//byte[4]
idx++;
if (data[idx] != 0) z += "reserved-ID33~40,";
//byte[5]
idx++;
if (data[idx] != 0) z += "reserved-ID41~48,";
//byte[6]
idx++;
if ((data[idx] & 0x1) != 0) z += "ConsistentFault_VGrid,";
if ((data[idx] & 0x2) != 0) z += "ConsistentFault_FGrid,";
if ((data[idx] & 0x4) != 0) z += "ConsistentFault_DCI,";
if ((data[idx] & 0x8) != 0) z += "ConsistentFault_GFCI,";
if ((data[idx] & 0x10) != 0) z += "SpiCommLose,";
if ((data[idx] & 0x20) != 0) z += "SciCommLose,";
if ((data[idx] & 0x40) != 0) z += "RelayTestFail,";
if ((data[idx] & 0x80) != 0) z += "PvIsoFault,";
//byte[7]
idx++;
if ((data[idx] & 0x1) != 0) z += "OverTempFault_Inv,";
if ((data[idx] & 0x2) != 0) z += "OverTempFault_Boost,";
if ((data[idx] & 0x4) != 0) z += "OverTempFault_Env,";
if ((data[idx] & 0x8) != 0) z += "PEConnectFault,";
if ((data[idx] & 0x10) != 0) z += "reserved-ID61,";
if ((data[idx] & 0x20) != 0) z += "reserved-ID62,";
if ((data[idx] & 0x40) != 0) z += "reserved-ID63,";
if ((data[idx] & 0x80) != 0) z += "reserved-ID64,";
//byte[8]
idx++;
if ((data[idx] & 0x1) != 0) z += "unrecoverHwAcOCP,";
if ((data[idx] & 0x2) != 0) z += "unrecoverBusOVP,";
if ((data[idx] & 0x4) != 0) z += "unrecoverIacRmsUnbalance,";
if ((data[idx] & 0x8) != 0) z += "unrecoverIpvUnbalance,";
if ((data[idx] & 0x10) != 0) z += "unrecoverVbusUnbalance,";
if ((data[idx] & 0x20) != 0) z += "unrecoverOCPInstant,";
if ((data[idx] & 0x40) != 0) z += "unrecoverPvConfigSetWrong,";
if ((data[idx] & 0x80) != 0) z += "reserved-ID72,";
//byte[9]
idx++;
if ((data[idx] & 0x1) != 0) z += "reserved-ID73,";
if ((data[idx] & 0x2) != 0) z += "unrecoverIPVInstant,";
if ((data[idx] & 0x4) != 0) z += "unrecoverWRITEEEPROM,";
if ((data[idx] & 0x8) != 0) z += "unrecoverREADEEPROM,";
if ((data[idx] & 0x10) != 0) z += "unrecoverRelayFail,";
if ((data[idx] & 0x20) != 0) z += "reserved-ID78,";
if ((data[idx] & 0x40) != 0) z += "reserved-ID79,";
if ((data[idx] & 0x80) != 0) z += "reserved-ID80,";
if(z.length() > 0){
z.pop_back();
}
return {z};
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} Battery type
id: battery_type
register_type: read
address: 0x10B0
skip_updates: 360
lambda: |-
auto z = "Unknown";
char d = data[item->offset+1];
if (d == 0) z = "DARFON";
else if (d == 1) z = "PYLON";
else if (d == 2) z = "SOLTARO";
else if (d == 3) z = "ALPHA.ESS";
else if (d == 4) z = "GENERAL";
else if (d == 100) z = "DEFAULT";
return {z};
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} Serial number
id: serial_number
register_type: read
address: 0x2001
skip_updates: 360
raw_encode: NONE
register_count: 14
response_size: 26
offset: 2
lambda: |-
return x.substr(0,14);
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} Software version
id: software_version
register_type: read
address: 0x2001
skip_updates: 360
raw_encode: NONE
register_count: 14
response_size: 26
offset: 2
lambda: |-
return x.substr(12,4);
- platform: homeassistant
entity_id: input_select.me3000sp_mode
id: mode
on_value:
- lambda: |-
if (id(mode).state == "auto")
{
ESP_LOGI("mode", "Mode set to auto");
//id(led_auto).execute();
modbus_controller::ModbusController *controller = id(me3000sp);
std::vector<uint8_t> payload = {0x1, 0x42, 0x01, 0x03, 0x00, 0x00};
modbus_controller::ModbusCommandItem command = modbus_controller::ModbusCommandItem::create_custom_command(controller, payload);
controller->queue_command(command);
}
else if (id(mode).state == "wait")
{
ESP_LOGI("mode", "Mode set to wait");
//id(led_wait).execute();
modbus_controller::ModbusController *controller = id(me3000sp);
std::vector<uint8_t> payload = {0x1, 0x42, 0x01, 0x00, 0x55, 0x55};
modbus_controller::ModbusCommandItem command = modbus_controller::ModbusCommandItem::create_custom_command(controller, payload);
controller->queue_command(command);
}
else if (id(mode).state == "battery_save")
{
ESP_LOGI("mode", "Mode set to battery_save");
}
else if (id(mode).state == "charge")
{
ESP_LOGI("mode", "Mode set to charge");
int rate = min(3000, (int) id(charge_rate).state);
//id(led_charge).execute();
if (rate > 0)
{
ESP_LOGI("charging", "Forcing charge at %d W", rate);
modbus_controller::ModbusController *controller = id(me3000sp);
std::vector<uint8_t> payload = {0x1, 0x42, 0x01, 0x02, (rate >> 8) & 0xFF, rate & 0xFF};
modbus_controller::ModbusCommandItem command = modbus_controller::ModbusCommandItem::create_custom_command(controller, payload);
controller->queue_command(command);
}
else
{
ESP_LOGI("charging", "Alert! Mode is charge, but rate is set to %d - doing nothing", rate);
}
}
sensor:
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} Grid voltage
id: grid_voltage
unit_of_measurement: "V"
device_class: voltage
register_type: holding
address: 0x0206
value_type: U_WORD
filters:
- multiply: 0.1
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} Grid current
id: grid_current
unit_of_measurement: "A"
device_class: current
register_type: holding
address: 0x0207
value_type: S_WORD
filters:
- multiply: 0.01
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} Grid frequency
id: grid_frequency
unit_of_measurement: "Hz"
device_class: frequency
register_type: holding
address: 0x020C
value_type: U_WORD
filters:
- multiply: 0.01
# read from the CT clamp?
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} Grid power
id: grid_power
register_type: holding
address: 0x0212
unit_of_measurement: "W"
device_class: power
value_type: S_WORD
filters:
- multiply: 10
####
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} Battery power
id: battery_power
unit_of_measurement: "W"
device_class: power
register_type: holding
address: 0x020d
value_type: S_WORD
filters:
- multiply: 10
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} Battery charge today
id: battery_charge_today
unit_of_measurement: "Wh"
device_class: energy
register_type: holding
address: 0x0224
skip_updates: 6
value_type: S_WORD
filters:
- multiply: 10
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} Battery discharge today
id: battery_discharge_today
unit_of_measurement: "Wh"
device_class: energy
register_type: holding
address: 0x0225
skip_updates: 6
value_type: S_WORD
filters:
- multiply: 10
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} Internal temperature
id: internal_temperature
unit_of_measurement: "C"
device_class: temperature
register_type: holding
address: 0x0238
value_type: S_WORD
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} Heat shrink temperature
id: heat_shrink_temperature
unit_of_measurement: "C"
device_class: temperature
register_type: holding
address: 0x0239
value_type: S_WORD
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} Battery voltage
id: battery_voltage
unit_of_measurement: "V"
device_class: voltage
register_type: holding
address: 0x020e
value_type: U_WORD
filters:
- multiply: .01
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} Battery current
id: battery_current
unit_of_measurement: "A"
device_class: current
register_type: holding
address: 0x020f
value_type: S_WORD
filters:
- multiply: .01
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} Battery SOC
id: battery_percent
register_type: holding
address: 0x0210
device_class: battery
unit_of_measurement: "%"
value_type: U_WORD
filters:
- lambda: |-
if (x > 0 && x <= 100) return x;
else return {};
# this is from the inverter's own temperature sensor port
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} Battery temperature
id: battery_temperature
register_type: holding
address: 0x0211
unit_of_measurement: "C"
device_class: temperature
value_type: U_WORD
# Presuambly calculated from the CT clamp and internal consumption
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} Load power
id: load_power
register_type: holding
address: 0x0213
unit_of_measurement: "W"
device_class: power
value_type: U_WORD
filters:
- multiply: 10
# EPS
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} EPS output power
id: eps_output_power
register_type: holding
address: 0x0217
unit_of_measurement: "W"
device_class: power
value_type: U_WORD
filters:
- multiply: 10
- platform: modbus_controller
modbus_controller_id: me3000sp
name: ${device_verbose_name} EPS voltage
id: eps_voltage
register_type: holding
address: 0x0216
unit_of_measurement: "V"
device_class: voltage
value_type: U_WORD
filters:
- multiply: 0.1
switch:
- platform: restart
name: ${device_verbose_name} Restart
number:
- platform: template
name: ${device_verbose_name} Charge rate
id: charge_rate
icon: "mdi:lightning-bolt"
unit_of_measurement: W
device_class: power
optimistic: true
min_value: 0
max_value: 3000
restore_value: True
initial_value: 2750
step: 1