Merge branch 'master' into dev-v2.2

This commit is contained in:
Gunnar Skjold
2022-10-13 20:47:48 +02:00
51 changed files with 863 additions and 269 deletions

View File

@@ -517,6 +517,7 @@ bool AmsConfiguration::getEnergyAccountingConfig(EnergyAccountingConfig& config)
}
bool AmsConfiguration::setEnergyAccountingConfig(EnergyAccountingConfig& config) {
if(config.hours > 5) config.hours = 5;
EnergyAccountingConfig existing;
if(getEnergyAccountingConfig(existing)) {
for(int i = 0; i < 9; i++) {
@@ -525,6 +526,7 @@ bool AmsConfiguration::setEnergyAccountingConfig(EnergyAccountingConfig& config)
}
}
config.thresholds[9] = 255;
energyAccountingChanged |= config.hours != existing.hours;
} else {
energyAccountingChanged = true;
}
@@ -741,7 +743,7 @@ bool AmsConfiguration::relocateConfig86() {
}
bool AmsConfiguration::relocateConfig87() {
MeterConfig87 meter87;
MeterConfig87 meter87 = {0,0,0,0,0,0,0};
MeterConfig meter;
EEPROM.begin(EEPROM_SIZE);
EEPROM.get(CONFIG_METER_START_87, meter87);

View File

@@ -12,6 +12,7 @@ enum AmsType {
AmsTypeIskra = 0x08,
AmsTypeLandis = 0x09,
AmsTypeSagemcom = 0x0A,
AmsTypeLng = 0x0B,
AmsTypeCustom = 0x88,
AmsTypeUnknown = 0xFF
};
@@ -53,6 +54,14 @@ public:
float getL2PowerFactor();
float getL3PowerFactor();
float getL1ActiveImportPower();
float getL2ActiveImportPower();
float getL3ActiveImportPower();
float getL1ActiveExportPower();
float getL2ActiveExportPower();
float getL3ActiveExportPower();
double getActiveImportCounter();
double getReactiveImportCounter();
double getActiveExportCounter();
@@ -70,6 +79,8 @@ protected:
time_t meterTimestamp = 0;
uint16_t activeImportPower = 0, reactiveImportPower = 0, activeExportPower = 0, reactiveExportPower = 0;
float l1voltage = 0, l2voltage = 0, l3voltage = 0, l1current = 0, l2current = 0, l3current = 0;
float l1activeImportPower = 0, l2activeImportPower = 0, l3activeImportPower = 0;
float l1activeExportPower = 0, l2activeExportPower = 0, l3activeExportPower = 0;
float powerFactor = 0, l1PowerFactor = 0, l2PowerFactor = 0, l3PowerFactor = 0;
double activeImportCounter = 0, reactiveImportCounter = 0, activeExportCounter = 0, reactiveExportCounter = 0;
bool threePhase = false, twoPhase = false, counterEstimated = false;

View File

@@ -45,6 +45,12 @@ void AmsData::apply(AmsData& other) {
this->l1PowerFactor = other.getL1PowerFactor();
this->l2PowerFactor = other.getL2PowerFactor();
this->l3PowerFactor = other.getL3PowerFactor();
this->l1activeImportPower = other.getL1ActiveImportPower();
this->l2activeImportPower = other.getL2ActiveImportPower();
this->l3activeImportPower = other.getL3ActiveImportPower();
this->l1activeExportPower = other.getL1ActiveExportPower();
this->l2activeExportPower = other.getL2ActiveExportPower();
this->l3activeExportPower = other.getL3ActiveExportPower();
case 3:
this->meterTimestamp = other.getMeterTimestamp();
this->activeImportCounter = other.getActiveImportCounter();
@@ -161,6 +167,30 @@ float AmsData::getL3PowerFactor() {
return this->l3PowerFactor;
}
float AmsData::getL1ActiveImportPower() {
return this->l1activeImportPower;
}
float AmsData::getL2ActiveImportPower() {
return this->l2activeImportPower;
}
float AmsData::getL3ActiveImportPower() {
return this->l3activeImportPower;
}
float AmsData::getL1ActiveExportPower() {
return this->l1activeExportPower;
}
float AmsData::getL2ActiveExportPower() {
return this->l2activeExportPower;
}
float AmsData::getL3ActiveExportPower() {
return this->l3activeExportPower;
}
double AmsData::getActiveImportCounter() {
return this->activeImportCounter;
}

View File

@@ -15,11 +15,12 @@ public:
this->mqtt = mqtt;
this->json = buf;
};
virtual ~AmsMqttHandler() {};
virtual bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
virtual bool publishTemperatures(AmsConfiguration*, HwTools*);
virtual bool publishPrices(EntsoeApi* eapi);
virtual bool publishSystem(HwTools*);
virtual bool publishSystem(HwTools*, EntsoeApi*, EnergyAccounting*);
protected:
MQTTClient* mqtt;

View File

@@ -316,6 +316,7 @@ $(function() {
url: swv.data('url'),
dataType: 'json'
}).done(function(releases) {
var isnew = false;
if(/^v\d{1,2}\.\d{1,2}\.\d{1,2}$/.test(swv.text()) && fwl.length == 0) {
releases.reverse();
var next_patch;
@@ -352,10 +353,13 @@ $(function() {
});
if(next_minor) {
nextVersion = next_minor;
isnew = true;
} else if(next_major) {
nextVersion = next_major;
isnew = true;
} else if(next_patch) {
nextVersion = next_patch;
isnew = true;
}
} else {
nextVersion = releases[0];
@@ -375,9 +379,11 @@ $(function() {
}
});
};
$('#newVersionTag').text(nextVersion.tag_name);
$('#newVersionUrl').prop('href', nextVersion.html_url);
$('#newVersion').removeClass('d-none');
if(isnew) {
$('#newVersionTag').text(nextVersion.tag_name);
$('#newVersionUrl').prop('href', nextVersion.html_url);
$('#newVersion').removeClass('d-none');
}
}
});
}
@@ -884,7 +890,7 @@ var fetch = function() {
var upgrade = function() {
if(nextVersion) {
if(confirm("WARNING: Please keep USB power connected while upgrading. Are you sure you want to perform upgrade to " + nextVersion.tag_name + "?")) {
if(confirm("WARNING: If you have a M-BUS powered device (Pow-U), please keep USB power connected while upgrading.\n\nAre you sure you want to perform upgrade to " + nextVersion.tag_name + "?")) {
$('#loading-indicator').show();
window.location.href="/upgrade?version=" + nextVersion.tag_name;
}

View File

@@ -23,8 +23,8 @@
"v" : %.3f,
"r" : %d,
"t" : %.2f,
"u" : %lu,
"m" : %lu,
"u" : %u,
"m" : %u,
"em" : %d,
"hm" : %d,
"wm" : %d,
@@ -53,5 +53,5 @@
"p" : %.2f
}
},
"c" : %lu
"c" : %u
}

View File

@@ -34,6 +34,19 @@
<option value="10YDK-1--------W" {eaDk1}>DK1</option>
<option value="10YDK-2--------M" {eaDk2}>DK2</option>
</optgroup>
<option value="10YAT-APG------L" {at}>Austria</option>
<option value="10YBE----------2" {be}>Belgium</option>
<option value="10YCZ-CEPS-----N" {cz}>Czech Republic</option>
<option value="10Y1001A1001A39I" {ee}>Estonia</option>
<option value="10YFI-1--------U" {fi}>Finland</option>
<option value="10YFR-RTE------C" {fr}>France</option>
<option value="10Y1001A1001A83F" {de}>Germany</option>
<option value="10YGB----------A" {gb}>Great Britain</option>
<option value="10YLV-1001A00074" {lv}>Latvia</option>
<option value="10YLT-1001A0008Q" {lt}>Lithuania</option>
<option value="10YNL----------L" {nl}>Netherland</option>
<option value="10YPL-AREA-----S" {pl}>Poland</option>
<option value="10YCH-SWISSGRIDZ" {ch}>Switzerland</option>
</select>
</div>
</div>

View File

@@ -1,18 +1,21 @@
<div class="alert alert-danger">
WARNING: Units powered over M-bus must be connected to an external power supply during firmware upload. Failure to do so may cause power-down during upload resulting in non-functioning unit.
WARNING: Units powered by M-BUS (Pow-U) must be connected to an external power supply during firmware upload. Failure to do so may cause power-down during upload resulting in non-functioning unit.
</div>
<div class="alert alert-warning">
Your board is using {chipset} chipset. Only upload firmware designed for this chipset. Failure to do so may result in non-functioning unit.
<span id="fwDownload" style="display: none;"><br/>Download latest firmware file <a id="fwLink" href="#" data-chipset="{chipset}">here</a></span>
</div>
<div class="alert alert-warning">
When using URL, only a valid ESP OTA server response will be accepted.
</div>
<form method="post" enctype="multipart/form-data" class="upload-form">
<div class="my-3 p-3 bg-white rounded shadow">
<div class="row">
<div class="col-lg-6">
<div class="input-group mb-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">Upload</span>
<span class="input-group-text">Upload file</span>
</div>
<div class="custom-file">
<input name="file" type="file" class="custom-file-input" id="fileUploadField">
@@ -21,6 +24,19 @@
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">or</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">Use URL</span>
</div>
<input type="text" name="url" class="form-control"/>
</div>
</div>
</div>
</div>
<hr/>
<div class="row form-group">
@@ -28,7 +44,7 @@
<a href="javascript:history.back();" class="btn btn-outline-secondary">Back</a>
</div>
<div class="col-6 text-right">
<button class="btn btn-primary">Upload</button>
<button class="btn btn-primary">Upgrade firmware</button>
</div>
</div>
</form>
</form>

View File

@@ -1,6 +1,6 @@
<div class="alert alert-warning">!!WARNING!!<br/>Do not change anything here unless you know exactly what you are doing! Changing things here could cause the device to stop responding</div>
<form method="post" action="/save">
<input type="hidden" name="gpioConfig" value="true"/>
<input type="hidden" name="gc" value="true"/>
<div class="my-3 p-3 bg-white rounded shadow">
<h6>GPIO settings</h6>
<div class="d-flex flex-row flex-wrap">
@@ -8,18 +8,18 @@
<div class="input-group-prepend">
<span class="input-group-text">HAN</span>
</div>
<select name="hanPin" class="form-control">
${options.han}
<select name="h" class="form-control">
${h}
</select>
</div>
<div class="m-2 input-group input-group-sm" style="width: 150px;">
<div class="input-group-prepend">
<span class="input-group-text">LED</span>
</div>
<input name="ledPin" type="number" min="2" max="${gpio.max}" class="form-control" value="${config.ledPin}"/>
<input name="l" type="number" min="2" max="${g}" class="form-control" value="${l}"/>
<div class="input-group-append" title="Inverted">
<label class="input-group-text">
<input type="checkbox" name="ledInverted" value="true" ${config.ledInverted}/> inv
<input type="checkbox" name="i" value="true" ${i}/> inv
</label>
</div>
</div>
@@ -27,12 +27,12 @@
<div class="input-group-prepend">
<span class="input-group-text">RGB</span>
</div>
<input name="ledPinRed" type="number" min="2" max="${gpio.max}" class="form-control" value="${config.ledPinRed}"/>
<input name="ledPinGreen" type="number" min="2" max="${gpio.max}" class="form-control" value="${config.ledPinGreen}"/>
<input name="ledPinBlue" type="number" min="2" max="${gpio.max}" class="form-control" value="${config.ledPinBlue}"/>
<input name="r" type="number" min="2" max="${g}" class="form-control" value="${r}"/>
<input name="e" type="number" min="2" max="${g}" class="form-control" value="${e}"/>
<input name="b" type="number" min="2" max="${g}" class="form-control" value="${b}"/>
<div class="input-group-append" title="Inverted">
<label class="input-group-text">
<input type="checkbox" name="ledRgbInverted" value="true" ${config.ledRgbInverted}/> inv
<input type="checkbox" name="n" value="true" ${n}/> inv
</label>
</div>
</div>
@@ -40,31 +40,31 @@
<div class="input-group-prepend">
<span class="input-group-text">AP button</span>
</div>
<input name="apPin" type="number" min="0" max="${gpio.max}" class="form-control" value="${config.apPin}"/>
<input name="a" type="number" min="0" max="${g}" class="form-control" value="${a}"/>
</div>
<div class="m-2 input-group input-group-sm" style="width: 150px;">
<div class="input-group-prepend">
<span class="input-group-text">Temperature</span>
</div>
<input name="tempSensorPin" type="number" min="0" max="${gpio.max}" class="form-control" value="${config.tempSensorPin}"/>
<input name="t" type="number" min="0" max="${g}" class="form-control" value="${t}"/>
</div>
<div class="m-2 input-group input-group-sm" style="width: 150px;">
<div class="input-group-prepend">
<span class="input-group-text">Analog temp</span>
</div>
<input name="tempAnalogSensorPin" type="number" min="0" max="${gpio.max}" class="form-control" value="${config.tempAnalogSensorPin}"/>
<input name="m" type="number" min="0" max="${g}" class="form-control" value="${m}"/>
</div>
<div class="m-2 input-group input-group-sm" style="width: 100px;">
<div class="input-group-prepend">
<span class="input-group-text">Vcc</span>
</div>
<input name="vccPin" type="number" min="0" max="${gpio.max}" class="form-control" value="${config.vccPin}"/>
<input name="v" type="number" min="0" max="${g}" class="form-control" value="${v}"/>
</div>
<div class="m-2 input-group input-group-sm" style="width: 200px;">
<div class="input-group-prepend">
<span class="input-group-text">GND resistor</span>
</div>
<input type="number" min="1" max="1000" step="1" class="form-control" name="vccResistorGnd" value="${config.vccResistorGnd}" />
<input type="number" min="1" max="1000" step="1" class="form-control" name="d" value="${d}" />
<div class="input-group-append" title="Inverted">
<label class="input-group-text">k&ohm;</label>
</div>
@@ -73,7 +73,7 @@
<div class="input-group-prepend">
<span class="input-group-text">Vcc resistor</span>
</div>
<input type="number" min="1" max="1000" step="1" class="form-control" name="vccResistorVcc" value="${config.vccResistorVcc}" />
<input type="number" min="1" max="1000" step="1" class="form-control" name="s" value="${s}" />
<div class="input-group-append" title="Inverted">
<label class="input-group-text">k&ohm;</label>
</div>
@@ -82,19 +82,19 @@
<div class="input-group-prepend">
<span class="input-group-text">Multiplier</span>
</div>
<input type="number" min="0.1" max="10" step="0.01" class="form-control" name="vccMultiplier" value="${config.vccMultiplier}" />
<input type="number" min="0.1" max="10" step="0.01" class="form-control" name="u" value="${u}" />
</div>
<div class="m-2 input-group input-group-sm" style="width: 120px;">
<div class="input-group-prepend">
<span class="input-group-text">Offset</span>
</div>
<input type="number" min="0.0" max="3.5" step="0.01" class="form-control" name="vccOffset" value="${config.vccOffset}" />
<input type="number" min="0.0" max="3.5" step="0.01" class="form-control" name="o" value="${o}" />
</div>
<div class="m-2 input-group input-group-sm" style="width: 130px;">
<div class="input-group-prepend">
<span class="input-group-text">Boot limit</span>
</div>
<input type="number" min="2.5" max="3.5" step="0.1" class="form-control" name="vccBootLimit" value="${config.vccBootLimit}" />
<input type="number" min="2.5" max="3.5" step="0.1" class="form-control" name="c" value="${c}" />
</div>
</div>
</div>

View File

@@ -0,0 +1,25 @@
{
"lv" : "%s",
"id" : "%s",
"type" : "%s",
"P" : %d,
"P1" : %.2f,
"P2" : %.2f,
"P3" : %.2f,
"Q" : %d,
"PO" : %d,
"PO1" : %.2f,
"PO2" : %.2f,
"PO3" : %.2f,
"QO" : %d,
"I1" : %.2f,
"I2" : %.2f,
"I3" : %.2f,
"U1" : %.2f,
"U2" : %.2f,
"U3" : %.2f,
"PF" : %.2f,
"PF1" : %.2f,
"PF2" : %.2f,
"PF3" : %.2f
}

View File

@@ -1,3 +1,4 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">

View File

@@ -0,0 +1,20 @@
{
"max" : %.1f,
"peaks" : [ %s ],
"threshold" : %d,
"hour" : {
"use" : %.2f,
"cost" : %.2f,
"produced" : %.2f
},
"day" : {
"use" : %.2f,
"cost" : %.2f,
"produced" : %.2f
},
"month" : {
"use" : %.2f,
"cost" : %.2f,
"produced" : %.2f
}
}

View File

@@ -26,6 +26,7 @@
<select name="board" class="form-control" required>
<option value=""></option>
<optgroup label="Custom hardware">
<option value="7" ${config.boardType7}>Pow-U+ (ESP32) from amsleser.no</option>
<option value="6" ${config.boardType6}>Pow-P1 from amsleser.no</option>
<option value="5" ${config.boardType5}>Pow-K+ (ESP32) from amsleser.no</option>
<option value="4" ${config.boardType4}>Pow-U or Pow-K from amsleser.no (GPIO12)</option>

View File

@@ -61,6 +61,11 @@ private:
bool performRestart = false;
bool performUpgrade = false;
bool rebootForUpgrade = false;
#if defined(AMS2MQTT_FIRMWARE_URL)
String customFirmwareUrl = AMS2MQTT_FIRMWARE_URL;
#else
String customFirmwareUrl;
#endif
static const uint16_t BufferSize = 2048;
char* buf;
@@ -104,6 +109,7 @@ private:
String getSerialSelectOptions(int selected);
void firmwareHtml();
void firmwarePost();
void firmwareUpload();
void firmwareDownload();
void restartHtml();

View File

@@ -90,7 +90,7 @@ void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, Meter
server.on("/debugging", HTTP_GET, std::bind(&AmsWebServer::configDebugHtml, this));
server.on("/firmware", HTTP_GET, std::bind(&AmsWebServer::firmwareHtml, this));
server.on("/firmware", HTTP_POST, std::bind(&AmsWebServer::uploadPost, this), std::bind(&AmsWebServer::firmwareUpload, this));
server.on("/firmware", HTTP_POST, std::bind(&AmsWebServer::firmwarePost, this), std::bind(&AmsWebServer::firmwareUpload, this));
server.on("/upgrade", HTTP_GET, std::bind(&AmsWebServer::firmwareDownload, this));
server.on("/restart", HTTP_GET, std::bind(&AmsWebServer::restartHtml, this));
server.on("/restart", HTTP_POST, std::bind(&AmsWebServer::restartPost, this));
@@ -336,6 +336,9 @@ void AmsWebServer::configMeterHtml() {
case AmsTypeSagemcom:
manufacturer = "Sagemcom";
break;
case AmsTypeLng:
manufacturer = "L&G";
break;
default:
manufacturer = "Unknown";
break;
@@ -567,6 +570,20 @@ void AmsWebServer::configEntsoeHtml() {
html.replace("{eaDk1}", strcmp(entsoe.area, "10YDK-1--------W") == 0 ? "selected" : "");
html.replace("{eaDk2}", strcmp(entsoe.area, "10YDK-2--------M") == 0 ? "selected" : "");
html.replace("{at}", strcmp(entsoe.area, "10YAT-APG------L") == 0 ? "selected" : "");
html.replace("{be}", strcmp(entsoe.area, "10YBE----------2") == 0 ? "selected" : "");
html.replace("{cz}", strcmp(entsoe.area, "10YCZ-CEPS-----N") == 0 ? "selected" : "");
html.replace("{ee}", strcmp(entsoe.area, "10Y1001A1001A39I") == 0 ? "selected" : "");
html.replace("{fi}", strcmp(entsoe.area, "10YFI-1--------U") == 0 ? "selected" : "");
html.replace("{fr}", strcmp(entsoe.area, "10YFR-RTE------C") == 0 ? "selected" : "");
html.replace("{de}", strcmp(entsoe.area, "10Y1001A1001A83F") == 0 ? "selected" : "");
html.replace("{gb}", strcmp(entsoe.area, "10YGB----------A") == 0 ? "selected" : "");
html.replace("{lv}", strcmp(entsoe.area, "10YLV-1001A00074") == 0 ? "selected" : "");
html.replace("{lt}", strcmp(entsoe.area, "10YLT-1001A0008Q") == 0 ? "selected" : "");
html.replace("{nl}", strcmp(entsoe.area, "10YNL----------L") == 0 ? "selected" : "");
html.replace("{pl}", strcmp(entsoe.area, "10YPL-AREA-----S") == 0 ? "selected" : "");
html.replace("{ch}", strcmp(entsoe.area, "10YCH-SWISSGRIDZ") == 0 ? "selected" : "");
html.replace("{ecNOK}", strcmp(entsoe.currency, "NOK") == 0 ? "selected" : "");
html.replace("{ecSEK}", strcmp(entsoe.currency, "SEK") == 0 ? "selected" : "");
html.replace("{ecDKK}", strcmp(entsoe.currency, "DKK") == 0 ? "selected" : "");
@@ -990,7 +1007,7 @@ void AmsWebServer::handleSetup() {
server.sendHeader("Location", String("/"), true);
server.send (302, MIME_PLAIN, "");
} else {
SystemConfig sys { server.arg("board").toInt() };
SystemConfig sys { static_cast<uint8_t>(server.arg("board").toInt()) };
DebugConfig debugConfig;
config->getDebugConfig(debugConfig);
@@ -1060,6 +1077,16 @@ void AmsWebServer::handleSetup() {
gpioConfig->vccResistorGnd = 22;
gpioConfig->vccResistorVcc = 33;
break;
case 7: // Pow-U+
gpioConfig->hanPin = 16;
gpioConfig->apPin = 0;
gpioConfig->ledPinRed = 13;
gpioConfig->ledPinGreen = 14;
gpioConfig->ledRgbInverted = true;
gpioConfig->vccPin = 10;
gpioConfig->vccResistorGnd = 22;
gpioConfig->vccResistorVcc = 33;
break;
case 101: // D1
gpioConfig->hanPin = 5;
gpioConfig->apPin = 4;
@@ -1260,11 +1287,11 @@ void AmsWebServer::handleSave() {
if(server.hasArg("dc") && server.arg("dc") == "true") {
printD("Received Domoticz config");
DomoticzConfig domo {
server.arg("elidx").toInt(),
server.arg("vl1idx").toInt(),
server.arg("vl2idx").toInt(),
server.arg("vl3idx").toInt(),
server.arg("cl1idx").toInt()
static_cast<uint16_t>(server.arg("elidx").toInt()),
static_cast<uint16_t>(server.arg("vl1idx").toInt()),
static_cast<uint16_t>(server.arg("vl2idx").toInt()),
static_cast<uint16_t>(server.arg("vl3idx").toInt()),
static_cast<uint16_t>(server.arg("cl1idx").toInt())
};
config->setDomoticzConfig(domo);
}
@@ -1285,24 +1312,24 @@ void AmsWebServer::handleSave() {
config->setWebConfig(webConfig);
}
if(server.hasArg("gpioConfig") && server.arg("gpioConfig") == "true") {
if(server.hasArg("gc") && server.arg("gc") == "true") {
printD("Received GPIO config");
gpioConfig->hanPin = server.hasArg("hanPin") && !server.arg("hanPin").isEmpty() ? server.arg("hanPin").toInt() : 3;
gpioConfig->ledPin = server.hasArg("ledPin") && !server.arg("ledPin").isEmpty() ? server.arg("ledPin").toInt() : 0xFF;
gpioConfig->ledInverted = server.hasArg("ledInverted") && server.arg("ledInverted") == "true";
gpioConfig->ledPinRed = server.hasArg("ledPinRed") && !server.arg("ledPinRed").isEmpty() ? server.arg("ledPinRed").toInt() : 0xFF;
gpioConfig->ledPinGreen = server.hasArg("ledPinGreen") && !server.arg("ledPinGreen").isEmpty() ? server.arg("ledPinGreen").toInt() : 0xFF;
gpioConfig->ledPinBlue = server.hasArg("ledPinBlue") && !server.arg("ledPinBlue").isEmpty() ? server.arg("ledPinBlue").toInt() : 0xFF;
gpioConfig->ledRgbInverted = server.hasArg("ledRgbInverted") && server.arg("ledRgbInverted") == "true";
gpioConfig->apPin = server.hasArg("apPin") && !server.arg("apPin").isEmpty() ? server.arg("apPin").toInt() : 0xFF;
gpioConfig->tempSensorPin = server.hasArg("tempSensorPin") && !server.arg("tempSensorPin").isEmpty() ?server.arg("tempSensorPin").toInt() : 0xFF;
gpioConfig->tempAnalogSensorPin = server.hasArg("tempAnalogSensorPin") && !server.arg("tempAnalogSensorPin").isEmpty() ?server.arg("tempAnalogSensorPin").toInt() : 0xFF;
gpioConfig->vccPin = server.hasArg("vccPin") && !server.arg("vccPin").isEmpty() ? server.arg("vccPin").toInt() : 0xFF;
gpioConfig->vccOffset = server.hasArg("vccOffset") && !server.arg("vccOffset").isEmpty() ? server.arg("vccOffset").toFloat() * 100 : 0;
gpioConfig->vccMultiplier = server.hasArg("vccMultiplier") && !server.arg("vccMultiplier").isEmpty() ? server.arg("vccMultiplier").toFloat() * 1000 : 1000;
gpioConfig->vccBootLimit = server.hasArg("vccBootLimit") && !server.arg("vccBootLimit").isEmpty() ? server.arg("vccBootLimit").toFloat() * 10 : 0;
gpioConfig->vccResistorGnd = server.hasArg("vccResistorGnd") && !server.arg("vccResistorGnd").isEmpty() ? server.arg("vccResistorGnd").toInt() : 0;
gpioConfig->vccResistorVcc = server.hasArg("vccResistorVcc") && !server.arg("vccResistorVcc").isEmpty() ? server.arg("vccResistorVcc").toInt() : 0;
gpioConfig->hanPin = server.hasArg("h") && !server.arg("h").isEmpty() ? server.arg("h").toInt() : 3;
gpioConfig->ledPin = server.hasArg("l") && !server.arg("l").isEmpty() ? server.arg("l").toInt() : 0xFF;
gpioConfig->ledInverted = server.hasArg("i") && server.arg("i") == "true";
gpioConfig->ledPinRed = server.hasArg("r") && !server.arg("r").isEmpty() ? server.arg("r").toInt() : 0xFF;
gpioConfig->ledPinGreen = server.hasArg("e") && !server.arg("e").isEmpty() ? server.arg("e").toInt() : 0xFF;
gpioConfig->ledPinBlue = server.hasArg("b") && !server.arg("b").isEmpty() ? server.arg("b").toInt() : 0xFF;
gpioConfig->ledRgbInverted = server.hasArg("n") && server.arg("n") == "true";
gpioConfig->apPin = server.hasArg("a") && !server.arg("a").isEmpty() ? server.arg("a").toInt() : 0xFF;
gpioConfig->tempSensorPin = server.hasArg("t") && !server.arg("t").isEmpty() ?server.arg("t").toInt() : 0xFF;
gpioConfig->tempAnalogSensorPin = server.hasArg("m") && !server.arg("m").isEmpty() ?server.arg("m").toInt() : 0xFF;
gpioConfig->vccPin = server.hasArg("v") && !server.arg("v").isEmpty() ? server.arg("v").toInt() : 0xFF;
gpioConfig->vccOffset = server.hasArg("o") && !server.arg("o").isEmpty() ? server.arg("o").toFloat() * 100 : 0;
gpioConfig->vccMultiplier = server.hasArg("u") && !server.arg("u").isEmpty() ? server.arg("u").toFloat() * 1000 : 1000;
gpioConfig->vccBootLimit = server.hasArg("c") && !server.arg("c").isEmpty() ? server.arg("c").toFloat() * 10 : 0;
gpioConfig->vccResistorGnd = server.hasArg("d") && !server.arg("d").isEmpty() ? server.arg("d").toInt() : 0;
gpioConfig->vccResistorVcc = server.hasArg("s") && !server.arg("s").isEmpty() ? server.arg("s").toInt() : 0;
config->setGpioConfig(*gpioConfig);
}
@@ -1341,8 +1368,8 @@ void AmsWebServer::handleSave() {
NtpConfig ntp {
server.hasArg("n") && server.arg("n") == "true",
server.hasArg("nd") && server.arg("nd") == "true",
server.arg("o").toInt() / 10,
server.arg("so").toInt() / 10
static_cast<int16_t>(server.arg("o").toInt() / 10),
static_cast<int16_t>(server.arg("so").toInt() / 10)
};
strcpy(ntp.server, server.arg("ns").c_str());
config->setNtpConfig(ntp);
@@ -1434,32 +1461,32 @@ void AmsWebServer::configGpioHtml() {
String html = String((const __FlashStringHelper*) GPIO_HTML);
#if defined(CONFIG_IDF_TARGET_ESP32S2)
html.replace("${gpio.max}", "44");
html.replace("${g}", "44");
#elif defined(ESP32)
html.replace("${gpio.max}", "39");
html.replace("${g}", "39");
#else
html.replace("${gpio.max}", "16");
html.replace("${g}", "16");
#endif
html.replace("${options.han}", getSerialSelectOptions(gpioConfig->hanPin));
html.replace("${h}", getSerialSelectOptions(gpioConfig->hanPin));
html.replace("${config.ledPin}", gpioConfig->ledPin == 0xFF ? "" : String(gpioConfig->ledPin));
html.replace("${config.ledInverted}", gpioConfig->ledInverted ? "checked" : "");
html.replace("${config.ledPinRed}", gpioConfig->ledPinRed == 0xFF ? "" : String(gpioConfig->ledPinRed));
html.replace("${config.ledPinGreen}", gpioConfig->ledPinGreen == 0xFF ? "" : String(gpioConfig->ledPinGreen));
html.replace("${config.ledPinBlue}", gpioConfig->ledPinBlue == 0xFF ? "" : String(gpioConfig->ledPinBlue));
html.replace("${config.ledRgbInverted}", gpioConfig->ledRgbInverted ? "checked" : "");
html.replace("${config.apPin}", gpioConfig->apPin == 0xFF ? "" : String(gpioConfig->apPin));
html.replace("${config.tempSensorPin}", gpioConfig->tempSensorPin == 0xFF ? "" : String(gpioConfig->tempSensorPin));
html.replace("${config.tempAnalogSensorPin}", gpioConfig->tempAnalogSensorPin == 0xFF ? "" : String(gpioConfig->tempAnalogSensorPin));
html.replace("${config.vccPin}", gpioConfig->vccPin == 0xFF ? "" : String(gpioConfig->vccPin));
html.replace("${l}", gpioConfig->ledPin == 0xFF ? "" : String(gpioConfig->ledPin));
html.replace("${i}", gpioConfig->ledInverted ? "checked" : "");
html.replace("${r}", gpioConfig->ledPinRed == 0xFF ? "" : String(gpioConfig->ledPinRed));
html.replace("${e}", gpioConfig->ledPinGreen == 0xFF ? "" : String(gpioConfig->ledPinGreen));
html.replace("${b}", gpioConfig->ledPinBlue == 0xFF ? "" : String(gpioConfig->ledPinBlue));
html.replace("${n}", gpioConfig->ledRgbInverted ? "checked" : "");
html.replace("${a}", gpioConfig->apPin == 0xFF ? "" : String(gpioConfig->apPin));
html.replace("${t}", gpioConfig->tempSensorPin == 0xFF ? "" : String(gpioConfig->tempSensorPin));
html.replace("${m}", gpioConfig->tempAnalogSensorPin == 0xFF ? "" : String(gpioConfig->tempAnalogSensorPin));
html.replace("${v}", gpioConfig->vccPin == 0xFF ? "" : String(gpioConfig->vccPin));
html.replace("${config.vccOffset}", gpioConfig->vccOffset > 0 ? String(gpioConfig->vccOffset / 100.0, 2) : "");
html.replace("${config.vccMultiplier}", gpioConfig->vccMultiplier > 0 ? String(gpioConfig->vccMultiplier / 1000.0, 2) : "");
html.replace("${config.vccBootLimit}", gpioConfig->vccBootLimit > 0 ? String(gpioConfig->vccBootLimit / 10.0, 1) : "");
html.replace("${o}", gpioConfig->vccOffset > 0 ? String(gpioConfig->vccOffset / 100.0, 2) : "");
html.replace("${u}", gpioConfig->vccMultiplier > 0 ? String(gpioConfig->vccMultiplier / 1000.0, 2) : "");
html.replace("${c}", gpioConfig->vccBootLimit > 0 ? String(gpioConfig->vccBootLimit / 10.0, 1) : "");
html.replace("${config.vccResistorGnd}", gpioConfig->vccResistorGnd > 0 ? String(gpioConfig->vccResistorGnd) : "");
html.replace("${config.vccResistorVcc}", gpioConfig->vccResistorVcc > 0 ? String(gpioConfig->vccResistorVcc) : "");
html.replace("${d}", gpioConfig->vccResistorGnd > 0 ? String(gpioConfig->vccResistorGnd) : "");
html.replace("${s}", gpioConfig->vccResistorVcc > 0 ? String(gpioConfig->vccResistorVcc) : "");
server.sendHeader(HEADER_CACHE_CONTROL, "no-cache, no-store, must-revalidate");
server.sendHeader("Pragma", "no-cache");
@@ -1572,21 +1599,21 @@ HTTPUpload& AmsWebServer::uploadFile(const char* path) {
}
file = LittleFS.open(path, "w");
if(debugger->isActive(RemoteDebug::DEBUG)) {
debugger->printf("handleFileUpload Open file and write: %lu\n", upload.currentSize);
debugger->printf("handleFileUpload Open file and write: %u\n", upload.currentSize);
}
size_t written = file.write(upload.buf, upload.currentSize);
if(debugger->isActive(RemoteDebug::DEBUG)) {
debugger->printf("handleFileUpload Written: %lu\n", written);
debugger->printf("handleFileUpload Written: %u\n", written);
}
}
} else if(upload.status == UPLOAD_FILE_WRITE) {
if(debugger->isActive(RemoteDebug::DEBUG)) {
debugger->printf("handleFileUpload Writing: %lu\n", upload.currentSize);
debugger->printf("handleFileUpload Writing: %u\n", upload.currentSize);
}
if(file) {
size_t written = file.write(upload.buf, upload.currentSize);
if(debugger->isActive(RemoteDebug::DEBUG)) {
debugger->printf("handleFileUpload Written: %lu\n", written);
debugger->printf("handleFileUpload Written: %u\n", written);
}
delay(1);
if(written != upload.currentSize) {
@@ -1649,13 +1676,40 @@ void AmsWebServer::firmwareHtml() {
server.sendContent_P(FOOT_HTML);
}
void AmsWebServer::firmwarePost() {
printD("Handlling firmware post...");
if(!checkSecurity(1))
return;
if(rebootForUpgrade) {
server.send(200);
} else {
if(server.hasArg("url")) {
String url = server.arg("url");
if(!url.isEmpty() && (url.startsWith("http://") || url.startsWith("https://"))) {
printD("Custom firmware URL was provided");
customFirmwareUrl = url;
performUpgrade = true;
server.sendHeader("Location","/restart-wait");
server.send(303);
return;
}
}
server.sendHeader("Location","/firmware");
server.send(303);
}
}
void AmsWebServer::firmwareUpload() {
printD("Handlling firmware upload...");
if(!checkSecurity(1))
return;
HTTPUpload& upload = server.upload();
String filename = upload.filename;
if(filename.isEmpty()) return;
if(upload.status == UPLOAD_FILE_START) {
String filename = upload.filename;
if(!filename.endsWith(".bin")) {
server.send(500, MIME_PLAIN, "500: couldn't create file");
} else {
@@ -1764,7 +1818,7 @@ void AmsWebServer::restartWaitHtml() {
performRestart = false;
} else if(performUpgrade) {
WiFiClient client;
String url = "http://ams2mqtt.rewiredinvent.no/hub/firmware/update";
String url = customFirmwareUrl.isEmpty() || !customFirmwareUrl.startsWith("http") ? "http://ams2mqtt.rewiredinvent.no/hub/firmware/update" : customFirmwareUrl;
#if defined(ESP8266)
String chipType = "esp8266";
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
@@ -1778,9 +1832,11 @@ void AmsWebServer::restartWaitHtml() {
#endif
#if defined(ESP8266)
ESPhttpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
t_httpUpdate_return ret = ESPhttpUpdate.update(client, url, VERSION);
#elif defined(ESP32)
HTTPUpdate httpUpdate;
httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
HTTPUpdateResult ret = httpUpdate.update(client, url, String(VERSION) + "-" + chipType);
#endif
@@ -2276,7 +2332,7 @@ void AmsWebServer::configFileDownload() {
ds->getHourImport(23)
));
if(day.activeExport > 0) {
server.sendContent(buf, snprintf(buf, BufferSize, " %lu %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
server.sendContent(buf, snprintf(buf, BufferSize, " %u %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
day.activeExport,
ds->getHourExport(0),
ds->getHourExport(1),
@@ -2345,7 +2401,7 @@ void AmsWebServer::configFileDownload() {
ds->getDayImport(31)
));
if(month.activeExport > 0) {
server.sendContent(buf, snprintf_P(buf, BufferSize, " %lu %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
server.sendContent(buf, snprintf_P(buf, BufferSize, " %u %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
month.activeExport,
ds->getDayExport(1),
ds->getDayExport(2),

View File

@@ -12,7 +12,7 @@ public:
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
bool publishTemperatures(AmsConfiguration*, HwTools*);
bool publishPrices(EntsoeApi*);
bool publishSystem(HwTools*);
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
private:
DomoticzConfig config;

View File

@@ -71,6 +71,6 @@ bool DomoticzMqttHandler::publishPrices(EntsoeApi* eapi) {
return false;
}
bool DomoticzMqttHandler::publishSystem(HwTools* hw) {
bool DomoticzMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
return false;
}

View File

@@ -57,7 +57,7 @@ bool EnergyAccounting::update(AmsData* amsData) {
for(uint8_t i = 0; i < 5; i++) {
debugger->printf("(EnergyAccounting) Peak hour from day %d: %d\n", data.peaks[i].day, data.peaks[i].value*10);
}
debugger->printf("(EnergyAccounting) Loaded cost yesterday: %d, this month: %d, last month: %d\n", data.costYesterday / 10.0, data.costThisMonth, data.costLastMonth);
debugger->printf("(EnergyAccounting) Loaded cost yesterday: %.2f, this month: %d, last month: %d\n", data.costYesterday / 10.0, data.costThisMonth, data.costLastMonth);
}
init = true;
}
@@ -237,11 +237,12 @@ float EnergyAccounting::getMonthMax() {
uint32_t maxHour = 0.0;
bool included[5] = { false, false, false, false, false };
while(count < config->hours) {
while(count < config->hours && count <= 5) {
uint8_t maxIdx = 0;
uint16_t maxVal = 0;
for(uint8_t i = 0; i < 5; i++) {
if(included[i]) continue;
if(data.peaks[i].day == 0) continue;
if(data.peaks[i].value > maxVal) {
maxVal = data.peaks[i].value;
maxIdx = i;
@@ -253,9 +254,7 @@ float EnergyAccounting::getMonthMax() {
for(uint8_t i = 0; i < 5; i++) {
if(!included[i]) continue;
if(data.peaks[i].day > 0) {
maxHour += data.peaks[i].value;
}
maxHour += data.peaks[i].value;
}
return maxHour > 0 ? maxHour / count / 100.0 : 0.0;
}
@@ -266,7 +265,7 @@ float EnergyAccounting::getPeak(uint8_t num) {
uint8_t count = 0;
bool included[5] = { false, false, false, false, false };
while(count < config->hours) {
while(count < config->hours && count <= 5) {
uint8_t maxIdx = 0;
uint16_t maxVal = 0;
for(uint8_t i = 0; i < 5; i++) {
@@ -341,7 +340,7 @@ bool EnergyAccounting::load() {
this->data.peaks[b].day = b;
memcpy(&this->data.peaks[b].value, buf+i, 2);
b++;
if(b >= config->hours) break;
if(b >= config->hours || b >= 5) break;
}
ret = true;
} else if(buf[0] == 1) {

View File

@@ -14,6 +14,7 @@
class EntsoeA44Parser: public Stream {
public:
EntsoeA44Parser();
virtual ~EntsoeA44Parser();
char* getCurrency();
char* getMeasurementUnit();

View File

@@ -22,7 +22,7 @@ void DnbCurrParser::flush() {
}
size_t DnbCurrParser::write(const uint8_t *buffer, size_t size) {
for(int i = 0; i < size; i++) {
for(size_t i = 0; i < size; i++) {
write(buffer[i]);
}
return size;

View File

@@ -5,6 +5,10 @@ EntsoeA44Parser::EntsoeA44Parser() {
for(int i = 0; i < 24; i++) points[i] = ENTSOE_NO_VALUE;
}
EntsoeA44Parser::~EntsoeA44Parser() {
}
char* EntsoeA44Parser::getCurrency() {
return currency;
}
@@ -35,7 +39,7 @@ void EntsoeA44Parser::flush() {
}
size_t EntsoeA44Parser::write(const uint8_t *buffer, size_t size) {
for(int i = 0; i < size; i++) {
for(size_t i = 0; i < size; i++) {
write(buffer[i]);
}
return size;

View File

@@ -106,11 +106,11 @@ bool EntsoeApi::loop() {
if(midnightMillis == 0) {
uint32_t curDayMillis = (((((tm.Hour * 60) + tm.Minute) * 60) + tm.Second) * 1000);
midnightMillis = now + (SECS_PER_DAY * 1000) - curDayMillis;
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Setting midnight millis %lu\n", midnightMillis);
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Setting midnight millis %llu\n", midnightMillis);
currentDay = tm.Day;
return false;
} else if(now > midnightMillis && currentDay != tm.Day) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Rotating price objects at %lu\n", t);
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Rotating price objects at %lld\n", t);
if(today != NULL) delete today;
if(tomorrow != NULL) {
today = tomorrow;

View File

@@ -13,11 +13,9 @@ public:
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
bool publishTemperatures(AmsConfiguration*, HwTools*);
bool publishPrices(EntsoeApi*);
bool publishSystem(HwTools*);
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
private:
static const uint8_t sensors = 17;
String haTopic = "homeassistant/sensor/";
String haName = "AMS reader";

View File

@@ -3,12 +3,79 @@
#include "Arduino.h"
const char* HA_TOPICS[17] PROGMEM = {"/state", "/state", "/state", "/power", "/power", "/power", "/power", "/power", "/power", "/power", "/power", "/power", "/power", "/energy", "/energy", "/energy", "/energy"};
const char* HA_NAMES[17] PROGMEM = {"Status", "Supply volt", "Temperature", "Active import", "Reactive import", "Active export", "Reactive export", "L1 current", "L2 current", "L3 current",
"L1 voltage", "L2 voltage", "L3 voltage", "Accumulated active import", "Accumulated active export", "Accumulated reactive import", "Accumulated reactive export"};
const char* HA_PARAMS[17] PROGMEM = {"rssi", "vcc", "temp", "P", "Q", "PO", "QO", "I1", "I2", "I3", "U1", "U2", "U3", "tPI", "tPO", "tQI", "tQO"};
const char* HA_UOM[17] PROGMEM = {"dBm", "V", "C", "W", "W", "W", "W", "A", "A", "A", "V", "V", "V", "kWh", "kWh", "kWh", "kWh"};
const char* HA_DEVCL[17] PROGMEM = {"signal_strength", "voltage", "temperature", "power", "power", "power", "power", "current", "current", "current", "voltage", "voltage", "voltage", "energy", "energy", "energy", "energy"};
const char* HA_STACL[17] PROGMEM = {"", "", "", "\"measurement\"", "\"measurement\"", "\"measurement\"", "\"measurement\"", "", "", "", "", "", "", "\"total_increasing\"", "\"total_increasing\"", "\"total_increasing\"", "\"total_increasing\""};
struct HomeAssistantSensor {
const char* name;
const char* topic;
const char* path;
const char* uom;
const char* devcl;
const char* stacl;
};
const uint8_t HA_SENSOR_COUNT PROGMEM = 60;
HomeAssistantSensor HA_SENSORS[HA_SENSOR_COUNT] PROGMEM = {
{"Status", "/state", "rssi", "dBm", "signal_strength", "\"measurement\""},
{"Supply volt", "/state", "vcc", "V", "voltage", "\"measurement\""},
{"Temperature", "/state", "temp", "C", "temperature", "\"measurement\""},
{"Active import", "/power", "P", "W", "power", "\"measurement\""},
{"L1 active import", "/power", "P1", "W", "power", "\"measurement\""},
{"L2 active import", "/power", "P2", "W", "power", "\"measurement\""},
{"L3 active import", "/power", "P3", "W", "power", "\"measurement\""},
{"Reactive import", "/power", "Q", "VAr", "reactive_power", "\"measurement\""},
{"Active export", "/power", "PO", "W", "power", "\"measurement\""},
{"L1 active export", "/power", "PO1", "W", "power", "\"measurement\""},
{"L2 active export", "/power", "PO2", "W", "power", "\"measurement\""},
{"L3 active export", "/power", "PO3", "W", "power", "\"measurement\""},
{"Reactive export", "/power", "QO", "VAr", "reactive_power", "\"measurement\""},
{"L1 current", "/power", "I1", "A", "current", "\"measurement\""},
{"L2 current", "/power", "I2", "A", "current", "\"measurement\""},
{"L3 current", "/power", "I3", "A", "current", "\"measurement\""},
{"L1 voltage", "/power", "U1", "V", "voltage", "\"measurement\""},
{"L2 voltage", "/power", "U2", "V", "voltage", "\"measurement\""},
{"L3 voltage", "/power", "U3", "V", "voltage", "\"measurement\""},
{"Accumulated active import", "/energy", "tPI", "kWh", "energy", "\"total_increasing\""},
{"Accumulated active export", "/energy", "tPO", "kWh", "energy", "\"total_increasing\""},
{"Accumulated reactive import","/energy", "tQI", "kVArh","energy", "\"total_increasing\""},
{"Accumulated reactive export","/energy", "tQO", "kVArh","energy", "\"total_increasing\""},
{"Power factor", "/power", "PF", "", "power_factor", "\"measurement\""},
{"L1 power factor", "/power", "PF1", "", "power_factor", "\"measurement\""},
{"L2 power factor", "/power", "PF2", "", "power_factor", "\"measurement\""},
{"L3 power factor", "/power", "PF3", "", "power_factor", "\"measurement\""},
{"Price current hour", "/prices", "prices['0']", "", "monetary", ""},
{"Price next hour", "/prices", "prices['1']", "", "monetary", ""},
{"Price in two hour", "/prices", "prices['2']", "", "monetary", ""},
{"Price in three hour", "/prices", "prices['3']", "", "monetary", ""},
{"Price in four hour", "/prices", "prices['4']", "", "monetary", ""},
{"Price in five hour", "/prices", "prices['5']", "", "monetary", ""},
{"Price in six hour", "/prices", "prices['6']", "", "monetary", ""},
{"Price in seven hour", "/prices", "prices['7']", "", "monetary", ""},
{"Price in eight hour", "/prices", "prices['8']", "", "monetary", ""},
{"Price in nine hour", "/prices", "prices['9']", "", "monetary", ""},
{"Price in ten hour", "/prices", "prices['10']", "", "monetary", ""},
{"Price in eleven hour", "/prices", "prices['11']", "", "monetary", ""},
{"Minimum price ahead", "/prices", "prices.min", "", "monetary", ""},
{"Maximum price ahead", "/prices", "prices.max", "", "monetary", ""},
{"Cheapest 1hr period ahead", "/prices", "prices.cheapest1hr","", "timestamp", ""},
{"Cheapest 3hr period ahead", "/prices", "prices.cheapest3hr","", "timestamp", ""},
{"Cheapest 6hr period ahead", "/prices", "prices.cheapest6hr","", "timestamp", ""},
{"Month max", "/realtime","max", "kWh", "energy", "\"total_increasing\""},
{"Tariff threshold", "/realtime","threshold", "kWh", "energy", "\"total_increasing\""},
{"Current hour used", "/realtime","hour.use", "kWh", "energy", "\"total_increasing\""},
{"Current hour cost", "/realtime","hour.cost", "", "monetary", "\"total_increasing\""},
{"Current hour produced", "/realtime","hour.produced", "kWh", "energy", "\"total_increasing\""},
{"Current day used", "/realtime","day.use", "kWh", "energy", "\"total_increasing\""},
{"Current day cost", "/realtime","day.cost", "", "monetary", "\"total_increasing\""},
{"Current day produced", "/realtime","day.produced", "kWh", "energy", "\"total_increasing\""},
{"Current month used", "/realtime","month.use", "kWh", "energy", "\"total_increasing\""},
{"Current month cost", "/realtime","month.cost", "", "monetary", "\"total_increasing\""},
{"Current month produced", "/realtime","month.produced", "kWh", "energy", "\"total_increasing\""},
{"Current month peak 1", "/realtime","peaks[0]", "kWh", "energy", ""},
{"Current month peak 2", "/realtime","peaks[1]", "kWh", "energy", ""},
{"Current month peak 3", "/realtime","peaks[2]", "kWh", "energy", ""},
{"Current month peak 4", "/realtime","peaks[3]", "kWh", "energy", ""},
{"Current month peak 5", "/realtime","peaks[4]", "kWh", "energy", ""},
};
#endif

View File

@@ -3,5 +3,5 @@
"tPO" : %.2f,
"tQI" : %.2f,
"tQO" : %.2f,
"rtc" : %lu
"rtc" : %lld
}

View File

@@ -11,9 +11,5 @@
"I3" : %.2f,
"U1" : %.2f,
"U2" : %.2f,
"U3" : %.2f,
"PF" : %.2f,
"PF1" : %.2f,
"PF2" : %.2f,
"PF3" : %.2f
"U3" : %.2f
}

View File

@@ -6,9 +6,11 @@
#include "json/ha1_json.h"
#include "json/ha2_json.h"
#include "json/ha3_json.h"
#include "json/ha4_json.h"
#include "json/jsonsys_json.h"
#include "json/jsonprices_json.h"
#include "json/hadiscover_json.h"
#include "json/realtime_json.h"
bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea) {
if(topic.isEmpty() || !mqtt->connected())
@@ -32,8 +34,8 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En
snprintf_P(json, BufferSize, HA1_JSON,
data->getActiveImportPower()
);
return mqtt->publish(topic + "/power", json);
} else if(data->getListType() >= 2) { // publish power counts and volts/amps
mqtt->publish(topic + "/power", json);
} else if(data->getListType() <= 3) { // publish power counts and volts/amps
snprintf_P(json, BufferSize, HA3_JSON,
data->getListId().c_str(),
data->getMeterId().c_str(),
@@ -47,15 +49,62 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En
data->getL3Current(),
data->getL1Voltage(),
data->getL2Voltage(),
data->getL3Voltage()
);
mqtt->publish(topic + "/power", json);
} else if(data->getListType() == 4) { // publish power counts and volts/amps/phase power and PF
snprintf_P(json, BufferSize, HA4_JSON,
data->getListId().c_str(),
data->getMeterId().c_str(),
meterModel.c_str(),
data->getActiveImportPower(),
data->getL1ActiveImportPower(),
data->getL2ActiveImportPower(),
data->getL3ActiveImportPower(),
data->getReactiveImportPower(),
data->getActiveExportPower(),
data->getL1ActiveExportPower(),
data->getL2ActiveExportPower(),
data->getL3ActiveExportPower(),
data->getReactiveExportPower(),
data->getL1Current(),
data->getL2Current(),
data->getL3Current(),
data->getL1Voltage(),
data->getL2Voltage(),
data->getL3Voltage(),
data->getPowerFactor() == 0 ? 1 : data->getPowerFactor(),
data->getPowerFactor() == 0 ? 1 : data->getL1PowerFactor(),
data->getPowerFactor() == 0 ? 1 : data->getL2PowerFactor(),
data->getPowerFactor() == 0 ? 1 : data->getL3PowerFactor()
);
return mqtt->publish(topic + "/power", json);
mqtt->publish(topic + "/power", json);
}
return false;
String peaks = "";
uint8_t peakCount = ea->getConfig()->hours;
if(peakCount > 5) peakCount = 5;
for(uint8_t i = 1; i <= peakCount; i++) {
if(!peaks.isEmpty()) peaks += ",";
peaks += String(ea->getPeak(i), 2);
}
snprintf_P(json, BufferSize, REALTIME_JSON,
ea->getMonthMax(),
peaks.c_str(),
ea->getCurrentThreshold(),
ea->getUseThisHour(),
ea->getCostThisHour(),
ea->getProducedThisHour(),
ea->getUseToday(),
ea->getCostToday(),
ea->getProducedToday(),
ea->getUseThisMonth(),
ea->getCostThisMonth(),
ea->getProducedThisMonth()
);
mqtt->publish(topic + "/realtime", json);
return true;
}
bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) {
@@ -92,7 +141,7 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
time_t now = time(nullptr);
float min1hr, min3hr, min6hr;
float min1hr = 0.0, min3hr = 0.0, min6hr = 0.0;
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
float min = INT16_MAX, max = INT16_MIN;
float values[24];
@@ -142,7 +191,7 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
}
char ts1hr[21];
char ts1hr[24];
if(min1hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min1hrIdx);
//Serial.printf("1hr: %d %lu\n", min1hrIdx, ts);
@@ -150,7 +199,7 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
breakTime(ts, tm);
sprintf(ts1hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
}
char ts3hr[21];
char ts3hr[24];
if(min3hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min3hrIdx);
//Serial.printf("3hr: %d %lu\n", min3hrIdx, ts);
@@ -158,7 +207,7 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
breakTime(ts, tm);
sprintf(ts3hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
}
char ts6hr[21];
char ts6hr[24];
if(min6hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min6hrIdx);
//Serial.printf("6hr: %d %lu\n", min6hrIdx, ts);
@@ -187,10 +236,10 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
ts3hr,
ts6hr
);
return mqtt->publish(topic + "/prices", json);
return mqtt->publish(topic + "/prices", json, true, 0);
}
bool HomeAssistantMqttHandler::publishSystem(HwTools* hw) {
bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
if(topic.isEmpty() || !mqtt->connected()){
sequence = 0;
return false;
@@ -217,27 +266,46 @@ bool HomeAssistantMqttHandler::publishSystem(HwTools* hw) {
#endif
String haUrl = "http://" + haUID + ".local/";
// Could this be necessary? haUID.replace("-", "_");
uint8_t peakCount = ea->getConfig()->hours;
if(peakCount > 5) peakCount = 5;
for(int i=0;i<sensors;i++){
uint8_t peaks = 0;
for(int i=0;i<HA_SENSOR_COUNT;i++) {
HomeAssistantSensor sensor = HA_SENSORS[i];
String uid = String(sensor.path);
uid.replace(".", "");
uid.replace("[", "");
uid.replace("]", "");
uid.replace("'", "");
String uom = String(sensor.uom);
if(strncmp(sensor.devcl, "monetary", 8) == 0) {
if(eapi == NULL) continue;
uom = String(eapi->getCurrency());
}
if(strncmp(sensor.path, "peaks[", 6) == 0) {
if(peaks >= peakCount) continue;
peaks++;
}
snprintf_P(json, BufferSize, HADISCOVER_JSON,
FPSTR(HA_NAMES[i]),
topic.c_str(), FPSTR(HA_TOPICS[i]),
haUID.c_str(), FPSTR(HA_PARAMS[i]),
haUID.c_str(), FPSTR(HA_PARAMS[i]),
FPSTR(HA_UOM[i]),
FPSTR(HA_PARAMS[i]),
FPSTR(HA_DEVCL[i]),
sensor.name,
topic.c_str(), sensor.topic,
haUID.c_str(), uid.c_str(),
haUID.c_str(), uid.c_str(),
uom.c_str(),
sensor.path,
sensor.devcl,
haUID.c_str(),
haName.c_str(),
haModel.c_str(),
VERSION,
haManuf.c_str(),
haUrl.c_str(),
strlen_P(HA_STACL[i]) > 0 ? ", \"stat_cla\" :" : "",
strlen_P(HA_STACL[i]) > 0 ? (char *) FPSTR(HA_STACL[i]) : ""
strlen_P(sensor.stacl) > 0 ? ", \"stat_cla\" :" : "",
strlen_P(sensor.stacl) > 0 ? (char *) FPSTR(sensor.stacl) : ""
);
mqtt->publish(haTopic + haUID + "_" + FPSTR(HA_PARAMS[i]) + "/config", json, true, 0);
mqtt->publish(haTopic + haUID + "_" + uid.c_str() + "/config", json, true, 0);
}
autodiscoverInit = true;
}
if(listType>0) sequence++;

View File

@@ -371,10 +371,10 @@ bool HwTools::ledOff(uint8_t color) {
bool HwTools::ledBlink(uint8_t color, uint8_t blink) {
for(int i = 0; i < blink; i++) {
if(!ledOn(color)) return false;
delay(50);
delay(150);
ledOff(color);
if(i != blink)
delay(50);
if(i != blink-1)
delay(250);
}
return true;
}

View File

@@ -13,7 +13,7 @@ public:
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
bool publishTemperatures(AmsConfiguration*, HwTools*);
bool publishPrices(EntsoeApi*);
bool publishSystem(HwTools*);
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
private:
String clientId;

View File

@@ -1,8 +1,8 @@
{
"id" : "%s",
"name" : "%s",
"up" : %lu,
"t" : %lu,
"up" : %u,
"t" : %lld,
"vcc" : %.3f,
"rssi": %d,
"temp": %.2f,

View File

@@ -1,8 +1,8 @@
{
"id" : "%s",
"name" : "%s",
"up" : %lu,
"t" : %lu,
"up" : %u,
"t" : %lld,
"vcc" : %.3f,
"rssi": %d,
"temp": %.2f,

View File

@@ -1,8 +1,8 @@
{
"id" : "%s",
"name" : "%s",
"up" : %lu,
"t" : %lu,
"up" : %u,
"t" : %lld,
"vcc" : %.3f,
"rssi": %d,
"temp": %.2f,
@@ -24,7 +24,7 @@
"tPO" : %.3f,
"tQI" : %.3f,
"tQO" : %.3f,
"rtc" : %lu
"rtc" : %lld
},
"realtime" : {
"h" : %.2f,

View File

@@ -1,8 +1,8 @@
{
"id" : "%s",
"name" : "%s",
"up" : %lu,
"t" : %lu,
"up" : %u,
"t" : %lld,
"vcc" : %.3f,
"rssi": %d,
"temp": %.2f,
@@ -11,8 +11,14 @@
"id" : "%s",
"type" : "%s",
"P" : %d,
"P1" : %.2f,
"P2" : %.2f,
"P3" : %.2f,
"Q" : %d,
"PO" : %d,
"PO1" : %.2f,
"PO2" : %.2f,
"PO3" : %.2f,
"QO" : %d,
"I1" : %.2f,
"I2" : %.2f,
@@ -28,7 +34,7 @@
"tPO" : %.2f,
"tQI" : %.2f,
"tQO" : %.2f,
"rtc" : %lu
"rtc" : %lld
},
"realtime" : {
"h" : %.2f,

View File

@@ -111,8 +111,14 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
data->getMeterId().c_str(),
meterModel.c_str(),
data->getActiveImportPower(),
data->getL1ActiveImportPower(),
data->getL2ActiveImportPower(),
data->getL3ActiveImportPower(),
data->getReactiveImportPower(),
data->getActiveExportPower(),
data->getL1ActiveExportPower(),
data->getL2ActiveExportPower(),
data->getL3ActiveExportPower(),
data->getReactiveExportPower(),
data->getL1Current(),
data->getL2Current(),
@@ -143,8 +149,9 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
bool JsonMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) {
int count = hw->getTempSensorCount();
if(count < 2)
if(count < 2) {
return false;
}
snprintf(json, 24, "{\"temperatures\":{");
@@ -173,7 +180,7 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
time_t now = time(nullptr);
float min1hr, min3hr, min6hr;
float min1hr = 0.0, min3hr = 0.0, min6hr = 0.0;
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
float min = INT16_MAX, max = INT16_MIN;
float values[24];
@@ -223,7 +230,7 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
}
char ts1hr[21];
char ts1hr[24];
if(min1hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min1hrIdx);
//Serial.printf("1hr: %d %lu\n", min1hrIdx, ts);
@@ -231,7 +238,7 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
breakTime(ts, tm);
sprintf(ts1hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
}
char ts3hr[21];
char ts3hr[24];
if(min3hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min3hrIdx);
//Serial.printf("3hr: %d %lu\n", min3hrIdx, ts);
@@ -239,7 +246,7 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
breakTime(ts, tm);
sprintf(ts3hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
}
char ts6hr[21];
char ts6hr[24];
if(min6hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min6hrIdx);
//Serial.printf("6hr: %d %lu\n", min6hrIdx, ts);
@@ -271,7 +278,7 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
return mqtt->publish(topic, json);
}
bool JsonMqttHandler::publishSystem(HwTools* hw) {
bool JsonMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
if(init || topic.isEmpty() || !mqtt->connected())
return false;

View File

@@ -12,7 +12,7 @@ public:
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
bool publishTemperatures(AmsConfiguration*, HwTools*);
bool publishPrices(EntsoeApi*);
bool publishSystem(HwTools*);
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
private:
String topic;

View File

@@ -11,6 +11,24 @@ bool RawMqttHandler::publish(AmsData* data, AmsData* meterState, EnergyAccountin
}
switch(data->getListType()) {
case 4:
if(full || meterState->getL1ActiveImportPower() != data->getL1ActiveImportPower()) {
mqtt->publish(topic + "/meter/import/l1", String(data->getL1ActiveImportPower(), 2));
}
if(full || meterState->getL2ActiveImportPower() != data->getL2ActiveImportPower()) {
mqtt->publish(topic + "/meter/import/l2", String(data->getL2ActiveImportPower(), 2));
}
if(full || meterState->getL3ActiveImportPower() != data->getL3ActiveImportPower()) {
mqtt->publish(topic + "/meter/import/l3", String(data->getL3ActiveImportPower(), 2));
}
if(full || meterState->getL1ActiveExportPower() != data->getL1ActiveExportPower()) {
mqtt->publish(topic + "/meter/export/l1", String(data->getL1ActiveExportPower(), 2));
}
if(full || meterState->getL2ActiveExportPower() != data->getL2ActiveExportPower()) {
mqtt->publish(topic + "/meter/export/l2", String(data->getL2ActiveExportPower(), 2));
}
if(full || meterState->getL3ActiveExportPower() != data->getL3ActiveExportPower()) {
mqtt->publish(topic + "/meter/export/l3", String(data->getL3ActiveExportPower(), 2));
}
if(full || meterState->getPowerFactor() != data->getPowerFactor()) {
mqtt->publish(topic + "/meter/powerfactor", String(data->getPowerFactor(), 2));
}
@@ -106,7 +124,7 @@ bool RawMqttHandler::publishPrices(EntsoeApi* eapi) {
time_t now = time(nullptr);
float min1hr, min3hr, min6hr;
float min1hr = 0.0, min3hr = 0.0, min6hr = 0.0;
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
float min = INT16_MAX, max = INT16_MIN;
float values[34];
@@ -157,7 +175,7 @@ bool RawMqttHandler::publishPrices(EntsoeApi* eapi) {
}
char ts1hr[21];
char ts1hr[24];
if(min1hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min1hrIdx);
//Serial.printf("1hr: %d %lu\n", min1hrIdx, ts);
@@ -165,7 +183,7 @@ bool RawMqttHandler::publishPrices(EntsoeApi* eapi) {
breakTime(ts, tm);
sprintf(ts1hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
}
char ts3hr[21];
char ts3hr[24];
if(min3hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min3hrIdx);
//Serial.printf("3hr: %d %lu\n", min3hrIdx, ts);
@@ -173,7 +191,7 @@ bool RawMqttHandler::publishPrices(EntsoeApi* eapi) {
breakTime(ts, tm);
sprintf(ts3hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
}
char ts6hr[21];
char ts6hr[24];
if(min6hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min6hrIdx);
//Serial.printf("6hr: %d %lu\n", min6hrIdx, ts);
@@ -211,7 +229,7 @@ bool RawMqttHandler::publishPrices(EntsoeApi* eapi) {
return true;
}
bool RawMqttHandler::publishSystem(HwTools* hw) {
bool RawMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
if(topic.isEmpty() || !mqtt->connected())
return false;

View File

@@ -3,8 +3,6 @@
#include "Arduino.h"
static uint32_t _uptime_last_value = 0;
static uint32_t _uptime_rollovers = 0;
uint64_t millis64();
#endif

View File

@@ -1,5 +1,8 @@
#include "Uptime.h"
uint32_t _uptime_last_value = 0;
uint32_t _uptime_rollovers = 0;
uint64_t millis64() {
uint32_t new_low32 = millis();
if (new_low32 < _uptime_last_value) _uptime_rollovers++;