Compare commits

...

12 Commits

Author SHA1 Message Date
Gunnar Skjold
557ba659d5 Make sure MQTT config is updated for current connection if changed 2023-12-22 19:47:00 +01:00
Gunnar Skjold
32ad71bba6 Fixed checking "Clear all other configuration" 2023-12-21 15:56:30 +01:00
Gunnar Skjold
8816097bca Fixed receive buffer for large payloads 2023-12-21 15:25:23 +01:00
Gunnar Skjold
378b67a5bd Show when I2 is unavailable 2023-12-21 14:50:20 +01:00
Gunnar Skjold
859868c99b Use multipliers with DSMR 2023-12-21 14:23:36 +01:00
Gunnar Skjold
d7ca741c92 Some changes for HA 2023-12-21 14:12:46 +01:00
Gunnar Skjold
d49753ed33 Resend sensors to HA if online status received 2023-12-15 20:37:25 +01:00
Gunnar Skjold
2ae15ac13a Expire time on HA sensors 2023-12-15 20:19:49 +01:00
Gunnar Skjold
00278659f8 Resend sensors to HA if online status received 2023-12-15 20:03:24 +01:00
Gunnar Skjold
0c1525b018 Fixed ESP8266 for previous commit 2023-12-15 19:04:39 +01:00
Gunnar Skjold
ed778441d5 Ability to load cert/key for mqtt without ca 2023-12-15 18:50:08 +01:00
Gunnar Skjold
ed899440ed Typ-o fix 2023-12-10 09:17:44 +01:00
30 changed files with 412 additions and 271 deletions

View File

@@ -68,7 +68,7 @@ public:
bool isThreePhase(); bool isThreePhase();
bool isTwoPhase(); bool isTwoPhase();
bool isL2currentEstimated(); bool isL2currentMissing();
int8_t getLastError(); int8_t getLastError();
void setLastError(int8_t); void setLastError(int8_t);
@@ -86,7 +86,7 @@ protected:
float l1activeExportPower = 0, l2activeExportPower = 0, l3activeExportPower = 0; float l1activeExportPower = 0, l2activeExportPower = 0, l3activeExportPower = 0;
float powerFactor = 0, l1PowerFactor = 0, l2PowerFactor = 0, l3PowerFactor = 0; float powerFactor = 0, l1PowerFactor = 0, l2PowerFactor = 0, l3PowerFactor = 0;
double activeImportCounter = 0, reactiveImportCounter = 0, activeExportCounter = 0, reactiveExportCounter = 0; double activeImportCounter = 0, reactiveImportCounter = 0, activeExportCounter = 0, reactiveExportCounter = 0;
bool threePhase = false, twoPhase = false, counterEstimated = false, l2currentEstimated = false; bool threePhase = false, twoPhase = false, counterEstimated = false, l2currentMissing = false;;
int8_t lastError = 0x00; int8_t lastError = 0x00;
uint8_t lastErrorCount = 0; uint8_t lastErrorCount = 0;

View File

@@ -67,7 +67,7 @@ void AmsData::apply(AmsData& other) {
this->reactiveExportPower = other.getReactiveExportPower(); this->reactiveExportPower = other.getReactiveExportPower();
this->l1current = other.getL1Current(); this->l1current = other.getL1Current();
this->l2current = other.getL2Current(); this->l2current = other.getL2Current();
this->l2currentEstimated = other.isL2currentEstimated(); this->l2currentMissing = other.isL2currentMissing();
this->l3current = other.getL3Current(); this->l3current = other.getL3Current();
this->l1voltage = other.getL1Voltage(); this->l1voltage = other.getL1Voltage();
this->l2voltage = other.getL2Voltage(); this->l2voltage = other.getL2Voltage();
@@ -219,8 +219,8 @@ bool AmsData::isTwoPhase() {
return this->twoPhase; return this->twoPhase;
} }
bool AmsData::isL2currentEstimated() { bool AmsData::isL2currentMissing() {
return this->l2currentEstimated; return this->l2currentMissing;
} }
int8_t AmsData::getLastError() { int8_t AmsData::getLastError() {

View File

@@ -7,7 +7,11 @@
class DSMRParser { class DSMRParser {
public: public:
int8_t parse(uint8_t *buf, DataParserContext &ctx, bool verified); int8_t parse(uint8_t *buf, DataParserContext &ctx, bool verified);
uint16_t getCrc();
uint16_t getCrcCalc();
private: private:
uint16_t crc;
uint16_t crc_calc;
}; };
#endif #endif

View File

@@ -17,8 +17,8 @@ int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified) {
if(!reachedEnd) return DATA_PARSE_INCOMPLETE; if(!reachedEnd) return DATA_PARSE_INCOMPLETE;
buf[ctx.length+1] = '\0'; buf[ctx.length+1] = '\0';
if(crcPos > 0) { if(crcPos > 0) {
uint16_t crc_calc = crc16(buf, crcPos); crc_calc = crc16(buf, crcPos);
uint16_t crc = 0x0000; crc = 0x0000;
fromHex((uint8_t*) &crc, String((char*) buf+crcPos), 2); fromHex((uint8_t*) &crc, String((char*) buf+crcPos), 2);
crc = ntohs(crc); crc = ntohs(crc);
@@ -26,4 +26,11 @@ int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified) {
return DATA_PARSE_FOOTER_CHECKSUM_ERROR; return DATA_PARSE_FOOTER_CHECKSUM_ERROR;
} }
return DATA_PARSE_OK; return DATA_PARSE_OK;
}
uint16_t DSMRParser::getCrc() {
return crc;
}
uint16_t DSMRParser::getCrcCalc() {
return crc_calc;
} }

View File

@@ -23,6 +23,7 @@ public:
}; };
void setCaVerification(bool); void setCaVerification(bool);
void setConfig(MqttConfig& mqttConfig);
bool connect(); bool connect();
void disconnect(); void disconnect();
@@ -37,6 +38,7 @@ public:
virtual bool publishPrices(EntsoeApi* eapi) { return false; }; virtual bool publishPrices(EntsoeApi* eapi) { return false; };
virtual bool publishSystem(HwTools*, EntsoeApi*, EnergyAccounting*) { return false; }; virtual bool publishSystem(HwTools*, EntsoeApi*, EnergyAccounting*) { return false; };
virtual bool publishRaw(String data) { return false; }; virtual bool publishRaw(String data) { return false; };
virtual void onMessage(String &topic, String &payload) {};
virtual ~AmsMqttHandler() { virtual ~AmsMqttHandler() {
if(mqttClient != NULL) { if(mqttClient != NULL) {

View File

@@ -7,6 +7,10 @@ void AmsMqttHandler::setCaVerification(bool caVerification) {
this->caVerification = caVerification; this->caVerification = caVerification;
} }
void AmsMqttHandler::setConfig(MqttConfig& mqttConfig) {
this->mqttConfig = mqttConfig;
}
bool AmsMqttHandler::connect() { bool AmsMqttHandler::connect() {
if(millis() - lastMqttRetry < 10000) { if(millis() - lastMqttRetry < 10000) {
yield(); yield();
@@ -50,46 +54,62 @@ bool AmsMqttHandler::connect() {
} }
#endif #endif
file.close(); file.close();
if(LittleFS.exists(FILE_MQTT_CERT) && LittleFS.exists(FILE_MQTT_KEY)) {
#if defined(ESP8266)
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT certificate file (%dkb free heap)\n"), ESP.getFreeHeap());
file = LittleFS.open(FILE_MQTT_CERT, (char*) "r");
BearSSL::X509List *serverCertList = new BearSSL::X509List(file);
file.close();
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT key file (%dkb free heap)\n"), ESP.getFreeHeap());
file = LittleFS.open(FILE_MQTT_KEY, (char*) "r");
BearSSL::PrivateKey *serverPrivKey = new BearSSL::PrivateKey(file);
file.close();
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Setting client certificates (%dkb free heap)"), ESP.getFreeHeap());
mqttSecureClient->setClientRSACert(serverCertList, serverPrivKey);
#elif defined(ESP32)
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT certificate file (%dkb free heap)\n"), ESP.getFreeHeap());
file = LittleFS.open(FILE_MQTT_CERT, (char*) "r");
mqttSecureClient->loadCertificate(file, file.size());
file.close();
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT key file (%dkb free heap)\n"), ESP.getFreeHeap());
file = LittleFS.open(FILE_MQTT_KEY, (char*) "r");
mqttSecureClient->loadPrivateKey(file, file.size());
file.close();
#endif
}
} else { } else {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("No CA, disabling validation\n")); if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("No CA, disabling validation\n"));
mqttSecureClient->setInsecure(); mqttSecureClient->setInsecure();
} }
#if defined(ESP8266)
if(LittleFS.exists(FILE_MQTT_CERT) && LittleFS.exists(FILE_MQTT_KEY)) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT certificate file (%dkb free heap)\n"), ESP.getFreeHeap());
file = LittleFS.open(FILE_MQTT_CERT, (char*) "r");
BearSSL::X509List *serverCertList = new BearSSL::X509List(file);
file.close();
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT key file (%dkb free heap)\n"), ESP.getFreeHeap());
file = LittleFS.open(FILE_MQTT_KEY, (char*) "r");
BearSSL::PrivateKey *serverPrivKey = new BearSSL::PrivateKey(file);
file.close();
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Loading cert and key (%dkb free heap)\n"), ESP.getFreeHeap());
mqttSecureClient->setClientRSACert(serverCertList, serverPrivKey);
}
#endif
#if defined(ESP32)
if(LittleFS.exists(FILE_MQTT_CERT)) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT certificate file (%dkb free heap)\n"), ESP.getFreeHeap());
file = LittleFS.open(FILE_MQTT_CERT, (char*) "r");
mqttSecureClient->loadCertificate(file, file.size());
file.close();
}
if(LittleFS.exists(FILE_MQTT_KEY)) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT key file (%dkb free heap)\n"), ESP.getFreeHeap());
file = LittleFS.open(FILE_MQTT_KEY, (char*) "r");
mqttSecureClient->loadPrivateKey(file, file.size());
file.close();
}
#endif
LittleFS.end(); LittleFS.end();
} else { } else {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("CA verification disabled\n")); if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("CA verification disabled\n"));
mqttSecureClient->setInsecure(); mqttSecureClient->setInsecure();
} }
if(mqttClient != NULL) {
mqttClient->stop();
delete mqttClient;
}
mqttClient = mqttSecureClient; mqttClient = mqttSecureClient;
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("MQTT SSL setup complete (%dkb free heap)\n"), ESP.getFreeHeap()); if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("MQTT SSL setup complete (%dkb free heap)\n"), ESP.getFreeHeap());
} }
} else if(mqttSecureClient != NULL) {
mqttSecureClient->stop();
delete mqttSecureClient;
mqttSecureClient = NULL;
mqttClient = NULL;
} }
if(mqttClient == NULL) { if(mqttClient == NULL) {
@@ -112,7 +132,14 @@ bool AmsMqttHandler::connect() {
// Connect to a unsecure or secure MQTT server // Connect to a unsecure or secure MQTT server
if ((strlen(mqttConfig.username) == 0 && mqtt.connect(mqttConfig.clientId)) || if ((strlen(mqttConfig.username) == 0 && mqtt.connect(mqttConfig.clientId)) ||
(strlen(mqttConfig.username) > 0 && mqtt.connect(mqttConfig.clientId, mqttConfig.username, mqttConfig.password))) { (strlen(mqttConfig.username) > 0 && mqtt.connect(mqttConfig.clientId, mqttConfig.username, mqttConfig.password))) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Successfully connected to MQTT!\n")); if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Successfully connected to MQTT\n"));
mqtt.onMessage(std::bind(&AmsMqttHandler::onMessage, this, std::placeholders::_1, std::placeholders::_2));
if(strlen(mqttConfig.subscribeTopic) > 0) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR(" Subscribing to [%s]\n"), mqttConfig.subscribeTopic);
if(!mqtt.subscribe(mqttConfig.subscribeTopic)) {
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR(" Unable to subscribe to to [%s]\n"), mqttConfig.subscribeTopic);
}
}
return true; return true;
} else { } else {
if (debugger->isActive(RemoteDebug::ERROR)) { if (debugger->isActive(RemoteDebug::ERROR)) {

View File

@@ -15,6 +15,8 @@ public:
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea); bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
bool publishRaw(String data); bool publishRaw(String data);
void onMessage(String &topic, String &payload);
uint8_t getFormat(); uint8_t getFormat();
private: private:

View File

@@ -82,3 +82,6 @@ uint8_t DomoticzMqttHandler::getFormat() {
bool DomoticzMqttHandler::publishRaw(String data) { bool DomoticzMqttHandler::publishRaw(String data) {
return false; return false;
} }
void DomoticzMqttHandler::onMessage(String &topic, String &payload) {
}

View File

@@ -9,6 +9,7 @@ class HomeAssistantMqttHandler : public AmsMqttHandler {
public: public:
HomeAssistantMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, uint8_t boardType, HomeAssistantConfig config, HwTools* hw) : AmsMqttHandler(mqttConfig, debugger, buf) { HomeAssistantMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, uint8_t boardType, HomeAssistantConfig config, HwTools* hw) : AmsMqttHandler(mqttConfig, debugger, buf) {
this->hw = hw; this->hw = hw;
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = false; l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = false;
topic = String(mqttConfig.publishTopic); topic = String(mqttConfig.publishTopic);
@@ -46,18 +47,26 @@ public:
} }
if(strlen(config.discoveryPrefix) > 0) { if(strlen(config.discoveryPrefix) > 0) {
snprintf_P(json, 128, PSTR("%s/status"), config.discoveryPrefix);
statusTopic = String(buf);
snprintf_P(buf, 128, PSTR("%s/sensor/"), config.discoveryPrefix); snprintf_P(buf, 128, PSTR("%s/sensor/"), config.discoveryPrefix);
discoveryTopic = String(buf); discoveryTopic = String(buf);
} else { } else {
statusTopic = F("homeassistant/status");
discoveryTopic = F("homeassistant/sensor/"); discoveryTopic = F("homeassistant/sensor/");
} }
strcpy(this->mqttConfig.subscribeTopic, statusTopic.c_str());
}; };
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi); bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi);
bool publishTemperatures(AmsConfiguration*, HwTools*); bool publishTemperatures(AmsConfiguration*, HwTools*);
bool publishPrices(EntsoeApi*); bool publishPrices(EntsoeApi*);
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea); bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
bool publishRaw(String data); bool publishRaw(String data);
void onMessage(String &topic, String &payload);
uint8_t getFormat(); uint8_t getFormat();
private: private:
@@ -69,6 +78,7 @@ private:
String manufacturer; String manufacturer;
String deviceUrl; String deviceUrl;
String statusTopic;
String discoveryTopic; String discoveryTopic;
String sensorNamePrefix; String sensorNamePrefix;

View File

@@ -7,6 +7,7 @@ struct HomeAssistantSensor {
const char* name; const char* name;
const char* topic; const char* topic;
const char* path; const char* path;
const uint16_t ttl;
const char* uom; const char* uom;
const char* devcl; const char* devcl;
const char* stacl; const char* stacl;
@@ -15,98 +16,98 @@ struct HomeAssistantSensor {
const uint8_t List1SensorCount PROGMEM = 1; const uint8_t List1SensorCount PROGMEM = 1;
const HomeAssistantSensor List1Sensors[List1SensorCount] PROGMEM = { const HomeAssistantSensor List1Sensors[List1SensorCount] PROGMEM = {
{"Active import", "/power", "P", "W", "power", "measurement"} {"Active import", "/power", "P", 30, "W", "power", "measurement"}
}; };
const uint8_t List2SensorCount PROGMEM = 8; const uint8_t List2SensorCount PROGMEM = 8;
const HomeAssistantSensor List2Sensors[List2SensorCount] PROGMEM = { const HomeAssistantSensor List2Sensors[List2SensorCount] PROGMEM = {
{"Reactive import", "/power", "Q", "var", "reactive_power", "measurement"}, {"Reactive import", "/power", "Q", 30, "var", "reactive_power", "measurement"},
{"Reactive export", "/power", "QO", "var", "reactive_power", "measurement"}, {"Reactive export", "/power", "QO", 30, "var", "reactive_power", "measurement"},
{"L1 current", "/power", "I1", "A", "current", "measurement"}, {"L1 current", "/power", "I1", 30, "A", "current", "measurement"},
{"L2 current", "/power", "I2", "A", "current", "measurement"}, {"L2 current", "/power", "I2", 30, "A", "current", "measurement"},
{"L3 current", "/power", "I3", "A", "current", "measurement"}, {"L3 current", "/power", "I3", 30, "A", "current", "measurement"},
{"L1 voltage", "/power", "U1", "V", "voltage", "measurement"}, {"L1 voltage", "/power", "U1", 30, "V", "voltage", "measurement"},
{"L2 voltage", "/power", "U2", "V", "voltage", "measurement"}, {"L2 voltage", "/power", "U2", 30, "V", "voltage", "measurement"},
{"L3 voltage", "/power", "U3", "V", "voltage", "measurement"} {"L3 voltage", "/power", "U3", 30, "V", "voltage", "measurement"}
}; };
const uint8_t List2ExportSensorCount PROGMEM = 1; const uint8_t List2ExportSensorCount PROGMEM = 1;
const HomeAssistantSensor List2ExportSensors[List2ExportSensorCount] PROGMEM = { const HomeAssistantSensor List2ExportSensors[List2ExportSensorCount] PROGMEM = {
{"Active export", "/power", "PO", "W", "power", "measurement"} {"Active export", "/power", "PO", 30, "W", "power", "measurement"}
}; };
const uint8_t List3SensorCount PROGMEM = 3; const uint8_t List3SensorCount PROGMEM = 3;
const HomeAssistantSensor List3Sensors[List3SensorCount] PROGMEM = { const HomeAssistantSensor List3Sensors[List3SensorCount] PROGMEM = {
{"Accumulated active import", "/energy", "tPI", "kWh", "energy", "total_increasing"}, {"Accumulated active import", "/energy", "tPI", 4000, "kWh", "energy", "total_increasing"},
{"Accumulated reactive import","/energy", "tQI", "kvarh","", "total_increasing"}, {"Accumulated reactive import","/energy", "tQI", 4000, "kvarh","", "total_increasing"},
{"Accumulated reactive export","/energy", "tQO", "kvarh","", "total_increasing"} {"Accumulated reactive export","/energy", "tQO", 4000, "kvarh","", "total_increasing"}
}; };
const uint8_t List3ExportSensorCount PROGMEM = 1; const uint8_t List3ExportSensorCount PROGMEM = 1;
const HomeAssistantSensor List3ExportSensors[List3ExportSensorCount] PROGMEM = { const HomeAssistantSensor List3ExportSensors[List3ExportSensorCount] PROGMEM = {
{"Accumulated active export", "/energy", "tPO", "kWh", "energy", "total_increasing"} {"Accumulated active export", "/energy", "tPO", 4000, "kWh", "energy", "total_increasing"}
}; };
const uint8_t List4SensorCount PROGMEM = 7; const uint8_t List4SensorCount PROGMEM = 7;
const HomeAssistantSensor List4Sensors[List4SensorCount] PROGMEM = { const HomeAssistantSensor List4Sensors[List4SensorCount] PROGMEM = {
{"Power factor", "/power", "PF", "%", "power_factor", "measurement"}, {"Power factor", "/power", "PF", 30, "%", "power_factor", "measurement"},
{"L1 power factor", "/power", "PF1", "%", "power_factor", "measurement"}, {"L1 power factor", "/power", "PF1", 30, "%", "power_factor", "measurement"},
{"L2 power factor", "/power", "PF2", "%", "power_factor", "measurement"}, {"L2 power factor", "/power", "PF2", 30, "%", "power_factor", "measurement"},
{"L3 power factor", "/power", "PF3", "%", "power_factor", "measurement"}, {"L3 power factor", "/power", "PF3", 30, "%", "power_factor", "measurement"},
{"L1 active import", "/power", "P1", "W", "power", "measurement"}, {"L1 active import", "/power", "P1", 30, "W", "power", "measurement"},
{"L2 active import", "/power", "P2", "W", "power", "measurement"}, {"L2 active import", "/power", "P2", 30, "W", "power", "measurement"},
{"L3 active import", "/power", "P3", "W", "power", "measurement"} {"L3 active import", "/power", "P3", 30, "W", "power", "measurement"}
}; };
const uint8_t List4ExportSensorCount PROGMEM = 3; const uint8_t List4ExportSensorCount PROGMEM = 3;
const HomeAssistantSensor List4ExportSensors[List4ExportSensorCount] PROGMEM = { const HomeAssistantSensor List4ExportSensors[List4ExportSensorCount] PROGMEM = {
{"L1 active export", "/power", "PO1", "W", "power", "measurement"}, {"L1 active export", "/power", "PO1", 30, "W", "power", "measurement"},
{"L2 active export", "/power", "PO2", "W", "power", "measurement"}, {"L2 active export", "/power", "PO2", 30, "W", "power", "measurement"},
{"L3 active export", "/power", "PO3", "W", "power", "measurement"} {"L3 active export", "/power", "PO3", 30, "W", "power", "measurement"}
}; };
const uint8_t RealtimeSensorCount PROGMEM = 8; const uint8_t RealtimeSensorCount PROGMEM = 8;
const HomeAssistantSensor RealtimeSensors[RealtimeSensorCount] PROGMEM = { const HomeAssistantSensor RealtimeSensors[RealtimeSensorCount] PROGMEM = {
{"Month max", "/realtime","max", "kWh", "energy", "total_increasing"}, {"Month max", "/realtime","max", 120, "kWh", "energy", "total_increasing"},
{"Tariff threshold", "/realtime","threshold", "kWh", "energy", "total_increasing"}, {"Tariff threshold", "/realtime","threshold", 120, "kWh", "energy", "total_increasing"},
{"Current hour used", "/realtime","hour.use", "kWh", "energy", "total_increasing"}, {"Current hour used", "/realtime","hour.use", 120, "kWh", "energy", "total_increasing"},
{"Current hour cost", "/realtime","hour.cost", "", "monetary", ""}, {"Current hour cost", "/realtime","hour.cost", 120, "", "monetary", ""},
{"Current day used", "/realtime","day.use", "kWh", "energy", "total_increasing"}, {"Current day used", "/realtime","day.use", 120, "kWh", "energy", "total_increasing"},
{"Current day cost", "/realtime","day.cost", "", "monetary", ""}, {"Current day cost", "/realtime","day.cost", 120, "", "monetary", ""},
{"Current month used", "/realtime","month.use", "kWh", "energy", "total_increasing"}, {"Current month used", "/realtime","month.use", 120, "kWh", "energy", "total_increasing"},
{"Current month cost", "/realtime","month.cost", "", "monetary", ""} {"Current month cost", "/realtime","month.cost", 120, "", "monetary", ""}
}; };
const uint8_t RealtimeExportSensorCount PROGMEM = 6; const uint8_t RealtimeExportSensorCount PROGMEM = 6;
const HomeAssistantSensor RealtimeExportSensors[RealtimeExportSensorCount] PROGMEM = { const HomeAssistantSensor RealtimeExportSensors[RealtimeExportSensorCount] PROGMEM = {
{"Current hour produced", "/realtime","hour.produced", "kWh", "energy", "total_increasing"}, {"Current hour produced", "/realtime","hour.produced", 120, "kWh", "energy", "total_increasing"},
{"Current hour income", "/realtime","hour.income", "", "monetary", ""}, {"Current hour income", "/realtime","hour.income", 120, "", "monetary", ""},
{"Current day produced", "/realtime","day.produced", "kWh", "energy", "total_increasing"}, {"Current day produced", "/realtime","day.produced", 120, "kWh", "energy", "total_increasing"},
{"Current day income", "/realtime","day.income", "", "monetary", ""}, {"Current day income", "/realtime","day.income", 120, "", "monetary", ""},
{"Current month produced", "/realtime","month.produced", "kWh", "energy", "total_increasing"}, {"Current month produced", "/realtime","month.produced", 120, "kWh", "energy", "total_increasing"},
{"Current month income", "/realtime","month.income", "", "monetary", ""} {"Current month income", "/realtime","month.income", 120, "", "monetary", ""}
}; };
const HomeAssistantSensor RealtimePeakSensor PROGMEM = {"Current month peak %d", "/realtime", "peaks[%d]", "kWh", "energy", ""}; const HomeAssistantSensor RealtimePeakSensor PROGMEM = {"Current month peak %d", "/realtime", "peaks[%d]", 4000, "kWh", "energy", ""};
const uint8_t PriceSensorCount PROGMEM = 5; const uint8_t PriceSensorCount PROGMEM = 5;
const HomeAssistantSensor PriceSensors[PriceSensorCount] PROGMEM = { const HomeAssistantSensor PriceSensors[PriceSensorCount] PROGMEM = {
{"Minimum price ahead", "/prices", "prices.min", "", "monetary", ""}, {"Minimum price ahead", "/prices", "prices.min", 4000, "", "monetary", ""},
{"Maximum price ahead", "/prices", "prices.max", "", "monetary", ""}, {"Maximum price ahead", "/prices", "prices.max", 4000, "", "monetary", ""},
{"Cheapest 1hr period ahead", "/prices", "prices.cheapest1hr","", "timestamp", ""}, {"Cheapest 1hr period ahead", "/prices", "prices.cheapest1hr",4000, "", "timestamp", ""},
{"Cheapest 3hr period ahead", "/prices", "prices.cheapest3hr","", "timestamp", ""}, {"Cheapest 3hr period ahead", "/prices", "prices.cheapest3hr",4000, "", "timestamp", ""},
{"Cheapest 6hr period ahead", "/prices", "prices.cheapest6hr","", "timestamp", ""} {"Cheapest 6hr period ahead", "/prices", "prices.cheapest6hr",4000, "", "timestamp", ""}
}; };
const HomeAssistantSensor PriceSensor PROGMEM = {"Price in %02d %s", "/prices", "prices['%d']", "", "monetary", ""}; const HomeAssistantSensor PriceSensor PROGMEM = {"Price in %02d %s", "/prices", "prices['%d']", 4000, "", "monetary", ""};
const uint8_t SystemSensorCount PROGMEM = 2; const uint8_t SystemSensorCount PROGMEM = 2;
const HomeAssistantSensor SystemSensors[SystemSensorCount] PROGMEM = { const HomeAssistantSensor SystemSensors[SystemSensorCount] PROGMEM = {
{"Status", "/state", "rssi", "dBm", "signal_strength", "measurement"}, {"Status", "/state", "rssi", 180, "dBm", "signal_strength", "measurement"},
{"Supply volt", "/state", "vcc", "V", "voltage", "measurement"} {"Supply volt", "/state", "vcc", 180, "V", "voltage", "measurement"}
}; };
const HomeAssistantSensor TemperatureSensor PROGMEM = {"Temperature sensor %s", "/temperatures", "temperatures['%s']", "°C", "temperature", "measurement"}; const HomeAssistantSensor TemperatureSensor PROGMEM = {"Temperature sensor %s", "/temperatures", "temperatures['%s']", 900, "°C", "temperature", "measurement"};
#endif #endif

View File

@@ -5,6 +5,7 @@
"obj_id" : "%s_%s", "obj_id" : "%s_%s",
"unit_of_meas" : "%s", "unit_of_meas" : "%s",
"val_tpl" : "{{ value_json.%s | is_defined }}", "val_tpl" : "{{ value_json.%s | is_defined }}",
"expire_after" : %d,
"dev" : { "dev" : {
"ids" : [ "%s" ], "ids" : [ "%s" ],
"name" : "%s", "name" : "%s",

View File

@@ -10,6 +10,7 @@
#include "json/jsonprices_json.h" #include "json/jsonprices_json.h"
#include "json/hadiscover_json.h" #include "json/hadiscover_json.h"
#include "json/realtime_json.h" #include "json/realtime_json.h"
#include "FirmwareVersion.h"
#if defined(ESP32) #if defined(ESP32)
#include <esp_task_wdt.h> #include <esp_task_wdt.h>
@@ -19,6 +20,9 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En
if(topic.isEmpty() || !mqtt.connected()) if(topic.isEmpty() || !mqtt.connected())
return false; return false;
if(time(nullptr) < FirmwareVersion::BuildEpoch)
return false;
if(data->getListType() >= 3) { // publish energy counts if(data->getListType() >= 3) { // publish energy counts
publishList3(data, ea); publishList3(data, ea);
loop(); loop();
@@ -42,9 +46,7 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En
bool HomeAssistantMqttHandler::publishList1(AmsData* data, EnergyAccounting* ea) { bool HomeAssistantMqttHandler::publishList1(AmsData* data, EnergyAccounting* ea) {
publishList1Sensors(); publishList1Sensors();
snprintf_P(json, BufferSize, HA1_JSON, snprintf_P(json, BufferSize, HA1_JSON, data->getActiveImportPower());
data->getActiveImportPower()
);
return mqtt.publish(topic + "/power", json); return mqtt.publish(topic + "/power", json);
} }
@@ -263,47 +265,46 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
breakTime(ts, tm); breakTime(ts, tm);
sprintf_P(ts6hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour); sprintf_P(ts6hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
} }
snprintf_P(json, BufferSize, JSONPRICES_JSON, snprintf_P(json, BufferSize, JSONPRICES_JSON,
WiFi.macAddress().c_str(), WiFi.macAddress().c_str(),
values[0], values[0] == ENTSOE_NO_VALUE ? "null" : String(values[0], 4).c_str(),
values[1], values[1] == ENTSOE_NO_VALUE ? "null" : String(values[1], 4).c_str(),
values[2], values[2] == ENTSOE_NO_VALUE ? "null" : String(values[2], 4).c_str(),
values[3], values[3] == ENTSOE_NO_VALUE ? "null" : String(values[3], 4).c_str(),
values[4], values[4] == ENTSOE_NO_VALUE ? "null" : String(values[4], 4).c_str(),
values[5], values[5] == ENTSOE_NO_VALUE ? "null" : String(values[5], 4).c_str(),
values[6], values[6] == ENTSOE_NO_VALUE ? "null" : String(values[6], 4).c_str(),
values[7], values[7] == ENTSOE_NO_VALUE ? "null" : String(values[7], 4).c_str(),
values[8], values[8] == ENTSOE_NO_VALUE ? "null" : String(values[8], 4).c_str(),
values[9], values[9] == ENTSOE_NO_VALUE ? "null" : String(values[9], 4).c_str(),
values[10], values[10] == ENTSOE_NO_VALUE ? "null" : String(values[10], 4).c_str(),
values[11], values[11] == ENTSOE_NO_VALUE ? "null" : String(values[11], 4).c_str(),
values[12], values[12] == ENTSOE_NO_VALUE ? "null" : String(values[12], 4).c_str(),
values[13], values[13] == ENTSOE_NO_VALUE ? "null" : String(values[13], 4).c_str(),
values[14], values[14] == ENTSOE_NO_VALUE ? "null" : String(values[14], 4).c_str(),
values[15], values[15] == ENTSOE_NO_VALUE ? "null" : String(values[15], 4).c_str(),
values[16], values[16] == ENTSOE_NO_VALUE ? "null" : String(values[16], 4).c_str(),
values[17], values[17] == ENTSOE_NO_VALUE ? "null" : String(values[17], 4).c_str(),
values[18], values[18] == ENTSOE_NO_VALUE ? "null" : String(values[18], 4).c_str(),
values[19], values[19] == ENTSOE_NO_VALUE ? "null" : String(values[19], 4).c_str(),
values[20], values[20] == ENTSOE_NO_VALUE ? "null" : String(values[20], 4).c_str(),
values[21], values[21] == ENTSOE_NO_VALUE ? "null" : String(values[21], 4).c_str(),
values[22], values[22] == ENTSOE_NO_VALUE ? "null" : String(values[22], 4).c_str(),
values[23], values[23] == ENTSOE_NO_VALUE ? "null" : String(values[23], 4).c_str(),
values[24], values[24] == ENTSOE_NO_VALUE ? "null" : String(values[24], 4).c_str(),
values[25], values[25] == ENTSOE_NO_VALUE ? "null" : String(values[25], 4).c_str(),
values[26], values[26] == ENTSOE_NO_VALUE ? "null" : String(values[26], 4).c_str(),
values[27], values[27] == ENTSOE_NO_VALUE ? "null" : String(values[27], 4).c_str(),
values[28], values[28] == ENTSOE_NO_VALUE ? "null" : String(values[28], 4).c_str(),
values[29], values[29] == ENTSOE_NO_VALUE ? "null" : String(values[29], 4).c_str(),
values[30], values[30] == ENTSOE_NO_VALUE ? "null" : String(values[30], 4).c_str(),
values[31], values[31] == ENTSOE_NO_VALUE ? "null" : String(values[31], 4).c_str(),
values[32], values[32] == ENTSOE_NO_VALUE ? "null" : String(values[32], 4).c_str(),
values[33], values[33] == ENTSOE_NO_VALUE ? "null" : String(values[33], 4).c_str(),
values[34], values[34] == ENTSOE_NO_VALUE ? "null" : String(values[34], 4).c_str(),
values[35], values[35] == ENTSOE_NO_VALUE ? "null" : String(values[35], 4).c_str(),
values[36], values[36] == ENTSOE_NO_VALUE ? "null" : String(values[36], 4).c_str(),
values[37], values[37] == ENTSOE_NO_VALUE ? "null" : String(values[37], 4).c_str(),
min == INT16_MAX ? 0.0 : min, min == INT16_MAX ? 0.0 : min,
max == INT16_MIN ? 0.0 : max, max == INT16_MIN ? 0.0 : max,
ts1hr, ts1hr,
@@ -350,6 +351,7 @@ void HomeAssistantMqttHandler::publishSensor(const HomeAssistantSensor& sensor)
deviceUid.c_str(), uid.c_str(), deviceUid.c_str(), uid.c_str(),
sensor.uom, sensor.uom,
sensor.path, sensor.path,
sensor.ttl,
deviceUid.c_str(), deviceUid.c_str(),
deviceName.c_str(), deviceName.c_str(),
deviceModel.c_str(), deviceModel.c_str(),
@@ -449,6 +451,7 @@ void HomeAssistantMqttHandler::publishRealtimeSensors(EnergyAccounting* ea, Ents
name, name,
RealtimePeakSensor.topic, RealtimePeakSensor.topic,
path, path,
RealtimePeakSensor.ttl,
RealtimePeakSensor.uom, RealtimePeakSensor.uom,
RealtimePeakSensor.devcl, RealtimePeakSensor.devcl,
RealtimePeakSensor.stacl RealtimePeakSensor.stacl
@@ -487,6 +490,7 @@ void HomeAssistantMqttHandler::publishTemperatureSensor(uint8_t index, String id
name, name,
index == 0 ? SystemSensors[0].topic : TemperatureSensor.topic, index == 0 ? SystemSensors[0].topic : TemperatureSensor.topic,
path, path,
TemperatureSensor.ttl,
TemperatureSensor.uom, TemperatureSensor.uom,
TemperatureSensor.devcl, TemperatureSensor.devcl,
TemperatureSensor.stacl TemperatureSensor.stacl
@@ -522,6 +526,7 @@ void HomeAssistantMqttHandler::publishPriceSensors(EntsoeApi* eapi) {
i == 0 ? "Price current hour" : name, i == 0 ? "Price current hour" : name,
PriceSensor.topic, PriceSensor.topic,
path, path,
PriceSensor.ttl,
uom.c_str(), uom.c_str(),
PriceSensor.devcl, PriceSensor.devcl,
i == 0 ? "total" : PriceSensor.stacl i == 0 ? "total" : PriceSensor.stacl
@@ -547,3 +552,14 @@ uint8_t HomeAssistantMqttHandler::getFormat() {
bool HomeAssistantMqttHandler::publishRaw(String data) { bool HomeAssistantMqttHandler::publishRaw(String data) {
return false; return false;
} }
void HomeAssistantMqttHandler::onMessage(String &topic, String &payload) {
if(topic.equals(statusTopic)) {
if(payload.equals("online")) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Received online status from HA, resetting sensor status\n"));
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = false;
for(uint8_t i = 0; i < 32; i++) tInit[i] = false;
for(uint8_t i = 0; i < 38; i++) prInit[i] = false;
}
}
}

View File

@@ -14,6 +14,8 @@ public:
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea); bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
bool publishRaw(String data); bool publishRaw(String data);
void onMessage(String &topic, String &payload);
uint8_t getFormat(); uint8_t getFormat();
private: private:

View File

@@ -1,44 +1,44 @@
{ {
"id" : "%s", "id" : "%s",
"prices" : { "prices" : {
"0" : %.4f, "0" : %s,
"1" : %.4f, "1" : %s,
"2" : %.4f, "2" : %s,
"3" : %.4f, "3" : %s,
"4" : %.4f, "4" : %s,
"5" : %.4f, "5" : %s,
"6" : %.4f, "6" : %s,
"7" : %.4f, "7" : %s,
"8" : %.4f, "8" : %s,
"9" : %.4f, "9" : %s,
"10" : %.4f, "10" : %s,
"11" : %.4f, "11" : %s,
"12" : %.4f, "12" : %s,
"13" : %.4f, "13" : %s,
"14" : %.4f, "14" : %s,
"15" : %.4f, "15" : %s,
"16" : %.4f, "16" : %s,
"17" : %.4f, "17" : %s,
"18" : %.4f, "18" : %s,
"19" : %.4f, "19" : %s,
"20" : %.4f, "20" : %s,
"21" : %.4f, "21" : %s,
"22" : %.4f, "22" : %s,
"23" : %.4f, "23" : %s,
"24" : %.4f, "24" : %s,
"25" : %.4f, "25" : %s,
"26" : %.4f, "26" : %s,
"27" : %.4f, "27" : %s,
"28" : %.4f, "28" : %s,
"29" : %.4f, "29" : %s,
"30" : %.4f, "30" : %s,
"31" : %.4f, "31" : %s,
"32" : %.4f, "32" : %s,
"33" : %.4f, "33" : %s,
"34" : %.4f, "34" : %s,
"35" : %.4f, "35" : %s,
"36" : %.4f, "36" : %s,
"37" : %.4f, "37" : %s,
"min" : %.4f, "min" : %.4f,
"max" : %.4f, "max" : %.4f,
"cheapest1hr" : "%s", "cheapest1hr" : "%s",

View File

@@ -3,7 +3,7 @@
"name" : "%s", "name" : "%s",
"up" : %d, "up" : %d,
"vcc" : %.3f, "vcc" : %.3f,
"rssi": %d, "rssi" : %d,
"temp": %.2f, "temp" : %.2f,
"version": "%s" "version" : "%s"
} }

View File

@@ -288,44 +288,44 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
snprintf_P(json, BufferSize, JSONPRICES_JSON, snprintf_P(json, BufferSize, JSONPRICES_JSON,
WiFi.macAddress().c_str(), WiFi.macAddress().c_str(),
values[0], values[0] == ENTSOE_NO_VALUE ? "null" : String(values[0], 4).c_str(),
values[1], values[1] == ENTSOE_NO_VALUE ? "null" : String(values[1], 4).c_str(),
values[2], values[2] == ENTSOE_NO_VALUE ? "null" : String(values[2], 4).c_str(),
values[3], values[3] == ENTSOE_NO_VALUE ? "null" : String(values[3], 4).c_str(),
values[4], values[4] == ENTSOE_NO_VALUE ? "null" : String(values[4], 4).c_str(),
values[5], values[5] == ENTSOE_NO_VALUE ? "null" : String(values[5], 4).c_str(),
values[6], values[6] == ENTSOE_NO_VALUE ? "null" : String(values[6], 4).c_str(),
values[7], values[7] == ENTSOE_NO_VALUE ? "null" : String(values[7], 4).c_str(),
values[8], values[8] == ENTSOE_NO_VALUE ? "null" : String(values[8], 4).c_str(),
values[9], values[9] == ENTSOE_NO_VALUE ? "null" : String(values[9], 4).c_str(),
values[10], values[10] == ENTSOE_NO_VALUE ? "null" : String(values[10], 4).c_str(),
values[11], values[11] == ENTSOE_NO_VALUE ? "null" : String(values[11], 4).c_str(),
values[12], values[12] == ENTSOE_NO_VALUE ? "null" : String(values[12], 4).c_str(),
values[13], values[13] == ENTSOE_NO_VALUE ? "null" : String(values[13], 4).c_str(),
values[14], values[14] == ENTSOE_NO_VALUE ? "null" : String(values[14], 4).c_str(),
values[15], values[15] == ENTSOE_NO_VALUE ? "null" : String(values[15], 4).c_str(),
values[16], values[16] == ENTSOE_NO_VALUE ? "null" : String(values[16], 4).c_str(),
values[17], values[17] == ENTSOE_NO_VALUE ? "null" : String(values[17], 4).c_str(),
values[18], values[18] == ENTSOE_NO_VALUE ? "null" : String(values[18], 4).c_str(),
values[19], values[19] == ENTSOE_NO_VALUE ? "null" : String(values[19], 4).c_str(),
values[20], values[20] == ENTSOE_NO_VALUE ? "null" : String(values[20], 4).c_str(),
values[21], values[21] == ENTSOE_NO_VALUE ? "null" : String(values[21], 4).c_str(),
values[22], values[22] == ENTSOE_NO_VALUE ? "null" : String(values[22], 4).c_str(),
values[23], values[23] == ENTSOE_NO_VALUE ? "null" : String(values[23], 4).c_str(),
values[24], values[24] == ENTSOE_NO_VALUE ? "null" : String(values[24], 4).c_str(),
values[25], values[25] == ENTSOE_NO_VALUE ? "null" : String(values[25], 4).c_str(),
values[26], values[26] == ENTSOE_NO_VALUE ? "null" : String(values[26], 4).c_str(),
values[27], values[27] == ENTSOE_NO_VALUE ? "null" : String(values[27], 4).c_str(),
values[28], values[28] == ENTSOE_NO_VALUE ? "null" : String(values[28], 4).c_str(),
values[29], values[29] == ENTSOE_NO_VALUE ? "null" : String(values[29], 4).c_str(),
values[30], values[30] == ENTSOE_NO_VALUE ? "null" : String(values[30], 4).c_str(),
values[31], values[31] == ENTSOE_NO_VALUE ? "null" : String(values[31], 4).c_str(),
values[32], values[32] == ENTSOE_NO_VALUE ? "null" : String(values[32], 4).c_str(),
values[33], values[33] == ENTSOE_NO_VALUE ? "null" : String(values[33], 4).c_str(),
values[34], values[34] == ENTSOE_NO_VALUE ? "null" : String(values[34], 4).c_str(),
values[35], values[35] == ENTSOE_NO_VALUE ? "null" : String(values[35], 4).c_str(),
values[36], values[36] == ENTSOE_NO_VALUE ? "null" : String(values[36], 4).c_str(),
values[37], values[37] == ENTSOE_NO_VALUE ? "null" : String(values[37], 4).c_str(),
min == INT16_MAX ? 0.0 : min, min == INT16_MAX ? 0.0 : min,
max == INT16_MIN ? 0.0 : max, max == INT16_MIN ? 0.0 : max,
ts1hr, ts1hr,
@@ -362,3 +362,6 @@ uint8_t JsonMqttHandler::getFormat() {
bool JsonMqttHandler::publishRaw(String data) { bool JsonMqttHandler::publishRaw(String data) {
return false; return false;
} }
void JsonMqttHandler::onMessage(String &topic, String &payload) {
}

View File

@@ -15,6 +15,8 @@ public:
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea); bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
bool publishRaw(String data); bool publishRaw(String data);
void onMessage(String &topic, String &payload);
uint8_t getFormat(); uint8_t getFormat();
private: private:

View File

@@ -287,3 +287,6 @@ uint8_t RawMqttHandler::getFormat() {
bool RawMqttHandler::publishRaw(String data) { bool RawMqttHandler::publishRaw(String data) {
return false; return false;
} }
void RawMqttHandler::onMessage(String &topic, String &payload) {
}

File diff suppressed because one or more lines are too long

View File

@@ -13,12 +13,12 @@
let config = {}; let config = {};
function point(v,e) { function point(v) {
return { return {
label: fmtnum(v) + 'A', label: fmtnum(v) + 'A',
title: (e ? 'Estimated ' : '') + v.toFixed(1) + ' A', title: v.toFixed(1) + ' A',
value: isNaN(v) ? 0 : v, value: isNaN(v) ? 0 : v,
color: ampcol(v ? (v)/(max)*100 : 0, e) color: ampcol(v ? (v)/(max)*100 : 0)
}; };
}; };
@@ -30,8 +30,19 @@
points.push(point(i1)); points.push(point(i1));
} }
if(u2 > 0) { if(u2 > 0) {
xTicks.push({ label: 'L2' }); if(i2e) {
points.push(point(i2, i2e)); xTicks.push({ label: 'L2' });
points.push({
label: 'N/A',
labelAngle: 90,
title: 'The value is not reported by your meter',
value: 0,
color: '#7c3aedcc'
});
} else {
xTicks.push({ label: 'L2' });
points.push(point(i2));
}
} }
if(u3 > 0) { if(u3 > 0) {
xTicks.push({ label: 'L3' }); xTicks.push({ label: 'L3' });

View File

@@ -11,7 +11,7 @@ export function voltcol(volt) {
return '#d90000'; return '#d90000';
}; };
export function ampcol(pct, est) { export function ampcol(pct) {
let col; let col;
if(pct > 90) col = '#d90000'; if(pct > 90) col = '#d90000';
else if(pct > 85) col = '#e32100'; else if(pct > 85) col = '#e32100';
@@ -19,7 +19,7 @@ export function ampcol(pct, est) {
else if(pct > 75) col = '#dcd800'; else if(pct > 75) col = '#dcd800';
else col = '#32d900'; else col = '#32d900';
return col+(est?'88':''); return col;
}; };
export function exportcol(pct) { export function exportcol(pct) {
@@ -258,7 +258,7 @@ export function getResetReason(sysinfo) {
export function getPriceSourceName(code) { export function getPriceSourceName(code) {
if(code == "EOE") return "ENTSO-E"; if(code == "EOE") return "ENTSO-E";
if(code == "HKS") return "hvakosterstrommen.no"; if(code == "HKS") return "hvakosterstrommen.no";
if(code == "EDS") return "Energy Data Service"; if(code == "EDS") return "Energi Data Service";
if(code == "MIX") return "Mixed sources"; if(code == "MIX") return "Mixed sources";
return "Unknown (" + code + ")"; return "Unknown (" + code + ")";
} }

View File

@@ -33,9 +33,12 @@
} }
let cc = false; let cc = false;
$: { sysinfoStore.subscribe(update => {
sysinfo = update;
if(update.fwconsent === 1) {
cc = !sysinfo.usrcfg; cc = !sysinfo.usrcfg;
} }
});
</script> </script>
<div class="grid xl:grid-cols-4 lg:grid-cols-3 md:grid-cols-2"> <div class="grid xl:grid-cols-4 lg:grid-cols-3 md:grid-cols-2">

View File

@@ -17,21 +17,21 @@ export default defineConfig({
plugins: [svelte()], plugins: [svelte()],
server: { server: {
proxy: { proxy: {
"/data.json": "http://192.168.233.244", "/data.json": "http://192.168.233.49",
"/energyprice.json": "http://192.168.233.244", "/energyprice.json": "http://192.168.233.49",
"/dayplot.json": "http://192.168.233.244", "/dayplot.json": "http://192.168.233.49",
"/monthplot.json": "http://192.168.233.244", "/monthplot.json": "http://192.168.233.49",
"/temperature.json": "http://192.168.233.244", "/temperature.json": "http://192.168.233.49",
"/sysinfo.json": "http://192.168.233.244", "/sysinfo.json": "http://192.168.233.49",
"/configuration.json": "http://192.168.233.244", "/configuration.json": "http://192.168.233.49",
"/tariff.json": "http://192.168.233.244", "/tariff.json": "http://192.168.233.49",
"/save": "http://192.168.233.244", "/save": "http://192.168.233.49",
"/reboot": "http://192.168.233.244", "/reboot": "http://192.168.233.49",
"/configfile": "http://192.168.233.244", "/configfile": "http://192.168.233.49",
"/upgrade": "http://192.168.233.244", "/upgrade": "http://192.168.233.49",
"/mqtt-ca": "http://192.168.233.244", "/mqtt-ca": "http://192.168.233.49",
"/mqtt-cert": "http://192.168.233.244", "/mqtt-cert": "http://192.168.233.49",
"/mqtt-key": "http://192.168.233.244", "/mqtt-key": "http://192.168.233.49",
} }
} }
}) })

View File

@@ -464,7 +464,7 @@ void AmsWebServer::dataJson() {
meterState->getL3Voltage(), meterState->getL3Voltage(),
meterState->getL1Current(), meterState->getL1Current(),
meterState->getL2Current(), meterState->getL2Current(),
meterState->isL2currentEstimated() ? "true" : "false", meterState->isL2currentMissing() ? "true" : "false",
meterState->getL3Current(), meterState->getL3Current(),
meterState->getPowerFactor(), meterState->getPowerFactor(),
meterState->getL1PowerFactor(), meterState->getL1PowerFactor(),

View File

@@ -63,7 +63,6 @@ ADC_MODE(ADC_VCC);
#define BUF_SIZE_COMMON (2048) #define BUF_SIZE_COMMON (2048)
#define BUF_SIZE_HAN (1280)
#include "IEC6205621.h" #include "IEC6205621.h"
#include "IEC6205675.h" #include "IEC6205675.h"
@@ -74,7 +73,8 @@ ADC_MODE(ADC_VCC);
#include "Timezones.h" #include "Timezones.h"
uint8_t commonBuffer[BUF_SIZE_COMMON]; uint8_t commonBuffer[BUF_SIZE_COMMON];
uint8_t hanBuffer[BUF_SIZE_HAN]; uint8_t* hanBuffer;
uint16_t hanBufferSize;
HwTools hw; HwTools hw;
@@ -557,8 +557,15 @@ void loop() {
if (mqttEnabled || config.isMqttChanged()) { if (mqttEnabled || config.isMqttChanged()) {
if(mqttHandler == NULL || !mqttHandler->connected() || config.isMqttChanged()) { if(mqttHandler == NULL || !mqttHandler->connected() || config.isMqttChanged()) {
if(mqttHandler != NULL && config.isMqttChanged()) {
MqttConfig mqttConfig;
if(config.getMqttConfig(mqttConfig)) {
mqttHandler->disconnect();
mqttHandler->setConfig(mqttConfig);
config.ackMqttChange();
}
}
MQTT_connect(); MQTT_connect();
config.ackMqttChange();
} }
} else if(mqttHandler != NULL) { } else if(mqttHandler != NULL) {
mqttHandler->disconnect(); mqttHandler->disconnect();
@@ -1111,6 +1118,12 @@ void setupHanPort(GpioConfig& gpioConfig, uint32_t baud, uint8_t parityOrdinal,
hwSerial = NULL; hwSerial = NULL;
} }
if(hanBuffer != NULL) {
free(hanBuffer);
}
hanBufferSize = 64 * meterConfig.bufferSize * 2;
hanBuffer = (uint8_t*) malloc(hanBufferSize);
// The library automatically sets the pullup in Serial.begin() // The library automatically sets the pullup in Serial.begin()
if(!gpioConfig.hanPinPullup) { if(!gpioConfig.hanPinPullup) {
debugI_P(PSTR("HAN pin pullup disabled")); debugI_P(PSTR("HAN pin pullup disabled"));
@@ -1229,7 +1242,7 @@ bool readHanPort() {
// Before reading, empty serial buffer to increase chance of getting first byte of a data transfer // Before reading, empty serial buffer to increase chance of getting first byte of a data transfer
if(!serialInit) { if(!serialInit) {
hanSerial->readBytes(hanBuffer, BUF_SIZE_HAN); hanSerial->readBytes(hanBuffer, hanBufferSize);
serialInit = true; serialInit = true;
return false; return false;
} }
@@ -1241,8 +1254,8 @@ bool readHanPort() {
start = millis(); start = millis();
while(hanSerial->available() && pos == DATA_PARSE_INCOMPLETE) { while(hanSerial->available() && pos == DATA_PARSE_INCOMPLETE) {
// If buffer was overflowed, reset // If buffer was overflowed, reset
if(len >= BUF_SIZE_HAN) { if(len >= hanBufferSize) {
hanSerial->readBytes(hanBuffer, BUF_SIZE_HAN); hanSerial->readBytes(hanBuffer, hanBufferSize);
len = 0; len = 0;
debugI_P(PSTR("Buffer overflow, resetting")); debugI_P(PSTR("Buffer overflow, resetting"));
return false; return false;
@@ -1274,7 +1287,7 @@ bool readHanPort() {
} else if(pos == DATA_PARSE_UNKNOWN_DATA) { } else if(pos == DATA_PARSE_UNKNOWN_DATA) {
debugW_P(PSTR("Unknown data received")); debugW_P(PSTR("Unknown data received"));
meterState.setLastError(pos); meterState.setLastError(pos);
len = len + hanSerial->readBytes(hanBuffer+len, BUF_SIZE_HAN-len); len = len + hanSerial->readBytes(hanBuffer+len, hanBufferSize-len);
if(Debug.isActive(RemoteDebug::VERBOSE)) { if(Debug.isActive(RemoteDebug::VERBOSE)) {
debugV_P(PSTR(" payload:")); debugV_P(PSTR(" payload:"));
debugPrint(hanBuffer, 0, len); debugPrint(hanBuffer, 0, len);
@@ -1289,7 +1302,7 @@ bool readHanPort() {
} else if(pos < 0) { } else if(pos < 0) {
meterState.setLastError(pos); meterState.setLastError(pos);
printHanReadError(pos); printHanReadError(pos);
len += hanSerial->readBytes(hanBuffer+len, BUF_SIZE_HAN-len); len += hanSerial->readBytes(hanBuffer+len, hanBufferSize-len);
if(mqttHandler != NULL) { if(mqttHandler != NULL) {
mqttHandler->publishRaw(toHex(hanBuffer+pos, len)); mqttHandler->publishRaw(toHex(hanBuffer+pos, len));
} }
@@ -1299,7 +1312,7 @@ bool readHanPort() {
} }
// Data is valid, clear the rest of the buffer to avoid tainted parsing // Data is valid, clear the rest of the buffer to avoid tainted parsing
for(int i = pos+ctx.length; i<BUF_SIZE_HAN; i++) { for(int i = pos+ctx.length; i<hanBufferSize; i++) {
hanBuffer[i] = 0x00; hanBuffer[i] = 0x00;
} }
meterState.setLastError(DATA_PARSE_OK); meterState.setLastError(DATA_PARSE_OK);
@@ -1346,7 +1359,7 @@ bool readHanPort() {
data = new IEC6205675(payload, meterState.getMeterType(), &meterConfig, ctx, meterState); data = new IEC6205675(payload, meterState.getMeterType(), &meterConfig, ctx, meterState);
} }
} else if(ctx.type == DATA_TAG_DSMR) { } else if(ctx.type == DATA_TAG_DSMR) {
data = new IEC6205621(payload, tz); data = new IEC6205621(payload, tz, &meterConfig);
} }
len = 0; len = 0;
@@ -1437,7 +1450,7 @@ void printHanReadError(int pos) {
debugW_P(PSTR("Header checksum error")); debugW_P(PSTR("Header checksum error"));
break; break;
case DATA_PARSE_FOOTER_CHECKSUM_ERROR: case DATA_PARSE_FOOTER_CHECKSUM_ERROR:
debugW_P(PSTR("Frame checksum error")); debugW_P(PSTR("Frame checksum error (%04x)"), dsmrParser == NULL ? 0x0000 : dsmrParser->getCrcCalc());
break; break;
case DATA_PARSE_INCOMPLETE: case DATA_PARSE_INCOMPLETE:
debugW_P(PSTR("Received frame is incomplete")); debugW_P(PSTR("Received frame is incomplete"));
@@ -1726,7 +1739,7 @@ void WiFi_post_connect() {
int16_t unwrapData(uint8_t *buf, DataParserContext &context) { int16_t unwrapData(uint8_t *buf, DataParserContext &context) {
int16_t ret = 0; int16_t ret = 0;
bool doRet = false; bool doRet = false;
uint16_t end = BUF_SIZE_HAN; uint16_t end = hanBufferSize;
uint8_t tag = (*buf); uint8_t tag = (*buf);
uint8_t lastTag = DATA_TAG_NONE; uint8_t lastTag = DATA_TAG_NONE;
while(tag != DATA_TAG_NONE) { while(tag != DATA_TAG_NONE) {
@@ -1853,9 +1866,14 @@ void MQTT_connect() {
mqttEnabled = true; mqttEnabled = true;
ws.setMqttEnabled(true); ws.setMqttEnabled(true);
if(mqttHandler != NULL && mqttHandler->getFormat() != mqttConfig.payloadFormat) { if(mqttHandler != NULL) {
delete mqttHandler; mqttHandler->disconnect();
mqttHandler = NULL; if(mqttHandler->getFormat() != mqttConfig.payloadFormat) {
delete mqttHandler;
mqttHandler = NULL;
} else if(config.isMqttChanged()) {
mqttHandler->setConfig(mqttConfig);
}
} }
if(mqttHandler == NULL) { if(mqttHandler == NULL) {
@@ -1887,6 +1905,9 @@ void MQTT_connect() {
if(mqttHandler != NULL) { if(mqttHandler != NULL) {
mqttHandler->connect(); mqttHandler->connect();
mqttHandler->publishSystem(&hw, eapi, &ea); mqttHandler->publishSystem(&hw, eapi, &ea);
if(eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
mqttHandler->publishPrices(eapi);
}
} }
} }

View File

@@ -1,7 +1,7 @@
#include "IEC6205621.h" #include "IEC6205621.h"
#include "Uptime.h" #include "Uptime.h"
IEC6205621::IEC6205621(const char* p, Timezone* tz) { IEC6205621::IEC6205621(const char* p, Timezone* tz, MeterConfig* meterConfig) {
if(strlen(p) < 16) if(strlen(p) < 16)
return; return;
@@ -19,6 +19,9 @@ IEC6205621::IEC6205621(const char* p, Timezone* tz) {
} else if(listId.startsWith(F("KMP"))) { } else if(listId.startsWith(F("KMP"))) {
meterType = AmsTypeKamstrup; meterType = AmsTypeKamstrup;
listId = listId.substring(0,4); listId = listId.substring(0,4);
} else if(listId.startsWith(F("KAM"))) {
meterType = AmsTypeKamstrup;
listId = listId.substring(0,4);
} else if(listId.startsWith(F("ISk"))) { } else if(listId.startsWith(F("ISk"))) {
meterType = AmsTypeIskra; meterType = AmsTypeIskra;
listId = listId.substring(0,5); listId = listId.substring(0,5);
@@ -130,6 +133,29 @@ IEC6205621::IEC6205621(const char* p, Timezone* tz) {
if (l1activeImportPower > 0 || l2activeImportPower > 0 || l3activeImportPower > 0 || l1activeExportPower > 0 || l2activeExportPower > 0 || l3activeExportPower > 0) if (l1activeImportPower > 0 || l2activeImportPower > 0 || l3activeImportPower > 0 || l1activeExportPower > 0 || l2activeExportPower > 0 || l3activeExportPower > 0)
listType = 4; listType = 4;
if(meterConfig->wattageMultiplier > 0) {
activeImportPower = activeImportPower > 0 ? activeImportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
activeExportPower = activeExportPower > 0 ? activeExportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
reactiveImportPower = reactiveImportPower > 0 ? reactiveImportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
reactiveExportPower = reactiveExportPower > 0 ? reactiveExportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
}
if(meterConfig->voltageMultiplier > 0) {
l1voltage = l1voltage > 0 ? l1voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
l2voltage = l2voltage > 0 ? l2voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
l3voltage = l3voltage > 0 ? l3voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
}
if(meterConfig->amperageMultiplier > 0) {
l1current = l1current > 0 ? l1current * (meterConfig->amperageMultiplier / 1000.0) : 0;
l2current = l2current > 0 ? l2current * (meterConfig->amperageMultiplier / 1000.0) : 0;
l3current = l3current > 0 ? l3current * (meterConfig->amperageMultiplier / 1000.0) : 0;
}
if(meterConfig->accumulatedMultiplier > 0) {
activeImportCounter = activeImportCounter > 0 ? activeImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
activeExportCounter = activeExportCounter > 0 ? activeExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
reactiveImportCounter = reactiveImportCounter > 0 ? reactiveImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
reactiveExportCounter = reactiveExportCounter > 0 ? reactiveExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
}
threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0; threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0;
twoPhase = (l1voltage > 0 && l2voltage > 0) || (l2voltage > 0 && l3voltage > 0) || (l3voltage > 0 && l1voltage > 0); twoPhase = (l1voltage > 0 && l2voltage > 0) || (l2voltage > 0 && l3voltage > 0) || (l3voltage > 0 && l1voltage > 0);
} }

View File

@@ -4,10 +4,11 @@
#include "Arduino.h" #include "Arduino.h"
#include "AmsData.h" #include "AmsData.h"
#include "Timezone.h" #include "Timezone.h"
#include "AmsConfiguration.h"
class IEC6205621 : public AmsData { class IEC6205621 : public AmsData {
public: public:
IEC6205621(const char* payload, Timezone* tz); IEC6205621(const char* payload, Timezone* tz, MeterConfig* meterConfig);
private: private:
String extract(String payload, String obis); String extract(String payload, String obis);

View File

@@ -100,10 +100,7 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
if(listType >= 2 && memcmp(meterModel.c_str(), "MA304T3", 7) == 0) { if(listType >= 2 && memcmp(meterModel.c_str(), "MA304T3", 7) == 0) {
l2voltage = sqrt(pow(l1voltage - l3voltage * cos(60 * (PI/180)), 2) + pow(l3voltage * sin(60 * (PI/180)),2)); l2voltage = sqrt(pow(l1voltage - l3voltage * cos(60 * (PI/180)), 2) + pow(l3voltage * sin(60 * (PI/180)),2));
if(l2voltage > 0) { l2currentMissing = true;
l2current = ((activeImportPower - activeExportPower) - (l1voltage * l1current) - (l3voltage * l3current)) / l2voltage;
l2currentEstimated = true;
}
} }
if(listType == 3) { if(listType == 3) {
@@ -297,6 +294,8 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
if(val != NOVALUE) { if(val != NOVALUE) {
listType = 2; listType = 2;
l2current = val; l2current = val;
} else if(listType == 2) {
l2currentMissing = true;
} }
val = getNumber(AMS_OBIS_CURRENT_L3, sizeof(AMS_OBIS_CURRENT_L3), ((char *) (d))); val = getNumber(AMS_OBIS_CURRENT_L3, sizeof(AMS_OBIS_CURRENT_L3), ((char *) (d)));
if(val != NOVALUE) { if(val != NOVALUE) {
@@ -487,15 +486,7 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
// Special case for Norwegian IT/TT meters that does not report all values // Special case for Norwegian IT/TT meters that does not report all values
if(meterConfig->distributionSystem == 1) { if(meterConfig->distributionSystem == 1) {
if(threePhase) { if(twoPhase && l1current > 0.0 && l2current > 0.0 && l3current > 0.0) {
if(l2current == 0.0 && l1current > 0.0 && l3current > 0.0) {
l2current = ((activeImportPower - activeExportPower) - (l1voltage * l1current) - (l3voltage * l3current)) / l2voltage;
if(activeExportPower == 0.0) {
l2current = max((float) 0.0, l2current);
}
l2currentEstimated = true;
}
} else if(twoPhase && l1current > 0.0 && l2current > 0.0 && l3current > 0.0) {
l2voltage = sqrt(pow(l1voltage - l3voltage * cos(60.0 * (PI/180.0)), 2) + pow(l3voltage * sin(60.0 * (PI/180.0)),2)); l2voltage = sqrt(pow(l1voltage - l3voltage * cos(60.0 * (PI/180.0)), 2) + pow(l3voltage * sin(60.0 * (PI/180.0)),2));
threePhase = true; threePhase = true;
} }

View File

@@ -25,4 +25,7 @@ bool PassthroughMqttHandler::publishRaw(String data) {
uint8_t PassthroughMqttHandler::getFormat() { uint8_t PassthroughMqttHandler::getFormat() {
return 255; return 255;
} }
void PassthroughMqttHandler::onMessage(String &topic, String &payload) {
}

View File

@@ -12,6 +12,8 @@ public:
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea); bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
bool publishRaw(String data); bool publishRaw(String data);
void onMessage(String &topic, String &payload);
uint8_t getFormat(); uint8_t getFormat();
}; };
#endif #endif