merging branches to prepare mqtt enablement

This commit is contained in:
MalteSchm 2023-04-12 09:41:40 +02:00
commit 97a8545d78
19 changed files with 778 additions and 21 deletions

View File

@ -52,6 +52,9 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Get tags
run: git fetch --force --tags origin
- name: Cache pip
uses: actions/cache@v3
with:

View File

@ -188,6 +188,12 @@ Topics for 3 phases of a power meter is configurable. Given is an example for th
| huawei/output_temp | R | Output air temperature | °C |
| huawei/efficiency | R | Efficiency | Percentage |
## Power Limiter topics
| Topic | R / W | Description | Value / Unit |
| --------------------------------------- | ----- | ---------------------------------------------------- | -------------------------- |
| powerlimiter/cmd/disable | W | Power Limiter disable override for external PL control | 0 / 1 |
| powerlimiter/status/disabled | R | Power Limiter disable override status | 0 / 1 |
## Currently supported Inverters
* Hoymiles HM-300
* Hoymiles HM-350

View File

@ -0,0 +1,20 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Configuration.h"
#include <espMqttClient.h>
class MqttHandlePowerLimiterClass {
public:
void init();
void loop();
private:
void onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);
uint32_t _lastPublishStats;
uint32_t _lastPublish;
};
extern MqttHandlePowerLimiterClass MqttHandlePowerLimiter;

View File

@ -26,6 +26,8 @@ public:
void loop();
plStates getPowerLimiterState();
int32_t getLastRequestedPowewrLimit();
void setDisable(bool disable);
bool getDisable();
private:
uint32_t _lastCommandSent = 0;
@ -33,6 +35,7 @@ private:
int32_t _lastRequestedPowerLimit = 0;
uint32_t _lastLimitSetTime = 0;
plStates _plState = STATE_DISCOVER;
bool _disabled = false;
float _powerMeter1Power;
float _powerMeter2Power;

View File

@ -7,6 +7,7 @@
#include <Hoymiles.h>
#include <memory>
#include "SDM.h"
#include "sml.h"
#ifndef SDM_RX_PIN
#define SDM_RX_PIN 13
@ -16,6 +17,16 @@
#define SDM_TX_PIN 32
#endif
#ifndef SML_RX_PIN
#define SML_RX_PIN 35
#endif
typedef struct {
const unsigned char OBIS[6];
void (*Fn)(double&);
float* Arg;
} OBISHandler;
class PowerMeterClass {
public:
enum SOURCE {
@ -23,6 +34,7 @@ public:
SOURCE_SDM1PH = 1,
SOURCE_SDM3PH = 2,
SOURCE_HTTP = 3,
SOURCE_SML = 4
};
void init();
void mqtt();
@ -40,14 +52,20 @@ private:
float _powerMeter1Power = 0.0;
float _powerMeter2Power = 0.0;
float _powerMeter3Power = 0.0;
float _powerMeterTotalPower = 0.0;
float _powerMeter1Voltage = 0.0;
float _powerMeter2Voltage = 0.0;
float _powerMeter3Voltage = 0.0;
float _PowerMeterImport = 0.0;
float _PowerMeterExport = 0.0;
float _powerMeterImport = 0.0;
float _powerMeterExport = 0.0;
bool mqttInitDone = false;
bool smlReadLoop();
const std::list<OBISHandler> smlHandlerList{
{{0x01, 0x00, 0x10, 0x07, 0x00, 0xff}, &smlOBISW, &_powerMeter1Power},
{{0x01, 0x00, 0x01, 0x08, 0x00, 0xff}, &smlOBISWh, &_powerMeterImport},
{{0x01, 0x00, 0x02, 0x08, 0x00, 0xff}, &smlOBISWh, &_powerMeterExport}
};
};
extern PowerMeterClass PowerMeter;

405
lib/SMLParser/sml.cpp Normal file
View File

@ -0,0 +1,405 @@
#include <stdio.h>
#include <string.h>
#include "sml.h"
#include "smlCrcTable.h"
#ifdef SML_DEBUG
char logBuff[200];
#ifdef SML_NATIVE
#define SML_LOG(...) \
do { \
printf(__VA_ARGS__); \
} while (0)
#define SML_TREELOG(level, ...) \
do { \
printf("%.*s", level, " "); \
printf(__VA_ARGS__); \
} while (0)
#elif ARDUINO
#include <Arduino.h>
#define SML_LOG(...) \
do { \
sprintf(logBuff, __VA_ARGS__); \
Serial.print(logBuff); \
} while (0)
#define SML_TREELOG(level, ...) \
do { \
sprintf(logBuff, __VA_ARGS__); \
Serial.print(logBuff); \
} while (0)
#endif
#else
#define SML_LOG(...) \
do { \
} while (0)
#define SML_TREELOG(level, ...) \
do { \
} while (0)
#endif
#define MAX_LIST_SIZE 80
#define MAX_TREE_SIZE 10
static sml_states_t currentState = SML_START;
static char nodes[MAX_TREE_SIZE];
static unsigned char currentLevel = 0;
static unsigned short crc = 0xFFFF;
static signed char sc;
static unsigned short crcMine = 0xFFFF;
static unsigned short crcReceived = 0x0000;
static unsigned char len = 4;
static unsigned char listBuffer[MAX_LIST_SIZE]; /* keeps a list
as length + state + data */
static unsigned char listPos = 0;
void crc16(unsigned char &byte)
{
#ifdef ARDUINO
crc =
pgm_read_word_near(&smlCrcTable[(byte ^ crc) & 0xff]) ^ (crc >> 8 & 0xff);
#else
crc = smlCrcTable[(byte ^ crc) & 0xff] ^ (crc >> 8 & 0xff);
#endif
}
void setState(sml_states_t state, int byteLen)
{
currentState = state;
len = byteLen;
}
void pushListBuffer(unsigned char byte)
{
if (listPos < MAX_LIST_SIZE) {
listBuffer[listPos++] = byte;
}
}
void reduceList()
{
if (currentLevel <= MAX_TREE_SIZE && nodes[currentLevel] > 0)
nodes[currentLevel]--;
}
void smlNewList(unsigned char size)
{
reduceList();
if (currentLevel < MAX_TREE_SIZE)
currentLevel++;
nodes[currentLevel] = size;
SML_TREELOG(currentLevel, "LISTSTART on level %i with %i nodes\n",
currentLevel, size);
setState(SML_LISTSTART, size);
// @todo workaround for lists inside obis lists
if (size > 5) {
listPos = 0;
memset(listBuffer, '\0', MAX_LIST_SIZE);
}
else {
pushListBuffer(size);
pushListBuffer(currentState);
}
}
void checkMagicByte(unsigned char &byte)
{
unsigned int size = 0;
while (currentLevel > 0 && nodes[currentLevel] == 0) {
/* go back in tree if no nodes remaining */
SML_TREELOG(currentLevel, "back to previous list\n");
currentLevel--;
}
if (byte > 0x70 && byte <= 0x7F) {
/* new list */
size = byte & 0x0F;
smlNewList(size);
}
else if (byte >= 0x01 && byte <= 0x6F && nodes[currentLevel] > 0) {
if (byte == 0x01) {
/* no data, get next */
SML_TREELOG(currentLevel, " Data %i (empty)\n", nodes[currentLevel]);
pushListBuffer(0);
pushListBuffer(currentState);
if (nodes[currentLevel] == 1) {
setState(SML_LISTEND, 1);
SML_TREELOG(currentLevel, "LISTEND\n");
}
else {
setState(SML_NEXT, 1);
}
}
else {
size = (byte & 0x0F) - 1;
setState(SML_DATA, size);
if ((byte & 0xF0) == 0x50) {
setState(SML_DATA_SIGNED_INT, size);
}
else if ((byte & 0xF0) == 0x60) {
setState(SML_DATA_UNSIGNED_INT, size);
}
else if ((byte & 0xF0) == 0x00) {
setState(SML_DATA_OCTET_STRING, size);
}
SML_TREELOG(currentLevel,
" Data %i (length = %i%s): ", nodes[currentLevel], size,
(currentState == SML_DATA_SIGNED_INT) ? ", signed int"
: (currentState == SML_DATA_UNSIGNED_INT) ? ", unsigned int"
: (currentState == SML_DATA_OCTET_STRING) ? ", octet string"
: "");
pushListBuffer(size);
pushListBuffer(currentState);
}
reduceList();
}
else if (byte == 0x00) {
/* end of block */
reduceList();
SML_TREELOG(currentLevel, "End of block at level %i\n", currentLevel);
if (currentLevel == 0) {
setState(SML_NEXT, 1);
}
else {
setState(SML_BLOCKEND, 1);
}
}
else if (byte & 0x80) {
// MSB bit is set, another TL byte will follow
if (byte >= 0x80 && byte <= 0x8F) {
// Datatype Octet String
setState(SML_HDATA, (byte & 0x0F) << 4);
}
else if (byte >= 0xF0 /*&& byte <= 0xFF*/) {
/* Datatype List of ...*/
setState(SML_LISTEXTENDED, (byte & 0x0F) << 4);
}
}
else if (byte == 0x1B && currentLevel == 0) {
/* end sequence */
setState(SML_END, 3);
}
else {
/* Unexpected Byte */
SML_TREELOG(currentLevel,
"UNEXPECTED magicbyte >%02X< at currentLevel %i\n", byte,
currentLevel);
setState(SML_UNEXPECTED, 4);
}
}
sml_states_t smlState(unsigned char &currentByte)
{
unsigned char size;
if (len > 0)
len--;
crc16(currentByte);
switch (currentState) {
case SML_UNEXPECTED:
case SML_CHECKSUM_ERROR:
case SML_FINAL:
case SML_START:
currentState = SML_START;
currentLevel = 0; // Reset current level at the begin of a new transmission
// to prevent problems
if (currentByte != 0x1b)
setState(SML_UNEXPECTED, 4);
if (len == 0) {
SML_TREELOG(0, "START\n");
/* completely clean any garbage from crc checksum */
crc = 0xFFFF;
currentByte = 0x1b;
crc16(currentByte);
crc16(currentByte);
crc16(currentByte);
crc16(currentByte);
setState(SML_VERSION, 4);
}
break;
case SML_VERSION:
if (currentByte != 0x01)
setState(SML_UNEXPECTED, 4);
if (len == 0) {
setState(SML_BLOCKSTART, 1);
}
break;
case SML_END:
if (currentByte != 0x1b) {
SML_LOG("UNEXPECTED char >%02X< at SML_END\n", currentByte);
setState(SML_UNEXPECTED, 4);
}
if (len == 0) {
setState(SML_CHECKSUM, 4);
}
break;
case SML_CHECKSUM:
// SML_LOG("CHECK: %02X\n", currentByte);
if (len == 2) {
crcMine = crc ^ 0xFFFF;
}
if (len == 1) {
crcReceived += currentByte;
}
if (len == 0) {
crcReceived = crcReceived | (currentByte << 8);
SML_LOG("Received checksum: %02X\n", crcReceived);
SML_LOG("Calculated checksum: %02X\n", crcMine);
if (crcMine == crcReceived) {
setState(SML_FINAL, 4);
}
else {
setState(SML_CHECKSUM_ERROR, 4);
}
crc = 0xFFFF;
crcReceived = 0x000; /* reset CRC */
}
break;
case SML_HDATA:
size = len + currentByte - 1;
setState(SML_DATA, size);
pushListBuffer(size);
pushListBuffer(currentState);
SML_TREELOG(currentLevel, " Data (length = %i): ", size);
break;
case SML_LISTEXTENDED:
size = len + (currentByte & 0x0F);
SML_TREELOG(currentLevel, "Extended List with Size=%i\n", size);
smlNewList(size);
break;
case SML_DATA:
case SML_DATA_SIGNED_INT:
case SML_DATA_UNSIGNED_INT:
case SML_DATA_OCTET_STRING:
SML_LOG("%02X ", currentByte);
pushListBuffer(currentByte);
if (nodes[currentLevel] == 0 && len == 0) {
SML_LOG("\n");
SML_TREELOG(currentLevel, "LISTEND on level %i\n", currentLevel);
currentState = SML_LISTEND;
}
else if (len == 0) {
currentState = SML_DATAEND;
SML_LOG("\n");
}
break;
case SML_DATAEND:
case SML_NEXT:
case SML_LISTSTART:
case SML_LISTEND:
case SML_BLOCKSTART:
case SML_BLOCKEND:
checkMagicByte(currentByte);
break;
}
return currentState;
}
bool smlOBISCheck(const unsigned char *obis)
{
return (memcmp(obis, &listBuffer[2], 6) == 0);
}
void smlOBISManufacturer(unsigned char *str, int maxSize)
{
int i = 0, pos = 0, size = 0;
while (i < listPos) {
size = (int)listBuffer[i];
i++;
pos++;
if (pos == 6) {
/* get manufacturer at position 6 in list */
size = (size > maxSize - 1) ? maxSize : size;
memcpy(str, &listBuffer[i + 1], size);
str[size + 1] = 0;
}
i += size + 1;
}
}
void smlPow(double &val, signed char &scaler)
{
if (scaler < 0) {
while (scaler++) {
val /= 10;
}
}
else {
while (scaler--) {
val *= 10;
}
}
}
void smlOBISByUnit(long long int &val, signed char &scaler, sml_units_t unit)
{
unsigned char i = 0, pos = 0, size = 0, y = 0, skip = 0;
sml_states_t type;
val = -1; /* unknown or error */
while (i < listPos) {
pos++;
size = (int)listBuffer[i++];
type = (sml_states_t)listBuffer[i++];
if (type == SML_LISTSTART && size > 0) {
// skip a list inside an obis list
skip = size;
while (skip > 0) {
size = (int)listBuffer[i++];
type = (sml_states_t)listBuffer[i++];
i += size;
skip--;
}
size = 0;
}
if (pos == 4 && listBuffer[i] != unit) {
/* return unknown (-1) if unit does not match */
return;
}
if (pos == 5) {
scaler = listBuffer[i];
}
if (pos == 6) {
y = size;
// initialize 64bit signed integer based on MSB from received value
val =
(type == SML_DATA_SIGNED_INT && (listBuffer[i] & (1 << 7))) ? ~0 : 0;
for (y = 0; y < size; y++) {
// left shift received bytes to 64 bit signed integer
val = (val << 8) | listBuffer[i + y];
}
}
i += size;
}
}
void smlOBISWh(double &wh)
{
long long int val;
smlOBISByUnit(val, sc, SML_WATT_HOUR);
wh = val;
smlPow(wh, sc);
}
void smlOBISW(double &w)
{
long long int val;
smlOBISByUnit(val, sc, SML_WATT);
w = val;
smlPow(w, sc);
}
void smlOBISVolt(double &v)
{
long long int val;
smlOBISByUnit(val, sc, SML_VOLT);
v = val;
smlPow(v, sc);
}
void smlOBISAmpere(double &a)
{
long long int val;
smlOBISByUnit(val, sc, SML_AMPERE);
a = val;
smlPow(a, sc);
}

106
lib/SMLParser/sml.h Normal file
View File

@ -0,0 +1,106 @@
#ifndef SML_H
#define SML_H
#include <stdbool.h>
typedef enum {
SML_START,
SML_END,
SML_VERSION,
SML_NEXT,
SML_LISTSTART,
SML_LISTEND,
SML_LISTEXTENDED,
SML_DATA,
SML_HDATA,
SML_DATAEND,
SML_BLOCKSTART,
SML_BLOCKEND,
SML_CHECKSUM,
SML_CHECKSUM_ERROR, /* calculated checksum does not match */
SML_UNEXPECTED, /* unexpected byte received */
SML_FINAL, /* final state, checksum OK */
SML_DATA_SIGNED_INT,
SML_DATA_UNSIGNED_INT,
SML_DATA_OCTET_STRING,
} sml_states_t;
typedef enum {
SML_YEAR = 1,
SML_MONTH = 2,
SML_WEEK = 3,
SML_DAY = 4,
SML_HOUR = 5,
SML_MIN = 6,
SML_SECOND = 7,
SML_DEGREE = 8,
SML_DEGREE_CELSIUS = 9,
SML_CURRENCY = 10,
SML_METRE = 11,
SML_METRE_PER_SECOND = 12,
SML_CUBIC_METRE = 13,
SML_CUBIC_METRE_CORRECTED = 14,
SML_CUBIC_METRE_PER_HOUR = 15,
SML_CUBIC_METRE_PER_HOUR_CORRECTED = 16,
SML_CUBIC_METRE_PER_DAY = 17,
SML_CUBIC_METRE_PER_DAY_CORRECTED = 18,
SML_LITRE = 19,
SML_KILOGRAM = 20,
SML_NEWTON = 21,
SML_NEWTONMETER = 22,
SML_PASCAL = 23,
SML_BAR = 24,
SML_JOULE = 25,
SML_JOULE_PER_HOUR = 26,
SML_WATT = 27,
SML_VOLT_AMPERE = 28,
SML_VAR = 29,
SML_WATT_HOUR = 30,
SML_VOLT_AMPERE_HOUR = 31,
SML_VAR_HOUR = 32,
SML_AMPERE = 33,
SML_COULOMB = 34,
SML_VOLT = 35,
SML_VOLT_PER_METRE = 36,
SML_FARAD = 37,
SML_OHM = 38,
SML_OHM_METRE = 39,
SML_WEBER = 40,
SML_TESLA = 41,
SML_AMPERE_PER_METRE = 42,
SML_HENRY = 43,
SML_HERTZ = 44,
SML_ACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE = 45,
SML_REACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE = 46,
SML_APPARENT_ENERGY_METER_CONSTANT_OR_PULSE_VALUE = 47,
SML_VOLT_SQUARED_HOURS = 48,
SML_AMPERE_SQUARED_HOURS = 49,
SML_KILOGRAM_PER_SECOND = 50,
SML_KELVIN = 52,
SML_VOLT_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE = 53,
SML_AMPERE_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE = 54,
SML_METER_CONSTANT_OR_PULSE_VALUE = 55,
SML_PERCENTAGE = 56,
SML_AMPERE_HOUR = 57,
SML_ENERGY_PER_VOLUME = 60,
SML_CALORIFIC_VALUE = 61,
SML_MOLE_PERCENT = 62,
SML_MASS_DENSITY = 63,
SML_PASCAL_SECOND = 64,
SML_RESERVED = 253,
SML_OTHER_UNIT = 254,
SML_COUNT = 255
} sml_units_t;
sml_states_t smlState(unsigned char &byte);
bool smlOBISCheck(const unsigned char *obis);
void smlOBISManufacturer(unsigned char *str, int maxSize);
void smlOBISByUnit(long long int &wh, signed char &scaler, sml_units_t unit);
// Be aware that double on Arduino UNO is just 32 bit
void smlOBISWh(double &wh);
void smlOBISW(double &w);
void smlOBISVolt(double &v);
void smlOBISAmpere(double &a);
#endif

View File

@ -0,0 +1,42 @@
#ifndef SML_CRC_TABLE_H
#define SML_CRC_TABLE_H
#include <stdint.h>
#ifdef ARDUINO
#include <Arduino.h>
static const uint16_t smlCrcTable[256] PROGMEM =
#else
static const uint16_t smlCrcTable[256] =
#endif
{0x0000, 0x1189, 0x2312, 0x329B, 0x4624, 0x57AD, 0x6536, 0x74BF, 0x8C48,
0x9DC1, 0xAF5A, 0xBED3, 0xCA6C, 0xDBE5, 0xE97E, 0xF8F7, 0x1081, 0x0108,
0x3393, 0x221A, 0x56A5, 0x472C, 0x75B7, 0x643E, 0x9CC9, 0x8D40, 0xBFDB,
0xAE52, 0xDAED, 0xCB64, 0xF9FF, 0xE876, 0x2102, 0x308B, 0x0210, 0x1399,
0x6726, 0x76AF, 0x4434, 0x55BD, 0xAD4A, 0xBCC3, 0x8E58, 0x9FD1, 0xEB6E,
0xFAE7, 0xC87C, 0xD9F5, 0x3183, 0x200A, 0x1291, 0x0318, 0x77A7, 0x662E,
0x54B5, 0x453C, 0xBDCB, 0xAC42, 0x9ED9, 0x8F50, 0xFBEF, 0xEA66, 0xD8FD,
0xC974, 0x4204, 0x538D, 0x6116, 0x709F, 0x0420, 0x15A9, 0x2732, 0x36BB,
0xCE4C, 0xDFC5, 0xED5E, 0xFCD7, 0x8868, 0x99E1, 0xAB7A, 0xBAF3, 0x5285,
0x430C, 0x7197, 0x601E, 0x14A1, 0x0528, 0x37B3, 0x263A, 0xDECD, 0xCF44,
0xFDDF, 0xEC56, 0x98E9, 0x8960, 0xBBFB, 0xAA72, 0x6306, 0x728F, 0x4014,
0x519D, 0x2522, 0x34AB, 0x0630, 0x17B9, 0xEF4E, 0xFEC7, 0xCC5C, 0xDDD5,
0xA96A, 0xB8E3, 0x8A78, 0x9BF1, 0x7387, 0x620E, 0x5095, 0x411C, 0x35A3,
0x242A, 0x16B1, 0x0738, 0xFFCF, 0xEE46, 0xDCDD, 0xCD54, 0xB9EB, 0xA862,
0x9AF9, 0x8B70, 0x8408, 0x9581, 0xA71A, 0xB693, 0xC22C, 0xD3A5, 0xE13E,
0xF0B7, 0x0840, 0x19C9, 0x2B52, 0x3ADB, 0x4E64, 0x5FED, 0x6D76, 0x7CFF,
0x9489, 0x8500, 0xB79B, 0xA612, 0xD2AD, 0xC324, 0xF1BF, 0xE036, 0x18C1,
0x0948, 0x3BD3, 0x2A5A, 0x5EE5, 0x4F6C, 0x7DF7, 0x6C7E, 0xA50A, 0xB483,
0x8618, 0x9791, 0xE32E, 0xF2A7, 0xC03C, 0xD1B5, 0x2942, 0x38CB, 0x0A50,
0x1BD9, 0x6F66, 0x7EEF, 0x4C74, 0x5DFD, 0xB58B, 0xA402, 0x9699, 0x8710,
0xF3AF, 0xE226, 0xD0BD, 0xC134, 0x39C3, 0x284A, 0x1AD1, 0x0B58, 0x7FE7,
0x6E6E, 0x5CF5, 0x4D7C, 0xC60C, 0xD785, 0xE51E, 0xF497, 0x8028, 0x91A1,
0xA33A, 0xB2B3, 0x4A44, 0x5BCD, 0x6956, 0x78DF, 0x0C60, 0x1DE9, 0x2F72,
0x3EFB, 0xD68D, 0xC704, 0xF59F, 0xE416, 0x90A9, 0x8120, 0xB3BB, 0xA232,
0x5AC5, 0x4B4C, 0x79D7, 0x685E, 0x1CE1, 0x0D68, 0x3FF3, 0x2E7A, 0xE70E,
0xF687, 0xC41C, 0xD595, 0xA12A, 0xB0A3, 0x8238, 0x93B1, 0x6B46, 0x7ACF,
0x4854, 0x59DD, 0x2D62, 0x3CEB, 0x0E70, 0x1FF9, 0xF78F, 0xE606, 0xD49D,
0xC514, 0xB1AB, 0xA022, 0x92B9, 0x8330, 0x7BC7, 0x6A4E, 0x58D5, 0x495C,
0x3DE3, 0x2C6A, 0x1EF1, 0x0F78};
#endif

View File

@ -27,12 +27,13 @@ build_unflags =
lib_deps =
https://github.com/yubox-node-org/ESPAsyncWebServer
bblanchon/ArduinoJson @ ^6.21.0
bblanchon/ArduinoJson @ ^6.21.1
https://github.com/bertmelis/espMqttClient.git#v1.4.2
nrf24/RF24 @ ^1.4.5
olikraus/U8g2 @ ^2.34.16
olikraus/U8g2 @ ^2.34.17
buelowp/sunset @ ^1.1.7
https://github.com/coryjfowler/MCP_CAN_lib
plerup/EspSoftwareSerial@^8.0.1
mobizt/FirebaseJson @ ^3.0.6
extra_scripts =

View File

@ -0,0 +1,83 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler, Malte Schmidt and others
*/
#include "MessageOutput.h"
#include "MqttSettings.h"
#include "MqttHandlePowerLimiter.h"
#include "PowerLimiter.h"
#include <ctime>
#define TOPIC_SUB_POWER_LIMITER "disable"
MqttHandlePowerLimiterClass MqttHandlePowerLimiter;
void MqttHandlePowerLimiterClass::init()
{
using std::placeholders::_1;
using std::placeholders::_2;
using std::placeholders::_3;
using std::placeholders::_4;
using std::placeholders::_5;
using std::placeholders::_6;
String topic = MqttSettings.getPrefix();
MqttSettings.subscribe(String(topic + "powerlimiter/cmd/" + TOPIC_SUB_POWER_LIMITER).c_str(), 0, std::bind(&MqttHandlePowerLimiterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
_lastPublish = millis();
}
void MqttHandlePowerLimiterClass::loop()
{
if (!MqttSettings.getConnected() ) {
return;
}
const CONFIG_T& config = Configuration.get();
if ((millis() - _lastPublish) > (config.Mqtt_PublishInterval * 1000) ) {
MqttSettings.publish("powerlimiter/status/disabled", String(PowerLimiter.getDisable()));
yield();
_lastPublish = millis();
}
}
void MqttHandlePowerLimiterClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)
{
const CONFIG_T& config = Configuration.get();
char token_topic[MQTT_MAX_TOPIC_STRLEN + 40]; // respect all subtopics
strncpy(token_topic, topic, MQTT_MAX_TOPIC_STRLEN + 40); // convert const char* to char*
char* setting;
char* rest = &token_topic[strlen(config.Mqtt_Topic)];
strtok_r(rest, "/", &rest); // Remove "powerlimiter"
strtok_r(rest, "/", &rest); // Remove "cmd"
setting = strtok_r(rest, "/", &rest);
if (setting == NULL) {
return;
}
char* strlimit = new char[len + 1];
memcpy(strlimit, payload, len);
strlimit[len] = '\0';
float payload_val = strtof(strlimit, NULL);
delete[] strlimit;
if (!strcmp(setting, TOPIC_SUB_POWER_LIMITER)) {
MessageOutput.printf("Disable power limter: %f A\r\n", payload_val);
if(payload_val == 1) {
PowerLimiter.setDisable(true);
}
if(payload_val == 0) {
PowerLimiter.setDisable(false);
}
}
}

View File

@ -24,11 +24,13 @@ void PowerLimiterClass::loop()
CONFIG_T& config = Configuration.get();
if (!config.PowerLimiter_Enabled
|| _disabled
|| !config.PowerMeter_Enabled
|| !Hoymiles.getRadio()->isIdle()
|| (millis() - _lastCommandSent) < (config.PowerLimiter_Interval * 1000)
|| (millis() - _lastLoop) < (config.PowerLimiter_Interval * 1000)) {
if (!config.PowerLimiter_Enabled)
if (!config.PowerLimiter_Enabled
|| _disabled)
_plState = STATE_DISCOVER; // ensure STATE_DISCOVER is set, if PowerLimiter will be enabled.
return;
}
@ -150,6 +152,14 @@ int32_t PowerLimiterClass::getLastRequestedPowewrLimit() {
return _lastRequestedPowerLimit;
}
bool PowerLimiterClass::getDisable() {
return _disabled;
}
void PowerLimiterClass::setDisable(bool disable) {
_disabled = disable;
}
bool PowerLimiterClass::canUseDirectSolarPower()
{
CONFIG_T& config = Configuration.get();

View File

@ -10,11 +10,14 @@
#include "SDM.h"
#include "MessageOutput.h"
#include <ctime>
#include <SoftwareSerial.h>
PowerMeterClass PowerMeter;
SDM sdm(Serial2, 9600, NOT_A_PIN, SERIAL_8N1, SDM_RX_PIN, SDM_TX_PIN);
SoftwareSerial inputSerial;
void PowerMeterClass::init()
{
using std::placeholders::_1;
@ -29,7 +32,11 @@ void PowerMeterClass::init()
CONFIG_T& config = Configuration.get();
if (config.PowerMeter_Enabled && config.PowerMeter_Source == 0) {
if (!config.PowerMeter_Enabled) {
return;
}
if (config.PowerMeter_Source == SOURCE_MQTT) {
if (strlen(config.PowerMeter_MqttTopicPowerMeter1) > 0) {
MqttSettings.subscribe(config.PowerMeter_MqttTopicPowerMeter1, 0, std::bind(&PowerMeterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
}
@ -43,16 +50,30 @@ void PowerMeterClass::init()
}
}
mqttInitDone = true;
if(config.PowerMeter_Source == SOURCE_SDM1PH || config.PowerMeter_Source == SOURCE_SDM3PH) {
sdm.begin();
}
sdm.begin();
HttpPowerMeter.init();
if (config.PowerMeter_Source == SOURCE_HTTP) {
HttpPowerMeter.init();
}
if (config.PowerMeter_Source == SOURCE_SML) {
pinMode(SML_RX_PIN, INPUT);
inputSerial.begin(9600, SWSERIAL_8N1, SML_RX_PIN, -1, false, 128, 95);
inputSerial.enableRx(true);
inputSerial.enableTx(false);
inputSerial.flush();
}
mqttInitDone = true;
}
void PowerMeterClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)
{
CONFIG_T& config = Configuration.get();
if (config.PowerMeter_Enabled && config.PowerMeter_Source != SOURCE_MQTT) {
if (!config.PowerMeter_Enabled || config.PowerMeter_Source != SOURCE_MQTT) {
return;
}
@ -96,8 +117,8 @@ void PowerMeterClass::mqtt()
MqttSettings.publish(topic + "/voltage1", String(_powerMeter1Voltage));
MqttSettings.publish(topic + "/voltage2", String(_powerMeter2Voltage));
MqttSettings.publish(topic + "/voltage3", String(_powerMeter3Voltage));
MqttSettings.publish(topic + "/import", String(_PowerMeterImport));
MqttSettings.publish(topic + "/export", String(_PowerMeterExport));
MqttSettings.publish(topic + "/import", String(_powerMeterImport));
MqttSettings.publish(topic + "/export", String(_powerMeterExport));
}
}
@ -105,6 +126,12 @@ void PowerMeterClass::loop()
{
CONFIG_T& config = Configuration.get();
if (config.PowerMeter_Enabled && config.PowerMeter_Source == SOURCE_SML) {
if (!smlReadLoop()) {
return;
}
}
if (!config.PowerMeter_Enabled
|| (millis() - _lastPowerMeterCheck) < (config.PowerMeter_Interval * 1000)) {
return;
@ -112,15 +139,15 @@ void PowerMeterClass::loop()
uint8_t _address = config.PowerMeter_SdmAddress;
if (config.PowerMeter_Source== SOURCE_SDM1PH) {
if (config.PowerMeter_Source == SOURCE_SDM1PH) {
_powerMeter1Power = static_cast<float>(sdm.readVal(SDM_PHASE_1_POWER, _address));
_powerMeter2Power = 0.0;
_powerMeter3Power = 0.0;
_powerMeter1Voltage = static_cast<float>(sdm.readVal(SDM_PHASE_1_VOLTAGE, _address));
_powerMeter2Voltage = 0.0;
_powerMeter3Voltage = 0.0;
_PowerMeterImport = static_cast<float>(sdm.readVal(SDM_IMPORT_ACTIVE_ENERGY, _address));
_PowerMeterExport = static_cast<float>(sdm.readVal(SDM_EXPORT_ACTIVE_ENERGY, _address));
_powerMeterImport = static_cast<float>(sdm.readVal(SDM_IMPORT_ACTIVE_ENERGY, _address));
_powerMeterExport = static_cast<float>(sdm.readVal(SDM_EXPORT_ACTIVE_ENERGY, _address));
_lastPowerMeterUpdate = millis();
}
else if (config.PowerMeter_Source == SOURCE_SDM3PH) {
@ -130,8 +157,8 @@ void PowerMeterClass::loop()
_powerMeter1Voltage = static_cast<float>(sdm.readVal(SDM_PHASE_1_VOLTAGE, _address));
_powerMeter2Voltage = static_cast<float>(sdm.readVal(SDM_PHASE_2_VOLTAGE, _address));
_powerMeter3Voltage = static_cast<float>(sdm.readVal(SDM_PHASE_3_VOLTAGE, _address));
_PowerMeterImport = static_cast<float>(sdm.readVal(SDM_IMPORT_ACTIVE_ENERGY, _address));
_PowerMeterExport = static_cast<float>(sdm.readVal(SDM_EXPORT_ACTIVE_ENERGY, _address));
_powerMeterImport = static_cast<float>(sdm.readVal(SDM_IMPORT_ACTIVE_ENERGY, _address));
_powerMeterExport = static_cast<float>(sdm.readVal(SDM_EXPORT_ACTIVE_ENERGY, _address));
_lastPowerMeterUpdate = millis();
}
else if (config.PowerMeter_Source == SOURCE_HTTP) {
@ -149,3 +176,24 @@ void PowerMeterClass::loop()
_lastPowerMeterCheck = millis();
}
bool PowerMeterClass::smlReadLoop()
{
while (inputSerial.available()) {
double readVal = 0;
unsigned char smlCurrentChar = inputSerial.read();
sml_states_t smlCurrentState = smlState(smlCurrentChar);
if (smlCurrentState == SML_LISTEND) {
for (auto& handler: smlHandlerList) {
if (smlOBISCheck(handler.OBIS)) {
handler.Fn(readVal);
*handler.Arg = readVal;
}
}
} else if (smlCurrentState == SML_FINAL) {
return true;
}
}
return false;
}

View File

@ -17,7 +17,7 @@
</tr>
<tr>
<th>{{ $t('firmwareinfo.FirmwareVersion') }}</th>
<td><a :href="'https://github.com/tbnobody/OpenDTU/commits/' + systemStatus.git_hash"
<td><a :href="versionInfoUrl"
target="_blank" v-tooltip :title="$t('firmwareinfo.FirmwareVersionHint')">
{{ systemStatus.git_hash }}
</a></td>
@ -72,6 +72,12 @@ export default defineComponent({
return timestampToString(value, true);
};
},
versionInfoUrl(): string {
if (this.systemStatus.git_is_hash) {
return 'https://github.com/tbnobody/OpenDTU/commits/' + this.systemStatus.git_hash;
}
return 'https://github.com/tbnobody/OpenDTU/releases/tag/' + this.systemStatus.git_hash;
}
},
});
</script>

View File

@ -470,6 +470,7 @@
"typeSDM1ph": "SDM 1 phase (SDM120/220/230)",
"typeSDM3ph": "SDM 3 phase (SDM72/630)",
"typeHTTP": "HTTP(S) + JSON",
"typeSML": "SML (OBIS 16.7.0)",
"MqttTopicPowerMeter1": "MQTT topic - Stromzähler #1",
"MqttTopicPowerMeter2": "MQTT topic - Stromzähler #2 (Optional)",
"MqttTopicPowerMeter3": "MQTT topic - Stromzähler #3 (Optional)",

View File

@ -470,6 +470,7 @@
"typeSDM1ph": "SDM 1 phase (SDM120/220/230)",
"typeSDM3ph": "SDM 3 phase (SDM72/630)",
"typeHTTP": "HTTP(s) + JSON",
"typeSML": "SML (OBIS 16.7.0)",
"MqttTopicPowerMeter1": "MQTT topic - Power meter #1",
"MqttTopicPowerMeter2": "MQTT topic - Power meter #2",
"MqttTopicPowerMeter3": "MQTT topic - Power meter #3",

View File

@ -9,6 +9,7 @@ export interface SystemStatus {
sdkversion: string;
config_version: string;
git_hash: string;
git_is_hash: boolean;
resetreason_0: string;
resetreason_1: string;
cfgsavecount: number;

View File

@ -205,10 +205,11 @@ export default defineComponent({
dataLoading: true,
powerMeterConfigList: {} as PowerMeterConfig,
powerMeterSourceList: [
{ key: 3, value: this.$t('powermeteradmin.typeHTTP') },
{ key: 0, value: this.$t('powermeteradmin.typeMQTT') },
{ key: 1, value: this.$t('powermeteradmin.typeSDM1ph') },
{ key: 2, value: this.$t('powermeteradmin.typeSDM3ph') },
{ key: 3, value: this.$t('powermeteradmin.typeHTTP') },
{ key: 4, value: this.$t('powermeteradmin.typeSML') },
],
alertMessage: "",
alertType: "info",

View File

@ -51,11 +51,13 @@ export default defineComponent({
},
getUpdateInfo() {
// If the left char is a "g" the value is the git hash (remove the "g")
this.systemDataList.git_hash = this.systemDataList.git_hash?.substring(0, 1) == 'g' ? this.systemDataList.git_hash?.substring(1) : this.systemDataList.git_hash;
this.systemDataList.git_is_hash = this.systemDataList.git_hash?.substring(0, 1) == 'g';
this.systemDataList.git_hash = this.systemDataList.git_is_hash ? this.systemDataList.git_hash?.substring(1) : this.systemDataList.git_hash;
// Handle format "v0.1-5-gabcdefh"
if (this.systemDataList.git_hash.lastIndexOf("-") >= 0) {
this.systemDataList.git_hash = this.systemDataList.git_hash.substring(this.systemDataList.git_hash.lastIndexOf("-") + 2)
this.systemDataList.git_is_hash = true;
}
const fetchUrl = "https://api.github.com/repos/tbnobody/OpenDTU/compare/"

Binary file not shown.