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:
```C
typedef enum {
TurnOn = 0, // 0x00
TurnOff = 1, // 0x01
Restart = 2, // 0x02
Lock = 3, // 0x03
Unlock = 4, // 0x04
ActivePowerContr = 11, // 0x0b
ReactivePowerContr = 12, // 0x0c
PFSet = 13, // 0x0d
TurnOn = 0, // 0x00
TurnOff = 1, // 0x01
Restart = 2, // 0x02
Lock = 3, // 0x03
Unlock = 4, // 0x04
ActivePowerContr = 11, // 0x0b
ReactivePowerContr = 12, // 0x0c
PFSet = 13, // 0x0d
CleanState_LockAndAlarm = 20, // 0x14
SelfInspection = 40, // 0x28, self-inspection of grid-connected protection files
Init = 0xff
SelfInspection = 40, // 0x28, self-inspection of grid-connected protection files
Init = 0xff
} 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.
@ -215,8 +215,8 @@ Internally this values will be set for the second two bytes for MainCmd: 0x51 Su
typedef enum {
AbsolutNonPersistent = 0x0000, // 0
RelativNonPersistent = 0x0001, // 1
AbsolutPersistent = 0x0100, // 256
RelativPersistent = 0x0101 // 257
AbsolutPersistent = 0x0100, // 256
RelativPersistent = 0x0101 // 257
} PowerLimitControlType;
```

40
tools/esp8266/app.cpp

@ -381,23 +381,47 @@ void app::processPayload(bool retransmit) {
// MQTT send out
if(mMqttActive) {
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);
if (NULL != iv)
{
if (iv->isAvailable(mTimestamp))
{
for (uint8_t i = 0; i < iv->listLen; i++)
{
if (NULL != iv) {
if (iv->isAvailable(mTimestamp)) {
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(val, 10, "%.3f", iv->getValue(i));
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();
}
}
}
}
// 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__

90
tools/esp8266/defines.h

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

327
tools/esp8266/hmInverter.h

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

2
tools/esp8266/hmRadio.h

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

4
tools/esp8266/hmSystem.h

@ -89,7 +89,9 @@ class HmSystem {
INVERTERTYPE *getInverterByPos(uint8_t pos, bool check = true) {
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];
else
return NULL;

20
tools/esp8266/html/api.js

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

52
tools/esp8266/html/serial.html

@ -12,6 +12,19 @@
<div class="serial">
<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"/>
<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 id="footer">
@ -70,6 +83,45 @@
}
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>
</body>
</html>

8
tools/esp8266/html/visualization.html

@ -48,10 +48,10 @@
ch0.appendChild(sub);
switch(j) {
case 2: total[j] += val; break; // P_AC
case 6: total[j] += val; break; // YieldTotal
case 7: total[j] += val; break; // YieldDay
case 8: total[j] += val; break; // P_DC
case 2: total[j] += val; break; // P_AC
case 6: total[j] += val; break; // YieldTotal
case 7: total[j] += val; break; // YieldDay
case 8: total[j] += val; break; // P_DC
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 cmd = response["cmd"];
Inverter<> *iv = mMain->mSys->getInverterByPos(iv_id);
if (NULL != iv)
{
if (response["tx_request"] == (uint8_t)TX_REQ_INFO)
{
if (NULL != iv) {
if (response["tx_request"] == (uint8_t)TX_REQ_INFO) {
// 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
iv->alarmMesIndex = response["payload"];
}
@ -366,43 +364,32 @@ void web::showWebApi(AsyncWebServerRequest *request) {
}
if (response["tx_request"] == (uint8_t)TX_REQ_DEVCONTROL)
{
if (response["cmd"] == (uint8_t)ActivePowerContr)
{
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)
{
if (webapiPayload > 0 && webapiPayload < 10000) {
iv->devControlCmd = ActivePowerContr;
iv->powerLimit[0] = webapiPayload;
if (webapiPayload2 > 0)
{
iv->powerLimit[1] = webapiPayload2; // dev option, no sanity check
}
else
{ // if not set, set it to 0x0000 default
iv->powerLimit[1] = AbsolutNonPersistent; // payload will be seted temporay in Watt absolut
}
else // if not set, set it to 0x0000 default
iv->powerLimit[1] = AbsolutNonPersistent; // payload will be seted temporary in Watt absolut
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("W via REST API"));
}
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->devControlRequest = true; // queue it in the request loop
}
if (response["cmd"] == (uint8_t)TurnOn){
}
if (response["cmd"] == (uint8_t)TurnOn) {
iv->devControlCmd = TurnOn;
iv->devControlRequest = true; // queue it in the request loop
}
}
}
}
request->send(200, "text/json", "{success:true}");
@ -480,8 +467,10 @@ void web::serialCb(String msg) {
strncpy(&mSerialBuf[mSerialBufFill], mMain->getTimeStr().c_str(), 9);
mSerialBufFill += 9;
}
else
else {
mSerialBufFill = 0;
mEvts->send("webSerial, buffer overflow!", "serial", millis());
}
mSerialAddTime = false;
}
@ -493,7 +482,9 @@ void web::serialCb(String msg) {
strncpy(&mSerialBuf[mSerialBufFill], msg.c_str(), length);
mSerialBufFill += length;
}
else
else {
mSerialBufFill = 0;
mEvts->send("webSerial, buffer overflow!", "serial", millis());
}
}

2
tools/esp8266/web.h

@ -12,7 +12,7 @@
#include "app.h"
#include "webApi.h"
#define WEB_SERIAL_BUF_SIZE 1024
#define WEB_SERIAL_BUF_SIZE 2048
class app;
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) {
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) {
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 ++) {
iv = mApp->mSys->getInverterByPos(i);
if(NULL != iv) {
JsonObject obj2 = invArr.createNestedObject();
obj2[F("name")] = String(iv->name);
obj2[F("channels")] = iv->channels;
obj2[F("power_limit_read")] = iv->actPowerLimit;
obj2[F("power_limit_active")] = NoPowerLimit != iv->powerLimit[1];
obj2[F("last_alarm")] = String(iv->lastAlarmMsg);
obj2[F("ts_last_success")] = iv->ts;
JsonArray ch = obj2.createNestedArray("ch");
JsonArray ch0 = ch.createNestedArray();
obj2[F("ch_names")][0] = "AC";
for (uint8_t fld = 0; fld < 11; fld++) {
pos = (iv->getPosByChFld(CH0, list[fld]));
ch0[fld] = (0xff != pos) ? iv->getValue(pos) : 0.0;
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");
}
if(iv->isLiveDataAssignment()) {
JsonObject obj2 = invArr.createNestedObject();
obj2[F("name")] = String(iv->name);
obj2[F("channels")] = iv->channels;
obj2[F("power_limit_read")] = iv->actPowerLimit;
obj2[F("power_limit_active")] = NoPowerLimit != iv->powerLimit[1];
obj2[F("last_alarm")] = String(iv->lastAlarmMsg);
obj2[F("ts_last_success")] = iv->ts;
JsonArray ch = obj2.createNestedArray("ch");
JsonArray ch0 = ch.createNestedArray();
obj2[F("ch_names")][0] = "AC";
for (uint8_t fld = 0; fld < 11; fld++) {
pos = (iv->getPosByChFld(CH0, list[fld]));
ch0[fld] = (0xff != pos) ? iv->getValue(pos) : 0.0;
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 ++) {
obj2[F("ch_names")][j] = String(iv->chName[j-1]);
JsonArray cur = ch.createNestedArray();
for (uint8_t k = 0; k < 6; k++) {
switch(k) {
default: pos = (iv->getPosByChFld(j, FLD_UDC)); break;
case 1: pos = (iv->getPosByChFld(j, FLD_IDC)); break;
case 2: pos = (iv->getPosByChFld(j, FLD_PDC)); break;
case 3: pos = (iv->getPosByChFld(j, FLD_YD)); break;
case 4: pos = (iv->getPosByChFld(j, FLD_YT)); break;
case 5: pos = (iv->getPosByChFld(j, FLD_IRR)); break;
}
cur[k] = (0xff != pos) ? iv->getValue(pos) : 0.0;
if(1 == j) {
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");
for(uint8_t j = 1; j <= iv->channels; j ++) {
obj2[F("ch_names")][j] = String(iv->chName[j-1]);
JsonArray cur = ch.createNestedArray();
for (uint8_t k = 0; k < 6; k++) {
switch(k) {
default: pos = (iv->getPosByChFld(j, FLD_UDC)); break;
case 1: pos = (iv->getPosByChFld(j, FLD_IDC)); break;
case 2: pos = (iv->getPosByChFld(j, FLD_PDC)); break;
case 3: pos = (iv->getPosByChFld(j, FLD_YD)); break;
case 4: pos = (iv->getPosByChFld(j, FLD_YT)); break;
case 5: pos = (iv->getPosByChFld(j, FLD_IRR)); break;
}
cur[k] = (0xff != pos) ? iv->getValue(pos) : 0.0;
if(1 == j) {
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");
}
}
}
}
}
}
}
//-----------------------------------------------------------------------------
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:
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 getSystem(JsonObject obj);
@ -34,6 +36,10 @@ class webApi {
void getSetup(JsonObject obj);
void getLive(JsonObject obj);
bool setCtrl(DynamicJsonDocument jsonIn, JsonObject jsonOut);
Inverter<> *getInverter(DynamicJsonDocument jsonIn, JsonObject jsonOut);
AsyncWebServer *mSrv;
app *mApp;

Loading…
Cancel
Save