Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions SmartEVSE-3/src/esp32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -936,11 +936,17 @@ void SetupMQTTClient() {
if (MainsMeter.Type) {
MQTTclient.announce("Mains Import Active Energy", "sensor", optional_payload);
MQTTclient.announce("Mains Export Active Energy", "sensor", optional_payload);
MQTTclient.announce("Mains Energy L1", "sensor", optional_payload);
MQTTclient.announce("Mains Energy L2", "sensor", optional_payload);
MQTTclient.announce("Mains Energy L3", "sensor", optional_payload);
}

if (EVMeter.Type) {
MQTTclient.announce("EV Import Active Energy", "sensor", optional_payload);
MQTTclient.announce("EV Export Active Energy", "sensor", optional_payload);
MQTTclient.announce("EV Energy L1", "sensor", optional_payload);
MQTTclient.announce("EV Energy L2", "sensor", optional_payload);
MQTTclient.announce("EV Energy L3", "sensor", optional_payload);
//set the parameters for and MQTTclient.announce other sensor entities:
optional_payload = MQTTclient.jsna("device_class","power") + MQTTclient.jsna("unit_of_measurement","W") + MQTTclient.jsna("state_class","measurement");
MQTTclient.announce("EV Charge Power", "sensor", optional_payload);
Expand Down Expand Up @@ -1110,6 +1116,13 @@ void mqttPublishData() {
mqtt_pub_int(MQTT_SLOT_MAINS_POWER_L1, "/MainsPowerL1", MainsMeter.Power[0], false, now_s);
mqtt_pub_int(MQTT_SLOT_MAINS_POWER_L2, "/MainsPowerL2", MainsMeter.Power[1], false, now_s);
mqtt_pub_int(MQTT_SLOT_MAINS_POWER_L3, "/MainsPowerL3", MainsMeter.Power[2], false, now_s);
// Zero-value guard: suppress per-phase energy when meter hasn't reported it
if (MainsMeter.EnergyPhase[0] > 0)
mqtt_pub_int(MQTT_SLOT_MAINS_ENERGY_L1, "/MainsEnergyL1", MainsMeter.EnergyPhase[0], false, now_s);
if (MainsMeter.EnergyPhase[1] > 0)
mqtt_pub_int(MQTT_SLOT_MAINS_ENERGY_L2, "/MainsEnergyL2", MainsMeter.EnergyPhase[1], false, now_s);
if (MainsMeter.EnergyPhase[2] > 0)
mqtt_pub_int(MQTT_SLOT_MAINS_ENERGY_L3, "/MainsEnergyL3", MainsMeter.EnergyPhase[2], false, now_s);
}
if (EVMeter.Type) {
mqtt_pub_int(MQTT_SLOT_EV_L1, "/EVCurrentL1", EVMeter.Irms[0], false, now_s);
Expand All @@ -1123,6 +1136,12 @@ void mqttPublishData() {
mqtt_pub_int(MQTT_SLOT_EV_POWER_L1, "/EVPowerL1", EVMeter.Power[0], false, now_s);
mqtt_pub_int(MQTT_SLOT_EV_POWER_L2, "/EVPowerL2", EVMeter.Power[1], false, now_s);
mqtt_pub_int(MQTT_SLOT_EV_POWER_L3, "/EVPowerL3", EVMeter.Power[2], false, now_s);
if (EVMeter.EnergyPhase[0] > 0)
mqtt_pub_int(MQTT_SLOT_EV_ENERGY_L1, "/EVEnergyL1", EVMeter.EnergyPhase[0], false, now_s);
if (EVMeter.EnergyPhase[1] > 0)
mqtt_pub_int(MQTT_SLOT_EV_ENERGY_L2, "/EVEnergyL2", EVMeter.EnergyPhase[1], false, now_s);
if (EVMeter.EnergyPhase[2] > 0)
mqtt_pub_int(MQTT_SLOT_EV_ENERGY_L3, "/EVEnergyL3", EVMeter.EnergyPhase[2], false, now_s);
}
mqtt_pub_int(MQTT_SLOT_ESP_TEMP, "/ESPTemp", TempEVSE, false, now_s);
mqtt_pub_str(MQTT_SLOT_MODE, "/Mode", AccessStatus == OFF ? "Off" : AccessStatus == PAUSE ? "Pause" : Mode > 3 ? "N/A" : StrMode[Mode], true, now_s);
Expand Down
29 changes: 29 additions & 0 deletions SmartEVSE-3/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ EXT void PowerPanicCtrl(uint8_t enable);
EXT uint8_t ReadESPdata(char *buf);

extern void requestEnergyMeasurement(uint8_t Meter, uint8_t Address, bool Export);
extern bool requestPhaseEnergyMeasurement(uint8_t Meter, uint8_t Address);
extern void requestNodeConfig(uint8_t NodeNr);
extern void requestPowerMeasurement(uint8_t Meter, uint8_t Address, uint16_t PRegister);
extern void requestNodeStatus(uint8_t NodeNr);
Expand Down Expand Up @@ -1443,6 +1444,26 @@ void requestEnergyMeasurement(uint8_t Meter, uint8_t Address, bool Export) {
requestMeasurement(Meter, Address, Register, Count);
}

/**
* Request per-phase energy measurement from meters that support it.
* Returns true if a request was sent, false if meter doesn't support per-phase energy.
*
* @param uint8_t Meter type
* @param uint8_t Address
*/
bool requestPhaseEnergyMeasurement(uint8_t Meter, uint8_t Address) {
switch (Meter) {
case EM_EASTRON3P:
case EM_EASTRON3P_INV:
// Eastron SDM630: L1/L2/L3 total active energy at registers 0x015A-0x015F
// 3 phases × 2 registers each = 6 registers (FLOAT32)
ModbusReadInputRequest(Address, EMConfig[Meter].Function, 0x015A, 6);
return true;
default:
return false;
}
}

/**
* Send Power measurement request over modbus
*
Expand Down Expand Up @@ -1992,6 +2013,14 @@ void ModbusRequestLoop() {
}
ModbusRequest++;
// fall through
case 22:
// Request per-phase energy if Mainsmeter supports it (every ~60s via energytimer)
if (MainsMeter.Type && energytimer == 0) {
if (requestPhaseEnergyMeasurement(MainsMeter.Type, MainsMeter.Address))
break;
}
ModbusRequest++;
// fall through
default:
// slave never gets here
// what about normal mode with no meters attached?
Expand Down
9 changes: 9 additions & 0 deletions SmartEVSE-3/src/meter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ Meter::Meter(uint8_t type, uint8_t address, uint8_t timeout) {
Imeasured = 0;
Import_active_energy = 0;
Export_active_energy = 0;
EnergyPhase[0] = 0;
EnergyPhase[1] = 0;
EnergyPhase[2] = 0;
Energy = 0;
#if !defined(SMARTEVSE_VERSION) || SMARTEVSE_VERSION >=30 && SMARTEVSE_VERSION < 40 //not on ESP32 v4
Timeout = timeout;
Expand Down Expand Up @@ -450,6 +453,12 @@ void Meter::ResponseToMeasurement(ModBus MB) {
else
Export_active_energy = receiveEnergyMeasurement(MB.Data);
UpdateEnergies();
} else if (MB.Register == 0x015A && (Type == EM_EASTRON3P || Type == EM_EASTRON3P_INV)) {
// Per-phase total active energy (Eastron SDM630: registers 0x015A-0x015F)
// 3 × FLOAT32 values in kWh, convert to Wh
for (int x = 0; x < 3; x++) {
EnergyPhase[x] = decodeMeasurement(MB.Data, x, EMConfig[Type].EDivisor - 3);
}
}

}
Expand Down
1 change: 1 addition & 0 deletions SmartEVSE-3/src/meter.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class Meter {
#endif
int32_t Import_active_energy; // Imported active energy
int32_t Export_active_energy; // Exported active energy
int32_t EnergyPhase[3]; // Per-phase net energy (Wh) — 0 if meter doesn't support it
int32_t Energy; // Wh -> Import_active_energy - Export_active_energy
int32_t EnergyCharged; // kWh meter value energy charged. (Wh) (will reset if state changes from A->B)
int32_t EnergyMeterStart; // kWh meter value is stored once EV is connected to EVSE (Wh)
Expand Down
8 changes: 7 additions & 1 deletion SmartEVSE-3/src/mqtt_publish.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
extern "C" {
#endif

#define MQTT_CACHE_MAX_SLOTS 72 /* 62 current topics + headroom */
#define MQTT_CACHE_MAX_SLOTS 84 /* 74 current topics + headroom */

/* One slot per published MQTT topic, in mqttPublishData() call order */
typedef enum {
Expand All @@ -21,6 +21,9 @@ typedef enum {
MQTT_SLOT_MAINS_POWER_L1,
MQTT_SLOT_MAINS_POWER_L2,
MQTT_SLOT_MAINS_POWER_L3,
MQTT_SLOT_MAINS_ENERGY_L1,
MQTT_SLOT_MAINS_ENERGY_L2,
MQTT_SLOT_MAINS_ENERGY_L3,
MQTT_SLOT_EV_L1,
MQTT_SLOT_EV_L2,
MQTT_SLOT_EV_L3,
Expand All @@ -29,6 +32,9 @@ typedef enum {
MQTT_SLOT_EV_POWER_L1,
MQTT_SLOT_EV_POWER_L2,
MQTT_SLOT_EV_POWER_L3,
MQTT_SLOT_EV_ENERGY_L1,
MQTT_SLOT_EV_ENERGY_L2,
MQTT_SLOT_EV_ENERGY_L3,
MQTT_SLOT_ESP_TEMP,
MQTT_SLOT_MODE,
MQTT_SLOT_MAX_CURRENT,
Expand Down
Loading