mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-12 21:44:21 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb1d343ee3 | ||
|
|
1f3c32e80a | ||
|
|
43fbca7099 | ||
|
|
7a36082564 | ||
|
|
fb2cfdfe01 | ||
|
|
6c3dca9344 | ||
|
|
cce5d75fd7 | ||
|
|
8fd411c1d6 | ||
|
|
508b2e6c45 | ||
|
|
af630615db | ||
|
|
222a4f13e2 | ||
|
|
3bc6c75c5a | ||
|
|
dbb3eac709 | ||
|
|
1cee48eab4 | ||
|
|
c28752a00a | ||
|
|
365061df29 | ||
|
|
870617f780 | ||
|
|
4972b980ba | ||
|
|
51c70abda3 | ||
|
|
8d448533c7 | ||
|
|
43cb9a0000 | ||
|
|
d49b7eac09 | ||
|
|
8f057e687c | ||
|
|
5b4f680114 | ||
|
|
fabdfbadf4 | ||
|
|
afa47ea633 | ||
|
|
461d76e651 | ||
|
|
c40e20c8e9 |
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
@@ -8,6 +8,7 @@ on:
|
||||
- scripts/**
|
||||
- web/**
|
||||
- platformio.ini
|
||||
- .github/workflows/**
|
||||
branches:
|
||||
- '*'
|
||||
tags:
|
||||
@@ -22,6 +23,12 @@ jobs:
|
||||
steps:
|
||||
- name: Check out code from repo
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Inject secrets into ini file
|
||||
run: |
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_KEY/AMS2MQTT_PRICE_KEY="${{secrets.AMS2MQTT_PRICE_KEY}}"/g' platformio.ini
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_AUTHENTICATION/AMS2MQTT_PRICE_AUTHENTICATION="${{secrets.AMS2MQTT_PRICE_AUTHENTICATION}}"/g' platformio.ini
|
||||
|
||||
- name: Cache Python dependencies
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
@@ -40,6 +47,18 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio css_html_js_minify
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '16.x'
|
||||
- name: Build with node
|
||||
run: |
|
||||
cd lib/SvelteUi/app
|
||||
npm ci
|
||||
npm run build
|
||||
cd -
|
||||
env:
|
||||
CI: true
|
||||
- name: PlatformIO lib install
|
||||
run: pio lib install
|
||||
- name: PlatformIO run
|
||||
|
||||
41
.github/workflows/release.yml
vendored
41
.github/workflows/release.yml
vendored
@@ -23,6 +23,12 @@ jobs:
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
run: echo "GITHUB_TAG=$(echo ${GITHUB_REF##*/})" >> $GITHUB_ENV
|
||||
|
||||
- name: Inject secrets into ini file
|
||||
run: |
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_KEY/AMS2MQTT_PRICE_KEY="${{secrets.AMS2MQTT_PRICE_KEY}}"/g' platformio.ini
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_AUTHENTICATION/AMS2MQTT_PRICE_AUTHENTICATION="${{secrets.AMS2MQTT_PRICE_AUTHENTICATION}}"/g' platformio.ini
|
||||
|
||||
- name: Cache Python dependencies
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
@@ -41,12 +47,39 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio css_html_js_minify
|
||||
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '16.x'
|
||||
- name: Build with node
|
||||
run: |
|
||||
cd lib/SvelteUi/app
|
||||
npm ci
|
||||
npm run build
|
||||
cd -
|
||||
env:
|
||||
CI: false
|
||||
|
||||
- name: PlatformIO lib install
|
||||
run: pio lib install
|
||||
- name: PlatformIO run
|
||||
run: pio run
|
||||
- name: Create zip files
|
||||
run: /bin/sh scripts/mkzip.sh
|
||||
- name: Build ESP8266 firmware
|
||||
run: pio run -e esp8266
|
||||
- name: Create ESP8266 zip file
|
||||
run: /bin/sh scripts/esp8266/mkzip.sh
|
||||
- name: Build ESP32 firmware
|
||||
run: pio run -e esp32
|
||||
- name: Create ESP32 zip file
|
||||
run: /bin/sh scripts/esp32/mkzip.sh
|
||||
- name: Build ESP32-S2 firmware
|
||||
run: pio run -e esp32s2
|
||||
- name: Create ESP32-S2 zip file
|
||||
run: /bin/sh scripts/esp32s2/mkzip.sh
|
||||
- name: Build ESP32-SOLO firmware
|
||||
run: pio run -e esp32solo
|
||||
- name: Create ESP32-SOLO zip file
|
||||
run: /bin/sh scripts/esp32solo/mkzip.sh
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1.0.0
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
#include "Arduino.h"
|
||||
|
||||
#define EEPROM_SIZE 1024*3
|
||||
#define EEPROM_CHECK_SUM 100 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CHECK_SUM 101 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CLEARED_INDICATOR 0xFC
|
||||
#define EEPROM_CONFIG_ADDRESS 0
|
||||
#define EEPROM_TEMP_CONFIG_ADDRESS 2048
|
||||
|
||||
#define CONFIG_SYSTEM_START 8
|
||||
#define CONFIG_METER_START 32
|
||||
#define CONFIG_UI_START 248
|
||||
#define CONFIG_GPIO_START 266
|
||||
#define CONFIG_ENTSOE_START 290
|
||||
#define CONFIG_WIFI_START 360
|
||||
@@ -95,6 +96,23 @@ struct WebConfig {
|
||||
}; // 129
|
||||
|
||||
struct MeterConfig {
|
||||
uint32_t baud;
|
||||
uint8_t parity;
|
||||
bool invert;
|
||||
uint8_t distributionSystem;
|
||||
uint16_t mainFuse;
|
||||
uint16_t productionCapacity;
|
||||
uint8_t encryptionKey[16];
|
||||
uint8_t authenticationKey[16];
|
||||
uint32_t wattageMultiplier;
|
||||
uint32_t voltageMultiplier;
|
||||
uint32_t amperageMultiplier;
|
||||
uint32_t accumulatedMultiplier;
|
||||
uint8_t source;
|
||||
uint8_t parser;
|
||||
}; // 52
|
||||
|
||||
struct MeterConfig100 {
|
||||
uint32_t baud;
|
||||
uint8_t parity;
|
||||
bool invert;
|
||||
@@ -199,6 +217,20 @@ struct EnergyAccountingConfig {
|
||||
uint8_t hours;
|
||||
}; // 11
|
||||
|
||||
struct UiConfig {
|
||||
uint8_t showImport;
|
||||
uint8_t showExport;
|
||||
uint8_t showVoltage;
|
||||
uint8_t showAmperage;
|
||||
uint8_t showReactive;
|
||||
uint8_t showRealtime;
|
||||
uint8_t showPeaks;
|
||||
uint8_t showPricePlot;
|
||||
uint8_t showDayPlot;
|
||||
uint8_t showMonthPlot;
|
||||
uint8_t showTemperaturePlot;
|
||||
}; // 11
|
||||
|
||||
struct TempSensorConfig {
|
||||
uint8_t address[8];
|
||||
char name[16];
|
||||
@@ -275,6 +307,10 @@ public:
|
||||
bool isEnergyAccountingChanged();
|
||||
void ackEnergyAccountingChange();
|
||||
|
||||
bool getUiConfig(UiConfig&);
|
||||
bool setUiConfig(UiConfig&);
|
||||
void clearUiConfig(UiConfig&);
|
||||
|
||||
void loadTempSensors();
|
||||
void saveTempSensors();
|
||||
uint8_t getTempSensorCount();
|
||||
@@ -302,6 +338,7 @@ private:
|
||||
bool relocateConfig94(); // 2.1.0
|
||||
bool relocateConfig95(); // 2.1.4
|
||||
bool relocateConfig96(); // 2.1.14
|
||||
bool relocateConfig100(); // 2.2-dev
|
||||
|
||||
void saveToFs();
|
||||
bool loadFromFs(uint8_t version);
|
||||
|
||||
@@ -230,6 +230,8 @@ void AmsConfiguration::clearMeter(MeterConfig& config) {
|
||||
config.voltageMultiplier = 0;
|
||||
config.amperageMultiplier = 0;
|
||||
config.accumulatedMultiplier = 0;
|
||||
config.source = 1; // Serial
|
||||
config.parser = 0; // Auto
|
||||
}
|
||||
|
||||
bool AmsConfiguration::isMeterChanged() {
|
||||
@@ -547,7 +549,6 @@ bool AmsConfiguration::setEnergyAccountingConfig(EnergyAccountingConfig& config)
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearEnergyAccountingConfig(EnergyAccountingConfig& config) {
|
||||
@@ -572,6 +573,42 @@ void AmsConfiguration::ackEnergyAccountingChange() {
|
||||
energyAccountingChanged = false;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::getUiConfig(UiConfig& config) {
|
||||
if(hasConfig()) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_UI_START, config);
|
||||
if(config.showImport > 2) clearUiConfig(config); // Must be wrong
|
||||
EEPROM.end();
|
||||
return true;
|
||||
} else {
|
||||
clearUiConfig(config);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool AmsConfiguration::setUiConfig(UiConfig& config) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.put(CONFIG_UI_START, config);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearUiConfig(UiConfig& config) {
|
||||
// 1 = Always, 2 = If value present, 0 = Hidden
|
||||
config.showImport = 1;
|
||||
config.showExport = 2;
|
||||
config.showVoltage = 2;
|
||||
config.showAmperage = 2;
|
||||
config.showReactive = 0;
|
||||
config.showRealtime = 1;
|
||||
config.showPeaks = 2;
|
||||
config.showPricePlot = 2;
|
||||
config.showDayPlot = 1;
|
||||
config.showMonthPlot = 1;
|
||||
config.showTemperaturePlot = 2;
|
||||
}
|
||||
|
||||
|
||||
void AmsConfiguration::clear() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
@@ -619,6 +656,10 @@ void AmsConfiguration::clear() {
|
||||
clearDebug(debug);
|
||||
EEPROM.put(CONFIG_DEBUG_START, debug);
|
||||
|
||||
UiConfig ui;
|
||||
clearUiConfig(ui);
|
||||
EEPROM.put(CONFIG_UI_START, ui);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CLEARED_INDICATOR);
|
||||
EEPROM.commit();
|
||||
EEPROM.end();
|
||||
@@ -694,6 +735,14 @@ bool AmsConfiguration::hasConfig() {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 100:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig100()) {
|
||||
configVersion = 101;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case EEPROM_CHECK_SUM:
|
||||
return true;
|
||||
default:
|
||||
@@ -848,9 +897,13 @@ bool AmsConfiguration::relocateConfig96() {
|
||||
SystemConfig sys;
|
||||
EEPROM.get(CONFIG_SYSTEM_START, sys);
|
||||
|
||||
#if defined(ESP8266)
|
||||
MeterConfig meter;
|
||||
EEPROM.get(CONFIG_METER_START, meter);
|
||||
meter.source = 1; // Serial
|
||||
meter.parser = 0; // Auto
|
||||
EEPROM.put(CONFIG_METER_START, meter);
|
||||
|
||||
#if defined(ESP8266)
|
||||
GpioConfig gpio;
|
||||
EEPROM.get(CONFIG_GPIO_START, gpio);
|
||||
|
||||
@@ -912,6 +965,39 @@ bool AmsConfiguration::relocateConfig96() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig100() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
|
||||
MeterConfig100 meter100;
|
||||
EEPROM.get(CONFIG_METER_START, meter100);
|
||||
MeterConfig meter;
|
||||
meter.baud = meter100.baud;
|
||||
meter.parity = meter100.parity;
|
||||
meter.invert = meter100.invert;
|
||||
meter.distributionSystem = meter100.distributionSystem;
|
||||
meter.mainFuse = meter100.mainFuse;
|
||||
meter.productionCapacity = meter100.productionCapacity;
|
||||
memcpy(meter.encryptionKey, meter100.encryptionKey, 16);
|
||||
memcpy(meter.authenticationKey, meter100.authenticationKey, 16);
|
||||
meter.wattageMultiplier = meter100.wattageMultiplier;
|
||||
meter.voltageMultiplier = meter100.voltageMultiplier;
|
||||
meter.amperageMultiplier = meter100.amperageMultiplier;
|
||||
meter.accumulatedMultiplier = meter100.accumulatedMultiplier;
|
||||
meter.source = meter100.source;
|
||||
meter.parser = meter100.parser;
|
||||
|
||||
EEPROM.put(CONFIG_METER_START, meter);
|
||||
|
||||
UiConfig ui;
|
||||
clearUiConfig(ui);
|
||||
EEPROM.put(CONFIG_UI_START, ui);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 101);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::save() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CHECK_SUM);
|
||||
|
||||
@@ -10,9 +10,8 @@ enum AmsType {
|
||||
AmsTypeKaifa = 0x02,
|
||||
AmsTypeKamstrup = 0x03,
|
||||
AmsTypeIskra = 0x08,
|
||||
AmsTypeLandis = 0x09,
|
||||
AmsTypeLandisGyr = 0x09,
|
||||
AmsTypeSagemcom = 0x0A,
|
||||
AmsTypeLng = 0x0B,
|
||||
AmsTypeCustom = 0x88,
|
||||
AmsTypeUnknown = 0xFF
|
||||
};
|
||||
|
||||
@@ -64,7 +64,6 @@ void AmsData::apply(AmsData& other) {
|
||||
this->meterType = other.getMeterType();
|
||||
this->meterModel = other.getMeterModel();
|
||||
this->reactiveImportPower = other.getReactiveImportPower();
|
||||
this->activeExportPower = other.getActiveExportPower();
|
||||
this->reactiveExportPower = other.getReactiveExportPower();
|
||||
this->l1current = other.getL1Current();
|
||||
this->l2current = other.getL2Current();
|
||||
@@ -74,9 +73,13 @@ void AmsData::apply(AmsData& other) {
|
||||
this->l3voltage = other.getL3Voltage();
|
||||
this->threePhase = other.isThreePhase();
|
||||
this->twoPhase = other.isTwoPhase();
|
||||
case 1:
|
||||
this->activeImportPower = other.getActiveImportPower();
|
||||
}
|
||||
|
||||
// Moved outside switch to handle meters alternating between sending active and accumulated values
|
||||
if(other.getListType() == 1 || (other.getActiveImportPower() > 0 || other.getActiveExportPower() > 0))
|
||||
this->activeImportPower = other.getActiveImportPower();
|
||||
if(other.getListType() == 2 || (other.getActiveImportPower() > 0 || other.getActiveExportPower() > 0))
|
||||
this->activeExportPower = other.getActiveExportPower();
|
||||
}
|
||||
|
||||
unsigned long AmsData::getLastUpdateMillis() {
|
||||
|
||||
@@ -12,7 +12,8 @@ struct DayDataPoints {
|
||||
uint32_t activeImport;
|
||||
uint32_t activeExport;
|
||||
uint16_t hExport[24];
|
||||
}; // 112 bytes
|
||||
uint8_t accuracy;
|
||||
}; // 113 bytes
|
||||
|
||||
struct MonthDataPoints {
|
||||
uint8_t version;
|
||||
@@ -21,17 +22,18 @@ struct MonthDataPoints {
|
||||
uint32_t activeImport;
|
||||
uint32_t activeExport;
|
||||
uint16_t dExport[31];
|
||||
}; // 141 bytes
|
||||
uint8_t accuracy;
|
||||
}; // 142 bytes
|
||||
|
||||
class AmsDataStorage {
|
||||
public:
|
||||
AmsDataStorage(RemoteDebug*);
|
||||
void setTimezone(Timezone*);
|
||||
bool update(AmsData*);
|
||||
int32_t getHourImport(uint8_t);
|
||||
int32_t getHourExport(uint8_t);
|
||||
int32_t getDayImport(uint8_t);
|
||||
int32_t getDayExport(uint8_t);
|
||||
uint32_t getHourImport(uint8_t);
|
||||
uint32_t getHourExport(uint8_t);
|
||||
uint32_t getDayImport(uint8_t);
|
||||
uint32_t getDayExport(uint8_t);
|
||||
bool load();
|
||||
bool save();
|
||||
|
||||
@@ -40,6 +42,11 @@ public:
|
||||
MonthDataPoints getMonthData();
|
||||
bool setMonthData(MonthDataPoints&);
|
||||
|
||||
uint8_t getDayAccuracy();
|
||||
void setDayAccuracy(uint8_t);
|
||||
uint8_t getMonthAccuracy();
|
||||
void setMonthAccuracy(uint8_t);
|
||||
|
||||
bool isHappy();
|
||||
bool isDayHappy();
|
||||
bool isMonthHappy();
|
||||
@@ -50,19 +57,21 @@ private:
|
||||
0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
10
|
||||
};
|
||||
MonthDataPoints month = {
|
||||
0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
10
|
||||
};
|
||||
RemoteDebug* debugger;
|
||||
void setHourImport(uint8_t, int32_t);
|
||||
void setHourExport(uint8_t, int32_t);
|
||||
void setDayImport(uint8_t, int32_t);
|
||||
void setDayExport(uint8_t, int32_t);
|
||||
void setHourImport(uint8_t, uint32_t);
|
||||
void setHourExport(uint8_t, uint32_t);
|
||||
void setDayImport(uint8_t, uint32_t);
|
||||
void setDayExport(uint8_t, uint32_t);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
#include "version.h"
|
||||
|
||||
AmsDataStorage::AmsDataStorage(RemoteDebug* debugger) {
|
||||
day.version = 4;
|
||||
month.version = 5;
|
||||
day.version = 5;
|
||||
day.accuracy = 1;
|
||||
month.version = 6;
|
||||
month.accuracy = 1;
|
||||
this->debugger = debugger;
|
||||
}
|
||||
|
||||
@@ -237,44 +239,160 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AmsDataStorage::setHourImport(uint8_t hour, int32_t val) {
|
||||
void AmsDataStorage::setHourImport(uint8_t hour, uint32_t val) {
|
||||
if(hour < 0 || hour > 24) return;
|
||||
day.hImport[hour] = val / 10;
|
||||
|
||||
uint8_t accuracy = day.accuracy;
|
||||
uint32_t update = val / pow(10, accuracy);
|
||||
while(update > UINT16_MAX) {
|
||||
accuracy++;
|
||||
update = val / pow(10, accuracy);
|
||||
}
|
||||
|
||||
if(accuracy != day.accuracy) {
|
||||
setDayAccuracy(accuracy);
|
||||
}
|
||||
|
||||
day.hImport[hour] = update;
|
||||
|
||||
uint32_t max = 0;
|
||||
for(uint8_t i = 0; i < 24; i++) {
|
||||
if(day.hImport[i] > max)
|
||||
max = day.hImport[i];
|
||||
if(day.hExport[i] > max)
|
||||
max = day.hExport[i];
|
||||
}
|
||||
|
||||
while(max < UINT16_MAX/10 && accuracy > 0) {
|
||||
accuracy--;
|
||||
max = max*10;
|
||||
}
|
||||
|
||||
if(accuracy != day.accuracy) {
|
||||
setDayAccuracy(accuracy);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t AmsDataStorage::getHourImport(uint8_t hour) {
|
||||
uint32_t AmsDataStorage::getHourImport(uint8_t hour) {
|
||||
if(hour < 0 || hour > 24) return 0;
|
||||
return day.hImport[hour] * 10;
|
||||
return day.hImport[hour] * pow(10, day.accuracy);
|
||||
}
|
||||
|
||||
void AmsDataStorage::setHourExport(uint8_t hour, int32_t val) {
|
||||
void AmsDataStorage::setHourExport(uint8_t hour, uint32_t val) {
|
||||
if(hour < 0 || hour > 24) return;
|
||||
day.hExport[hour] = val / 10;
|
||||
|
||||
uint8_t accuracy = day.accuracy;
|
||||
uint32_t update = val / pow(10, accuracy);
|
||||
while(update > UINT16_MAX) {
|
||||
accuracy++;
|
||||
update = val / pow(10, accuracy);
|
||||
}
|
||||
|
||||
if(accuracy != day.accuracy) {
|
||||
setDayAccuracy(accuracy);
|
||||
}
|
||||
|
||||
day.hExport[hour] = update;
|
||||
|
||||
uint32_t max = 0;
|
||||
for(uint8_t i = 0; i < 24; i++) {
|
||||
if(day.hImport[i] > max)
|
||||
max = day.hImport[i];
|
||||
if(day.hExport[i] > max)
|
||||
max = day.hExport[i];
|
||||
}
|
||||
|
||||
while(max < UINT16_MAX/10 && accuracy > 0) {
|
||||
accuracy--;
|
||||
max = max*10;
|
||||
}
|
||||
|
||||
if(accuracy != day.accuracy) {
|
||||
setDayAccuracy(accuracy);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t AmsDataStorage::getHourExport(uint8_t hour) {
|
||||
uint32_t AmsDataStorage::getHourExport(uint8_t hour) {
|
||||
if(hour < 0 || hour > 24) return 0;
|
||||
return day.hExport[hour] * 10;
|
||||
return day.hExport[hour] * pow(10, day.accuracy);
|
||||
}
|
||||
|
||||
void AmsDataStorage::setDayImport(uint8_t day, int32_t val) {
|
||||
void AmsDataStorage::setDayImport(uint8_t day, uint32_t val) {
|
||||
if(day < 1 || day > 31) return;
|
||||
month.dImport[day-1] = val / 10;
|
||||
|
||||
uint8_t accuracy = month.accuracy;
|
||||
uint32_t update = val / pow(10, accuracy);
|
||||
while(update > UINT16_MAX) {
|
||||
accuracy++;
|
||||
update = val / pow(10, accuracy);
|
||||
}
|
||||
|
||||
if(accuracy != month.accuracy) {
|
||||
setMonthAccuracy(accuracy);
|
||||
}
|
||||
|
||||
month.dImport[day-1] = update;
|
||||
|
||||
uint32_t max = 0;
|
||||
for(uint8_t i = 0; i < 31; i++) {
|
||||
if(month.dImport[i] > max)
|
||||
max = month.dImport[i];
|
||||
if(month.dExport[i] > max)
|
||||
max = month.dExport[i];
|
||||
}
|
||||
|
||||
while(max < UINT16_MAX/10 && accuracy > 0) {
|
||||
accuracy--;
|
||||
max = max*10;
|
||||
}
|
||||
|
||||
if(accuracy != month.accuracy) {
|
||||
setMonthAccuracy(accuracy);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t AmsDataStorage::getDayImport(uint8_t day) {
|
||||
uint32_t AmsDataStorage::getDayImport(uint8_t day) {
|
||||
if(day < 1 || day > 31) return 0;
|
||||
return (month.dImport[day-1] * 10);
|
||||
return (month.dImport[day-1] * pow(10, month.accuracy));
|
||||
}
|
||||
|
||||
void AmsDataStorage::setDayExport(uint8_t day, int32_t val) {
|
||||
void AmsDataStorage::setDayExport(uint8_t day, uint32_t val) {
|
||||
if(day < 1 || day > 31) return;
|
||||
month.dExport[day-1] = val / 10;
|
||||
|
||||
uint8_t accuracy = month.accuracy;
|
||||
uint32_t update = val / pow(10, accuracy);
|
||||
while(update > UINT16_MAX) {
|
||||
accuracy++;
|
||||
update = val / pow(10, accuracy);
|
||||
}
|
||||
|
||||
if(accuracy != month.accuracy) {
|
||||
setMonthAccuracy(accuracy);
|
||||
}
|
||||
|
||||
month.dExport[day-1] = update;
|
||||
|
||||
uint32_t max = 0;
|
||||
for(uint8_t i = 0; i < 31; i++) {
|
||||
if(month.dImport[i] > max)
|
||||
max = month.dImport[i];
|
||||
if(month.dExport[i] > max)
|
||||
max = month.dExport[i];
|
||||
}
|
||||
|
||||
while(max < UINT16_MAX/10 && accuracy > 0) {
|
||||
accuracy--;
|
||||
max = max*10;
|
||||
}
|
||||
|
||||
if(accuracy != month.accuracy) {
|
||||
setMonthAccuracy(accuracy);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t AmsDataStorage::getDayExport(uint8_t day) {
|
||||
uint32_t AmsDataStorage::getDayExport(uint8_t day) {
|
||||
if(day < 1 || day > 31) return 0;
|
||||
return (month.dExport[day-1] * 10);
|
||||
return (month.dExport[day-1] * pow(10, month.accuracy));
|
||||
}
|
||||
|
||||
bool AmsDataStorage::load() {
|
||||
@@ -348,31 +466,74 @@ MonthDataPoints AmsDataStorage::getMonthData() {
|
||||
}
|
||||
|
||||
bool AmsDataStorage::setDayData(DayDataPoints& day) {
|
||||
if(day.version == 4) {
|
||||
if(day.version == 5) {
|
||||
this->day = day;
|
||||
return true;
|
||||
} else if(day.version == 4) {
|
||||
this->day = day;
|
||||
this->day.accuracy = 1;
|
||||
this->day.version = 5;
|
||||
return true;
|
||||
} else if(day.version == 3) {
|
||||
this->day = day;
|
||||
for(uint8_t i = 0; i < 24; i++) this->day.hExport[i] = 0;
|
||||
this->day.version = 4;
|
||||
this->day.accuracy = 1;
|
||||
this->day.version = 5;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AmsDataStorage::setMonthData(MonthDataPoints& month) {
|
||||
if(month.version == 5) {
|
||||
if(month.version == 6) {
|
||||
this->month = month;
|
||||
return true;
|
||||
} else if(month.version == 5) {
|
||||
this->month = month;
|
||||
this->month.accuracy = 1;
|
||||
this->month.version = 6;
|
||||
return true;
|
||||
} else if(month.version == 4) {
|
||||
this->month = month;
|
||||
for(uint8_t i = 0; i < 31; i++) this->month.dExport[i] = 0;
|
||||
this->month.version = 5;
|
||||
this->month.accuracy = 1;
|
||||
this->month.version = 6;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t AmsDataStorage::getDayAccuracy() {
|
||||
return day.accuracy;
|
||||
}
|
||||
|
||||
void AmsDataStorage::setDayAccuracy(uint8_t accuracy) {
|
||||
if(day.accuracy != accuracy) {
|
||||
uint16_t multiplier = pow(10, day.accuracy)/pow(10, accuracy);
|
||||
for(uint8_t i = 0; i < 24; i++) {
|
||||
day.hImport[i] = day.hImport[i] * multiplier;
|
||||
day.hExport[i] = day.hExport[i] * multiplier;
|
||||
}
|
||||
day.accuracy = accuracy;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t AmsDataStorage::getMonthAccuracy() {
|
||||
return month.accuracy;
|
||||
}
|
||||
|
||||
void AmsDataStorage::setMonthAccuracy(uint8_t accuracy) {
|
||||
if(month.accuracy != accuracy) {
|
||||
uint16_t multiplier = pow(10, month.accuracy)/pow(10, accuracy);
|
||||
for(uint8_t i = 0; i < 31; i++) {
|
||||
month.dImport[i] = month.dImport[i] * multiplier;
|
||||
month.dExport[i] = month.dExport[i] * multiplier;
|
||||
}
|
||||
month.accuracy = accuracy;
|
||||
}
|
||||
month.accuracy = accuracy;
|
||||
}
|
||||
|
||||
bool AmsDataStorage::isHappy() {
|
||||
return isDayHappy() && isMonthHappy();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "Arduino.h"
|
||||
#include <stdint.h>
|
||||
|
||||
uint16_t crc16(const uint8_t* p, int len);
|
||||
uint16_t crc16_x25(const uint8_t* p, int len);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#include "DsmrParser.h"
|
||||
#include "crc.h"
|
||||
#include "hexutils.h"
|
||||
#include "lwip/def.h"
|
||||
|
||||
int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified) {
|
||||
uint16_t crcPos = 0;
|
||||
@@ -14,8 +17,13 @@ int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified) {
|
||||
if(!reachedEnd) return DATA_PARSE_INCOMPLETE;
|
||||
buf[ctx.length+1] = '\0';
|
||||
if(crcPos > 0) {
|
||||
// TODO: CRC
|
||||
Serial.printf("CRC: %s\n", buf+crcPos);
|
||||
uint16_t crc_calc = crc16(buf, crcPos);
|
||||
uint16_t crc = 0x0000;
|
||||
fromHex((uint8_t*) &crc, String((char*) buf+crcPos), 2);
|
||||
crc = ntohs(crc);
|
||||
|
||||
if(crc != crc_calc)
|
||||
return DATA_PARSE_FOOTER_CHECKSUM_ERROR;
|
||||
}
|
||||
return DATA_PARSE_OK;
|
||||
}
|
||||
@@ -10,3 +10,20 @@ uint16_t crc16_x25(const uint8_t* p, int len)
|
||||
|
||||
return (~crc << 8) | (~crc >> 8 & 0xff);
|
||||
}
|
||||
|
||||
uint16_t crc16 (const uint8_t *p, int len) {
|
||||
uint16_t crc = 0;
|
||||
|
||||
while (len--) {
|
||||
int i;
|
||||
crc ^= *p++;
|
||||
for (i = 0 ; i < 8 ; ++i) {
|
||||
if (crc & 1)
|
||||
crc = (crc >> 1) ^ 0xa001;
|
||||
else
|
||||
crc = (crc >> 1);
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
@@ -854,7 +854,7 @@ var fetch = function() {
|
||||
$('.jmt').html("Iskra");
|
||||
break;
|
||||
case 9:
|
||||
$('.jmt').html("Landis");
|
||||
$('.jmt').html("Landis+Gyr");
|
||||
break;
|
||||
case 10:
|
||||
$('.jmt').html("Sagemcom");
|
||||
|
||||
@@ -329,8 +329,8 @@ void AmsWebServer::configMeterHtml() {
|
||||
case AmsTypeIskra:
|
||||
manufacturer = F("Iskra");
|
||||
break;
|
||||
case AmsTypeLandis:
|
||||
manufacturer = F("Landis + Gyro");
|
||||
case AmsTypeLandisGyr:
|
||||
manufacturer = F("Landis+Gyr");
|
||||
break;
|
||||
case AmsTypeSagemcom:
|
||||
manufacturer = F("Sagemcom");
|
||||
|
||||
@@ -37,8 +37,7 @@ private:
|
||||
HTTPClient http;
|
||||
|
||||
uint8_t currentDay = 0, currentHour = 0;
|
||||
uint32_t tomorrowFetchMillis = 36000000; // Number of ms before midnight. Default fetch 10hrs before midnight (14:00 CE(S)T)
|
||||
uint64_t midnightMillis = 0;
|
||||
uint8_t tomorrowFetchMinute = 15; // How many minutes over 13:00 should it fetch prices
|
||||
uint64_t lastTodayFetch = 0;
|
||||
uint64_t lastTomorrowFetch = 0;
|
||||
uint64_t lastCurrencyFetch = 0;
|
||||
@@ -60,7 +59,7 @@ private:
|
||||
|
||||
PricesContainer* fetchPrices(time_t);
|
||||
bool retrieve(const char* url, Stream* doc);
|
||||
float getCurrencyMultiplier(const char* from, const char* to);
|
||||
float getCurrencyMultiplier(const char* from, const char* to, time_t t);
|
||||
|
||||
void printD(String fmt, ...);
|
||||
void printE(String fmt, ...);
|
||||
|
||||
@@ -21,7 +21,7 @@ EntsoeApi::EntsoeApi(RemoteDebug* Debug) {
|
||||
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
|
||||
tz = new Timezone(CEST, CET);
|
||||
|
||||
tomorrowFetchMillis = 36000000 + (random(1800) * 1000); // Random between 13:30 and 14:00
|
||||
tomorrowFetchMinute = 15 + random(45); // Random between 13:15 and 14:00
|
||||
}
|
||||
|
||||
void EntsoeApi::setup(EntsoeConfig& config) {
|
||||
@@ -96,7 +96,7 @@ float EntsoeApi::getValueForHour(time_t cur, int8_t hour) {
|
||||
} else {
|
||||
return ENTSOE_NO_VALUE;
|
||||
}
|
||||
float mult = getCurrencyMultiplier(tomorrow->currency, config->currency);
|
||||
float mult = getCurrencyMultiplier(tomorrow->currency, config->currency, cur);
|
||||
if(mult == 0) return ENTSOE_NO_VALUE;
|
||||
multiplier *= mult;
|
||||
} else if(pos >= 0) {
|
||||
@@ -112,7 +112,7 @@ float EntsoeApi::getValueForHour(time_t cur, int8_t hour) {
|
||||
} else {
|
||||
return ENTSOE_NO_VALUE;
|
||||
}
|
||||
float mult = getCurrencyMultiplier(today->currency, config->currency);
|
||||
float mult = getCurrencyMultiplier(today->currency, config->currency, cur);
|
||||
if(mult == 0) return ENTSOE_NO_VALUE;
|
||||
multiplier *= mult;
|
||||
}
|
||||
@@ -138,21 +138,15 @@ bool EntsoeApi::loop() {
|
||||
if(strlen(config->currency) == 0)
|
||||
return false;
|
||||
|
||||
bool ret = false;
|
||||
tmElements_t tm;
|
||||
breakTime(tz->toLocal(t), tm);
|
||||
if(currentHour != tm.Hour) {
|
||||
currentHour = tm.Hour;
|
||||
ret = today != NULL; // Only trigger MQTT publish if we have todays prices.
|
||||
}
|
||||
|
||||
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 %llu\n", midnightMillis);
|
||||
if(currentDay == 0) {
|
||||
currentDay = tm.Day;
|
||||
return false;
|
||||
} else if(now > midnightMillis && currentDay != tm.Day) {
|
||||
currentHour = tm.Hour;
|
||||
}
|
||||
|
||||
if(currentDay != tm.Day) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Rotating price objects at %lu\n", t);
|
||||
if(today != NULL) delete today;
|
||||
if(tomorrow != NULL) {
|
||||
@@ -160,38 +154,38 @@ bool EntsoeApi::loop() {
|
||||
tomorrow = NULL;
|
||||
}
|
||||
currentDay = tm.Day;
|
||||
midnightMillis = 0; // Force new midnight millis calculation
|
||||
return true;
|
||||
} else {
|
||||
if(today == NULL && (lastTodayFetch == 0 || now - lastTodayFetch > 60000)) {
|
||||
try {
|
||||
lastTodayFetch = now;
|
||||
today = fetchPrices(t);
|
||||
} catch(const std::exception& e) {
|
||||
if(lastError == 0) lastError = 900;
|
||||
today = NULL;
|
||||
}
|
||||
return today != NULL;
|
||||
}
|
||||
|
||||
// Prices for next day are published at 13:00 CE(S)T, but to avoid heavy server traffic at that time, we will
|
||||
// fetch 1 hr after that (with some random delay) and retry every 15 minutes
|
||||
if(tomorrow == NULL
|
||||
&& midnightMillis - now < tomorrowFetchMillis
|
||||
&& (lastTomorrowFetch == 0 || now - lastTomorrowFetch > 900000)
|
||||
) {
|
||||
try {
|
||||
breakTime(t+SECS_PER_DAY, tm); // Break UTC tomorrow to find UTC midnight
|
||||
lastTomorrowFetch = now;
|
||||
tomorrow = fetchPrices(t+SECS_PER_DAY);
|
||||
} catch(const std::exception& e) {
|
||||
if(lastError == 0) lastError = 900;
|
||||
tomorrow = NULL;
|
||||
}
|
||||
return tomorrow != NULL;
|
||||
}
|
||||
currentHour = tm.Hour;
|
||||
return today != NULL; // Only trigger MQTT publish if we have todays prices.
|
||||
} else if(currentHour != tm.Hour) {
|
||||
currentHour = tm.Hour;
|
||||
return today != NULL; // Only trigger MQTT publish if we have todays prices.
|
||||
}
|
||||
return ret;
|
||||
|
||||
if(today == NULL && (lastTodayFetch == 0 || now - lastTodayFetch > 60000)) {
|
||||
try {
|
||||
lastTodayFetch = now;
|
||||
today = fetchPrices(t);
|
||||
} catch(const std::exception& e) {
|
||||
if(lastError == 0) lastError = 900;
|
||||
today = NULL;
|
||||
}
|
||||
return today != NULL; // Only trigger MQTT publish if we have todays prices.
|
||||
}
|
||||
|
||||
// Prices for next day are published at 13:00 CE(S)T, but to avoid heavy server traffic at that time, we will
|
||||
// fetch with one hour (with some random delay) and retry every 15 minutes
|
||||
if(tomorrow == NULL && (tm.Hour > 13 || (tm.Hour == 13 && tm.Minute >= tomorrowFetchMinute)) && (lastTomorrowFetch == 0 || now - lastTomorrowFetch > 900000)) {
|
||||
try {
|
||||
lastTomorrowFetch = now;
|
||||
tomorrow = fetchPrices(t+SECS_PER_DAY);
|
||||
} catch(const std::exception& e) {
|
||||
if(lastError == 0) lastError = 900;
|
||||
tomorrow = NULL;
|
||||
}
|
||||
return tomorrow != NULL;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EntsoeApi::retrieve(const char* url, Stream* doc) {
|
||||
@@ -235,7 +229,7 @@ bool EntsoeApi::retrieve(const char* url, Stream* doc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float EntsoeApi::getCurrencyMultiplier(const char* from, const char* to) {
|
||||
float EntsoeApi::getCurrencyMultiplier(const char* from, const char* to, time_t t) {
|
||||
if(strcmp(from, to) == 0)
|
||||
return 1.00;
|
||||
|
||||
@@ -272,7 +266,9 @@ float EntsoeApi::getCurrencyMultiplier(const char* from, const char* to) {
|
||||
return 0;
|
||||
}
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) Resulting currency multiplier: %.4f\n", currencyMultiplier);
|
||||
lastCurrencyFetch = midnightMillis;
|
||||
tmElements_t tm;
|
||||
breakTime(t, tm);
|
||||
lastCurrencyFetch = now + (SECS_PER_DAY * 1000) - (((((tm.Hour * 60) + tm.Minute) * 60) + tm.Second) * 1000);
|
||||
}
|
||||
return currencyMultiplier;
|
||||
}
|
||||
|
||||
@@ -22,12 +22,12 @@ HomeAssistantSensor HA_SENSORS[HA_SENSOR_COUNT] PROGMEM = {
|
||||
{"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\""},
|
||||
{"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\""},
|
||||
{"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\""},
|
||||
@@ -36,12 +36,12 @@ HomeAssistantSensor HA_SENSORS[HA_SENSOR_COUNT] PROGMEM = {
|
||||
{"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\""},
|
||||
{"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", ""},
|
||||
|
||||
@@ -136,7 +136,7 @@ bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwT
|
||||
bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
return false;
|
||||
if(strlen(eapi->getToken()) == 0)
|
||||
if(eapi->getValueForHour(0) == ENTSOE_NO_VALUE)
|
||||
return false;
|
||||
|
||||
time_t now = time(nullptr);
|
||||
@@ -280,12 +280,19 @@ bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, Energ
|
||||
String uom = String(sensor.uom);
|
||||
if(strncmp(sensor.devcl, "monetary", 8) == 0) {
|
||||
if(eapi == NULL) continue;
|
||||
uom = String(eapi->getCurrency());
|
||||
if(strncmp(sensor.path, "prices", 5) == 0) {
|
||||
uom = String(eapi->getCurrency()) + "/kWh";
|
||||
} else {
|
||||
uom = String(eapi->getCurrency());
|
||||
}
|
||||
}
|
||||
if(strncmp(sensor.path, "peaks[", 6) == 0) {
|
||||
if(peaks >= peakCount) continue;
|
||||
peaks++;
|
||||
}
|
||||
if(strncmp(sensor.path, "temp", 4) == 0) {
|
||||
if(hw->getTemperature() < 0) continue;
|
||||
}
|
||||
snprintf_P(json, BufferSize, HADISCOVER_JSON,
|
||||
sensor.name,
|
||||
topic.c_str(), sensor.topic,
|
||||
|
||||
@@ -175,7 +175,7 @@ bool JsonMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw)
|
||||
bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
return false;
|
||||
if(strlen(eapi->getToken()) == 0)
|
||||
if(eapi->getValueForHour(0) == ENTSOE_NO_VALUE)
|
||||
return false;
|
||||
|
||||
time_t now = time(nullptr);
|
||||
|
||||
@@ -92,6 +92,7 @@ bool RawMqttHandler::publish(AmsData* data, AmsData* meterState, EnergyAccountin
|
||||
}
|
||||
mqtt->publish(topic + "/realtime/import/hour", String(ea->getUseThisHour(), 3));
|
||||
mqtt->publish(topic + "/realtime/import/day", String(ea->getUseToday(), 2));
|
||||
mqtt->publish(topic + "/realtime/import/month", String(ea->getUseThisMonth(), 1));
|
||||
uint8_t peakCount = ea->getConfig()->hours;
|
||||
if(peakCount > 5) peakCount = 5;
|
||||
for(uint8_t i = 1; i <= peakCount; i++) {
|
||||
@@ -101,6 +102,7 @@ bool RawMqttHandler::publish(AmsData* data, AmsData* meterState, EnergyAccountin
|
||||
mqtt->publish(topic + "/realtime/import/monthmax", String(ea->getMonthMax(), 3), true, 0);
|
||||
mqtt->publish(topic + "/realtime/export/hour", String(ea->getProducedThisHour(), 3));
|
||||
mqtt->publish(topic + "/realtime/export/day", String(ea->getProducedToday(), 2));
|
||||
mqtt->publish(topic + "/realtime/export/month", String(ea->getProducedThisMonth(), 1));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -121,7 +123,7 @@ bool RawMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw)
|
||||
bool RawMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
return false;
|
||||
if(strcmp(eapi->getToken(), "") == 0)
|
||||
if(eapi->getValueForHour(0) == ENTSOE_NO_VALUE)
|
||||
return false;
|
||||
|
||||
time_t now = time(nullptr);
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" href="/favicon.svg">
|
||||
<link rel="mask-icon" href="/favicon.svg" color="#000000">
|
||||
<title>AMS reader</title>
|
||||
</head>
|
||||
<body class="bg-gray-100">
|
||||
|
||||
19
lib/SvelteUi/app/manifest.json
Normal file
19
lib/SvelteUi/app/manifest.json
Normal file
@@ -0,0 +1,19 @@
|
||||
// HTTPS required for this to work
|
||||
// Remember: <link rel="manifest" href="manifest.json" />
|
||||
{
|
||||
"short_name": "amsreader",
|
||||
"name": "AMS reader",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicon.svg",
|
||||
"sizes": "any",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
}
|
||||
],
|
||||
"start_url": "/",
|
||||
"background_color": "#f3f4f6",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"theme_color": "#7c3aed"
|
||||
}
|
||||
11
lib/SvelteUi/app/service-worker.js
Normal file
11
lib/SvelteUi/app/service-worker.js
Normal file
@@ -0,0 +1,11 @@
|
||||
self.addEventListener('install', (event) => {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event) => {
|
||||
return self.clients.claim();
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', function(event) {
|
||||
event.respondWith(fetch(event.request));
|
||||
});
|
||||
@@ -10,7 +10,7 @@
|
||||
import Mask from './lib/Mask.svelte';
|
||||
import FileUploadComponent from "./lib/FileUploadComponent.svelte";
|
||||
import ConsentComponent from "./lib/ConsentComponent.svelte";
|
||||
|
||||
|
||||
let sysinfo = {};
|
||||
sysinfoStore.subscribe(update => {
|
||||
sysinfo = update;
|
||||
@@ -33,7 +33,7 @@
|
||||
<Router>
|
||||
<Header data={data}/>
|
||||
<Route path="/">
|
||||
<Dashboard data={data}/>
|
||||
<Dashboard data={data} sysinfo={sysinfo}/>
|
||||
</Route>
|
||||
<Route path="/configuration">
|
||||
<ConfigurationPanel sysinfo={sysinfo}/>
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
}
|
||||
.pl-ov {
|
||||
position: absolute;
|
||||
top: 28%;
|
||||
top: 27%;
|
||||
left: 25%;
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
@@ -76,6 +76,7 @@
|
||||
color: grey;
|
||||
}
|
||||
.pl-sub {
|
||||
padding-top: 10px;
|
||||
font-size: 1.0rem;
|
||||
}
|
||||
.pl-snt {
|
||||
|
||||
19
lib/SvelteUi/app/src/assets/favicon.svg
Normal file
19
lib/SvelteUi/app/src/assets/favicon.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
|
||||
<title>Amsleser</title>
|
||||
<g transform="translate(-29.5,-83)">
|
||||
<circle r="4.8016944" cy="123.56455" cx="55.064552"
|
||||
style="fill:none;stroke:#045c7c;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path d="m 41.298717,103.9049 a 24,24 0 0 1 27.531669,0"
|
||||
style="fill:none;stroke:#045c7c;stroke-width:3.3;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path d="m 35.562952,95.713384 a 34,34 0 0 1 39.003199,-2e-6"
|
||||
style="fill:none;stroke:#045c7c;stroke-width:3.3;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path d="m 47.034482,112.09642 a 14,14 0 0 1 16.06014,0"
|
||||
style="fill:none;stroke:#045c7c;stroke-width:3.3;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<circle r="3" cy="105.99158" cx="38.181862"
|
||||
style="fill:none;stroke:#045c7c;stroke-width:2.4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<circle r="3" cy="97.959579" cx="77.491386"
|
||||
style="fill:none;stroke:#045c7c;stroke-width:2.4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -18,7 +18,7 @@
|
||||
if(u1 > 0) {
|
||||
xTicks.push({ label: 'L1' });
|
||||
points.push({
|
||||
label: i1 ? i1 + 'A' : '-',
|
||||
label: i1 ? (i1 > 10 ? i1.toFixed(0) : i1.toFixed(1)) + 'A' : '-',
|
||||
value: i1 ? i1 : 0,
|
||||
color: ampcol(i1 ? (i1)/(max)*100 : 0)
|
||||
});
|
||||
@@ -26,7 +26,7 @@
|
||||
if(u2 > 0) {
|
||||
xTicks.push({ label: 'L2' });
|
||||
points.push({
|
||||
label: i2 ? i2 + 'A' : '-',
|
||||
label: i2 ? (i2 > 10 ? i2.toFixed(0) : i2.toFixed(1)) + 'A' : '-',
|
||||
value: i2 ? i2 : 0,
|
||||
color: ampcol(i2 ? (i2)/(max)*100 : 0)
|
||||
});
|
||||
@@ -34,7 +34,7 @@
|
||||
if(u3 > 0) {
|
||||
xTicks.push({ label: 'L3' });
|
||||
points.push({
|
||||
label: i3 ? i3 + 'A' : '-',
|
||||
label: i3 ? (i3 > 10 ? i3.toFixed(0) : i3.toFixed(1)) + 'A' : '-',
|
||||
value: i3 ? i3 : 0,
|
||||
color: ampcol(i3 ? (i3)/(max)*100 : 0)
|
||||
});
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
let xScale;
|
||||
let yScale;
|
||||
let heightAvailable;
|
||||
let labelOffset;
|
||||
|
||||
$: {
|
||||
heightAvailable = height-(config.title ? 20 : 0);
|
||||
let innerWidth = width - (config.padding.left + config.padding.right);
|
||||
barWidth = innerWidth / config.points.length;
|
||||
labelOffset = barWidth < 25 ? 28 : 17;
|
||||
|
||||
let yPerUnit = (heightAvailable-config.padding.top-config.padding.bottom)/(config.y.max-config.y.min);
|
||||
|
||||
@@ -19,8 +21,13 @@
|
||||
return (i*barWidth)+config.padding.left;
|
||||
};
|
||||
yScale = function(i) {
|
||||
if(i > config.y.max) return heightAvailable;
|
||||
let ret = heightAvailable-config.padding.bottom-((i-config.y.min)*yPerUnit);
|
||||
let ret = 0;
|
||||
if(i > config.y.max)
|
||||
ret = config.padding.bottom;
|
||||
else if(i < config.y.min)
|
||||
ret = heightAvailable-config.padding.bottom;
|
||||
else
|
||||
ret = heightAvailable-config.padding.bottom-((i-config.y.min)*yPerUnit);
|
||||
return ret > heightAvailable || ret < 0.0 ? 0.0 : ret;
|
||||
};
|
||||
};
|
||||
@@ -53,42 +60,45 @@
|
||||
<g class='bars'>
|
||||
{#each config.points as point, i}
|
||||
{#if point.value !== undefined}
|
||||
<rect
|
||||
x="{xScale(i) + 2}"
|
||||
y="{yScale(point.value)}"
|
||||
width="{barWidth - 4}"
|
||||
height="{yScale(config.y.min) - yScale(Math.min(config.y.min, 0) + point.value)}"
|
||||
fill="{point.color}"
|
||||
/>
|
||||
<rect
|
||||
x="{xScale(i) + 2}"
|
||||
y="{yScale(point.value)}"
|
||||
width="{barWidth - 4}"
|
||||
height="{yScale(config.y.min) - yScale(Math.min(config.y.min, 0) + point.value)}"
|
||||
fill="{point.color}"
|
||||
/>
|
||||
|
||||
<text
|
||||
y="{yScale(point.value) > yScale(0)-15 ? yScale(point.value) - 12 : yScale(point.value)+10}"
|
||||
x="{xScale(i) + barWidth/2}"
|
||||
width="{barWidth - 4}"
|
||||
dominant-baseline="middle"
|
||||
text-anchor="{barWidth < 25 ? 'left' : 'middle'}"
|
||||
fill="{yScale(point.value) > yScale(0)-15 ? point.color : 'white'}"
|
||||
transform="rotate({barWidth < 25 ? 90 : 0}, {xScale(i) + (barWidth/2)}, {yScale(point.value) > yScale(0)-12 ? yScale(point.value) - 12 : yScale(point.value)+9})"
|
||||
>{point.label}</text>
|
||||
{#if barWidth > 15}
|
||||
<text
|
||||
y="{yScale(point.value) > yScale(0)-labelOffset ? yScale(point.value) - labelOffset : yScale(point.value)+10}"
|
||||
x="{xScale(i) + barWidth/2}"
|
||||
width="{barWidth - 4}"
|
||||
dominant-baseline="middle"
|
||||
text-anchor="{barWidth < 25 ? 'left' : 'middle'}"
|
||||
fill="{yScale(point.value) > yScale(0)-labelOffset ? point.color : 'white'}"
|
||||
transform="rotate({barWidth < 25 ? 90 : 0}, {xScale(i) + (barWidth/2)}, {yScale(point.value) > yScale(0)-labelOffset ? yScale(point.value) - labelOffset : yScale(point.value)+9})"
|
||||
>{point.label}</text>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if point.value2 > 0.0001}
|
||||
<rect
|
||||
x="{xScale(i) + 2}"
|
||||
y="{yScale(0)}"
|
||||
width="{barWidth - 4}"
|
||||
height="{yScale(config.y.min) - yScale(config.y.min + point.value2)}"
|
||||
fill="{point.color}"
|
||||
/>
|
||||
|
||||
<text
|
||||
y="{yScale(-point.value2) < yScale(0)+12 ? yScale(-point.value2) + 12 : yScale(-point.value2)-10}"
|
||||
x="{xScale(i) + barWidth/2}"
|
||||
width="{barWidth - 4}"
|
||||
dominant-baseline="middle"
|
||||
text-anchor="{barWidth < 25 ? 'left' : 'middle'}"
|
||||
fill="{yScale(-point.value2) < yScale(0)+12 ? point.color : 'white'}"
|
||||
transform="rotate({barWidth < 25 ? 90 : 0}, {xScale(i) + (barWidth/2)}, {yScale(point.value2 - config.y.min) > yScale(0)-12 ? yScale(point.value2 - config.y.min) - 12 : yScale(point.value2 - config.y.min)+9})"
|
||||
>{point.label2}</text>
|
||||
<rect
|
||||
x="{xScale(i) + 2}"
|
||||
y="{yScale(0)}"
|
||||
width="{barWidth - 4}"
|
||||
height="{yScale(config.y.min) - yScale(config.y.min + point.value2)}"
|
||||
fill="{point.color}"
|
||||
/>
|
||||
{#if barWidth > 15}
|
||||
<text
|
||||
y="{yScale(-point.value2) < yScale(0)+12 ? yScale(-point.value2) + 12 : yScale(-point.value2)-10}"
|
||||
x="{xScale(i) + barWidth/2}"
|
||||
width="{barWidth - 4}"
|
||||
dominant-baseline="middle"
|
||||
text-anchor="{barWidth < 25 ? 'left' : 'middle'}"
|
||||
fill="{yScale(-point.value2) < yScale(0)+12 ? point.color : 'white'}"
|
||||
transform="rotate({barWidth < 25 ? 90 : 0}, {xScale(i) + (barWidth/2)}, {yScale(point.value2 - config.y.min) > yScale(0)-12 ? yScale(point.value2 - config.y.min) - 12 : yScale(point.value2 - config.y.min)+9})"
|
||||
>{point.label2}</text>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
</g>
|
||||
|
||||
@@ -31,6 +31,13 @@
|
||||
h: '', p: 1883, u: '', a: '', b: '',
|
||||
s: { e: false, c: false, r: true, k: false }
|
||||
},
|
||||
o: {
|
||||
e: '',
|
||||
c: '',
|
||||
u1: '',
|
||||
u2: '',
|
||||
u3: ''
|
||||
},
|
||||
t: {
|
||||
t: [0,0,0,0,0,0,0,0,0,0], h: 1
|
||||
},
|
||||
@@ -40,6 +47,9 @@
|
||||
d: {
|
||||
s: false, t: false, l: 5
|
||||
},
|
||||
u: {
|
||||
i: 0, e: 0, v: 0, a: 0, r: 0, c: 0, t: 0, p: 0, d: 0, m: 0, s: 0
|
||||
},
|
||||
i: {
|
||||
h: null, a: null,
|
||||
l: { p: null, i: false },
|
||||
@@ -87,6 +97,7 @@
|
||||
|
||||
sysinfoStore.update(s => {
|
||||
s.booting = res.reboot;
|
||||
s.ui = configuration.u;
|
||||
return s;
|
||||
});
|
||||
|
||||
@@ -120,7 +131,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<form on:submit|preventDefault={handleSubmit}>
|
||||
<form on:submit|preventDefault={handleSubmit} autocomplete="off">
|
||||
<div class="grid xl:grid-cols-4 lg:grid-cols-2 md:grid-cols-2">
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">General</strong>
|
||||
@@ -257,14 +268,14 @@
|
||||
<div class="mx-1">
|
||||
Main fuse<br/>
|
||||
<label class="flex">
|
||||
<input name="mf" bind:value={configuration.m.f} type="number" min="5" max="255" class="in-f tr w-full"/>
|
||||
<input name="mf" bind:value={configuration.m.f} type="number" min="5" max="65535" class="in-f tr w-full"/>
|
||||
<span class="in-post">A</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="mx-1">
|
||||
Production<br/>
|
||||
<label class="flex">
|
||||
<input name="mr" bind:value={configuration.m.r} type="number" min="0" max="255" class="in-f tr w-full"/>
|
||||
<input name="mr" bind:value={configuration.m.r} type="number" min="0" max="65535" class="in-f tr w-full"/>
|
||||
<span class="in-post">kWp</span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -289,7 +300,7 @@
|
||||
{#if configuration.m.m.e}
|
||||
<div class="flex my-1">
|
||||
<div class="w-1/4">
|
||||
Instant<br/>
|
||||
Watt<br/>
|
||||
<input name="mmw" bind:value={configuration.m.m.w} type="number" min="0.00" max="655.35" step="0.01" class="in-f tr w-full"/>
|
||||
</div>
|
||||
<div class="w-1/4">
|
||||
@@ -301,7 +312,7 @@
|
||||
<input name="mma" bind:value={configuration.m.m.a} type="number" min="0.00" max="655.35" step="0.01" class="in-m tr w-full"/>
|
||||
</div>
|
||||
<div class="w-1/4">
|
||||
Acc.<br/>
|
||||
kWh<br/>
|
||||
<input name="mmc" bind:value={configuration.m.m.c} type="number" min="0.00" max="655.35" step="0.01" class="in-l tr w-full"/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -457,6 +468,31 @@
|
||||
<input name="qb" bind:value={configuration.q.b} type="text" class="in-s"/>
|
||||
</div>
|
||||
</div>
|
||||
{#if configuration.q.m == 3}
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Domoticz</strong>
|
||||
<a href="https://github.com/gskjold/AmsToMqttBridge/wiki/MQTT-configuration#domoticz" target="_blank" class="float-right"><HelpIcon/></a>
|
||||
<input type="hidden" name="o" value="true"/>
|
||||
<div class="my-1 flex">
|
||||
<div class="w-1/2">
|
||||
Electricity IDX<br/>
|
||||
<input name="oe" bind:value={configuration.o.e} type="text" class="in-f tr w-full"/>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Current IDX<br/>
|
||||
<input name="oc" bind:value={configuration.o.c} type="text" class="in-l tr w-full"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-1">
|
||||
Voltage IDX: L1, L2 & L3
|
||||
<div class="flex">
|
||||
<input name="ou1" bind:value={configuration.o.u1} type="text" class="in-f tr w-1/3"/>
|
||||
<input name="ou2" bind:value={configuration.o.u2} type="text" class="in-m tr w-1/3"/>
|
||||
<input name="ou2" bind:value={configuration.o.u3} type="text" class="in-l tr w-1/3"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if configuration.p.r.startsWith("10YNO") || configuration.p.r == '10Y1001A1001A48H'}
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Tariff thresholds</strong>
|
||||
@@ -516,6 +552,100 @@
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">User interface</strong>
|
||||
<input type="hidden" name="u" value="true"/>
|
||||
<div class="flex flex-wrap">
|
||||
<div class="w-1/2">
|
||||
Import gauge<br/>
|
||||
<select name="ui" bind:value={configuration.u.i} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Export gauge<br/>
|
||||
<select name="ue" bind:value={configuration.u.e} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Voltage<br/>
|
||||
<select name="uv" bind:value={configuration.u.v} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Amperage<br/>
|
||||
<select name="ua" bind:value={configuration.u.a} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Reactive<br/>
|
||||
<select name="ur" bind:value={configuration.u.r} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Realtime<br/>
|
||||
<select name="uc" bind:value={configuration.u.c} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Peaks<br/>
|
||||
<select name="ut" bind:value={configuration.u.t} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Price<br/>
|
||||
<select name="up" bind:value={configuration.u.p} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Day plot<br/>
|
||||
<select name="ud" bind:value={configuration.u.d} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Month plot<br/>
|
||||
<select name="um" bind:value={configuration.u.m} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Temperature plot<br/>
|
||||
<select name="us" bind:value={configuration.u.s} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if sysinfo.board > 20 || sysinfo.chip == 'esp8266'}
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Hardware</strong>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
<div class="grid xl:grid-cols-3 lg:grid-cols-2">
|
||||
<div class="cnt">
|
||||
<form on:submit|preventDefault={handleSubmit}>
|
||||
<form on:submit|preventDefault={handleSubmit} autocomplete="off">
|
||||
<div>
|
||||
Various permissions we need to do stuff:
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script>
|
||||
import { pricesStore, dayPlotStore, monthPlotStore, temperaturesStore } from './DataStores.js';
|
||||
import { metertype } from './Helpers.js';
|
||||
import { metertype, uiVisibility } from './Helpers.js';
|
||||
import PowerGauge from './PowerGauge.svelte';
|
||||
import VoltPlot from './VoltPlot.svelte';
|
||||
import AmpPlot from './AmpPlot.svelte';
|
||||
@@ -13,6 +13,7 @@
|
||||
import TariffPeakChart from './TariffPeakChart.svelte';
|
||||
|
||||
export let data = {}
|
||||
export let sysinfo = {}
|
||||
let prices = {}
|
||||
let dayPlot = {}
|
||||
let monthPlot = {}
|
||||
@@ -32,6 +33,7 @@
|
||||
</script>
|
||||
|
||||
<div class="grid xl:grid-cols-6 lg:grid-cols-4 md:grid-cols-3 sm:grid-cols-2">
|
||||
{#if uiVisibility(sysinfo.ui.i, data.i)}
|
||||
<div class="cnt">
|
||||
<div class="grid grid-cols-2">
|
||||
<div class="col-span-2">
|
||||
@@ -41,7 +43,8 @@
|
||||
<div class="text-right">{data.ic ? data.ic.toFixed(1) : '-'} kWh</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if data.om || data.e > 0}
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.e, data.om || data.e > 0)}
|
||||
<div class="cnt">
|
||||
<div class="grid grid-cols-2">
|
||||
<div class="col-span-2">
|
||||
@@ -52,39 +55,47 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if data.u1 > 100 || data.u2 > 100 || data.u3 > 100}
|
||||
{#if uiVisibility(sysinfo.ui.v, data.u1 > 100 || data.u2 > 100 || data.u3 > 100)}
|
||||
<div class="cnt">
|
||||
<VoltPlot u1={data.u1} u2={data.u2} u3={data.u3} ds={data.ds}/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if data.i1 > 0.01 || data.i2 > 0.01 || data.i3 > 0.01}
|
||||
{#if uiVisibility(sysinfo.ui.a, data.i1 > 0.01 || data.i2 > 0.01 || data.i3 > 0.01)}
|
||||
<div class="cnt">
|
||||
<AmpPlot u1={data.u1} u2={data.u2} u3={data.u3} i1={data.i1} i2={data.i2} i3={data.i3} max={data.mf ? data.mf : 32}/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.r, data.ri > 0 || data.re > 0 || data.ric > 0 || data.rec > 0)}
|
||||
<div class="cnt">
|
||||
<ReactiveData importInstant={data.ri} exportInstant={data.re} importTotal={data.ric} exportTotal={data.rec}/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.c, data.ea)}
|
||||
<div class="cnt">
|
||||
<AccountingData data={data.ea} currency={prices.currency}/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if data && data.pr && (data.pr.startsWith("10YNO") || data.pr == '10Y1001A1001A48H')}
|
||||
<div class="cnt h-64">
|
||||
<TariffPeakChart />
|
||||
</div>
|
||||
{/if}
|
||||
{#if (typeof data.p == "number") && !Number.isNaN(data.p)}
|
||||
{#if uiVisibility(sysinfo.ui.p, (typeof data.p == "number") && !Number.isNaN(data.p))}
|
||||
<div class="cnt xl:col-span-6 lg:col-span-4 md:col-span-3 sm:col-span-2 h-64">
|
||||
<PricePlot json={prices}/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.d, dayPlot)}
|
||||
<div class="cnt xl:col-span-6 lg:col-span-4 md:col-span-3 sm:col-span-2 h-64">
|
||||
<DayPlot json={dayPlot} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.m, monthPlot)}
|
||||
<div class="cnt xl:col-span-6 lg:col-span-4 md:col-span-3 sm:col-span-2 h-64">
|
||||
<MonthPlot json={monthPlot} />
|
||||
</div>
|
||||
{#if data.t && data.t != -127 && temperatures.c > 1}
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.s, data.t && data.t != -127 && temperatures.c > 1)}
|
||||
<div class="cnt xl:col-span-6 lg:col-span-4 md:col-span-3 sm:col-span-2 h-64">
|
||||
<TemperaturePlot json={temperatures} />
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { readable, writable } from 'svelte/store';
|
||||
import { isBusPowered } from './Helpers';
|
||||
|
||||
async function fetchWithTimeout(resource, options = {}) {
|
||||
const { timeout = 8000 } = options;
|
||||
@@ -16,11 +17,14 @@ async function fetchWithTimeout(resource, options = {}) {
|
||||
let sysinfo = {
|
||||
version: '',
|
||||
chip: '',
|
||||
mac: null,
|
||||
apmac: null,
|
||||
vndcfg: null,
|
||||
usrcfg: null,
|
||||
fwconsent: null,
|
||||
booting: false,
|
||||
upgrading: false,
|
||||
ui: {},
|
||||
security: 0
|
||||
};
|
||||
export const sysinfoStore = writable(sysinfo);
|
||||
@@ -43,18 +47,31 @@ export const dataStore = readable(data, (set) => {
|
||||
set(data);
|
||||
if(lastTemp != data.t) {
|
||||
lastTemp = data.t;
|
||||
getTemperatures();
|
||||
setTimeout(getTemperatures, 2000);
|
||||
}
|
||||
if(lastPrice != data.p) {
|
||||
lastPrice = data.p;
|
||||
getPrices();
|
||||
setTimeout(getPrices, 4000);
|
||||
}
|
||||
if(sysinfo.upgrading) {
|
||||
window.location.reload();
|
||||
} else if(sysinfo.booting) {
|
||||
} else if(!sysinfo || !sysinfo.chip || sysinfo.booting || (tries > 1 && !isBusPowered(sysinfo.board))) {
|
||||
getSysinfo();
|
||||
if(dayPlotTimeout) clearTimeout(dayPlotTimeout);
|
||||
dayPlotTimeout = setTimeout(getDayPlot, 2000);
|
||||
if(monthPlotTimeout) clearTimeout(monthPlotTimeout);
|
||||
monthPlotTimeout = setTimeout(getMonthPlot, 3000);
|
||||
}
|
||||
timeout = setTimeout(getData, 5000);
|
||||
let to = 5000;
|
||||
if(isBusPowered(sysinfo.board) && data.v > 2.5) {
|
||||
let diff = (3.3 - Math.min(3.3, data.v));
|
||||
if(diff > 0) {
|
||||
to = Math.max(diff, 0.1) * 10 * 5000;
|
||||
}
|
||||
}
|
||||
if(to > 5000) console.log("Scheduling next data fetch in " + to + "ms");
|
||||
if(timeout) clearTimeout(timeout);
|
||||
timeout = setTimeout(getData, to);
|
||||
tries = 0;
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -68,7 +85,7 @@ export const dataStore = readable(data, (set) => {
|
||||
});
|
||||
timeout = setTimeout(getData, 15000);
|
||||
} else {
|
||||
timeout = setTimeout(getData, 5000);
|
||||
timeout = setTimeout(getData, isBusPowered(sysinfo.board) ? 10000 : 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -80,37 +97,49 @@ export const dataStore = readable(data, (set) => {
|
||||
|
||||
let prices = {};
|
||||
export const pricesStore = writable(prices);
|
||||
export async function getPrices(){
|
||||
export async function getPrices() {
|
||||
const response = await fetchWithTimeout("/energyprice.json");
|
||||
prices = (await response.json())
|
||||
pricesStore.set(prices);
|
||||
}
|
||||
|
||||
let dayPlot = {};
|
||||
export const dayPlotStore = readable(dayPlot, (set) => {
|
||||
async function getDayPlot(){
|
||||
const response = await fetchWithTimeout("/dayplot.json");
|
||||
dayPlot = (await response.json())
|
||||
set(dayPlot);
|
||||
|
||||
let date = new Date();
|
||||
setTimeout(getDayPlot, (61-date.getMinutes())*60000)
|
||||
let dayPlotTimeout;
|
||||
export async function getDayPlot() {
|
||||
if(dayPlotTimeout) {
|
||||
clearTimeout(dayPlotTimeout);
|
||||
dayPlotTimeout = 0;
|
||||
}
|
||||
const response = await fetchWithTimeout("/dayplot.json");
|
||||
dayPlot = (await response.json())
|
||||
dayPlotStore.set(dayPlot);
|
||||
|
||||
let date = new Date();
|
||||
dayPlotTimeout = setTimeout(getDayPlot, ((60-date.getMinutes())*60000)+20)
|
||||
}
|
||||
|
||||
export const dayPlotStore = writable(dayPlot, (set) => {
|
||||
getDayPlot();
|
||||
return function stop() {}
|
||||
});
|
||||
|
||||
let monthPlot = {};
|
||||
export const monthPlotStore = readable(monthPlot, (set) => {
|
||||
async function getmonthPlot(){
|
||||
const response = await fetchWithTimeout("/monthplot.json");
|
||||
monthPlot = (await response.json())
|
||||
set(monthPlot);
|
||||
|
||||
let date = new Date();
|
||||
setTimeout(getmonthPlot, (24-date.getHours())*3600000)
|
||||
let monthPlotTimeout;
|
||||
export async function getMonthPlot() {
|
||||
if(monthPlotTimeout) {
|
||||
clearTimeout(monthPlotTimeout);
|
||||
monthPlotTimeout = 0;
|
||||
}
|
||||
getmonthPlot();
|
||||
const response = await fetchWithTimeout("/monthplot.json");
|
||||
monthPlot = (await response.json())
|
||||
monthPlotStore.set(monthPlot);
|
||||
|
||||
let date = new Date();
|
||||
monthPlotTimeout = setTimeout(getMonthPlot, ((24-date.getHours())*3600000)+40)
|
||||
}
|
||||
|
||||
export const monthPlotStore = writable(monthPlot, (set) => {
|
||||
getMonthPlot();
|
||||
return function stop() {}
|
||||
});
|
||||
|
||||
@@ -127,12 +156,17 @@ export const temperaturesStore = writable(temperatures, (set) => {
|
||||
});
|
||||
|
||||
let tariff = {};
|
||||
let tariffTimeout;
|
||||
export async function getTariff() {
|
||||
if(tariffTimeout) {
|
||||
clearTimeout(tariffTimeout);
|
||||
tariffTimeout = 0;
|
||||
}
|
||||
const response = await fetchWithTimeout("/tariff.json");
|
||||
tariff = (await response.json())
|
||||
tariffStore.set(tariff);
|
||||
let date = new Date();
|
||||
setTimeout(getTariff, (61-date.getMinutes())*60000)
|
||||
tariffTimeout = setTimeout(getTariff, ((60-date.getMinutes())*60000)+30)
|
||||
}
|
||||
|
||||
export const tariffStore = writable(tariff, (set) => {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="cnt">
|
||||
<strong>Upload {title}</strong>
|
||||
<p class="mb-4">Select a suitable file and click upload</p>
|
||||
<form action="{action}" enctype="multipart/form-data" method="post" on:submit={() => uploading=true}>
|
||||
<form action="{action}" enctype="multipart/form-data" method="post" on:submit={() => uploading=true} autocomplete="off">
|
||||
<input name="file" type="file">
|
||||
<div class="w-full text-right mt-4">
|
||||
<button type="submit" class="btn-pri">Upload</button>
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
import { Link } from "svelte-navigator";
|
||||
import { sysinfoStore, getGitHubReleases, gitHubReleaseStore } from './DataStores.js';
|
||||
import { upgrade, getNextVersion } from './UpgradeHelper';
|
||||
import { boardtype, hanError, mqttError, priceError } from './Helpers.js';
|
||||
import { boardtype, hanError, mqttError, priceError, isBusPowered } from './Helpers.js';
|
||||
import AmsleserSvg from "./../assets/favicon.svg";
|
||||
import GitHubLogo from './../assets/github.svg';
|
||||
import Uptime from "./Uptime.svelte";
|
||||
import Badge from './Badge.svelte';
|
||||
@@ -19,7 +20,7 @@
|
||||
|
||||
function askUpgrade() {
|
||||
if(confirm('Do you want to upgrade this device to ' + nextVersion.tag_name + '?')) {
|
||||
if((sysinfo.board != 2 && sysinfo.board != 4 && sysinfo.board != 7) || confirm('WARNING: ' + boardtype(sysinfo.chip, sysinfo.board) + ' must be connected to an external power supply during firmware upgrade. Failure to do so may cause power-down during upload resulting in non-functioning unit.')) {
|
||||
if(!isBusPowered(sysinfo.board) || confirm('WARNING: ' + boardtype(sysinfo.chip, sysinfo.board) + ' must be connected to an external power supply during firmware upgrade. Failure to do so may cause power-down during upload resulting in non-functioning unit.')) {
|
||||
sysinfoStore.update(s => {
|
||||
s.upgrading = true;
|
||||
return s;
|
||||
|
||||
@@ -31,7 +31,7 @@ export function metertype(mt) {
|
||||
case 8:
|
||||
return "Iskra";
|
||||
case 9:
|
||||
return "Landis";
|
||||
return "Landis+Gyr";
|
||||
case 10:
|
||||
return "Sagemcom";
|
||||
default:
|
||||
@@ -143,4 +143,18 @@ export function priceError(err) {
|
||||
|
||||
if(err < 0) return "Unspecified error "+err;
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
export function isBusPowered(boardType) {
|
||||
switch(boardType) {
|
||||
case 2:
|
||||
case 4:
|
||||
case 7:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function uiVisibility(choice, state) {
|
||||
return choice == 1 || (choice == 2 && state);
|
||||
}
|
||||
|
||||
@@ -76,8 +76,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
console.log(yTicks);
|
||||
|
||||
config = {
|
||||
title: "Future energy price (" + json.currency + ")",
|
||||
padding: { top: 20, right: 15, bottom: 20, left: 35 },
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
const data = new URLSearchParams();
|
||||
for (let field of formData) {
|
||||
const [key, value] = field;
|
||||
data.append(key, value)
|
||||
if(key == 'sh') hostname = value;
|
||||
}
|
||||
|
||||
@@ -69,7 +70,7 @@
|
||||
|
||||
<div class="grid xl:grid-cols-4 lg:grid-cols-3 md:grid-cols-2">
|
||||
<div class="cnt">
|
||||
<form on:submit|preventDefault={handleSubmit}>
|
||||
<form on:submit|preventDefault={handleSubmit} autocomplete="off">
|
||||
<input type="hidden" name="s" value="true"/>
|
||||
<strong class="text-sm">Setup</strong>
|
||||
<div class="my-3">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import { metertype, boardtype } from './Helpers.js';
|
||||
import { metertype, boardtype, isBusPowered } from './Helpers.js';
|
||||
import { getSysinfo, gitHubReleaseStore, sysinfoStore } from './DataStores.js';
|
||||
import { upgrade, getNextVersion } from './UpgradeHelper';
|
||||
import DownloadIcon from './DownloadIcon.svelte';
|
||||
@@ -69,6 +69,11 @@
|
||||
<div class="my-2">
|
||||
MAC: {sysinfo.mac}
|
||||
</div>
|
||||
{#if sysinfo.apmac && sysinfo.apmac != sysinfo.mac}
|
||||
<div class="my-2">
|
||||
AP MAC: {sysinfo.apmac}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="my-2">
|
||||
<Link to="/consent">
|
||||
<span class="text-xs py-1 px-2 rounded bg-blue-500 text-white mr-3 ">Update consents</span>
|
||||
@@ -103,7 +108,7 @@
|
||||
Gateway: {sysinfo.net.gw}
|
||||
</div>
|
||||
<div class="my-2">
|
||||
DNS: {sysinfo.net.dns1} {#if sysinfo.net.dns2 != '0.0.0.0'}/ {sysinfo.net.dns2}{/if}
|
||||
DNS: {sysinfo.net.dns1} {#if sysinfo.net.dns2 && sysinfo.net.dns2 != '0.0.0.0'}/ {sysinfo.net.dns2}{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -128,14 +133,14 @@
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if (sysinfo.security == 0 || data.a) && (sysinfo.board == 2 || sysinfo.board == 4 || sysinfo.board == 7) }
|
||||
{#if (sysinfo.security == 0 || data.a) && isBusPowered(sysinfo.board) }
|
||||
<div class="bd-red">
|
||||
{boardtype(sysinfo.chip, sysinfo.board)} must be connected to an external power supply during firmware upgrade. Failure to do so may cause power-down during upload resulting in non-functioning unit.
|
||||
</div>
|
||||
{/if}
|
||||
{#if sysinfo.security == 0 || data.a}
|
||||
<div class="my-2 flex">
|
||||
<form action="/firmware" enctype="multipart/form-data" method="post" on:submit={() => firmwareUploading=true}>
|
||||
<form action="/firmware" enctype="multipart/form-data" method="post" on:submit={() => firmwareUploading=true} autocomplete="off">
|
||||
<input style="display:none" name="file" type="file" accept=".bin" bind:this={firmwareFileInput} bind:files={firmwareFiles}>
|
||||
{#if firmwareFiles.length == 0}
|
||||
<button type="button" on:click={()=>{firmwareFileInput.click();}} class="text-xs py-1 px-2 rounded bg-blue-500 text-white float-right mr-3">Select firmware file for upgrade</button>
|
||||
@@ -150,7 +155,7 @@
|
||||
{#if sysinfo.security == 0 || data.a}
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Configuration</strong>
|
||||
<form method="get" action="/configfile.cfg">
|
||||
<form method="get" action="/configfile.cfg" autocomplete="off">
|
||||
<div class="grid grid-cols-2">
|
||||
<label class="my-1 mx-3"><input type="checkbox" class="rounded" name="iw" value="true" checked/> WiFi</label>
|
||||
<label class="my-1 mx-3"><input type="checkbox" class="rounded" name="im" value="true" checked/> MQTT</label>
|
||||
@@ -167,7 +172,7 @@
|
||||
<button type="submit" class="ml-2 text-xs py-1 px-2 rounded bg-blue-500 text-white float-right mr-3">Download</button>
|
||||
{/if}
|
||||
</form>
|
||||
<form action="/configfile" enctype="multipart/form-data" method="post" on:submit={() => configUploading=true}>
|
||||
<form action="/configfile" enctype="multipart/form-data" method="post" on:submit={() => configUploading=true} autocomplete="off">
|
||||
<input style="display:none" name="file" type="file" accept=".cfg" bind:this={configFileInput} bind:files={configFiles}>
|
||||
{#if configFiles.length == 0}
|
||||
<button type="button" on:click={()=>{configFileInput.click();}} class="text-xs py-1 px-2 rounded bg-blue-500 text-white mr-3">Select file...</button>
|
||||
|
||||
@@ -4,24 +4,26 @@
|
||||
let hours = 0;
|
||||
let minutes = 0;
|
||||
$: {
|
||||
days = Math.round(epoch/86400);
|
||||
hours = Math.round(epoch/3600);
|
||||
minutes = Math.round(epoch/60);
|
||||
days = Math.floor(epoch/86400);
|
||||
hours = Math.floor(epoch/3600);
|
||||
minutes = Math.floor(epoch/60);
|
||||
}
|
||||
</script>
|
||||
Up
|
||||
{#if days > 1}
|
||||
{days} days
|
||||
{:else if days > 0}
|
||||
{days} day
|
||||
{:else if hours > 1}
|
||||
{hours} hours
|
||||
{:else if hours > 0}
|
||||
{hours} hour
|
||||
{:else if minutes > 1}
|
||||
{minutes} minutes
|
||||
{:else if minutes > 0}
|
||||
{minutes} minute
|
||||
{:else}
|
||||
{epoch} seconds
|
||||
{#if epoch}
|
||||
Up
|
||||
{#if days > 1}
|
||||
{days} days
|
||||
{:else if days > 0}
|
||||
{days} day
|
||||
{:else if hours > 1}
|
||||
{hours} hours
|
||||
{:else if hours > 0}
|
||||
{hours} hour
|
||||
{:else if minutes > 1}
|
||||
{minutes} minutes
|
||||
{:else if minutes > 0}
|
||||
{minutes} minute
|
||||
{:else}
|
||||
{epoch} seconds
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
<div class="grid xl:grid-cols-4 lg:grid-cols-3 md:grid-cols-2">
|
||||
<div class="cnt">
|
||||
<form on:submit|preventDefault={handleSubmit}>
|
||||
<form on:submit|preventDefault={handleSubmit} autocomplete="off">
|
||||
<input type="hidden" name="v" value="true"/>
|
||||
<strong class="text-sm">Initial configuration</strong>
|
||||
<div class="my-3">
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
if(u1 > 0) {
|
||||
xTicks.push({ label: ds === 1 ? 'L1-L2' : 'L1' });
|
||||
points.push({
|
||||
label: u1 ? u1 + 'V' : '-',
|
||||
label: u1 ? u1.toFixed(0) + 'V' : '-',
|
||||
value: u1 ? u1 : 0,
|
||||
color: voltcol(u1 ? (u1-min)/(max-min)*100 : 0)
|
||||
});
|
||||
@@ -25,7 +25,7 @@
|
||||
if(u2 > 0) {
|
||||
xTicks.push({ label: ds === 1 ? 'L1-L3' : 'L2' });
|
||||
points.push({
|
||||
label: u2 ? u2 + 'V' : '-',
|
||||
label: u2 ? u2.toFixed(0) + 'V' : '-',
|
||||
value: u2 ? u2 : 0,
|
||||
color: voltcol(u2 ? (u2-min)/(max-min)*100 : 0)
|
||||
});
|
||||
@@ -33,7 +33,7 @@
|
||||
if(u3 > 0) {
|
||||
xTicks.push({ label: ds === 1 ? 'L2-L3' : 'L3' });
|
||||
points.push({
|
||||
label: u3 ? u3 + 'V' : '-',
|
||||
label: u3 ? u3.toFixed(0) + 'V' : '-',
|
||||
value: u3 ? u3 : 0,
|
||||
color: voltcol(u3 ? (u3-min)/(max-min)*100 : 0)
|
||||
});
|
||||
|
||||
@@ -61,6 +61,11 @@ private:
|
||||
bool performUpgrade = false;
|
||||
bool rebootForUpgrade = false;
|
||||
String priceRegion = "";
|
||||
#if defined(AMS2MQTT_FIRMWARE_URL)
|
||||
String customFirmwareUrl = AMS2MQTT_FIRMWARE_URL;
|
||||
#else
|
||||
String customFirmwareUrl;
|
||||
#endif
|
||||
|
||||
static const uint16_t BufferSize = 2048;
|
||||
char* buf;
|
||||
@@ -77,7 +82,7 @@ private:
|
||||
void indexJs();
|
||||
void indexCss();
|
||||
void githubSvg();
|
||||
void faviconIco();
|
||||
void faviconSvg();
|
||||
|
||||
void sysinfoJson();
|
||||
void dataJson();
|
||||
|
||||
7
lib/SvelteUi/json/conf_domoticz.json
Normal file
7
lib/SvelteUi/json/conf_domoticz.json
Normal file
@@ -0,0 +1,7 @@
|
||||
"o": {
|
||||
"e" : %d,
|
||||
"c" : %d,
|
||||
"u1" : %d,
|
||||
"u2" : %d,
|
||||
"u3" : %d
|
||||
}
|
||||
@@ -25,4 +25,4 @@
|
||||
},
|
||||
"b": %.1f
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
13
lib/SvelteUi/json/conf_ui.json
Normal file
13
lib/SvelteUi/json/conf_ui.json
Normal file
@@ -0,0 +1,13 @@
|
||||
"u": {
|
||||
"i": %d,
|
||||
"e": %d,
|
||||
"v": %d,
|
||||
"a": %d,
|
||||
"r": %d,
|
||||
"c": %d,
|
||||
"t": %d,
|
||||
"p": %d,
|
||||
"d": %d,
|
||||
"m": %d,
|
||||
"s": %d
|
||||
},
|
||||
@@ -1,6 +1,11 @@
|
||||
<html>
|
||||
<form action="/firmware" enctype="multipart/form-data" method="post">
|
||||
<input name="file" type="file" accept=".bin">
|
||||
<form action="/firmware" enctype="multipart/form-data" method="post" autocomplete="off">
|
||||
File: <input name="file" type="file" accept=".bin">
|
||||
<button type="submit" class="">Upload</button>
|
||||
</form>
|
||||
or<br/><br/>
|
||||
<form action="/firmware" method="post" autocomplete="off">
|
||||
URL: <input name="url" type="text"/>
|
||||
<button type="submit" class="">Install</button>
|
||||
</form>
|
||||
</html>
|
||||
@@ -3,6 +3,7 @@
|
||||
"chip": "%s",
|
||||
"chipId": "%s",
|
||||
"mac": "%s",
|
||||
"apmac": "%s",
|
||||
"board": %d,
|
||||
"vndcfg": %s,
|
||||
"usrcfg": %s,
|
||||
@@ -22,5 +23,18 @@
|
||||
"model": "%s",
|
||||
"id": "%s"
|
||||
},
|
||||
"ui": {
|
||||
"i": %d,
|
||||
"e": %d,
|
||||
"v": %d,
|
||||
"a": %d,
|
||||
"r": %d,
|
||||
"c": %d,
|
||||
"t": %d,
|
||||
"p": %d,
|
||||
"d": %d,
|
||||
"m": %d,
|
||||
"s": %d
|
||||
},
|
||||
"security": %d
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "html/index_css.h"
|
||||
#include "html/index_js.h"
|
||||
#include "html/github_svg.h"
|
||||
#include "html/favicon_svg.h"
|
||||
#include "html/data_json.h"
|
||||
#include "html/dayplot_json.h"
|
||||
#include "html/monthplot_json.h"
|
||||
@@ -25,12 +26,15 @@
|
||||
#include "html/conf_thresholds_json.h"
|
||||
#include "html/conf_debug_json.h"
|
||||
#include "html/conf_gpio_json.h"
|
||||
#include "html/conf_domoticz_json.h"
|
||||
#include "html/conf_ui_json.h"
|
||||
#include "html/firmware_html.h"
|
||||
|
||||
#include "version.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
#include <esp_wifi.h>
|
||||
#endif
|
||||
|
||||
|
||||
@@ -64,7 +68,7 @@ void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, Meter
|
||||
server.on(F("/mqtt-key"), HTTP_GET, std::bind(&AmsWebServer::indexHtml, this));
|
||||
|
||||
server.on(F("/github.svg"), HTTP_GET, std::bind(&AmsWebServer::githubSvg, this));
|
||||
server.on(F("/favicon.ico"), HTTP_GET, std::bind(&AmsWebServer::faviconIco, this));
|
||||
server.on(F("/favicon.svg"), HTTP_GET, std::bind(&AmsWebServer::faviconSvg, this));
|
||||
server.on(F("/sysinfo.json"), HTTP_GET, std::bind(&AmsWebServer::sysinfoJson, this));
|
||||
server.on(F("/data.json"), HTTP_GET, std::bind(&AmsWebServer::dataJson, this));
|
||||
server.on(F("/dayplot.json"), HTTP_GET, std::bind(&AmsWebServer::dayplotJson, this));
|
||||
@@ -183,10 +187,11 @@ void AmsWebServer::githubSvg() {
|
||||
server.send_P(200, "image/svg+xml", GITHUB_SVG);
|
||||
}
|
||||
|
||||
void AmsWebServer::faviconIco() {
|
||||
void AmsWebServer::faviconSvg() {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("Serving /favicon.ico over http...\n");
|
||||
|
||||
notFound(); //TODO
|
||||
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_1HR);
|
||||
server.send_P(200, "image/svg+xml", FAVICON_SVG);
|
||||
}
|
||||
|
||||
void AmsWebServer::sysinfoJson() {
|
||||
@@ -215,6 +220,26 @@ void AmsWebServer::sysinfoJson() {
|
||||
IPAddress dns1 = WiFi.dnsIP(0);
|
||||
IPAddress dns2 = WiFi.dnsIP(1);
|
||||
|
||||
char macStr[18] = { 0 };
|
||||
char apMacStr[18] = { 0 };
|
||||
|
||||
uint8_t mac[6];
|
||||
uint8_t apmac[6];
|
||||
|
||||
#if defined(ESP8266)
|
||||
wifi_get_macaddr(STATION_IF, mac);
|
||||
wifi_get_macaddr(SOFTAP_IF, apmac);
|
||||
#elif defined(ESP32)
|
||||
esp_wifi_get_mac((wifi_interface_t)ESP_IF_WIFI_STA, mac);
|
||||
esp_wifi_get_mac((wifi_interface_t)ESP_IF_WIFI_AP, apmac);
|
||||
#endif
|
||||
|
||||
sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
sprintf(apMacStr, "%02X:%02X:%02X:%02X:%02X:%02X", apmac[0], apmac[1], apmac[2], apmac[3], apmac[4], apmac[5]);
|
||||
|
||||
UiConfig ui;
|
||||
config->getUiConfig(ui);
|
||||
|
||||
snprintf_P(buf, BufferSize, SYSINFO_JSON,
|
||||
VERSION,
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
@@ -227,7 +252,8 @@ void AmsWebServer::sysinfoJson() {
|
||||
"esp8266",
|
||||
#endif
|
||||
chipIdStr.c_str(),
|
||||
WiFi.macAddress().c_str(),
|
||||
macStr,
|
||||
apMacStr,
|
||||
sys.boardType,
|
||||
sys.vendorConfigured ? "true" : "false",
|
||||
sys.userConfigured ? "true" : "false",
|
||||
@@ -248,6 +274,17 @@ void AmsWebServer::sysinfoJson() {
|
||||
meterState->getMeterType(),
|
||||
meterState->getMeterModel().c_str(),
|
||||
meterState->getMeterId().c_str(),
|
||||
ui.showImport,
|
||||
ui.showExport,
|
||||
ui.showVoltage,
|
||||
ui.showAmperage,
|
||||
ui.showReactive,
|
||||
ui.showRealtime,
|
||||
ui.showPeaks,
|
||||
ui.showPricePlot,
|
||||
ui.showDayPlot,
|
||||
ui.showMonthPlot,
|
||||
ui.showTemperaturePlot,
|
||||
webConfig.security
|
||||
);
|
||||
|
||||
@@ -727,6 +764,10 @@ void AmsWebServer::configurationJson() {
|
||||
config->getEntsoeConfig(entsoe);
|
||||
DebugConfig debugConfig;
|
||||
config->getDebugConfig(debugConfig);
|
||||
DomoticzConfig domo;
|
||||
config->getDomoticzConfig(domo);
|
||||
UiConfig ui;
|
||||
config->getUiConfig(ui);
|
||||
|
||||
bool qsc = false;
|
||||
bool qsr = false;
|
||||
@@ -838,7 +879,7 @@ void AmsWebServer::configurationJson() {
|
||||
snprintf_P(buf, BufferSize, CONF_GPIO_JSON,
|
||||
gpioConfig->hanPin == 0xff ? "null" : String(gpioConfig->hanPin, 10).c_str(),
|
||||
gpioConfig->apPin == 0xff ? "null" : String(gpioConfig->apPin, 10).c_str(),
|
||||
gpioConfig->hanPin == 0xff ? "null" : String(gpioConfig->hanPin, 10).c_str(),
|
||||
gpioConfig->ledPin == 0xff ? "null" : String(gpioConfig->ledPin, 10).c_str(),
|
||||
gpioConfig->ledInverted ? "true" : "false",
|
||||
gpioConfig->ledPinRed == 0xff ? "null" : String(gpioConfig->ledPinRed, 10).c_str(),
|
||||
gpioConfig->ledPinGreen == 0xff ? "null" : String(gpioConfig->ledPinGreen, 10).c_str(),
|
||||
@@ -854,6 +895,28 @@ void AmsWebServer::configurationJson() {
|
||||
gpioConfig->vccBootLimit / 10.0
|
||||
);
|
||||
server.sendContent(buf);
|
||||
snprintf_P(buf, BufferSize, CONF_UI_JSON,
|
||||
ui.showImport,
|
||||
ui.showExport,
|
||||
ui.showVoltage,
|
||||
ui.showAmperage,
|
||||
ui.showReactive,
|
||||
ui.showRealtime,
|
||||
ui.showPeaks,
|
||||
ui.showPricePlot,
|
||||
ui.showDayPlot,
|
||||
ui.showMonthPlot,
|
||||
ui.showTemperaturePlot
|
||||
);
|
||||
server.sendContent(buf);
|
||||
snprintf_P(buf, BufferSize, CONF_DOMOTICZ_JSON,
|
||||
domo.elidx,
|
||||
domo.cl1idx,
|
||||
domo.vl1idx,
|
||||
domo.vl2idx,
|
||||
domo.vl3idx
|
||||
);
|
||||
server.sendContent(buf);
|
||||
server.sendContent("}");
|
||||
}
|
||||
|
||||
@@ -1141,14 +1204,14 @@ void AmsWebServer::handleSave() {
|
||||
config->setMqttConfig(mqtt);
|
||||
}
|
||||
|
||||
if(server.hasArg(F("dc")) && server.arg(F("dc")) == F("true")) {
|
||||
if(server.hasArg(F("o")) && server.arg(F("o")) == F("true")) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(PSTR("Received Domoticz config"));
|
||||
DomoticzConfig domo {
|
||||
static_cast<uint16_t>(server.arg(F("elidx")).toInt()),
|
||||
static_cast<uint16_t>(server.arg(F("vl1idx")).toInt()),
|
||||
static_cast<uint16_t>(server.arg(F("vl2idx")).toInt()),
|
||||
static_cast<uint16_t>(server.arg(F("vl3idx")).toInt()),
|
||||
static_cast<uint16_t>(server.arg(F("cl1idx")).toInt())
|
||||
static_cast<uint16_t>(server.arg(F("oe")).toInt()),
|
||||
static_cast<uint16_t>(server.arg(F("ou1")).toInt()),
|
||||
static_cast<uint16_t>(server.arg(F("ou2")).toInt()),
|
||||
static_cast<uint16_t>(server.arg(F("ou3")).toInt()),
|
||||
static_cast<uint16_t>(server.arg(F("oc")).toInt())
|
||||
};
|
||||
config->setDomoticzConfig(domo);
|
||||
}
|
||||
@@ -1240,6 +1303,23 @@ void AmsWebServer::handleSave() {
|
||||
config->setDebugConfig(debug);
|
||||
}
|
||||
|
||||
if(server.hasArg(F("u")) && server.arg(F("u")) == F("true")) {
|
||||
UiConfig ui;
|
||||
config->getUiConfig(ui);
|
||||
ui.showImport = server.arg(F("ui")).toInt();
|
||||
ui.showExport = server.arg(F("ue")).toInt();
|
||||
ui.showVoltage = server.arg(F("uv")).toInt();
|
||||
ui.showAmperage = server.arg(F("ua")).toInt();
|
||||
ui.showReactive = server.arg(F("ur")).toInt();
|
||||
ui.showRealtime = server.arg(F("uc")).toInt();
|
||||
ui.showPeaks = server.arg(F("ut")).toInt();
|
||||
ui.showPricePlot = server.arg(F("up")).toInt();
|
||||
ui.showDayPlot = server.arg(F("ud")).toInt();
|
||||
ui.showMonthPlot = server.arg(F("um")).toInt();
|
||||
ui.showTemperaturePlot = server.arg(F("us")).toInt();
|
||||
config->setUiConfig(ui);
|
||||
}
|
||||
|
||||
if(server.hasArg(F("p")) && server.arg(F("p")) == F("true")) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(PSTR("Received price API config"));
|
||||
|
||||
@@ -1351,7 +1431,6 @@ void AmsWebServer::upgrade() {
|
||||
server.handleClient();
|
||||
delay(250);
|
||||
|
||||
String customFirmwareUrl = "";
|
||||
if(server.hasArg(F("url"))) {
|
||||
customFirmwareUrl = server.arg(F("url"));
|
||||
}
|
||||
@@ -1415,8 +1494,55 @@ void AmsWebServer::firmwarePost() {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(PSTR("Handling firmware post..."));
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
if(rebootForUpgrade) {
|
||||
server.send(200);
|
||||
} else {
|
||||
if(server.hasArg(F("url"))) {
|
||||
String url = server.arg(F("url"));
|
||||
if(!url.isEmpty() && (url.startsWith(F("http://")) || url.startsWith(F("https://")))) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(PSTR("Custom firmware URL was provided"));
|
||||
|
||||
server.send(200);
|
||||
WiFiClient client;
|
||||
#if defined(ESP8266)
|
||||
String chipType = F("esp8266");
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
String chipType = F("esp32s2");
|
||||
#elif defined(ESP32)
|
||||
#if defined(CONFIG_FREERTOS_UNICORE)
|
||||
String chipType = F("esp32solo");
|
||||
#else
|
||||
String chipType = F("esp32");
|
||||
#endif
|
||||
#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
|
||||
|
||||
switch(ret) {
|
||||
case HTTP_UPDATE_FAILED:
|
||||
debugger->printf(PSTR("Update failed"));
|
||||
break;
|
||||
case HTTP_UPDATE_NO_UPDATES:
|
||||
debugger->printf(PSTR("No Update"));
|
||||
break;
|
||||
case HTTP_UPDATE_OK:
|
||||
debugger->printf(PSTR("Update OK"));
|
||||
break;
|
||||
}
|
||||
server.send(200, MIME_PLAIN, "OK");
|
||||
return;
|
||||
}
|
||||
}
|
||||
server.sendHeader(HEADER_LOCATION,F("/firmware"));
|
||||
server.send(303);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1425,8 +1551,12 @@ void AmsWebServer::firmwareUpload() {
|
||||
return;
|
||||
|
||||
HTTPUpload& upload = server.upload();
|
||||
if(upload.status == UPLOAD_FILE_START) {
|
||||
if(upload.status == UPLOAD_FILE_START) {
|
||||
String filename = upload.filename;
|
||||
if(filename.isEmpty()) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(PSTR("No file, falling back to post\n"));
|
||||
return;
|
||||
}
|
||||
if(!filename.endsWith(".bin")) {
|
||||
server.send(500, MIME_PLAIN, "500: couldn't create file");
|
||||
} else {
|
||||
@@ -1450,10 +1580,10 @@ HTTPUpload& AmsWebServer::uploadFile(const char* path) {
|
||||
HTTPUpload& upload = server.upload();
|
||||
if(upload.status == UPLOAD_FILE_START){
|
||||
if(uploading) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(PSTR("Upload already in progress"));
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(PSTR("Upload already in progress\n"));
|
||||
server.send_P(500, MIME_HTML, PSTR("<html><body><h1>Upload already in progress!</h1></body></html>"));
|
||||
} else if (!LittleFS.begin()) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(PSTR("An Error has occurred while mounting LittleFS"));
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(PSTR("An Error has occurred while mounting LittleFS\n"));
|
||||
server.send_P(500, MIME_HTML, PSTR("<html><body><h1>Unable to mount LittleFS!</h1></body></html>"));
|
||||
} else {
|
||||
uploading = true;
|
||||
@@ -1491,7 +1621,7 @@ HTTPUpload& AmsWebServer::uploadFile(const char* path) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(PSTR("An Error has occurred while writing file"));
|
||||
snprintf_P(buf, BufferSize, RESPONSE_JSON,
|
||||
"false",
|
||||
"Unable to upload",
|
||||
"File size does not match",
|
||||
"false"
|
||||
);
|
||||
server.setContentLength(strlen(buf));
|
||||
@@ -1499,14 +1629,18 @@ HTTPUpload& AmsWebServer::uploadFile(const char* path) {
|
||||
}
|
||||
}
|
||||
} else if(upload.status == UPLOAD_FILE_END) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("handleFileUpload Ended\n"));
|
||||
}
|
||||
if(file) {
|
||||
file.flush();
|
||||
file.close();
|
||||
// LittleFS.end();
|
||||
} else {
|
||||
debugger->printf_P(PSTR("File was not valid in the end... Write error: %d, \n"), file.getWriteError());
|
||||
snprintf_P(buf, BufferSize, RESPONSE_JSON,
|
||||
"false",
|
||||
"Unable to upload",
|
||||
"Upload ended, but file is missing",
|
||||
"false"
|
||||
);
|
||||
server.setContentLength(strlen(buf));
|
||||
@@ -1830,10 +1964,11 @@ void AmsWebServer::configFileDownload() {
|
||||
|
||||
if(ds != NULL) {
|
||||
DayDataPoints day = ds->getDayData();
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("dayplot %d %lu %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"),
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("dayplot %d %lu %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"),
|
||||
day.version,
|
||||
(int32_t) day.lastMeterReadTime,
|
||||
day.activeImport,
|
||||
day.accuracy,
|
||||
ds->getHourImport(0),
|
||||
ds->getHourImport(1),
|
||||
ds->getHourImport(2),
|
||||
@@ -1892,10 +2027,11 @@ void AmsWebServer::configFileDownload() {
|
||||
}
|
||||
|
||||
MonthDataPoints month = ds->getMonthData();
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("monthplot %d %lu %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"),
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("monthplot %d %lu %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 %d"),
|
||||
month.version,
|
||||
(int32_t) month.lastMeterReadTime,
|
||||
month.activeImport,
|
||||
month.accuracy,
|
||||
ds->getDayImport(1),
|
||||
ds->getDayImport(2),
|
||||
ds->getDayImport(3),
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
extra_configs = platformio-user.ini
|
||||
|
||||
[common]
|
||||
lib_deps = EEPROM, LittleFS, DNSServer, 256dpi/MQTT@2.5.0, OneWireNg@0.10.0, DallasTemperature@3.9.1, EspSoftwareSerial@6.14.1, https://github.com/gskjold/RemoteDebug.git, Time@1.6.1, Timezone@1.2.4, AmsConfiguration, AmsData, AmsDataStorage, HwTools, Uptime, AmsDecoder, EntsoePriceApi, EnergyAccounting, AmsMqttHandler, RawMqttHandler, JsonMqttHandler, DomoticzMqttHandler, HomeAssistantMqttHandler, SvelteUi
|
||||
lib_deps = EEPROM, LittleFS, DNSServer, 256dpi/MQTT@2.5.0, OneWireNg@0.10.0, DallasTemperature@3.9.1, EspSoftwareSerial@6.14.1, https://github.com/gskjold/RemoteDebug.git, Time@1.6.1, Timezone@1.2.4, AmsConfiguration, AmsData, AmsDataStorage, HwTools, Uptime, AmsDecoder, EntsoePriceApi, EnergyAccounting, RawMqttHandler, JsonMqttHandler, DomoticzMqttHandler, HomeAssistantMqttHandler, SvelteUi
|
||||
lib_ignore = OneWire
|
||||
extra_scripts =
|
||||
pre:scripts/addversion.py
|
||||
@@ -10,6 +10,11 @@ extra_scripts =
|
||||
lib/DomoticzMqttHandler/scripts/generate_includes.py
|
||||
lib/HomeAssistantMqttHandler/scripts/generate_includes.py
|
||||
lib/SvelteUi/scripts/generate_includes.py
|
||||
build_flags =
|
||||
-D WEBSOCKET_DISABLED=1
|
||||
-D NO_AMS2MQTT_PRICE_KEY
|
||||
-D NO_AMS2MQTT_PRICE_AUTHENTICATION
|
||||
-fexceptions
|
||||
|
||||
[esp32]
|
||||
lib_deps = WiFi, ESPmDNS, WiFiClientSecure, HTTPClient, FS, Update, HTTPUpdate, WebServer, ${common.lib_deps}
|
||||
@@ -19,8 +24,9 @@ platform = espressif8266@3.2.0
|
||||
framework = arduino
|
||||
board = esp12e
|
||||
board_build.ldscript = eagle.flash.4m2m.ld
|
||||
build_flags = -D WEBSOCKET_DISABLED=1 -fexceptions
|
||||
build_flags = ${common.build_flags}
|
||||
lib_ldf_mode = off
|
||||
lib_compat_mode = off
|
||||
lib_deps = ESP8266WiFi, ESP8266mDNS, ESP8266WebServer, ESP8266HTTPClient, ESP8266httpUpdate, ${common.lib_deps}
|
||||
lib_ignore = ${common.lib_ignore}
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
@@ -30,8 +36,9 @@ platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.
|
||||
framework = arduino
|
||||
board = esp32dev
|
||||
board_build.f_cpu = 160000000L
|
||||
build_flags = -D WEBSOCKET_DISABLED=1 -fexceptions
|
||||
build_flags = ${common.build_flags}
|
||||
lib_ldf_mode = off
|
||||
lib_compat_mode = off
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
lib_ignore = ${common.lib_ignore}
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
@@ -48,8 +55,9 @@ board_build.variant = esp32s2
|
||||
board_build.flash_mode = qio
|
||||
board_build.f_cpu = 160000000L
|
||||
board_build.f_flash = 40000000L
|
||||
build_flags = -D WEBSOCKET_DISABLED=1 -fexceptions
|
||||
build_flags = ${common.build_flags}
|
||||
lib_ldf_mode = off
|
||||
lib_compat_mode = off
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
lib_ignore = ${common.lib_ignore}
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
@@ -59,8 +67,9 @@ platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.
|
||||
framework = arduino
|
||||
board = esp32-solo1
|
||||
board_build.f_cpu = 160000000L
|
||||
build_flags = -D WEBSOCKET_DISABLED=1 -DFRAMEWORK_ARDUINO_SOLO1 -fexceptions
|
||||
build_flags = ${common.build_flags} -DFRAMEWORK_ARDUINO_SOLO1
|
||||
lib_ldf_mode = off
|
||||
lib_compat_mode = off
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
lib_ignore = ${common.lib_ignore}
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
|
||||
@@ -1124,7 +1124,6 @@ void WiFi_connect() {
|
||||
#endif
|
||||
|
||||
MDNS.end();
|
||||
WiFi.persistent(false);
|
||||
WiFi.disconnect(true);
|
||||
WiFi.softAPdisconnect(true);
|
||||
WiFi.enableAP(false);
|
||||
@@ -1175,8 +1174,11 @@ void WiFi_connect() {
|
||||
WiFi.hostname(wifi.hostname);
|
||||
}
|
||||
#endif
|
||||
#if defined(ESP32)
|
||||
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
|
||||
WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
|
||||
#endif
|
||||
WiFi.setAutoReconnect(true);
|
||||
WiFi.persistent(true);
|
||||
if(WiFi.begin(wifi.ssid, wifi.psk)) {
|
||||
if(wifi.sleep <= 2) {
|
||||
switch(wifi.sleep) {
|
||||
@@ -1463,6 +1465,7 @@ void MQTT_connect() {
|
||||
|
||||
#if defined(ESP8266)
|
||||
if(mqttSecureClient) {
|
||||
time_t epoch = time(nullptr);
|
||||
debugD("Setting NTP time %lu for secure MQTT connection", epoch);
|
||||
mqttSecureClient->setX509Time(epoch);
|
||||
}
|
||||
@@ -1535,54 +1538,62 @@ void configFileParse() {
|
||||
char* buf = (char*) commonBuffer;
|
||||
memset(buf, 0, 1024);
|
||||
while((size = file.readBytesUntil('\n', buf, 1024)) > 0) {
|
||||
for(uint16_t i = 0; i < size; i++) {
|
||||
if(buf[i] < 32 || buf[i] > 126) {
|
||||
memset(buf+i, 0, size-i);
|
||||
debugD("Found non-ascii, shortening line from %d to %d", size, i);
|
||||
size = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(strncmp_P(buf, PSTR("boardType "), 10) == 0) {
|
||||
if(!lSys) { config.getSystemConfig(sys); lSys = true; };
|
||||
sys.boardType = String(buf+10).toInt();
|
||||
} else if(strncmp_P(buf, PSTR("ssid "), 5) == 0) {
|
||||
if(!lWiFi) { config.getWiFiConfig(wifi); lWiFi = true; };
|
||||
memcpy(wifi.ssid, buf+5, size-5);
|
||||
strcpy(wifi.ssid, buf+5);
|
||||
} else if(strncmp_P(buf, PSTR("psk "), 4) == 0) {
|
||||
if(!lWiFi) { config.getWiFiConfig(wifi); lWiFi = true; };
|
||||
memcpy(wifi.psk, buf+4, size-4);
|
||||
strcpy(wifi.psk, buf+4);
|
||||
} else if(strncmp_P(buf, PSTR("ip "), 3) == 0) {
|
||||
if(!lWiFi) { config.getWiFiConfig(wifi); lWiFi = true; };
|
||||
memcpy(wifi.ip, buf+3, size-3);
|
||||
strcpy(wifi.ip, buf+3);
|
||||
} else if(strncmp_P(buf, PSTR("gateway "), 8) == 0) {
|
||||
if(!lWiFi) { config.getWiFiConfig(wifi); lWiFi = true; };
|
||||
memcpy(wifi.gateway, buf+8, size-8);
|
||||
strcpy(wifi.gateway, buf+8);
|
||||
} else if(strncmp_P(buf, PSTR("subnet "), 7) == 0) {
|
||||
if(!lWiFi) { config.getWiFiConfig(wifi); lWiFi = true; };
|
||||
memcpy(wifi.subnet, buf+7, size-7);
|
||||
strcpy(wifi.subnet, buf+7);
|
||||
} else if(strncmp_P(buf, PSTR("dns1 "), 5) == 0) {
|
||||
if(!lWiFi) { config.getWiFiConfig(wifi); lWiFi = true; };
|
||||
memcpy(wifi.dns1, buf+5, size-5);
|
||||
strcpy(wifi.dns1, buf+5);
|
||||
} else if(strncmp_P(buf, PSTR("dns2 "), 5) == 0) {
|
||||
if(!lWiFi) { config.getWiFiConfig(wifi); lWiFi = true; };
|
||||
memcpy(wifi.dns2, buf+5, size-5);
|
||||
strcpy(wifi.dns2, buf+5);
|
||||
} else if(strncmp_P(buf, PSTR("hostname "), 9) == 0) {
|
||||
if(!lWiFi) { config.getWiFiConfig(wifi); lWiFi = true; };
|
||||
memcpy(wifi.hostname, buf+9, size-9);
|
||||
strcpy(wifi.hostname, buf+9);
|
||||
} else if(strncmp_P(buf, PSTR("mdns "), 5) == 0) {
|
||||
if(!lWiFi) { config.getWiFiConfig(wifi); lWiFi = true; };
|
||||
wifi.mdns = String(buf+5).toInt() == 1;;
|
||||
} else if(strncmp_P(buf, PSTR("mqttHost "), 9) == 0) {
|
||||
if(!lMqtt) { config.getMqttConfig(mqtt); lMqtt = true; };
|
||||
memcpy(mqtt.host, buf+9, size-9);
|
||||
strcpy(mqtt.host, buf+9);
|
||||
} else if(strncmp_P(buf, PSTR("mqttPort "), 9) == 0) {
|
||||
if(!lMqtt) { config.getMqttConfig(mqtt); lMqtt = true; };
|
||||
mqtt.port = String(buf+9).toInt();
|
||||
} else if(strncmp_P(buf, PSTR("mqttClientId "), 13) == 0) {
|
||||
if(!lMqtt) { config.getMqttConfig(mqtt); lMqtt = true; };
|
||||
memcpy(mqtt.clientId, buf+13, size-13);
|
||||
strcpy(mqtt.clientId, buf+13);
|
||||
} else if(strncmp_P(buf, PSTR("mqttPublishTopic "), 17) == 0) {
|
||||
if(!lMqtt) { config.getMqttConfig(mqtt); lMqtt = true; };
|
||||
memcpy(mqtt.publishTopic, buf+17, size-17);
|
||||
strcpy(mqtt.publishTopic, buf+17);
|
||||
} else if(strncmp_P(buf, PSTR("mqttUsername "), 13) == 0) {
|
||||
if(!lMqtt) { config.getMqttConfig(mqtt); lMqtt = true; };
|
||||
memcpy(mqtt.username, buf+13, size-13);
|
||||
strcpy(mqtt.username, buf+13);
|
||||
} else if(strncmp_P(buf, PSTR("mqttPassword "), 13) == 0) {
|
||||
if(!lMqtt) { config.getMqttConfig(mqtt); lMqtt = true; };
|
||||
memcpy(mqtt.password, buf+13, size-13);
|
||||
strcpy(mqtt.password, buf+13);
|
||||
} else if(strncmp_P(buf, PSTR("mqttPayloadFormat "), 18) == 0) {
|
||||
if(!lMqtt) { config.getMqttConfig(mqtt); lMqtt = true; };
|
||||
mqtt.payloadFormat = String(buf+18).toInt();
|
||||
@@ -1594,10 +1605,10 @@ void configFileParse() {
|
||||
web.security = String(buf+12).toInt();
|
||||
} else if(strncmp_P(buf, PSTR("webUsername "), 12) == 0) {
|
||||
if(!lWeb) { config.getWebConfig(web); lWeb = true; };
|
||||
memcpy(web.username, buf+12, size-12);
|
||||
strcpy(web.username, buf+12);
|
||||
} else if(strncmp_P(buf, PSTR("webPassword "), 12) == 0) {
|
||||
if(!lWeb) { config.getWebConfig(web); lWeb = true; };
|
||||
memcpy(web.username, buf+12, size-12);
|
||||
strcpy(web.username, buf+12);
|
||||
} else if(strncmp_P(buf, PSTR("meterBaud "), 10) == 0) {
|
||||
if(!lMeter) { config.getMeterConfig(meter); lMeter = true; };
|
||||
meter.baud = String(buf+10).toInt();
|
||||
@@ -1696,19 +1707,19 @@ void configFileParse() {
|
||||
ntp.dhcp = String(buf+8).toInt() == 1;
|
||||
} else if(strncmp_P(buf, PSTR("ntpServer "), 10) == 0) {
|
||||
if(!lNtp) { config.getNtpConfig(ntp); lNtp = true; };
|
||||
memcpy(ntp.server, buf+10, size-10);
|
||||
strcpy(ntp.server, buf+10);
|
||||
} else if(strncmp_P(buf, PSTR("ntpTimezone "), 12) == 0) {
|
||||
if(!lNtp) { config.getNtpConfig(ntp); lNtp = true; };
|
||||
memcpy(ntp.timezone, buf+12, size-12);
|
||||
strcpy(ntp.timezone, buf+12);
|
||||
} else if(strncmp_P(buf, PSTR("entsoeToken "), 12) == 0) {
|
||||
if(!lEntsoe) { config.getEntsoeConfig(entsoe); lEntsoe = true; };
|
||||
memcpy(entsoe.token, buf+12, size-12);
|
||||
strcpy(entsoe.token, buf+12);
|
||||
} else if(strncmp_P(buf, PSTR("entsoeArea "), 11) == 0) {
|
||||
if(!lEntsoe) { config.getEntsoeConfig(entsoe); lEntsoe = true; };
|
||||
memcpy(entsoe.area, buf+11, size-11);
|
||||
strcpy(entsoe.area, buf+11);
|
||||
} else if(strncmp_P(buf, PSTR("entsoeCurrency "), 15) == 0) {
|
||||
if(!lEntsoe) { config.getEntsoeConfig(entsoe); lEntsoe = true; };
|
||||
memcpy(entsoe.currency, buf+15, size-15);
|
||||
strcpy(entsoe.currency, buf+15);
|
||||
} else if(strncmp_P(buf, PSTR("entsoeMultiplier "), 17) == 0) {
|
||||
if(!lEntsoe) { config.getEntsoeConfig(entsoe); lEntsoe = true; };
|
||||
entsoe.multiplier = String(buf+17).toDouble() * 1000;
|
||||
@@ -1723,20 +1734,38 @@ void configFileParse() {
|
||||
eac.hours = String(pch).toInt();
|
||||
} else if(strncmp_P(buf, PSTR("dayplot "), 8) == 0) {
|
||||
int i = 0;
|
||||
DayDataPoints day = { 4 }; // Use a version we know the multiplier of the data points
|
||||
DayDataPoints day = { 0 };
|
||||
char * pch = strtok (buf+8," ");
|
||||
while (pch != NULL) {
|
||||
int64_t val = String(pch).toInt();
|
||||
if(i == 1) {
|
||||
day.lastMeterReadTime = val;
|
||||
} else if(i == 2) {
|
||||
day.activeImport = val;
|
||||
} else if(i > 2 && i < 27) {
|
||||
day.hImport[i-3] = val / 10;
|
||||
} else if(i == 27) {
|
||||
day.activeExport = val;
|
||||
} else if(i > 27 && i < 52) {
|
||||
day.hExport[i-28] = val / 10;
|
||||
if(day.version < 5) {
|
||||
if(i == 0) {
|
||||
day.version = val;
|
||||
} else if(i == 1) {
|
||||
day.lastMeterReadTime = val;
|
||||
} else if(i == 2) {
|
||||
day.activeImport = val;
|
||||
} else if(i > 2 && i < 27) {
|
||||
day.hImport[i-3] = val / 10;
|
||||
} else if(i == 27) {
|
||||
day.activeExport = val;
|
||||
} else if(i > 27 && i < 52) {
|
||||
day.hExport[i-28] = val / 10;
|
||||
}
|
||||
} else {
|
||||
if(i == 1) {
|
||||
day.lastMeterReadTime = val;
|
||||
} else if(i == 2) {
|
||||
day.activeImport = val;
|
||||
} else if(i == 3) {
|
||||
day.accuracy = val;
|
||||
} else if(i > 3 && i < 28) {
|
||||
day.hImport[i-4] = val / pow(10, day.accuracy);
|
||||
} else if(i == 28) {
|
||||
day.activeExport = val;
|
||||
} else if(i > 28 && i < 53) {
|
||||
day.hExport[i-29] = val / pow(10, day.accuracy);
|
||||
}
|
||||
}
|
||||
|
||||
pch = strtok (NULL, " ");
|
||||
@@ -1746,22 +1775,39 @@ void configFileParse() {
|
||||
sDs = true;
|
||||
} else if(strncmp_P(buf, PSTR("monthplot "), 10) == 0) {
|
||||
int i = 0;
|
||||
MonthDataPoints month = { 5 }; // Use a version we know the multiplier of the data points
|
||||
MonthDataPoints month = { 0 };
|
||||
char * pch = strtok (buf+10," ");
|
||||
while (pch != NULL) {
|
||||
int64_t val = String(pch).toInt();
|
||||
if(i == 1) {
|
||||
month.lastMeterReadTime = val;
|
||||
} else if(i == 2) {
|
||||
month.activeImport = val;
|
||||
} else if(i > 2 && i < 34) {
|
||||
month.dImport[i-3] = val / 10;
|
||||
} else if(i == 34) {
|
||||
month.activeExport = val;
|
||||
} else if(i > 34 && i < 66) {
|
||||
month.dExport[i-35] = val / 10;
|
||||
if(month.version < 6) {
|
||||
if(i == 0) {
|
||||
month.version = val;
|
||||
} else if(i == 1) {
|
||||
month.lastMeterReadTime = val;
|
||||
} else if(i == 2) {
|
||||
month.activeImport = val;
|
||||
} else if(i > 2 && i < 34) {
|
||||
month.dImport[i-3] = val / 10;
|
||||
} else if(i == 34) {
|
||||
month.activeExport = val;
|
||||
} else if(i > 34 && i < 66) {
|
||||
month.dExport[i-35] = val / 10;
|
||||
}
|
||||
} else {
|
||||
if(i == 1) {
|
||||
month.lastMeterReadTime = val;
|
||||
} else if(i == 2) {
|
||||
month.activeImport = val;
|
||||
} else if(i == 3) {
|
||||
month.accuracy = val;
|
||||
} else if(i > 3 && i < 35) {
|
||||
month.dImport[i-4] = val / pow(10, month.accuracy);
|
||||
} else if(i == 35) {
|
||||
month.activeExport = val;
|
||||
} else if(i > 35 && i < 67) {
|
||||
month.dExport[i-36] = val / pow(10, month.accuracy);
|
||||
}
|
||||
}
|
||||
|
||||
pch = strtok (NULL, " ");
|
||||
i++;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
#include "IEC6205621.h"
|
||||
#include "crc.h"
|
||||
|
||||
IEC6205621::IEC6205621(const char* p) {
|
||||
if(strlen(p) < 16)
|
||||
return;
|
||||
|
||||
String payload(p+1);
|
||||
int crc_pos = payload.lastIndexOf("!");
|
||||
String crc = payload.substring(crc_pos+1, crc_pos+5);
|
||||
//uint16_t crc_calc = crc16_x25((uint8_t*) (payload.startsWith("/") ? p+1 : p), crc_pos);
|
||||
|
||||
//Serial.printf("CRC %s :: %04X\n", crc.c_str(), crc_calc);
|
||||
|
||||
lastUpdateMillis = millis();
|
||||
listId = payload.substring(payload.startsWith("/") ? 1 : 0, payload.indexOf("\n"));
|
||||
@@ -28,11 +22,14 @@ IEC6205621::IEC6205621(const char* p) {
|
||||
meterType = AmsTypeIskra;
|
||||
listId = listId.substring(0,5);
|
||||
} else if(listId.startsWith("XMX")) {
|
||||
meterType = AmsTypeLandis;
|
||||
meterType = AmsTypeLandisGyr;
|
||||
listId = listId.substring(0,6);
|
||||
} else if(listId.startsWith("Ene") || listId.startsWith("EST")) {
|
||||
meterType = AmsTypeSagemcom;
|
||||
listId = listId.substring(0,4);
|
||||
} else if(listId.startsWith("LGF")) {
|
||||
meterType = AmsTypeLandisGyr;
|
||||
listId = listId.substring(0,4);
|
||||
} else {
|
||||
meterType = AmsTypeUnknown;
|
||||
listId = listId.substring(0,4);
|
||||
@@ -79,6 +76,14 @@ IEC6205621::IEC6205621(const char* p) {
|
||||
l1current = extractDouble(payload, "31.7.0");
|
||||
l2current = extractDouble(payload, "51.7.0");
|
||||
l3current = extractDouble(payload, "71.7.0");
|
||||
|
||||
l1activeImportPower = extractDouble(payload, "21.7.0");
|
||||
l2activeImportPower = extractDouble(payload, "41.7.0");
|
||||
l3activeImportPower = extractDouble(payload, "61.7.0");
|
||||
|
||||
l1activeExportPower = extractDouble(payload, "22.7.0");
|
||||
l2activeExportPower = extractDouble(payload, "42.7.0");
|
||||
l3activeExportPower = extractDouble(payload, "62.7.0");
|
||||
|
||||
if(l1voltage > 0 || l2voltage > 0 || l3voltage > 0)
|
||||
listType = 2;
|
||||
@@ -128,6 +133,9 @@ IEC6205621::IEC6205621(const char* p) {
|
||||
l2current = (((activeImportPower - activeExportPower) * sqrt(3)) - (l1voltage * l1current) - (l3voltage * l3current)) / l2voltage;
|
||||
}
|
||||
}
|
||||
|
||||
if (l1activeImportPower > 0 || l2activeImportPower > 0 || l3activeImportPower > 0 || l1activeExportPower > 0 || l2activeExportPower > 0 || l3activeExportPower > 0)
|
||||
listType = 4;
|
||||
}
|
||||
|
||||
String IEC6205621::extract(String payload, String obis) {
|
||||
|
||||
15
src/LNG.cpp
15
src/LNG.cpp
@@ -5,7 +5,7 @@
|
||||
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;
|
||||
meterType = AmsTypeLandisGyr;
|
||||
this->packageTimestamp = ctx.timestamp;
|
||||
|
||||
uint8_t* ptr = (uint8_t*) &h[1];
|
||||
@@ -26,6 +26,7 @@ LNG::LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, Da
|
||||
if(descriptor->obis[3] == 7) {
|
||||
if(descriptor->obis[4] == 0) {
|
||||
o170 = getNumber(item);
|
||||
listType = listType >= 1 ? listType : 1;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu", o170);
|
||||
}
|
||||
} else if(descriptor->obis[3] == 8) {
|
||||
@@ -36,9 +37,11 @@ LNG::LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, Da
|
||||
activeImportCounter = o180 / 1000.0;
|
||||
} else if(descriptor->obis[4] == 1) {
|
||||
o181 = getNumber(item);
|
||||
listType = listType >= 3 ? listType : 3;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu", o181);
|
||||
} else if(descriptor->obis[4] == 2) {
|
||||
o182 = getNumber(item);
|
||||
listType = listType >= 3 ? listType : 3;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu", o182);
|
||||
}
|
||||
}
|
||||
@@ -46,6 +49,7 @@ LNG::LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, Da
|
||||
if(descriptor->obis[3] == 7) {
|
||||
if(descriptor->obis[4] == 0) {
|
||||
o270 = getNumber(item);
|
||||
listType = listType >= 2 ? listType : 2;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu", o270);
|
||||
}
|
||||
} else if(descriptor->obis[3] == 8) {
|
||||
@@ -56,9 +60,11 @@ LNG::LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, Da
|
||||
activeExportCounter = o280 / 1000.0;
|
||||
} else if(descriptor->obis[4] == 1) {
|
||||
o281 = getNumber(item);
|
||||
listType = listType >= 3 ? listType : 3;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu", o281);
|
||||
} else if(descriptor->obis[4] == 2) {
|
||||
o282 = getNumber(item);
|
||||
listType = listType >= 3 ? listType : 3;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu", o282);
|
||||
}
|
||||
}
|
||||
@@ -69,12 +75,14 @@ LNG::LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, Da
|
||||
memcpy(str, item->oct.data, item->oct.length);
|
||||
str[item->oct.length] = '\0';
|
||||
meterId = String(str);
|
||||
listType = listType >= 2 ? listType : 2;
|
||||
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);
|
||||
listType = listType >= 2 ? listType : 2;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %s (oct)", str);
|
||||
}
|
||||
}
|
||||
@@ -85,21 +93,18 @@ LNG::LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, Da
|
||||
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;
|
||||
listType = listType >= 2 ? listType : 2;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user