Merge remote-tracking branch 'tbnobody/OpenDTU/master'

This commit is contained in:
helgeerbe 2022-10-20 23:39:05 +02:00
commit a9336968c7
26 changed files with 225 additions and 164 deletions

View File

@ -231,3 +231,4 @@ A documentation of all available MQTT Topics can be found here: [MQTT Documentat
- [Ahoy](https://github.com/grindylow/ahoy) - [Ahoy](https://github.com/grindylow/ahoy)
- [DTU Simulator](https://github.com/Ziyatoe/DTUsimMI1x00-Hoymiles) - [DTU Simulator](https://github.com/Ziyatoe/DTUsimMI1x00-Hoymiles)
- [OpenDTU extended to talk to Victrons MPPT battery chargers (Ve.Direct)](https://github.com/helgeerbe/OpenDTU_VeDirect) - [OpenDTU extended to talk to Victrons MPPT battery chargers (Ve.Direct)](https://github.com/helgeerbe/OpenDTU_VeDirect)
- [BreakoutBoard - sample printed circuit board for OpenDTU and Ahoy](https://github.com/dokuhn/openDTU-BreakoutBoard)

View File

@ -39,7 +39,7 @@ serial will be replaced with the serial number of the inverter.
| [serial]/0/temperature | R | Temperature of inverter in degree celsius | Degree Celsius (°C) | | [serial]/0/temperature | R | Temperature of inverter in degree celsius | Degree Celsius (°C) |
| [serial]/0/voltage | R | AC voltage in volt | Volt (V) | | [serial]/0/voltage | R | AC voltage in volt | Volt (V) |
| [serial]/0/yieldday | R | Energy converted to AC per day in watt hours | Watt hours (Wh) | | [serial]/0/yieldday | R | Energy converted to AC per day in watt hours | Watt hours (Wh) |
| [serial]/0/yieldtotal | R | Energy converted to AC since reset watt hours | Watt hours (kWh) | | [serial]/0/yieldtotal | R | Energy converted to AC since reset watt hours | Kilo watt hours (kWh) |
### DC input channel topics ### DC input channel topics
@ -52,7 +52,7 @@ serial will be replaced with the serial number of the inverter.
| [serial]/[1-4]/power | R | DC power of specific input in watt | Watt (W) | | [serial]/[1-4]/power | R | DC power of specific input in watt | Watt (W) |
| [serial]/[1-4]/voltage | R | DC voltage of specific input in volt | Volt (V) | | [serial]/[1-4]/voltage | R | DC voltage of specific input in volt | Volt (V) |
| [serial]/[1-4]/yieldday | R | Energy converted to AC per day on specific input | Watt hours (Wh) | | [serial]/[1-4]/yieldday | R | Energy converted to AC per day on specific input | Watt hours (Wh) |
| [serial]/[1-4]/yieldtotal | R | Energy converted to AC since reset on specific input | Watt hours (kWh) | | [serial]/[1-4]/yieldtotal | R | Energy converted to AC since reset on specific input | Kilo watt hours (kWh) |
### Inverter limit specific topics ### Inverter limit specific topics

View File

@ -62,7 +62,6 @@ private:
void setStaticIp(); void setStaticIp();
void setupMode(); void setupMode();
void NetworkEvent(WiFiEvent_t event); void NetworkEvent(WiFiEvent_t event);
static uint32_t getChipId();
bool adminEnabled = true; bool adminEnabled = true;
bool forceDisconnection = false; bool forceDisconnection = false;
int adminTimeoutCounter = 0; int adminTimeoutCounter = 0;

9
include/Utils.h Normal file
View File

@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstdint>
class Utils {
public:
static uint32_t getChipId();
};

View File

@ -38,7 +38,7 @@ void CommandAbstract::setTargetAddress(uint64_t address)
convertSerialToPacketId(&_payload[1], address); convertSerialToPacketId(&_payload[1], address);
_targetAddress = address; _targetAddress = address;
} }
const uint64_t CommandAbstract::getTargetAddress() uint64_t CommandAbstract::getTargetAddress()
{ {
return _targetAddress; return _targetAddress;
} }
@ -49,7 +49,7 @@ void CommandAbstract::setRouterAddress(uint64_t address)
_routerAddress = address; _routerAddress = address;
} }
const uint64_t CommandAbstract::getRouterAddress() uint64_t CommandAbstract::getRouterAddress()
{ {
return _routerAddress; return _routerAddress;
} }

View File

@ -19,10 +19,10 @@ public:
uint8_t getDataSize(); uint8_t getDataSize();
void setTargetAddress(uint64_t address); void setTargetAddress(uint64_t address);
const uint64_t getTargetAddress(); uint64_t getTargetAddress();
void setRouterAddress(uint64_t address); void setRouterAddress(uint64_t address);
const uint64_t getRouterAddress(); uint64_t getRouterAddress();
void setTimeout(uint32_t timeout); void setTimeout(uint32_t timeout);
uint32_t getTimeout(); uint32_t getTimeout();

View File

@ -33,7 +33,7 @@ const byteAssign_t* HM_1CH::getByteAssignment()
return byteAssignment; return byteAssignment;
} }
const uint8_t HM_1CH::getAssignmentCount() uint8_t HM_1CH::getAssignmentCount()
{ {
return sizeof(byteAssignment) / sizeof(byteAssign_t); return sizeof(byteAssignment) / sizeof(byteAssign_t);
} }

View File

@ -8,28 +8,28 @@ public:
static bool isValidSerial(uint64_t serial); static bool isValidSerial(uint64_t serial);
String typeName(); String typeName();
const byteAssign_t* getByteAssignment(); const byteAssign_t* getByteAssignment();
const uint8_t getAssignmentCount(); uint8_t getAssignmentCount();
private: private:
const byteAssign_t byteAssignment[18] = { const byteAssign_t byteAssignment[18] = {
{ FLD_UDC, UNIT_V, CH1, 2, 2, 10 }, { FLD_UDC, UNIT_V, CH1, 2, 2, 10, false },
{ FLD_IDC, UNIT_A, CH1, 4, 2, 100 }, { FLD_IDC, UNIT_A, CH1, 4, 2, 100, false },
{ FLD_PDC, UNIT_W, CH1, 6, 2, 10 }, { FLD_PDC, UNIT_W, CH1, 6, 2, 10, false },
{ FLD_YD, UNIT_WH, CH1, 12, 2, 1 }, { FLD_YD, UNIT_WH, CH1, 12, 2, 1, false },
{ FLD_YT, UNIT_KWH, CH1, 8, 4, 1000 }, { FLD_YT, UNIT_KWH, CH1, 8, 4, 1000, false },
{ FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC }, { FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC, false },
{ FLD_UAC, UNIT_V, CH0, 14, 2, 10 }, { FLD_UAC, UNIT_V, CH0, 14, 2, 10, false },
{ FLD_IAC, UNIT_A, CH0, 22, 2, 100 }, { FLD_IAC, UNIT_A, CH0, 22, 2, 100, false },
{ FLD_PAC, UNIT_W, CH0, 18, 2, 10 }, { FLD_PAC, UNIT_W, CH0, 18, 2, 10, false },
{ FLD_PRA, UNIT_VA, CH0, 20, 2, 10 }, { FLD_PRA, UNIT_VA, CH0, 20, 2, 10, false },
{ FLD_F, UNIT_HZ, CH0, 16, 2, 100 }, { FLD_F, UNIT_HZ, CH0, 16, 2, 100, false },
{ FLD_PCT, UNIT_PCT, CH0, 24, 2, 10 }, { FLD_PCT, UNIT_PCT, CH0, 24, 2, 10, false },
{ FLD_T, UNIT_C, CH0, 26, 2, 10 }, { FLD_T, UNIT_C, CH0, 26, 2, 10, true },
{ FLD_EVT_LOG, UNIT_CNT, CH0, 28, 2, 1 }, { FLD_EVT_LOG, UNIT_CNT, CH0, 28, 2, 1, false },
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, { FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC, false },
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC, false },
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC, false },
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC, false }
}; };
}; };

View File

@ -33,7 +33,7 @@ const byteAssign_t* HM_2CH::getByteAssignment()
return byteAssignment; return byteAssignment;
} }
const uint8_t HM_2CH::getAssignmentCount() uint8_t HM_2CH::getAssignmentCount()
{ {
return sizeof(byteAssignment) / sizeof(byteAssign_t); return sizeof(byteAssignment) / sizeof(byteAssign_t);
} }

View File

@ -8,35 +8,35 @@ public:
static bool isValidSerial(uint64_t serial); static bool isValidSerial(uint64_t serial);
String typeName(); String typeName();
const byteAssign_t* getByteAssignment(); const byteAssign_t* getByteAssignment();
const uint8_t getAssignmentCount(); uint8_t getAssignmentCount();
private: private:
const byteAssign_t byteAssignment[24] = { const byteAssign_t byteAssignment[24] = {
{ FLD_UDC, UNIT_V, CH1, 2, 2, 10 }, { FLD_UDC, UNIT_V, CH1, 2, 2, 10, false },
{ FLD_IDC, UNIT_A, CH1, 4, 2, 100 }, { FLD_IDC, UNIT_A, CH1, 4, 2, 100, false },
{ FLD_PDC, UNIT_W, CH1, 6, 2, 10 }, { FLD_PDC, UNIT_W, CH1, 6, 2, 10, false },
{ FLD_YD, UNIT_WH, CH1, 22, 2, 1 }, { FLD_YD, UNIT_WH, CH1, 22, 2, 1, false },
{ FLD_YT, UNIT_KWH, CH1, 14, 4, 1000 }, { FLD_YT, UNIT_KWH, CH1, 14, 4, 1000, false },
{ FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC }, { FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC, false },
{ FLD_UDC, UNIT_V, CH2, 8, 2, 10 }, { FLD_UDC, UNIT_V, CH2, 8, 2, 10, false },
{ FLD_IDC, UNIT_A, CH2, 10, 2, 100 }, { FLD_IDC, UNIT_A, CH2, 10, 2, 100, false },
{ FLD_PDC, UNIT_W, CH2, 12, 2, 10 }, { FLD_PDC, UNIT_W, CH2, 12, 2, 10, false },
{ FLD_YD, UNIT_WH, CH2, 24, 2, 1 }, { FLD_YD, UNIT_WH, CH2, 24, 2, 1, false },
{ FLD_YT, UNIT_KWH, CH2, 18, 4, 1000 }, { FLD_YT, UNIT_KWH, CH2, 18, 4, 1000, false },
{ FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC }, { FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC, false },
{ FLD_UAC, UNIT_V, CH0, 26, 2, 10 }, { FLD_UAC, UNIT_V, CH0, 26, 2, 10, false },
{ FLD_IAC, UNIT_A, CH0, 34, 2, 100 }, { FLD_IAC, UNIT_A, CH0, 34, 2, 100, false },
{ FLD_PAC, UNIT_W, CH0, 30, 2, 10 }, { FLD_PAC, UNIT_W, CH0, 30, 2, 10, false },
{ FLD_PRA, UNIT_VA, CH0, 32, 2, 10 }, { FLD_PRA, UNIT_VA, CH0, 32, 2, 10, false },
{ FLD_F, UNIT_HZ, CH0, 28, 2, 100 }, { FLD_F, UNIT_HZ, CH0, 28, 2, 100, false },
{ FLD_PCT, UNIT_PCT, CH0, 36, 2, 10 }, { FLD_PCT, UNIT_PCT, CH0, 36, 2, 10, false },
{ FLD_T, UNIT_C, CH0, 38, 2, 10 }, { FLD_T, UNIT_C, CH0, 38, 2, 10, true },
{ FLD_EVT_LOG, UNIT_CNT, CH0, 40, 2, 1 }, { FLD_EVT_LOG, UNIT_CNT, CH0, 40, 2, 1, false },
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, { FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC, false },
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC, false },
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC, false },
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC, false }
}; };
}; };

View File

@ -33,7 +33,7 @@ const byteAssign_t* HM_4CH::getByteAssignment()
return byteAssignment; return byteAssignment;
} }
const uint8_t HM_4CH::getAssignmentCount() uint8_t HM_4CH::getAssignmentCount()
{ {
return sizeof(byteAssignment) / sizeof(byteAssign_t); return sizeof(byteAssignment) / sizeof(byteAssign_t);
} }

View File

@ -8,49 +8,49 @@ public:
static bool isValidSerial(uint64_t serial); static bool isValidSerial(uint64_t serial);
String typeName(); String typeName();
const byteAssign_t* getByteAssignment(); const byteAssign_t* getByteAssignment();
const uint8_t getAssignmentCount(); uint8_t getAssignmentCount();
private: private:
const byteAssign_t byteAssignment[36] = { const byteAssign_t byteAssignment[36] = {
{ FLD_UDC, UNIT_V, CH1, 2, 2, 10 }, { FLD_UDC, UNIT_V, CH1, 2, 2, 10, false },
{ FLD_IDC, UNIT_A, CH1, 4, 2, 100 }, { FLD_IDC, UNIT_A, CH1, 4, 2, 100, false },
{ FLD_PDC, UNIT_W, CH1, 8, 2, 10 }, { FLD_PDC, UNIT_W, CH1, 8, 2, 10, false },
{ FLD_YD, UNIT_WH, CH1, 20, 2, 1 }, { FLD_YD, UNIT_WH, CH1, 20, 2, 1, false },
{ FLD_YT, UNIT_KWH, CH1, 12, 4, 1000 }, { FLD_YT, UNIT_KWH, CH1, 12, 4, 1000, false },
{ FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC }, { FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC, false },
{ FLD_UDC, UNIT_V, CH2, CALC_UDC_CH, CH1, CMD_CALC }, { FLD_UDC, UNIT_V, CH2, CALC_UDC_CH, CH1, CMD_CALC, false },
{ FLD_IDC, UNIT_A, CH2, 6, 2, 100 }, { FLD_IDC, UNIT_A, CH2, 6, 2, 100, false },
{ FLD_PDC, UNIT_W, CH2, 10, 2, 10 }, { FLD_PDC, UNIT_W, CH2, 10, 2, 10, false },
{ FLD_YD, UNIT_WH, CH2, 22, 2, 1 }, { FLD_YD, UNIT_WH, CH2, 22, 2, 1, false },
{ FLD_YT, UNIT_KWH, CH2, 16, 4, 1000 }, { FLD_YT, UNIT_KWH, CH2, 16, 4, 1000, false },
{ FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC }, { FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC, false },
{ FLD_UDC, UNIT_V, CH3, 24, 2, 10 }, { FLD_UDC, UNIT_V, CH3, 24, 2, 10, false },
{ FLD_IDC, UNIT_A, CH3, 26, 2, 100 }, { FLD_IDC, UNIT_A, CH3, 26, 2, 100, false },
{ FLD_PDC, UNIT_W, CH3, 30, 2, 10 }, { FLD_PDC, UNIT_W, CH3, 30, 2, 10, false },
{ FLD_YD, UNIT_WH, CH3, 42, 2, 1 }, { FLD_YD, UNIT_WH, CH3, 42, 2, 1, false },
{ FLD_YT, UNIT_KWH, CH3, 34, 4, 1000 }, { FLD_YT, UNIT_KWH, CH3, 34, 4, 1000, false },
{ FLD_IRR, UNIT_PCT, CH3, CALC_IRR_CH, CH3, CMD_CALC }, { FLD_IRR, UNIT_PCT, CH3, CALC_IRR_CH, CH3, CMD_CALC, false },
{ FLD_UDC, UNIT_V, CH4, CALC_UDC_CH, CH3, CMD_CALC }, { FLD_UDC, UNIT_V, CH4, CALC_UDC_CH, CH3, CMD_CALC, false },
{ FLD_IDC, UNIT_A, CH4, 28, 2, 100 }, { FLD_IDC, UNIT_A, CH4, 28, 2, 100, false },
{ FLD_PDC, UNIT_W, CH4, 32, 2, 10 }, { FLD_PDC, UNIT_W, CH4, 32, 2, 10, false },
{ FLD_YD, UNIT_WH, CH4, 44, 2, 1 }, { FLD_YD, UNIT_WH, CH4, 44, 2, 1, false },
{ FLD_YT, UNIT_KWH, CH4, 38, 4, 1000 }, { FLD_YT, UNIT_KWH, CH4, 38, 4, 1000, false },
{ FLD_IRR, UNIT_PCT, CH4, CALC_IRR_CH, CH4, CMD_CALC }, { FLD_IRR, UNIT_PCT, CH4, CALC_IRR_CH, CH4, CMD_CALC, false },
{ FLD_UAC, UNIT_V, CH0, 46, 2, 10 }, { FLD_UAC, UNIT_V, CH0, 46, 2, 10, false },
{ FLD_IAC, UNIT_A, CH0, 54, 2, 100 }, { FLD_IAC, UNIT_A, CH0, 54, 2, 100, false },
{ FLD_PAC, UNIT_W, CH0, 50, 2, 10 }, { FLD_PAC, UNIT_W, CH0, 50, 2, 10, false },
{ FLD_PRA, UNIT_VA, CH0, 52, 2, 10 }, { FLD_PRA, UNIT_VA, CH0, 52, 2, 10, false },
{ FLD_F, UNIT_HZ, CH0, 48, 2, 100 }, { FLD_F, UNIT_HZ, CH0, 48, 2, 100, false },
{ FLD_PCT, UNIT_PCT, CH0, 56, 2, 10 }, { FLD_PCT, UNIT_PCT, CH0, 56, 2, 10, false },
{ FLD_T, UNIT_C, CH0, 58, 2, 10 }, { FLD_T, UNIT_C, CH0, 58, 2, 10, true },
{ FLD_EVT_LOG, UNIT_CNT, CH0, 60, 2, 1 }, { FLD_EVT_LOG, UNIT_CNT, CH0, 60, 2, 1, false },
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, { FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC, false },
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC, false },
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC, false },
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC, false }
}; };
}; };

View File

@ -37,7 +37,7 @@ public:
const char* name(); const char* name();
virtual String typeName() = 0; virtual String typeName() = 0;
virtual const byteAssign_t* getByteAssignment() = 0; virtual const byteAssign_t* getByteAssignment() = 0;
virtual const uint8_t getAssignmentCount() = 0; virtual uint8_t getAssignmentCount() = 0;
bool isProducing(); bool isProducing();
bool isReachable(); bool isReachable();

View File

@ -1,21 +1,24 @@
#include "DevInfoParser.h" #include "DevInfoParser.h"
#include <cstring> #include <cstring>
#define ALL 0xff
typedef struct { typedef struct {
uint8_t hwPart[3]; uint8_t hwPart[4];
uint16_t maxPower; uint16_t maxPower;
const char* modelName; const char* modelName;
} devInfo_t; } devInfo_t;
const devInfo_t devInfo[] = { const devInfo_t devInfo[] = {
{ { 0x10, 0x10, 0x10 }, 300, "HM-300" }, { { 0x10, 0x10, 0x10, ALL }, 300, "HM-300" },
{ { 0x10, 0x10, 0x20 }, 350, "HM-350" }, { { 0x10, 0x10, 0x20, ALL }, 350, "HM-350" },
{ { 0x10, 0x10, 0x40 }, 400, "HM-400" }, { { 0x10, 0x10, 0x40, ALL }, 400, "HM-400" },
{ { 0x10, 0x11, 0x10 }, 600, "HM-600" }, { { 0x10, 0x11, 0x10, ALL }, 600, "HM-600" },
{ { 0x10, 0x11, 0x20 }, 700, "HM-700" }, { { 0x10, 0x11, 0x20, ALL }, 700, "HM-700" },
{ { 0x10, 0x11, 0x40 }, 800, "HM-800" }, { { 0x10, 0x11, 0x40, ALL }, 800, "HM-800" },
{ { 0x10, 0x12, 0x10 }, 1200, "HM-1200" }, { { 0x10, 0x12, 0x10, ALL }, 1200, "HM-1200" },
{ { 0x10, 0x12, 0x30 }, 1500, "HM-1500" } { { 0x10, 0x12, 0x30, ALL }, 1500, "HM-1500" },
{ { 0x10, 0x10, 0x10, 0x15 }, static_cast<uint16_t>(300 * 0.7), "HM-300" }, // HM-300 factory limitted to 70%
}; };
void DevInfoParser::clearBufferAll() void DevInfoParser::clearBufferAll()
@ -79,7 +82,7 @@ uint16_t DevInfoParser::getFwBuildVersion()
time_t DevInfoParser::getFwBuildDateTime() time_t DevInfoParser::getFwBuildDateTime()
{ {
struct tm timeinfo = { 0 }; struct tm timeinfo = { };
timeinfo.tm_year = ((((uint16_t)_payloadDevInfoAll[2]) << 8) | _payloadDevInfoAll[3]) - 1900; timeinfo.tm_year = ((((uint16_t)_payloadDevInfoAll[2]) << 8) | _payloadDevInfoAll[3]) - 1900;
timeinfo.tm_mon = ((((uint16_t)_payloadDevInfoAll[4]) << 8) | _payloadDevInfoAll[5]) / 100 - 1; timeinfo.tm_mon = ((((uint16_t)_payloadDevInfoAll[4]) << 8) | _payloadDevInfoAll[5]) / 100 - 1;
@ -135,6 +138,17 @@ String DevInfoParser::getHwModelName()
uint8_t DevInfoParser::getDevIdx() uint8_t DevInfoParser::getDevIdx()
{ {
uint8_t pos; uint8_t pos;
// Check for all 4 bytes first
for (pos = 0; pos < sizeof(devInfo) / sizeof(devInfo_t); pos++) {
if (devInfo[pos].hwPart[0] == _payloadDevInfoSimple[2]
&& devInfo[pos].hwPart[1] == _payloadDevInfoSimple[3]
&& devInfo[pos].hwPart[2] == _payloadDevInfoSimple[4]
&& devInfo[pos].hwPart[3] == _payloadDevInfoSimple[5]) {
return pos;
}
}
// Then only for 3 bytes
for (pos = 0; pos < sizeof(devInfo) / sizeof(devInfo_t); pos++) { for (pos = 0; pos < sizeof(devInfo) / sizeof(devInfo_t); pos++) {
if (devInfo[pos].hwPart[0] == _payloadDevInfoSimple[2] if (devInfo[pos].hwPart[0] == _payloadDevInfoSimple[2]
&& devInfo[pos].hwPart[1] == _payloadDevInfoSimple[3] && devInfo[pos].hwPart[1] == _payloadDevInfoSimple[3]

View File

@ -79,7 +79,17 @@ float StatisticsParser::getChannelFieldValue(uint8_t channel, uint8_t fieldId)
val |= _payloadStatistic[ptr]; val |= _payloadStatistic[ptr];
} while (++ptr != end); } while (++ptr != end);
return static_cast<float>(val) / static_cast<float>(div); float result;
if (b[pos].isSigned && b[pos].num == 2) {
result = static_cast<float>(static_cast<int16_t>(val));
} else if (b[pos].isSigned && b[pos].num == 4) {
result = static_cast<float>(static_cast<int32_t>(val));
} else {
result = static_cast<float>(val);
}
result /= static_cast<float>(div);
return result;
} else { } else {
// Value has to be calculated // Value has to be calculated
return calcFunctions[b[pos].start].func(this, b[pos].num); return calcFunctions[b[pos].start].func(this, b[pos].num);

View File

@ -68,6 +68,7 @@ typedef struct {
uint8_t start; // pos of first byte in buffer uint8_t start; // pos of first byte in buffer
uint8_t num; // number of bytes in buffer uint8_t num; // number of bytes in buffer
uint16_t div; // divisor / calc command uint16_t div; // divisor / calc command
bool isSigned; // allow negative numbers
} byteAssign_t; } byteAssign_t;
class StatisticsParser : public Parser { class StatisticsParser : public Parser {

View File

@ -18,7 +18,7 @@ platform = espressif32@>=5.2.0
build_flags = build_flags =
-D=${PIOENV} -D=${PIOENV}
-DCOMPONENT_EMBED_FILES=webapp_dist/index.html.gz:webapp_dist/zones.json.gz:webapp_dist/favicon.ico:webapp_dist/js/app.js.gz -DCOMPONENT_EMBED_FILES=webapp_dist/index.html.gz:webapp_dist/zones.json.gz:webapp_dist/favicon.ico:webapp_dist/js/app.js.gz
-Wall -Wall -Wextra -Werror
lib_deps = lib_deps =
https://github.com/yubox-node-org/ESPAsyncWebServer https://github.com/yubox-node-org/ESPAsyncWebServer
@ -35,16 +35,17 @@ monitor_filters = esp32_exception_decoder, time, log2file, colorize
monitor_speed = 115200 monitor_speed = 115200
upload_protocol = esptool upload_protocol = esptool
; Specify port here. Comment out (add ; in front of line) to use auto detection.
[env:generic]
board = esp32dev
monitor_port = COM4 monitor_port = COM4
upload_port = COM4 upload_port = COM4
[env:generic]
board = esp32dev
[env:olimex_esp32_poe] [env:olimex_esp32_poe]
; https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/open-source-hardware ; https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/open-source-hardware
board = esp32-poe board = esp32-poe
build_flags = ${env.build_flags} build_flags = ${env.build_flags}
-DHOYMILES_PIN_MISO=15 -DHOYMILES_PIN_MISO=15
@ -55,13 +56,9 @@ build_flags = ${env.build_flags}
-DHOYMILES_PIN_CS=5 -DHOYMILES_PIN_CS=5
-DOPENDTU_ETHERNET -DOPENDTU_ETHERNET
monitor_port = COM3
upload_port = COM3
[env:olimex_esp32_evb] [env:olimex_esp32_evb]
; https://www.olimex.com/Products/IoT/ESP32/ESP32-EVB/open-source-hardware ; https://www.olimex.com/Products/IoT/ESP32/ESP32-EVB/open-source-hardware
board = esp32-evb board = esp32-evb
build_flags = ${env.build_flags} build_flags = ${env.build_flags}
-DHOYMILES_PIN_MISO=15 -DHOYMILES_PIN_MISO=15
@ -72,9 +69,6 @@ build_flags = ${env.build_flags}
-DHOYMILES_PIN_CS=17 -DHOYMILES_PIN_CS=17
-DOPENDTU_ETHERNET -DOPENDTU_ETHERNET
monitor_port = /dev/tty.usbserial-1450
upload_port = /dev/tty.usbserial-1450
[env:d1 mini esp32] [env:d1 mini esp32]
board = wemos_d1_mini32 board = wemos_d1_mini32
@ -90,3 +84,15 @@ build_flags =
-DVICTRON_PIN_RX=22 -DVICTRON_PIN_RX=22
monitor_port = /dev/cu.usbserial-01E68DD0 monitor_port = /dev/cu.usbserial-01E68DD0
upload_port = /dev/cu.usbserial-01E68DD0 upload_port = /dev/cu.usbserial-01E68DD0
[env:wt32_eth01]
; http://www.wireless-tag.com/portfolio/wt32-eth01/
board = wt32-eth01
build_flags = ${env.build_flags}
-DHOYMILES_PIN_MISO=4
-DHOYMILES_PIN_MOSI=2
-DHOYMILES_PIN_SCLK=32
-DHOYMILES_PIN_IRQ=33
-DHOYMILES_PIN_CE=14
-DHOYMILES_PIN_CS=15
-DOPENDTU_ETHERNET

View File

@ -4,6 +4,7 @@
*/ */
#include "NetworkSettings.h" #include "NetworkSettings.h"
#include "Configuration.h" #include "Configuration.h"
#include "Utils.h"
#include "defaults.h" #include "defaults.h"
#include <WiFi.h> #include <WiFi.h>
#ifdef OPENDTU_ETHERNET #ifdef OPENDTU_ETHERNET
@ -142,7 +143,7 @@ void NetworkSettingsClass::enableAdminMode()
String NetworkSettingsClass::getApName() String NetworkSettingsClass::getApName()
{ {
return String(ACCESS_POINT_NAME + String(getChipId())); return String(ACCESS_POINT_NAME + String(Utils::getChipId()));
} }
void NetworkSettingsClass::loop() void NetworkSettingsClass::loop()
@ -385,7 +386,7 @@ String NetworkSettingsClass::getHostname()
char resultHostname[WIFI_MAX_HOSTNAME_STRLEN + 1]; char resultHostname[WIFI_MAX_HOSTNAME_STRLEN + 1];
uint8_t pos = 0; uint8_t pos = 0;
uint32_t chipId = getChipId(); uint32_t chipId = Utils::getChipId();
snprintf(preparedHostname, WIFI_MAX_HOSTNAME_STRLEN + 1, config.WiFi_Hostname, chipId); snprintf(preparedHostname, WIFI_MAX_HOSTNAME_STRLEN + 1, config.WiFi_Hostname, chipId);
const char* pC = preparedHostname; const char* pC = preparedHostname;
@ -431,13 +432,4 @@ network_mode NetworkSettingsClass::NetworkMode()
return _networkMode; return _networkMode;
} }
uint32_t NetworkSettingsClass::getChipId()
{
uint32_t chipId = 0;
for (int i = 0; i < 17; i += 8) {
chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
}
return chipId;
}
NetworkSettingsClass NetworkSettings; NetworkSettingsClass NetworkSettings;

11
src/Utils.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "Utils.h"
#include <Esp.h>
uint32_t Utils::getChipId()
{
uint32_t chipId = 0;
for (int i = 0; i < 17; i += 8) {
chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
}
return chipId;
}

View File

@ -42,6 +42,7 @@ void WebApiDevInfoClass::onDevInfoStatus(AsyncWebServerRequest* request)
devInfoObj[F("hw_part_number")] = inv->DevInfo()->getHwPartNumber(); devInfoObj[F("hw_part_number")] = inv->DevInfo()->getHwPartNumber();
devInfoObj[F("hw_version")] = inv->DevInfo()->getHwVersion(); devInfoObj[F("hw_version")] = inv->DevInfo()->getHwVersion();
devInfoObj[F("hw_model_name")] = inv->DevInfo()->getHwModelName(); devInfoObj[F("hw_model_name")] = inv->DevInfo()->getHwModelName();
devInfoObj[F("max_power")] = inv->DevInfo()->getMaxPower();
char timebuffer[32]; char timebuffer[32];
const time_t t = inv->DevInfo()->getFwBuildDateTime(); const time_t t = inv->DevInfo()->getFwBuildDateTime();

View File

@ -228,21 +228,21 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
return; return;
} }
if (root[F("hour")].as<uint>() < 0 || root[F("hour")].as<uint>() > 23) { if (root[F("hour")].as<uint>() > 23) {
retMsg[F("message")] = F("Hour must be a number between 0 and 23!"); retMsg[F("message")] = F("Hour must be a number between 0 and 23!");
response->setLength(); response->setLength();
request->send(response); request->send(response);
return; return;
} }
if (root[F("minute")].as<uint>() < 0 || root[F("minute")].as<uint>() > 59) { if (root[F("minute")].as<uint>() > 59) {
retMsg[F("message")] = F("Minute must be a number between 0 and 59!"); retMsg[F("message")] = F("Minute must be a number between 0 and 59!");
response->setLength(); response->setLength();
request->send(response); request->send(response);
return; return;
} }
if (root[F("second")].as<uint>() < 0 || root[F("second")].as<uint>() > 59) { if (root[F("second")].as<uint>() > 59) {
retMsg[F("message")] = F("Second must be a number between 0 and 59!"); retMsg[F("message")] = F("Second must be a number between 0 and 59!");
response->setLength(); response->setLength();
request->send(response); request->send(response);
@ -258,7 +258,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
local.tm_year = root[F("year")].as<uint>() - 1900; // years since 1900 local.tm_year = root[F("year")].as<uint>() - 1900; // years since 1900
time_t t = mktime(&local); time_t t = mktime(&local);
struct timeval now = { .tv_sec = t }; struct timeval now = { .tv_sec = t, .tv_usec = 0 };
settimeofday(&now, NULL); settimeofday(&now, NULL);
retMsg[F("type")] = F("success"); retMsg[F("type")] = F("success");

View File

@ -13,6 +13,10 @@
<a href="https://github.com/tbnobody/OpenDTU/issues" target="_blank">here</a>. <a href="https://github.com/tbnobody/OpenDTU/issues" target="_blank">here</a>.
</td> </td>
</tr> </tr>
<tr>
<td>Detected max. Power</td>
<td>{{ devInfoList.max_power }} W</td>
</tr>
<tr> <tr>
<td>Bootloader Version</td> <td>Bootloader Version</td>
<td>{{ formatVersion(devInfoList.fw_bootloader_version) }}</td> <td>{{ formatVersion(devInfoList.fw_bootloader_version) }}</td>

View File

@ -5,5 +5,6 @@ export interface DevInfoStatus {
fw_build_datetime: Date, fw_build_datetime: Date,
hw_part_number: number, hw_part_number: number,
hw_version: number, hw_version: number,
hw_model_name: string hw_model_name: string,
max_power: number,
} }

View File

@ -0,0 +1,5 @@
export interface LimitConfig {
serial: number,
limit_value: number,
limit_type: number
}

View File

@ -0,0 +1,5 @@
export interface LimitStatus {
limit_relative: number,
max_power: number,
limit_set_status: string,
}

View File

@ -181,12 +181,12 @@
<div class="col-sm-4"> <div class="col-sm-4">
<div class="input-group"> <div class="input-group">
<input type="number" class="form-control" id="inputCurrentLimit" <input type="number" class="form-control" id="inputCurrentLimit"
aria-describedby="currentLimitType" v-model="currentLimit" disabled /> aria-describedby="currentLimitType" v-model="currentLimitRelative" disabled />
<span class="input-group-text" id="currentLimitType">%</span> <span class="input-group-text" id="currentLimitType">%</span>
</div> </div>
</div> </div>
<div class="col-sm-4" v-if="maxPower > 0"> <div class="col-sm-4" v-if="currentLimitList.max_power > 0">
<div class="input-group"> <div class="input-group">
<input type="number" class="form-control" id="inputCurrentLimitAbsolute" <input type="number" class="form-control" id="inputCurrentLimitAbsolute"
aria-describedby="currentLimitTypeAbsolute" v-model="currentLimitAbsolute" aria-describedby="currentLimitTypeAbsolute" v-model="currentLimitAbsolute"
@ -201,12 +201,12 @@
Status:</label> Status:</label>
<div class="col-sm-9"> <div class="col-sm-9">
<span class="badge" :class="{ <span class="badge" :class="{
'bg-danger': successCommandLimit == 'Failure', 'bg-danger': currentLimitList.limit_set_status == 'Failure',
'bg-warning': successCommandLimit == 'Pending', 'bg-warning': currentLimitList.limit_set_status == 'Pending',
'bg-success': successCommandLimit == 'Ok', 'bg-success': currentLimitList.limit_set_status == 'Ok',
'bg-secondary': successCommandLimit == 'Unknown', 'bg-secondary': currentLimitList.limit_set_status == 'Unknown',
}"> }">
{{ successCommandLimit }} {{ currentLimitList.limit_set_status }}
</span> </span>
</div> </div>
</div> </div>
@ -217,7 +217,7 @@
<div class="input-group"> <div class="input-group">
<input type="number" name="inputTargetLimit" class="form-control" <input type="number" name="inputTargetLimit" class="form-control"
id="inputTargetLimit" :min="targetLimitMin" :max="targetLimitMax" id="inputTargetLimit" :min="targetLimitMin" :max="targetLimitMax"
v-model="targetLimit"> v-model="targetLimitList.limit_value">
<button class="btn btn-primary dropdown-toggle" type="button" <button class="btn btn-primary dropdown-toggle" type="button"
data-bs-toggle="dropdown" aria-expanded="false">{{ targetLimitTypeText data-bs-toggle="dropdown" aria-expanded="false">{{ targetLimitTypeText
}}</button> }}</button>
@ -334,6 +334,8 @@ import VedirectView from '@/views/VedirectView.vue';
import type { DevInfoStatus } from '@/types/DevInfoStatus'; import type { DevInfoStatus } from '@/types/DevInfoStatus';
import type { EventlogItems } from '@/types/EventlogStatus'; import type { EventlogItems } from '@/types/EventlogStatus';
import type { Inverters } from '@/types/LiveDataStatus'; import type { Inverters } from '@/types/LiveDataStatus';
import type { LimitStatus } from '@/types/LimitStatus';
import type { LimitConfig } from '@/types/LimitConfig';
export default defineComponent({ export default defineComponent({
components: { components: {
@ -370,14 +372,11 @@ export default defineComponent({
devInfoLoading: true, devInfoLoading: true,
limitSettingView: {} as bootstrap.Modal, limitSettingView: {} as bootstrap.Modal,
limitSettingSerial: 0,
limitSettingLoading: true, limitSettingLoading: true,
currentLimit: 0, currentLimitList: {} as LimitStatus,
currentLimitAbsolute: 0, targetLimitList: {} as LimitConfig,
successCommandLimit: "",
maxPower: 0,
targetLimit: 0,
targetLimitMin: 10, targetLimitMin: 10,
targetLimitMax: 100, targetLimitMax: 100,
targetLimitTypeText: "Relative (%)", targetLimitTypeText: "Relative (%)",
@ -428,6 +427,17 @@ export default defineComponent({
} }
} }
}, },
computed: {
currentLimitAbsolute(): number {
if (this.currentLimitList.max_power > 0) {
return Number((this.currentLimitList.limit_relative * this.currentLimitList.max_power / 100).toFixed(1));
}
return 0;
},
currentLimitRelative(): number {
return Number((this.currentLimitList.limit_relative).toFixed(1));
}
},
methods: { methods: {
getInitialData() { getInitialData() {
this.dataLoading = true; this.dataLoading = true;
@ -518,24 +528,20 @@ export default defineComponent({
this.devInfoView.show(); this.devInfoView.show();
}, },
onHideLimitSettings() { onHideLimitSettings() {
this.limitSettingSerial = 0;
this.targetLimit = 0;
this.targetLimitType = 1;
this.targetLimitTypeText = "Relative (%)";
this.showAlertLimit = false; this.showAlertLimit = false;
}, },
onShowLimitSettings(serial: number) { onShowLimitSettings(serial: number) {
this.targetLimitList.serial = 0;
this.targetLimitList.limit_value = 0;
this.targetLimitType = 1;
this.targetLimitTypeText = "Relative (%)";
this.limitSettingLoading = true; this.limitSettingLoading = true;
fetch("/api/limit/status") fetch("/api/limit/status")
.then((response) => response.json()) .then((response) => response.json())
.then((data) => { .then((data) => {
this.maxPower = data[serial].max_power; this.currentLimitList = data[serial];
this.currentLimit = Number((data[serial].limit_relative).toFixed(1)); this.targetLimitList.serial = serial;
if (this.maxPower > 0) {
this.currentLimitAbsolute = Number((this.currentLimit * this.maxPower / 100).toFixed(1));
}
this.successCommandLimit = data[serial].limit_set_status;
this.limitSettingSerial = serial;
this.limitSettingLoading = false; this.limitSettingLoading = false;
}); });
@ -544,15 +550,11 @@ export default defineComponent({
onSubmitLimit(e: Event) { onSubmitLimit(e: Event) {
e.preventDefault(); e.preventDefault();
const data = { this.targetLimitList.limit_type = (this.targetLimitPersistent ? 256 : 0) + this.targetLimitType
serial: this.limitSettingSerial,
limit_value: this.targetLimit,
limit_type: (this.targetLimitPersistent ? 256 : 0) + this.targetLimitType,
};
const formData = new FormData(); const formData = new FormData();
formData.append("data", JSON.stringify(data)); formData.append("data", JSON.stringify(this.targetLimitList));
console.log(data); console.log(this.targetLimitList);
fetch("/api/limit/config", { fetch("/api/limit/config", {
method: "POST", method: "POST",
@ -588,7 +590,7 @@ export default defineComponent({
} else { } else {
this.targetLimitTypeText = "Absolute (W)"; this.targetLimitTypeText = "Absolute (W)";
this.targetLimitMin = 10; this.targetLimitMin = 10;
this.targetLimitMax = 1500; this.targetLimitMax = (this.currentLimitList.max_power > 0 ? this.currentLimitList.max_power : 1500);
} }
this.targetLimitType = type; this.targetLimitType = type;
}, },