Browse Source

* started to implement POST api

* improved web serial console
* added multi inverter total values (published through MQTT)
* fixed: after boot there were transferred wrong data because of incorrect assignment (mqtt, visualization)
  -> not tested with sun
pull/283/head
lumapu 2 years ago
parent
commit
6bd7e01f1a
  1. 24
      tools/esp8266/User_Manual.md
  2. 40
      tools/esp8266/app.cpp
  3. 90
      tools/esp8266/defines.h
  4. 327
      tools/esp8266/hmInverter.h
  5. 2
      tools/esp8266/hmRadio.h
  6. 4
      tools/esp8266/hmSystem.h
  7. 20
      tools/esp8266/html/api.js
  8. 52
      tools/esp8266/html/serial.html
  9. 8
      tools/esp8266/html/visualization.html
  10. 45
      tools/esp8266/web.cpp
  11. 2
      tools/esp8266/web.h
  12. 165
      tools/esp8266/webApi.cpp
  13. 6
      tools/esp8266/webApi.h

24
tools/esp8266/User_Manual.md

@ -92,17 +92,17 @@ To set the active power limit (controled value is the AC Power of the inverter)
The implementation allows to set any of the available `<DevControlCmdType>` Commands: The implementation allows to set any of the available `<DevControlCmdType>` Commands:
```C ```C
typedef enum { typedef enum {
TurnOn = 0, // 0x00 TurnOn = 0, // 0x00
TurnOff = 1, // 0x01 TurnOff = 1, // 0x01
Restart = 2, // 0x02 Restart = 2, // 0x02
Lock = 3, // 0x03 Lock = 3, // 0x03
Unlock = 4, // 0x04 Unlock = 4, // 0x04
ActivePowerContr = 11, // 0x0b ActivePowerContr = 11, // 0x0b
ReactivePowerContr = 12, // 0x0c ReactivePowerContr = 12, // 0x0c
PFSet = 13, // 0x0d PFSet = 13, // 0x0d
CleanState_LockAndAlarm = 20, // 0x14 CleanState_LockAndAlarm = 20, // 0x14
SelfInspection = 40, // 0x28, self-inspection of grid-connected protection files SelfInspection = 40, // 0x28, self-inspection of grid-connected protection files
Init = 0xff Init = 0xff
} DevControlCmdType; } DevControlCmdType;
``` ```
The MQTT payload will be set on first to bytes and `<DATA2>`, which is taken from the topic path will be set 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>`, which is taken from the topic path will be set on the second two bytes if the corresponding DevControlCmdType supports 4 byte data.
@ -215,8 +215,8 @@ Internally this values will be set for the second two bytes for MainCmd: 0x51 Su
typedef enum { typedef enum {
AbsolutNonPersistent = 0x0000, // 0 AbsolutNonPersistent = 0x0000, // 0
RelativNonPersistent = 0x0001, // 1 RelativNonPersistent = 0x0001, // 1
AbsolutPersistent = 0x0100, // 256 AbsolutPersistent = 0x0100, // 256
RelativPersistent = 0x0101 // 257 RelativPersistent = 0x0101 // 257
} PowerLimitControlType; } PowerLimitControlType;
``` ```

40
tools/esp8266/app.cpp

@ -381,23 +381,47 @@ void app::processPayload(bool retransmit) {
// MQTT send out // MQTT send out
if(mMqttActive) { if(mMqttActive) {
char topic[30], val[10]; char topic[30], val[10];
for (uint8_t id = 0; id < mSys->getNumInverters(); id++) float total[4];
{ memset(total, 0, sizeof(float) * 4);
for (uint8_t id = 0; id < mSys->getNumInverters(); id++) {
Inverter<> *iv = mSys->getInverterByPos(id); Inverter<> *iv = mSys->getInverterByPos(id);
if (NULL != iv) if (NULL != iv) {
{ if (iv->isAvailable(mTimestamp)) {
if (iv->isAvailable(mTimestamp)) for (uint8_t i = 0; i < iv->listLen; i++) {
{
for (uint8_t i = 0; i < iv->listLen; i++)
{
snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, fields[iv->assign[i].fieldId]); snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, fields[iv->assign[i].fieldId]);
snprintf(val, 10, "%.3f", iv->getValue(i)); snprintf(val, 10, "%.3f", iv->getValue(i));
mMqtt.sendMsg(topic, val); mMqtt.sendMsg(topic, val);
if(iv->isLiveDataAssignment()) {
if(CH0 == iv->assign[i].ch) {
switch(iv->assign[i].fieldId) {
case FLD_PAC: total[0] += iv->getValue(i); break;
case FLD_YT: total[1] += iv->getValue(i); break;
case FLD_YD: total[2] += iv->getValue(i); break;
case FLD_PDC: total[3] += iv->getValue(i); break;
}
}
}
yield(); yield();
} }
} }
} }
} }
// total values (sum of all inverters)
if(mSys->getNumInverters() > 1) {
uint8_t fieldId = 0;
for (uint8_t i = 0; i < 4; i++) {
switch(i) {
case 0: fieldId = FLD_PAC; break;
case 1: fieldId = FLD_YT; break;
case 2: fieldId = FLD_YD; break;
case 3: fieldId = FLD_PDC; break;
}
snprintf(topic, 30, "total/%s", fields[fieldId]);
snprintf(val, 10, "%.3f", total[i]);
mMqtt.sendMsg(topic, val);
}
}
} }
#ifdef __MQTT_AFTER_RX__ #ifdef __MQTT_AFTER_RX__

90
tools/esp8266/defines.h

@ -23,55 +23,50 @@ typedef struct {
} packet_t; } packet_t;
typedef enum { typedef enum {
InverterDevInform_Simple = 0, // 0x00 InverterDevInform_Simple = 0, // 0x00
InverterDevInform_All = 1, // 0x01 InverterDevInform_All = 1, // 0x01
GridOnProFilePara = 2, // 0x02 GridOnProFilePara = 2, // 0x02
HardWareConfig = 3, // 0x03 HardWareConfig = 3, // 0x03
SimpleCalibrationPara = 4, // 0x04 SimpleCalibrationPara = 4, // 0x04
SystemConfigPara = 5, // 0x05 SystemConfigPara = 5, // 0x05
RealTimeRunData_Debug = 11, // 0x0b RealTimeRunData_Debug = 11, // 0x0b
RealTimeRunData_Reality = 12, // 0x0c RealTimeRunData_Reality = 12, // 0x0c
RealTimeRunData_A_Phase = 13, // 0x0d RealTimeRunData_A_Phase = 13, // 0x0d
RealTimeRunData_B_Phase = 14, // 0x0e RealTimeRunData_B_Phase = 14, // 0x0e
RealTimeRunData_C_Phase = 15, // 0x0f RealTimeRunData_C_Phase = 15, // 0x0f
AlarmData = 17, // 0x11, Alarm data - all unsent alarms AlarmData = 17, // 0x11, Alarm data - all unsent alarms
AlarmUpdate = 18, // 0x12, Alarm data - all pending alarms AlarmUpdate = 18, // 0x12, Alarm data - all pending alarms
RecordData = 19, // 0x13 RecordData = 19, // 0x13
InternalData = 20, // 0x14 InternalData = 20, // 0x14
GetLossRate = 21, // 0x15 GetLossRate = 21, // 0x15
GetSelfCheckState = 30, // 0x1e GetSelfCheckState = 30, // 0x1e
InitDataState = 0xff InitDataState = 0xff
} InfoCmdType; } InfoCmdType;
typedef enum { typedef enum {
TurnOn = 0, // 0x00 TurnOn = 0, // 0x00
TurnOff = 1, // 0x01 TurnOff = 1, // 0x01
Restart = 2, // 0x02 Restart = 2, // 0x02
Lock = 3, // 0x03 Lock = 3, // 0x03
Unlock = 4, // 0x04 Unlock = 4, // 0x04
ActivePowerContr = 11, // 0x0b ActivePowerContr = 11, // 0x0b
ReactivePowerContr = 12, // 0x0c ReactivePowerContr = 12, // 0x0c
PFSet = 13, // 0x0d PFSet = 13, // 0x0d
CleanState_LockAndAlarm = 20, // 0x14 CleanState_LockAndAlarm = 20, // 0x14
SelfInspection = 40, // 0x28, self-inspection of grid-connected protection files SelfInspection = 40, // 0x28, self-inspection of grid-connected protection files
Init = 0xff Init = 0xff
} DevControlCmdType; } DevControlCmdType;
typedef enum { // ToDo: to be verified by field tests typedef enum {
NoPowerLimit = 0xffff, // ahoy internal value, no hoymiles value! NoPowerLimit = 0xffff, // ahoy internal value, no hoymiles value!
AbsolutNonPersistent = 0UL, // 0x0000 AbsolutNonPersistent = 0UL, // 0x0000
RelativNonPersistent = 1UL, // 0x0001 RelativNonPersistent = 1UL, // 0x0001
AbsolutPersistent = 256UL, // 0x0100 AbsolutPersistent = 256UL, // 0x0100
RelativPersistent = 257UL // 0x0101 RelativPersistent = 257UL // 0x0101
} PowerLimitControlType; } PowerLimitControlType;
// minimum serial interval
#define MIN_SERIAL_INTERVAL 5 #define MIN_SERIAL_INTERVAL 5
// minimum send interval
#define MIN_SEND_INTERVAL 15 #define MIN_SEND_INTERVAL 15
// minimum mqtt interval
#define MIN_MQTT_INTERVAL 60 #define MIN_MQTT_INTERVAL 60
//------------------------------------- //-------------------------------------
@ -90,27 +85,16 @@ typedef enum { // ToDo: to be verified by field tests
#define INV_MAX_RTRY_LEN 1 // uint8_t #define INV_MAX_RTRY_LEN 1 // uint8_t
#define INV_PWR_LIM_LEN MAX_NUM_INVERTERS * 2 // uint16_t #define INV_PWR_LIM_LEN MAX_NUM_INVERTERS * 2 // uint16_t
#define PINOUT_LEN 3 // 3 pins: CS, CE, IRQ
#define RF24_AMP_PWR_LEN 1
#define NTP_ADDR_LEN 32 // DNS Name #define NTP_ADDR_LEN 32 // DNS Name
#define NTP_PORT_LEN 2 // uint16_t
#define MQTT_ADDR_LEN 32 // DNS Name #define MQTT_ADDR_LEN 32 // DNS Name
#define MQTT_USER_LEN 16 #define MQTT_USER_LEN 16
#define MQTT_PWD_LEN 32 #define MQTT_PWD_LEN 32
#define MQTT_TOPIC_LEN 32 #define MQTT_TOPIC_LEN 32
#define MQTT_INTERVAL_LEN 2 // uint16_t
#define MQTT_PORT_LEN 2 // uint16_t
#define MQTT_DISCOVERY_PREFIX "homeassistant" #define MQTT_DISCOVERY_PREFIX "homeassistant"
#define MQTT_MAX_PACKET_SIZE 384 #define MQTT_MAX_PACKET_SIZE 384
#define MQTT_RECONNECT_DELAY 5000 #define MQTT_RECONNECT_DELAY 5000
#define SER_ENABLE_LEN 1 // uint8_t
#define SER_DEBUG_LEN 1 // uint8_t
#define SER_INTERVAL_LEN 2 // uint16_t
#pragma pack(push) // push current alignment to stack #pragma pack(push) // push current alignment to stack
#pragma pack(1) // set alignment to 1 byte boundary #pragma pack(1) // set alignment to 1 byte boundary
typedef struct { typedef struct {
@ -119,8 +103,10 @@ typedef struct {
char user[MQTT_USER_LEN]; char user[MQTT_USER_LEN];
char pwd[MQTT_PWD_LEN]; char pwd[MQTT_PWD_LEN];
char topic[MQTT_TOPIC_LEN]; char topic[MQTT_TOPIC_LEN];
} /*__attribute__((__packed__))*/ mqttConfig_t; } mqttConfig_t;
#pragma pack(pop) // restore original alignment from stack #pragma pack(pop) // restore original alignment from stack
typedef struct { typedef struct {
char deviceName[DEVNAME_LEN]; char deviceName[DEVNAME_LEN];
@ -151,7 +137,7 @@ typedef struct {
uint16_t serialInterval; uint16_t serialInterval;
bool serialShowIv; bool serialShowIv;
bool serialDebug; bool serialDebug;
} /*__attribute__((__packed__))*/ config_t; } config_t;
#pragma pack(pop) // restore original alignment from stack #pragma pack(pop) // restore original alignment from stack
typedef struct { typedef struct {

327
tools/esp8266/hmInverter.h

@ -136,27 +136,26 @@ class Inverter {
} }
template <typename T> template <typename T>
void enqueCommand(uint8_t cmd) void enqueCommand(uint8_t cmd) {
{
_commandQueue.push(std::make_shared<T>(cmd)); _commandQueue.push(std::make_shared<T>(cmd));
DPRINTLN(DBG_INFO, "enqueuedCmd: " + String(cmd)); DPRINTLN(DBG_INFO, "enqueuedCmd: " + String(cmd));
} }
void setQueuedCmdFinished(){ void setQueuedCmdFinished() {
if (!_commandQueue.empty()){ if (!_commandQueue.empty()) {
// Will destroy CommandAbstract Class Object (?) // Will destroy CommandAbstract Class Object (?)
_commandQueue.pop(); _commandQueue.pop();
} }
} }
void clearCmdQueue(){ void clearCmdQueue() {
while (!_commandQueue.empty()){ while (!_commandQueue.empty()) {
// Will destroy CommandAbstract Class Object (?) // Will destroy CommandAbstract Class Object (?)
_commandQueue.pop(); _commandQueue.pop();
} }
} }
uint8_t getQueuedCmd()
{ uint8_t getQueuedCmd() {
if (_commandQueue.empty()){ if (_commandQueue.empty()){
// Fill with default commands // Fill with default commands
enqueCommand<InfoCommand>(RealTimeRunData_Debug); enqueCommand<InfoCommand>(RealTimeRunData_Debug);
@ -296,36 +295,30 @@ class Inverter {
return false; return false;
} }
uint32_t getLastTs(void) uint32_t getLastTs(void) {
{
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getLastTs")); DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getLastTs"));
return ts; return ts;
} }
void getAssignment() void getAssignment() {
{
DPRINTLN(DBG_DEBUG, F("hmInverter.h:getAssignment")); DPRINTLN(DBG_DEBUG, F("hmInverter.h:getAssignment"));
// Default assignment; // Default assignment;
if (INV_TYPE_1CH == type) if (INV_TYPE_1CH == type) {
{
listLen = (uint8_t)(HM1CH_LIST_LEN); listLen = (uint8_t)(HM1CH_LIST_LEN);
assign = (byteAssign_t *)hm1chAssignment; assign = (byteAssign_t *)hm1chAssignment;
channels = 1; channels = 1;
} }
else if (INV_TYPE_2CH == type) else if (INV_TYPE_2CH == type) {
{
listLen = (uint8_t)(HM2CH_LIST_LEN); listLen = (uint8_t)(HM2CH_LIST_LEN);
assign = (byteAssign_t *)hm2chAssignment; assign = (byteAssign_t *)hm2chAssignment;
channels = 2; channels = 2;
} }
else if (INV_TYPE_4CH == type) else if (INV_TYPE_4CH == type) {
{
listLen = (uint8_t)(HM4CH_LIST_LEN); listLen = (uint8_t)(HM4CH_LIST_LEN);
assign = (byteAssign_t *)hm4chAssignment; assign = (byteAssign_t *)hm4chAssignment;
channels = 4; channels = 4;
} }
else else {
{
listLen = 0; listLen = 0;
channels = 0; channels = 0;
assign = NULL; assign = NULL;
@ -352,217 +345,89 @@ class Inverter {
break; break;
} }
} }
String getAlarmStr(u_int16_t alarmCode)
{ bool isLiveDataAssignment(void) {
switch (alarmCode) if(assign == (byteAssign_t *)hm1chAssignment)
{ return true;
case 1: else if(assign == (byteAssign_t *)hm2chAssignment)
return String(F("Inverter start")); return true;
break; else if(assign == (byteAssign_t *)hm4chAssignment)
case 2: return true;
return String(F("DTU command failed")); else
break; return false;
case 121: }
return String(F("Over temperature protection"));
break; String getAlarmStr(u_int16_t alarmCode) {
case 125: switch (alarmCode) { // breaks are intentionally missing!
return String(F("Grid configuration parameter error")); case 1: return String(F("Inverter start"));
break; case 2: return String(F("DTU command failed"));
case 126: case 121: return String(F("Over temperature protection"));
return String(F("Software error code 126")); case 125: return String(F("Grid configuration parameter error"));
break; case 126: return String(F("Software error code 126"));
case 127: case 127: return String(F("Firmware error"));
return String(F("Firmware error")); case 128: return String(F("Software error code 128"));
break; case 129: return String(F("Software error code 129"));
case 128: case 130: return String(F("Offline"));
return String(F("Software error code 128")); case 141: return String(F("Grid overvoltage"));
break; case 142: return String(F("Average grid overvoltage"));
case 129: case 143: return String(F("Grid undervoltage"));
return String(F("Software error code 129")); case 144: return String(F("Grid overfrequency"));
break; case 145: return String(F("Grid underfrequency"));
case 130: case 146: return String(F("Rapid grid frequency change"));
return String(F("Offline")); case 147: return String(F("Power grid outage"));
break; case 148: return String(F("Grid disconnection"));
case 141: case 149: return String(F("Island detected"));
return String(F("Grid overvoltage")); case 205: return String(F("Input port 1 & 2 overvoltage"));
break; case 206: return String(F("Input port 3 & 4 overvoltage"));
case 142: case 207: return String(F("Input port 1 & 2 undervoltage"));
return String(F("Average grid overvoltage")); case 208: return String(F("Input port 3 & 4 undervoltage"));
break; case 209: return String(F("Port 1 no input"));
case 143: case 210: return String(F("Port 2 no input"));
return String(F("Grid undervoltage")); case 211: return String(F("Port 3 no input"));
break; case 212: return String(F("Port 4 no input"));
case 144: case 213: return String(F("PV-1 & PV-2 abnormal wiring"));
return String(F("Grid overfrequency")); case 214: return String(F("PV-3 & PV-4 abnormal wiring"));
break; case 215: return String(F("PV-1 Input overvoltage"));
case 145: case 216: return String(F("PV-1 Input undervoltage"));
return String(F("Grid underfrequency")); case 217: return String(F("PV-2 Input overvoltage"));
break; case 218: return String(F("PV-2 Input undervoltage"));
case 146: case 219: return String(F("PV-3 Input overvoltage"));
return String(F("Rapid grid frequency change")); case 220: return String(F("PV-3 Input undervoltage"));
break; case 221: return String(F("PV-4 Input overvoltage"));
case 147: case 222: return String(F("PV-4 Input undervoltage"));
return String(F("Power grid outage")); case 301: return String(F("Hardware error code 301"));
break; case 302: return String(F("Hardware error code 302"));
case 148: case 303: return String(F("Hardware error code 303"));
return String(F("Grid disconnection")); case 304: return String(F("Hardware error code 304"));
break; case 305: return String(F("Hardware error code 305"));
case 149: case 306: return String(F("Hardware error code 306"));
return String(F("Island detected")); case 307: return String(F("Hardware error code 307"));
break; case 308: return String(F("Hardware error code 308"));
case 205: case 309: return String(F("Hardware error code 309"));
return String(F("Input port 1 & 2 overvoltage")); case 310: return String(F("Hardware error code 310"));
break; case 311: return String(F("Hardware error code 311"));
case 206: case 312: return String(F("Hardware error code 312"));
return String(F("Input port 3 & 4 overvoltage")); case 313: return String(F("Hardware error code 313"));
break; case 314: return String(F("Hardware error code 314"));
case 207: case 5041: return String(F("Error code-04 Port 1"));
return String(F("Input port 1 & 2 undervoltage")); case 5042: return String(F("Error code-04 Port 2"));
break; case 5043: return String(F("Error code-04 Port 3"));
case 208: case 5044: return String(F("Error code-04 Port 4"));
return String(F("Input port 3 & 4 undervoltage")); case 5051: return String(F("PV Input 1 Overvoltage/Undervoltage"));
break; case 5052: return String(F("PV Input 2 Overvoltage/Undervoltage"));
case 209: case 5053: return String(F("PV Input 3 Overvoltage/Undervoltage"));
return String(F("Port 1 no input")); case 5054: return String(F("PV Input 4 Overvoltage/Undervoltage"));
break; case 5060: return String(F("Abnormal bias"));
case 210: case 5070: return String(F("Over temperature protection"));
return String(F("Port 2 no input")); case 5080: return String(F("Grid Overvoltage/Undervoltage"));
break; case 5090: return String(F("Grid Overfrequency/Underfrequency"));
case 211: case 5100: return String(F("Island detected"));
return String(F("Port 3 no input")); case 5120: return String(F("EEPROM reading and writing error"));
break; case 5150: return String(F("10 min value grid overvoltage"));
case 212: case 5200: return String(F("Firmware error"));
return String(F("Port 4 no input")); case 8310: return String(F("Shut down"));
break; case 9000: return String(F("Microinverter is suspected of being stolen"));
case 213: default: return String(F("Unknown"));
return String(F("PV-1 & PV-2 abnormal wiring"));
break;
case 214:
return String(F("PV-3 & PV-4 abnormal wiring"));
break;
case 215:
return String(F("PV-1 Input overvoltage"));
break;
case 216:
return String(F("PV-1 Input undervoltage"));
break;
case 217:
return String(F("PV-2 Input overvoltage"));
break;
case 218:
return String(F("PV-2 Input undervoltage"));
break;
case 219:
return String(F("PV-3 Input overvoltage"));
break;
case 220:
return String(F("PV-3 Input undervoltage"));
break;
case 221:
return String(F("PV-4 Input overvoltage"));
break;
case 222:
return String(F("PV-4 Input undervoltage"));
break;
case 301:
return String(F("Hardware error code 301"));
break;
case 302:
return String(F("Hardware error code 302"));
break;
case 303:
return String(F("Hardware error code 303"));
break;
case 304:
return String(F("Hardware error code 304"));
break;
case 305:
return String(F("Hardware error code 305"));
break;
case 306:
return String(F("Hardware error code 306"));
break;
case 307:
return String(F("Hardware error code 307"));
break;
case 308:
return String(F("Hardware error code 308"));
break;
case 309:
return String(F("Hardware error code 309"));
break;
case 310:
return String(F("Hardware error code 310"));
break;
case 311:
return String(F("Hardware error code 311"));
break;
case 312:
return String(F("Hardware error code 312"));
break;
case 313:
return String(F("Hardware error code 313"));
break;
case 314:
return String(F("Hardware error code 314"));
break;
case 5041:
return String(F("Error code-04 Port 1"));
break;
case 5042:
return String(F("Error code-04 Port 2"));
break;
case 5043:
return String(F("Error code-04 Port 3"));
break;
case 5044:
return String(F("Error code-04 Port 4"));
break;
case 5051:
return String(F("PV Input 1 Overvoltage/Undervoltage"));
break;
case 5052:
return String(F("PV Input 2 Overvoltage/Undervoltage"));
break;
case 5053:
return String(F("PV Input 3 Overvoltage/Undervoltage"));
break;
case 5054:
return String(F("PV Input 4 Overvoltage/Undervoltage"));
break;
case 5060:
return String(F("Abnormal bias"));
break;
case 5070:
return String(F("Over temperature protection"));
break;
case 5080:
return String(F("Grid Overvoltage/Undervoltage"));
break;
case 5090:
return String(F("Grid Overfrequency/Underfrequency"));
break;
case 5100:
return String(F("Island detected"));
break;
case 5120:
return String(F("EEPROM reading and writing error"));
break;
case 5150:
return String(F("10 min value grid overvoltage"));
break;
case 5200:
return String(F("Firmware error"));
break;
case 8310:
return String(F("Shut down"));
break;
case 9000:
return String(F("Microinverter is suspected of being stolen"));
break;
default:
return String(F("Unknown"));
break;
} }
} }

2
tools/esp8266/hmRadio.h

@ -21,7 +21,7 @@
#define RF_CHANNELS 5 #define RF_CHANNELS 5
#define RF_LOOP_CNT 300 #define RF_LOOP_CNT 300
#define TX_REQ_INFO 0X15 #define TX_REQ_INFO 0x15
#define TX_REQ_DEVCONTROL 0x51 #define TX_REQ_DEVCONTROL 0x51
#define ALL_FRAMES 0x80 #define ALL_FRAMES 0x80
#define SINGLE_FRAME 0x81 #define SINGLE_FRAME 0x81

4
tools/esp8266/hmSystem.h

@ -89,7 +89,9 @@ class HmSystem {
INVERTERTYPE *getInverterByPos(uint8_t pos, bool check = true) { INVERTERTYPE *getInverterByPos(uint8_t pos, bool check = true) {
DPRINTLN(DBG_VERBOSE, F("hmSystem.h:getInverterByPos")); DPRINTLN(DBG_VERBOSE, F("hmSystem.h:getInverterByPos"));
if((mInverter[pos].initialized && mInverter[pos].serial.u64 != 0ULL) || false == check) if(pos >= MAX_INVERTER)
return NULL;
else if((mInverter[pos].initialized && mInverter[pos].serial.u64 != 0ULL) || false == check)
return &mInverter[pos]; return &mInverter[pos];
else else
return NULL; return NULL;

20
tools/esp8266/html/api.js

@ -8,17 +8,19 @@ function toggle(name, hide) {
elm.classList.remove('hide'); elm.classList.remove('hide');
} }
function getAjax(url, ptr) { function getAjax(url, ptr, method="GET", json=null) {
var http = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
if(http != null) { if(xhr != null) {
http.open("GET", url, true); xhr.open(method, url, true);
http.onreadystatechange = p; xhr.onreadystatechange = p;
http.send(null); if("POST" == method)
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.send(json);
} }
function p() { function p() {
if(http.readyState == 4) { if(xhr.readyState == 4) {
if(null != http.responseText) if(null != xhr.responseText)
ptr(JSON.parse(http.responseText)); ptr(JSON.parse(xhr.responseText));
} }
} }
} }

52
tools/esp8266/html/serial.html

@ -12,6 +12,19 @@
<div class="serial"> <div class="serial">
<textarea id="serial" cols="80" rows="20" readonly></textarea><br/> <textarea id="serial" cols="80" rows="20" readonly></textarea><br/>
conntected: <span class="dot" id="connected"></span> Uptime: <span id="uptime"></span><input type="button" value="clear" class="btn" id="clear"/> <input type="button" value="autoscroll" class="btn" id="scroll"/> conntected: <span class="dot" id="connected"></span> Uptime: <span id="uptime"></span><input type="button" value="clear" class="btn" id="clear"/> <input type="button" value="autoscroll" class="btn" id="scroll"/>
<br/>
<br/>
<br/>
<br/>
<hr>
<h3>handle next buttons with care - test / debug only!!</h3>
<br/>
<input type="button" value="power limit 100%" class="btn" id="pwrlim2"/>
<input type="button" value="power limit 10%" class="btn" id="pwrlim"/>
<input type="button" value="Turn Off" class="btn" id="turnoff"/>
<input type="button" value="Turn On" class="btn" id="turnon"/><br/>
Ctrl result: <span id="result">n/a</span>
</div> </div>
</div> </div>
<div id="footer"> <div id="footer">
@ -70,6 +83,45 @@
} }
getAjax("/api/system", parseSys); getAjax("/api/system", parseSys);
// only for test
function ctrlCb(obj) {
var e = document.getElementById("result");
if(obj["success"])
e.innerHTML = "ok";
else
e.innerHTML = "Error: " + obj["error"];
}
document.getElementById("turnon").addEventListener("click", function() {
var obj = new Object();
obj.cmd = 0;
obj.tx_request = 81;
getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj));
});
document.getElementById("turnoff").addEventListener("click", function() {
var obj = new Object();
obj.cmd = 1;
obj.tx_request = 81;
getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj));
});
document.getElementById("pwrlim").addEventListener("click", function() {
var obj = new Object();
obj.cmd = 11;
obj.tx_request = 81;
obj.payload = [10, 1];
getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj));
});
document.getElementById("pwrlim2").addEventListener("click", function() {
var obj = new Object();
obj.cmd = 11;
obj.tx_request = 81;
obj.payload = [2000, 1];
getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj));
});
</script> </script>
</body> </body>
</html> </html>

8
tools/esp8266/html/visualization.html

@ -48,10 +48,10 @@
ch0.appendChild(sub); ch0.appendChild(sub);
switch(j) { switch(j) {
case 2: total[j] += val; break; // P_AC case 2: total[j] += val; break; // P_AC
case 6: total[j] += val; break; // YieldTotal case 6: total[j] += val; break; // YieldTotal
case 7: total[j] += val; break; // YieldDay case 7: total[j] += val; break; // YieldDay
case 8: total[j] += val; break; // P_DC case 8: total[j] += val; break; // P_DC
case 10: total[j] += val; break; // P_ACr case 10: total[j] += val; break; // P_ACr
} }
} }

45
tools/esp8266/web.cpp

@ -351,12 +351,10 @@ void web::showWebApi(AsyncWebServerRequest *request) {
uint8_t iv_id = response["inverter"]; uint8_t iv_id = response["inverter"];
uint8_t cmd = response["cmd"]; uint8_t cmd = response["cmd"];
Inverter<> *iv = mMain->mSys->getInverterByPos(iv_id); Inverter<> *iv = mMain->mSys->getInverterByPos(iv_id);
if (NULL != iv) if (NULL != iv) {
{ if (response["tx_request"] == (uint8_t)TX_REQ_INFO) {
if (response["tx_request"] == (uint8_t)TX_REQ_INFO)
{
// if the AlarmData is requested set the Alarm Index to the requested one // if the AlarmData is requested set the Alarm Index to the requested one
if (cmd == AlarmData || cmd == AlarmUpdate){ if (cmd == AlarmData || cmd == AlarmUpdate) {
// set the AlarmMesIndex for the request from user input // set the AlarmMesIndex for the request from user input
iv->alarmMesIndex = response["payload"]; iv->alarmMesIndex = response["payload"];
} }
@ -366,43 +364,32 @@ void web::showWebApi(AsyncWebServerRequest *request) {
} }
if (response["tx_request"] == (uint8_t)TX_REQ_DEVCONTROL) if (response["tx_request"] == (uint8_t)TX_REQ_DEVCONTROL) {
{ if (response["cmd"] == (uint8_t)ActivePowerContr) {
if (response["cmd"] == (uint8_t)ActivePowerContr)
{
uint16_t webapiPayload = response["payload"]; uint16_t webapiPayload = response["payload"];
uint16_t webapiPayload2 = response["payload2"]; uint16_t webapiPayload2 = response["payload2"];
if (webapiPayload > 0 && webapiPayload < 10000) 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 iv->powerLimit[1] = AbsolutNonPersistent; // payload will be seted temporary in Watt absolut
{ // if not set, set it to 0x0000 default
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")); DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("% via REST API"));
}
else else
{
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W 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"));
}
iv->devControlRequest = true; // queue it in the request loop iv->devControlRequest = true; // queue it in the request loop
} }
} }
if (response["cmd"] == (uint8_t)TurnOff){ if (response["cmd"] == (uint8_t)TurnOff) {
iv->devControlCmd = TurnOff; iv->devControlCmd = TurnOff;
iv->devControlRequest = true; // queue it in the request loop iv->devControlRequest = true; // queue it in the request loop
} }
if (response["cmd"] == (uint8_t)TurnOn){ if (response["cmd"] == (uint8_t)TurnOn) {
iv->devControlCmd = TurnOn; iv->devControlCmd = TurnOn;
iv->devControlRequest = true; // queue it in the request loop iv->devControlRequest = true; // queue it in the request loop
} }
} }
} }
request->send(200, "text/json", "{success:true}"); request->send(200, "text/json", "{success:true}");
@ -480,8 +467,10 @@ void web::serialCb(String msg) {
strncpy(&mSerialBuf[mSerialBufFill], mMain->getTimeStr().c_str(), 9); strncpy(&mSerialBuf[mSerialBufFill], mMain->getTimeStr().c_str(), 9);
mSerialBufFill += 9; mSerialBufFill += 9;
} }
else else {
mSerialBufFill = 0;
mEvts->send("webSerial, buffer overflow!", "serial", millis()); mEvts->send("webSerial, buffer overflow!", "serial", millis());
}
mSerialAddTime = false; mSerialAddTime = false;
} }
@ -493,7 +482,9 @@ void web::serialCb(String msg) {
strncpy(&mSerialBuf[mSerialBufFill], msg.c_str(), length); strncpy(&mSerialBuf[mSerialBufFill], msg.c_str(), length);
mSerialBufFill += length; mSerialBufFill += length;
} }
else else {
mSerialBufFill = 0;
mEvts->send("webSerial, buffer overflow!", "serial", millis()); mEvts->send("webSerial, buffer overflow!", "serial", millis());
}
} }

2
tools/esp8266/web.h

@ -12,7 +12,7 @@
#include "app.h" #include "app.h"
#include "webApi.h" #include "webApi.h"
#define WEB_SERIAL_BUF_SIZE 1024 #define WEB_SERIAL_BUF_SIZE 2048
class app; class app;
class webApi; class webApi;

165
tools/esp8266/webApi.cpp

@ -23,7 +23,9 @@ webApi::webApi(AsyncWebServer *srv, app *app, sysConfig_t *sysCfg, config_t *con
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void webApi::setup(void) { void webApi::setup(void) {
mSrv->on("/api", HTTP_GET, std::bind(&webApi::onApi, this, std::placeholders::_1)); mSrv->on("/api", HTTP_GET, std::bind(&webApi::onApi, this, std::placeholders::_1));
mSrv->on("/api", HTTP_POST, std::bind(&webApi::onApiPost, this, std::placeholders::_1)).onBody(
std::bind(&webApi::onApiPostBody, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
} }
@ -59,6 +61,44 @@ void webApi::onApi(AsyncWebServerRequest *request) {
} }
//-----------------------------------------------------------------------------
void webApi::onApiPost(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, "onApiPost");
}
//-----------------------------------------------------------------------------
void webApi::onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
DPRINTLN(DBG_VERBOSE, "onApiPostBody");
DynamicJsonDocument json(200);
AsyncJsonResponse* response = new AsyncJsonResponse(false, 200);
JsonObject root = response->getRoot();
DeserializationError err = deserializeJson(json, (const char *)data);
root[F("success")] = (err) ? false : true;
if(!err) {
String path = request->url().substring(5);
if(path == "ctrl")
root[F("success")] = setCtrl(json, root);
else {
root[F("success")] = false;
root[F("error")] = "Path not found: " + path;
}
}
else {
switch (err.code()) {
case DeserializationError::Ok: break;
case DeserializationError::InvalidInput: root[F("error")] = F("Invalid input"); break;
case DeserializationError::NoMemory: root[F("error")] = F("Not enough memory"); break;
default: root[F("error")] = F("Deserialization failed"); break;
}
}
response->setLength();
request->send(response);
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void webApi::getNotFound(JsonObject obj, String url) { void webApi::getNotFound(JsonObject obj, String url) {
JsonObject ep = obj.createNestedObject("avail_endpoints"); JsonObject ep = obj.createNestedObject("avail_endpoints");
@ -227,43 +267,100 @@ void webApi::getLive(JsonObject obj) {
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
iv = mApp->mSys->getInverterByPos(i); iv = mApp->mSys->getInverterByPos(i);
if(NULL != iv) { if(NULL != iv) {
JsonObject obj2 = invArr.createNestedObject(); if(iv->isLiveDataAssignment()) {
obj2[F("name")] = String(iv->name); JsonObject obj2 = invArr.createNestedObject();
obj2[F("channels")] = iv->channels; obj2[F("name")] = String(iv->name);
obj2[F("power_limit_read")] = iv->actPowerLimit; obj2[F("channels")] = iv->channels;
obj2[F("power_limit_active")] = NoPowerLimit != iv->powerLimit[1]; obj2[F("power_limit_read")] = iv->actPowerLimit;
obj2[F("last_alarm")] = String(iv->lastAlarmMsg); obj2[F("power_limit_active")] = NoPowerLimit != iv->powerLimit[1];
obj2[F("ts_last_success")] = iv->ts; obj2[F("last_alarm")] = String(iv->lastAlarmMsg);
obj2[F("ts_last_success")] = iv->ts;
JsonArray ch = obj2.createNestedArray("ch");
JsonArray ch0 = ch.createNestedArray(); JsonArray ch = obj2.createNestedArray("ch");
obj2[F("ch_names")][0] = "AC"; JsonArray ch0 = ch.createNestedArray();
for (uint8_t fld = 0; fld < 11; fld++) { obj2[F("ch_names")][0] = "AC";
pos = (iv->getPosByChFld(CH0, list[fld])); for (uint8_t fld = 0; fld < 11; fld++) {
ch0[fld] = (0xff != pos) ? iv->getValue(pos) : 0.0; pos = (iv->getPosByChFld(CH0, list[fld]));
obj[F("ch0_fld_units")][fld] = (0xff != pos) ? String(iv->getUnit(pos)) : F("n/a"); ch0[fld] = (0xff != pos) ? iv->getValue(pos) : 0.0;
obj[F("ch0_fld_names")][fld] = (0xff != pos) ? String(iv->getFieldName(pos)) : F("n/a"); obj[F("ch0_fld_units")][fld] = (0xff != pos) ? String(iv->getUnit(pos)) : F("n/a");
} obj[F("ch0_fld_names")][fld] = (0xff != pos) ? String(iv->getFieldName(pos)) : F("n/a");
}
for(uint8_t j = 1; j <= iv->channels; j ++) { for(uint8_t j = 1; j <= iv->channels; j ++) {
obj2[F("ch_names")][j] = String(iv->chName[j-1]); obj2[F("ch_names")][j] = String(iv->chName[j-1]);
JsonArray cur = ch.createNestedArray(); JsonArray cur = ch.createNestedArray();
for (uint8_t k = 0; k < 6; k++) { for (uint8_t k = 0; k < 6; k++) {
switch(k) { switch(k) {
default: pos = (iv->getPosByChFld(j, FLD_UDC)); break; default: pos = (iv->getPosByChFld(j, FLD_UDC)); break;
case 1: pos = (iv->getPosByChFld(j, FLD_IDC)); break; case 1: pos = (iv->getPosByChFld(j, FLD_IDC)); break;
case 2: pos = (iv->getPosByChFld(j, FLD_PDC)); break; case 2: pos = (iv->getPosByChFld(j, FLD_PDC)); break;
case 3: pos = (iv->getPosByChFld(j, FLD_YD)); break; case 3: pos = (iv->getPosByChFld(j, FLD_YD)); break;
case 4: pos = (iv->getPosByChFld(j, FLD_YT)); break; case 4: pos = (iv->getPosByChFld(j, FLD_YT)); break;
case 5: pos = (iv->getPosByChFld(j, FLD_IRR)); break; case 5: pos = (iv->getPosByChFld(j, FLD_IRR)); break;
} }
cur[k] = (0xff != pos) ? iv->getValue(pos) : 0.0; cur[k] = (0xff != pos) ? iv->getValue(pos) : 0.0;
if(1 == j) { if(1 == j) {
obj[F("fld_units")][k] = (0xff != pos) ? String(iv->getUnit(pos)) : F("n/a"); obj[F("fld_units")][k] = (0xff != pos) ? String(iv->getUnit(pos)) : F("n/a");
obj[F("fld_names")][k] = (0xff != pos) ? String(iv->getFieldName(pos)) : F("n/a"); obj[F("fld_names")][k] = (0xff != pos) ? String(iv->getFieldName(pos)) : F("n/a");
}
} }
} }
} }
} }
} }
} }
//-----------------------------------------------------------------------------
bool webApi::setCtrl(DynamicJsonDocument jsonIn, JsonObject jsonOut) {
uint8_t cmd = jsonIn[F("cmd")];
if(TX_REQ_DEVCONTROL == jsonIn[F("tx_request")]) {
DPRINTLN(DBG_INFO, F("devcontrol, cmd: 0x") + String(cmd, HEX));
if(ActivePowerContr == cmd) {
Inverter<> *iv = getInverter(jsonIn, jsonOut);
if(NULL != iv) {
JsonArray payload = jsonIn[F("payload")].as<JsonArray>();
iv->powerLimit[0] = payload[0];
iv->powerLimit[1] = payload[1];
}
}
else if(TurnOn == cmd) {
Inverter<> *iv = getInverter(jsonIn, jsonOut);
if(NULL != iv) {
iv->devControlCmd = TurnOn;
iv->devControlRequest = true;
}
else
return false;
}
else if(TurnOff == cmd) {
Inverter<> *iv = getInverter(jsonIn, jsonOut);
if(NULL != iv) {
iv->devControlCmd = TurnOff;
iv->devControlRequest = true;
}
else
return false;
}
else {
jsonOut["error"] = "unknown 'cmd' = " + String(cmd);
return false;
}
}
else {
jsonOut["error"] = "unknown 'tx_request'";
return false;
}
return true;
}
//-----------------------------------------------------------------------------
Inverter<> *webApi::getInverter(DynamicJsonDocument jsonIn, JsonObject jsonOut) {
uint8_t id = jsonIn[F("inverter")];
Inverter<> *iv = mApp->mSys->getInverterByPos(id);
if(NULL == iv)
jsonOut["error"] = F("inverter index to high: ") + String(id);
return iv;
}

6
tools/esp8266/webApi.h

@ -19,6 +19,8 @@ class webApi {
private: private:
void onApi(AsyncWebServerRequest *request); void onApi(AsyncWebServerRequest *request);
void onApiPost(AsyncWebServerRequest *request);
void onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total);
void getNotFound(JsonObject obj, String url); void getNotFound(JsonObject obj, String url);
void getSystem(JsonObject obj); void getSystem(JsonObject obj);
@ -34,6 +36,10 @@ class webApi {
void getSetup(JsonObject obj); void getSetup(JsonObject obj);
void getLive(JsonObject obj); void getLive(JsonObject obj);
bool setCtrl(DynamicJsonDocument jsonIn, JsonObject jsonOut);
Inverter<> *getInverter(DynamicJsonDocument jsonIn, JsonObject jsonOut);
AsyncWebServer *mSrv; AsyncWebServer *mSrv;
app *mApp; app *mApp;

Loading…
Cancel
Save