Browse Source

Merge branch 'main' into esp32-adjustments

pull/157/head
Andreas Schiffler 3 years ago
committed by GitHub
parent
commit
c38afda489
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 30
      tools/esp8266/User_Manual.md
  2. 107
      tools/esp8266/app.cpp
  3. 2
      tools/esp8266/app.h
  4. 10
      tools/esp8266/defines.h
  5. 25
      tools/esp8266/hmDefines.h
  6. 86
      tools/esp8266/hmInverter.h
  7. 18
      tools/esp8266/hmRadio.h
  8. 2
      tools/esp8266/html/h/setup_html.h
  9. 2
      tools/esp8266/html/setup.html
  10. 72
      tools/esp8266/web.cpp

30
tools/esp8266/User_Manual.md

@ -1,30 +1,30 @@
# User Manual Ahoy DTU (on ESP8266) # User Manual Ahoy DTU (on ESP8266)
15.08.2022 16.08.2022
## Introduction ## Introduction
see the repository [here](https://github.com/grindylow/ahoy/blob/main/tools/esp8266/README.md) See the repository [here](https://github.com/grindylow/ahoy/blob/main/tools/esp8266/README.md)
## Setup ## Setup
Assuming you have a running ahoy-dtu and you can access te setup page. Assuming you have a running ahoy-dtu and you can access the setup page.
In the initial case or after click "erease settings" the fields for the inverter setup are empty. In the initial case or after click "erase settings" the fields for the inverter setup are empty.
Set at least the serial number and a name for each inverter, check the "reboot after save" and click the "Save" button. Set at least the serial number and a name for each inverter, check the "reboot after save" and click the "Save" button.
## Active Power Limit via Setup Page ## Active Power Limit via Setup Page
I you leave the field "Active Power Limit" empty during the setup and reboot the ahoy-dtu a value of 65535 will be filled in. I you leave the field "Active Power Limit" empty during the setup and reboot the ahoy-dtu a value of 65535 will be filled in.
That is the value you have to fill in case you want to operate the inverter without a active power limit. That is the value you have to fill in case you want to operate the inverter without a active power limit.
If the value is 65535 or -1 after another reboot the value will be seted automatically to "100" and in the drop-down menu "relativ in percent persistent" will be seted. Of course you can do this also by your self. If the value is 65535 or -1 after another reboot the value will be set automatically to "100" and in the drop-down menu "relative in percent persistent" will be set. Of course you can do this also by your self.
You can change the setting in the following manner. You can change the setting in the following manner.
Decide if you want to set Decide if you want to set
- an absolut value in Watt - an absolute value in Watt
- an relativ value in percent based on the maximum Power cababilities of the inverter - an relative value in percent based on the maximum Power capabilities of the inverter
and if this settings shall be and if this settings shall be
- persistent - persistent
- not persistent - not persistent
after a power cylce of the inverter (P_DC=0 and P_AC=0 for at least 10 seconds) after a power cycle of the inverter (P_DC=0 and P_AC=0 for at least 10 seconds)
The user has to ensure sensfule settings. Remember that for the inverters of 3rd generation the relative active power limit is in the range of 2% up to 100%. The user has to ensure correct settings. Remember that for the inverters of 3rd generation the relative active power limit is in the range of 2% up to 100%.
Also an absolut active power limit below approx. 30Watt is not senseful because of the control capabilities and reactive power load. Also an absolute active power limit below approx. 30Watt is not correct because of the control capabilities and reactive power load.
## Active Power Limit via MQTT ## Active Power Limit via MQTT
The ahoy-dtu subscribes on the topic <CHOOSEN_TOPIC_FROM_SETUP>/devcontrol/# if the mqtt broker is set-up correctly. The default topic is inverter/devcontrol/#. The ahoy-dtu subscribes on the topic <CHOOSEN_TOPIC_FROM_SETUP>/devcontrol/# if the mqtt broker is set-up correctly. The default topic is inverter/devcontrol/#.
@ -58,7 +58,7 @@ The implementation allows to set any of the available <DevCntrlType> Commands:
Init = 0xff Init = 0xff
} DevControlCmdType; } DevControlCmdType;
``` ```
The MQTT payload will be seted on first to bytes and DATA2 will be seted on the second two bytes if the corresponding DevControlCmdType supports 4 byte data. The MQTT payload will be set on first to bytes and DATA2 will be set on the second two bytes if the corresponding DevControlCmdType supports 4 byte data.
So as example sending any payload on inverter/devcontrol/0/1 will switch off the inverter. So as example sending any payload on inverter/devcontrol/0/1 will switch off the inverter.
@ -107,21 +107,21 @@ Example to set the active power limit persistent to 600Watt
``` ```
### Developer Information REST API ### Developer Information REST API
In the same approach as for MQTT any other SubCmd can be applied and the respsine payload can be observed in the serial logs. Eg. request the Alarm Data. In the same approach as for MQTT any other SubCmd can be applied and the response payload can be observed in the serial logs. Eg. request the Alarm Data.
## Issues and Debuging for active power limit settings ## Issues and Debuging for active power limit settings
Turn on the serial debuging in the setup. Try to have find out if the behavior is deterministic. That means can you reproduce the behavior. Be patient and wait on inverter reactions at least some minutes and beware that the DC-Power is sufficient. Turn on the serial debugging in the setup. Try to have find out if the behavior is deterministic. That means can you reproduce the behavior. Be patient and wait on inverter reactions at least some minutes and beware that the DC-Power is sufficient.
In case of issues please report: In case of issues please report:
1. Version of firmware 1. Version of firmware
2. The output of the serial debug esp. the TX messages starting with "0x51" and the RX messages starting with "0xD1" or "0xF1" 2. The output of the serial debug esp. the TX messages starting with "0x51" and the RX messages starting with "0xD1" or "0xF1"
3. Which case you have tried: Setup-Page, MQTT, REST API and at what was shown on the "Visualization Page" at the Location "Limit" 3. Which case you have tried: Setup-Page, MQTT, REST API and at what was shown on the "Visualization Page" at the Location "Limit"
4. The setting means payload, relativ, absolut, persisten, not persisten (see tables above) 4. The setting means payload, relative, absolute, persistent, not persistent (see tables above)
**Developer Information General for Active Power Limit** **Developer Information General for Active Power Limit**
⚡To be verified by field tests and feedback ⚡To be verified by field tests and feedback
Internally this values will be seted for the second two bytes for MainCmd: 0x51 SubCmd: 0x0b --> DevControl set ActivePowerLimit Internally this values will be set for the second two bytes for MainCmd: 0x51 SubCmd: 0x0b --> DevControl set ActivePowerLimit
```C ```C
typedef enum { // ToDo: to be verified by field tests typedef enum { // ToDo: to be verified by field tests
AbsolutNonPersistent = 0x0000, // 0 AbsolutNonPersistent = 0x0000, // 0

107
tools/esp8266/app.cpp

@ -84,6 +84,29 @@ void app::loop(void) {
Inverter<> *iv = mSys->findInverter(&p->packet[1]); Inverter<> *iv = mSys->findInverter(&p->packet[1]);
if(NULL != iv && p->packet[0] == (TX_REQ_INFO + 0x80)) { // response from get information command if(NULL != iv && p->packet[0] == (TX_REQ_INFO + 0x80)) { // response from get information command
DPRINTLN(DBG_DEBUG, F("Response from info request received")); DPRINTLN(DBG_DEBUG, F("Response from info request received"));
uint8_t *pid = &p->packet[9];
if (*pid == 0x00)
{
DPRINT(DBG_DEBUG, "fragment number zero received and ignored");
}
else
{
if ((*pid & 0x7F) < 5)
{
memcpy(mPayload[iv->id].data[(*pid & 0x7F) - 1], &p->packet[10], len - 11);
mPayload[iv->id].len[(*pid & 0x7F) - 1] = len - 11;
}
if ((*pid & 0x80) == 0x80)
{ // Last packet
if ((*pid & 0x7f) > mPayload[iv->id].maxPackId)
{
mPayload[iv->id].maxPackId = (*pid & 0x7f);
if (*pid > 0x81)
mLastPacketId = *pid;
}
}
}
switch (mSys->InfoCmd){ switch (mSys->InfoCmd){
case InverterDevInform_Simple: case InverterDevInform_Simple:
{ {
@ -94,7 +117,6 @@ void app::loop(void) {
case InverterDevInform_All: case InverterDevInform_All:
{ {
DPRINT(DBG_INFO, "Response from inform all\n"); DPRINT(DBG_INFO, "Response from inform all\n");
mSys->InfoCmd = RealTimeRunData_Debug; // Set back to default
break; break;
} }
case GetLossRate: case GetLossRate:
@ -117,23 +139,6 @@ void app::loop(void) {
} }
case RealTimeRunData_Debug: case RealTimeRunData_Debug:
{ {
uint8_t *pid = &p->packet[9];
if (*pid == 0x00) {
DPRINT(DBG_DEBUG, "fragment number zero received and ignored");
} else {
if((*pid & 0x7F) < 5) {
memcpy(mPayload[iv->id].data[(*pid & 0x7F) - 1], &p->packet[10], len-11);
mPayload[iv->id].len[(*pid & 0x7F) - 1] = len-11;
}
if((*pid & 0x80) == 0x80) { // Last packet
if((*pid & 0x7f) > mPayload[iv->id].maxPackId) {
mPayload[iv->id].maxPackId = (*pid & 0x7f);
if(*pid > 0x81)
mLastPacketId = *pid;
}
}
}
break; break;
} }
} }
@ -175,7 +180,7 @@ void app::loop(void) {
if(rxRdy) { if(rxRdy) {
processPayload(true); processPayload(true,mSys->InfoCmd);
} }
} }
@ -261,7 +266,7 @@ void app::loop(void) {
if(NULL != iv) { if(NULL != iv) {
if(!mPayload[iv->id].complete) if(!mPayload[iv->id].complete)
processPayload(false); processPayload(false,mSys->InfoCmd);
if(!mPayload[iv->id].complete) { if(!mPayload[iv->id].complete) {
mRxFailed++; mRxFailed++;
@ -271,13 +276,7 @@ void app::loop(void) {
} }
} }
// reset payload data resetPayload(iv);
memset(mPayload[iv->id].len, 0, MAX_PAYLOAD_ENTRIES);
mPayload[iv->id].retransmits = 0;
mPayload[iv->id].maxPackId = 0;
mPayload[iv->id].complete = false;
mPayload[iv->id].requested = true;
mPayload[iv->id].ts = mTimestamp;
yield(); yield();
if(mConfig.serialDebug) if(mConfig.serialDebug)
@ -335,6 +334,9 @@ bool app::buildPayload(uint8_t id) {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void app::processPayload(bool retransmit) { void app::processPayload(bool retransmit) {
processPayload(retransmit, RealTimeRunData_Debug);
}
void app::processPayload(bool retransmit, uint8_t cmd = RealTimeRunData_Debug) { // cmd value decides which parser is used to decode payload
#ifdef __MQTT_AFTER_RX__ #ifdef __MQTT_AFTER_RX__
boolean doMQTT = false; boolean doMQTT = false;
@ -390,12 +392,14 @@ void app::processPayload(bool retransmit) {
mSys->Radio.dumpBuf(NULL, payload, offs); mSys->Radio.dumpBuf(NULL, payload, offs);
} }
mRxSuccess++; mRxSuccess++;
mSys->InfoCmd = RealTimeRunData_Debug; // On success set back to default
iv->getAssignment(cmd); // choose the parser
for(uint8_t i = 0; i < iv->listLen; i++) { for(uint8_t i = 0; i < iv->listLen; i++) {
iv->addValue(i, payload); iv->addValue(i, payload,cmd); // cmd value decides which parser is used to decode payload
yield(); yield();
} }
iv->doCalculations(); iv->doCalculations(cmd); // cmd value decides which parser is used to decode payload
#ifdef __MQTT_AFTER_RX__ #ifdef __MQTT_AFTER_RX__
doMQTT = true; doMQTT = true;
@ -432,7 +436,6 @@ void app::cbMqtt(char* topic, byte* payload, unsigned int length) {
if (std::strcmp(token,"devcontrol")==0){ if (std::strcmp(token,"devcontrol")==0){
token = strtok(NULL, "/"); token = strtok(NULL, "/");
uint8_t iv_id = std::stoi(token); uint8_t iv_id = std::stoi(token);
uint8_t powerLimitControl = 0;
if (iv_id >= 0 && iv_id <= MAX_NUM_INVERTERS){ if (iv_id >= 0 && iv_id <= MAX_NUM_INVERTERS){
Inverter<> *iv = this->mSys->getInverterByPos(iv_id); Inverter<> *iv = this->mSys->getInverterByPos(iv_id);
if(NULL != iv) { if(NULL != iv) {
@ -442,24 +445,22 @@ void app::cbMqtt(char* topic, byte* payload, unsigned int length) {
case ActivePowerContr: // Active Power Control case ActivePowerContr: // Active Power Control
token = strtok(NULL, "/"); // get ControlMode aka "PowerPF.Desc" in DTU-Pro Code from topic string token = strtok(NULL, "/"); // get ControlMode aka "PowerPF.Desc" in DTU-Pro Code from topic string
if (token == NULL) // default via mqtt ommit the LimitControlMode if (token == NULL) // default via mqtt ommit the LimitControlMode
powerLimitControl = AbsolutNonPersistent; iv->powerLimit[1] = AbsolutNonPersistent;
else else
powerLimitControl = std::stoi(token); iv->powerLimit[1] = std::stoi(token);
// 0x0001 -> relativ limit in percent if (length<=5){ // if (std::stoi((char*)payload) > 0) more error handling powerlimit needed?
// 0x0000 -> absolut limit in Watt if (iv->powerLimit[1] >= AbsolutNonPersistent && iv->powerLimit[1] <= RelativPersistent){
// 0x0101 -> persisten limit in percent (?) iv->devControlCmd = ActivePowerContr;
// ... iv->powerLimit[0] = std::stoi(std::string((char*)payload, (unsigned int)length)); // THX to @silversurfer
if (true){ // if (std::stoi((char*)payload) > 0) error handling powerlimit needed? if (iv->powerLimit[1] & 0x0001)
iv->devControlCmd = ActivePowerContr; DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("%") );
iv->powerLimit[0] = std::stoi((char*)payload); else
if (powerLimitControl >= AbsolutNonPersistent && powerLimitControl <= RelativNonPersistent) DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W") );
iv->powerLimit[1] = powerLimitControl; }
if (iv->powerLimit[1] & 0x0001) iv->devControlRequest = true;
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("%") ); } else {
else DPRINTLN(DBG_INFO, F("Invalid mqtt payload recevied: ") + String((char*)payload));
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W") );
} }
iv->devControlRequest = true;
break; break;
case TurnOn: // Turn On case TurnOn: // Turn On
iv->devControlCmd = TurnOn; iv->devControlCmd = TurnOn;
@ -518,7 +519,7 @@ String app::getStatistics(void) {
iv = mSys->getInverterByPos(i); iv = mSys->getInverterByPos(i);
if(NULL != iv) { if(NULL != iv) {
bool avail = true; bool avail = true;
content += F("Inverter '") + String(iv->name) + F("' is "); content += F("Inverter '") + String(iv->name) + F(" (FW-Version: ") + String(iv->fwVersion) +F(")") + F("' is ");
if(!iv->isAvailable(mTimestamp)) { if(!iv->isAvailable(mTimestamp)) {
content += F("not "); content += F("not ");
avail = false; avail = false;
@ -937,3 +938,15 @@ void app::setupMqtt(void) {
} }
} }
} }
//-----------------------------------------------------------------------------
void app::resetPayload(Inverter<>* iv)
{
// reset payload data
memset(mPayload[iv->id].len, 0, MAX_PAYLOAD_ENTRIES);
mPayload[iv->id].retransmits = 0;
mPayload[iv->id].maxPackId = 0;
mPayload[iv->id].complete = false;
mPayload[iv->id].requested = true;
mPayload[iv->id].ts = mTimestamp;
}

2
tools/esp8266/app.h

@ -69,6 +69,7 @@ class app {
void handleIntr(void); void handleIntr(void);
void cbMqtt(char* topic, byte* payload, unsigned int length); void cbMqtt(char* topic, byte* payload, unsigned int length);
void saveValues(void); void saveValues(void);
void resetPayload(Inverter<>* iv);
String getStatistics(void); String getStatistics(void);
String getLiveData(void); String getLiveData(void);
String getJson(void); String getJson(void);
@ -150,6 +151,7 @@ class app {
bool buildPayload(uint8_t id); bool buildPayload(uint8_t id);
void processPayload(bool retransmit); void processPayload(bool retransmit);
void processPayload(bool retransmit, uint8_t cmd);
void sendMqttDiscoveryConfig(void); void sendMqttDiscoveryConfig(void);
const char* getFieldDeviceClass(uint8_t fieldId); const char* getFieldDeviceClass(uint8_t fieldId);

10
tools/esp8266/defines.h

@ -13,7 +13,7 @@
//------------------------------------- //-------------------------------------
#define VERSION_MAJOR 0 #define VERSION_MAJOR 0
#define VERSION_MINOR 5 #define VERSION_MINOR 5
#define VERSION_PATCH 12 #define VERSION_PATCH 14
//------------------------------------- //-------------------------------------
@ -58,10 +58,10 @@ typedef enum {
} DevControlCmdType; } DevControlCmdType;
typedef enum { // ToDo: to be verified by field tests typedef enum { // ToDo: to be verified by field tests
AbsolutNonPersistent = 0x0000, // 0 AbsolutNonPersistent = 0UL, // 0x0000
RelativNonPersistent = 0x0001, // 1 RelativNonPersistent = 1UL, // 0x0001
AbsolutPersistent = 0x0100, // 256 AbsolutPersistent = 256UL, // 0x0100
RelativPersistent = 0x0101 // 257 RelativPersistent = 257UL // 0x0101
} PowerLimitControlType; } PowerLimitControlType;
// minimum serial interval // minimum serial interval

25
tools/esp8266/hmDefines.h

@ -17,15 +17,15 @@ union serial_u {
// units // units
enum {UNIT_V = 0, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_HZ, UNIT_C, UNIT_PCT, UNIT_VA, UNIT_ALARM_MES_ID}; enum {UNIT_V = 0, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_HZ, UNIT_C, UNIT_PCT, UNIT_VA, UNIT_NONE};
const char* const units[] = {"V", "A", "W", "Wh", "kWh", "Hz", "°C", "%","VAr",""}; const char* const units[] = {"V", "A", "W", "Wh", "kWh", "Hz", "°C", "%","VAr",""};
// field types // field types
enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT, enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT,
FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PCT, FLD_EFF, FLD_IRR, FLD_PRA,FLD_ALARM_MES_ID}; FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PCT, FLD_EFF, FLD_IRR, FLD_PRA,FLD_ALARM_MES_ID,FLD_FW_VERSION,FLD_FW_BUILD_YEAR,FLD_FW_BUILD_MONTH_DAY,FLD_HW_ID};
const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal", const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal",
"U_AC", "I_AC", "P_AC", "Freq", "Temp", "Pct", "Efficiency", "Irradiation","P_ACr","ALARM_MES_ID"}; "U_AC", "I_AC", "P_AC", "Freq", "Temp", "Pct", "Efficiency", "Irradiation","P_ACr","ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","HWPartId"};
// mqtt discovery device classes // mqtt discovery device classes
enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP}; enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP};
@ -81,6 +81,19 @@ typedef struct {
* (complete payload in buffer) * (complete payload in buffer)
* */ * */
//-------------------------------------
// HM-Series
//-------------------------------------
const byteAssign_t InfoAssignment[] = {
{ FLD_FW_VERSION, UNIT_NONE, CH0, 0, 2, 1 },
{ FLD_FW_BUILD_YEAR, UNIT_NONE, CH0, 2, 2, 1 },
{ FLD_FW_BUILD_MONTH_DAY, UNIT_NONE, CH0, 4, 2, 1 },
{ FLD_HW_ID, UNIT_NONE, CH0, 8, 2, 1 }
};
#define HMINFO_LIST_LEN (sizeof(InfoAssignment) / sizeof(byteAssign_t))
//------------------------------------- //-------------------------------------
// HM300, HM350, HM400 // HM300, HM350, HM400
//------------------------------------- //-------------------------------------
@ -98,7 +111,7 @@ const byteAssign_t hm1chAssignment[] = {
{ FLD_PRA, UNIT_VA, CH0, 20, 2, 10 }, { FLD_PRA, UNIT_VA, CH0, 20, 2, 10 },
{ FLD_F, UNIT_HZ, CH0, 16, 2, 100 }, { FLD_F, UNIT_HZ, CH0, 16, 2, 100 },
{ FLD_T, UNIT_C, CH0, 26, 2, 10 }, { FLD_T, UNIT_C, CH0, 26, 2, 10 },
{ FLD_ALARM_MES_ID, UNIT_ALARM_MES_ID, CH0, 28, 2, 1 }, { FLD_ALARM_MES_ID, UNIT_NONE, CH0, 28, 2, 1 },
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC }
}; };
@ -129,7 +142,7 @@ const byteAssign_t hm2chAssignment[] = {
{ FLD_PRA, UNIT_VA, CH0, 32, 2, 10 }, { FLD_PRA, UNIT_VA, CH0, 32, 2, 10 },
{ FLD_F, UNIT_HZ, CH0, 28, 2, 100 }, { FLD_F, UNIT_HZ, CH0, 28, 2, 100 },
{ FLD_T, UNIT_C, CH0, 38, 2, 10 }, { FLD_T, UNIT_C, CH0, 38, 2, 10 },
{ FLD_ALARM_MES_ID, UNIT_ALARM_MES_ID, CH0, 40, 2, 1 }, { FLD_ALARM_MES_ID, UNIT_NONE, CH0, 40, 2, 1 },
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, { FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC },
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC },
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
@ -178,7 +191,7 @@ const byteAssign_t hm4chAssignment[] = {
{ FLD_F, UNIT_HZ, CH0, 48, 2, 100 }, { FLD_F, UNIT_HZ, CH0, 48, 2, 100 },
{ FLD_PCT, UNIT_PCT, CH0, 56, 2, 10 }, { FLD_PCT, UNIT_PCT, CH0, 56, 2, 10 },
{ FLD_T, UNIT_C, CH0, 58, 2, 10 }, { FLD_T, UNIT_C, CH0, 58, 2, 10 },
{ FLD_ALARM_MES_ID, UNIT_ALARM_MES_ID, CH0, 60, 2, 1 }, { FLD_ALARM_MES_ID, UNIT_NONE, CH0, 60, 2, 1 },
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, { FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC },
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC },
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },

86
tools/esp8266/hmInverter.h

@ -70,6 +70,7 @@ class Inverter {
byteAssign_t* assign; // type of inverter byteAssign_t* assign; // type of inverter
uint8_t listLen; // length of assignments uint8_t listLen; // length of assignments
uint16_t alarmMesIndex; // Last recorded Alarm Message Index uint16_t alarmMesIndex; // Last recorded Alarm Message Index
uint16_t fwVersion; // Firmware Version from Info Command Request
uint16_t powerLimit[2]; // limit power output uint16_t powerLimit[2]; // limit power output
uint8_t devControlCmd; // carries the requested cmd uint8_t devControlCmd; // carries the requested cmd
bool devControlRequest; // true if change needed bool devControlRequest; // true if change needed
@ -89,6 +90,7 @@ class Inverter {
devControlRequest = false; devControlRequest = false;
devControlCmd = 0xff; devControlCmd = 0xff;
initialized = false; initialized = false;
fwVersion = 0;
} }
~Inverter() { ~Inverter() {
@ -131,7 +133,7 @@ class Inverter {
return assign[pos].ch; return assign[pos].ch;
} }
void addValue(uint8_t pos, uint8_t buf[]) { void addValue(uint8_t pos, uint8_t buf[],uint8_t cmd) {
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:addValue")); DPRINTLN(DBG_VERBOSE, F("hmInverter.h:addValue"));
uint8_t ptr = assign[pos].start; uint8_t ptr = assign[pos].start;
uint8_t end = ptr + assign[pos].num; uint8_t end = ptr + assign[pos].num;
@ -145,9 +147,18 @@ class Inverter {
record[pos] = (RECORDTYPE)(val) / (RECORDTYPE)(div); record[pos] = (RECORDTYPE)(val) / (RECORDTYPE)(div);
} }
// get last alarm message index and save it in the inverter instance if (cmd == RealTimeRunData_Debug) {
if (getPosByChFld(0, FLD_ALARM_MES_ID) == pos){ // get last alarm message index and save it in the inverter object
alarmMesIndex = record[pos]; if (getPosByChFld(0, FLD_ALARM_MES_ID) == pos){
alarmMesIndex = record[pos];
}
}
if (cmd == InverterDevInform_All) {
// get at least the firmware version and save it to the inverter object
if (getPosByChFld(0, FLD_FW_VERSION) == pos){
fwVersion = record[pos];
DPRINT(DBG_DEBUG, F("Inverter FW-Version: ") + String(fwVersion));
}
} }
} }
@ -156,13 +167,16 @@ class Inverter {
return record[pos]; return record[pos];
} }
void doCalculations(void) { void doCalculations(uint8_t cmd=RealTimeRunData_Debug) {
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:doCalculations")); DPRINTLN(DBG_VERBOSE, F("hmInverter.h:doCalculations"));
for(uint8_t i = 0; i < listLen; i++) { getAssignment(cmd);
if(CMD_CALC == assign[i].div) { if (cmd == RealTimeRunData_Debug){
record[i] = calcFunctions<RECORDTYPE>[assign[i].start].func(this, assign[i].num); for(uint8_t i = 0; i < listLen; i++) {
if(CMD_CALC == assign[i].div) {
record[i] = calcFunctions<RECORDTYPE>[assign[i].start].func(this, assign[i].num);
}
yield();
} }
yield();
} }
} }
@ -185,6 +199,36 @@ class Inverter {
return ts; return ts;
} }
void getAssignment(uint8_t cmd=RealTimeRunData_Debug) {
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getAssignment"));
if(cmd == RealTimeRunData_Debug){
if(INV_TYPE_1CH == type) {
listLen = (uint8_t)(HM1CH_LIST_LEN);
assign = (byteAssign_t*)hm1chAssignment;
channels = 1;
}
else if(INV_TYPE_2CH == type) {
listLen = (uint8_t)(HM2CH_LIST_LEN);
assign = (byteAssign_t*)hm2chAssignment;
channels = 2;
}
else if(INV_TYPE_4CH == type) {
listLen = (uint8_t)(HM4CH_LIST_LEN);
assign = (byteAssign_t*)hm4chAssignment;
channels = 4;
}
else {
listLen = 0;
channels = 0;
assign = NULL;
}
}
if(cmd == InverterDevInform_All){
listLen = (uint8_t)(HMINFO_LIST_LEN);
assign = (byteAssign_t*)InfoAssignment;
}
}
private: private:
void toRadioId(void) { void toRadioId(void) {
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:toRadioId")); DPRINTLN(DBG_VERBOSE, F("hmInverter.h:toRadioId"));
@ -195,30 +239,6 @@ class Inverter {
radioId.b[1] = serial.b[3]; radioId.b[1] = serial.b[3];
radioId.b[0] = 0x01; radioId.b[0] = 0x01;
} }
void getAssignment(void) {
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getAssignment"));
if(INV_TYPE_1CH == type) {
listLen = (uint8_t)(HM1CH_LIST_LEN);
assign = (byteAssign_t*)hm1chAssignment;
channels = 1;
}
else if(INV_TYPE_2CH == type) {
listLen = (uint8_t)(HM2CH_LIST_LEN);
assign = (byteAssign_t*)hm2chAssignment;
channels = 2;
}
else if(INV_TYPE_4CH == type) {
listLen = (uint8_t)(HM4CH_LIST_LEN);
assign = (byteAssign_t*)hm4chAssignment;
channels = 4;
}
else {
listLen = 0;
channels = 0;
assign = NULL;
}
}
}; };

18
tools/esp8266/hmRadio.h

@ -170,20 +170,10 @@ class HmRadio {
mTxBuf[10] = cmd; // cmd --> 0x0b => Type_ActivePowerContr, 0 on, 1 off, 2 restart, 12 reactive power, 13 power factor mTxBuf[10] = cmd; // cmd --> 0x0b => Type_ActivePowerContr, 0 on, 1 off, 2 restart, 12 reactive power, 13 power factor
mTxBuf[10 + (++cnt)] = 0x00; mTxBuf[10 + (++cnt)] = 0x00;
if (cmd >= ActivePowerContr && cmd <= PFSet){ if (cmd >= ActivePowerContr && cmd <= PFSet){
// 4 bytes control data mTxBuf[10 + (++cnt)] = ((data[0] * 10) >> 8) & 0xff; // power limit
// Power Limit fix point 10 eg. 30 W --> 0d300 = 0x012c mTxBuf[10 + (++cnt)] = ((data[0] * 10) ) & 0xff; // power limit
// -1 = 0xffff --> no limit mTxBuf[10 + (++cnt)] = ((data[1] ) >> 8) & 0xff; // setting for persistens handlings
uint16_t powerLimit = data[0]; mTxBuf[10 + (++cnt)] = ((data[1] ) ) & 0xff; // setting for persistens handling
uint16_t powerLimitSetting = data[1];
if (powerLimit == 0xffff){
powerLimit &= 0xffff; // ToDo: unlimit value is needed and is inverter specific! --> get it via RF from inverter or via user interface
} else {
powerLimit *= 10; // will overwrite the data bc it is a pointer
}
mTxBuf[10 + (++cnt)] = (powerLimit >> 8) & 0xff; // power limit
mTxBuf[10 + (++cnt)] = (powerLimit ) & 0xff; // power limit
mTxBuf[10 + (++cnt)] = (powerLimitSetting >> 8) & 0xff; // setting for persistens handling
mTxBuf[10 + (++cnt)] = (powerLimitSetting ) & 0xff; // setting for persistens handling
} }
// crc control data // crc control data
uint16_t crc = Hoymiles::crc16(&mTxBuf[10], cnt+1); uint16_t crc = Hoymiles::crc16(&mTxBuf[10], cnt+1);

2
tools/esp8266/html/h/setup_html.h

File diff suppressed because one or more lines are too long

2
tools/esp8266/html/setup.html

@ -109,8 +109,6 @@
<input type="text" class="text" name="mqttPwd" value="{MQTT_PWD}"/> <input type="text" class="text" name="mqttPwd" value="{MQTT_PWD}"/>
<label for="mqttTopic">Topic</label> <label for="mqttTopic">Topic</label>
<input type="text" class="text" name="mqttTopic" value="{MQTT_TOPIC}"/> <input type="text" class="text" name="mqttTopic" value="{MQTT_TOPIC}"/>
<label for="mqttIntvl">Interval [s] (read-only)</label>
<input type="text" class="text" name="mqttIntvl" value="{MQTT_INTVL}" readonly/>
</fieldset> </fieldset>
</div> </div>

72
tools/esp8266/web.cpp

@ -269,7 +269,6 @@ void web::showSetup(void) {
html.replace(F("{MQTT_USER}"), String(mConfig->mqtt.user)); html.replace(F("{MQTT_USER}"), String(mConfig->mqtt.user));
html.replace(F("{MQTT_PWD}"), String(mConfig->mqtt.pwd)); html.replace(F("{MQTT_PWD}"), String(mConfig->mqtt.pwd));
html.replace(F("{MQTT_TOPIC}"), String(mConfig->mqtt.topic)); html.replace(F("{MQTT_TOPIC}"), String(mConfig->mqtt.topic));
html.replace(F("{MQTT_INTVL}"), String("0"));
mWeb->send(200, F("text/html"), html); mWeb->send(200, F("text/html"), html);
} }
@ -334,7 +333,7 @@ void web::showSave(void) {
if(mWeb->arg("invInterval") != "") if(mWeb->arg("invInterval") != "")
mConfig->sendInterval = mWeb->arg("invInterval").toInt(); mConfig->sendInterval = mWeb->arg("invInterval").toInt();
if(mWeb->arg("invRetry") != "") if(mWeb->arg("invRetry") != "")
mConfig->sendInterval = mWeb->arg("invRetry").toInt(); mConfig->maxRetransPerPyld = mWeb->arg("invRetry").toInt();
// pinout // pinout
uint8_t pin; uint8_t pin;
@ -369,8 +368,8 @@ void web::showSave(void) {
if(mWeb->arg("serIntvl") != "") { if(mWeb->arg("serIntvl") != "") {
mConfig->serialInterval = mWeb->arg("serIntvl").toInt() & 0xffff; mConfig->serialInterval = mWeb->arg("serIntvl").toInt() & 0xffff;
mConfig->serialDebug = (mWeb->arg("serEn") == "on"); mConfig->serialDebug = (mWeb->arg("serDbg") == "on");
mConfig->serialShowIv = (mWeb->arg("serDbg") == "on"); mConfig->serialShowIv = (mWeb->arg("serEn") == "on");
// Needed to log TX buffers to serial console // Needed to log TX buffers to serial console
mMain->mSys->Radio.mSerialDebug = mConfig->serialDebug; mMain->mSys->Radio.mSerialDebug = mConfig->serialDebug;
} }
@ -418,45 +417,62 @@ void web::showJson(void) {
mWeb->send(200, F("application/json"), mMain->getJson()); mWeb->send(200, F("application/json"), mMain->getJson());
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void web::showWebApi(void) { void web::showWebApi(void)
{
DPRINTLN(DBG_VERBOSE, F("web::showWebApi")); DPRINTLN(DBG_VERBOSE, F("web::showWebApi"));
DPRINTLN(DBG_DEBUG, mWeb->arg("plain")); DPRINTLN(DBG_DEBUG, mWeb->arg("plain"));
const size_t capacity = 200; // Use arduinojson.org/assistant to compute the capacity. const size_t capacity = 200; // Use arduinojson.org/assistant to compute the capacity.
DynamicJsonDocument payload(capacity); DynamicJsonDocument response(capacity);
// Parse JSON object // Parse JSON object
deserializeJson(payload, mWeb->arg("plain")); deserializeJson(response, mWeb->arg("plain"));
// ToDo: error handling for payload // ToDo: error handling for payload
if (payload["tx_request"] == TX_REQ_INFO) { uint8_t iv_id = response["inverter"];
mMain->mSys->InfoCmd = payload["cmd"]; Inverter<> *iv = mMain->mSys->getInverterByPos(iv_id);
DPRINTLN(DBG_INFO, F("Will make tx-request 0x15 with subcmd ") + String(mMain->mSys->InfoCmd)); if (NULL != iv)
} {
if (payload["tx_request"] == (uint8_t)TX_REQ_DEVCONTROL){ if (response["tx_request"] == (uint8_t)TX_REQ_INFO)
if(payload["cmd"] == (uint8_t)ActivePowerContr){ {
uint8_t iv_id = payload["inverter"]; mMain->mSys->InfoCmd = response["cmd"];
if (iv_id >= 0 && iv_id <= MAX_NUM_INVERTERS){ mMain->resetPayload(iv); // start request from new
Inverter<> *iv = mMain->mSys->getInverterByPos(iv_id); // process payload from web request corresponding to the cmd
uint16_t webapiPayload = payload["payload"]; if (mMain->mSys->InfoCmd == AlarmData)
uint16_t webapiPayload2 = payload["payload2"]; iv->alarmMesIndex = response["payload"];
if (webapiPayload > 0 && webapiPayload < 10000){ DPRINTLN(DBG_INFO, F("Will make tx-request 0x15 with subcmd ") + String(mMain->mSys->InfoCmd) + F(" and payload ") + String(response["payload"]));
}
if (response["tx_request"] == (uint8_t)TX_REQ_DEVCONTROL)
{
if (response["cmd"] == (uint8_t)ActivePowerContr)
{
uint16_t webapiPayload = response["payload"];
uint16_t webapiPayload2 = response["payload2"];
if (webapiPayload > 0 && webapiPayload < 10000)
{
iv->devControlCmd = ActivePowerContr; iv->devControlCmd = ActivePowerContr;
iv->powerLimit[0] = webapiPayload; iv->powerLimit[0] = webapiPayload;
if (webapiPayload2 > 0){ if (webapiPayload2 > 0)
{
iv->powerLimit[1] = webapiPayload2; // dev option, no sanity check iv->powerLimit[1] = webapiPayload2; // dev option, no sanity check
} else { // if not set, set it to 0x0000 default }
else
{ // if not set, set it to 0x0000 default
iv->powerLimit[1] = AbsolutNonPersistent; // payload will be seted temporay in Watt absolut iv->powerLimit[1] = AbsolutNonPersistent; // payload will be seted temporay in Watt absolut
} }
if (iv->powerLimit[1] & 0x0001 ){ if (iv->powerLimit[1] & 0x0001)
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("% via REST API") ); {
} else { DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("% via REST API"));
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W via REST API") ); }
else
{
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W via REST API"));
} }
iv->devControlRequest = true; // queue it in the request loop iv->devControlRequest = true; // queue it in the request loop
} }
} }
} }
} }
mWeb->send ( 200, "text/json", "{success:true}" ); mWeb->send(200, "text/json", "{success:true}");
} }

Loading…
Cancel
Save