mirror of
https://github.com/lumapu/ahoy.git
synced 2025-05-11 07:56:39 +02:00
* 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
This commit is contained in:
parent
9e1b6be70e
commit
6bd7e01f1a
13 changed files with 404 additions and 379 deletions
|
@ -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__
|
||||||
|
|
|
@ -57,7 +57,7 @@ typedef enum {
|
||||||
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
|
||||||
|
@ -65,13 +65,8 @@ typedef enum { // ToDo: to be verified by field tests
|
||||||
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 {
|
||||||
|
|
|
@ -136,8 +136,7 @@ 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));
|
||||||
}
|
}
|
||||||
|
@ -155,8 +154,8 @@ class Inverter {
|
||||||
_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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -351,10 +351,8 @@ 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
|
||||||
|
@ -366,32 +364,21 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -24,6 +24,8 @@ 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,6 +267,7 @@ 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) {
|
||||||
|
if(iv->isLiveDataAssignment()) {
|
||||||
JsonObject obj2 = invArr.createNestedObject();
|
JsonObject obj2 = invArr.createNestedObject();
|
||||||
obj2[F("name")] = String(iv->name);
|
obj2[F("name")] = String(iv->name);
|
||||||
obj2[F("channels")] = iv->channels;
|
obj2[F("channels")] = iv->channels;
|
||||||
|
@ -267,3 +308,59 @@ void webApi::getLive(JsonObject obj) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -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…
Add table
Add a link
Reference in a new issue