mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-10 20:54:24 +00:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
931f0d400b | ||
|
|
0178dc4184 | ||
|
|
be522b40f9 | ||
|
|
068c55e7bb | ||
|
|
8e88d7e11b | ||
|
|
e36acef1d4 | ||
|
|
07d5481a72 | ||
|
|
dbd6205cca | ||
|
|
938f9f69d1 | ||
|
|
cd27472e2d | ||
|
|
64f8414217 | ||
|
|
a2c1250724 | ||
|
|
4b7160b502 | ||
|
|
cc50457404 | ||
|
|
cd48192a74 | ||
|
|
cd031c33aa | ||
|
|
6c59b15681 | ||
|
|
7c8593122b | ||
|
|
6d8fd4e083 | ||
|
|
79d674710f | ||
|
|
062068eacd | ||
|
|
98309ea532 | ||
|
|
0f75fa4a58 | ||
|
|
d08f75d9c3 | ||
|
|
dd4a43c831 | ||
|
|
1f5a04e606 |
23
.github/workflows/release.yml
vendored
23
.github/workflows/release.yml
vendored
@@ -166,3 +166,26 @@ jobs:
|
||||
asset_path: esp32solo.zip
|
||||
asset_name: ams2mqtt-esp32solo-${{ steps.release_tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Build esp32c3 firmware
|
||||
run: pio run -e esp32c3
|
||||
- name: Create esp32c3 zip file
|
||||
run: /bin/sh scripts/esp32c3/mkzip.sh
|
||||
- name: Upload esp32c3 binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: .pio/build/esp32c3/firmware.bin
|
||||
asset_name: ams2mqtt-esp32c3-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp32c3 zip to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: esp32c3.zip
|
||||
asset_name: ams2mqtt-esp32c3-${{ steps.release_tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "Arduino.h"
|
||||
|
||||
#define EEPROM_SIZE 1024*3
|
||||
#define EEPROM_CHECK_SUM 101 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CHECK_SUM 102 // 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
|
||||
@@ -175,6 +175,11 @@ struct EntsoeConfig {
|
||||
}; // 62
|
||||
|
||||
struct EnergyAccountingConfig {
|
||||
uint16_t thresholds[10];
|
||||
uint8_t hours;
|
||||
}; // 21
|
||||
|
||||
struct EnergyAccountingConfig101 {
|
||||
uint8_t thresholds[10];
|
||||
uint8_t hours;
|
||||
}; // 11
|
||||
@@ -298,6 +303,7 @@ private:
|
||||
bool relocateConfig95(); // 2.1.4
|
||||
bool relocateConfig96(); // 2.1.14
|
||||
bool relocateConfig100(); // 2.2-dev
|
||||
bool relocateConfig101(); // 2.2.0 through 2.2.8
|
||||
|
||||
void saveToFs();
|
||||
bool loadFromFs(uint8_t version);
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
String toHex(uint8_t* in);
|
||||
String toHex(uint8_t* in, uint16_t size);
|
||||
void fromHex(uint8_t *out, String in, uint16_t size);
|
||||
void stripNonAscii(uint8_t* in, uint16_t size);
|
||||
void stripNonAscii(uint8_t* in, uint16_t size, bool extended = false);
|
||||
|
||||
#endif
|
||||
@@ -62,7 +62,7 @@ bool AmsConfiguration::setWiFiConfig(WiFiConfig& config) {
|
||||
wifiChanged = true;
|
||||
}
|
||||
|
||||
stripNonAscii((uint8_t*) config.ssid, 32);
|
||||
stripNonAscii((uint8_t*) config.ssid, 32, true);
|
||||
stripNonAscii((uint8_t*) config.psk, 64);
|
||||
stripNonAscii((uint8_t*) config.ip, 16);
|
||||
stripNonAscii((uint8_t*) config.gateway, 16);
|
||||
@@ -556,7 +556,7 @@ bool AmsConfiguration::getEnergyAccountingConfig(EnergyAccountingConfig& config)
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_ENERGYACCOUNTING_START, config);
|
||||
EEPROM.end();
|
||||
if(config.thresholds[9] != 255) {
|
||||
if(config.thresholds[9] != 0xFFFF) {
|
||||
clearEnergyAccountingConfig(config);
|
||||
}
|
||||
if(config.hours > 5) config.hours = 5;
|
||||
@@ -576,7 +576,7 @@ bool AmsConfiguration::setEnergyAccountingConfig(EnergyAccountingConfig& config)
|
||||
energyAccountingChanged = true;
|
||||
}
|
||||
}
|
||||
config.thresholds[9] = 255;
|
||||
config.thresholds[9] = 0xFFFF;
|
||||
energyAccountingChanged |= config.hours != existing.hours;
|
||||
} else {
|
||||
energyAccountingChanged = true;
|
||||
@@ -598,7 +598,7 @@ void AmsConfiguration::clearEnergyAccountingConfig(EnergyAccountingConfig& confi
|
||||
config.thresholds[6] = 75;
|
||||
config.thresholds[7] = 100;
|
||||
config.thresholds[8] = 150;
|
||||
config.thresholds[9] = 255;
|
||||
config.thresholds[9] = 0xFFFF;
|
||||
config.hours = 3;
|
||||
}
|
||||
|
||||
@@ -756,6 +756,14 @@ bool AmsConfiguration::hasConfig() {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 101:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig101()) {
|
||||
configVersion = 102;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case EEPROM_CHECK_SUM:
|
||||
return true;
|
||||
default:
|
||||
@@ -958,6 +966,25 @@ bool AmsConfiguration::relocateConfig100() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig101() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
|
||||
EnergyAccountingConfig config;
|
||||
EnergyAccountingConfig101 config101;
|
||||
EEPROM.get(CONFIG_ENERGYACCOUNTING_START, config101);
|
||||
for(uint8_t i = 0; i < 9; i++) {
|
||||
config.thresholds[i] = config101.thresholds[i];
|
||||
}
|
||||
config.thresholds[9] = 0xFFFF;
|
||||
config.hours = config101.hours;
|
||||
EEPROM.put(CONFIG_ENERGYACCOUNTING_START, config);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 102);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::save() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CHECK_SUM);
|
||||
|
||||
@@ -22,13 +22,15 @@ void fromHex(uint8_t *out, String in, uint16_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
void stripNonAscii(uint8_t* in, uint16_t size) {
|
||||
void stripNonAscii(uint8_t* in, uint16_t size, bool extended) {
|
||||
for(uint16_t i = 0; i < size; i++) {
|
||||
if(in[i] == 0) { // Clear the rest with null-terminator
|
||||
memset(in+i, 0, size-i);
|
||||
break;
|
||||
}
|
||||
if(in[i] < 32 || in[i] > 126) {
|
||||
if(extended && (in[i] < 32 || in[i] == 127 || in[i] == 129 || in[i] == 141 || in[i] == 143 || in[i] == 144 || in[i] == 157)) {
|
||||
memset(in+i, ' ', 1);
|
||||
} else if(in[i] < 32 || in[i] > 126) {
|
||||
memset(in+i, ' ', 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,10 +35,10 @@ public:
|
||||
|
||||
time_t getMeterTimestamp();
|
||||
|
||||
uint16_t getActiveImportPower();
|
||||
uint16_t getReactiveImportPower();
|
||||
uint16_t getActiveExportPower();
|
||||
uint16_t getReactiveExportPower();
|
||||
uint32_t getActiveImportPower();
|
||||
uint32_t getReactiveImportPower();
|
||||
uint32_t getActiveExportPower();
|
||||
uint32_t getReactiveExportPower();
|
||||
|
||||
float getL1Voltage();
|
||||
float getL2Voltage();
|
||||
@@ -79,7 +79,7 @@ protected:
|
||||
time_t packageTimestamp = 0;
|
||||
String listId = "", meterId = "", meterModel = "";
|
||||
time_t meterTimestamp = 0;
|
||||
uint16_t activeImportPower = 0, reactiveImportPower = 0, activeExportPower = 0, reactiveExportPower = 0;
|
||||
uint32_t activeImportPower = 0, reactiveImportPower = 0, activeExportPower = 0, reactiveExportPower = 0;
|
||||
float l1voltage = 0, l2voltage = 0, l3voltage = 0, l1current = 0, l2current = 0, l3current = 0;
|
||||
float l1activeImportPower = 0, l2activeImportPower = 0, l3activeImportPower = 0;
|
||||
float l1activeExportPower = 0, l2activeExportPower = 0, l3activeExportPower = 0;
|
||||
|
||||
@@ -17,7 +17,7 @@ public:
|
||||
};
|
||||
virtual ~AmsMqttHandler() {};
|
||||
|
||||
virtual bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||
virtual bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
virtual bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
virtual bool publishPrices(EntsoeApi* eapi);
|
||||
virtual bool publishSystem(HwTools*, EntsoeApi*, EnergyAccounting*);
|
||||
|
||||
@@ -114,19 +114,19 @@ time_t AmsData::getMeterTimestamp() {
|
||||
return this->meterTimestamp;
|
||||
}
|
||||
|
||||
uint16_t AmsData::getActiveImportPower() {
|
||||
uint32_t AmsData::getActiveImportPower() {
|
||||
return this->activeImportPower;
|
||||
}
|
||||
|
||||
uint16_t AmsData::getReactiveImportPower() {
|
||||
uint32_t AmsData::getReactiveImportPower() {
|
||||
return this->reactiveImportPower;
|
||||
}
|
||||
|
||||
uint16_t AmsData::getActiveExportPower() {
|
||||
uint32_t AmsData::getActiveExportPower() {
|
||||
return this->activeExportPower;
|
||||
}
|
||||
|
||||
uint16_t AmsData::getReactiveExportPower() {
|
||||
uint32_t AmsData::getReactiveExportPower() {
|
||||
return this->reactiveExportPower;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,935 +0,0 @@
|
||||
var nextVersion;
|
||||
var im, em;
|
||||
var ds = 0;
|
||||
var currency = "";
|
||||
var swatt = false;
|
||||
|
||||
// Price plot
|
||||
var pp;
|
||||
var pa;
|
||||
var po = {
|
||||
title: 'Future energy price',
|
||||
titleTextStyle: {
|
||||
fontSize: 14
|
||||
},
|
||||
backgroundColor: { fill:'transparent' },
|
||||
bar: { groupWidth: '90%' },
|
||||
legend: { position: 'none' },
|
||||
vAxis: {
|
||||
viewWindowMode: 'maximized'
|
||||
},
|
||||
tooltip: { trigger: 'none'},
|
||||
enableInteractivity: false,
|
||||
};
|
||||
var pl = null; // Last price
|
||||
var tl = null; // Last temperature
|
||||
|
||||
// Day plot
|
||||
var ep;
|
||||
var ea;
|
||||
var eo = {
|
||||
title: 'Energy use last 24 hours',
|
||||
titleTextStyle: {
|
||||
fontSize: 14
|
||||
},
|
||||
colors: ['#6f42c1', '#6f42c1'],
|
||||
backgroundColor: { fill:'transparent' },
|
||||
bar: { groupWidth: '90%' },
|
||||
legend: { position: 'none' },
|
||||
vAxis: {
|
||||
title: 'kWh',
|
||||
viewWindowMode: 'maximized'
|
||||
},
|
||||
tooltip: { trigger: 'none'},
|
||||
enableInteractivity: false,
|
||||
isStacked: true
|
||||
};
|
||||
|
||||
// Month plot
|
||||
var mp;
|
||||
var ma;
|
||||
var mo = {
|
||||
title: 'Energy use last month',
|
||||
titleTextStyle: {
|
||||
fontSize: 14
|
||||
},
|
||||
colors: ['#6f42c1', '#6f42c1'],
|
||||
backgroundColor: { fill:'transparent' },
|
||||
bar: { groupWidth: '90%' },
|
||||
legend: { position: 'none' },
|
||||
vAxis: {
|
||||
title: 'kWh',
|
||||
viewWindowMode: 'maximized'
|
||||
},
|
||||
tooltip: { trigger: 'none'},
|
||||
enableInteractivity: false,
|
||||
isStacked: true
|
||||
};
|
||||
|
||||
// Voltage plot
|
||||
var vp;
|
||||
var va;
|
||||
var vo = {
|
||||
title: 'Phase voltage',
|
||||
titleTextStyle: {
|
||||
fontSize: 14
|
||||
},
|
||||
backgroundColor: { fill:'transparent' },
|
||||
bar: { groupWidth: '90%' },
|
||||
vAxis: {
|
||||
minValue: 200,
|
||||
maxValue: 260,
|
||||
ticks: [
|
||||
{ v: 207, f: '-10%'},
|
||||
{ v: 230, f: '230V'},
|
||||
{ v: 253, f: '+10%'}
|
||||
]
|
||||
},
|
||||
legend: { position: 'none' },
|
||||
tooltip: { trigger: 'none'},
|
||||
enableInteractivity: false,
|
||||
};
|
||||
|
||||
// Amperage plot
|
||||
var ap;
|
||||
var aa;
|
||||
var ao = {
|
||||
title: 'Phase current',
|
||||
titleTextStyle: {
|
||||
fontSize: 14
|
||||
},
|
||||
backgroundColor: { fill:'transparent' },
|
||||
bar: { groupWidth: '90%' },
|
||||
vAxis: {
|
||||
minValue: 0,
|
||||
maxValue: 100,
|
||||
ticks: [
|
||||
{ v: 25, f: '25%'},
|
||||
{ v: 50, f: '50%'},
|
||||
{ v: 75, f: '75%'},
|
||||
{ v: 100, f: '100%'}
|
||||
]
|
||||
},
|
||||
legend: { position: 'none' },
|
||||
tooltip: { trigger: 'none'},
|
||||
enableInteractivity: false,
|
||||
};
|
||||
|
||||
// Import plot
|
||||
var ip;
|
||||
var ia;
|
||||
var io = {
|
||||
legend: 'none',
|
||||
backgroundColor: {
|
||||
fill:'transparent',
|
||||
stroke: 'transparent'
|
||||
},
|
||||
pieHole: 0.6,
|
||||
pieSliceText: 'none',
|
||||
pieStartAngle: 216,
|
||||
pieSliceBorderColor: 'transparent',
|
||||
slices: {
|
||||
0: { color: 'green' },
|
||||
1: { color: '#eee' },
|
||||
2: { color: 'transparent' }
|
||||
},
|
||||
legend: { position: 'none' },
|
||||
tooltip: { trigger: 'none'},
|
||||
enableInteractivity: false,
|
||||
chartArea: {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: 'transparent'
|
||||
}
|
||||
};
|
||||
|
||||
// Export plot
|
||||
var xp;
|
||||
var xa;
|
||||
var xo = {
|
||||
legend: 'none',
|
||||
backgroundColor: {
|
||||
fill:'transparent',
|
||||
stroke: 'transparent'
|
||||
},
|
||||
pieHole: 0.6,
|
||||
pieSliceText: 'none',
|
||||
pieStartAngle: 216,
|
||||
pieSliceBorderColor: 'transparent',
|
||||
slices: {
|
||||
0: { color: 'green' },
|
||||
1: { color: '#eee' },
|
||||
2: { color: 'transparent' }
|
||||
},
|
||||
legend: { position: 'none' },
|
||||
tooltip: { trigger: 'none'},
|
||||
enableInteractivity: false,
|
||||
chartArea: {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: 'transparent'
|
||||
}
|
||||
};
|
||||
|
||||
// Temperature plot
|
||||
var td = false; // Disable temperature
|
||||
var tp;
|
||||
var ta;
|
||||
var to = {
|
||||
title: 'Temperature sensors',
|
||||
titleTextStyle: {
|
||||
fontSize: 14
|
||||
},
|
||||
backgroundColor: { fill:'transparent' },
|
||||
bar: { groupWidth: '90%' },
|
||||
legend: { position: 'none' },
|
||||
vAxis: {
|
||||
title: '°C',
|
||||
viewWindowMode: 'maximized'
|
||||
},
|
||||
tooltip: { trigger: 'none'},
|
||||
enableInteractivity: false,
|
||||
};
|
||||
|
||||
$(function() {
|
||||
var meters = $('.plot1');
|
||||
|
||||
if(meters.length > 0) {
|
||||
// Chart
|
||||
google.charts.load('current', {'packages':['corechart']});
|
||||
google.charts.setOnLoadCallback(setupChart);
|
||||
}
|
||||
|
||||
// For mqtt
|
||||
$('#m').on('change', function() {
|
||||
var inputs = $('.mc');
|
||||
inputs.prop('disabled', !$(this).is(':checked'));
|
||||
});
|
||||
|
||||
$('#f').on('change', function() {
|
||||
var val = parseInt($(this).val());
|
||||
if(val == 3) {
|
||||
$('.f3-s').show();
|
||||
} else {
|
||||
$('.f3-s').hide();
|
||||
}
|
||||
});
|
||||
|
||||
$('#s').on('change', function() {
|
||||
var port = $('#p');
|
||||
if($(this).is(':checked')) {
|
||||
if(port.val() == 1883) {
|
||||
port.val(8883);
|
||||
}
|
||||
} else {
|
||||
if(port.val() == 8883) {
|
||||
port.val(1883);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('#m').trigger('change');
|
||||
$('#f').trigger('change');
|
||||
|
||||
// For wifi
|
||||
$('#st').on('change', function() {
|
||||
if($(this).is(':checked')) {
|
||||
$('.sip').prop('disabled', false);
|
||||
} else {
|
||||
$('.sip').prop('disabled', true);
|
||||
}
|
||||
});
|
||||
$('#st').trigger('change');
|
||||
|
||||
// For web
|
||||
$('#as').on('change', function() {
|
||||
var inputs = $('.ac');
|
||||
inputs.prop('disabled', $(this).val() == 0);
|
||||
});
|
||||
|
||||
$('#as').trigger('change');
|
||||
|
||||
// For file upload
|
||||
$('#fileUploadField').on('change',function(){
|
||||
var fileName = $(this).val();
|
||||
$(this).next('.custom-file-label').html(fileName);
|
||||
})
|
||||
$('.upload-form').on('submit', function(i, form) {
|
||||
$('#loading-indicator').show();
|
||||
});
|
||||
|
||||
// For NTP
|
||||
$('#n').on('change', function() {
|
||||
var inputs = $('.nc');
|
||||
inputs.prop('disabled', !$(this).is(':checked'));
|
||||
});
|
||||
$('#n').trigger('change');
|
||||
|
||||
$('.ipo,.epo').on('click', function() {
|
||||
swatt = !swatt;
|
||||
$('.ipo,.epo').html('wait');
|
||||
});
|
||||
|
||||
// Navbar
|
||||
switch(window.location.pathname) {
|
||||
case '/temperature':
|
||||
$('#temp-link').addClass('active');
|
||||
break;
|
||||
case '/price':
|
||||
$('#price-link').addClass('active');
|
||||
break;
|
||||
case '/meter':
|
||||
case '/wifi':
|
||||
case '/mqtt':
|
||||
case '/mqtt-ca':
|
||||
case '/mqtt-cert':
|
||||
case '/mqtt-key':
|
||||
case '/domoticz':
|
||||
case '/web':
|
||||
case '/ntp':
|
||||
case '/entsoe':
|
||||
$('#config-link').addClass('active');
|
||||
break;
|
||||
case '/gpio':
|
||||
case '/debugging':
|
||||
case '/configfile':
|
||||
case '/firmware':
|
||||
$('#firmware-warn').show();
|
||||
case '/reset':
|
||||
$('#system-link').addClass('active');
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for software upgrade
|
||||
var swv = $('#swVersion');
|
||||
var fwl = $('#fwLink');
|
||||
if((meters.length > 0 || fwl.length > 0) && swv.length == 1) {
|
||||
var v = swv.text().substring(1).split('.');
|
||||
var v_major = parseInt(v[0]);
|
||||
var v_minor = parseInt(v[1]);
|
||||
var v_patch = parseInt(v[2]);
|
||||
$.ajax({
|
||||
url: swv.data('url'),
|
||||
dataType: 'json'
|
||||
}).done(function(releases) {
|
||||
var isnew = false;
|
||||
if(/^v\d{1,2}\.\d{1,2}\.\d{1,2}$/.test(swv.text()) && fwl.length == 0) {
|
||||
releases.reverse();
|
||||
var next_patch;
|
||||
var next_minor;
|
||||
var next_major;
|
||||
$.each(releases, function(i, release) {
|
||||
var ver2 = release.tag_name;
|
||||
var v2 = ver2.substring(1).split('.');
|
||||
var v2_major = parseInt(v2[0]);
|
||||
var v2_minor = parseInt(v2[1]);
|
||||
var v2_patch = parseInt(v2[2]);
|
||||
|
||||
if(v2_major == v_major) {
|
||||
if(v2_minor == v_minor) {
|
||||
if(v2_patch > v_patch) {
|
||||
next_patch = release;
|
||||
}
|
||||
} else if(v2_minor == v_minor+1) {
|
||||
next_minor = release;
|
||||
}
|
||||
} else if(v2_major == v_major+1) {
|
||||
if(next_major) {
|
||||
var mv = next_major.tag_name.substring(1).split('.');
|
||||
var mv_major = parseInt(mv[0]);
|
||||
var mv_minor = parseInt(mv[1]);
|
||||
var mv_patch = parseInt(mv[2]);
|
||||
if(v2_minor == mv_minor) {
|
||||
next_major = release;
|
||||
}
|
||||
} else {
|
||||
next_major = release;
|
||||
}
|
||||
}
|
||||
});
|
||||
if(next_minor) {
|
||||
nextVersion = next_minor;
|
||||
isnew = true;
|
||||
} else if(next_major) {
|
||||
nextVersion = next_major;
|
||||
isnew = true;
|
||||
} else if(next_patch) {
|
||||
nextVersion = next_patch;
|
||||
isnew = true;
|
||||
}
|
||||
} else {
|
||||
nextVersion = releases[0];
|
||||
}
|
||||
if(nextVersion) {
|
||||
if(fwl.length > 0) {
|
||||
var chipset = fwl.data('chipset').toLowerCase();
|
||||
$.each(releases, function(i, release) {
|
||||
if(release.tag_name == nextVersion.tag_name) {
|
||||
$.each(release.assets, function(i, asset) {
|
||||
if(asset.name.includes(chipset) && !asset.name.includes("partitions")) {
|
||||
fwl.prop('href', asset.browser_download_url);
|
||||
$('#fwDownload').show();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
if(isnew) {
|
||||
$('#newVersionTag').text(nextVersion.tag_name);
|
||||
$('#newVersionUrl').prop('href', nextVersion.html_url);
|
||||
$('#newVersion').removeClass('d-none');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Temperature
|
||||
var tt = $('#temp-template');
|
||||
if(tt.length > 0) {
|
||||
setTimeout(loadTempSensors, 500);
|
||||
}
|
||||
});
|
||||
|
||||
var resizeTO;
|
||||
$( window ).resize(function() {
|
||||
if(resizeTO) clearTimeout(resizeTO);
|
||||
resizeTO = setTimeout(function() {
|
||||
$(this).trigger('resizeEnd');
|
||||
}, 250);
|
||||
});
|
||||
|
||||
$(window).on('resizeEnd', function() {
|
||||
redraw();
|
||||
});
|
||||
|
||||
var zeropad = function(num) {
|
||||
num = num.toString();
|
||||
while (num.length < 2) num = "0" + num;
|
||||
return num;
|
||||
}
|
||||
|
||||
var setupChart = function() {
|
||||
pp = new google.visualization.ColumnChart(document.getElementById('pp'));
|
||||
ep = new google.visualization.ColumnChart(document.getElementById('ep'));
|
||||
mp = new google.visualization.ColumnChart(document.getElementById('mp'));
|
||||
vp = new google.visualization.ColumnChart(document.getElementById('vp'));
|
||||
ap = new google.visualization.ColumnChart(document.getElementById('ap'));
|
||||
ip = new google.visualization.PieChart(document.getElementById('ip'));
|
||||
xp = new google.visualization.PieChart(document.getElementById('xp'));
|
||||
tp = new google.visualization.ColumnChart(document.getElementById('tp'));
|
||||
fetch();
|
||||
drawDay();
|
||||
drawMonth();
|
||||
};
|
||||
|
||||
var redraw = function() {
|
||||
if(pl != null) {
|
||||
pp.draw(pa, po);
|
||||
}
|
||||
ep.draw(ea, eo);
|
||||
mp.draw(ma, mo);
|
||||
vp.draw(va, vo);
|
||||
ap.draw(aa, ao);
|
||||
ip.draw(ia, io);
|
||||
xp.draw(xa, xo);
|
||||
tp.draw(ta, to);
|
||||
if(tl != null) {
|
||||
tp.draw(ta, to);
|
||||
}
|
||||
};
|
||||
|
||||
var drawPrices = function() {
|
||||
$('#ppc').show();
|
||||
$.ajax({
|
||||
url: '/energyprice.json',
|
||||
timeout: 30000,
|
||||
dataType: 'json',
|
||||
}).done(function(json) {
|
||||
currency = json.currency;
|
||||
data = [['Hour',json.currency + '/kWh', { role: 'style' }, { role: 'annotation' }]];
|
||||
var r = 1;
|
||||
var hour = moment.utc().hours();
|
||||
var offset = moment().utcOffset()/60;
|
||||
var min = 0;
|
||||
var h = 0;
|
||||
var d = json["20"] == null ? 2 : 1;
|
||||
for(var i = hour; i<24; i++) {
|
||||
var val = json[zeropad(h++)];
|
||||
if(val == null) break;
|
||||
data[r++] = [zeropad((i+offset)%24), val, "color: #6f42c1;opacity: 0.9;", val == null ? "" : val.toFixed(d)];
|
||||
Math.min(0, val);
|
||||
};
|
||||
for(var i = 0; i < 24; i++) {
|
||||
var val = json[zeropad(h++)];
|
||||
if(val == null) break;
|
||||
data[r++] = [zeropad((i+offset)%24), val, "color: #6f42c1;opacity: 0.9;", val == null ? "" : val.toFixed(d)];
|
||||
Math.min(0, val);
|
||||
};
|
||||
pa = google.visualization.arrayToDataTable(data);
|
||||
po.vAxis.title = json.currency;
|
||||
if(min == 0)
|
||||
po.vAxis.minValue = 0;
|
||||
pp.draw(pa, po);
|
||||
});
|
||||
}
|
||||
|
||||
var drawDay = function() {
|
||||
$.ajax({
|
||||
url: '/dayplot.json',
|
||||
timeout: 30000,
|
||||
dataType: 'json',
|
||||
}).done(function(json) {
|
||||
data = [['Hour', 'Import', { role: 'style' }, { role: 'annotation' }, 'Export', { role: 'style' }, { role: 'annotation' }]];
|
||||
var r = 1;
|
||||
var hour = moment.utc().hours();
|
||||
var offset = moment().utcOffset()/60;
|
||||
var min = 0;
|
||||
for(var i = hour; i<24; i++) {
|
||||
var imp = json["i"+zeropad(i)];
|
||||
var exp = json["e"+zeropad(i)];
|
||||
data[r++] = [zeropad((i+offset)%24), imp, "opacity: 0.9;", imp == 0 ? "" : imp.toFixed(1), exp == 0 ? 0 : -exp, "opacity: 0.9;", exp == 0 ? "" : -exp.toFixed(1)];
|
||||
min = Math.min(0, -exp);
|
||||
};
|
||||
for(var i = 0; i < hour; i++) {
|
||||
var imp = json["i"+zeropad(i)];
|
||||
var exp = json["e"+zeropad(i)];
|
||||
data[r++] = [zeropad((i+offset)%24), imp, "opacity: 0.9;", imp == 0 ? "" : imp.toFixed(1), exp == 0 ? 0 : -exp, "opacity: 0.9;", exp == 0 ? "" : -exp.toFixed(1)];
|
||||
min = Math.min(0, -exp);
|
||||
};
|
||||
ea = google.visualization.arrayToDataTable(data);
|
||||
if(min == 0)
|
||||
eo.vAxis.minValue = 0;
|
||||
|
||||
ep.draw(ea, eo);
|
||||
|
||||
setTimeout(drawDay, (61-moment().minute())*60000);
|
||||
});
|
||||
};
|
||||
|
||||
var drawMonth = function() {
|
||||
$.ajax({
|
||||
url: '/monthplot.json',
|
||||
timeout: 30000,
|
||||
dataType: 'json',
|
||||
}).done(function(json) {
|
||||
data = [['Hour', 'Import', { role: 'style' }, { role: 'annotation' }, 'Export', { role: 'style' }, { role: 'annotation' }]];
|
||||
var r = 1;
|
||||
var day = moment().date();
|
||||
var eom = moment().subtract(1, 'months').endOf('month').date();
|
||||
var min = 0;
|
||||
for(var i = day; i<=eom; i++) {
|
||||
var imp = json["i"+zeropad(i)];
|
||||
var exp = json["e"+zeropad(i)];
|
||||
data[r++] = [zeropad(i), imp, "opacity: 0.9;", imp == 0 ? "" : imp.toFixed(0), exp == 0 ? 0 : -exp, "opacity: 0.9;", exp == 0 ? "" : -exp.toFixed(0)];
|
||||
min = Math.min(0, -exp);
|
||||
}
|
||||
for(var i = 1; i < day; i++) {
|
||||
var imp = json["i"+zeropad(i)];
|
||||
var exp = json["e"+zeropad(i)];
|
||||
data[r++] = [zeropad(i), imp, "opacity: 0.9;", imp == 0 ? "" : imp.toFixed(0), exp == 0 ? 0 : -exp, "opacity: 0.9;", exp == 0 ? "" : -exp.toFixed(0)];
|
||||
min = Math.min(0, -exp);
|
||||
}
|
||||
ma = google.visualization.arrayToDataTable(data);
|
||||
if(min == 0)
|
||||
mo.vAxis.minValue = 0;
|
||||
mp.draw(ma, mo);
|
||||
|
||||
setTimeout(drawMonth, (24-moment().hour())*60000);
|
||||
});
|
||||
};
|
||||
|
||||
var drawTemperature = function() {
|
||||
if(td) return;
|
||||
|
||||
$.ajax({
|
||||
url: '/temperature.json',
|
||||
timeout: 30000,
|
||||
dataType: 'json',
|
||||
}).done(function(json) {
|
||||
if(json.c > 1) {
|
||||
$('#tpc').show();
|
||||
|
||||
var r = 1;
|
||||
var min = 0;
|
||||
data = [['Sensor','°C', { role: 'style' }, { role: 'annotation' }]];
|
||||
$.each(json.s, function(i, o) {
|
||||
var name = o.n ? o.n : o.a;
|
||||
data[r++] = [name, o.v, "color: #6f42c1;opacity: 0.9;", o.v.toFixed(1)];
|
||||
Math.min(0, o.v);
|
||||
});
|
||||
if(min == 0)
|
||||
to.vAxis.minValue = 0;
|
||||
ta = google.visualization.arrayToDataTable(data);
|
||||
ta.sort("Sensor");
|
||||
tp.draw(ta, to);
|
||||
td = false;
|
||||
} else {
|
||||
td = true;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var setStatus = function(id, sid) {
|
||||
var item = $('#'+id);
|
||||
item.removeClass('d-none');
|
||||
item.removeClass (function (index, className) {
|
||||
return (className.match (/(^|\s)badge-\S+/g) || []).join(' ');
|
||||
});
|
||||
var status;
|
||||
if(sid == 0) {
|
||||
status = "secondary";
|
||||
} else if(sid == 1) {
|
||||
status = "success";
|
||||
} else if(sid == 2) {
|
||||
status = "warning";
|
||||
} else {
|
||||
status = "danger";
|
||||
}
|
||||
item.addClass('badge badge-' + status);
|
||||
};
|
||||
|
||||
var voltcol = function(pct) {
|
||||
if(pct > 85) return '#d90000';
|
||||
else if(pct > 75) return'#e32100';
|
||||
else if(pct > 70) return '#ffb800';
|
||||
else if(pct > 65) return '#dcd800';
|
||||
else if(pct > 35) return '#32d900';
|
||||
else if(pct > 25) return '#dcd800';
|
||||
else if(pct > 20) return '#ffb800';
|
||||
else if(pct > 15) return'#e32100';
|
||||
else return '#d90000';
|
||||
};
|
||||
|
||||
var ampcol = function(pct) {
|
||||
if(pct > 90) return '#d90000';
|
||||
else if(pct > 85) return'#e32100';
|
||||
else if(pct > 80) return '#ffb800';
|
||||
else if(pct > 75) return '#dcd800';
|
||||
else return '#32d900';
|
||||
};
|
||||
|
||||
var retrycount = 0;
|
||||
var interval = 5000;
|
||||
var fetch = function() {
|
||||
$.ajax({
|
||||
url: '/data.json',
|
||||
timeout: 10000,
|
||||
dataType: 'json',
|
||||
}).done(function(json) {
|
||||
retrycount = 0;
|
||||
|
||||
for(var id in json) {
|
||||
var str = json[id];
|
||||
if(typeof str === "object") {
|
||||
continue;
|
||||
}
|
||||
if(isNaN(str)) {
|
||||
$('.j'+id).html(str);
|
||||
} else {
|
||||
var num = parseFloat(str);
|
||||
$('.j'+id).html(num.toFixed(num < 0 ? 0 : num < 10 ? 2 : 1));
|
||||
}
|
||||
$('.r'+id).show();
|
||||
}
|
||||
|
||||
if(window.moment) {
|
||||
$('.ju').html(moment.duration(parseInt(json.u), 'seconds').humanize());
|
||||
}
|
||||
|
||||
ds = parseInt(json.ds);
|
||||
|
||||
var kib = parseInt(json.m)/1000;
|
||||
$('.jm').html(kib.toFixed(1));
|
||||
if(kib > 32) {
|
||||
$('.ssl-capable').removeClass('d-none');
|
||||
}
|
||||
|
||||
setStatus("esp", json.em);
|
||||
setStatus("han", json.hm);
|
||||
setStatus("wifi", json.wm);
|
||||
setStatus("mqtt", json.mm);
|
||||
|
||||
|
||||
if(ip) {
|
||||
var v = parseInt(json.i);
|
||||
var pct = Math.min((v*100)/parseInt(json.im), 100);
|
||||
var append = "W";
|
||||
if(v > 1000 && !swatt) {
|
||||
v = (v/1000).toFixed(1);
|
||||
append = "kW";
|
||||
}
|
||||
$('.ipo').html(v);
|
||||
$('.ipoa').html(append);
|
||||
var arr = [
|
||||
['Slice', 'Value'],
|
||||
['', (pct*2.88)],
|
||||
['', ((100-pct)*2.88)],
|
||||
['', 72],
|
||||
];
|
||||
io.slices[0].color = ampcol(pct);
|
||||
ia = google.visualization.arrayToDataTable(arr);
|
||||
ip.draw(ia, io);
|
||||
}
|
||||
|
||||
var om = parseInt(json.om);
|
||||
|
||||
if(om > 0) {
|
||||
$('.rex').show();
|
||||
$('.rim').hide();
|
||||
if(xp) {
|
||||
var v = parseInt(json.e);
|
||||
var pct = Math.min((v*100)/(om*1000), 100);
|
||||
var append = "W";
|
||||
if(v > 1000 && !swatt) {
|
||||
v = (v/1000).toFixed(1);
|
||||
append = "kW";
|
||||
}
|
||||
$('.epo').html(v);
|
||||
$('.epoa').html(append);
|
||||
var arr = [
|
||||
['Slice', 'Value'],
|
||||
['', (pct*2.88)],
|
||||
['', ((100-pct)*2.88)],
|
||||
['', 72],
|
||||
];
|
||||
xo.slices[0].color = ampcol(pct);
|
||||
xa = google.visualization.arrayToDataTable(arr);
|
||||
xp.draw(xa, xo);
|
||||
}
|
||||
} else {
|
||||
$('.rex').hide();
|
||||
$('.rim').show();
|
||||
}
|
||||
|
||||
if(vp) {
|
||||
switch(ds) {
|
||||
case 1:
|
||||
vo.title = 'Voltage between';
|
||||
break;
|
||||
case 2:
|
||||
vo.title = 'Phase voltage';
|
||||
break;
|
||||
}
|
||||
var c = 0;
|
||||
var t = 0;
|
||||
var r = 1;
|
||||
var arr = [['Phase', 'Voltage', { role: 'style' }, { role: 'annotation' }]];
|
||||
if(json.u1) {
|
||||
var u1 = parseFloat(json.u1);
|
||||
t += u1;
|
||||
c++;
|
||||
var pct = Math.min(Math.max(parseFloat(json.u1)-195.5, 1)*100/69, 100);
|
||||
arr[r++] = [ds == 1 ? 'L1-L2' : 'L1', u1, "color: " + voltcol(pct) + ";opacity: 0.9;", u1 + "V"];
|
||||
}
|
||||
if(json.u2) {
|
||||
var u2 = parseFloat(json.u2);
|
||||
t += u2;
|
||||
c++;
|
||||
var pct = Math.min(Math.max(parseFloat(json.u2)-195.5, 1)*100/69, 100);
|
||||
arr[r++] = [ds == 1 ? 'L1-L3' : 'L2', u2, "color: " + voltcol(pct) + ";opacity: 0.9;", u2 + "V"];
|
||||
}
|
||||
if(json.u3) {
|
||||
var u3 = parseFloat(json.u3);
|
||||
t += u3;
|
||||
c++;
|
||||
var pct = Math.min(Math.max(parseFloat(json.u3)-195.5, 1)*100/69, 100);
|
||||
arr[r++] = [ds == 1 ? 'L2-L3' : 'L3', u3, "color: " + voltcol(pct) + ";opacity: 0.9;", u3 + "V"];
|
||||
}
|
||||
v = t/c;
|
||||
if(v > 0) {
|
||||
va = google.visualization.arrayToDataTable(arr);
|
||||
vp.draw(va, vo);
|
||||
}
|
||||
}
|
||||
|
||||
if(ap && json.mf) {
|
||||
switch(ds) {
|
||||
case 1:
|
||||
ao.title = 'Line current';
|
||||
break;
|
||||
case 2:
|
||||
ao.title = 'Phase current';
|
||||
break;
|
||||
}
|
||||
var dA = false;
|
||||
var r = 1;
|
||||
var arr = [['Phase', 'Amperage', { role: 'style' }, { role: 'annotation' }]];
|
||||
if(json.i1 || json.u1) {
|
||||
var i1 = parseFloat(json.i1);
|
||||
dA = true;
|
||||
var pct = Math.min((parseFloat(json.i1)/parseInt(json.mf))*100, 100);
|
||||
arr[r++] = ['L1', pct, "color: " + ampcol(pct) + ";opacity: 0.9;", i1 + "A"];
|
||||
}
|
||||
if(json.i2 || json.u2) {
|
||||
var i2 = parseFloat(json.i2);
|
||||
dA = true;
|
||||
var pct = Math.min((parseFloat(json.i2)/parseInt(json.mf))*100, 100);
|
||||
arr[r++] = ['L2', pct, "color: " + ampcol(pct) + ";opacity: 0.9;", i2 + "A"];
|
||||
}
|
||||
if(json.i3 || json.u3) {
|
||||
var i3 = parseFloat(json.i3);
|
||||
dA = true;
|
||||
var pct = Math.min((parseFloat(json.i3)/parseInt(json.mf))*100, 100);
|
||||
arr[r++] = ['L3', pct, "color: " + ampcol(pct) + ";opacity: 0.9;", i3 + "A"];
|
||||
}
|
||||
if(dA) {
|
||||
aa = google.visualization.arrayToDataTable(arr);
|
||||
ap.draw(aa, ao);
|
||||
}
|
||||
}
|
||||
|
||||
if(json.ea) {
|
||||
$('#each').html(json.ea.h.u.toFixed(2));
|
||||
$('#eachc').html(json.ea.h.c.toFixed(2));
|
||||
$('#eacd').html(json.ea.d.u.toFixed(1));
|
||||
$('#eacdc').html(json.ea.d.c.toFixed(1));
|
||||
$('#eacm').html(json.ea.m.u.toFixed(0));
|
||||
$('#eacmc').html(json.ea.m.c.toFixed(0));
|
||||
$('#eax').html(json.ea.x.toFixed(1));
|
||||
$('#eat').html(json.ea.t.toFixed(0));
|
||||
$('.cr').html(currency);
|
||||
if(currency) {
|
||||
$('.sp').show();
|
||||
}
|
||||
if(om > 0) {
|
||||
$('.se').removeClass('d-none');
|
||||
$('#eache').html(json.ea.h.p.toFixed(2));
|
||||
$('#eacde').html(json.ea.d.p.toFixed(1));
|
||||
$('#eacme').html(json.ea.m.p.toFixed(0));
|
||||
}
|
||||
}
|
||||
|
||||
if(json.me) {
|
||||
$('.me').addClass('d-none');
|
||||
$('.me'+json.me).removeClass('d-none');
|
||||
$('#ml').html(json.me);
|
||||
}
|
||||
|
||||
var temp = parseFloat(json.t);
|
||||
if(temp == -127.0) {
|
||||
$('.jt').html("N/A");
|
||||
$('.rt').hide();
|
||||
} else {
|
||||
$('.rt').show();
|
||||
if(tl != temp) {
|
||||
drawTemperature();
|
||||
}
|
||||
tl = temp;
|
||||
}
|
||||
|
||||
var vcc = parseFloat(json.v);
|
||||
if(vcc > 0.0) {
|
||||
$('.rv').show();
|
||||
} else {
|
||||
$('.rv').hide();
|
||||
}
|
||||
|
||||
var unixtime = moment().unix();
|
||||
var ts = parseInt(json.c);
|
||||
if(Math.abs(unixtime-ts) < 300) {
|
||||
$('.jc').html(moment(ts * 1000).format('DD. MMM HH:mm'));
|
||||
$('.jc').removeClass('text-danger');
|
||||
} else {
|
||||
$('.jc').html(moment(ts * 1000).format('DD.MM.YYYY HH:mm'));
|
||||
$('.jc').addClass('text-danger');
|
||||
}
|
||||
|
||||
var mt = parseInt(json.mt);
|
||||
switch(mt) {
|
||||
case 1:
|
||||
$('.jmt').html("Aidon");
|
||||
break;
|
||||
case 2:
|
||||
$('.jmt').html("Kaifa");
|
||||
break;
|
||||
case 3:
|
||||
$('.jmt').html("Kamstrup");
|
||||
break;
|
||||
case 8:
|
||||
$('.jmt').html("Iskra");
|
||||
break;
|
||||
case 9:
|
||||
$('.jmt').html("Landis+Gyr");
|
||||
break;
|
||||
case 10:
|
||||
$('.jmt').html("Sagemcom");
|
||||
break;
|
||||
default:
|
||||
$('.jmt').html("");
|
||||
}
|
||||
|
||||
setTimeout(fetch, interval);
|
||||
|
||||
var price = parseFloat(json.p);
|
||||
if(price && price != pl) {
|
||||
pl = price;
|
||||
drawPrices();
|
||||
}
|
||||
}).fail(function(x, text, error) {
|
||||
if(retrycount > 2) {
|
||||
console.log("Failed request");
|
||||
console.log(text);
|
||||
console.log(error);
|
||||
setTimeout(fetch, interval*4);
|
||||
|
||||
setStatus("mqtt", 0);
|
||||
setStatus("wifi", 0);
|
||||
setStatus("han", 0);
|
||||
setStatus("esp", 3);
|
||||
} else {
|
||||
setTimeout(fetch, interval);
|
||||
}
|
||||
retrycount++;
|
||||
});
|
||||
}
|
||||
|
||||
var upgrade = function() {
|
||||
if(nextVersion) {
|
||||
if(confirm("WARNING: If you have a M-BUS powered device (Pow-U), please keep USB power connected while upgrading.\n\nAre you sure you want to perform upgrade to " + nextVersion.tag_name + "?")) {
|
||||
$('#loading-indicator').show();
|
||||
window.location.href="/upgrade?version=" + nextVersion.tag_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var loadTempSensors = function() {
|
||||
$.ajax({
|
||||
url: '/temperature.json',
|
||||
timeout: 10000,
|
||||
dataType: 'json',
|
||||
}).done(function(json) {
|
||||
if($('#loading').length > 0) {
|
||||
$('#loading').hide();
|
||||
|
||||
var list = $('#sensors');
|
||||
if(json.c > 0) {
|
||||
list.empty();
|
||||
var temp = $.trim($('#temp-template').html());
|
||||
$.each(json.s, function(i, o) {
|
||||
var item = temp.replace(/{{index}}/ig, o.i);
|
||||
var item = item.replace(/{{address}}/ig, o.a);
|
||||
var item = item.replace(/{{name}}/ig, o.n);
|
||||
var item = item.replace(/{{value}}/ig, o.v > -50 && o.v < 127 ? o.v : "N/A");
|
||||
var item = item.replace(/{{common}}/ig, o.c ? "checked" : "");
|
||||
list.append(item);
|
||||
});
|
||||
} else {
|
||||
$('#notemp').show();
|
||||
}
|
||||
} else {
|
||||
$.each(json.s, function(i, o) {
|
||||
$('#temp-'+o.i).html(o.v > -50 && o.v < 127 ? o.v : "N/A");
|
||||
});
|
||||
}
|
||||
setTimeout(loadTempSensors, 10000);
|
||||
}).fail(function() {
|
||||
setTimeout(loadTempSensors, 60000);
|
||||
$('#loading').hide();
|
||||
$('#error').show();
|
||||
});
|
||||
}
|
||||
@@ -1,432 +0,0 @@
|
||||
/* Ripped necessary style from bootstrap 4.4.1 to make the page look good without internet access. Meant to be overridden by CSS from CDN */
|
||||
:root {
|
||||
--blue: #007bff;
|
||||
--indigo: #6610f2;
|
||||
--purple: #6f42c1;
|
||||
--pink: #e83e8c;
|
||||
--red: #dc3545;
|
||||
--orange: #fd7e14;
|
||||
--yellow: #ffc107;
|
||||
--green: #28a745;
|
||||
--teal: #20c997;
|
||||
--cyan: #17a2b8;
|
||||
--white: #fff;
|
||||
--gray: #6c757d;
|
||||
--gray-dark: #343a40;
|
||||
--primary: #007bff;
|
||||
--secondary: #6c757d;
|
||||
--success: #28a745;
|
||||
--info: #17a2b8;
|
||||
--warning: #ffc107;
|
||||
--danger: #dc3545;
|
||||
--light: #f8f9fa;
|
||||
--dark: #343a40;
|
||||
--breakpoint-xs: 0;
|
||||
--breakpoint-sm: 576px;
|
||||
--breakpoint-md: 768px;
|
||||
--breakpoint-lg: 992px;
|
||||
--breakpoint-xl: 1200px;
|
||||
--font-family-sans-serif: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
|
||||
--font-family-monospace: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
|
||||
}
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
line-height: 1.15;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
color: -internal-root-color;
|
||||
}
|
||||
body {
|
||||
display: block;
|
||||
margin: 8px;
|
||||
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #212529;
|
||||
text-align: left;
|
||||
background-color: #fff;
|
||||
}
|
||||
.bg-white {
|
||||
background-color: #fff!important;
|
||||
}
|
||||
.bg-light {
|
||||
background-color: #f8f9fa!important;
|
||||
}
|
||||
.bg-purple {
|
||||
background-color: var(--purple);
|
||||
}
|
||||
.mt-2, .my-2 {
|
||||
margin-top: .5rem!important;
|
||||
}
|
||||
.p-3 {
|
||||
padding: 1rem!important;
|
||||
}
|
||||
.mb-3, .my-3 {
|
||||
margin-bottom: 1rem!important;
|
||||
}
|
||||
.mt-3, .my-3 {
|
||||
margin-top: 1rem!important;
|
||||
}
|
||||
.mb-4, .my-4 {
|
||||
margin-bottom: 1.5rem!important;
|
||||
}
|
||||
.shadow {
|
||||
box-shadow: 0 .5rem 1rem rgba(0,0,0,.15)!important;
|
||||
}
|
||||
.rounded {
|
||||
border-radius: .25rem!important;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
|
||||
display: block;
|
||||
}
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
.text-right {
|
||||
text-align: right!important;
|
||||
}
|
||||
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
|
||||
margin-bottom: .5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
.h5, h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
.h6, h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
.row {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
margin-right: -15px;
|
||||
margin-left: -15px;
|
||||
}
|
||||
.col, .col-1, .col-10, .col-11, .col-12, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-auto, .col-lg, .col-lg-1, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-auto, .col-md, .col-md-1, .col-md-10, .col-md-11, .col-md-12, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-auto, .col-sm, .col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-auto, .col-xl, .col-xl-1, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-auto {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
.col-6 {
|
||||
-ms-flex: 0 0 50%;
|
||||
flex: 0 0 50%;
|
||||
max-width: 50%;
|
||||
}
|
||||
.d-none {
|
||||
display: none!important;
|
||||
}
|
||||
.flex-row {
|
||||
-ms-flex-direction: row!important;
|
||||
flex-direction: row!important;
|
||||
}
|
||||
|
||||
.flex-column {
|
||||
-ms-flex-direction: column!important;
|
||||
flex-direction: column!important;
|
||||
}
|
||||
a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
.btn {
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
color: #212529;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
background-color: transparent;
|
||||
border: 1px solid transparent;
|
||||
padding: .375rem .75rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
border-radius: .25rem;
|
||||
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
||||
}
|
||||
.btn-group-sm>.btn, .btn-sm {
|
||||
padding: .25rem .5rem;
|
||||
font-size: .875rem;
|
||||
line-height: 1.5;
|
||||
border-radius: .2rem;
|
||||
}
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #007bff;
|
||||
border-color: #007bff;
|
||||
}
|
||||
.navbar {
|
||||
position: relative;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
padding: .5rem 1rem;
|
||||
}
|
||||
.navbar-dark .navbar-brand {
|
||||
color: #fff;
|
||||
}
|
||||
.navbar-expand {
|
||||
-ms-flex-flow: row nowrap;
|
||||
flex-flow: row nowrap;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.navbar-nav {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
padding-left: 0;
|
||||
margin-bottom: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.navbar-expand .navbar-nav {
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
.navbar-brand {
|
||||
display: inline-block;
|
||||
padding-top: .3125rem;
|
||||
padding-bottom: .3125rem;
|
||||
margin-right: 1rem;
|
||||
font-size: 1.25rem;
|
||||
line-height: inherit;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.navbar-dark .navbar-nav .nav-link {
|
||||
color: rgba(255,255,255,.5);
|
||||
}
|
||||
.navbar-expand .navbar-nav .nav-link {
|
||||
padding-right: .5rem;
|
||||
padding-left: .5rem;
|
||||
}
|
||||
.navbar-nav .nav-link {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
.nav-link {
|
||||
display: block;
|
||||
padding: .5rem 1rem;
|
||||
}
|
||||
.navbar-dark .navbar-nav .active>.nav-link, .navbar-dark .navbar-nav .nav-link.active, .navbar-dark .navbar-nav .nav-link.show, .navbar-dark .navbar-nav .show>.nav-link {
|
||||
color: #fff;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.form-control {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: calc(1.5em + .75rem + 2px);
|
||||
padding: .375rem .75rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
background-color: #fff;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: .25rem;
|
||||
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
||||
}
|
||||
.form-control:disabled, .form-control[readonly] {
|
||||
background-color: #e9ecef;
|
||||
opacity: 1;
|
||||
}
|
||||
input:not([type="image" i]) {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
button, input {
|
||||
overflow: visible;
|
||||
}
|
||||
button, input, optgroup, select, textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
input[type="radio"], input[type="checkbox"] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
.input-group {
|
||||
position: relative;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
-ms-flex-align: stretch;
|
||||
align-items: stretch;
|
||||
width: 100%;
|
||||
}
|
||||
.input-group-append {
|
||||
margin-left: -1px;
|
||||
}
|
||||
.input-group-prepend {
|
||||
margin-right: -1px;
|
||||
}
|
||||
.input-group-append, .input-group-prepend {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
.input-group>.input-group-append>.btn, .input-group>.input-group-append>.input-group-text, .input-group>.input-group-prepend:first-child>.btn:not(:first-child), .input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child), .input-group>.input-group-prepend:not(:first-child)>.btn, .input-group>.input-group-prepend:not(:first-child)>.input-group-text {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle), .input-group>.input-group-append:last-child>.input-group-text:not(:last-child), .input-group>.input-group-append:not(:last-child)>.btn, .input-group>.input-group-append:not(:last-child)>.input-group-text, .input-group>.input-group-prepend>.btn, .input-group>.input-group-prepend>.input-group-text {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
.input-group>.custom-select:not(:first-child), .input-group>.form-control:not(:first-child) {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
.input-group-text {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
padding: .375rem .75rem;
|
||||
margin-bottom: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
background-color: #e9ecef;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: .25rem;
|
||||
}
|
||||
.input-group>.custom-select:not(:last-child), .input-group>.form-control:not(:last-child) {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
.input-group>.custom-file, .input-group>.custom-select, .input-group>.form-control, .input-group>.form-control-plaintext {
|
||||
position: relative;
|
||||
-ms-flex: 1 1 0%;
|
||||
flex: 1 1 0%;
|
||||
min-width: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
hr {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 0;
|
||||
border-top: 1px solid rgba(0,0,0,.1);
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
ul {
|
||||
display: block;
|
||||
list-style-type: disc;
|
||||
margin-block-start: 1em;
|
||||
margin-block-end: 1em;
|
||||
margin-inline-start: 0px;
|
||||
margin-inline-end: 0px;
|
||||
padding-inline-start: 40px;
|
||||
}
|
||||
li {
|
||||
display: list-item;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
dl, ol, ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.container, .container-sm {
|
||||
max-width: 540px;
|
||||
}
|
||||
.col-sm-6 {
|
||||
-ms-flex: 0 0 50%;
|
||||
flex: 0 0 50%;
|
||||
max-width: 50%;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container, .container-md, .container-sm {
|
||||
max-width: 720px;
|
||||
}
|
||||
.col-md-3 {
|
||||
-ms-flex: 0 0 25%;
|
||||
flex: 0 0 25%;
|
||||
max-width: 25%;
|
||||
}
|
||||
.col-md-6 {
|
||||
-ms-flex: 0 0 50%;
|
||||
flex: 0 0 50%;
|
||||
max-width: 50%;
|
||||
}
|
||||
.d-md-flex {
|
||||
display: -ms-flexbox!important;
|
||||
display: flex!important;
|
||||
}
|
||||
.flex-md-row {
|
||||
-ms-flex-direction: row!important;
|
||||
flex-direction: row!important;
|
||||
}
|
||||
.ml-md-auto, .mx-md-auto {
|
||||
margin-left: auto!important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.container, .container-lg, .container-md, .container-sm {
|
||||
max-width: 960px;
|
||||
}
|
||||
.col-lg-4 {
|
||||
-ms-flex: 0 0 33.333333%;
|
||||
flex: 0 0 33.333333%;
|
||||
max-width: 33.333333%;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.container, .container-lg, .container-md, .container-sm, .container-xl {
|
||||
max-width: 1140px;
|
||||
}
|
||||
.col-xl-2 {
|
||||
-ms-flex: 0 0 16.666667%;
|
||||
flex: 0 0 16.666667%;
|
||||
max-width: 16.666667%;
|
||||
}
|
||||
.col-xl-3 {
|
||||
-ms-flex: 0 0 25%;
|
||||
flex: 0 0 25%;
|
||||
max-width: 25%;
|
||||
}
|
||||
.col-xl-4 {
|
||||
-ms-flex: 0 0 33.333333%;
|
||||
flex: 0 0 33.333333%;
|
||||
max-width: 33.333333%;
|
||||
}
|
||||
}
|
||||
|
||||
*, ::after, ::before {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
<form method="get" action="/configfile.cfg">
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<h6>Download configuration</h6>
|
||||
<div class="row">
|
||||
<div class="col-xl-2 col-md-3 col-sm-6">
|
||||
<label><input type="checkbox" name="iw" value="true" checked/> WiFi</label>
|
||||
</div>
|
||||
<div class="col-xl-2 col-md-3 col-sm-6">
|
||||
<label><input type="checkbox" name="im" value="true" checked/> MQTT</label>
|
||||
</div>
|
||||
<div class="col-xl-2 col-md-3 col-sm-6">
|
||||
<label><input type="checkbox" name="ie" value="true" checked/> Web</label>
|
||||
</div>
|
||||
<div class="col-xl-2 col-md-3 col-sm-6">
|
||||
<label><input type="checkbox" name="it" value="true" checked/> Meter</label>
|
||||
</div>
|
||||
<div class="col-xl-2 col-md-3 col-sm-6">
|
||||
<label><input type="checkbox" name="ih" value="true" checked/> Thresholds</label>
|
||||
</div>
|
||||
<div class="col-xl-2 col-md-3 col-sm-6">
|
||||
<label><input type="checkbox" name="ig" value="true" checked/> GPIO</label>
|
||||
</div>
|
||||
<div class="col-xl-2 col-md-3 col-sm-6">
|
||||
<label><input type="checkbox" name="id" value="true" checked/> Domoticz</label>
|
||||
</div>
|
||||
<div class="col-xl-2 col-md-3 col-sm-6">
|
||||
<label><input type="checkbox" name="in" value="true" checked/> NTP</label>
|
||||
</div>
|
||||
<div class="col-xl-2 col-md-3 col-sm-6">
|
||||
<label><input type="checkbox" name="is" value="true" checked/> ENTSO-E</label>
|
||||
</div>
|
||||
<div class="col-xl-6 col-md-8">
|
||||
<label><input type="checkbox" name="ic" value="true"/> Include Secrets (SSID, PSK, passwords and tokens)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 text-right">
|
||||
<button class="btn btn-primary">Download</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<form method="post" enctype="multipart/form-data" class="upload-form">
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<h6>Upload configuration</h6>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Upload</span>
|
||||
</div>
|
||||
<div class="custom-file">
|
||||
<input name="file" type="file" class="custom-file-input" id="fileUploadField">
|
||||
<label class="custom-file-label" for="fileUploadField">Choose file</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 text-right">
|
||||
<button class="btn btn-primary">Upload</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="/" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,60 +0,0 @@
|
||||
{
|
||||
"im" : %d,
|
||||
"om" : %d,
|
||||
"mf" : %d,
|
||||
"i" : %d,
|
||||
"e" : %d,
|
||||
"ri" : %d,
|
||||
"re" : %d,
|
||||
"ic" : %.3f,
|
||||
"ec" : %.3f,
|
||||
"ric" : %.3f,
|
||||
"rec" : %.3f,
|
||||
"u1" : %.2f,
|
||||
"u2" : %.2f,
|
||||
"u3" : %.2f,
|
||||
"i1" : %.2f,
|
||||
"i2" : %.2f,
|
||||
"i3" : %.2f,
|
||||
"f" : %.2f,
|
||||
"f1" : %.2f,
|
||||
"f2" : %.2f,
|
||||
"f3" : %.2f,
|
||||
"v" : %.3f,
|
||||
"r" : %d,
|
||||
"t" : %.2f,
|
||||
"u" : %u,
|
||||
"m" : %u,
|
||||
"em" : %d,
|
||||
"hm" : %d,
|
||||
"wm" : %d,
|
||||
"mm" : %d,
|
||||
"me" : %d,
|
||||
"p" : %s,
|
||||
"mt" : %d,
|
||||
"ds" : %d,
|
||||
"ea" : {
|
||||
"x" : %.1f,
|
||||
"p" : [ %s ],
|
||||
"t" : %d,
|
||||
"h" : {
|
||||
"u" : %.2f,
|
||||
"c" : %.2f,
|
||||
"p" : %.2f,
|
||||
"i" : %.2f
|
||||
},
|
||||
"d" : {
|
||||
"u" : %.2f,
|
||||
"c" : %.2f,
|
||||
"p" : %.2f,
|
||||
"i" : %.2f
|
||||
},
|
||||
"m" : {
|
||||
"u" : %.2f,
|
||||
"c" : %.2f,
|
||||
"p" : %.2f,
|
||||
"i" : %.2f
|
||||
}
|
||||
},
|
||||
"c" : %u
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
{
|
||||
"i00" : %.2f,
|
||||
"i01" : %.2f,
|
||||
"i02" : %.2f,
|
||||
"i03" : %.2f,
|
||||
"i04" : %.2f,
|
||||
"i05" : %.2f,
|
||||
"i06" : %.2f,
|
||||
"i07" : %.2f,
|
||||
"i08" : %.2f,
|
||||
"i09" : %.2f,
|
||||
"i10" : %.2f,
|
||||
"i11" : %.2f,
|
||||
"i12" : %.2f,
|
||||
"i13" : %.2f,
|
||||
"i14" : %.2f,
|
||||
"i15" : %.2f,
|
||||
"i16" : %.2f,
|
||||
"i17" : %.2f,
|
||||
"i18" : %.2f,
|
||||
"i19" : %.2f,
|
||||
"i20" : %.2f,
|
||||
"i21" : %.2f,
|
||||
"i22" : %.2f,
|
||||
"i23" : %.2f,
|
||||
"e00" : %.2f,
|
||||
"e01" : %.2f,
|
||||
"e02" : %.2f,
|
||||
"e03" : %.2f,
|
||||
"e04" : %.2f,
|
||||
"e05" : %.2f,
|
||||
"e06" : %.2f,
|
||||
"e07" : %.2f,
|
||||
"e08" : %.2f,
|
||||
"e09" : %.2f,
|
||||
"e10" : %.2f,
|
||||
"e11" : %.2f,
|
||||
"e12" : %.2f,
|
||||
"e13" : %.2f,
|
||||
"e14" : %.2f,
|
||||
"e15" : %.2f,
|
||||
"e16" : %.2f,
|
||||
"e17" : %.2f,
|
||||
"e18" : %.2f,
|
||||
"e19" : %.2f,
|
||||
"e20" : %.2f,
|
||||
"e21" : %.2f,
|
||||
"e22" : %.2f,
|
||||
"e23" : %.2f
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
<div class="alert alert-danger">
|
||||
!!WARNING!!<br/>
|
||||
Telnet debugging is not considered safe and should be switched off when not in use.<br/>
|
||||
<br/>
|
||||
!!WARNING!!<br/>
|
||||
Enabling debugging can cause sudden reboots. Do not leave this on unless you are debugging!
|
||||
</div>
|
||||
<form method="post" action="/save">
|
||||
<input type="hidden" name="debugConfig" value="true"/>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<h6>Debugging</h6>
|
||||
<div class="row">
|
||||
<div class="col-xl-2 col-md-3">
|
||||
<label><input type="checkbox" name="debugTelnet" value="true" %s/> Telnet debugger</label>
|
||||
</div>
|
||||
<div class="col-xl-2 col-md-3">
|
||||
<label><input type="checkbox" name="debugSerial" value="true" %s/> Serial debugger</label>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-4">
|
||||
<div class="row form-group">
|
||||
<label class="col-6">Debug level</label>
|
||||
<div class="col-6">
|
||||
<select class="form-control form-control-sm" name="debugLevel">
|
||||
<option value="1" %s>Verbose</option>
|
||||
<option value="2" %s>Debug</option>
|
||||
<option value="3" %s>Info</option>
|
||||
<option value="4" %s>Warning</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="/" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,14 +0,0 @@
|
||||
<form method="post">
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<div class="alert alert-warning">Are you sure you want to delete this file?</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="javascript:history.back();" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-danger">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,48 +0,0 @@
|
||||
<form method="post" action="/save">
|
||||
<input type="hidden" name="dc" value="true"/>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<div class="d-flex flex-row flex-wrap">
|
||||
<div class="m-2 input-group input-group-sm" style="width: 200px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Electricity IDX</span>
|
||||
</div>
|
||||
<input type="number" class="form-control" name="elidx" value="%d" min="0" max="65535"/>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 240px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Current (3 Phase) IDX</span>
|
||||
</div>
|
||||
<input type="number" class="form-control" name="cl1idx" value="%d" min="0" max="65535"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-row flex-wrap">
|
||||
<div class="m-2 input-group input-group-sm" style="width: 200px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Voltage L1 IDX</span>
|
||||
</div>
|
||||
<input type="number" class="form-control" name="vl1idx" value="%d" min="0" max="65535"/>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 200px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Voltage L2 IDX</span>
|
||||
</div>
|
||||
<input type="number" class="form-control" name="vl2idx" value="%d" min="0" max="65535"/>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 200px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Voltage L3 IDX</span>
|
||||
</div>
|
||||
<input type="number" class="form-control" name="vl3idx" value="%d" min="0" max="65535"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="/mqtt" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"currency" : "%s",
|
||||
"00" : %s,
|
||||
"01" : %s,
|
||||
"02" : %s,
|
||||
"03" : %s,
|
||||
"04" : %s,
|
||||
"05" : %s,
|
||||
"06" : %s,
|
||||
"07" : %s,
|
||||
"08" : %s,
|
||||
"09" : %s,
|
||||
"10" : %s,
|
||||
"11" : %s,
|
||||
"12" : %s,
|
||||
"13" : %s,
|
||||
"14" : %s,
|
||||
"15" : %s,
|
||||
"16" : %s,
|
||||
"17" : %s,
|
||||
"18" : %s,
|
||||
"19" : %s,
|
||||
"20" : %s,
|
||||
"21" : %s,
|
||||
"22" : %s,
|
||||
"23" : %s,
|
||||
"24" : %s,
|
||||
"25" : %s,
|
||||
"26" : %s,
|
||||
"27" : %s,
|
||||
"28" : %s,
|
||||
"29" : %s,
|
||||
"30" : %s,
|
||||
"31" : %s,
|
||||
"32" : %s,
|
||||
"33" : %s,
|
||||
"34" : %s,
|
||||
"35" : %s
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
<div class="alert alert-danger">
|
||||
WARNING: Units powered by M-BUS (Pow-U) must be connected to an external power supply during firmware upload. Failure to do so may cause power-down during upload resulting in non-functioning unit.
|
||||
</div>
|
||||
<div class="alert alert-warning">
|
||||
Your board is using {chipset} chipset. Only upload firmware designed for this chipset. Failure to do so may result in non-functioning unit.
|
||||
<span id="fwDownload" style="display: none;"><br/>Download latest firmware file <a id="fwLink" href="#" data-chipset="{chipset}">here</a></span>
|
||||
</div>
|
||||
<div class="alert alert-warning">
|
||||
When using URL, only a valid ESP OTA server response will be accepted.
|
||||
</div>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" class="upload-form">
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Upload file</span>
|
||||
</div>
|
||||
<div class="custom-file">
|
||||
<input name="file" type="file" class="custom-file-input" id="fileUploadField">
|
||||
<label class="custom-file-label" for="fileUploadField">Choose file</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">or</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Use URL</span>
|
||||
</div>
|
||||
<input type="text" name="url" class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="javascript:history.back();" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-primary">Upgrade firmware</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,19 +0,0 @@
|
||||
<div id="newVersion" class="alert alert-info d-none">New version <span id="newVersionTag"></span>!
|
||||
<a id="newVersionUrl" href="#" target="_blank">view</a> or <a href="javascript:upgrade();">click here to upgrade</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div id="loading-indicator" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: #dddddd99; z-index: 999999; padding-top: 20%; display: none;" class="text-center">
|
||||
<div class="spinner-border text-primary" role="status" style="width: 5rem; height: 5rem;">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js" integrity="sha512-qTXRIMyZIFb8iQcfjXWCO8+M5Tbc38Qi5WzdPOYZHIlZpzBHG3L3by84BBBOiRGiEb7KKtAOAs5qYdUiZiQNNQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js" integrity="sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13" crossorigin="anonymous"></script>
|
||||
<script src="https://www.gstatic.com/charts/loader.js"></script>
|
||||
<script src="application-${version}.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?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 512 499.36" focusable="false">
|
||||
<title>GitHub</title>
|
||||
<path d="M256 0C114.64 0 0 114.61 0 256c0 113.09 73.34 209 175.08 242.9 12.8 2.35 17.47-5.56 17.47-12.34 0-6.08-.22-22.18-.35-43.54-71.2 15.49-86.2-34.34-86.2-34.34-11.64-29.57-28.42-37.45-28.42-37.45-23.27-15.84 1.73-15.55 1.73-15.55 25.69 1.81 39.21 26.38 39.21 26.38 22.84 39.12 59.92 27.82 74.5 21.27 2.33-16.54 8.94-27.82 16.25-34.22-56.84-6.43-116.6-28.43-116.6-126.49 0-27.95 10-50.8 26.35-68.69-2.63-6.48-11.42-32.5 2.51-67.75 0 0 21.49-6.88 70.4 26.24a242.65 242.65 0 0 1 128.18 0c48.87-33.13 70.33-26.24 70.33-26.24 14 35.25 5.18 61.27 2.55 67.75 16.41 17.9 26.31 40.75 26.31 68.69 0 98.35-59.85 120-116.88 126.32 9.19 7.9 17.38 23.53 17.38 47.41 0 34.22-.31 61.83-.31 70.23 0 6.85 4.61 14.81 17.6 12.31C438.72 464.97 512 369.08 512 256.02 512 114.62 397.37 0 256 0z" fill="#f8f9fa" fill-rule="evenodd"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,110 +0,0 @@
|
||||
<div class="alert alert-warning">!!WARNING!!<br/>Do not change anything here unless you know exactly what you are doing! Changing things here could cause the device to stop responding</div>
|
||||
<form method="post" action="/save">
|
||||
<input type="hidden" name="gc" value="true"/>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<h6>GPIO settings</h6>
|
||||
<div class="d-flex flex-row flex-wrap">
|
||||
<div class="m-2 input-group input-group-sm" style="width: 150px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">HAN</span>
|
||||
</div>
|
||||
<select name="h" class="form-control">
|
||||
${h}
|
||||
</select>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 150px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">LED</span>
|
||||
</div>
|
||||
<input name="l" type="number" min="2" max="${g}" class="form-control" value="${l}"/>
|
||||
<div class="input-group-append" title="Inverted">
|
||||
<label class="input-group-text">
|
||||
<input type="checkbox" name="i" value="true" ${i}/> inv
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 250px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">RGB</span>
|
||||
</div>
|
||||
<input name="r" type="number" min="2" max="${g}" class="form-control" value="${r}"/>
|
||||
<input name="e" type="number" min="2" max="${g}" class="form-control" value="${e}"/>
|
||||
<input name="b" type="number" min="2" max="${g}" class="form-control" value="${b}"/>
|
||||
<div class="input-group-append" title="Inverted">
|
||||
<label class="input-group-text">
|
||||
<input type="checkbox" name="n" value="true" ${n}/> inv
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 130px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">AP button</span>
|
||||
</div>
|
||||
<input name="a" type="number" min="0" max="${g}" class="form-control" value="${a}"/>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 150px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Temperature</span>
|
||||
</div>
|
||||
<input name="t" type="number" min="0" max="${g}" class="form-control" value="${t}"/>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 150px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Analog temp</span>
|
||||
</div>
|
||||
<input name="m" type="number" min="0" max="${g}" class="form-control" value="${m}"/>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 100px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Vcc</span>
|
||||
</div>
|
||||
<input name="v" type="number" min="0" max="${g}" class="form-control" value="${v}"/>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 200px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">GND resistor</span>
|
||||
</div>
|
||||
<input type="number" min="1" max="1000" step="1" class="form-control" name="d" value="${d}" />
|
||||
<div class="input-group-append" title="Inverted">
|
||||
<label class="input-group-text">kΩ</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 190px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Vcc resistor</span>
|
||||
</div>
|
||||
<input type="number" min="1" max="1000" step="1" class="form-control" name="s" value="${s}" />
|
||||
<div class="input-group-append" title="Inverted">
|
||||
<label class="input-group-text">kΩ</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 140px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Multiplier</span>
|
||||
</div>
|
||||
<input type="number" min="0.1" max="10" step="0.01" class="form-control" name="u" value="${u}" />
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 120px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Offset</span>
|
||||
</div>
|
||||
<input type="number" min="0.0" max="3.5" step="0.01" class="form-control" name="o" value="${o}" />
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 130px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Boot limit</span>
|
||||
</div>
|
||||
<input type="number" min="2.5" max="3.5" step="0.1" class="form-control" name="c" value="${c}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="/" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,104 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>AMS reader</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous" referrerpolicy="no-referrer">
|
||||
<style>
|
||||
.navbar-expand .navbar-nav .nav-link,.navbar-brand {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.plot1 {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
.plot2 {
|
||||
width: 100%;
|
||||
height: 224px;
|
||||
}
|
||||
.overlay-plot {
|
||||
position: relative;
|
||||
}
|
||||
.plot-overlay {
|
||||
position: absolute;
|
||||
top: 70px;
|
||||
left: 25%;
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
}
|
||||
.ipo,.epo {
|
||||
font-size: 1.7rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.ipoa,.epoa {
|
||||
font-size: 1.0rem;
|
||||
color: grey;
|
||||
}
|
||||
.pol {
|
||||
font-size: 1.0rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<main role="main" class="container">
|
||||
<header class="navbar navbar-expand navbar-dark flex-column flex-lg-row rounded shadow mt-2 mb-3" style="background-color: var(--purple);">
|
||||
<a href="/" class="">
|
||||
<h6 class="navbar-brand">AMS reader <small id="swVersion" data-url="https://api.github.com/repos/UtilitechAS/amsreader-firmware/releases">${version}</small></h6>
|
||||
</a>
|
||||
<div class="navbar-nav-scroll">
|
||||
<ul class="navbar-nav bd-navbar-nav flex-row">
|
||||
<li class="nav-item">
|
||||
<div class="dropdown">
|
||||
<a class="dropdown-toggle nav-link" href="#" role="button" id="config-link" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Config<span class="d-none d-md-inline d-lg-none d-xl-inline">uration</span>
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu" aria-labelledby="config-link">
|
||||
<a class="dropdown-item" href="/meter">Meter</a>
|
||||
<a class="dropdown-item" href="/wifi">WiFi</a>
|
||||
<a class="dropdown-item" href="/mqtt">MQTT</a>
|
||||
<a class="dropdown-item" href="/web">Web</a>
|
||||
<a class="dropdown-item" href="/ntp">NTP</a>
|
||||
<a class="dropdown-item" href="/priceapi">Price API</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="https://github.com/UtilitechAS/amsreader-firmware/wiki" target="_blank">Documentation</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<div class="dropdown">
|
||||
<a class="dropdown-toggle nav-link" href="#" role="button" id="system-link" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Sys<span class="d-none d-sm-inline">tem</span>
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="system-link">
|
||||
<a class="dropdown-item" href="/gpio">GPIO</a>
|
||||
<a class="dropdown-item" href="/debugging">Debugging</a>
|
||||
<a class="dropdown-item" href="/configfile">Configuration file</a>
|
||||
<a class="dropdown-item" href="/firmware">Firmware</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="/restart">Restart</a>
|
||||
<a class="dropdown-item text-danger" href="/reset">Factory reset</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="flex-row">
|
||||
<div id="esp" class="d-none m-2">ESP</div>
|
||||
<div id="han" class="d-none m-2">HAN</div>
|
||||
<div id="wifi" class="d-none m-2">WiFi</div>
|
||||
<div id="mqtt" class="d-none m-2">MQTT</div>
|
||||
</div>
|
||||
<ul class="navbar-nav flex-row ml-md-auto d-none d-lg-flex">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link p-2" href="https://github.com/UtilitechAS/amsreader-firmware" target="_blank" rel="noopener" aria-label="GitHub">
|
||||
<img class="d-inline-block align-text-top" style="width: 2rem; height: 2rem;" src="github.svg"/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</header>
|
||||
@@ -1,210 +0,0 @@
|
||||
<div class="bg-white rounded shadow p-1 mb-3">
|
||||
<div class="d-flex flex-wrap">
|
||||
<div class="flex-fill">
|
||||
<div class="text-center">Up <span class="ju">-</span></div>
|
||||
</div>
|
||||
<div class="flex-fill rt">
|
||||
<div class="text-center">Temperature: <span class="jt">-</span>°C</div>
|
||||
</div>
|
||||
<div class="flex-fill rv">
|
||||
<div class="text-center">ESP volt: <span class="jv">-</span>V</div>
|
||||
</div>
|
||||
<div class="flex-fill">
|
||||
<div class="text-center">WiFi RSSI: <span class="jr">-</span>dBm</div>
|
||||
</div>
|
||||
<div class="flex-fill">
|
||||
<div class="text-center">Free mem: <span class="jm">-</span>kb</div>
|
||||
</div>
|
||||
<div class="flex-fill rc">
|
||||
<div class="text-center"><span class="jc"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-sm-6 mb-3">
|
||||
<div class="bg-white rounded shadow p-3">
|
||||
<div class="text-center overlay-plot">
|
||||
<div id="ip" class="plot1"></div>
|
||||
<span class="plot-overlay">
|
||||
<span class="ipo">-</span>
|
||||
<span class="ipoa">W</span>
|
||||
<br/>
|
||||
<span class="pol">Import</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="row ric" style="display: none;">
|
||||
<div class="col-5"><span class="jmt"></span></div>
|
||||
<div class="col-7 text-right"><span class="jic">-</span> kWh</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6 mb-3 rex" style="display: none;">
|
||||
<div class="bg-white rounded shadow p-3">
|
||||
<div class="text-center overlay-plot">
|
||||
<div id="xp" class="plot1"></div>
|
||||
<span class="plot-overlay">
|
||||
<span class="epo">-</span>
|
||||
<span class="epoa">W</span>
|
||||
<br/>
|
||||
<span class="pol">Export</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="row rec" style="display: none;">
|
||||
<div class="col-12 text-right"><span class="jec">-</span> kWh</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6 mb-3">
|
||||
<div class="bg-white rounded shadow p-3">
|
||||
<div class="text-center">
|
||||
<div id="vp" class="plot2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6 mb-3">
|
||||
<div class="bg-white rounded shadow p-3">
|
||||
<div class="text-center">
|
||||
<div id="ap" class="plot2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6 mb-3 rim" style="display: none;">
|
||||
<div class="bg-white rounded shadow p-3">
|
||||
<h5 class="text-center">Reactive</h5>
|
||||
<div class="row rri">
|
||||
<div class="col-12 font-weight-bold">Instant</div>
|
||||
<div class="col-4">In</div>
|
||||
<div class="col-8 text-right"><span class="jri">-</span> VAr</div>
|
||||
<div class="col-4">Out</div>
|
||||
<div class="col-8 text-right"><span class="jre">-</span> VAr</div>
|
||||
</div>
|
||||
<div class="row rric">
|
||||
<div class="col-12 font-weight-bold">Total</div>
|
||||
<div class="col-4">In</div>
|
||||
<div class="col-8 text-right"><span class="jric">-</span> kVArh</div>
|
||||
<div class="col-4">Out</div>
|
||||
<div class="col-8 text-right"><span class="jrec">-</span> kVArh</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 mb-3 rex" style="display: none;">
|
||||
<div class="bg-white rounded shadow p-3">
|
||||
<div class="row rrec">
|
||||
<div class="col-sm-4 font-weight-bold">Instant reactive</div>
|
||||
<div class="col-4 col-sm-1">In</div>
|
||||
<div class="col-8 col-sm-3 text-right"><span class="jri">-</span> VAr</div>
|
||||
<div class="col-4 col-sm-1">Out</div>
|
||||
<div class="col-8 col-sm-3 text-right"><span class="jre">-</span> VAr</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 mb-3 rex" style="display: none;">
|
||||
<div class="bg-white rounded shadow p-3">
|
||||
<div class="row rrec">
|
||||
<div class="col-sm-4 font-weight-bold">Total reactive</div>
|
||||
<div class="col-4 col-sm-1">In</div>
|
||||
<div class="col-8 col-sm-3 text-right"><span class="jric">-</span> kVArh</div>
|
||||
<div class="col-4 col-sm-1">Out</div>
|
||||
<div class="col-8 col-sm-3 text-right"><span class="jrec">-</span> kVArh</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-12 mb-3">
|
||||
<div class="bg-white rounded shadow pt-3 pb-3" style="font-size: 14px;">
|
||||
<strong class="mr-3 ml-3">Real time consumption</strong><br/>
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-sm-6">
|
||||
<div class="mr-3 ml-3 d-flex">
|
||||
<div>Hour</div>
|
||||
<div class="flex-fill text-right">
|
||||
<span id="each"></span> kWh
|
||||
<span class="sp text-nowrap" style="display: none;">(<span id="eachc"></span> <span class="cr"></span>)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6">
|
||||
<div class="mr-3 ml-3 d-flex">
|
||||
<div>Day</div>
|
||||
<div class="flex-fill text-right">
|
||||
<span id="eacd"></span> kWh
|
||||
<span class="sp text-nowrap" style="display: none;">(<span id="eacdc"></span> <span class="cr"></span>)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6">
|
||||
<div class="mr-3 ml-3 d-flex">
|
||||
<div>Month</div>
|
||||
<div class="flex-fill text-right">
|
||||
<span id="eacm"></span> kWh
|
||||
<span class="sp text-nowrap" style="display: none;">(<span id="eacmc"></span> <span class="cr"></span>)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6">
|
||||
<div class="mr-3 ml-3 d-flex">
|
||||
<div>Max</div>
|
||||
<div class="flex-fill text-right">
|
||||
<span id="eax"></span> / <span id="eat"></span> kWh
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<strong class="mr-3 ml-3 se d-none">Real time production</strong><br/>
|
||||
<div class="row se d-none">
|
||||
<div class="col-lg-3 col-sm-6">
|
||||
<div class="mr-3 ml-3 d-flex">
|
||||
<div>Hour</div>
|
||||
<div class="flex-fill text-right">
|
||||
<span id="eache"></span> kWh
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6">
|
||||
<div class="mr-3 ml-3 d-flex">
|
||||
<div>Day</div>
|
||||
<div class="flex-fill text-right">
|
||||
<span id="eacde"></span> kWh
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6">
|
||||
<div class="mr-3 ml-3 d-flex">
|
||||
<div>Month</div>
|
||||
<div class="flex-fill text-right">
|
||||
<span id="eacme"></span> kWh
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="ppc" class="col-xl-12 mb-3" style="display: none;">
|
||||
<div class="bg-white rounded shadow" id="pp" style="width: 100%; height: 224px;"></div>
|
||||
</div>
|
||||
<div class="col-xl-12 mb-3">
|
||||
<div class="bg-white rounded shadow" id="ep" style="width: 100%; height: 224px;"></div>
|
||||
</div>
|
||||
<div class="col-xl-12 mb-3">
|
||||
<div class="bg-white rounded shadow" id="mp" style="width: 100%; height: 224px;"></div>
|
||||
</div>
|
||||
<div id="tpc" class="col-xl-12 mb-3" style="display: none;">
|
||||
<div class="bg-white rounded shadow pb-3">
|
||||
<div id="tp" style="width: 100%; height: 224px;"></div>
|
||||
<a class="m-4" href="/temperature">Configuration</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6 mb-3 d-none me me-1 me-2 me-3 me-4 me-5 me-6 me-7 me-8 me-9 me-10 me-11 me-12 me-13">
|
||||
<div class="d-none badge badge-danger me me-1 me-2 me-5 me-6 me-7 me-8 me-9 me-12">MQTT communication error (<span id="ml">-</span>)</div>
|
||||
<div class="d-none badge badge-danger me me-3">MQTT failed to connect</div>
|
||||
<div class="d-none badge badge-danger me me-4">MQTT network timeout</div>
|
||||
<div class="d-none badge badge-danger me me-10">MQTT connection denied</div>
|
||||
<div class="d-none badge badge-danger me me-11">MQTT failed to subscribe</div>
|
||||
<div class="d-none badge badge-danger me me-13">MQTT lost connection</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
<form method="post" action="/save">
|
||||
<input type="hidden" name="mc" value="true"/>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<h6>Meter</h6>
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-md-4 col-sm-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Manufacturer</span>
|
||||
</div>
|
||||
<input class="form-control" value="{maf}" disabled/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-4 col-sm-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Model</span>
|
||||
</div>
|
||||
<input class="form-control" value="{mod}" disabled/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-4 col-sm-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">ID</span>
|
||||
</div>
|
||||
<input class="form-control" value="{mid}" disabled/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-md-4 col-sm-5">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Baud rate</span>
|
||||
</div>
|
||||
<select class="form-control sd" name="b">
|
||||
<option value="300" {b300}>300</option>
|
||||
<option value="2400" {b2400}>2400</option>
|
||||
<option value="4800" {b4800}>4800</option>
|
||||
<option value="9600" {b9600}>9600</option>
|
||||
<option value="19200" {b19200}>19200</option>
|
||||
<option value="38400" {b38400}>38400</option>
|
||||
<option value="57600" {b57600}>57600</option>
|
||||
<option value="115200" {b115200}>115200</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Parity</span>
|
||||
</div>
|
||||
<select class="form-control sd" name="c">
|
||||
<option value="2" {c2}>7N1</option>
|
||||
<option value="3" {c3}>8N1</option>
|
||||
<option value="10" {c10}>7E1</option>
|
||||
<option value="11" {c11}>8E1</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-4 col-sm-3 col-6">
|
||||
<div class="m-2">
|
||||
<label class="small"><input type="checkbox" name="i" value="true" {i}/> Invert <span class="d-none d-md-inline">signal</span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-8">
|
||||
<a href="https://github.com/UtilitechAS/amsreader-firmware/wiki/Known-hardware-configurations" target="_blank">Known hardware configurations</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xl-3 col-lg-4 col-md-5 col-sm-7">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Distribution system</span>
|
||||
</div>
|
||||
<select class="form-control sd" name="d">
|
||||
<option value="0" {d0}></option>
|
||||
<option value="1" {d1}>IT or TT (230V)</option>
|
||||
<option value="2" {d2}>TN (400V)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-4 col-sm-5">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Main fuse</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="f" type="number" min="5" max="255" step="1" value="{f}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">A</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-lg-4 col-md-5 col-sm-7">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Production capacity</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="p" type="number" min="0" max="255" step="1" value="{p}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">kWp</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Encryption key</span>
|
||||
</div>
|
||||
<input class="form-control" name="e" type="text" value="{e}" placeholder="If applicable"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Authentication key</span>
|
||||
</div>
|
||||
<input class="form-control" name="a" type="text" value="{a}" placeholder="If applicable"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-sm-6">
|
||||
<a href="/thresholds">Configure tariff thresholds</a>
|
||||
</div>
|
||||
<div class="col-sm-6 text-right">
|
||||
<a href="/meteradvanced">Multipliers</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="/" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,49 +0,0 @@
|
||||
<form method="post" action="/save">
|
||||
<input type="hidden" name="ma" value="true"/>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<h6>Multipliers</h6>
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-md-4 col-sm-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Instant</span>
|
||||
</div>
|
||||
<input type="number" class="form-control text-right" name="wm" value="%.2f" min="0.00" max="655.35" step="0.01"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-4 col-sm-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Voltage</span>
|
||||
</div>
|
||||
<input type="number" class="form-control text-right" name="vm" value="%.2f" min="0.00" max="655.35" step="0.01"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-4 col-sm-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Amperage</span>
|
||||
</div>
|
||||
<input type="number" class="form-control text-right" name="am" value="%.2f" min="0.00" max="655.35" step="0.01"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-4 col-sm-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Accumulated</span>
|
||||
</div>
|
||||
<input type="number" class="form-control text-right" name="cm" value="%.2f" min="0.00" max="655.35" step="0.01"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="/meter" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,64 +0,0 @@
|
||||
{
|
||||
"i01" : %.2f,
|
||||
"i02" : %.2f,
|
||||
"i03" : %.2f,
|
||||
"i04" : %.2f,
|
||||
"i05" : %.2f,
|
||||
"i06" : %.2f,
|
||||
"i07" : %.2f,
|
||||
"i08" : %.2f,
|
||||
"i09" : %.2f,
|
||||
"i10" : %.2f,
|
||||
"i11" : %.2f,
|
||||
"i12" : %.2f,
|
||||
"i13" : %.2f,
|
||||
"i14" : %.2f,
|
||||
"i15" : %.2f,
|
||||
"i16" : %.2f,
|
||||
"i17" : %.2f,
|
||||
"i18" : %.2f,
|
||||
"i19" : %.2f,
|
||||
"i20" : %.2f,
|
||||
"i21" : %.2f,
|
||||
"i22" : %.2f,
|
||||
"i23" : %.2f,
|
||||
"i24" : %.2f,
|
||||
"i25" : %.2f,
|
||||
"i26" : %.2f,
|
||||
"i27" : %.2f,
|
||||
"i28" : %.2f,
|
||||
"i29" : %.2f,
|
||||
"i30" : %.2f,
|
||||
"i31" : %.2f,
|
||||
"e01" : %.2f,
|
||||
"e02" : %.2f,
|
||||
"e03" : %.2f,
|
||||
"e04" : %.2f,
|
||||
"e05" : %.2f,
|
||||
"e06" : %.2f,
|
||||
"e07" : %.2f,
|
||||
"e08" : %.2f,
|
||||
"e09" : %.2f,
|
||||
"e10" : %.2f,
|
||||
"e11" : %.2f,
|
||||
"e12" : %.2f,
|
||||
"e13" : %.2f,
|
||||
"e14" : %.2f,
|
||||
"e15" : %.2f,
|
||||
"e16" : %.2f,
|
||||
"e17" : %.2f,
|
||||
"e18" : %.2f,
|
||||
"e19" : %.2f,
|
||||
"e20" : %.2f,
|
||||
"e21" : %.2f,
|
||||
"e22" : %.2f,
|
||||
"e23" : %.2f,
|
||||
"e24" : %.2f,
|
||||
"e25" : %.2f,
|
||||
"e26" : %.2f,
|
||||
"e27" : %.2f,
|
||||
"e28" : %.2f,
|
||||
"e29" : %.2f,
|
||||
"e30" : %.2f,
|
||||
"e31" : %.2f
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
<form method="post" action="/save">
|
||||
<input type="hidden" name="mqc" value="true"/>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<h6>MQTT</h6>
|
||||
<label class="m-2"><input id="m" type="checkbox" name="m" value="true" {m}/> Enable</label>
|
||||
<div class="row">
|
||||
<div class="col-xl-4 col-lg-5 col-md-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Host</span>
|
||||
</div>
|
||||
<input type="text" class="form-control mc" name="h" value="{h}" maxlength="127"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-2 col-lg-3 col-md-3 col-sm-4">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Port</span>
|
||||
</div>
|
||||
<input id="p" type="number" class="form-control mc" name="p" value="{p}" min="1024" max="65535" placeholder="1883"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-lg-4 col-md-6 col-sm-8">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Client ID</span>
|
||||
</div>
|
||||
<input type="text" class="form-control mc" name="i" value="{i}" maxlength="31"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-lg-4 col-md-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Publish topic</span>
|
||||
</div>
|
||||
<input type="text" class="form-control mc" name="t" value="{t}" maxlength="63"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-lg-4 col-md-6 col-sm-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Username</span>
|
||||
</div>
|
||||
<input type="text" class="form-control mc" name="u" value="{u}" maxlength="127"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-lg-4 col-md-6 col-sm-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Password</span>
|
||||
</div>
|
||||
<input type="password" class="form-control mc" name="pw" value="{pw}" maxlength="255"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-4 col-sm-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Payload</span>
|
||||
</div>
|
||||
<select id="f" class="form-control mc" name="f">
|
||||
<option value="0" {f0}>JSON</option>
|
||||
<option value="1" {f1}>Raw values (minimal)</option>
|
||||
<option value="2" {f2}>Raw values (full)</option>
|
||||
<option value="3" {f3}>Domoticz</option>
|
||||
<option value="4" {f4}>Home-Assistant</option>
|
||||
<option value="255" {f255}>Raw data (bytes)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-6 f3-s">
|
||||
<div class="m-2">
|
||||
<a href="/domoticz" class="btn btn-sm btn-outline-secondary">Configuration</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<label class="m-2"><input id="s" type="checkbox" name="s" value="true" {s}/> SSL</label>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">CA</span>
|
||||
</div>
|
||||
<div class="input-group-append">
|
||||
<span style="display: {dcu};">
|
||||
<a href="/mqtt-ca" class="btn btn-sm btn-outline-secondary">Upload</a>
|
||||
</span>
|
||||
<span style="display: {dcf};">
|
||||
<a href="/mqtt-ca" class="btn btn-sm btn-danger">Delete</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-2 col-md-3">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Certificate</span>
|
||||
</div>
|
||||
<div class="input-group-append">
|
||||
<span style="display: {deu};">
|
||||
<a href="/mqtt-cert" class="btn btn-sm btn-outline-secondary">Upload</a>
|
||||
</span>
|
||||
<span style="display: {def};">
|
||||
<a href="/mqtt-cert" class="btn btn-sm btn-danger">Delete</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-2 col-md-4">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Private key</span>
|
||||
</div>
|
||||
<div class="input-group-append">
|
||||
<span style="display: {dku};">
|
||||
<a href="/mqtt-key" class="btn btn-sm btn-outline-secondary">Upload</a>
|
||||
</span>
|
||||
<span style="display: {dkf};">
|
||||
<a href="/mqtt-key" class="btn btn-sm btn-danger">Delete</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="/" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1 +0,0 @@
|
||||
<div class="alert alert-danger">Page not found</div>
|
||||
@@ -1,54 +0,0 @@
|
||||
<form method="post" action="/save">
|
||||
<input type="hidden" name="nc" value="true"/>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<h6>NTP</h6>
|
||||
<label class="m-2"><input id="n" type="checkbox" name="n" value="true" %s/> Enable</label>
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-md-4 col-sm-5">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Timezone</span>
|
||||
</div>
|
||||
<select id="o" class="form-control nc" name="o">
|
||||
<option value="0" %s>UTC</option>
|
||||
<option value="3600" %s>UTC+1</option>
|
||||
<option value="7200" %s>UTC+2</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-lg-4 col-md-5 col-sm-7">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Summertime offset</span>
|
||||
</div>
|
||||
<select id="so" class="form-control nc" name="so">
|
||||
<option value="0" %s>Disabled</option>
|
||||
<option value="3600" %s>+1hr</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-4 col-lg-5 col-md-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Server</span>
|
||||
</div>
|
||||
<input type="text" class="form-control nc" name="ns" value="%s" maxlength="64"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-5 col-sm-6">
|
||||
<div class="m-2">
|
||||
<label class="small"><input type="checkbox" name="nd" value="true" %s class="nc"/> Obtain NTP server from DHCP</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="/" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,86 +0,0 @@
|
||||
<form method="post" action="/save">
|
||||
<input type="hidden" name="ec" value="true"/>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<h6>Price API</h6>
|
||||
<div class="row">
|
||||
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Region</span>
|
||||
</div>
|
||||
<select name="ea" class="form-control">
|
||||
<option value="">None</option>
|
||||
<optgroup label="Norway">
|
||||
<option value="10YNO-1--------2" {no1}>NO1</option>
|
||||
<option value="10YNO-2--------T" {no2}>NO2</option>
|
||||
<option value="10YNO-3--------J" {no3}>NO3</option>
|
||||
<option value="10YNO-4--------9" {no4}>NO4</option>
|
||||
<option value="10Y1001A1001A48H" {no5}>NO5</option>
|
||||
</optgroup>
|
||||
<optgroup label="Sweden">
|
||||
<option value="10Y1001A1001A44P" {se1}>SE1</option>
|
||||
<option value="10Y1001A1001A45N" {se2}>SE2</option>
|
||||
<option value="10Y1001A1001A46L" {se3}>SE3</option>
|
||||
<option value="10Y1001A1001A47J" {se4}>SE4</option>
|
||||
</optgroup>
|
||||
<optgroup label="Denmark">
|
||||
<option value="10YDK-1--------W" {dk1}>DK1</option>
|
||||
<option value="10YDK-2--------M" {dk2}>DK2</option>
|
||||
</optgroup>
|
||||
<option value="10YAT-APG------L" {at}>Austria</option>
|
||||
<option value="10YBE----------2" {be}>Belgium</option>
|
||||
<option value="10YCZ-CEPS-----N" {cz}>Czech Republic</option>
|
||||
<option value="10Y1001A1001A39I" {ee}>Estonia</option>
|
||||
<option value="10YFI-1--------U" {fi}>Finland</option>
|
||||
<option value="10YFR-RTE------C" {fr}>France</option>
|
||||
<option value="10Y1001A1001A83F" {de}>Germany</option>
|
||||
<option value="10YGB----------A" {gb}>Great Britain</option>
|
||||
<option value="10YLV-1001A00074" {lv}>Latvia</option>
|
||||
<option value="10YLT-1001A0008Q" {lt}>Lithuania</option>
|
||||
<option value="10YNL----------L" {nl}>Netherland</option>
|
||||
<option value="10YPL-AREA-----S" {pl}>Poland</option>
|
||||
<option value="10YCH-SWISSGRIDZ" {ch}>Switzerland</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Currency</span>
|
||||
</div>
|
||||
<select name="ecu" class="form-control">
|
||||
<option value="NOK" {nok}>NOK</option>
|
||||
<option value="SEK" {sek}>SEK</option>
|
||||
<option value="DKK" {dkk}>DKK</option>
|
||||
<option value="EUR" {eur}>EUR</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Multiplier</span>
|
||||
</div>
|
||||
<input name="em" type="number" min="0.001" max="1000" step="0.001" class="form-control" value="{em}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-4 col-lg-6 col-md-8 {dt}">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">ENTSO-E token</span>
|
||||
</div>
|
||||
<input type="text" name="et" class="form-control" value="{et}" placeholder="Optional"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="/" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,14 +0,0 @@
|
||||
<form method="post">
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<div class="alert alert-danger">Are you sure you want reset this device to factory settings? ALL configuration will be erased!</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="javascript:history.back();" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-danger" name="perform" value="true">Perform factory reset</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,14 +0,0 @@
|
||||
<form method="post">
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<div class="alert alert-warning">Are you sure you want restart?</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="javascript:history.back();" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-success" name="perform" value="true">Restart</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,63 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>AMS reader - Restarting, please wait</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link rel="stylesheet" type="text/css" href="boot.css"/>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<main role="main" class="container">
|
||||
<header class="navbar navbar-expand navbar-dark flex-column flex-md-row bg-purple rounded mt-2 mb-4" style="background-color: var(--purple);">
|
||||
<a href="/" class=""><h6 class="navbar-brand">AMS reader <small>${version}</small></h6></a>
|
||||
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link p-2" href="https://github.com/UtilitechAS/amsreader-firmware" target="_blank" rel="noopener" aria-label="GitHub">
|
||||
<img class="d-inline-block align-text-top" style="width: 2rem; height: 2rem;" src="github.svg"/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</header>
|
||||
<div class="my-3 p-3 bg-white rounded shadow {rs}">
|
||||
Device is rebooting. You will be redirected to the main page when it is available again.
|
||||
</div>
|
||||
<div class="alert alert-warning shadow {us}">Firmware upgrade in progress, DO NOT disconnect power from the device. You will be redirected to the main page when firmware upgrade is complete.</div>
|
||||
</main>
|
||||
<script>
|
||||
var tries = 0;
|
||||
var ip = "${ip}";
|
||||
var hostname = "${hostname}";
|
||||
var url;
|
||||
var fetch = function() {
|
||||
tries++;
|
||||
|
||||
if(ip && tries%3 == 0) {
|
||||
url = "http://" + ip;
|
||||
} else if(hostname && tries%3 == 1) {
|
||||
url = "http://" + hostname;
|
||||
} else if(hostname && tries%3 == 2) {
|
||||
url = "http://" + hostname + ".local";
|
||||
} else {
|
||||
url = "";
|
||||
}
|
||||
if(console) console.log("Trying url " + url);
|
||||
|
||||
var retry = function() {
|
||||
setTimeout(fetch, 1000);
|
||||
};
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.timeout = 5000;
|
||||
xhr.addEventListener('abort', retry);
|
||||
xhr.addEventListener('error', retry);
|
||||
xhr.addEventListener('timeout', retry);
|
||||
xhr.addEventListener('load', function(e) {
|
||||
window.location.href = url ? url : "/";
|
||||
});
|
||||
xhr.open("GET", url + "/is-alive", true);
|
||||
xhr.send();
|
||||
};
|
||||
setTimeout(fetch, 10000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,138 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>AMS reader - Setup</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link rel="stylesheet" type="text/css" href="boot.css"/>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<main role="main" class="container">
|
||||
<header class="navbar navbar-expand navbar-dark flex-column flex-md-row bg-purple rounded mt-2 mb-4">
|
||||
<a href="/" class=""><h6 class="navbar-brand">AMS reader <small>${version}</small></h6></a>
|
||||
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link p-2" href="https://github.com/UtilitechAS/amsreader-firmware" target="_blank" rel="noopener" aria-label="GitHub">
|
||||
<img style="width: 2rem; height: 2rem;" src="github.svg"/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</header>
|
||||
<form method="post">
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<div class="row">
|
||||
<div class="col-xl-4 col-md-6">
|
||||
<h5>Hardware</h5>
|
||||
<select name="board" class="form-control" required>
|
||||
<option value=""></option>
|
||||
<optgroup label="Custom hardware">
|
||||
<option value="7" ${config.boardType7}>Pow-U+ (ESP32) from amsleser.no</option>
|
||||
<option value="6" ${config.boardType6}>Pow-P1 from amsleser.no</option>
|
||||
<option value="5" ${config.boardType5}>Pow-K+ (ESP32) from amsleser.no</option>
|
||||
<option value="4" ${config.boardType4}>Pow-U or Pow-K from amsleser.no (GPIO12)</option>
|
||||
<option value="3" ${config.boardType3}>Pow-U or Pow-K from amsleser.no (UART0)</option>
|
||||
<option value="2" ${config.boardType2}>HAN Reader 2.0 by Max Spencer</option>
|
||||
<option value="1" ${config.boardType1}>Kamstrup module by Egil Opsahl</option>
|
||||
<option value="0" ${config.boardType0}>Custom hardware by Roar Fredriksen</option>
|
||||
</optgroup>
|
||||
<optgroup label="ESP8266">
|
||||
<option value="101" ${config.boardType101}>Wemos D1</option>
|
||||
<option value="100" ${config.boardType100}>Generic ESP8266</option>
|
||||
</optgroup>
|
||||
<optgroup label="ESP32">
|
||||
<option value="201" ${config.boardType201}>Wemos LOLIN D32</option>
|
||||
<option value="202" ${config.boardType202}>Adafruit HUZZAH32</option>
|
||||
<option value="203" ${config.boardType203}>DevKitC</option>
|
||||
<option value="200" ${config.boardType200}>Generic ESP32</option>
|
||||
</optgroup>
|
||||
<optgroup label="ESP32-S2">
|
||||
<option value="51" ${config.boardType51}>Wemos S2 mini</option>
|
||||
<option value="50" ${config.boardType50}>Generic ESP32-S2</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<h5>WiFi</h5>
|
||||
<div class="row">
|
||||
<div class="col-xl-3 col-md-6 form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">SSID</span>
|
||||
</div>
|
||||
<input type="text" name="wifiSsid" class="form-control" maxlength="32" placeholder="Name of your WiFi" required value="${config.wifiSsid}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6 form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">PSK</span>
|
||||
</div>
|
||||
<input type="password" name="wifiPassword" class="form-control" maxlength="63" placeholder="Password for WiFi" required value="${config.wifiPassword}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-4 col-md-6 form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Hostname</span>
|
||||
</div>
|
||||
<input type="text" name="wifiHostname" class="form-control" maxlength="32" pattern="[a-z0-9_-]+" placeholder="Optional, ex.: ams-reader" value="${config.wifiHostname}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-2 col-md-6 form-group">
|
||||
<label><input type="checkbox" name="wifiIpType" value="1" onchange="staticChecked(this);" ${config.wifiStaticIp}/> Static IP</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" id="staticIp">
|
||||
<div class="col-xl-3 col-lg-4 col-sm-6 form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">IP</span>
|
||||
</div>
|
||||
<input type="text" name="wifiIp" class="form-control" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" placeholder="Ex: 192.168.1.200" value="${config.wifiIp}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-lg-4 col-sm-6 form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Subnet</span>
|
||||
</div>
|
||||
<input type="text" name="wifiSubnet" class="form-control" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" placeholder="Ex.: 255.255.255.0" value="${config.wifiSubnet}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-lg-4 col-sm-6 form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Gateway</span>
|
||||
</div>
|
||||
<input type="text" name="wifiGw" class="form-control" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" placeholder="Ex.: 192.168.1.1" value="${config.wifiGw}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-lg-4 col-sm-6 form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">DNS</span>
|
||||
</div>
|
||||
<input type="text" name="wifiDns1" class="form-control" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" placeholder="Ex.: 192.168.1.1" value="${config.wifiDns1}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-6"></div>
|
||||
<div class="col-6 text-right">
|
||||
<button type="submit" class="btn btn-primary">Save & reboot</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
<script>
|
||||
document.getElementById('staticIp').style.display = "none";
|
||||
var staticChecked = function(el) {
|
||||
document.getElementById('staticIp').style.display = el.checked ? "" : "none";
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,48 +0,0 @@
|
||||
<script id="temp-template" type="template">
|
||||
<div class="row mb-3">
|
||||
<input type="hidden" name="sensor{{index}}" value="{{address}}"/>
|
||||
<div class="col-xl-3 col-lg-4 col-sm-6">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Address {{index}}</span>
|
||||
</div>
|
||||
<input name="sensor{{index}}address" type="text" class="form-control" value="{{address}}" maxlength="16" disabled/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-4 col-lg-3 col-sm-6">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Name</span>
|
||||
</div>
|
||||
<input name="sensor{{index}}name" type="text" class="form-control" value="{{name}}" maxlength="16"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-lg-3 col-sm-6">
|
||||
<div class="form-check">
|
||||
<input name="sensor{{index}}common" class="form-check-input" type="checkbox" value="true" id="sensor{{index}}common" {{common}}>
|
||||
<label class="form-check-label" for="sensor{{index}}common">Include in average</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-2 col-lg-2 col-sm-6">
|
||||
<span id="temp-{{index}}">{{value}}</span> °C
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<form method="post">
|
||||
<input type="hidden" name="tempConfig" value="true"/>
|
||||
<div id="sensors" class="my-3 p-3 bg-white rounded shadow">
|
||||
<div id="loading" class="alert alert-info">Loading temperature sensors</div>
|
||||
<div id="notemp" class="alert alert-info" style="display: none;">No temperature sensors are configured or found</div>
|
||||
<div id="error" class="alert alert-danger" style="display: none;">Error loading data</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="/" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"i" : %d,
|
||||
"a" : "%s",
|
||||
"n" : "%s",
|
||||
"c" : %d,
|
||||
"v" : %.1f
|
||||
},
|
||||
@@ -1,129 +0,0 @@
|
||||
<form method="post" action="/save">
|
||||
<input type="hidden" name="cc" value="true"/>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<h6>Tariff thresholds</h6>
|
||||
<div class="row">
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">1</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="t0" type="number" min="1" max="255" step="1" value="{t0}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">kWh</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">2</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="t1" type="number" min="5" max="255" step="1" value="{t1}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">kWh</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">3</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="t2" type="number" min="5" max="255" step="1" value="{t2}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">kWh</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">4</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="t3" type="number" min="5" max="255" step="1" value="{t3}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">kWh</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">5</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="t4" type="number" min="5" max="255" step="1" value="{t4}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">kWh</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">6</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="t5" type="number" min="5" max="255" step="1" value="{t5}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">kWh</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">7</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="t6" type="number" min="5" max="255" step="1" value="{t6}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">kWh</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">8</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="t7" type="number" min="5" max="255" step="1" value="{t7}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">kWh</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">9</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="t8" type="number" min="5" max="255" step="1" value="{t8}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">kWh</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-md-5 col-sm-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Average of top</span>
|
||||
</div>
|
||||
<input class="form-control text-right" name="h" type="number" min="1" max="5" step="1" value="{h}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">hours</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="/meter" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,26 +0,0 @@
|
||||
<form method="post" enctype="multipart/form-data" class="upload-form">
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Upload</span>
|
||||
</div>
|
||||
<div class="custom-file">
|
||||
<input name="file" type="file" class="custom-file-input" id="fileUploadField">
|
||||
<label class="custom-file-label" for="fileUploadField">Choose file</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="javascript:history.back();" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-primary">Upload</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,45 +0,0 @@
|
||||
<form method="post" action="/save">
|
||||
<input type="hidden" name="ac" value="true"/>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<h6>Web</h6>
|
||||
<div class="row">
|
||||
<div class="col-xl-3 col-lg-4 col-md-7">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Security</span>
|
||||
</div>
|
||||
<select id="as" class="form-control" name="as">
|
||||
<option value="0" %s>None</option>
|
||||
<option value="1" %s>Only configuration</option>
|
||||
<option value="2" %s>Everything</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-lg-4 col-md-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Username</span>
|
||||
</div>
|
||||
<input type="text" class="form-control ac" name="au" value="%s" maxlength="64"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-lg-4 col-md-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Password</span>
|
||||
</div>
|
||||
<input type="password" class="form-control ac" name="ap" value="%s" maxlength="64"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="/" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,117 +0,0 @@
|
||||
<form method="post" action="/save">
|
||||
<input type="hidden" name="wc" value="true"/>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<h6>WiFi</h6>
|
||||
<div class="row">
|
||||
<div class="col-xl-3 col-md-6 form-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">SSID</span>
|
||||
</div>
|
||||
<input type="text" name="s" class="form-control" maxlength="32" value="{s}" required/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6 form-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">PSK</span>
|
||||
</div>
|
||||
<input type="password" name="p" class="form-control" maxlength="63" value="{p}" required/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-4 col-md-6 form-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Hostname</span>
|
||||
</div>
|
||||
<input type="text" name="h" class="form-control" maxlength="32" pattern="[a-z0-9_-]+" placeholder="Optional, ex.: ams-reader" value="{h}"/>
|
||||
<div class="input-group-append">
|
||||
<label class="input-group-text">
|
||||
<input type="checkbox" name="m" value="true" {m}/> mDNS
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-2 col-md-6 form-group">
|
||||
<label><input id="st" type="checkbox" name="st" value="1" {st}/> Static IP</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" id="i">
|
||||
<div class="col-xl-3 col-lg-4 col-md-6 form-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">IP</span>
|
||||
</div>
|
||||
<input type="text" name="i" class="form-control sip" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" value="{i}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-lg-4 col-md-6 form-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Netmask</span>
|
||||
</div>
|
||||
<input type="text" name="sn" class="form-control sip" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" value="{sn}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-lg-4 col-md-6 form-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Gateway</span>
|
||||
</div>
|
||||
<input type="text" name="g" class="form-control sip" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" value="{g}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-4 col-lg-5 col-md-6 form-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">DNS 1</span>
|
||||
</div>
|
||||
<input type="text" name="d1" class="form-control sip" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" value="{d1}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-4 col-lg-5 col-md-6 form-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">DNS 2</span>
|
||||
</div>
|
||||
<input type="text" name="d2" class="form-control sip" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" value="{d2}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 form-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Power</span>
|
||||
</div>
|
||||
<input type="number" name="w" class="form-control text-right" min="0" max="{wm}" step="0.5" value="{w}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">dBm</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 form-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Power saving</span>
|
||||
</div>
|
||||
<select name="z" class="form-control">
|
||||
<option value="255">Default</option>
|
||||
<option value="0" {z0}>Off</option>
|
||||
<option value="1" {z1}>Minimum</option>
|
||||
<option value="2" {z2}>Maximum</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="/" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
1
lib/ClassicUi/include/.gitignore
vendored
1
lib/ClassicUi/include/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
root/*.h
|
||||
@@ -1,15 +0,0 @@
|
||||
static const char HEADER_CACHE_CONTROL[] PROGMEM = "Cache-Control";
|
||||
static const char HEADER_PRAGMA[] PROGMEM = "Pragma";
|
||||
static const char HEADER_EXPIRES[] PROGMEM = "Expires";
|
||||
static const char HEADER_AUTHENTICATE[] PROGMEM = "WWW-Authenticate";
|
||||
static const char HEADER_LOCATION[] PROGMEM = "Location";
|
||||
|
||||
static const char CACHE_CONTROL_NO_CACHE[] PROGMEM = "no-cache, no-store, must-revalidate";
|
||||
static const char CACHE_1HR[] PROGMEM = "public, max-age=3600";
|
||||
static const char PRAGMA_NO_CACHE[] PROGMEM = "no-cache";
|
||||
static const char EXPIRES_OFF[] PROGMEM = "-1";
|
||||
static const char AUTHENTICATE_BASIC[] PROGMEM = "Basic realm=\"Secure Area\"";
|
||||
|
||||
static const char MIME_PLAIN[] PROGMEM = "text/plain";
|
||||
static const char MIME_HTML[] PROGMEM = "text/html";
|
||||
static const char MIME_JSON[] PROGMEM = "application/json";
|
||||
@@ -1,146 +0,0 @@
|
||||
#ifndef _AMSWEBSERVER_h
|
||||
#define _AMSWEBSERVER_h
|
||||
|
||||
#define BOOTSTRAP_URL "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css"
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <MQTT.h>
|
||||
#include "AmsConfiguration.h"
|
||||
#include "HwTools.h"
|
||||
#include "AmsData.h"
|
||||
#include "AmsStorage.h"
|
||||
#include "AmsDataStorage.h"
|
||||
#include "EnergyAccounting.h"
|
||||
#include "Uptime.h"
|
||||
#include "RemoteDebug.h"
|
||||
#include "EntsoeApi.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <ESP8266httpUpdate.h>
|
||||
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
|
||||
#include <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <HTTPUpdate.h>
|
||||
#else
|
||||
#warning "Unsupported board type"
|
||||
#endif
|
||||
|
||||
#include "LittleFS.h"
|
||||
|
||||
class AmsWebServer {
|
||||
public:
|
||||
AmsWebServer(uint8_t* buf, RemoteDebug* Debug, HwTools* hw);
|
||||
void setup(AmsConfiguration*, GpioConfig*, MeterConfig*, AmsData*, AmsDataStorage*, EnergyAccounting*);
|
||||
void loop();
|
||||
void setMqtt(MQTTClient* mqtt);
|
||||
void setTimezone(Timezone* tz);
|
||||
void setMqttEnabled(bool);
|
||||
void setEntsoeApi(EntsoeApi* eapi);
|
||||
|
||||
private:
|
||||
RemoteDebug* debugger;
|
||||
bool mqttEnabled = false;
|
||||
int maxPwr = 0;
|
||||
HwTools* hw;
|
||||
Timezone* tz;
|
||||
EntsoeApi* eapi = NULL;
|
||||
AmsConfiguration* config;
|
||||
GpioConfig* gpioConfig;
|
||||
MeterConfig* meterConfig;
|
||||
WebConfig webConfig;
|
||||
AmsData* meterState;
|
||||
AmsDataStorage* ds;
|
||||
EnergyAccounting* ea = NULL;
|
||||
MQTTClient* mqtt = NULL;
|
||||
bool uploading = false;
|
||||
File file;
|
||||
bool performRestart = false;
|
||||
bool performUpgrade = false;
|
||||
bool rebootForUpgrade = false;
|
||||
#if defined(AMS2MQTT_FIRMWARE_URL)
|
||||
String customFirmwareUrl = AMS2MQTT_FIRMWARE_URL;
|
||||
#else
|
||||
String customFirmwareUrl;
|
||||
#endif
|
||||
|
||||
static const uint16_t BufferSize = 2048;
|
||||
char* buf;
|
||||
|
||||
#if defined(ESP8266)
|
||||
ESP8266WebServer server;
|
||||
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
|
||||
WebServer server;
|
||||
#endif
|
||||
|
||||
bool checkSecurity(byte level);
|
||||
|
||||
void indexHtml();
|
||||
void applicationJs();
|
||||
void temperature();
|
||||
void temperaturePost();
|
||||
void temperatureJson();
|
||||
void configMeterHtml();
|
||||
void configMeterAdvancedHtml();
|
||||
void configWifiHtml();
|
||||
void configMqttHtml();
|
||||
void configWebHtml();
|
||||
void configDomoticzHtml();
|
||||
void configPriceApiHtml();
|
||||
void configNtpHtml();
|
||||
void configGpioHtml();
|
||||
void configDebugHtml();
|
||||
void configThresholdsHtml();
|
||||
void bootCss();
|
||||
void githubSvg();
|
||||
void dataJson();
|
||||
void dayplotJson();
|
||||
void monthplotJson();
|
||||
void energyPriceJson();
|
||||
void configFileHtml();
|
||||
void configFileDownload();
|
||||
void configFileUpload();
|
||||
|
||||
void handleSetup();
|
||||
void handleSave();
|
||||
|
||||
String getSerialSelectOptions(int selected);
|
||||
void firmwareHtml();
|
||||
void firmwarePost();
|
||||
void firmwareUpload();
|
||||
void firmwareDownload();
|
||||
void restartHtml();
|
||||
void restartPost();
|
||||
void restartWaitHtml();
|
||||
void isAliveCheck();
|
||||
|
||||
void uploadHtml(const char* label, const char* action, const char* menu);
|
||||
void deleteHtml(const char* label, const char* action, const char* menu);
|
||||
HTTPUpload& uploadFile(const char* path);
|
||||
void deleteFile(const char* path);
|
||||
void uploadPost();
|
||||
void mqttCa();
|
||||
void mqttCaUpload();
|
||||
void mqttCaDelete();
|
||||
void mqttCert();
|
||||
void mqttCertUpload();
|
||||
void mqttCertDelete();
|
||||
void mqttKey();
|
||||
void mqttKeyUpload();
|
||||
void mqttKeyDelete();
|
||||
|
||||
void factoryResetHtml();
|
||||
void factoryResetPost();
|
||||
|
||||
void notFound();
|
||||
|
||||
void printD(String fmt, ...);
|
||||
void printI(String fmt, ...);
|
||||
void printW(String fmt, ...);
|
||||
void printE(String fmt, ...);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,80 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
from css_html_js_minify import html_minify, js_minify, css_minify
|
||||
except:
|
||||
from SCons.Script import (
|
||||
ARGUMENTS,
|
||||
COMMAND_LINE_TARGETS,
|
||||
DefaultEnvironment,
|
||||
)
|
||||
env = DefaultEnvironment()
|
||||
|
||||
env.Execute(
|
||||
env.VerboseAction(
|
||||
'$PYTHONEXE -m pip install "css_html_js_minify" ',
|
||||
"Installing Python dependencies",
|
||||
)
|
||||
)
|
||||
try:
|
||||
from css_html_js_minify import html_minify, js_minify, css_minify
|
||||
except:
|
||||
print("WARN: Unable to load minifier")
|
||||
|
||||
|
||||
webroot = "lib/ClassicUi/html"
|
||||
srcroot = "lib/ClassicUi/include/root"
|
||||
|
||||
version = os.environ.get('GITHUB_TAG')
|
||||
if version == None:
|
||||
try:
|
||||
result = subprocess.run(['git','rev-parse','--short','HEAD'], capture_output=True, check=False)
|
||||
if result.returncode == 0:
|
||||
version = result.stdout.decode('utf-8').strip()
|
||||
else:
|
||||
version = "SNAPSHOT"
|
||||
except:
|
||||
version = "SNAPSHOT"
|
||||
|
||||
if os.path.exists(srcroot):
|
||||
shutil.rmtree(srcroot)
|
||||
os.mkdir(srcroot)
|
||||
else:
|
||||
os.mkdir(srcroot)
|
||||
|
||||
for filename in os.listdir(webroot):
|
||||
basename = re.sub("[^0-9a-zA-Z]+", "_", filename)
|
||||
|
||||
srcfile = webroot + "/" + filename
|
||||
dstfile = srcroot + "/" + basename + ".h"
|
||||
|
||||
varname = basename.upper()
|
||||
|
||||
with open(srcfile, encoding="utf-8") as f:
|
||||
content = f.read().replace("${version}", version)
|
||||
|
||||
try:
|
||||
if filename.endswith(".html"):
|
||||
content = html_minify(content)
|
||||
elif filename.endswith(".css"):
|
||||
content = css_minify(content)
|
||||
elif (filename.endswith(".js") and filename != 'gaugemeter.js') or filename.endswith(".json"):
|
||||
content = js_minify(content)
|
||||
except:
|
||||
print("WARN: Unable to minify")
|
||||
|
||||
with open(dstfile, "w") as dst:
|
||||
dst.write("static const char ")
|
||||
dst.write(varname)
|
||||
dst.write("[] PROGMEM = R\"==\"==(")
|
||||
dst.write(content)
|
||||
dst.write(")==\"==\";\n")
|
||||
dst.write("const int ");
|
||||
dst.write(varname)
|
||||
dst.write("_LEN PROGMEM = ");
|
||||
dst.write(str(len(content)))
|
||||
dst.write(";");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,14 +9,14 @@ public:
|
||||
DomoticzMqttHandler(MQTTClient* mqtt, char* buf, DomoticzConfig config) : AmsMqttHandler(mqtt, buf) {
|
||||
this->config = config;
|
||||
};
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||
|
||||
private:
|
||||
DomoticzConfig config;
|
||||
int energy = 0.0;
|
||||
double energy = 0.0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "DomoticzMqttHandler.h"
|
||||
#include "json/domoticz_json.h"
|
||||
|
||||
bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea) {
|
||||
bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi) {
|
||||
bool ret = false;
|
||||
if (config.elidx > 0) {
|
||||
if(data->getActiveImportCounter() > 1.0) {
|
||||
|
||||
@@ -52,6 +52,7 @@ public:
|
||||
bool update(AmsData* amsData);
|
||||
bool load();
|
||||
bool save();
|
||||
bool isInitialized();
|
||||
|
||||
double getUseThisHour();
|
||||
double getUseToday();
|
||||
|
||||
@@ -26,6 +26,10 @@ void EnergyAccounting::setTimezone(Timezone* tz) {
|
||||
this->tz = tz;
|
||||
}
|
||||
|
||||
bool EnergyAccounting::isInitialized() {
|
||||
return this->init;
|
||||
}
|
||||
|
||||
bool EnergyAccounting::update(AmsData* amsData) {
|
||||
if(config == NULL) return false;
|
||||
time_t now = time(nullptr);
|
||||
@@ -214,9 +218,10 @@ double EnergyAccounting::getProducedToday() {
|
||||
float ret = 0.0;
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH) return 0.0;
|
||||
tmElements_t utc;
|
||||
tmElements_t utc, local;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
for(int i = 0; i < currentHour; i++) {
|
||||
breakTime(now - ((currentHour - i) * 3600), utc);
|
||||
breakTime(now - ((local.Hour - i) * 3600), utc);
|
||||
ret += ds->getHourExport(utc.Hour) / 1000.0;
|
||||
}
|
||||
return ret + getProducedThisHour();
|
||||
|
||||
@@ -32,7 +32,7 @@ public:
|
||||
private:
|
||||
char currency[4];
|
||||
char measurementUnit[4];
|
||||
float points[24];
|
||||
float points[25];
|
||||
|
||||
char buf[64];
|
||||
uint8_t pos = 0;
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
struct PricesContainer {
|
||||
char currency[4];
|
||||
char measurementUnit[4];
|
||||
int32_t points[24];
|
||||
int32_t points[25];
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include "HardwareSerial.h"
|
||||
|
||||
EntsoeA44Parser::EntsoeA44Parser() {
|
||||
for(int i = 0; i < 24; i++) points[i] = ENTSOE_NO_VALUE;
|
||||
for(int i = 0; i < 25; i++) points[i] = ENTSOE_NO_VALUE;
|
||||
}
|
||||
|
||||
EntsoeA44Parser::~EntsoeA44Parser() {
|
||||
@@ -18,7 +18,7 @@ char* EntsoeA44Parser::getMeasurementUnit() {
|
||||
}
|
||||
|
||||
float EntsoeA44Parser::getPoint(uint8_t position) {
|
||||
if(position >= 24) return ENTSOE_NO_VALUE;
|
||||
if(position >= 25) return ENTSOE_NO_VALUE;
|
||||
return points[position];
|
||||
}
|
||||
|
||||
@@ -111,30 +111,7 @@ void EntsoeA44Parser::get(PricesContainer* container) {
|
||||
strcpy(container->currency, currency);
|
||||
strcpy(container->measurementUnit, measurementUnit);
|
||||
|
||||
container->points[0] = points[0] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[0] * 10000;
|
||||
container->points[1] = points[1] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[1] * 10000;
|
||||
container->points[2] = points[2] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[2] * 10000;
|
||||
container->points[3] = points[3] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[3] * 10000;
|
||||
container->points[4] = points[4] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[4] * 10000;
|
||||
container->points[5] = points[5] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[5] * 10000;
|
||||
container->points[6] = points[6] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[6] * 10000;
|
||||
container->points[7] = points[7] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[7] * 10000;
|
||||
container->points[8] = points[8] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[8] * 10000;
|
||||
container->points[9] = points[9] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[9] * 10000;
|
||||
|
||||
container->points[10] = points[10] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[10] * 10000;
|
||||
container->points[11] = points[11] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[11] * 10000;
|
||||
container->points[12] = points[12] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[12] * 10000;
|
||||
container->points[13] = points[13] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[13] * 10000;
|
||||
container->points[14] = points[14] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[14] * 10000;
|
||||
container->points[15] = points[15] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[15] * 10000;
|
||||
container->points[16] = points[16] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[16] * 10000;
|
||||
container->points[17] = points[17] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[17] * 10000;
|
||||
container->points[18] = points[18] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[18] * 10000;
|
||||
container->points[19] = points[19] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[19] * 10000;
|
||||
|
||||
container->points[20] = points[20] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[20] * 10000;
|
||||
container->points[21] = points[21] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[21] * 10000;
|
||||
container->points[22] = points[22] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[22] * 10000;
|
||||
container->points[23] = points[23] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[23] * 10000;
|
||||
for(uint8_t i = 0; i < 25; i++) {
|
||||
container->points[i] = points[i] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[i] * 10000;
|
||||
}
|
||||
}
|
||||
@@ -72,23 +72,34 @@ float EntsoeApi::getValueForHour(int8_t hour) {
|
||||
return getValueForHour(cur, hour);
|
||||
}
|
||||
|
||||
float EntsoeApi::getValueForHour(time_t cur, int8_t hour) {
|
||||
float EntsoeApi::getValueForHour(time_t ts, int8_t hour) {
|
||||
tmElements_t tm;
|
||||
if(tz != NULL)
|
||||
cur = tz->toLocal(cur);
|
||||
breakTime(cur, tm);
|
||||
int pos = tm.Hour + hour;
|
||||
int8_t pos = hour;
|
||||
|
||||
breakTime(tz->toLocal(ts), tm);
|
||||
while(tm.Hour > 0) {
|
||||
ts -= 3600;
|
||||
breakTime(tz->toLocal(ts), tm);
|
||||
pos++;
|
||||
}
|
||||
uint8_t hoursToday = 0;
|
||||
uint8_t todayDate = tm.Day;
|
||||
while(tm.Day == todayDate) {
|
||||
ts += 3600;
|
||||
breakTime(tz->toLocal(ts), tm);
|
||||
hoursToday++;
|
||||
}
|
||||
if(pos >= 48)
|
||||
return ENTSOE_NO_VALUE;
|
||||
|
||||
double value = ENTSOE_NO_VALUE;
|
||||
double multiplier = config->multiplier / 1000.0;
|
||||
if(pos > 23) {
|
||||
if(pos >= hoursToday) {
|
||||
if(tomorrow == NULL)
|
||||
return ENTSOE_NO_VALUE;
|
||||
if(tomorrow->points[pos-24] == ENTSOE_NO_VALUE)
|
||||
if(tomorrow->points[pos-hoursToday] == ENTSOE_NO_VALUE)
|
||||
return ENTSOE_NO_VALUE;
|
||||
value = tomorrow->points[pos-24] / 10000.0;
|
||||
value = tomorrow->points[pos-hoursToday] / 10000.0;
|
||||
if(strcmp(tomorrow->measurementUnit, "KWH") == 0) {
|
||||
// Multiplier is 1
|
||||
} else if(strcmp(tomorrow->measurementUnit, "MWH") == 0) {
|
||||
@@ -96,7 +107,7 @@ float EntsoeApi::getValueForHour(time_t cur, int8_t hour) {
|
||||
} else {
|
||||
return ENTSOE_NO_VALUE;
|
||||
}
|
||||
float mult = getCurrencyMultiplier(tomorrow->currency, config->currency, cur);
|
||||
float mult = getCurrencyMultiplier(tomorrow->currency, config->currency, time(nullptr));
|
||||
if(mult == 0) return ENTSOE_NO_VALUE;
|
||||
multiplier *= mult;
|
||||
} else if(pos >= 0) {
|
||||
@@ -112,7 +123,7 @@ float EntsoeApi::getValueForHour(time_t cur, int8_t hour) {
|
||||
} else {
|
||||
return ENTSOE_NO_VALUE;
|
||||
}
|
||||
float mult = getCurrencyMultiplier(today->currency, config->currency, cur);
|
||||
float mult = getCurrencyMultiplier(today->currency, config->currency, time(nullptr));
|
||||
if(mult == 0) return ENTSOE_NO_VALUE;
|
||||
multiplier *= mult;
|
||||
}
|
||||
@@ -161,6 +172,8 @@ bool EntsoeApi::loop() {
|
||||
return today != NULL; // Only trigger MQTT publish if we have todays prices.
|
||||
}
|
||||
|
||||
bool readyToFetchForTomorrow = tomorrow == NULL && (tm.Hour > 13 || (tm.Hour == 13 && tm.Minute >= tomorrowFetchMinute)) && (lastTomorrowFetch == 0 || now - lastTomorrowFetch > 900000);
|
||||
|
||||
if(today == NULL && (lastTodayFetch == 0 || now - lastTodayFetch > 60000)) {
|
||||
try {
|
||||
lastTodayFetch = now;
|
||||
@@ -169,12 +182,12 @@ bool EntsoeApi::loop() {
|
||||
if(lastError == 0) lastError = 900;
|
||||
today = NULL;
|
||||
}
|
||||
return today != NULL; // Only trigger MQTT publish if we have todays prices.
|
||||
return today != NULL && !readyToFetchForTomorrow; // Only trigger MQTT publish if we have todays prices and we are not immediately ready to fetch price for tomorrow.
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
if(readyToFetchForTomorrow) {
|
||||
try {
|
||||
lastTomorrowFetch = now;
|
||||
tomorrow = fetchPrices(t+SECS_PER_DAY);
|
||||
@@ -334,7 +347,37 @@ PricesContainer* EntsoeApi::fetchPrices(time_t t) {
|
||||
printD("Receiving data");
|
||||
data = http.getString();
|
||||
http.end();
|
||||
lastError = 0;
|
||||
|
||||
uint8_t* content = (uint8_t*) (data.c_str());
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
printD("Received content for prices:");
|
||||
debugPrint(content, 0, data.length());
|
||||
}
|
||||
|
||||
DataParserContext ctx;
|
||||
ctx.length = data.length();
|
||||
GCMParser gcm(key, auth);
|
||||
int8_t gcmRet = gcm.parse(content, ctx);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
printD("Decrypted content for prices:");
|
||||
debugPrint(content, 0, data.length());
|
||||
}
|
||||
if(gcmRet > 0) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) Price data starting at: %d\n", gcmRet);
|
||||
PricesContainer* ret = new PricesContainer();
|
||||
for(uint8_t i = 0; i < 25; i++) {
|
||||
ret->points[i] = ENTSOE_NO_VALUE;
|
||||
}
|
||||
memcpy(ret, content+gcmRet, sizeof(*ret));
|
||||
for(uint8_t i = 0; i < 25; i++) {
|
||||
ret->points[i] = ntohl(ret->points[i]);
|
||||
}
|
||||
lastError = 0;
|
||||
return ret;
|
||||
} else {
|
||||
lastError = gcmRet;
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("(EntsoeApi) Error code while decrypting prices: %d\n", gcmRet);
|
||||
}
|
||||
} else {
|
||||
lastError = status;
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("(EntsoeApi) Communication error, returned status: %d\n", status);
|
||||
@@ -344,33 +387,6 @@ PricesContainer* EntsoeApi::fetchPrices(time_t t) {
|
||||
http.end();
|
||||
}
|
||||
}
|
||||
uint8_t* content = (uint8_t*) (data.c_str());
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
printD("Received content for prices:");
|
||||
debugPrint(content, 0, data.length());
|
||||
}
|
||||
|
||||
DataParserContext ctx;
|
||||
ctx.length = data.length();
|
||||
GCMParser gcm(key, auth);
|
||||
int8_t gcmRet = gcm.parse(content, ctx);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
printD("Decrypted content for prices:");
|
||||
debugPrint(content, 0, data.length());
|
||||
}
|
||||
if(gcmRet > 0) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) Price data starting at: %d\n", gcmRet);
|
||||
PricesContainer* ret = new PricesContainer();
|
||||
memcpy(ret, content+gcmRet, sizeof(*ret));
|
||||
for(uint8_t i = 0; i < 24; i++) {
|
||||
ret->points[i] = ntohl(ret->points[i]);
|
||||
}
|
||||
lastError = 0;
|
||||
return ret;
|
||||
} else {
|
||||
lastError = gcmRet;
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("(EntsoeApi) Error code while decrypting prices: %d\n", gcmRet);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define _HOMEASSISTANTMQTTHANDLER_H
|
||||
|
||||
#include "AmsMqttHandler.h"
|
||||
#include "HomeAssistantStatic.h"
|
||||
|
||||
class HomeAssistantMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
@@ -9,12 +10,28 @@ public:
|
||||
this->clientId = clientId;
|
||||
this->topic = String(topic);
|
||||
this->hw = hw;
|
||||
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = false;
|
||||
};
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||
|
||||
void publishSensor(const HomeAssistantSensor& sensor);
|
||||
void publishList1Sensors();
|
||||
void publishList1ExportSensors();
|
||||
void publishList2Sensors();
|
||||
void publishList2ExportSensors();
|
||||
void publishList3Sensors();
|
||||
void publishList3ExportSensors();
|
||||
void publishList4Sensors();
|
||||
void publishList4ExportSensors();
|
||||
void publishRealtimeSensors(EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
void publishRealtimeExportSensors(EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
void publishTemperatureSensor(uint8_t index, String id);
|
||||
void publishPriceSensors(EntsoeApi* eapi);
|
||||
void publishSystemSensors();
|
||||
|
||||
private:
|
||||
String haTopic = "homeassistant/sensor/";
|
||||
|
||||
@@ -26,7 +43,9 @@ private:
|
||||
#endif
|
||||
String haManuf = "AmsToMqttBridge";
|
||||
|
||||
bool autodiscoverInit = false;
|
||||
bool l1Init, l2Init, l2eInit, l3Init, l3eInit, l4Init, l4eInit, rtInit, rteInit, pInit, sInit;
|
||||
bool tInit[32] = {false};
|
||||
bool prInit[38] = {false};
|
||||
|
||||
String clientId;
|
||||
String topic;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
struct HomeAssistantSensor {
|
||||
typedef struct HomeAssistantSensor {
|
||||
const char* name;
|
||||
const char* topic;
|
||||
const char* path;
|
||||
@@ -13,69 +13,100 @@ struct HomeAssistantSensor {
|
||||
};
|
||||
|
||||
|
||||
const uint8_t HA_SENSOR_COUNT PROGMEM = 60;
|
||||
HomeAssistantSensor HA_SENSORS[HA_SENSOR_COUNT] PROGMEM = {
|
||||
{"Status", "/state", "rssi", "dBm", "signal_strength", "\"measurement\""},
|
||||
{"Supply volt", "/state", "vcc", "V", "voltage", "\"measurement\""},
|
||||
{"Temperature", "/state", "temp", "°C", "temperature", "\"measurement\""},
|
||||
{"Active import", "/power", "P", "W", "power", "\"measurement\""},
|
||||
{"L1 active import", "/power", "P1", "W", "power", "\"measurement\""},
|
||||
{"L2 active import", "/power", "P2", "W", "power", "\"measurement\""},
|
||||
{"L3 active import", "/power", "P3", "W", "power", "\"measurement\""},
|
||||
const uint8_t List1SensorCount PROGMEM = 1;
|
||||
const HomeAssistantSensor List1Sensors[List1SensorCount] PROGMEM = {
|
||||
{"Active import", "/power", "P", "W", "power", "\"measurement\""}
|
||||
};
|
||||
|
||||
const uint8_t List2SensorCount PROGMEM = 8;
|
||||
const HomeAssistantSensor List2Sensors[List2SensorCount] PROGMEM = {
|
||||
{"Reactive import", "/power", "Q", "var", "reactive_power", "\"measurement\""},
|
||||
{"Active export", "/power", "PO", "W", "power", "\"measurement\""},
|
||||
{"L1 active export", "/power", "PO1", "W", "power", "\"measurement\""},
|
||||
{"L2 active export", "/power", "PO2", "W", "power", "\"measurement\""},
|
||||
{"L3 active export", "/power", "PO3", "W", "power", "\"measurement\""},
|
||||
{"Reactive export", "/power", "QO", "var", "reactive_power", "\"measurement\""},
|
||||
{"L1 current", "/power", "I1", "A", "current", "\"measurement\""},
|
||||
{"L2 current", "/power", "I2", "A", "current", "\"measurement\""},
|
||||
{"L3 current", "/power", "I3", "A", "current", "\"measurement\""},
|
||||
{"L1 voltage", "/power", "U1", "V", "voltage", "\"measurement\""},
|
||||
{"L2 voltage", "/power", "U2", "V", "voltage", "\"measurement\""},
|
||||
{"L3 voltage", "/power", "U3", "V", "voltage", "\"measurement\""},
|
||||
{"L3 voltage", "/power", "U3", "V", "voltage", "\"measurement\""}
|
||||
};
|
||||
|
||||
const uint8_t List2ExportSensorCount PROGMEM = 1;
|
||||
const HomeAssistantSensor List2ExportSensors[List2ExportSensorCount] PROGMEM = {
|
||||
{"Active export", "/power", "PO", "W", "power", "\"measurement\""}
|
||||
};
|
||||
|
||||
const uint8_t List3SensorCount PROGMEM = 3;
|
||||
const HomeAssistantSensor List3Sensors[List3SensorCount] PROGMEM = {
|
||||
{"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\""},
|
||||
{"Accumulated reactive import","/energy", "tQI", "", "energy", "\"total_increasing\""},
|
||||
{"Accumulated reactive export","/energy", "tQO", "", "energy", "\"total_increasing\""}
|
||||
};
|
||||
|
||||
const uint8_t List3ExportSensorCount PROGMEM = 1;
|
||||
const HomeAssistantSensor List3ExportSensors[List3ExportSensorCount] PROGMEM = {
|
||||
{"Accumulated active export", "/energy", "tPO", "kWh", "energy", "\"total_increasing\""}
|
||||
};
|
||||
|
||||
const uint8_t List4SensorCount PROGMEM = 7;
|
||||
const HomeAssistantSensor List4Sensors[List4SensorCount] PROGMEM = {
|
||||
{"Power factor", "/power", "PF", "%", "power_factor", "\"measurement\""},
|
||||
{"L1 power factor", "/power", "PF1", "%", "power_factor", "\"measurement\""},
|
||||
{"L2 power factor", "/power", "PF2", "%", "power_factor", "\"measurement\""},
|
||||
{"L3 power factor", "/power", "PF3", "%", "power_factor", "\"measurement\""},
|
||||
{"Price current hour", "/prices", "prices['0']", "", "monetary", ""},
|
||||
{"Price next hour", "/prices", "prices['1']", "", "monetary", ""},
|
||||
{"Price in two hour", "/prices", "prices['2']", "", "monetary", ""},
|
||||
{"Price in three hour", "/prices", "prices['3']", "", "monetary", ""},
|
||||
{"Price in four hour", "/prices", "prices['4']", "", "monetary", ""},
|
||||
{"Price in five hour", "/prices", "prices['5']", "", "monetary", ""},
|
||||
{"Price in six hour", "/prices", "prices['6']", "", "monetary", ""},
|
||||
{"Price in seven hour", "/prices", "prices['7']", "", "monetary", ""},
|
||||
{"Price in eight hour", "/prices", "prices['8']", "", "monetary", ""},
|
||||
{"Price in nine hour", "/prices", "prices['9']", "", "monetary", ""},
|
||||
{"Price in ten hour", "/prices", "prices['10']", "", "monetary", ""},
|
||||
{"Price in eleven hour", "/prices", "prices['11']", "", "monetary", ""},
|
||||
{"L1 active import", "/power", "P1", "W", "power", "\"measurement\""},
|
||||
{"L2 active import", "/power", "P2", "W", "power", "\"measurement\""},
|
||||
{"L3 active import", "/power", "P3", "W", "power", "\"measurement\""}
|
||||
};
|
||||
|
||||
const uint8_t List4ExportSensorCount PROGMEM = 3;
|
||||
const HomeAssistantSensor List4ExportSensors[List4ExportSensorCount] PROGMEM = {
|
||||
{"L1 active export", "/power", "PO1", "W", "power", "\"measurement\""},
|
||||
{"L2 active export", "/power", "PO2", "W", "power", "\"measurement\""},
|
||||
{"L3 active export", "/power", "PO3", "W", "power", "\"measurement\""}
|
||||
};
|
||||
|
||||
const uint8_t RealtimeSensorCount PROGMEM = 8;
|
||||
const HomeAssistantSensor RealtimeSensors[RealtimeSensorCount] PROGMEM = {
|
||||
{"Month max", "/realtime","max", "kWh", "energy", ""},
|
||||
{"Tariff threshold", "/realtime","threshold", "kWh", "energy", ""},
|
||||
{"Current hour used", "/realtime","hour.use", "kWh", "energy", ""},
|
||||
{"Current hour cost", "/realtime","hour.cost", "", "monetary", ""},
|
||||
{"Current day used", "/realtime","day.use", "kWh", "energy", ""},
|
||||
{"Current day cost", "/realtime","day.cost", "", "monetary", ""},
|
||||
{"Current month used", "/realtime","month.use", "kWh", "energy", ""},
|
||||
{"Current month cost", "/realtime","month.cost", "", "monetary", ""}
|
||||
};
|
||||
|
||||
const uint8_t RealtimeExportSensorCount PROGMEM = 6;
|
||||
const HomeAssistantSensor RealtimeExportSensors[RealtimeExportSensorCount] PROGMEM = {
|
||||
{"Current hour produced", "/realtime","hour.produced", "kWh", "energy", ""},
|
||||
{"Current hour income", "/realtime","hour.income", "", "monetary", ""},
|
||||
{"Current day produced", "/realtime","day.produced", "kWh", "energy", ""},
|
||||
{"Current day income", "/realtime","day.income", "", "monetary", ""},
|
||||
{"Current month produced", "/realtime","month.produced", "kWh", "energy", ""},
|
||||
{"Current month income", "/realtime","month.income", "", "monetary", ""}
|
||||
};
|
||||
|
||||
const HomeAssistantSensor RealtimePeakSensor PROGMEM = {"Current month peak %d", "/realtime", "peaks[%d]", "kWh", "energy", ""};
|
||||
|
||||
const uint8_t PriceSensorCount PROGMEM = 5;
|
||||
const HomeAssistantSensor PriceSensors[PriceSensorCount] PROGMEM = {
|
||||
{"Minimum price ahead", "/prices", "prices.min", "", "monetary", ""},
|
||||
{"Maximum price ahead", "/prices", "prices.max", "", "monetary", ""},
|
||||
{"Cheapest 1hr period ahead", "/prices", "prices.cheapest1hr","", "timestamp", ""},
|
||||
{"Cheapest 3hr period ahead", "/prices", "prices.cheapest3hr","", "timestamp", ""},
|
||||
{"Cheapest 6hr period ahead", "/prices", "prices.cheapest6hr","", "timestamp", ""},
|
||||
{"Month max", "/realtime","max", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Tariff threshold", "/realtime","threshold", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current hour used", "/realtime","hour.use", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current hour cost", "/realtime","hour.cost", "", "monetary", "\"total_increasing\""},
|
||||
{"Current hour produced", "/realtime","hour.produced", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current day used", "/realtime","day.use", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current day cost", "/realtime","day.cost", "", "monetary", "\"total_increasing\""},
|
||||
{"Current day produced", "/realtime","day.produced", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current month used", "/realtime","month.use", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current month cost", "/realtime","month.cost", "", "monetary", "\"total_increasing\""},
|
||||
{"Current month produced", "/realtime","month.produced", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current month peak 1", "/realtime","peaks[0]", "kWh", "energy", ""},
|
||||
{"Current month peak 2", "/realtime","peaks[1]", "kWh", "energy", ""},
|
||||
{"Current month peak 3", "/realtime","peaks[2]", "kWh", "energy", ""},
|
||||
{"Current month peak 4", "/realtime","peaks[3]", "kWh", "energy", ""},
|
||||
{"Current month peak 5", "/realtime","peaks[4]", "kWh", "energy", ""},
|
||||
{"Cheapest 6hr period ahead", "/prices", "prices.cheapest6hr","", "timestamp", ""}
|
||||
};
|
||||
|
||||
const HomeAssistantSensor PriceSensor PROGMEM = {"Price in %02d %s", "/prices", "prices['%d']", "", "monetary", ""};
|
||||
|
||||
const uint8_t SystemSensorCount PROGMEM = 2;
|
||||
const HomeAssistantSensor SystemSensors[SystemSensorCount] PROGMEM = {
|
||||
{"Status", "/state", "rssi", "dBm", "signal_strength", "\"measurement\""},
|
||||
{"Supply volt", "/state", "vcc", "V", "voltage", "\"measurement\""}
|
||||
};
|
||||
|
||||
const HomeAssistantSensor TemperatureSensor PROGMEM = {"Temperature sensor %s", "/temperatures", "temperatures['%s']", "°C", "temperature", "\"measurement\""};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -5,16 +5,19 @@
|
||||
"hour" : {
|
||||
"use" : %.2f,
|
||||
"cost" : %.2f,
|
||||
"produced" : %.2f
|
||||
"produced" : %.2f,
|
||||
"income" : %.2f
|
||||
},
|
||||
"day" : {
|
||||
"use" : %.2f,
|
||||
"cost" : %.2f,
|
||||
"produced" : %.2f
|
||||
"produced" : %.2f,
|
||||
"income" : %.2f
|
||||
},
|
||||
"month" : {
|
||||
"use" : %.2f,
|
||||
"cost" : %.2f,
|
||||
"produced" : %.2f
|
||||
"produced" : %.2f,
|
||||
"income" : %.2f
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "HomeAssistantMqttHandler.h"
|
||||
#include "HomeAssistantStatic.h"
|
||||
#include "hexutils.h"
|
||||
#include "Uptime.h"
|
||||
#include "version.h"
|
||||
@@ -12,11 +11,13 @@
|
||||
#include "json/hadiscover_json.h"
|
||||
#include "json/realtime_json.h"
|
||||
|
||||
bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea) {
|
||||
bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
return false;
|
||||
|
||||
if(data->getListType() >= 3) { // publish energy counts
|
||||
publishList3Sensors();
|
||||
if(data->getActiveExportCounter() > 0.0) publishList3ExportSensors();
|
||||
snprintf_P(json, BufferSize, HA2_JSON,
|
||||
data->getActiveImportCounter(),
|
||||
data->getActiveExportCounter(),
|
||||
@@ -30,11 +31,14 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En
|
||||
String meterModel = data->getMeterModel();
|
||||
meterModel.replace("\\", "\\\\");
|
||||
if(data->getListType() == 1) { // publish power counts
|
||||
publishList1Sensors();
|
||||
snprintf_P(json, BufferSize, HA1_JSON,
|
||||
data->getActiveImportPower()
|
||||
);
|
||||
mqtt->publish(topic + "/power", json);
|
||||
} else if(data->getListType() <= 3) { // publish power counts and volts/amps
|
||||
publishList2Sensors();
|
||||
if(data->getActiveExportPower() > 0) publishList2ExportSensors();
|
||||
snprintf_P(json, BufferSize, HA3_JSON,
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
@@ -52,6 +56,8 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En
|
||||
);
|
||||
mqtt->publish(topic + "/power", json);
|
||||
} else if(data->getListType() == 4) { // publish power counts and volts/amps/phase power and PF
|
||||
publishList4Sensors();
|
||||
if(data->getL1ActiveExportPower() > 0 || data->getL2ActiveExportPower() > 0 || data->getL3ActiveExportPower() > 0) publishList4ExportSensors();
|
||||
snprintf_P(json, BufferSize, HA4_JSON,
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
@@ -80,35 +86,42 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En
|
||||
mqtt->publish(topic + "/power", json);
|
||||
}
|
||||
|
||||
String peaks = "";
|
||||
uint8_t peakCount = ea->getConfig()->hours;
|
||||
if(peakCount > 5) peakCount = 5;
|
||||
for(uint8_t i = 1; i <= peakCount; i++) {
|
||||
if(!peaks.isEmpty()) peaks += ",";
|
||||
peaks += String(ea->getPeak(i).value / 100.0, 2);
|
||||
}
|
||||
snprintf_P(json, BufferSize, REALTIME_JSON,
|
||||
ea->getMonthMax(),
|
||||
peaks.c_str(),
|
||||
ea->getCurrentThreshold(),
|
||||
ea->getUseThisHour(),
|
||||
ea->getCostThisHour(),
|
||||
ea->getProducedThisHour(),
|
||||
ea->getUseToday(),
|
||||
ea->getCostToday(),
|
||||
ea->getProducedToday(),
|
||||
ea->getUseThisMonth(),
|
||||
ea->getCostThisMonth(),
|
||||
ea->getProducedThisMonth()
|
||||
);
|
||||
mqtt->publish(topic + "/realtime", json);
|
||||
if(ea->isInitialized()) {
|
||||
publishRealtimeSensors(ea, eapi);
|
||||
if(ea->getProducedThisHour() > 0.0 || ea->getProducedToday() > 0.0 || ea->getProducedThisMonth() > 0.0) publishRealtimeExportSensors(ea, eapi);
|
||||
String peaks = "";
|
||||
uint8_t peakCount = ea->getConfig()->hours;
|
||||
if(peakCount > 5) peakCount = 5;
|
||||
for(uint8_t i = 1; i <= peakCount; i++) {
|
||||
if(!peaks.isEmpty()) peaks += ",";
|
||||
peaks += String(ea->getPeak(i).value / 100.0, 2);
|
||||
}
|
||||
snprintf_P(json, BufferSize, REALTIME_JSON,
|
||||
ea->getMonthMax(),
|
||||
peaks.c_str(),
|
||||
ea->getCurrentThreshold(),
|
||||
ea->getUseThisHour(),
|
||||
ea->getCostThisHour(),
|
||||
ea->getProducedThisHour(),
|
||||
ea->getIncomeThisHour(),
|
||||
ea->getUseToday(),
|
||||
ea->getCostToday(),
|
||||
ea->getProducedToday(),
|
||||
ea->getIncomeToday(),
|
||||
ea->getUseThisMonth(),
|
||||
ea->getCostThisMonth(),
|
||||
ea->getProducedThisMonth(),
|
||||
ea->getIncomeThisMonth()
|
||||
);
|
||||
mqtt->publish(topic + "/realtime", json);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) {
|
||||
int count = hw->getTempSensorCount();
|
||||
if(count == 0) return false;
|
||||
if(count < 2) return false;
|
||||
|
||||
int size = 32 + (count * 26);
|
||||
|
||||
@@ -119,11 +132,13 @@ bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwT
|
||||
TempSensorData* data = hw->getTempSensorData(i);
|
||||
if(data != NULL) {
|
||||
char* pos = buf+strlen(buf);
|
||||
String id = toHex(data->address, 8);
|
||||
snprintf(pos, 26, "\"%s\":%.2f,",
|
||||
toHex(data->address, 8).c_str(),
|
||||
id.c_str(),
|
||||
data->lastRead
|
||||
);
|
||||
data->changed = false;
|
||||
publishTemperatureSensor(i+1, id);
|
||||
delay(1);
|
||||
}
|
||||
}
|
||||
@@ -138,14 +153,16 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
if(eapi->getValueForHour(0) == ENTSOE_NO_VALUE)
|
||||
return false;
|
||||
|
||||
publishPriceSensors(eapi);
|
||||
|
||||
time_t now = time(nullptr);
|
||||
|
||||
float min1hr = 0.0, min3hr = 0.0, min6hr = 0.0;
|
||||
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
|
||||
float min = INT16_MAX, max = INT16_MIN;
|
||||
float values[24];
|
||||
for(int i = 0;i < 24; i++) values[i] = ENTSOE_NO_VALUE;
|
||||
for(uint8_t i = 0; i < 24; i++) {
|
||||
float values[38];
|
||||
for(int i = 0;i < 38; i++) values[i] = ENTSOE_NO_VALUE;
|
||||
for(uint8_t i = 0; i < 38; i++) {
|
||||
float val = eapi->getValueForHour(now, i);
|
||||
values[i] = val;
|
||||
|
||||
@@ -232,6 +249,32 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
values[9],
|
||||
values[10],
|
||||
values[11],
|
||||
values[12],
|
||||
values[13],
|
||||
values[14],
|
||||
values[15],
|
||||
values[16],
|
||||
values[17],
|
||||
values[18],
|
||||
values[19],
|
||||
values[20],
|
||||
values[21],
|
||||
values[22],
|
||||
values[23],
|
||||
values[24],
|
||||
values[25],
|
||||
values[26],
|
||||
values[27],
|
||||
values[28],
|
||||
values[29],
|
||||
values[30],
|
||||
values[31],
|
||||
values[32],
|
||||
values[33],
|
||||
values[34],
|
||||
values[35],
|
||||
values[36],
|
||||
values[37],
|
||||
min == INT16_MAX ? 0.0 : min,
|
||||
max == INT16_MIN ? 0.0 : max,
|
||||
ts1hr,
|
||||
@@ -245,6 +288,9 @@ bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, Energ
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
return false;
|
||||
|
||||
publishSystemSensors();
|
||||
if(hw->getTemperature() > -50) publishTemperatureSensor(0, "");
|
||||
|
||||
snprintf_P(json, BufferSize, JSONSYS_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
clientId.c_str(),
|
||||
@@ -254,64 +300,207 @@ bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, Energ
|
||||
hw->getTemperature(),
|
||||
VERSION
|
||||
);
|
||||
mqtt->publish(topic + "/state", json);
|
||||
|
||||
if(!autodiscoverInit) {
|
||||
#if defined(ESP8266)
|
||||
String haUID = WiFi.hostname();
|
||||
#elif defined(ESP32)
|
||||
String haUID = WiFi.getHostname();
|
||||
#endif
|
||||
String haUrl = "http://" + haUID + ".local/";
|
||||
// Could this be necessary? haUID.replace("-", "_");
|
||||
uint8_t peakCount = ea->getConfig()->hours;
|
||||
if(peakCount > 5) peakCount = 5;
|
||||
|
||||
uint8_t peaks = 0;
|
||||
for(int i=0;i<HA_SENSOR_COUNT;i++) {
|
||||
HomeAssistantSensor sensor = HA_SENSORS[i];
|
||||
String uid = String(sensor.path);
|
||||
uid.replace(".", "");
|
||||
uid.replace("[", "");
|
||||
uid.replace("]", "");
|
||||
uid.replace("'", "");
|
||||
String uom = String(sensor.uom);
|
||||
if(strncmp(sensor.devcl, "monetary", 8) == 0) {
|
||||
if(eapi == NULL) continue;
|
||||
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,
|
||||
haUID.c_str(), uid.c_str(),
|
||||
haUID.c_str(), uid.c_str(),
|
||||
uom.c_str(),
|
||||
sensor.path,
|
||||
sensor.devcl,
|
||||
haUID.c_str(),
|
||||
haName.c_str(),
|
||||
haModel.c_str(),
|
||||
VERSION,
|
||||
haManuf.c_str(),
|
||||
haUrl.c_str(),
|
||||
strlen_P(sensor.stacl) > 0 ? ", \"stat_cla\" :" : "",
|
||||
strlen_P(sensor.stacl) > 0 ? (char *) FPSTR(sensor.stacl) : ""
|
||||
);
|
||||
mqtt->publish(haTopic + haUID + "_" + uid.c_str() + "/config", json, true, 0);
|
||||
}
|
||||
|
||||
autodiscoverInit = true;
|
||||
}
|
||||
return true;
|
||||
return mqtt->publish(topic + "/state", json);
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishSensor(const HomeAssistantSensor& sensor) {
|
||||
#if defined(ESP8266)
|
||||
String haUID = WiFi.hostname();
|
||||
#elif defined(ESP32)
|
||||
String haUID = WiFi.getHostname();
|
||||
#endif
|
||||
String haUrl = "http://" + haUID + ".local/";
|
||||
|
||||
String uid = String(sensor.path);
|
||||
uid.replace(".", "");
|
||||
uid.replace("[", "");
|
||||
uid.replace("]", "");
|
||||
uid.replace("'", "");
|
||||
String uom = String(sensor.uom);
|
||||
snprintf_P(json, BufferSize, HADISCOVER_JSON,
|
||||
sensor.name,
|
||||
topic.c_str(), sensor.topic,
|
||||
haUID.c_str(), uid.c_str(),
|
||||
haUID.c_str(), uid.c_str(),
|
||||
uom.c_str(),
|
||||
sensor.path,
|
||||
sensor.devcl,
|
||||
haUID.c_str(),
|
||||
haName.c_str(),
|
||||
haModel.c_str(),
|
||||
VERSION,
|
||||
haManuf.c_str(),
|
||||
haUrl.c_str(),
|
||||
strlen_P(sensor.stacl) > 0 ? ", \"stat_cla\" :" : "",
|
||||
strlen_P(sensor.stacl) > 0 ? (char *) FPSTR(sensor.stacl) : ""
|
||||
);
|
||||
mqtt->publish(haTopic + haUID + "_" + uid.c_str() + "/config", json, true, 0);
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishList1Sensors() {
|
||||
if(l1Init) return;
|
||||
for(uint8_t i = 0; i < List1SensorCount; i++) {
|
||||
publishSensor(List1Sensors[i]);
|
||||
}
|
||||
l1Init = true;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishList2Sensors() {
|
||||
if(l2Init) return;
|
||||
for(uint8_t i = 0; i < List2SensorCount; i++) {
|
||||
publishSensor(List2Sensors[i]);
|
||||
}
|
||||
l2Init = true;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishList2ExportSensors() {
|
||||
if(l2eInit) return;
|
||||
for(uint8_t i = 0; i < List2ExportSensorCount; i++) {
|
||||
publishSensor(List2ExportSensors[i]);
|
||||
}
|
||||
l2eInit = true;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishList3Sensors() {
|
||||
if(l3Init) return;
|
||||
for(uint8_t i = 0; i < List3SensorCount; i++) {
|
||||
publishSensor(List3Sensors[i]);
|
||||
}
|
||||
l3Init = true;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishList3ExportSensors() {
|
||||
if(l3eInit) return;
|
||||
for(uint8_t i = 0; i < List3ExportSensorCount; i++) {
|
||||
publishSensor(List3ExportSensors[i]);
|
||||
}
|
||||
l3eInit = true;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishList4Sensors() {
|
||||
if(l4Init) return;
|
||||
for(uint8_t i = 0; i < List4SensorCount; i++) {
|
||||
publishSensor(List4Sensors[i]);
|
||||
}
|
||||
l4Init = true;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishList4ExportSensors() {
|
||||
if(l4eInit) return;
|
||||
for(uint8_t i = 0; i < List4ExportSensorCount; i++) {
|
||||
publishSensor(List4ExportSensors[i]);
|
||||
}
|
||||
l4eInit = true;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishRealtimeSensors(EnergyAccounting* ea, EntsoeApi* eapi) {
|
||||
if(rtInit) return;
|
||||
for(uint8_t i = 0; i < RealtimeSensorCount; i++) {
|
||||
HomeAssistantSensor sensor = RealtimeSensors[i];
|
||||
if(strncmp(sensor.devcl, "monetary", 8) == 0) {
|
||||
if(eapi == NULL) continue;
|
||||
sensor.uom = eapi->getCurrency();
|
||||
}
|
||||
publishSensor(sensor);
|
||||
}
|
||||
uint8_t peakCount = ea->getConfig()->hours;
|
||||
if(peakCount > 5) peakCount = 5;
|
||||
for(uint8_t i = 0; i < peakCount; i++) {
|
||||
char name[strlen(RealtimePeakSensor.name)];
|
||||
snprintf(name, strlen(RealtimePeakSensor.name), RealtimePeakSensor.name, i+1);
|
||||
char path[strlen(RealtimePeakSensor.path)];
|
||||
snprintf(path, strlen(RealtimePeakSensor.path), RealtimePeakSensor.path, i+1);
|
||||
HomeAssistantSensor sensor = {
|
||||
name,
|
||||
RealtimePeakSensor.topic,
|
||||
path,
|
||||
RealtimePeakSensor.uom,
|
||||
RealtimePeakSensor.devcl,
|
||||
RealtimePeakSensor.stacl
|
||||
};
|
||||
publishSensor(sensor);
|
||||
}
|
||||
rtInit = true;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishRealtimeExportSensors(EnergyAccounting* ea, EntsoeApi* eapi) {
|
||||
if(rteInit) return;
|
||||
for(uint8_t i = 0; i < RealtimeExportSensorCount; i++) {
|
||||
HomeAssistantSensor sensor = RealtimeExportSensors[i];
|
||||
if(strncmp(sensor.devcl, "monetary", 8) == 0) {
|
||||
if(eapi == NULL) continue;
|
||||
sensor.uom = eapi->getCurrency();
|
||||
}
|
||||
publishSensor(sensor);
|
||||
}
|
||||
rteInit = true;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishTemperatureSensor(uint8_t index, String id) {
|
||||
if(index > 32) return;
|
||||
if(tInit[index]) return;
|
||||
char name[strlen(TemperatureSensor.name)+id.length()];
|
||||
snprintf(name, strlen(TemperatureSensor.name)+id.length(), TemperatureSensor.name, id.c_str());
|
||||
|
||||
char path[strlen(TemperatureSensor.path)+id.length()];
|
||||
if(index == 0) {
|
||||
memcpy_P(path, PSTR("temp\0"), 5);
|
||||
} else {
|
||||
snprintf(path, strlen(TemperatureSensor.path)+id.length(), TemperatureSensor.path, id.c_str());
|
||||
}
|
||||
HomeAssistantSensor sensor = {
|
||||
name,
|
||||
index == 0 ? SystemSensors[0].topic : TemperatureSensor.topic,
|
||||
path,
|
||||
TemperatureSensor.uom,
|
||||
TemperatureSensor.devcl,
|
||||
TemperatureSensor.stacl
|
||||
};
|
||||
publishSensor(sensor);
|
||||
tInit[index] = true;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishPriceSensors(EntsoeApi* eapi) {
|
||||
if(eapi == NULL) return;
|
||||
String uom = String(eapi->getCurrency()) + "/kWh";
|
||||
|
||||
if(!pInit) {
|
||||
for(uint8_t i = 0; i < PriceSensorCount; i++) {
|
||||
HomeAssistantSensor sensor = PriceSensors[i];
|
||||
if(strncmp(sensor.devcl, "monetary", 8) == 0) {
|
||||
sensor.uom = uom.c_str();
|
||||
}
|
||||
publishSensor(sensor);
|
||||
}
|
||||
pInit = true;
|
||||
}
|
||||
for(uint8_t i = 0; i < 38; i++) {
|
||||
if(prInit[i]) continue;
|
||||
float val = eapi->getValueForHour(i);
|
||||
if(val == ENTSOE_NO_VALUE) continue;
|
||||
|
||||
char name[strlen(PriceSensor.name)+2];
|
||||
snprintf(name, strlen(PriceSensor.name)+2, PriceSensor.name, i, i == 1 ? "hour" : "hours");
|
||||
char path[strlen(PriceSensor.path)+1];
|
||||
snprintf(path, strlen(PriceSensor.path)+1, PriceSensor.path, i);
|
||||
HomeAssistantSensor sensor = {
|
||||
i == 0 ? "Price current hour" : name,
|
||||
PriceSensor.topic,
|
||||
path,
|
||||
uom.c_str(),
|
||||
PriceSensor.devcl,
|
||||
PriceSensor.stacl
|
||||
};
|
||||
publishSensor(sensor);
|
||||
prInit[i] = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishSystemSensors() {
|
||||
if(sInit) return;
|
||||
for(uint8_t i = 0; i < SystemSensorCount; i++) {
|
||||
publishSensor(SystemSensors[i]);
|
||||
}
|
||||
sInit = true;
|
||||
}
|
||||
@@ -10,7 +10,7 @@ public:
|
||||
this->topic = String(topic);
|
||||
this->hw = hw;
|
||||
};
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||
|
||||
@@ -13,6 +13,32 @@
|
||||
"9" : %.4f,
|
||||
"10" : %.4f,
|
||||
"11" : %.4f,
|
||||
"12" : %.4f,
|
||||
"13" : %.4f,
|
||||
"14" : %.4f,
|
||||
"15" : %.4f,
|
||||
"16" : %.4f,
|
||||
"17" : %.4f,
|
||||
"18" : %.4f,
|
||||
"19" : %.4f,
|
||||
"20" : %.4f,
|
||||
"21" : %.4f,
|
||||
"22" : %.4f,
|
||||
"23" : %.4f,
|
||||
"24" : %.4f,
|
||||
"25" : %.4f,
|
||||
"26" : %.4f,
|
||||
"27" : %.4f,
|
||||
"28" : %.4f,
|
||||
"29" : %.4f,
|
||||
"30" : %.4f,
|
||||
"31" : %.4f,
|
||||
"32" : %.4f,
|
||||
"33" : %.4f,
|
||||
"34" : %.4f,
|
||||
"35" : %.4f,
|
||||
"36" : %.4f,
|
||||
"37" : %.4f,
|
||||
"min" : %.4f,
|
||||
"max" : %.4f,
|
||||
"cheapest1hr" : "%s",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "json/jsonsys_json.h"
|
||||
#include "json/jsonprices_json.h"
|
||||
|
||||
bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea) {
|
||||
bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
return false;
|
||||
|
||||
@@ -183,9 +183,9 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
float min1hr = 0.0, min3hr = 0.0, min6hr = 0.0;
|
||||
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
|
||||
float min = INT16_MAX, max = INT16_MIN;
|
||||
float values[24];
|
||||
for(int i = 0;i < 24; i++) values[i] = ENTSOE_NO_VALUE;
|
||||
for(uint8_t i = 0; i < 24; i++) {
|
||||
float values[38];
|
||||
for(int i = 0;i < 38; i++) values[i] = ENTSOE_NO_VALUE;
|
||||
for(uint8_t i = 0; i < 38; i++) {
|
||||
float val = eapi->getValueForHour(now, i);
|
||||
values[i] = val;
|
||||
|
||||
@@ -272,6 +272,32 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
values[9],
|
||||
values[10],
|
||||
values[11],
|
||||
values[12],
|
||||
values[13],
|
||||
values[14],
|
||||
values[15],
|
||||
values[16],
|
||||
values[17],
|
||||
values[18],
|
||||
values[19],
|
||||
values[20],
|
||||
values[21],
|
||||
values[22],
|
||||
values[23],
|
||||
values[24],
|
||||
values[25],
|
||||
values[26],
|
||||
values[27],
|
||||
values[28],
|
||||
values[29],
|
||||
values[30],
|
||||
values[31],
|
||||
values[32],
|
||||
values[33],
|
||||
values[34],
|
||||
values[35],
|
||||
values[36],
|
||||
values[37],
|
||||
min == INT16_MAX ? 0.0 : min,
|
||||
max == INT16_MIN ? 0.0 : max,
|
||||
ts1hr,
|
||||
|
||||
@@ -9,7 +9,7 @@ public:
|
||||
this->topic = String(topic);
|
||||
this->full = full;
|
||||
};
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include "hexutils.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
bool RawMqttHandler::publish(AmsData* data, AmsData* meterState, EnergyAccounting* ea) {
|
||||
bool RawMqttHandler::publish(AmsData* data, AmsData* meterState, EnergyAccounting* ea, EntsoeApi* eapi) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
return false;
|
||||
|
||||
@@ -90,19 +90,21 @@ bool RawMqttHandler::publish(AmsData* data, AmsData* meterState, EnergyAccountin
|
||||
mqtt->publish(topic + "/meter/import/active", String(data->getActiveImportPower()));
|
||||
}
|
||||
}
|
||||
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++) {
|
||||
mqtt->publish(topic + "/realtime/import/peak/" + String(i, 10), String(ea->getPeak(i).value / 100.0, 10), true, 0);
|
||||
if(ea->isInitialized()) {
|
||||
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++) {
|
||||
mqtt->publish(topic + "/realtime/import/peak/" + String(i, 10), String(ea->getPeak(i).value / 100.0, 10), true, 0);
|
||||
}
|
||||
mqtt->publish(topic + "/realtime/import/threshold", String(ea->getCurrentThreshold(), 10), true, 0);
|
||||
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));
|
||||
}
|
||||
mqtt->publish(topic + "/realtime/import/threshold", String(ea->getCurrentThreshold(), 10), true, 0);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
20
lib/SvelteUi/app/dist/index.js
vendored
20
lib/SvelteUi/app/dist/index.js
vendored
File diff suppressed because one or more lines are too long
@@ -41,6 +41,12 @@
|
||||
<option value={50}>{boardtype(chip, 50)}</option>
|
||||
</optgroup>
|
||||
{/if}
|
||||
{#if chip == 'esp32c3'}
|
||||
<optgroup label="Generic hardware">
|
||||
<option value={71}>{boardtype(chip, 71)}</option>
|
||||
<option value={70}>{boardtype(chip, 70)}</option>
|
||||
</optgroup>
|
||||
{/if}
|
||||
{#if chip == 'esp32solo'}
|
||||
<optgroup label="Generic hardware">
|
||||
<option value={200}>{boardtype(chip, 200)}</option>
|
||||
|
||||
@@ -540,7 +540,7 @@
|
||||
{#each {length: 9} as _, i}
|
||||
<label class="flex w-40 m-1">
|
||||
<span class="in-pre">{i+1}</span>
|
||||
<input name="t{i}" bind:value={configuration.t.t[i]} type="number" min="0" max="255" class="in-txt w-full"/>
|
||||
<input name="t{i}" bind:value={configuration.t.t[i]} type="number" min="0" max="65535" class="in-txt w-full"/>
|
||||
<span class="in-post">kWh</span>
|
||||
</label>
|
||||
{/each}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import { zeropad } from './Helpers.js';
|
||||
import { zeropad, addHours } from './Helpers.js';
|
||||
import BarChart from './BarChart.svelte';
|
||||
|
||||
export let json;
|
||||
@@ -13,16 +13,16 @@
|
||||
let yTicks = [];
|
||||
let xTicks = [];
|
||||
let points = [];
|
||||
let cur = new Date();
|
||||
let offset = -cur.getTimezoneOffset()/60;
|
||||
for(i = cur.getUTCHours(); i<24; i++) {
|
||||
let cur = addHours(new Date(), -24);
|
||||
let currentHour = new Date().getUTCHours();
|
||||
for(i = currentHour; i<24; i++) {
|
||||
let imp = json["i"+zeropad(i)];
|
||||
let exp = json["e"+zeropad(i)];
|
||||
if(imp === undefined) imp = 0;
|
||||
if(exp === undefined) exp = 0;
|
||||
|
||||
xTicks.push({
|
||||
label: zeropad((i+offset)%24)
|
||||
label: zeropad(cur.getHours())
|
||||
});
|
||||
points.push({
|
||||
label: imp.toFixed(1),
|
||||
@@ -33,15 +33,16 @@
|
||||
});
|
||||
min = Math.max(min, exp*10);
|
||||
max = Math.max(max, imp*10);
|
||||
addHours(cur, 1);
|
||||
};
|
||||
for(i = 0; i < cur.getUTCHours(); i++) {
|
||||
for(i = 0; i < currentHour; i++) {
|
||||
let imp = json["i"+zeropad(i)];
|
||||
let exp = json["e"+zeropad(i)];
|
||||
if(imp === undefined) imp = 0;
|
||||
if(exp === undefined) exp = 0;
|
||||
|
||||
xTicks.push({
|
||||
label: zeropad((i+offset)%24)
|
||||
label: zeropad(cur.getHours())
|
||||
});
|
||||
points.push({
|
||||
label: imp.toFixed(1),
|
||||
@@ -52,6 +53,7 @@
|
||||
});
|
||||
min = Math.max(min, exp*10);
|
||||
max = Math.max(max, imp*10);
|
||||
addHours(cur, 1);
|
||||
};
|
||||
|
||||
let boundary = Math.ceil(Math.max(min, max));
|
||||
|
||||
@@ -95,6 +95,10 @@ export function boardtype(c, b) {
|
||||
return "Wemos D1 mini";
|
||||
case 100:
|
||||
return "Generic ESP8266";
|
||||
case 70:
|
||||
return "Generic ESP32-C3";
|
||||
case 71:
|
||||
return "ESP32-C3-DevKitM-1";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +142,8 @@ export function priceError(err) {
|
||||
return "Unauthorized, check API key";
|
||||
case 404:
|
||||
return "Price unavailable, not found";
|
||||
case 425:
|
||||
return "Server says its too early";
|
||||
case 500:
|
||||
return "Internal server error";
|
||||
case -2: return "Incomplete data received";
|
||||
@@ -174,4 +180,43 @@ export function fmtnum(v,d) {
|
||||
if(isNaN(d))
|
||||
d = v < 10 ? 1 : 0;
|
||||
return v.toFixed(d);
|
||||
}
|
||||
|
||||
export function addHours(date, hours) {
|
||||
date.setTime(date.getTime() + hours * 3600000);
|
||||
return date;
|
||||
}
|
||||
|
||||
export function getResetReason(sysinfo) {
|
||||
if(sysinfo.chip == 'esp8266') {
|
||||
switch (sysinfo.boot_reason) {
|
||||
case 0: return "Normal";
|
||||
case 1: return "WDT reset";
|
||||
case 2: return "Exception reset";
|
||||
case 3: return "Soft WDT reset";
|
||||
case 4: return "Software restart";
|
||||
case 5: return "Deep sleep";
|
||||
case 6: return "External reset";
|
||||
default: return "Unknown (8266)";
|
||||
}
|
||||
} else {
|
||||
switch (sysinfo.boot_reason) {
|
||||
case 1 : return "Normal";
|
||||
case 3 : return "Software reset";
|
||||
case 4 : return "WDT reset";
|
||||
case 5 : return "Deep sleep";
|
||||
case 6 : return "SLC reset";
|
||||
case 7 : return "Timer Group0 WDT reset";
|
||||
case 8 : return "Timer Group1 WDT reset";
|
||||
case 9 : return "RTC WDT reset";
|
||||
case 10: return "Instrusion test reset";
|
||||
case 11: return "Time Group reset";
|
||||
case 12: return "Software reset CPU";
|
||||
case 13: return "RTC WTD reset CPU";
|
||||
case 14: return "PRO CPU";
|
||||
case 15: return "Unstable Vdd";
|
||||
case 16: return "RTC reset";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import { zeropad } from './Helpers.js';
|
||||
import { zeropad, addHours } from './Helpers.js';
|
||||
import BarChart from './BarChart.svelte';
|
||||
|
||||
export let json;
|
||||
@@ -19,7 +19,6 @@
|
||||
let points = [];
|
||||
let cur = new Date();
|
||||
for(i = hour; i<24; i++) {
|
||||
cur.setUTCHours(i);
|
||||
val = json[zeropad(h++)];
|
||||
if(val == null) break;
|
||||
xTicks.push({
|
||||
@@ -34,9 +33,9 @@
|
||||
});
|
||||
min = Math.min(min, val*100);
|
||||
max = Math.max(max, val*100);
|
||||
addHours(cur, 1);
|
||||
};
|
||||
for(i = 0; i < 24; i++) {
|
||||
cur.setUTCHours(i);
|
||||
val = json[zeropad(h++)];
|
||||
if(val == null) break;
|
||||
xTicks.push({
|
||||
@@ -51,6 +50,7 @@
|
||||
});
|
||||
min = Math.min(min, val*100);
|
||||
max = Math.max(max, val*100);
|
||||
addHours(cur, 1);
|
||||
};
|
||||
|
||||
max = Math.ceil(max);
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
};
|
||||
|
||||
if(sysinfo.net.ip && tries%3 == 0) {
|
||||
if(sysinfo.net.ip == '0.0.0.0') {
|
||||
if(!sysinfo.net.ip) {
|
||||
retry();
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import { metertype, boardtype, isBusPowered } from './Helpers.js';
|
||||
import { metertype, boardtype, isBusPowered, getResetReason } from './Helpers.js';
|
||||
import { getSysinfo, gitHubReleaseStore, sysinfoStore } from './DataStores.js';
|
||||
import { upgrade, getNextVersion, upgradeWarningText } from './UpgradeHelper';
|
||||
import DownloadIcon from './DownloadIcon.svelte';
|
||||
@@ -102,6 +102,9 @@
|
||||
<div class="my-2">
|
||||
AP MAC: {sysinfo.apmac}
|
||||
</div>
|
||||
<div class="my-2">
|
||||
Last boot: {getResetReason(sysinfo)} ({sysinfo.boot_reason})
|
||||
</div>
|
||||
{/if}
|
||||
<div class="my-2">
|
||||
<Link to="/consent">
|
||||
@@ -137,7 +140,7 @@
|
||||
Gateway: {sysinfo.net.gw}
|
||||
</div>
|
||||
<div class="my-2">
|
||||
DNS: {sysinfo.net.dns1} {#if sysinfo.net.dns2 && sysinfo.net.dns2 != '0.0.0.0'}/ {sysinfo.net.dns2}{/if}
|
||||
DNS: {sysinfo.net.dns1} {#if sysinfo.net.dns2}/ {sysinfo.net.dns2}{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -183,7 +186,7 @@
|
||||
</div>
|
||||
{#if sysinfo.security == 0 || data.a}
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Configuration</strong>
|
||||
<strong class="text-sm">Backup & restore</strong>
|
||||
<form method="get" action="/configfile.cfg" autocomplete="off">
|
||||
<div class="grid grid-cols-2">
|
||||
{#each cfgItems as el}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
"im" : %d,
|
||||
"om" : %d,
|
||||
"mf" : %d,
|
||||
"i" : %d,
|
||||
"e" : %d,
|
||||
"ri" : %d,
|
||||
"re" : %d,
|
||||
"i" : %lu,
|
||||
"e" : %lu,
|
||||
"ri" : %lu,
|
||||
"re" : %lu,
|
||||
"ic" : %.3f,
|
||||
"ec" : %.3f,
|
||||
"ric" : %.3f,
|
||||
|
||||
@@ -36,5 +36,6 @@
|
||||
"m": %d,
|
||||
"s": %d
|
||||
},
|
||||
"security": %d
|
||||
"security": %d,
|
||||
"boot_reason": %d
|
||||
}
|
||||
@@ -61,8 +61,6 @@ for webroot in ["lib/SvelteUi/app/dist", "lib/SvelteUi/json"]:
|
||||
try:
|
||||
if filename.endswith(".html"):
|
||||
content = html_minify(content)
|
||||
elif filename.endswith(".css"):
|
||||
content = css_minify(content)
|
||||
elif filename.endswith(".json"):
|
||||
content = js_minify(content)
|
||||
except:
|
||||
|
||||
@@ -38,6 +38,16 @@
|
||||
#endif
|
||||
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
|
||||
#include "esp32/rom/rtc.h"
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
#include "esp32s2/rom/rtc.h"
|
||||
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||
#include "esp32c3/rom/rtc.h"
|
||||
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||
#include "esp32s3/rom/rtc.h"
|
||||
#endif
|
||||
|
||||
AmsWebServer::AmsWebServer(uint8_t* buf, RemoteDebug* Debug, HwTools* hw) {
|
||||
this->debugger = Debug;
|
||||
this->hw = hw;
|
||||
@@ -217,6 +227,9 @@ void AmsWebServer::sysinfoJson() {
|
||||
hostname = "ams-"+chipIdStr;
|
||||
}
|
||||
|
||||
IPAddress localIp = WiFi.localIP();
|
||||
IPAddress subnet = WiFi.subnetMask();
|
||||
IPAddress gateway = WiFi.gatewayIP();
|
||||
IPAddress dns1 = WiFi.dnsIP(0);
|
||||
IPAddress dns2 = WiFi.dnsIP(1);
|
||||
|
||||
@@ -271,15 +284,18 @@ void AmsWebServer::sysinfoJson() {
|
||||
hostname.c_str(),
|
||||
performRestart ? "true" : "false",
|
||||
rebootForUpgrade ? "true" : "false",
|
||||
WiFi.localIP().toString().c_str(),
|
||||
WiFi.subnetMask().toString().c_str(),
|
||||
WiFi.gatewayIP().toString().c_str(),
|
||||
#if defined(ESP8266)
|
||||
localIp.isSet() ? localIp.toString().c_str() : "",
|
||||
subnet.isSet() ? subnet.toString().c_str() : "",
|
||||
gateway.isSet() ? gateway.toString().c_str() : "",
|
||||
dns1.isSet() ? dns1.toString().c_str() : "",
|
||||
dns2.isSet() ? dns2.toString().c_str() : "",
|
||||
#else
|
||||
dns1.toString().c_str(),
|
||||
dns2.toString().c_str(),
|
||||
localIp != INADDR_NONE ? localIp.toString().c_str() : "",
|
||||
subnet != INADDR_NONE ? subnet.toString().c_str() : "",
|
||||
gateway != INADDR_NONE ? gateway.toString().c_str() : "",
|
||||
dns1 != INADDR_NONE ? dns1.toString().c_str() : "",
|
||||
dns2 != INADDR_NONE ? dns2.toString().c_str() : "",
|
||||
#endif
|
||||
meterState->getMeterType(),
|
||||
meterModel.c_str(),
|
||||
@@ -295,7 +311,12 @@ void AmsWebServer::sysinfoJson() {
|
||||
ui.showDayPlot,
|
||||
ui.showMonthPlot,
|
||||
ui.showTemperaturePlot,
|
||||
webConfig.security
|
||||
webConfig.security,
|
||||
#if defined(ESP32)
|
||||
rtc_get_reset_reason(0)
|
||||
#else
|
||||
ESP.getResetInfoPtr()->reason
|
||||
#endif
|
||||
);
|
||||
|
||||
stripNonAscii((uint8_t*) buf, size+1);
|
||||
@@ -339,9 +360,9 @@ void AmsWebServer::dataJson() {
|
||||
#if defined(ESP8266)
|
||||
if(vcc < 2.0) { // Voltage not correct, ESP would not run on this voltage
|
||||
espStatus = 1;
|
||||
} else if(vcc > 3.1 && vcc < 3.5) {
|
||||
} else if(vcc > 2.8 && vcc < 3.5) {
|
||||
espStatus = 1;
|
||||
} else if(vcc > 3.0 && vcc < 3.6) {
|
||||
} else if(vcc > 2.7 && vcc < 3.6) {
|
||||
espStatus = 2;
|
||||
} else {
|
||||
espStatus = 3;
|
||||
@@ -349,9 +370,9 @@ void AmsWebServer::dataJson() {
|
||||
#elif defined(ESP32)
|
||||
if(vcc < 2.0) { // Voltage not correct, ESP would not run on this voltage
|
||||
espStatus = 1;
|
||||
} else if(vcc > 2.8 && vcc < 3.5) {
|
||||
} else if(vcc > 3.1 && vcc < 3.5) {
|
||||
espStatus = 1;
|
||||
} else if(vcc > 2.7 && vcc < 3.6) {
|
||||
} else if(vcc > 3.0 && vcc < 3.6) {
|
||||
espStatus = 2;
|
||||
} else {
|
||||
espStatus = 3;
|
||||
@@ -970,6 +991,15 @@ void AmsWebServer::handleSave() {
|
||||
success = false;
|
||||
}
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
switch(boardType) {
|
||||
case 71: // ESP32-C3-DevKitM-1
|
||||
gpioConfig->apPin = 9;
|
||||
case 70: // Generic ESP32-C3
|
||||
gpioConfig->hanPin = hanPin > 0 ? hanPin : 7;
|
||||
break;
|
||||
default:
|
||||
success = false;
|
||||
}
|
||||
#elif defined(ESP32)
|
||||
switch(boardType) {
|
||||
case 201: // D32
|
||||
@@ -1095,11 +1125,13 @@ void AmsWebServer::handleSave() {
|
||||
break;
|
||||
case 3: // Pow-K UART0
|
||||
case 5: // Pow-K+
|
||||
meterConfig->baud = 2400;
|
||||
meterConfig->parity = 3; // 8N1
|
||||
case 2: // spenceme
|
||||
case 50: // Generic ESP32-S2
|
||||
case 51: // Wemos S2 mini
|
||||
meterConfig->baud = 2400;
|
||||
case 70: // Generic ESP32-C3
|
||||
case 71: // ESP32-C3-DevKitM-1
|
||||
wifi.sleep = 1; // Modem sleep
|
||||
break;
|
||||
case 4: // Pow-U UART0
|
||||
@@ -1133,16 +1165,25 @@ void AmsWebServer::handleSave() {
|
||||
meterConfig->productionCapacity = server.arg(F("mr")).toInt();
|
||||
maxPwr = 0;
|
||||
|
||||
String encryptionKeyHex = server.arg(F("mek"));
|
||||
if(!encryptionKeyHex.isEmpty()) {
|
||||
encryptionKeyHex.replace(F("0x"), F(""));
|
||||
fromHex(meterConfig->encryptionKey, encryptionKeyHex, 16);
|
||||
}
|
||||
if(server.hasArg(F("me")) && server.arg(F("me")) == F("true")) {
|
||||
String encryptionKeyHex = server.arg(F("mek"));
|
||||
if(!encryptionKeyHex.isEmpty()) {
|
||||
encryptionKeyHex.replace(F("0x"), F(""));
|
||||
fromHex(meterConfig->encryptionKey, encryptionKeyHex, 16);
|
||||
} else {
|
||||
memset(meterConfig->encryptionKey, 0, 16);
|
||||
}
|
||||
|
||||
String authenticationKeyHex = server.arg(F("mea"));
|
||||
if(!authenticationKeyHex.isEmpty()) {
|
||||
authenticationKeyHex.replace(F("0x"), F(""));
|
||||
fromHex(meterConfig->authenticationKey, authenticationKeyHex, 16);
|
||||
String authenticationKeyHex = server.arg(F("mea"));
|
||||
if(!authenticationKeyHex.isEmpty()) {
|
||||
authenticationKeyHex.replace(F("0x"), F(""));
|
||||
fromHex(meterConfig->authenticationKey, authenticationKeyHex, 16);
|
||||
} else {
|
||||
memset(meterConfig->authenticationKey, 0, 16);
|
||||
}
|
||||
} else {
|
||||
memset(meterConfig->encryptionKey, 0, 16);
|
||||
memset(meterConfig->authenticationKey, 0, 16);
|
||||
}
|
||||
|
||||
meterConfig->wattageMultiplier = server.arg(F("mmw")).toDouble() * 1000;
|
||||
@@ -1166,12 +1207,20 @@ void AmsWebServer::handleSave() {
|
||||
wifi.autoreboot = server.hasArg(F("wa")) && server.arg(F("wa")) == F("true");
|
||||
config->setWiFiConfig(wifi);
|
||||
|
||||
if(server.hasArg(F("nm")) && server.arg(F("nm")) == "static") {
|
||||
strcpy(wifi.ip, server.arg(F("ni")).c_str());
|
||||
strcpy(wifi.gateway, server.arg(F("ng")).c_str());
|
||||
strcpy(wifi.subnet, server.arg(F("ns")).c_str());
|
||||
strcpy(wifi.dns1, server.arg(F("nd1")).c_str());
|
||||
strcpy(wifi.dns2, server.arg(F("nd2")).c_str());
|
||||
if(server.hasArg(F("nm"))) {
|
||||
if(server.arg(F("nm")) == "static") {
|
||||
strcpy(wifi.ip, server.arg(F("ni")).c_str());
|
||||
strcpy(wifi.gateway, server.arg(F("ng")).c_str());
|
||||
strcpy(wifi.subnet, server.arg(F("ns")).c_str());
|
||||
strcpy(wifi.dns1, server.arg(F("nd1")).c_str());
|
||||
strcpy(wifi.dns2, server.arg(F("nd2")).c_str());
|
||||
} else if(server.arg(F("nm")) == "dhcp") {
|
||||
strcpy(wifi.ip, "");
|
||||
strcpy(wifi.gateway, "");
|
||||
strcpy(wifi.subnet, "");
|
||||
strcpy(wifi.dns1, "");
|
||||
strcpy(wifi.dns2, "");
|
||||
}
|
||||
}
|
||||
wifi.mdns = server.hasArg(F("nd")) && server.arg(F("nd")) == F("true");
|
||||
config->setWiFiConfig(wifi);
|
||||
@@ -1459,6 +1508,8 @@ void AmsWebServer::upgrade() {
|
||||
String chipType = F("esp8266");
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
String chipType = F("esp32s2");
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
String chipType = F("esp32c3");
|
||||
#elif defined(ESP32)
|
||||
#if defined(CONFIG_FREERTOS_UNICORE)
|
||||
String chipType = F("esp32solo");
|
||||
@@ -1521,6 +1572,8 @@ void AmsWebServer::firmwarePost() {
|
||||
String chipType = F("esp8266");
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
String chipType = F("esp32s2");
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
String chipType = F("esp32c3");
|
||||
#elif defined(ESP32)
|
||||
#if defined(CONFIG_FREERTOS_UNICORE)
|
||||
String chipType = F("esp32solo");
|
||||
|
||||
@@ -32,7 +32,7 @@ ADC_MODE(ADC_VCC);
|
||||
#endif
|
||||
#define WDT_TIMEOUT 60
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#include <driver/uart.h>
|
||||
#endif
|
||||
|
||||
@@ -656,9 +656,8 @@ void setupHanPort(uint8_t pin, uint32_t baud, uint8_t parityOrdinal, bool invert
|
||||
if(pin == 16) {
|
||||
hwSerial = &Serial2;
|
||||
}
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
hwSerial = &Serial1;
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -690,7 +689,7 @@ void setupHanPort(uint8_t pin, uint32_t baud, uint8_t parityOrdinal, bool invert
|
||||
break;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
hwSerial->begin(baud, serialConfig, -1, -1, invert);
|
||||
uart_set_pin(UART_NUM_1, UART_PIN_NO_CHANGE, pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
#elif defined(ESP32)
|
||||
@@ -718,7 +717,6 @@ void setupHanPort(uint8_t pin, uint32_t baud, uint8_t parityOrdinal, bool invert
|
||||
swSerial->end();
|
||||
delete swSerial;
|
||||
}
|
||||
swSerial = new SoftwareSerial(pin);
|
||||
|
||||
SoftwareSerialConfig serialConfig;
|
||||
switch(parityOrdinal) {
|
||||
@@ -736,7 +734,7 @@ void setupHanPort(uint8_t pin, uint32_t baud, uint8_t parityOrdinal, bool invert
|
||||
break;
|
||||
}
|
||||
|
||||
SoftwareSerial *swSerial = new SoftwareSerial(pin, -1, invert);
|
||||
swSerial = new SoftwareSerial(pin, -1, invert);
|
||||
swSerial->begin(baud, serialConfig);
|
||||
hanSerial = swSerial;
|
||||
|
||||
@@ -936,7 +934,7 @@ bool readHanPort() {
|
||||
if(!hw.ledBlink(LED_GREEN, 1))
|
||||
hw.ledBlink(LED_INTERNAL, 1);
|
||||
if(mqttEnabled && mqttHandler != NULL && mqtt != NULL) {
|
||||
if(mqttHandler->publish(&data, &meterState, &ea)) {
|
||||
if(mqttHandler->publish(&data, &meterState, &ea, eapi)) {
|
||||
mqtt->loop();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user