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

@@ -43,3 +43,37 @@ FF // Last byte of OBIS in previous block
0600000000 // Accumulated export
8BA4
7E
7E A1 23 CE FF 03 13 21 55 E6 E7 00
0F 00 00 08 E2
0C 07 E5 07 13 01 0C 1A 0A FF 80 00 00
02 0B // 11
01 0B // 11
02 04 12 00 28 09 06 00 08 19 09 00 FF 0F 02 12 00 00
02 04 12 00 28 09 06 00 08 19 09 00 FF 0F 01 12 00 00
02 04 12 00 01 09 06 00 00 60 01 00 FF 0F 02 12 00 00
02 04 12 00 03 09 06 01 00 01 07 00 FF 0F 02 12 00 00
02 04 12 00 03 09 06 01 00 02 07 00 FF 0F 02 12 00 00
02 04 12 00 03 09 06 01 01 01 08 00 FF 0F 02 12 00 00
02 04 12 00 03 09 06 01 01 02 08 00 FF 0F 02 12 00 00
02 04 12 00 03 09 06 01 01 05 08 00 FF 0F 02 12 00 00
02 04 12 00 03 09 06 01 01 06 08 00 FF 0F 02 12 00 00
02 04 12 00 03 09 06 01 01 07 08 00 FF 0F 02 12 00 00
02 04 12 00 03 09 06 01 01 08 08 00 FF 0F 02 12 00 00
09 06 00 08 19 09 00 FF
09 08 34 33 30 39 34 33 35 31
06 00 00 00 0B
06 00 00 00 00
06 00 00 00 10
06 00 00 00 04
06 00 00 00 00
06 00 00 00 08
06 00 00 00 00
06 00 00 00 01
7C 8B
7E

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++;

View File

@@ -19,6 +19,6 @@ hf = """
#define VERSION "{}"
#endif
#define BUILD_EPOCH {}
""".format(version, time())
""".format(version, round(time()))
with open(FILENAME_VERSION_H, 'w+') as f:
f.write(hf)

View File

@@ -1,7 +1,7 @@
#ifndef _AMSTOMQTTBRIDGE_H
#define _AMSTOMQTTBRIDGE_H
#define WIFI_CONNECTION_TIMEOUT 30000;
#define WIFI_CONNECTION_TIMEOUT 30000
#define INVALID_BUTTON_PIN 0xFFFFFFFF

View File

@@ -65,10 +65,11 @@ ADC_MODE(ADC_VCC);
#include "RemoteDebug.h"
#define BUF_SIZE_COMMON (2048)
#define BUF_SIZE_HAN (1024)
#define BUF_SIZE_HAN (1280)
#include "IEC6205621.h"
#include "IEC6205675.h"
#include "LNG.h"
#include "ams/DataParsers.h"
@@ -118,7 +119,6 @@ DLMSParser *dlmsParser = NULL;
DSMRParser *dsmrParser = NULL;
void setup() {
WiFiConfig wifi;
Serial.begin(115200);
if(!config.getGpioConfig(gpioConfig)) {
@@ -245,7 +245,6 @@ void setup() {
}
Debug.setSerialEnabled(true);
DebugConfig debug;
delay(1);
float vcc = hw.getVcc();
@@ -435,6 +434,10 @@ void loop() {
}
}
if(now > 10000 && now - lastErrorBlink > 3000) {
errorBlink();
}
// Only do normal stuff if we're not booted as AP
if (WiFi.getMode() != WIFI_AP) {
if (WiFi.status() != WL_CONNECTED) {
@@ -505,13 +508,10 @@ void loop() {
MDNS.update();
#endif
if(now > 10000 && now - lastErrorBlink > 3000) {
errorBlink();
}
if (mqttEnabled || config.isMqttChanged()) {
if(mqtt == NULL || !mqtt->connected() || config.isMqttChanged()) {
MQTT_connect();
config.ackMqttChange();
}
} else if(mqtt != NULL && mqtt->connected()) {
mqttClient->stop();
@@ -589,11 +589,11 @@ void loop() {
if(mqtt != NULL && mqttHandler != NULL && WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt->connected() && !topic.isEmpty()) {
mqttHandler->publishTemperatures(&config, &hw);
}
debugD("Used %d ms to update temperature", millis()-start);
debugD("Used %ld ms to update temperature", millis()-start);
}
if(now - lastSysupdate > 10000) {
if(mqtt != NULL && mqttHandler != NULL && WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt->connected() && !topic.isEmpty()) {
mqttHandler->publishSystem(&hw);
mqttHandler->publishSystem(&hw, eapi, &ea);
}
lastSysupdate = now;
}
@@ -723,22 +723,25 @@ void errorBlink() {
if(lastError == 3)
lastError = 0;
lastErrorBlink = millis();
for(;lastError < 3;lastError++) {
switch(lastError) {
while(lastError < 3) {
switch(lastError++) {
case 0:
if(lastErrorBlink - meterState.getLastUpdateMillis() > 30000) {
debugW("No HAN data received last 30s, single blink");
hw.ledBlink(LED_RED, 1); // If no message received from AMS in 30 sec, blink once
return;
}
break;
case 1:
if(mqttEnabled && mqtt != NULL && mqtt->lastError() != 0) {
debugW("MQTT connection not available, double blink");
hw.ledBlink(LED_RED, 2); // If MQTT error, blink twice
return;
}
break;
case 2:
if(WiFi.getMode() != WIFI_AP && WiFi.status() != WL_CONNECTED) {
debugW("WiFi not connected, tripe blink");
hw.ledBlink(LED_RED, 3); // If WiFi not connected, blink three times
return;
}
@@ -825,6 +828,11 @@ bool readHanPort() {
}
if(pos == DATA_PARSE_INCOMPLETE) {
return false;
} else if(pos == DATA_PARSE_UNKNOWN_DATA) {
len = len + hanSerial->readBytes(hanBuffer+len, BUF_SIZE_HAN-len);
debugPrint(hanBuffer, 0, len);
len = 0;
return false;
}
if(pos == DATA_PARSE_INTERMEDIATE_SEGMENT) {
@@ -848,20 +856,26 @@ bool readHanPort() {
}
AmsData data;
char* payload = ((char *) (hanBuffer)) + pos;
if(ctx.type == DATA_TAG_DLMS) {
// If MQTT bytestream payload is selected (mqttHandler == NULL), send the payload to MQTT
if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) {
mqtt->publish(topic.c_str(), toHex(hanBuffer+pos, ctx.length));
mqtt->publish(topic.c_str(), toHex((byte*) payload, ctx.length));
mqtt->loop();
}
debugV("Using application data:");
if(Debug.isActive(RemoteDebug::VERBOSE)) debugPrint(hanBuffer+pos, 0, ctx.length);
if(Debug.isActive(RemoteDebug::VERBOSE)) debugPrint((byte*) payload, 0, ctx.length);
// TODO: Split IEC6205675 into DataParserKaifa and DataParserObis. This way we can add other means of parsing, for those other proprietary formats
data = IEC6205675(((char *) (hanBuffer)) + pos, meterState.getMeterType(), &meterConfig, ctx);
// Rudimentary detector for L&G proprietary format
if(payload[0] == CosemTypeStructure && payload[2] == CosemTypeArray && payload[1] == payload[3]) {
data = LNG(payload, meterState.getMeterType(), &meterConfig, ctx, &Debug);
} else {
// TODO: Split IEC6205675 into DataParserKaifa and DataParserObis. This way we can add other means of parsing, for those other proprietary formats
data = IEC6205675(payload, meterState.getMeterType(), &meterConfig, ctx);
}
} else if(ctx.type == DATA_TAG_DSMR) {
data = IEC6205621(((char *) (hanBuffer)) + pos);
data = IEC6205621(payload);
}
len = 0;
@@ -977,17 +991,34 @@ void debugPrint(byte *buffer, int start, int length) {
Debug.println("");
}
unsigned long wifiTimeout = WIFI_CONNECTION_TIMEOUT;
unsigned long lastWifiRetry = -WIFI_CONNECTION_TIMEOUT;
void WiFi_connect() {
if(millis() - lastWifiRetry < wifiTimeout) {
delay(50);
return;
}
lastWifiRetry = millis();
if (WiFi.status() != WL_CONNECTED) {
if(WiFi.status() == WL_DISCONNECTED) {
if(millis() - lastWifiRetry < WIFI_CONNECTION_TIMEOUT) {
return;
}
}
if(WiFi.getMode() != WIFI_OFF) {
switch(WiFi.status()) {
case WL_NO_SSID_AVAIL:
debugE("WiFi error, no SSID available");
break;
case WL_CONNECT_FAILED:
debugE("WiFi error, connection failed");
break;
case WL_CONNECTION_LOST:
debugE("WiFi error, connection lost");
break;
#if defined(ESP8266)
case WL_WRONG_PASSWORD:
debugE("WiFi error, wrong password");
break;
#endif
default:
debugE("WiFi error, %d", WiFi.status());
break;
}
if(wifiReconnectCount > 3) {
ESP.restart();
return;
@@ -1021,11 +1052,13 @@ void WiFi_connect() {
WiFi.softAPdisconnect(true);
WiFi.enableAP(false);
WiFi.mode(WIFI_OFF);
#if defined(ESP8266)
WiFi.forceSleepBegin();
#endif
yield();
wifiTimeout = 5000;
return;
}
wifiTimeout = WIFI_CONNECTION_TIMEOUT;
lastWifiRetry = millis();
WiFiConfig wifi;
if(!config.getWiFiConfig(wifi) || strlen(wifi.ssid) == 0) {
@@ -1043,7 +1076,7 @@ void WiFi_connect() {
}
#endif
WiFi.mode(WIFI_STA);
WiFi.setSleep(WIFI_PS_MIN_MODEM);
WiFi.setSleep(WIFI_PS_MAX_MODEM);
#if defined(ESP32)
if(wifi.power >= 195)
WiFi.setTxPower(WIFI_POWER_19_5dBm);
@@ -1113,16 +1146,16 @@ void WiFi_connect() {
void mqttMessageReceived(String &topic, String &payload) {
debugI("Received message for topic %s", topic.c_str() );
if(meterConfig.source == METER_SOURCE_MQTT) {
DataParserContext ctx = {payload.length()/2};
fromHex(hanBuffer, payload, ctx.length);
uint16_t pos = unwrapData(hanBuffer, ctx);
//if(meterConfig.source == METER_SOURCE_MQTT) {
//DataParserContext ctx = {static_cast<uint8_t>(payload.length()/2)};
//fromHex(hanBuffer, payload, ctx.length);
//uint16_t pos = unwrapData(hanBuffer, ctx);
// TODO: Run through DLMS/DMSR parser and apply AmsData
}
//}
}
int16_t unwrapData(uint8_t *buf, DataParserContext &context) {
int16_t ret;
int16_t ret = 0;
bool doRet = false;
uint16_t end = BUF_SIZE_HAN;
uint8_t tag = (*buf);
@@ -1243,7 +1276,6 @@ void MQTT_connect() {
if(Debug.isActive(RemoteDebug::WARNING)) debugW("No MQTT config");
mqttEnabled = false;
ws.setMqttEnabled(false);
config.ackMqttChange();
return;
}
if(mqtt != NULL) {
@@ -1258,20 +1290,19 @@ void MQTT_connect() {
}
mqtt->disconnect();
if(config.isMqttChanged()) {
if(mqttSecureClient != NULL) {
mqttSecureClient->stop();
delete mqttSecureClient;
mqttSecureClient = NULL;
} else {
mqttClient->stop();
}
mqttClient = NULL;
}
yield();
} else {
uint16_t size = 256;
switch(mqttConfig.payloadFormat) {
case 0: // JSON
case 4: // Home Assistant
size = 768;
break;
case 255: // Raw frame
size = 1024;
break;
}
mqtt = new MQTTClient(size);
mqtt = new MQTTClient(1024);
ws.setMqtt(mqtt);
}
@@ -1306,54 +1337,54 @@ void MQTT_connect() {
debugI("MQTT SSL is configured (%dkb free heap)", ESP.getFreeHeap());
if(mqttSecureClient == NULL) {
mqttSecureClient = new WiFiClientSecure();
}
#if defined(ESP8266)
mqttSecureClient->setBufferSizes(512, 512);
#endif
if(LittleFS.begin()) {
File file;
#if defined(ESP8266)
mqttSecureClient->setBufferSizes(512, 512);
#endif
if(LittleFS.begin()) {
File file;
if(LittleFS.exists(FILE_MQTT_CA)) {
debugI("Found MQTT CA file (%dkb free heap)", ESP.getFreeHeap());
file = LittleFS.open(FILE_MQTT_CA, "r");
#if defined(ESP8266)
BearSSL::X509List *serverTrustedCA = new BearSSL::X509List(file);
mqttSecureClient->setTrustAnchors(serverTrustedCA);
#elif defined(ESP32)
mqttSecureClient->loadCACert(file, file.size());
#endif
file.close();
if(LittleFS.exists(FILE_MQTT_CA)) {
debugI("Found MQTT CA file (%dkb free heap)", ESP.getFreeHeap());
file = LittleFS.open(FILE_MQTT_CA, "r");
#if defined(ESP8266)
BearSSL::X509List *serverTrustedCA = new BearSSL::X509List(file);
mqttSecureClient->setTrustAnchors(serverTrustedCA);
#elif defined(ESP32)
mqttSecureClient->loadCACert(file, file.size());
#endif
file.close();
}
if(LittleFS.exists(FILE_MQTT_CERT) && LittleFS.exists(FILE_MQTT_KEY)) {
#if defined(ESP8266)
debugI("Found MQTT certificate file (%dkb free heap)", ESP.getFreeHeap());
file = LittleFS.open(FILE_MQTT_CERT, "r");
BearSSL::X509List *serverCertList = new BearSSL::X509List(file);
file.close();
debugI("Found MQTT key file (%dkb free heap)", ESP.getFreeHeap());
file = LittleFS.open(FILE_MQTT_KEY, "r");
BearSSL::PrivateKey *serverPrivKey = new BearSSL::PrivateKey(file);
file.close();
debugD("Setting client certificates (%dkb free heap)", ESP.getFreeHeap());
mqttSecureClient->setClientRSACert(serverCertList, serverPrivKey);
#elif defined(ESP32)
debugI("Found MQTT certificate file (%dkb free heap)", ESP.getFreeHeap());
file = LittleFS.open(FILE_MQTT_CERT, "r");
mqttSecureClient->loadCertificate(file, file.size());
file.close();
debugI("Found MQTT key file (%dkb free heap)", ESP.getFreeHeap());
file = LittleFS.open(FILE_MQTT_KEY, "r");
mqttSecureClient->loadPrivateKey(file, file.size());
file.close();
#endif
}
LittleFS.end();
debugD("MQTT SSL setup complete (%dkb free heap)", ESP.getFreeHeap());
}
if(LittleFS.exists(FILE_MQTT_CERT) && LittleFS.exists(FILE_MQTT_KEY)) {
#if defined(ESP8266)
debugI("Found MQTT certificate file (%dkb free heap)", ESP.getFreeHeap());
file = LittleFS.open(FILE_MQTT_CERT, "r");
BearSSL::X509List *serverCertList = new BearSSL::X509List(file);
file.close();
debugI("Found MQTT key file (%dkb free heap)", ESP.getFreeHeap());
file = LittleFS.open(FILE_MQTT_KEY, "r");
BearSSL::PrivateKey *serverPrivKey = new BearSSL::PrivateKey(file);
file.close();
debugD("Setting client certificates (%dkb free heap)", ESP.getFreeHeap());
mqttSecureClient->setClientRSACert(serverCertList, serverPrivKey);
#elif defined(ESP32)
debugI("Found MQTT certificate file (%dkb free heap)", ESP.getFreeHeap());
file = LittleFS.open(FILE_MQTT_CERT, "r");
mqttSecureClient->loadCertificate(file, file.size());
file.close();
debugI("Found MQTT key file (%dkb free heap)", ESP.getFreeHeap());
file = LittleFS.open(FILE_MQTT_KEY, "r");
mqttSecureClient->loadPrivateKey(file, file.size());
file.close();
#endif
}
LittleFS.end();
debugD("MQTT SSL setup complete (%dkb free heap)", ESP.getFreeHeap());
}
mqttClient = mqttSecureClient;
} else if(mqttClient == NULL) {
@@ -1369,7 +1400,7 @@ void MQTT_connect() {
#if defined(ESP8266)
if(mqttSecureClient) {
time_t epoch = time(nullptr);
debugD("Setting NTP time %i for secure MQTT connection", epoch);
debugD("Setting NTP time %lld for secure MQTT connection", epoch);
mqttSecureClient->setX509Time(epoch);
}
#endif
@@ -1378,10 +1409,9 @@ void MQTT_connect() {
if ((strlen(mqttConfig.username) == 0 && mqtt->connect(mqttConfig.clientId)) ||
(strlen(mqttConfig.username) > 0 && mqtt->connect(mqttConfig.clientId, mqttConfig.username, mqttConfig.password))) {
if (Debug.isActive(RemoteDebug::INFO)) debugI("Successfully connected to MQTT!");
config.ackMqttChange();
if(mqttHandler != NULL) {
mqttHandler->publishSystem(&hw);
mqttHandler->publishSystem(&hw, eapi, &ea);
}
// Subscribe to the chosen MQTT topic, if set in configuration

View File

@@ -296,6 +296,38 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
l3PowerFactor = val;
}
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_L1, sizeof(AMS_OBIS_ACTIVE_IMPORT_L1), ((char *) (d)));
if (val != NOVALUE) {
listType = 4;
l1activeImportPower = val;
}
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_L2, sizeof(AMS_OBIS_ACTIVE_IMPORT_L2), ((char *) (d)));
if (val != NOVALUE) {
listType = 4;
l2activeImportPower = val;
}
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_L3, sizeof(AMS_OBIS_ACTIVE_IMPORT_L3), ((char *) (d)));
if (val != NOVALUE) {
listType = 4;
l3activeImportPower = val;
}
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_L1, sizeof(AMS_OBIS_ACTIVE_EXPORT_L1), ((char *) (d)));
if (val != NOVALUE) {
listType = 4;
l1activeExportPower = val;
}
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_L2, sizeof(AMS_OBIS_ACTIVE_EXPORT_L2), ((char *) (d)));
if (val != NOVALUE) {
listType = 4;
l2activeExportPower = val;
}
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_L3, sizeof(AMS_OBIS_ACTIVE_EXPORT_L3), ((char *) (d)));
if (val != NOVALUE) {
listType = 4;
l3activeExportPower = val;
}
if(meterType == AmsTypeKamstrup) {
if(listType >= 3) {
activeImportCounter *= 10;
@@ -336,12 +368,16 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
if(mid != NULL) {
switch(mid->base.type) {
case CosemTypeString:
memcpy(&meterId, mid->str.data, mid->str.length);
memcpy(str, mid->oct.data, mid->oct.length);
str[mid->oct.length] = 0x00;
meterId = String(str);
meterId[mid->str.length] = 0;
break;
case CosemTypeOctetString:
memcpy(&meterId, mid->oct.data, mid->oct.length);
meterId[mid->oct.length] = 0;
memcpy(str, mid->str.data, mid->str.length);
str[mid->str.length] = 0x00;
meterId = String(str);
meterId[mid->str.length] = 0;
break;
}
}

View File

@@ -49,6 +49,12 @@ private:
uint8_t AMS_OBIS_POWER_FACTOR_L1[4] = { 33, 7, 0, 255 };
uint8_t AMS_OBIS_POWER_FACTOR_L2[4] = { 53, 7, 0, 255 };
uint8_t AMS_OBIS_POWER_FACTOR_L3[4] = { 73, 7, 0, 255 };
uint8_t AMS_OBIS_ACTIVE_IMPORT_L1[4] = { 21, 7, 0, 255 };
uint8_t AMS_OBIS_ACTIVE_IMPORT_L2[4] = { 41, 7, 0, 255 };
uint8_t AMS_OBIS_ACTIVE_IMPORT_L3[4] = { 61, 7, 0, 255 };
uint8_t AMS_OBIS_ACTIVE_EXPORT_L1[4] = { 22, 7, 0, 255 };
uint8_t AMS_OBIS_ACTIVE_EXPORT_L2[4] = { 42, 7, 0, 255 };
uint8_t AMS_OBIS_ACTIVE_EXPORT_L3[4] = { 62, 7, 0, 255 };
};
#endif

111
src/LNG.cpp Normal file
View File

@@ -0,0 +1,111 @@
#include "LNG.h"
#include "lwip/def.h"
#include "ams/Cosem.h"
LNG::LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger) {
LngHeader* h = (LngHeader*) payload;
if(h->tag == CosemTypeStructure && h->arrayTag == CosemTypeArray) {
meterType = AmsTypeLng;
this->packageTimestamp = ctx.timestamp;
uint8_t* ptr = (uint8_t*) &h[1];
uint8_t* data = ptr + (18*h->arrayLength); // Skip descriptors
uint16_t o170 = 0, o270 = 0;
uint16_t o181 = 0, o182 = 0;
uint16_t o281 = 0, o282 = 0;
LngObisDescriptor* descriptor = (LngObisDescriptor*) ptr;
for(uint8_t x = 0; x < h->arrayLength-1; x++) {
ptr = (uint8_t*) &descriptor[1];
descriptor = (LngObisDescriptor*) ptr;
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(L&G) OBIS %d.%d.%d with type 0x%02X", descriptor->obis[2], descriptor->obis[3], descriptor->obis[4], *data);
CosemData* item = (CosemData*) data;
if(descriptor->obis[2] == 1) {
if(descriptor->obis[3] == 7) {
if(descriptor->obis[4] == 0) {
o170 = ntohl(item->dlu.data);
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
}
} else if(descriptor->obis[3] == 8) {
if(descriptor->obis[4] == 0) {
activeImportCounter = ntohl(item->dlu.data) / 1000.0;
listType = listType >= 3 ? listType : 3;
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
} else if(descriptor->obis[4] == 1) {
o181 = ntohl(item->dlu.data);
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
} else if(descriptor->obis[4] == 2) {
o182 = ntohl(item->dlu.data);
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
}
}
} else if(descriptor->obis[2] == 2) {
if(descriptor->obis[3] == 7) {
if(descriptor->obis[4] == 0) {
o270 = ntohl(item->dlu.data);
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
}
} else if(descriptor->obis[3] == 8) {
if(descriptor->obis[4] == 0) {
activeExportCounter = ntohl(item->dlu.data) / 1000.0;
listType = listType >= 3 ? listType : 3;
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
} else if(descriptor->obis[4] == 1) {
o281 = ntohl(item->dlu.data);
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
} else if(descriptor->obis[4] == 2) {
o282 = ntohl(item->dlu.data);
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
}
}
} else if(descriptor->obis[2] == 96) {
if(descriptor->obis[3] == 1) {
if(descriptor->obis[4] == 0) {
char str[item->oct.length+1];
memcpy(str, item->oct.data, item->oct.length);
str[item->oct.length] = '\0';
meterId = String(str);
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %s (oct)", str);
} else if(descriptor->obis[4] == 1) {
char str[item->oct.length+1];
memcpy(str, item->oct.data, item->oct.length);
str[item->oct.length] = '\0';
meterModel = String(str);
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %s (oct)", str);
}
}
}
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("\n");
if(o170 > 0 || o270 > 0) {
int32_t sum = o170-o270;
if(sum > 0) {
listType = listType >= 1 ? listType : 1;
activeImportPower = sum;
} else {
listType = listType >= 2 ? listType : 2;
activeExportPower = sum * -1;
}
}
if(o181 > 0 || o182 > 0) {
activeImportCounter = (o181 + o182) / 1000.0;
listType = listType >= 3 ? listType : 3;
}
if(o281 > 0 || o282 > 0) {
activeExportCounter = (o281 + o282) / 1000.0;
listType = listType >= 3 ? listType : 3;
}
if((*data) == 0x09) {
data += (*(data+1))+2;
} else {
data += 5;
}
lastUpdateMillis = millis();
}
}
}

30
src/LNG.h Normal file
View File

@@ -0,0 +1,30 @@
#ifndef _LNG_H
#define _LNG_H
#include "AmsData.h"
#include "AmsConfiguration.h"
#include "ams/DataParser.h"
#include "RemoteDebug.h"
struct LngHeader {
uint8_t tag;
uint8_t values;
uint8_t arrayTag;
uint8_t arrayLength;
} __attribute__((packed));
struct LngObisDescriptor {
uint8_t ignore1[5];
uint8_t octetTag;
uint8_t octetLength;
uint8_t obis[6];
uint8_t ignore2[5];
} __attribute__((packed));
class LNG : public AmsData {
public:
LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger);
};
#endif

View File

@@ -4,7 +4,6 @@ int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified) {
uint16_t crcPos = 0;
bool reachedEnd = verified;
uint8_t lastByte = 0x00;
int c = 0;
for(int pos = 0; pos < ctx.length; pos++) {
uint8_t b = *(buf+pos);
if(pos == 0 && b != '/') return DATA_PARSE_BOUNDRY_FLAG_MISSING;

View File

@@ -27,7 +27,7 @@ int8_t GCMParser::parse(uint8_t *d, DataParserContext &ctx) {
memcpy(ctx.system_title, ptr, systemTitleLength);
memcpy(initialization_vector, ctx.system_title, systemTitleLength);
int len;
int len = 0;
int headersize = 2 + systemTitleLength;
ptr += systemTitleLength;
if(((*ptr) & 0xFF) == 0x81) {

View File

@@ -5,8 +5,6 @@
int8_t HDLCParser::parse(uint8_t *d, DataParserContext &ctx) {
int len;
uint8_t flag = *d;
uint8_t* ptr;
if(ctx.length < 3)
return DATA_PARSE_INCOMPLETE;

View File

@@ -1,7 +1,6 @@
#include "LlcParser.h"
int8_t LLCParser::parse(uint8_t *buf, DataParserContext &ctx) {
LLCHeader* llc = (LLCHeader*) buf;
ctx.length -= 3;
return 3;
}

View File

@@ -5,8 +5,6 @@ int8_t MBUSParser::parse(uint8_t *d, DataParserContext &ctx) {
int headersize = 3;
int footersize = 1;
uint8_t flag = *d;
uint8_t* ptr;
// https://m-bus.com/documentation-wired/06-application-layer