mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-23 17:32:07 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
648cac0b06 | ||
|
|
a5d1a2c0ee | ||
|
|
55f1b4b129 | ||
|
|
b805363c60 | ||
|
|
846b166ae9 | ||
|
|
4e039c2cc9 | ||
|
|
e5556fe80b | ||
|
|
c5aa65a780 | ||
|
|
30cf1435da | ||
|
|
0dca85d67b | ||
|
|
d9a5a21fe0 | ||
|
|
68eaa7d39b | ||
|
|
3231d0747e | ||
|
|
2617956b38 | ||
|
|
07614226b3 | ||
|
|
cddb170f24 | ||
|
|
ba7915ca7c | ||
|
|
ee12db4f51 | ||
|
|
f01fbfca53 | ||
|
|
fab637cf60 | ||
|
|
b2e144efcf | ||
|
|
10308ce738 |
9
.github/workflows/release.yml
vendored
9
.github/workflows/release.yml
vendored
@@ -104,6 +104,15 @@ jobs:
|
|||||||
asset_path: .pio/build/esp32/firmware.bin
|
asset_path: .pio/build/esp32/firmware.bin
|
||||||
asset_name: ams2mqtt-esp32-${{ steps.release_tag.outputs.tag }}.bin
|
asset_name: ams2mqtt-esp32-${{ steps.release_tag.outputs.tag }}.bin
|
||||||
asset_content_type: application/octet-stream
|
asset_content_type: application/octet-stream
|
||||||
|
- name: Upload esp32solo 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/esp32solo/firmware.bin
|
||||||
|
asset_name: ams2mqtt-esp32solo-${{ steps.release_tag.outputs.tag }}.bin
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
- name: Upload esp32s2 binary to release
|
- name: Upload esp32s2 binary to release
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
|
|||||||
BIN
doc/Switzerland/RWB_SmartMeter_Bedienungsanleitung.pdf
Normal file
BIN
doc/Switzerland/RWB_SmartMeter_Bedienungsanleitung.pdf
Normal file
Binary file not shown.
45
frames/lng.raw
Normal file
45
frames/lng.raw
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
7E // Flag
|
||||||
|
A08B
|
||||||
|
CEFF03
|
||||||
|
13
|
||||||
|
EEE1
|
||||||
|
E6E700
|
||||||
|
E0 // GBT (Green book 9.4.6.13)
|
||||||
|
40 // Block control 0100 0000, last block=no, streaming=yes, remainig=window
|
||||||
|
0001 // Block sequence
|
||||||
|
0000 // Block sequence ack
|
||||||
|
77 // How many bytes in this block
|
||||||
|
|
||||||
|
0F 00000DB7 // APDU tag, Invoke ID and priority
|
||||||
|
|
||||||
|
0C07E604020607220FFF800000 // Date and time
|
||||||
|
0205 // Structure with 5 items
|
||||||
|
0105 // Array with 5 items
|
||||||
|
020412002809060008190900FF0F02120000 // Structure with 4 items, uint16, OBIS, int8, uint16 (0-8:25.9.0;2)
|
||||||
|
020412002809060008190900FF0F01120000 // Structure with 4 items, uint16, OBIS, int8, uint16 (0-8:25.9.0;1)
|
||||||
|
020412000109060000600101FF0F02120000 // Structure with 4 items, uint16, OBIS, int8, uint16 (96.1.1 - Meter model)
|
||||||
|
020412000309060100010700FF0F02120000 // Structure with 4 items, uint16, OBIS, int8, uint16 (1.7.0 Active import)
|
||||||
|
020412000309060100020700FF0F02120000 // Structure with 4 items, uint16, OBIS, int8, uint16 (2.7.0 Active export)
|
||||||
|
09060008190900 // OBIS 0-8:25.9.0 Object list push settings consumer information 1
|
||||||
|
ABA6
|
||||||
|
7E
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
7E
|
||||||
|
A024
|
||||||
|
CEFF03
|
||||||
|
13
|
||||||
|
D661
|
||||||
|
E0 // GBT
|
||||||
|
C0 // Block control 0100 0000, last block=yes, streaming=yes, remainig=window
|
||||||
|
0002 // Block sequence
|
||||||
|
0000 // Block sequence ack
|
||||||
|
13 // How many bytes in this block
|
||||||
|
|
||||||
|
FF // Last byte of OBIS in previous block
|
||||||
|
0906363031313039 // Device ID
|
||||||
|
0600000028 // Accumulated import
|
||||||
|
0600000000 // Accumulated export
|
||||||
|
8BA4
|
||||||
|
7E
|
||||||
@@ -17,7 +17,6 @@ extra_scripts =
|
|||||||
pre:scripts/addversion.py
|
pre:scripts/addversion.py
|
||||||
scripts/makeweb.py
|
scripts/makeweb.py
|
||||||
|
|
||||||
|
|
||||||
[env:esp32]
|
[env:esp32]
|
||||||
platform = https://github.com/platformio/platform-espressif32.git#feature/arduino-upstream
|
platform = https://github.com/platformio/platform-espressif32.git#feature/arduino-upstream
|
||||||
framework = arduino
|
framework = arduino
|
||||||
@@ -30,9 +29,12 @@ extra_scripts =
|
|||||||
pre:scripts/addversion.py
|
pre:scripts/addversion.py
|
||||||
scripts/makeweb.py
|
scripts/makeweb.py
|
||||||
|
|
||||||
|
# Tasmota has pre-built platform for C3, S2, S3 and Solo, more information at:
|
||||||
|
# https://github.com/Jason2866/esp32-arduino-lib-builder
|
||||||
|
|
||||||
[env:esp32s2]
|
[env:esp32s2]
|
||||||
platform = https://github.com/tasmota/platform-espressif32.git#v2.0.3
|
platform = https://github.com/tasmota/platform-espressif32/releases/download/v.2.0.3/platform-espressif32-v.2.0.3.zip
|
||||||
platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.2
|
platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.3
|
||||||
framework = arduino
|
framework = arduino
|
||||||
board = esp32dev
|
board = esp32dev
|
||||||
board_build.mcu = esp32s2
|
board_build.mcu = esp32s2
|
||||||
@@ -46,3 +48,15 @@ lib_ignore = ${common.lib_ignore}
|
|||||||
extra_scripts =
|
extra_scripts =
|
||||||
pre:scripts/addversion.py
|
pre:scripts/addversion.py
|
||||||
scripts/makeweb.py
|
scripts/makeweb.py
|
||||||
|
|
||||||
|
[env:esp32solo]
|
||||||
|
platform = https://github.com/tasmota/platform-espressif32/releases/download/v.2.0.3/platform-espressif32-solo1-v.2.0.3.zip
|
||||||
|
framework = arduino
|
||||||
|
board = esp32dev
|
||||||
|
board_build.f_cpu = 160000000L
|
||||||
|
build_flags = -D WEBSOCKET_DISABLED=1 -fexceptions
|
||||||
|
lib_deps = ${common.lib_deps}
|
||||||
|
lib_ignore = ${common.lib_ignore}
|
||||||
|
extra_scripts =
|
||||||
|
pre:scripts/addversion.py
|
||||||
|
scripts/makeweb.py
|
||||||
|
|||||||
40
scripts/esp32solo/flash.sh
Executable file
40
scripts/esp32solo/flash.sh
Executable file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
if [ -z "$1" ];then
|
||||||
|
echo "Usage: "
|
||||||
|
echo " Flashing first time : $0 flash /dev/ttyUSB0"
|
||||||
|
echo " When upgrading to new version : $0 upgrade /dev/ttyUSB0"
|
||||||
|
echo " NOTE: Replace /dev/ttyUSB0 with correct port"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$2" ];then
|
||||||
|
echo "Please specify port"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
esptool=`which esptool`
|
||||||
|
if [ -z "$esptool" ];then
|
||||||
|
esptool=`which esptool.py`
|
||||||
|
fi
|
||||||
|
if [ -z "$esptool" ];then
|
||||||
|
if [ -f esptool.py ];then
|
||||||
|
esptool="esptool.py"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ -z "$esptool" ];then
|
||||||
|
echo "esptool.py not available to run following command: "
|
||||||
|
esptool="echo esptool.py"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$1" = "flash" ];then
|
||||||
|
$esptool --chip esp32 --port $2 --baud 460800 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size detect \
|
||||||
|
0x1000 bootloader_dio_40m.bin \
|
||||||
|
0x8000 partitions.bin \
|
||||||
|
0xe000 boot_app0.bin \
|
||||||
|
0x10000 firmware.bin
|
||||||
|
exit $?
|
||||||
|
elif [ "$1" = "upgrade" ];then
|
||||||
|
$esptool --chip esp32 --port $2 --baud 460800 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size detect 0x10000 firmware.bin
|
||||||
|
exit $?
|
||||||
|
fi
|
||||||
14
scripts/esp32solo/mkzip.sh
Executable file
14
scripts/esp32solo/mkzip.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
env="esp32solo"
|
||||||
|
build_dir=".pio/build/$env/"
|
||||||
|
|
||||||
|
if [ ! -d $build_dir ];then
|
||||||
|
echo "No build directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp ~/.platformio/packages/framework-arduinoespressif32/tools/sdk/$env/bin/bootloader_dio_40m.bin $build_dir
|
||||||
|
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin $build_dir
|
||||||
|
chmod +x scripts/$env/flash.sh
|
||||||
|
zip -j $env.zip $build_dir/*.bin scripts/$env/flash.sh
|
||||||
@@ -64,6 +64,7 @@ ADC_MODE(ADC_VCC);
|
|||||||
#define BUF_SIZE_HAN (1024)
|
#define BUF_SIZE_HAN (1024)
|
||||||
#include "ams/hdlc.h"
|
#include "ams/hdlc.h"
|
||||||
#include "MbusAssembler.h"
|
#include "MbusAssembler.h"
|
||||||
|
#include "GBTAssembler.h"
|
||||||
|
|
||||||
#include "IEC6205621.h"
|
#include "IEC6205621.h"
|
||||||
#include "IEC6205675.h"
|
#include "IEC6205675.h"
|
||||||
@@ -86,7 +87,7 @@ Timezone* tz;
|
|||||||
AmsWebServer ws(commonBuffer, &Debug, &hw);
|
AmsWebServer ws(commonBuffer, &Debug, &hw);
|
||||||
|
|
||||||
MQTTClient *mqtt = NULL;
|
MQTTClient *mqtt = NULL;
|
||||||
WiFiClient *mqttClient = new WiFiClient();
|
WiFiClient *mqttClient = NULL;
|
||||||
WiFiClientSecure *mqttSecureClient = NULL;
|
WiFiClientSecure *mqttSecureClient = NULL;
|
||||||
AmsMqttHandler* mqttHandler = NULL;
|
AmsMqttHandler* mqttHandler = NULL;
|
||||||
|
|
||||||
@@ -607,6 +608,9 @@ void setupHanPort(uint8_t pin, uint32_t baud, uint8_t parityOrdinal, bool invert
|
|||||||
hwSerial = &Serial2;
|
hwSerial = &Serial2;
|
||||||
}
|
}
|
||||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||||
|
if(pin == 18) {
|
||||||
|
hwSerial = &Serial1;
|
||||||
|
}
|
||||||
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
|
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
@@ -765,15 +769,19 @@ void swapWifiMode() {
|
|||||||
|
|
||||||
int len = 0;
|
int len = 0;
|
||||||
MbusAssembler* ma = NULL;
|
MbusAssembler* ma = NULL;
|
||||||
|
GBTAssembler* ga = NULL;
|
||||||
int currentMeterType = -1;
|
int currentMeterType = -1;
|
||||||
bool readHanPort() {
|
bool readHanPort() {
|
||||||
if(!hanSerial->available()) return false;
|
if(!hanSerial->available()) return false;
|
||||||
|
|
||||||
|
// Before autodetect starts, empty serial buffer to increase chance of getting first byte of a data transfer
|
||||||
if(currentMeterType == -1) {
|
if(currentMeterType == -1) {
|
||||||
hanSerial->readBytes(hanBuffer, BUF_SIZE_HAN);
|
hanSerial->readBytes(hanBuffer, BUF_SIZE_HAN);
|
||||||
currentMeterType = 0;
|
currentMeterType = 0; // Start autodetection
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Data type autodetect
|
||||||
if(currentMeterType == 0) {
|
if(currentMeterType == 0) {
|
||||||
uint8_t flag = hanSerial->read();
|
uint8_t flag = hanSerial->read();
|
||||||
if(flag == 0x7E || flag == 0x68) {
|
if(flag == 0x7E || flag == 0x68) {
|
||||||
@@ -789,35 +797,44 @@ bool readHanPort() {
|
|||||||
debugD("DSMR");
|
debugD("DSMR");
|
||||||
currentMeterType = 2;
|
currentMeterType = 2;
|
||||||
} else {
|
} else {
|
||||||
currentMeterType = -1;
|
currentMeterType = -1; // Unable to detect, reset to flush serial buffer
|
||||||
}
|
}
|
||||||
|
// Empty serial buffer before continuing
|
||||||
hanSerial->readBytes(hanBuffer, BUF_SIZE_HAN);
|
hanSerial->readBytes(hanBuffer, BUF_SIZE_HAN);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
CosemDateTime timestamp = {0};
|
CosemDateTime timestamp = {0};
|
||||||
|
HDLCContext context = {0,0,0};
|
||||||
AmsData data;
|
AmsData data;
|
||||||
if(currentMeterType == 1) {
|
if(currentMeterType == 1) { // DLMS
|
||||||
int pos = HDLC_FRAME_INCOMPLETE;
|
int pos = HDLC_FRAME_INCOMPLETE;
|
||||||
|
// For each byte received, check if we have a complete HDLC (or MBUS) frame we can handle
|
||||||
while(hanSerial->available() && pos == HDLC_FRAME_INCOMPLETE) {
|
while(hanSerial->available() && pos == HDLC_FRAME_INCOMPLETE) {
|
||||||
hanBuffer[len++] = hanSerial->read();
|
hanBuffer[len++] = hanSerial->read();
|
||||||
pos = HDLC_validate((uint8_t *) hanBuffer, len, hc, ×tamp);
|
pos = HDLC_validate((uint8_t *) hanBuffer, len, hc, ×tamp, &context);
|
||||||
}
|
}
|
||||||
if(len > 0) {
|
if(len > 0) {
|
||||||
|
// If buffer was overflowed, reset
|
||||||
if(len >= BUF_SIZE_HAN) {
|
if(len >= BUF_SIZE_HAN) {
|
||||||
hanSerial->readBytes(hanBuffer, BUF_SIZE_HAN);
|
hanSerial->readBytes(hanBuffer, BUF_SIZE_HAN);
|
||||||
len = 0;
|
len = 0;
|
||||||
debugI("Buffer overflow, resetting");
|
debugI("Buffer overflow, resetting");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In case we get segmented MBUS frames, assemble before parsing
|
||||||
if(pos == MBUS_FRAME_INTERMEDIATE_SEGMENT) {
|
if(pos == MBUS_FRAME_INTERMEDIATE_SEGMENT) {
|
||||||
debugI("Intermediate segment");
|
debugI("Intermediate segment");
|
||||||
if(ma == NULL) {
|
if(ma == NULL) {
|
||||||
ma = new MbusAssembler();
|
ma = new MbusAssembler();
|
||||||
}
|
}
|
||||||
if(ma->append((uint8_t *) hanBuffer, len) < 0)
|
if(ma->append((uint8_t *) hanBuffer, len) < 0) {
|
||||||
pos = -77;
|
debugE("MBUS assembler failed");
|
||||||
|
len = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if(Debug.isActive(RemoteDebug::VERBOSE)) {
|
if(Debug.isActive(RemoteDebug::VERBOSE)) {
|
||||||
debugD("Frame dump (%db):", len);
|
debugD("Intermediate degment dump (%db):", len);
|
||||||
debugPrint(hanBuffer, 0, len);
|
debugPrint(hanBuffer, 0, len);
|
||||||
}
|
}
|
||||||
len = 0;
|
len = 0;
|
||||||
@@ -825,28 +842,73 @@ bool readHanPort() {
|
|||||||
} else if(pos == MBUS_FRAME_LAST_SEGMENT) {
|
} else if(pos == MBUS_FRAME_LAST_SEGMENT) {
|
||||||
debugI("Final segment");
|
debugI("Final segment");
|
||||||
if(Debug.isActive(RemoteDebug::VERBOSE)) {
|
if(Debug.isActive(RemoteDebug::VERBOSE)) {
|
||||||
debugD("Frame dump (%db):", len);
|
debugD("Final segment dump (%db):", len);
|
||||||
debugPrint(hanBuffer, 0, len);
|
debugPrint(hanBuffer, 0, len);
|
||||||
}
|
}
|
||||||
if(ma->append((uint8_t *) hanBuffer, len) >= 0) {
|
if(ma->append((uint8_t *) hanBuffer, len) >= 0) {
|
||||||
len = ma->write((uint8_t *) hanBuffer);
|
len = ma->write((uint8_t *) hanBuffer);
|
||||||
pos = HDLC_validate((uint8_t *) hanBuffer, len, hc, ×tamp);
|
pos = HDLC_validate((uint8_t *) hanBuffer, len, hc, ×tamp, &context);
|
||||||
} else {
|
} else {
|
||||||
pos = -77;
|
debugE("MBUS assembler failed");
|
||||||
|
len = 0;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(pos == HDLC_FRAME_INCOMPLETE) {
|
|
||||||
|
// In case we get segmented HDLC frames (General Block Transfer), assemble before parsing
|
||||||
|
if(pos == HDLC_GBT_INTERMEDIATE) {
|
||||||
|
debugI("Intermediate block");
|
||||||
|
if(ga == NULL) {
|
||||||
|
ga = new GBTAssembler();
|
||||||
|
}
|
||||||
|
if(ga->append(&context, (uint8_t *) hanBuffer, len, &Debug) < 0) {
|
||||||
|
debugE("GBT assembler failed");
|
||||||
|
len = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(Debug.isActive(RemoteDebug::VERBOSE)) {
|
||||||
|
debugD("Intermediate block dump (%db):", len);
|
||||||
|
debugPrint(hanBuffer, 0, len);
|
||||||
|
}
|
||||||
|
len = 0;
|
||||||
return false;
|
return false;
|
||||||
|
} else if(pos == HDLC_GBT_LAST) {
|
||||||
|
debugI("Final block");
|
||||||
|
if(Debug.isActive(RemoteDebug::VERBOSE)) {
|
||||||
|
debugD("Final block dump (%db):", len);
|
||||||
|
debugPrint(hanBuffer, 0, len);
|
||||||
|
}
|
||||||
|
if(ga->append(&context, (uint8_t *) hanBuffer, len, &Debug) >= 0) {
|
||||||
|
len = ga->write((uint8_t *) hanBuffer);
|
||||||
|
pos = HDLC_validate((uint8_t *) hanBuffer, len, hc, ×tamp, &context);
|
||||||
|
} else {
|
||||||
|
debugE("GBT assembler failed");
|
||||||
|
len = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for(int i = len; i<BUF_SIZE_HAN; i++) {
|
|
||||||
hanBuffer[i] = 0x00;
|
// Encryption, but config was not initialized
|
||||||
}
|
|
||||||
if(pos == HDLC_ENCRYPTION_CONFIG_MISSING) {
|
if(pos == HDLC_ENCRYPTION_CONFIG_MISSING) {
|
||||||
hc = new HDLCConfig();
|
hc = new HDLCConfig();
|
||||||
memcpy(hc->encryption_key, meterConfig.encryptionKey, 16);
|
memcpy(hc->encryption_key, meterConfig.encryptionKey, 16);
|
||||||
memcpy(hc->authentication_key, meterConfig.authenticationKey, 16);
|
memcpy(hc->authentication_key, meterConfig.authenticationKey, 16);
|
||||||
|
pos = HDLC_validate((uint8_t *) hanBuffer, len, hc, ×tamp, &context);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Received frame was incomplete, return to loop and wait for more data
|
||||||
|
if(pos == HDLC_FRAME_INCOMPLETE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data is valid, clear the rest of the buffer to avoid tainted read
|
||||||
|
for(int i = len; i<BUF_SIZE_HAN; i++) {
|
||||||
|
hanBuffer[i] = 0x00;
|
||||||
}
|
}
|
||||||
if(Debug.isActive(RemoteDebug::VERBOSE)) {
|
if(Debug.isActive(RemoteDebug::VERBOSE)) {
|
||||||
|
debugW("APDU tag %02X", context.apdu);
|
||||||
|
debugW("APDU start %d", context.apduStart);
|
||||||
|
|
||||||
debugD("Frame dump (%db):", len);
|
debugD("Frame dump (%db):", len);
|
||||||
debugPrint(hanBuffer, 0, len);
|
debugPrint(hanBuffer, 0, len);
|
||||||
}
|
}
|
||||||
@@ -860,12 +922,16 @@ bool readHanPort() {
|
|||||||
debugD("Authentication tag:");
|
debugD("Authentication tag:");
|
||||||
debugPrint(hc->authentication_tag, 0, 12);
|
debugPrint(hc->authentication_tag, 0, 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If MQTT bytestream payload is selected (mqttHandler == NULL), send the payload to MQTT
|
||||||
if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) {
|
if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) {
|
||||||
mqtt->publish(topic.c_str(), toHex(hanBuffer, len));
|
mqtt->publish(topic.c_str(), toHex(hanBuffer, len));
|
||||||
}
|
}
|
||||||
len = 0;
|
len = 0; // Reset length for next frame
|
||||||
if(pos > 0) {
|
if(pos > 0) {
|
||||||
|
// Parse valid data
|
||||||
debugD("Valid data, start at byte %d", pos);
|
debugD("Valid data, start at byte %d", pos);
|
||||||
|
// TODO: Split IEC6205675 into DataParserKaifa and DataParserObis. This way we can add other means of parsing, for those other proprietary formats
|
||||||
data = IEC6205675(((char *) (hanBuffer)) + pos, meterState.getMeterType(), &meterConfig, timestamp, hc);
|
data = IEC6205675(((char *) (hanBuffer)) + pos, meterState.getMeterType(), &meterConfig, timestamp, hc);
|
||||||
} else {
|
} else {
|
||||||
printHanReadError(pos);
|
printHanReadError(pos);
|
||||||
@@ -874,7 +940,7 @@ bool readHanPort() {
|
|||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if(currentMeterType == 2) {
|
} else if(currentMeterType == 2) { // DSMR
|
||||||
int pos = HDLC_FRAME_INCOMPLETE;
|
int pos = HDLC_FRAME_INCOMPLETE;
|
||||||
if(hc != NULL) {
|
if(hc != NULL) {
|
||||||
while(hanSerial->available() && pos == HDLC_FRAME_INCOMPLETE) {
|
while(hanSerial->available() && pos == HDLC_FRAME_INCOMPLETE) {
|
||||||
@@ -911,7 +977,7 @@ bool readHanPort() {
|
|||||||
len = 0;
|
len = 0;
|
||||||
data = IEC6205621(((char *) (hanBuffer)) + pos);
|
data = IEC6205621(((char *) (hanBuffer)) + pos);
|
||||||
if(data.getListType() == 0) {
|
if(data.getListType() == 0) {
|
||||||
currentMeterType = 0;
|
currentMeterType = 0; // Did not receive valid data, go bach to autodetect
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
if(Debug.isActive(RemoteDebug::DEBUG)) {
|
if(Debug.isActive(RemoteDebug::DEBUG)) {
|
||||||
@@ -1018,7 +1084,7 @@ void printHanReadError(int pos) {
|
|||||||
break;
|
break;
|
||||||
case HDLC_UNKNOWN_DATA:
|
case HDLC_UNKNOWN_DATA:
|
||||||
debugW("Unknown data format %02X", hanBuffer[0]);
|
debugW("Unknown data format %02X", hanBuffer[0]);
|
||||||
currentMeterType = 0;
|
currentMeterType = 0; // Did not receive valid data, go back to autodetect
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
debugW("Unspecified error while reading data: %d", pos);
|
debugW("Unspecified error while reading data: %d", pos);
|
||||||
@@ -1240,22 +1306,19 @@ void MQTT_connect() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(mqttConfig.ssl) {
|
if(mqttConfig.ssl) {
|
||||||
debugI("MQTT SSL is configured");
|
debugI("MQTT SSL is configured (%dkb free heap)", ESP.getFreeHeap());
|
||||||
if(mqttSecureClient == NULL) {
|
if(mqttSecureClient == NULL) {
|
||||||
mqttSecureClient = new WiFiClientSecure();
|
mqttSecureClient = new WiFiClientSecure();
|
||||||
}
|
}
|
||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
||||||
mqttSecureClient->setBufferSizes(512, 512);
|
mqttSecureClient->setBufferSizes(512, 512);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if(LittleFS.begin()) {
|
if(LittleFS.begin()) {
|
||||||
char *ca = NULL;
|
|
||||||
char *cert = NULL;
|
|
||||||
char *key = NULL;
|
|
||||||
File file;
|
File file;
|
||||||
|
|
||||||
if(LittleFS.exists(FILE_MQTT_CA)) {
|
if(LittleFS.exists(FILE_MQTT_CA)) {
|
||||||
debugI("Found MQTT CA file");
|
debugI("Found MQTT CA file (%dkb free heap)", ESP.getFreeHeap());
|
||||||
file = LittleFS.open(FILE_MQTT_CA, "r");
|
file = LittleFS.open(FILE_MQTT_CA, "r");
|
||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
||||||
BearSSL::X509List *serverTrustedCA = new BearSSL::X509List(file);
|
BearSSL::X509List *serverTrustedCA = new BearSSL::X509List(file);
|
||||||
@@ -1263,26 +1326,37 @@ void MQTT_connect() {
|
|||||||
#elif defined(ESP32)
|
#elif defined(ESP32)
|
||||||
mqttSecureClient->loadCACert(file, file.size());
|
mqttSecureClient->loadCACert(file, file.size());
|
||||||
#endif
|
#endif
|
||||||
|
file.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(LittleFS.exists(FILE_MQTT_CERT) && LittleFS.exists(FILE_MQTT_KEY)) {
|
if(LittleFS.exists(FILE_MQTT_CERT) && LittleFS.exists(FILE_MQTT_KEY)) {
|
||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
||||||
|
debugI("Found MQTT certificate file (%dkb free heap)", ESP.getFreeHeap());
|
||||||
file = LittleFS.open(FILE_MQTT_CERT, "r");
|
file = LittleFS.open(FILE_MQTT_CERT, "r");
|
||||||
BearSSL::X509List *serverCertList = new BearSSL::X509List(file);
|
BearSSL::X509List *serverCertList = new BearSSL::X509List(file);
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
debugI("Found MQTT key file (%dkb free heap)", ESP.getFreeHeap());
|
||||||
file = LittleFS.open(FILE_MQTT_KEY, "r");
|
file = LittleFS.open(FILE_MQTT_KEY, "r");
|
||||||
BearSSL::PrivateKey *serverPrivKey = new BearSSL::PrivateKey(file);
|
BearSSL::PrivateKey *serverPrivKey = new BearSSL::PrivateKey(file);
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
debugD("Setting client certificates (%dkb free heap)", ESP.getFreeHeap());
|
||||||
mqttSecureClient->setClientRSACert(serverCertList, serverPrivKey);
|
mqttSecureClient->setClientRSACert(serverCertList, serverPrivKey);
|
||||||
#elif defined(ESP32)
|
#elif defined(ESP32)
|
||||||
debugI("Found MQTT certificate file");
|
debugI("Found MQTT certificate file (%dkb free heap)", ESP.getFreeHeap());
|
||||||
file = LittleFS.open(FILE_MQTT_CERT, "r");
|
file = LittleFS.open(FILE_MQTT_CERT, "r");
|
||||||
mqttSecureClient->loadCertificate(file, file.size());
|
mqttSecureClient->loadCertificate(file, file.size());
|
||||||
|
file.close();
|
||||||
|
|
||||||
debugI("Found MQTT key file");
|
debugI("Found MQTT key file (%dkb free heap)", ESP.getFreeHeap());
|
||||||
file = LittleFS.open(FILE_MQTT_KEY, "r");
|
file = LittleFS.open(FILE_MQTT_KEY, "r");
|
||||||
mqttSecureClient->loadPrivateKey(file, file.size());
|
mqttSecureClient->loadPrivateKey(file, file.size());
|
||||||
|
file.close();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
LittleFS.end();
|
LittleFS.end();
|
||||||
|
debugD("MQTT SSL setup complete (%dkb free heap)", ESP.getFreeHeap());
|
||||||
}
|
}
|
||||||
mqttClient = mqttSecureClient;
|
mqttClient = mqttSecureClient;
|
||||||
} else if(mqttClient == NULL) {
|
} else if(mqttClient == NULL) {
|
||||||
|
|||||||
53
src/GBTAssembler.cpp
Normal file
53
src/GBTAssembler.cpp
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#include "Arduino.h"
|
||||||
|
#include "GBTAssembler.h"
|
||||||
|
#include "ams/crc.h"
|
||||||
|
|
||||||
|
GBTAssembler::GBTAssembler() {
|
||||||
|
buf = (uint8_t *)malloc((size_t)1024); // TODO find out from first package ?
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBTAssembler::init(const uint8_t* d, HDLCContext* context) {
|
||||||
|
memcpy(buf, d, context->headersize);
|
||||||
|
pos = headersize = context->headersize;
|
||||||
|
buf[pos++] = 0x00; // HCS
|
||||||
|
buf[pos++] = 0x00; // HCS
|
||||||
|
buf[pos++] = 0xE6;
|
||||||
|
buf[pos++] = 0xE7;
|
||||||
|
buf[pos++] = 0x00;
|
||||||
|
lastSequenceNumber = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GBTAssembler::append(HDLCContext* context, const uint8_t* d, int length, Print* debugger) {
|
||||||
|
GBTHeader* h = (GBTHeader*) (d+context->apduStart);
|
||||||
|
uint16_t sequence = ntohs(h->sequence);
|
||||||
|
|
||||||
|
if(h->flag != 0xE0) return -9;
|
||||||
|
|
||||||
|
if(sequence == 1) {
|
||||||
|
init(d, context);
|
||||||
|
} else if(lastSequenceNumber != sequence-1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* ptr = (uint8_t*) &h[1];
|
||||||
|
memcpy(buf + pos, ptr, h->size);
|
||||||
|
pos += h->size;
|
||||||
|
lastSequenceNumber = sequence;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t GBTAssembler::write(const uint8_t* d) {
|
||||||
|
uint16_t head = (0xA000) | pos+1;
|
||||||
|
buf[1] = (head>>8) & 0xFF;
|
||||||
|
buf[2] = head & 0xFF;
|
||||||
|
uint16_t hcs = crc16_x25(buf+1, headersize-1);
|
||||||
|
buf[headersize] = (hcs>>8) & 0xFF;
|
||||||
|
buf[headersize+1] = hcs & 0xFF;
|
||||||
|
|
||||||
|
uint16_t fcs = crc16_x25(buf+1, pos-1);
|
||||||
|
buf[pos++] = (fcs>>8) & 0xFF;
|
||||||
|
buf[pos++] = fcs & 0xFF;
|
||||||
|
buf[pos++] = HDLC_FLAG;
|
||||||
|
memcpy((uint8_t *) d, buf, pos);
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
29
src/GBTAssembler.h
Normal file
29
src/GBTAssembler.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef _GBT_ASSEMBLER_H
|
||||||
|
#define _GBT_ASSEMBLER_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "ams/hdlc.h"
|
||||||
|
|
||||||
|
typedef struct GBTHeader {
|
||||||
|
uint8_t flag;
|
||||||
|
uint8_t control;
|
||||||
|
uint16_t sequence;
|
||||||
|
uint16_t sequenceAck;
|
||||||
|
uint8_t size;
|
||||||
|
} __attribute__((packed)) GBTHeader;
|
||||||
|
|
||||||
|
class GBTAssembler {
|
||||||
|
public:
|
||||||
|
GBTAssembler();
|
||||||
|
void init(const uint8_t* d, HDLCContext* context);
|
||||||
|
int append(HDLCContext* context, const uint8_t* d, int length, Print* debugger);
|
||||||
|
uint16_t write(const uint8_t* d);
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint16_t pos = 0;
|
||||||
|
uint8_t headersize = 0;
|
||||||
|
uint8_t *buf;
|
||||||
|
uint8_t lastSequenceNumber = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -14,12 +14,30 @@ void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) {
|
|||||||
config->tempSensorPin = 0xFF;
|
config->tempSensorPin = 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(ESP32)
|
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||||
|
analogReadResolution(13);
|
||||||
|
analogRange = 8192;
|
||||||
|
analogSetAttenuation(ADC_11db);
|
||||||
|
#elif defined(ESP32)
|
||||||
analogReadResolution(12);
|
analogReadResolution(12);
|
||||||
analogRange = 4096;
|
analogRange = 4096;
|
||||||
|
analogSetAttenuation(ADC_6db);
|
||||||
#endif
|
#endif
|
||||||
if(config->vccPin > 0 && config->vccPin < 40) {
|
if(config->vccPin > 0 && config->vccPin < 40) {
|
||||||
#if defined(CONFIG_IDF_TARGET_ESP32)
|
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||||
|
getAdcChannel(config->vccPin, voltAdc);
|
||||||
|
if(voltAdc.unit != 0xFF) {
|
||||||
|
if(voltAdc.unit == ADC_UNIT_1) {
|
||||||
|
voltAdcChar = (esp_adc_cal_characteristics_t*) calloc(1, sizeof(esp_adc_cal_characteristics_t));
|
||||||
|
esp_adc_cal_value_t adcVal = esp_adc_cal_characterize((adc_unit_t) voltAdc.unit, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_13, 1100, voltAdcChar);
|
||||||
|
adc1_config_channel_atten((adc1_channel_t) voltAdc.channel, ADC_ATTEN_DB_11);
|
||||||
|
} else if(voltAdc.unit == ADC_UNIT_2) {
|
||||||
|
voltAdcChar = (esp_adc_cal_characteristics_t*) calloc(1, sizeof(esp_adc_cal_characteristics_t));
|
||||||
|
esp_adc_cal_value_t adcVal = esp_adc_cal_characterize((adc_unit_t) voltAdc.unit, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_13, 1100, voltAdcChar);
|
||||||
|
adc2_config_channel_atten((adc2_channel_t) voltAdc.channel, ADC_ATTEN_DB_11);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#elif defined(ESP32)
|
||||||
getAdcChannel(config->vccPin, voltAdc);
|
getAdcChannel(config->vccPin, voltAdc);
|
||||||
if(voltAdc.unit != 0xFF) {
|
if(voltAdc.unit != 0xFF) {
|
||||||
if(voltAdc.unit == ADC_UNIT_1) {
|
if(voltAdc.unit == ADC_UNIT_1) {
|
||||||
@@ -113,6 +131,16 @@ void HwTools::getAdcChannel(uint8_t pin, AdcConfig& config) {
|
|||||||
config.unit = ADC_UNIT_1;
|
config.unit = ADC_UNIT_1;
|
||||||
config.channel = ADC1_CHANNEL_7;
|
config.channel = ADC1_CHANNEL_7;
|
||||||
break;
|
break;
|
||||||
|
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||||
|
case ADC1_CHANNEL_8_GPIO_NUM:
|
||||||
|
config.unit = ADC_UNIT_1;
|
||||||
|
config.channel = ADC1_CHANNEL_8;
|
||||||
|
break;
|
||||||
|
case ADC1_CHANNEL_9_GPIO_NUM:
|
||||||
|
config.unit = ADC_UNIT_1;
|
||||||
|
config.channel = ADC1_CHANNEL_9;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
case ADC2_CHANNEL_0_GPIO_NUM:
|
case ADC2_CHANNEL_0_GPIO_NUM:
|
||||||
config.unit = ADC_UNIT_2;
|
config.unit = ADC_UNIT_2;
|
||||||
config.channel = ADC2_CHANNEL_0;
|
config.channel = ADC2_CHANNEL_0;
|
||||||
@@ -160,7 +188,7 @@ void HwTools::getAdcChannel(uint8_t pin, AdcConfig& config) {
|
|||||||
double HwTools::getVcc() {
|
double HwTools::getVcc() {
|
||||||
double volts = 0.0;
|
double volts = 0.0;
|
||||||
if(config->vccPin != 0xFF) {
|
if(config->vccPin != 0xFF) {
|
||||||
#if defined(CONFIG_IDF_TARGET_ESP32)
|
#if defined(ESP32)
|
||||||
if(voltAdc.unit != 0xFF) {
|
if(voltAdc.unit != 0xFF) {
|
||||||
uint32_t x = 0;
|
uint32_t x = 0;
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
@@ -168,7 +196,11 @@ double HwTools::getVcc() {
|
|||||||
x += adc1_get_raw((adc1_channel_t) voltAdc.channel);
|
x += adc1_get_raw((adc1_channel_t) voltAdc.channel);
|
||||||
} else if(voltAdc.unit == ADC_UNIT_2) {
|
} else if(voltAdc.unit == ADC_UNIT_2) {
|
||||||
int v = 0;
|
int v = 0;
|
||||||
|
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||||
|
adc2_get_raw((adc2_channel_t) voltAdc.channel, ADC_WIDTH_BIT_13, &v);
|
||||||
|
#elif defined(CONFIG_IDF_TARGET_ESP32)
|
||||||
adc2_get_raw((adc2_channel_t) voltAdc.channel, ADC_WIDTH_BIT_12, &v);
|
adc2_get_raw((adc2_channel_t) voltAdc.channel, ADC_WIDTH_BIT_12, &v);
|
||||||
|
#endif
|
||||||
x += v;
|
x += v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ void mbus_hexdump(const uint8_t* buf, int len) {
|
|||||||
printf("]\n");
|
printf("]\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTime* timestamp) {
|
int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTime* timestamp, HDLCContext* context) {
|
||||||
int len;
|
int len;
|
||||||
int headersize = 3;
|
int headersize = 3;
|
||||||
int footersize = 1;
|
int footersize = 1;
|
||||||
@@ -64,6 +64,8 @@ int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTim
|
|||||||
headersize++;
|
headersize++;
|
||||||
ptr++;
|
ptr++;
|
||||||
|
|
||||||
|
context->headersize = headersize + 1; // Include control byte in reported header size
|
||||||
|
|
||||||
HDLC3CtrlHcs* t3 = (HDLC3CtrlHcs*) (ptr);
|
HDLC3CtrlHcs* t3 = (HDLC3CtrlHcs*) (ptr);
|
||||||
headersize += 3;
|
headersize += 3;
|
||||||
|
|
||||||
@@ -73,10 +75,12 @@ int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTim
|
|||||||
|
|
||||||
ptr += sizeof *t3;
|
ptr += sizeof *t3;
|
||||||
|
|
||||||
// Extract LLC
|
// Extract LLC if present
|
||||||
HDLCLLC* llc = (HDLCLLC*) ptr;
|
if(((*ptr) & 0xFF) == 0xE6) {
|
||||||
ptr += sizeof *llc;
|
HDLCLLC* llc = (HDLCLLC*) ptr;
|
||||||
headersize += sizeof *llc;
|
ptr += sizeof *llc;
|
||||||
|
headersize += sizeof *llc;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return HDLC_UNKNOWN_DATA;
|
return HDLC_UNKNOWN_DATA;
|
||||||
}
|
}
|
||||||
@@ -133,8 +137,10 @@ int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTim
|
|||||||
return HDLC_UNKNOWN_DATA;
|
return HDLC_UNKNOWN_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.flush();
|
context->apdu = *ptr;
|
||||||
|
context->apduStart = ptr-d;
|
||||||
|
|
||||||
|
// Encrypted
|
||||||
if(((*ptr) & 0xFF) == 0xDB) {
|
if(((*ptr) & 0xFF) == 0xDB) {
|
||||||
if(length < headersize + 18)
|
if(length < headersize + 18)
|
||||||
return HDLC_FRAME_INCOMPLETE;
|
return HDLC_FRAME_INCOMPLETE;
|
||||||
@@ -144,8 +150,22 @@ int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTim
|
|||||||
ptr += ret;
|
ptr += ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
HDLCADPU* adpu = (HDLCADPU*) (ptr);
|
// GBT (General Block Transfer)
|
||||||
ptr += sizeof *adpu;
|
if(((*ptr) & 0xFF) == 0xE0) {
|
||||||
|
uint8_t control = *(ptr+1); // 1100 0000, 1=last frame, 1=streaming, remainig=window
|
||||||
|
// TODO GBT data from ptr-d
|
||||||
|
if((control & 0x80) == 0x00) {
|
||||||
|
return HDLC_GBT_INTERMEDIATE;
|
||||||
|
} else {
|
||||||
|
return HDLC_GBT_LAST;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Yes, we are doing this again, after potential decryption
|
||||||
|
context->apdu = *ptr;
|
||||||
|
context->apduStart = ptr-d;
|
||||||
|
ptr++;
|
||||||
|
ptr += 4; // Skip invoke ID and priority
|
||||||
|
|
||||||
// ADPU timestamp
|
// ADPU timestamp
|
||||||
CosemData* dateTime = (CosemData*) ptr;
|
CosemData* dateTime = (CosemData*) ptr;
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
#define HDLC_HCS_ERROR -3
|
#define HDLC_HCS_ERROR -3
|
||||||
#define HDLC_FRAME_INCOMPLETE -4
|
#define HDLC_FRAME_INCOMPLETE -4
|
||||||
#define HDLC_UNKNOWN_DATA -9
|
#define HDLC_UNKNOWN_DATA -9
|
||||||
|
#define HDLC_GBT_INTERMEDIATE -21
|
||||||
|
#define HDLC_GBT_LAST -22
|
||||||
#define HDLC_ENCRYPTION_CONFIG_MISSING -90
|
#define HDLC_ENCRYPTION_CONFIG_MISSING -90
|
||||||
#define HDLC_ENCRYPTION_AUTH_FAILED -91
|
#define HDLC_ENCRYPTION_AUTH_FAILED -91
|
||||||
#define HDLC_ENCRYPTION_KEY_FAILED -92
|
#define HDLC_ENCRYPTION_KEY_FAILED -92
|
||||||
@@ -35,6 +37,12 @@ struct HDLCConfig {
|
|||||||
uint8_t authentication_tag[12];
|
uint8_t authentication_tag[12];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct HDLCContext {
|
||||||
|
uint8_t apdu;
|
||||||
|
uint8_t apduStart;
|
||||||
|
uint8_t headersize;
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct HDLCHeader {
|
typedef struct HDLCHeader {
|
||||||
uint8_t flag;
|
uint8_t flag;
|
||||||
uint16_t format;
|
uint16_t format;
|
||||||
@@ -56,11 +64,6 @@ typedef struct HDLCLLC {
|
|||||||
uint8_t control;
|
uint8_t control;
|
||||||
} __attribute__((packed)) HDLCLLC;
|
} __attribute__((packed)) HDLCLLC;
|
||||||
|
|
||||||
typedef struct HDLCADPU {
|
|
||||||
uint8_t flag;
|
|
||||||
uint32_t id;
|
|
||||||
} __attribute__((packed)) HDLCADPU;
|
|
||||||
|
|
||||||
typedef struct MbusHeader {
|
typedef struct MbusHeader {
|
||||||
uint8_t flag1;
|
uint8_t flag1;
|
||||||
uint8_t len1;
|
uint8_t len1;
|
||||||
@@ -159,7 +162,7 @@ typedef union {
|
|||||||
} CosemData;
|
} CosemData;
|
||||||
|
|
||||||
void mbus_hexdump(const uint8_t* buf, int len);
|
void mbus_hexdump(const uint8_t* buf, int len);
|
||||||
int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTime* timestamp);
|
int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTime* timestamp, HDLCContext* context);
|
||||||
int mbus_decrypt(const uint8_t* d, int length, HDLCConfig* config);
|
int mbus_decrypt(const uint8_t* d, int length, HDLCConfig* config);
|
||||||
|
|
||||||
uint8_t mbusChecksum(const uint8_t* p, int len);
|
uint8_t mbusChecksum(const uint8_t* p, int len);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#include "DnbCurrParser.h"
|
#include "DnbCurrParser.h"
|
||||||
#include "Arduino.h"
|
#include "Arduino.h"
|
||||||
#include "HardwareSerial.h"
|
|
||||||
|
|
||||||
float DnbCurrParser::getValue() {
|
float DnbCurrParser::getValue() {
|
||||||
return value;
|
return value;
|
||||||
@@ -30,13 +29,14 @@ size_t DnbCurrParser::write(const uint8_t *buffer, size_t size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t DnbCurrParser::write(uint8_t byte) {
|
size_t DnbCurrParser::write(uint8_t byte) {
|
||||||
if(pos >= 64) pos = 0;
|
if(pos >= 128) pos = 0;
|
||||||
if(pos == 0) {
|
if(pos == 0) {
|
||||||
if(byte == '<') {
|
if(byte == '<') {
|
||||||
buf[pos++] = byte;
|
buf[pos++] = byte;
|
||||||
}
|
}
|
||||||
} else if(byte == '>') {
|
} else if(byte == '>') {
|
||||||
buf[pos++] = byte;
|
buf[pos++] = byte;
|
||||||
|
buf[pos++] = '\0';
|
||||||
if(strncmp(buf, "<Series", 7) == 0) {
|
if(strncmp(buf, "<Series", 7) == 0) {
|
||||||
for(int i = 0; i < pos; i++) {
|
for(int i = 0; i < pos; i++) {
|
||||||
if(strncmp(buf+i, "UNIT_MULT=\"", 11) == 0) {
|
if(strncmp(buf+i, "UNIT_MULT=\"", 11) == 0) {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ private:
|
|||||||
uint8_t scale = 0;
|
uint8_t scale = 0;
|
||||||
float value = 1.0;
|
float value = 1.0;
|
||||||
|
|
||||||
char buf[64];
|
char buf[128];
|
||||||
uint8_t pos = 0;
|
uint8_t pos = 0;
|
||||||
uint8_t mode = 0;
|
uint8_t mode = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,7 +27,10 @@ void EntsoeApi::setup(EntsoeConfig& config) {
|
|||||||
this->config = new EntsoeConfig();
|
this->config = new EntsoeConfig();
|
||||||
}
|
}
|
||||||
memcpy(this->config, &config, sizeof(config));
|
memcpy(this->config, &config, sizeof(config));
|
||||||
lastCurrencyFetch = 0;
|
lastTodayFetch = lastTomorrowFetch = lastCurrencyFetch = 0;
|
||||||
|
if(today != NULL) delete today;
|
||||||
|
if(tomorrow != NULL) delete tomorrow;
|
||||||
|
today = tomorrow = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* EntsoeApi::getToken() {
|
char* EntsoeApi::getToken() {
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En
|
|||||||
);
|
);
|
||||||
mqtt->publish(topic + "/energy", json);
|
mqtt->publish(topic + "/energy", json);
|
||||||
}
|
}
|
||||||
|
String meterModel = data->getMeterModel();
|
||||||
|
meterModel.replace("\\", "\\\\");
|
||||||
if(data->getListType() == 1) { // publish power counts
|
if(data->getListType() == 1) { // publish power counts
|
||||||
snprintf_P(json, BufferSize, HA1_JSON,
|
snprintf_P(json, BufferSize, HA1_JSON,
|
||||||
data->getActiveImportPower()
|
data->getActiveImportPower()
|
||||||
@@ -34,7 +36,7 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En
|
|||||||
snprintf_P(json, BufferSize, HA3_JSON,
|
snprintf_P(json, BufferSize, HA3_JSON,
|
||||||
data->getListId().c_str(),
|
data->getListId().c_str(),
|
||||||
data->getMeterId().c_str(),
|
data->getMeterId().c_str(),
|
||||||
data->getMeterModel().c_str(),
|
meterModel.c_str(),
|
||||||
data->getActiveImportPower(),
|
data->getActiveImportPower(),
|
||||||
data->getReactiveImportPower(),
|
data->getReactiveImportPower(),
|
||||||
data->getActiveExportPower(),
|
data->getActiveExportPower(),
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
|
|||||||
if(topic.isEmpty() || !mqtt->connected())
|
if(topic.isEmpty() || !mqtt->connected())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
String meterModel = data->getMeterModel();
|
||||||
|
meterModel.replace("\\", "\\\\");
|
||||||
if(data->getListType() == 1) {
|
if(data->getListType() == 1) {
|
||||||
snprintf_P(json, BufferSize, JSON1_JSON,
|
snprintf_P(json, BufferSize, JSON1_JSON,
|
||||||
WiFi.macAddress().c_str(),
|
WiFi.macAddress().c_str(),
|
||||||
@@ -39,7 +41,7 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
|
|||||||
hw->getTemperature(),
|
hw->getTemperature(),
|
||||||
data->getListId().c_str(),
|
data->getListId().c_str(),
|
||||||
data->getMeterId().c_str(),
|
data->getMeterId().c_str(),
|
||||||
data->getMeterModel().c_str(),
|
meterModel.c_str(),
|
||||||
data->getActiveImportPower(),
|
data->getActiveImportPower(),
|
||||||
data->getReactiveImportPower(),
|
data->getReactiveImportPower(),
|
||||||
data->getActiveExportPower(),
|
data->getActiveExportPower(),
|
||||||
@@ -67,7 +69,7 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
|
|||||||
hw->getTemperature(),
|
hw->getTemperature(),
|
||||||
data->getListId().c_str(),
|
data->getListId().c_str(),
|
||||||
data->getMeterId().c_str(),
|
data->getMeterId().c_str(),
|
||||||
data->getMeterModel().c_str(),
|
meterModel.c_str(),
|
||||||
data->getActiveImportPower(),
|
data->getActiveImportPower(),
|
||||||
data->getReactiveImportPower(),
|
data->getReactiveImportPower(),
|
||||||
data->getActiveExportPower(),
|
data->getActiveExportPower(),
|
||||||
@@ -100,7 +102,7 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
|
|||||||
hw->getTemperature(),
|
hw->getTemperature(),
|
||||||
data->getListId().c_str(),
|
data->getListId().c_str(),
|
||||||
data->getMeterId().c_str(),
|
data->getMeterId().c_str(),
|
||||||
data->getMeterModel().c_str(),
|
meterModel.c_str(),
|
||||||
data->getActiveImportPower(),
|
data->getActiveImportPower(),
|
||||||
data->getReactiveImportPower(),
|
data->getReactiveImportPower(),
|
||||||
data->getActiveExportPower(),
|
data->getActiveExportPower(),
|
||||||
|
|||||||
@@ -356,7 +356,11 @@ void AmsWebServer::configMeterHtml() {
|
|||||||
html.replace("{mid}", meterState->getMeterId());
|
html.replace("{mid}", meterState->getMeterId());
|
||||||
html.replace("{b}", String(meterConfig->baud));
|
html.replace("{b}", String(meterConfig->baud));
|
||||||
html.replace("{b2400}", meterConfig->baud == 2400 ? "selected" : "");
|
html.replace("{b2400}", meterConfig->baud == 2400 ? "selected" : "");
|
||||||
|
html.replace("{b4800}", meterConfig->baud == 4800 ? "selected" : "");
|
||||||
html.replace("{b9600}", meterConfig->baud == 9600 ? "selected" : "");
|
html.replace("{b9600}", meterConfig->baud == 9600 ? "selected" : "");
|
||||||
|
html.replace("{b19200}", meterConfig->baud == 19200 ? "selected" : "");
|
||||||
|
html.replace("{b38400}", meterConfig->baud == 38400 ? "selected" : "");
|
||||||
|
html.replace("{b57600}", meterConfig->baud == 57600 ? "selected" : "");
|
||||||
html.replace("{b115200}", meterConfig->baud == 115200 ? "selected" : "");
|
html.replace("{b115200}", meterConfig->baud == 115200 ? "selected" : "");
|
||||||
html.replace("{c}", String(meterConfig->baud));
|
html.replace("{c}", String(meterConfig->baud));
|
||||||
html.replace("{c2}", meterConfig->parity == 2 ? "selected" : "");
|
html.replace("{c2}", meterConfig->parity == 2 ? "selected" : "");
|
||||||
@@ -1482,8 +1486,13 @@ String AmsWebServer::getSerialSelectOptions(int selected) {
|
|||||||
gpioOptions += "<option value=\"3\">UART0 (GPIO3)</option>";
|
gpioOptions += "<option value=\"3\">UART0 (GPIO3)</option>";
|
||||||
}
|
}
|
||||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||||
int numGpio = 31;
|
int numGpio = 30;
|
||||||
int gpios[] = {4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,21,22,23,25,32,33,34,35,36,39,40,41,42,43,44};
|
int gpios[] = {4,5,6,7,8,9,10,11,12,13,14,15,16,17,19,21,22,23,25,32,33,34,35,36,39,40,41,42,43,44};
|
||||||
|
if(selected == 18) {
|
||||||
|
gpioOptions += "<option value=\"18\" selected>UART1 (GPIO18)</option>";
|
||||||
|
} else {
|
||||||
|
gpioOptions += "<option value=\"18\">UART1 (GPIO18)</option>";
|
||||||
|
}
|
||||||
#elif defined(ESP32)
|
#elif defined(ESP32)
|
||||||
int numGpio = 24;
|
int numGpio = 24;
|
||||||
int gpios[] = {4,5,6,7,8,10,11,12,13,14,15,17,18,19,21,22,23,25,32,33,34,35,36,39};
|
int gpios[] = {4,5,6,7,8,10,11,12,13,14,15,17,18,19,21,22,23,25,32,33,34,35,36,39};
|
||||||
@@ -1605,11 +1614,15 @@ void AmsWebServer::firmwareHtml() {
|
|||||||
String html = String((const __FlashStringHelper*) FIRMWARE_HTML);
|
String html = String((const __FlashStringHelper*) FIRMWARE_HTML);
|
||||||
|
|
||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
||||||
html.replace("{chipset}", "ESP8266");
|
html.replace("{chipset}", "ESP8266");
|
||||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||||
html.replace("{chipset}", "ESP32S2");
|
html.replace("{chipset}", "ESP32S2");
|
||||||
#elif defined(ESP32)
|
#elif defined(ESP32)
|
||||||
html.replace("{chipset}", "ESP32");
|
#if defined(CONFIG_FREERTOS_UNICORE)
|
||||||
|
html.replace("{chipset}", "ESP32SOLO");
|
||||||
|
#else
|
||||||
|
html.replace("{chipset}", "ESP32");
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN);
|
server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN);
|
||||||
@@ -1657,8 +1670,9 @@ void AmsWebServer::firmwareDownload() {
|
|||||||
printI("Downloading firmware...");
|
printI("Downloading firmware...");
|
||||||
HTTPClient httpClient;
|
HTTPClient httpClient;
|
||||||
httpClient.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
httpClient.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||||
httpClient.setTimeout(20000);
|
httpClient.setReuse(false);
|
||||||
httpClient.addHeader("User-Agent", "ams2mqtt/" + String(VERSION));
|
httpClient.setTimeout(60000);
|
||||||
|
httpClient.setUserAgent("ams2mqtt/" + String(VERSION));
|
||||||
|
|
||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
||||||
WiFiClient client;
|
WiFiClient client;
|
||||||
@@ -1687,18 +1701,28 @@ void AmsWebServer::firmwareDownload() {
|
|||||||
return;
|
return;
|
||||||
*/
|
*/
|
||||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||||
WiFiClientSecure client;
|
httpClient.setConnectTimeout(60000);
|
||||||
client.setInsecure();
|
|
||||||
String url = "https://github.com/gskjold/AmsToMqttBridge/releases/download/" + version + "/ams2mqtt-esp32s2-" + versionStripped + ".bin";
|
String url = "https://github.com/gskjold/AmsToMqttBridge/releases/download/" + version + "/ams2mqtt-esp32s2-" + versionStripped + ".bin";
|
||||||
httpClient.addHeader("Referer", "https://github.com/gskjold/AmsToMqttBridge/releases");
|
httpClient.addHeader("Referer", "https://github.com/gskjold/AmsToMqttBridge/releases");
|
||||||
#elif defined(ESP32)
|
#elif defined(ESP32)
|
||||||
WiFiClientSecure client;
|
httpClient.setConnectTimeout(60000);
|
||||||
client.setInsecure();
|
#if defined(CONFIG_FREERTOS_UNICORE)
|
||||||
String url = "https://github.com/gskjold/AmsToMqttBridge/releases/download/" + version + "/ams2mqtt-esp32-" + versionStripped + ".bin";
|
String url = "https://github.com/gskjold/AmsToMqttBridge/releases/download/" + version + "/ams2mqtt-esp32solo-" + versionStripped + ".bin";
|
||||||
|
#else
|
||||||
|
String url = "https://github.com/gskjold/AmsToMqttBridge/releases/download/" + version + "/ams2mqtt-esp32-" + versionStripped + ".bin";
|
||||||
|
#endif
|
||||||
|
|
||||||
httpClient.addHeader("Referer", "https://github.com/gskjold/AmsToMqttBridge/releases");
|
httpClient.addHeader("Referer", "https://github.com/gskjold/AmsToMqttBridge/releases");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
printD("Downloading from URL:");
|
||||||
|
printD(url);
|
||||||
|
|
||||||
|
#if defined(ESP8266)
|
||||||
if(httpClient.begin(client, url)) {
|
if(httpClient.begin(client, url)) {
|
||||||
|
#elif defined(ESP32)
|
||||||
|
if(httpClient.begin(url)) {
|
||||||
|
#endif
|
||||||
printD("HTTP client setup successful");
|
printD("HTTP client setup successful");
|
||||||
int status = httpClient.GET();
|
int status = httpClient.GET();
|
||||||
if(status == HTTP_CODE_OK) {
|
if(status == HTTP_CODE_OK) {
|
||||||
@@ -1726,8 +1750,8 @@ void AmsWebServer::firmwareDownload() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
printE("Communication error: ");
|
printE("Communication error: ");
|
||||||
|
debugger->printf("%d\n", status);
|
||||||
printE(httpClient.errorToString(status));
|
printE(httpClient.errorToString(status));
|
||||||
printI(url);
|
|
||||||
printD(httpClient.getString());
|
printD(httpClient.getString());
|
||||||
server.sendHeader("Location","/");
|
server.sendHeader("Location","/");
|
||||||
server.send(303);
|
server.send(303);
|
||||||
@@ -1738,7 +1762,6 @@ void AmsWebServer::firmwareDownload() {
|
|||||||
server.send(303);
|
server.send(303);
|
||||||
}
|
}
|
||||||
httpClient.end();
|
httpClient.end();
|
||||||
client.stop();
|
|
||||||
} else {
|
} else {
|
||||||
printI("No firmware version specified...");
|
printI("No firmware version specified...");
|
||||||
server.sendHeader("Location","/");
|
server.sendHeader("Location","/");
|
||||||
@@ -1857,7 +1880,7 @@ void AmsWebServer::mqttCa() {
|
|||||||
}
|
}
|
||||||
LittleFS.end();
|
LittleFS.end();
|
||||||
} else {
|
} else {
|
||||||
server.sendHeader("Location","/config-mqtt");
|
server.sendHeader("Location","/mqtt");
|
||||||
server.send(303);
|
server.send(303);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1869,7 +1892,7 @@ void AmsWebServer::mqttCaUpload() {
|
|||||||
uploadFile(FILE_MQTT_CA);
|
uploadFile(FILE_MQTT_CA);
|
||||||
HTTPUpload& upload = server.upload();
|
HTTPUpload& upload = server.upload();
|
||||||
if(upload.status == UPLOAD_FILE_END) {
|
if(upload.status == UPLOAD_FILE_END) {
|
||||||
server.sendHeader("Location","/config-mqtt");
|
server.sendHeader("Location","/mqtt");
|
||||||
server.send(303);
|
server.send(303);
|
||||||
|
|
||||||
MqttConfig mqttConfig;
|
MqttConfig mqttConfig;
|
||||||
@@ -1885,7 +1908,7 @@ void AmsWebServer::mqttCaDelete() {
|
|||||||
|
|
||||||
if(!uploading) { // Not an upload
|
if(!uploading) { // Not an upload
|
||||||
deleteFile(FILE_MQTT_CA);
|
deleteFile(FILE_MQTT_CA);
|
||||||
server.sendHeader("Location","/config-mqtt");
|
server.sendHeader("Location","/mqtt");
|
||||||
server.send(303);
|
server.send(303);
|
||||||
MqttConfig mqttConfig;
|
MqttConfig mqttConfig;
|
||||||
if(config->getMqttConfig(mqttConfig) && mqttConfig.ssl) {
|
if(config->getMqttConfig(mqttConfig) && mqttConfig.ssl) {
|
||||||
@@ -1911,7 +1934,7 @@ void AmsWebServer::mqttCert() {
|
|||||||
}
|
}
|
||||||
LittleFS.end();
|
LittleFS.end();
|
||||||
} else {
|
} else {
|
||||||
server.sendHeader("Location","/config-mqtt");
|
server.sendHeader("Location","/mqtt");
|
||||||
server.send(303);
|
server.send(303);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1923,7 +1946,7 @@ void AmsWebServer::mqttCertUpload() {
|
|||||||
uploadFile(FILE_MQTT_CERT);
|
uploadFile(FILE_MQTT_CERT);
|
||||||
HTTPUpload& upload = server.upload();
|
HTTPUpload& upload = server.upload();
|
||||||
if(upload.status == UPLOAD_FILE_END) {
|
if(upload.status == UPLOAD_FILE_END) {
|
||||||
server.sendHeader("Location","/config-mqtt");
|
server.sendHeader("Location","/mqtt");
|
||||||
server.send(303);
|
server.send(303);
|
||||||
MqttConfig mqttConfig;
|
MqttConfig mqttConfig;
|
||||||
if(config->getMqttConfig(mqttConfig) && mqttConfig.ssl) {
|
if(config->getMqttConfig(mqttConfig) && mqttConfig.ssl) {
|
||||||
@@ -1938,7 +1961,7 @@ void AmsWebServer::mqttCertDelete() {
|
|||||||
|
|
||||||
if(!uploading) { // Not an upload
|
if(!uploading) { // Not an upload
|
||||||
deleteFile(FILE_MQTT_CERT);
|
deleteFile(FILE_MQTT_CERT);
|
||||||
server.sendHeader("Location","/config-mqtt");
|
server.sendHeader("Location","/mqtt");
|
||||||
server.send(303);
|
server.send(303);
|
||||||
MqttConfig mqttConfig;
|
MqttConfig mqttConfig;
|
||||||
if(config->getMqttConfig(mqttConfig) && mqttConfig.ssl) {
|
if(config->getMqttConfig(mqttConfig) && mqttConfig.ssl) {
|
||||||
@@ -1964,7 +1987,7 @@ void AmsWebServer::mqttKey() {
|
|||||||
}
|
}
|
||||||
LittleFS.end();
|
LittleFS.end();
|
||||||
} else {
|
} else {
|
||||||
server.sendHeader("Location","/config-mqtt");
|
server.sendHeader("Location","/mqtt");
|
||||||
server.send(303);
|
server.send(303);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1976,7 +1999,7 @@ void AmsWebServer::mqttKeyUpload() {
|
|||||||
uploadFile(FILE_MQTT_KEY);
|
uploadFile(FILE_MQTT_KEY);
|
||||||
HTTPUpload& upload = server.upload();
|
HTTPUpload& upload = server.upload();
|
||||||
if(upload.status == UPLOAD_FILE_END) {
|
if(upload.status == UPLOAD_FILE_END) {
|
||||||
server.sendHeader("Location","/config-mqtt");
|
server.sendHeader("Location","/mqtt");
|
||||||
server.send(303);
|
server.send(303);
|
||||||
MqttConfig mqttConfig;
|
MqttConfig mqttConfig;
|
||||||
if(config->getMqttConfig(mqttConfig) && mqttConfig.ssl) {
|
if(config->getMqttConfig(mqttConfig) && mqttConfig.ssl) {
|
||||||
@@ -1991,7 +2014,7 @@ void AmsWebServer::mqttKeyDelete() {
|
|||||||
|
|
||||||
if(!uploading) { // Not an upload
|
if(!uploading) { // Not an upload
|
||||||
deleteFile(FILE_MQTT_KEY);
|
deleteFile(FILE_MQTT_KEY);
|
||||||
server.sendHeader("Location","/config-mqtt");
|
server.sendHeader("Location","/mqtt");
|
||||||
server.send(303);
|
server.send(303);
|
||||||
MqttConfig mqttConfig;
|
MqttConfig mqttConfig;
|
||||||
if(config->getMqttConfig(mqttConfig) && mqttConfig.ssl) {
|
if(config->getMqttConfig(mqttConfig) && mqttConfig.ssl) {
|
||||||
|
|||||||
@@ -36,7 +36,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<select class="form-control sd" name="b">
|
<select class="form-control sd" name="b">
|
||||||
<option value="2400" {b2400}>2400</option>
|
<option value="2400" {b2400}>2400</option>
|
||||||
|
<option value="4800" {b4800}>4800</option>
|
||||||
<option value="9600" {b9600}>9600</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>
|
<option value="115200" {b115200}>115200</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,11 +58,14 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-3 col-md-4 col-sm-3 col-6">
|
<div class="col-lg-2 col-md-4 col-sm-3 col-6">
|
||||||
<div class="m-2">
|
<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>
|
<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>
|
</div>
|
||||||
|
<div class="col-lg-4 col-sm-8">
|
||||||
|
<a href="https://github.com/gskjold/AmsToMqttBridge/wiki/Known-hardware-configurations" target="_blank">Known hardware configurations</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xl-3 col-lg-4 col-md-5 col-sm-7">
|
<div class="col-xl-3 col-lg-4 col-md-5 col-sm-7">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<span class="input-group-text">1</span>
|
<span class="input-group-text">1</span>
|
||||||
</div>
|
</div>
|
||||||
<input class="form-control text-right" name="t0" type="number" min="5" max="255" step="1" value="{t0}"/>
|
<input class="form-control text-right" name="t0" type="number" min="1" max="255" step="1" value="{t0}"/>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<span class="input-group-text">kWh</span>
|
<span class="input-group-text">kWh</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user