Browse Source

* new structure

* slim definitions of fields and units
* prepared multi inverter setup (not finished now)
pull/8/head
lumapu 3 years ago
parent
commit
d0731f7065
  1. 279
      tools/esp8266/CircularBuffer.h
  2. 278
      tools/esp8266/app.cpp
  3. 38
      tools/esp8266/app.h
  4. 38
      tools/esp8266/debug.h
  5. 14
      tools/esp8266/defines.h
  6. 130
      tools/esp8266/eep.cpp
  7. 141
      tools/esp8266/eep.h
  8. 10
      tools/esp8266/esp8266.ino
  9. 87
      tools/esp8266/hm1200Decode.h
  10. 111
      tools/esp8266/hmInverters.h
  11. 104
      tools/esp8266/hmRadio.h
  12. 149
      tools/esp8266/hmSystem.h
  13. 2
      tools/esp8266/html/h/setup_html.h
  14. 2
      tools/esp8266/html/h/style_css.h
  15. 67
      tools/esp8266/html/setup.html
  16. 65
      tools/esp8266/html/style.css
  17. 2
      tools/esp8266/mqtt.h

279
tools/esp8266/CircularBuffer.h

@ -1,21 +1,21 @@
/* /*
CircularBuffer - An Arduino circular buffering library for arbitrary types. CircularBuffer - An Arduino circular buffering library for arbitrary types.
Created by Ivo Pullens, Emmission, 2014 -- www.emmission.nl Created by Ivo Pullens, Emmission, 2014 -- www.emmission.nl
This library is free software; you can redistribute it and/or This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details. Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
#ifndef CircularBuffer_h #ifndef CircularBuffer_h
@ -26,133 +26,132 @@
#define RESTORE_IRQ interrupts() #define RESTORE_IRQ interrupts()
#else #else
#define DISABLE_IRQ \ #define DISABLE_IRQ \
uint8_t sreg = SREG; \ uint8_t sreg = SREG; \
cli(); cli();
#define RESTORE_IRQ \ #define RESTORE_IRQ \
SREG = sreg; SREG = sreg;
#endif #endif
template <class T> class CircularBuffer template <class BUFFERTYPE, uint8_t BUFFERSIZE>
{ class CircularBuffer {
public:
/** Constructor typedef BUFFERTYPE BufferType;
* @param buffer Preallocated buffer of at least size records. BufferType Buffer[BUFFERSIZE];
* @param size Number of records available in the buffer.
*/ public:
CircularBuffer(T* buffer, const uint8_t size ) CircularBuffer() : m_buff(Buffer) {
: m_size(size), m_buff(buffer) m_size = BUFFERSIZE;
{ clear();
clear(); }
}
/** Clear all entries in the circular buffer. */
/** Clear all entries in the circular buffer. */ void clear(void)
void clear(void) {
{ m_front = 0;
m_front = 0; m_fill = 0;
m_fill = 0; }
}
/** Test if the circular buffer is empty */
/** Test if the circular buffer is empty */ inline bool empty(void) const
inline bool empty(void) const {
{ return !m_fill;
return !m_fill; }
}
/** Return the number of records stored in the buffer */
/** Return the number of records stored in the buffer */ inline uint8_t available(void) const
inline uint8_t available(void) const {
{ return m_fill;
return m_fill; }
}
/** Test if the circular buffer is full */
/** Test if the circular buffer is full */ inline bool full(void) const
inline bool full(void) const {
{ return m_fill == m_size;
return m_fill == m_size; }
}
/** Aquire record on front of the buffer, for writing.
/** Aquire record on front of the buffer, for writing. * After filling the record, it has to be pushed to actually
* After filling the record, it has to be pushed to actually * add it to the buffer.
* add it to the buffer. * @return Pointer to record, or NULL when buffer is full.
* @return Pointer to record, or NULL when buffer is full. */
*/ BUFFERTYPE* getFront(void) const
T* getFront(void) const {
{ DISABLE_IRQ;
DISABLE_IRQ; BUFFERTYPE* f = NULL;
T* f = NULL; if (!full())
if (!full()) f = get(m_front);
f = get(m_front); RESTORE_IRQ;
RESTORE_IRQ; return f;
return f; }
}
/** Push record to front of the buffer
/** Push record to front of the buffer * @param record Record to push. If record was aquired previously (using getFront) its
* @param record Record to push. If record was aquired previously (using getFront) its * data will not be copied as it is already present in the buffer.
* data will not be copied as it is already present in the buffer. * @return True, when record was pushed successfully.
* @return True, when record was pushed successfully. */
*/ bool pushFront(BUFFERTYPE* record)
bool pushFront(T* record) {
{ bool ok = false;
bool ok = false; DISABLE_IRQ;
DISABLE_IRQ; if (!full())
if (!full()) {
{ BUFFERTYPE* f = get(m_front);
T* f = get(m_front); if (f != record)
if (f != record) *f = *record;
*f = *record; m_front = (m_front+1) % m_size;
m_front = (m_front+1) % m_size; m_fill++;
m_fill++; ok = true;
ok = true; }
} RESTORE_IRQ;
RESTORE_IRQ; return ok;
return ok; }
}
/** Aquire record on back of the buffer, for reading.
/** Aquire record on back of the buffer, for reading. * After reading the record, it has to be pop'ed to actually
* After reading the record, it has to be pop'ed to actually * remove it from the buffer.
* remove it from the buffer. * @return Pointer to record, or NULL when buffer is empty.
* @return Pointer to record, or NULL when buffer is empty. */
*/ BUFFERTYPE* getBack(void) const
T* getBack(void) const {
{ BUFFERTYPE* b = NULL;
T* b = NULL; DISABLE_IRQ;
DISABLE_IRQ; if (!empty())
if (!empty()) b = get(back());
b = get(back()); RESTORE_IRQ;
RESTORE_IRQ; return b;
return b; }
}
/** Remove record from back of the buffer.
/** Remove record from back of the buffer. * @return True, when record was pop'ed successfully.
* @return True, when record was pop'ed successfully. */
*/ bool popBack(void)
bool popBack(void) {
{ bool ok = false;
bool ok = false; DISABLE_IRQ;
DISABLE_IRQ; if (!empty())
if (!empty()) {
{ m_fill--;
m_fill--; ok = true;
ok = true; }
} RESTORE_IRQ;
RESTORE_IRQ; return ok;
return ok; }
}
protected:
protected: inline BUFFERTYPE * get(const uint8_t idx) const
inline T * get(const uint8_t idx) const {
{ return &(m_buff[idx]);
return &(m_buff[idx]); }
} inline uint8_t back(void) const
inline uint8_t back(void) const {
{ return (m_front - m_fill + m_size) % m_size;
return (m_front - m_fill + m_size) % m_size; }
}
uint8_t m_size; // Total number of records that can be stored in the buffer.
const uint8_t m_size; // Total number of records that can be stored in the buffer. BUFFERTYPE* const m_buff;
T* const m_buff; // Ptr to buffer holding all records. volatile uint8_t m_front; // Index of front element (not pushed yet).
volatile uint8_t m_front; // Index of front element (not pushed yet). volatile uint8_t m_fill; // Amount of records currently pushed.
volatile uint8_t m_fill; // Amount of records currently pushed.
}; };
#endif // CircularBuffer_h #endif // CircularBuffer_h

278
tools/esp8266/app.cpp

@ -4,12 +4,11 @@
#include "html/h/hoymiles_html.h" #include "html/h/hoymiles_html.h"
extern String setup_html; extern String setup_html;
#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL)
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
app::app() : Main() { app::app() : Main() {
mHoymiles = new hoymiles();
mDecoder = new hm1200Decode();
mBufCtrl = new CircularBuffer(mBuffer, PACKET_BUFFER_SIZE);
mSendCnt = 0; mSendCnt = 0;
mSendTicker = new Ticker(); mSendTicker = new Ticker();
mFlagSend = false; mFlagSend = false;
@ -19,6 +18,8 @@ app::app() : Main() {
memset(mCmds, 0, sizeof(uint32_t)); memset(mCmds, 0, sizeof(uint32_t));
memset(mChannelStat, 0, sizeof(uint32_t)); memset(mChannelStat, 0, sizeof(uint32_t));
mSys = new HmSystemType();
} }
@ -42,10 +43,12 @@ void app::setup(const char *ssid, const char *pwd, uint32_t timeout) {
if(mSettingsValid) { if(mSettingsValid) {
uint16_t interval; uint16_t interval;
uint64_t invSerial;
// hoymiles // hoymiles
mEep->read(ADDR_INV0_ADDR, mHoymiles->mAddrBytes, INV_ADDR_LEN); mEep->read(ADDR_INV0_ADDR, &invSerial);
mEep->read(ADDR_INV_INTERVAL, &interval); mEep->read(ADDR_INV_INTERVAL, &interval);
mSys->addInverter("HM1200", invSerial, INV_TYPE_HM1200);
if(interval < 1000) if(interval < 1000)
interval = 1000; interval = 1000;
@ -74,10 +77,6 @@ void app::setup(const char *ssid, const char *pwd, uint32_t timeout) {
mMqtt.sendMsg("version", mVersion); mMqtt.sendMsg("version", mVersion);
} }
else {
memset(mHoymiles->mAddrBytes, 0, 6);
}
mHoymiles->serial2RadioId();
initRadio(); initRadio();
@ -90,26 +89,32 @@ void app::setup(const char *ssid, const char *pwd, uint32_t timeout) {
void app::loop(void) { void app::loop(void) {
Main::loop(); Main::loop();
if(!mBufCtrl->empty()) { if(!mSys->BufCtrl.empty()) {
uint8_t len, rptCnt; uint8_t len, rptCnt;
NRF24_packet_t *p = mBufCtrl->getBack(); packet_t *p = mSys->BufCtrl.getBack();
//dumpBuf("RAW ", p->packet, MAX_RF_PAYLOAD_SIZE);
//mHoymiles->dumpBuf("RAW ", p->packet, MAX_RF_PAYLOAD_SIZE); if(mSys->Radio.checkCrc(p->packet, &len, &rptCnt)) {
if(mHoymiles->checkCrc(p->packet, &len, &rptCnt)) {
// process buffer only on first occurrence // process buffer only on first occurrence
if((0 != len) && (0 == rptCnt)) { if((0 != len) && (0 == rptCnt)) {
//Serial.println("CMD " + String(p->packet[11], HEX)); //Serial.println("CMD " + String(*cmd, HEX));
//mHoymiles->dumpBuf("Payload ", p->packet, len); //dumpBuf("Payload ", p->packet, len);
mDecoder->convert(&p->packet[11], len); uint8_t *cmd = &p->packet[11];
inverter_t *iv = mSys->findInverter(&p->packet[3]);
if(NULL != iv) {
for(uint8_t i = 0; i < iv->listLen; i++) {
if(iv->assign[i].cmdId == *cmd)
mSys->addValue(iv, i, &p->packet[11]);
}
}
if(p->packet[11] == 0x01) mCmds[0]++; if(*cmd == 0x01) mCmds[0]++;
else if(p->packet[11] == 0x02) mCmds[1]++; else if(*cmd == 0x02) mCmds[1]++;
else if(p->packet[11] == 0x03) mCmds[2]++; else if(*cmd == 0x03) mCmds[2]++;
else if(p->packet[11] == 0x81) mCmds[3]++; else if(*cmd == 0x81) mCmds[3]++;
else if(p->packet[11] == 0x84) mCmds[4]++; else if(*cmd == 0x84) mCmds[4]++;
else mCmds[5]++; else mCmds[5]++;
if(p->sendCh == 23) mChannelStat[0]++; if(p->sendCh == 23) mChannelStat[0]++;
else if(p->sendCh == 40) mChannelStat[1]++; else if(p->sendCh == 40) mChannelStat[1]++;
@ -117,94 +122,72 @@ void app::loop(void) {
else mChannelStat[3]++; else mChannelStat[3]++;
} }
} }
mBufCtrl->popBack(); mSys->BufCtrl.popBack();
} }
if(mFlagSend) { if(mFlagSend) {
mFlagSend = false; mFlagSend = false;
uint8_t size = 0; uint8_t size = 0;
//if((mSendCnt % 6) == 0) inverter_t *inv = mSys->getInverterByPos(0);
size = mHoymiles->getTimePacket(mSendBuf, mTimestamp); size = mSys->Radio.getTimePacket(&inv->radioId.u64, mSendBuf, mTimestamp);
/*else if((mSendCnt % 6) == 1)
size = mHoymiles->getCmdPacket(mSendBuf, 0x15, 0x81);
else if((mSendCnt % 6) == 2)
size = mHoymiles->getCmdPacket(mSendBuf, 0x15, 0x80);
else if((mSendCnt % 6) == 3)
size = mHoymiles->getCmdPacket(mSendBuf, 0x15, 0x83);
else if((mSendCnt % 6) == 4)
size = mHoymiles->getCmdPacket(mSendBuf, 0x15, 0x82);
else if((mSendCnt % 6) == 5)
size = mHoymiles->getCmdPacket(mSendBuf, 0x15, 0x84);*/
//Serial.println("sent packet: #" + String(mSendCnt)); //Serial.println("sent packet: #" + String(mSendCnt));
//dumpBuf(mSendBuf, size); //dumpBuf(mSendBuf, size);
sendPacket(mSendBuf, size); sendPacket(inv, mSendBuf, size);
mSendCnt++; mSendCnt++;
} }
// mqtt // mqtt
mMqtt.loop(); //mMqtt.loop();
if(mMqttEvt) { if(mMqttEvt) {
mMqttEvt = false; mMqttEvt = false;
mMqtt.isConnected(true); /*mMqtt.isConnected(true);
char topic[20], val[10]; char topic[20], val[10];
for(uint8_t i = 0; i < 4; i++) { for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
for(uint8_t j = 0; j < 5; j++) { inverter_t *iv = mSys->getInverterByPos(id);
switch(j) { if(NULL != iv) {
default: for(uint8_t i = 0; i < iv->listLen; i++) {
sprintf(topic, "ch%d/%s", i, "voltage"); if(0.0f != mSys->getValue(iv, i)) {
sprintf(val, "%.3f", mDecoder->mData.ch_dc[i/2].u); sprintf(topic, "%s/ch%d/%s", iv->name, iv->assign[i].ch, fields[iv->assign[i].fieldId]);
break; sprintf(val, "%.3f", mSys->getValue(iv, i));
case 1: mMqtt.sendMsg(topic, val);
sprintf(topic, "ch%d/%s", i, "current"); delay(10);
sprintf(val, "%.3f", mDecoder->mData.ch_dc[i].i); }
break;
case 2:
sprintf(topic, "ch%d/%s", i, "power");
sprintf(val, "%.3f", mDecoder->mData.ch_dc[i].p);
break;
case 3:
sprintf(topic, "ch%d/%s", i, "yield_day");
sprintf(val, "%.3f", (double)mDecoder->mData.ch_dc[i].y_d);
break;
case 4:
sprintf(topic, "ch%d/%s", i, "yield");
sprintf(val, "%.3f", mDecoder->mData.ch_dc[i].y_t);
break;
} }
if(0 != strncmp("0.000", val, 5)) { }
mMqtt.sendMsg(topic, val); }*/
delay(10);
// Serial debug
char topic[20], val[10];
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
inverter_t *iv = mSys->getInverterByPos(id);
if(NULL != iv) {
for(uint8_t i = 0; i < iv->listLen; i++) {
//if(0.0f != mSys->getValue(iv, i)) {
sprintf(topic, "%s/ch%d/%s", iv->name, iv->assign[i].ch, mSys->getFieldName(iv, i));
sprintf(val, "%.3f %s", mSys->getValue(iv, i), mSys->getUnit(iv, i));
Serial.println(String(topic) + ": " + String(val));
//}
} }
} }
} }
sprintf(val, "%.3f", mDecoder->mData.ch_ac.u);
mMqtt.sendMsg("ac/voltage", val);
delay(10);
sprintf(val, "%.3f", mDecoder->mData.ch_ac.i);
mMqtt.sendMsg("ac/current", val);
delay(10);
sprintf(val, "%.3f", mDecoder->mData.temp);
mMqtt.sendMsg("temperature", val);
delay(10);
} }
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void app::handleIntr(void) { void app::handleIntr(void) {
uint8_t lostCnt = 0, pipe, len; uint8_t pipe, len;
NRF24_packet_t *p; packet_t *p;
DISABLE_IRQ; DISABLE_IRQ;
while(mRadio->available(&pipe)) { while(mRadio->available(&pipe)) {
if(!mBufCtrl->full()) { if(!mSys->BufCtrl.full()) {
p = mBufCtrl->getFront(); p = mSys->BufCtrl.getFront();
memset(p->packet, 0xcc, MAX_RF_PAYLOAD_SIZE); memset(p->packet, 0xcc, MAX_RF_PAYLOAD_SIZE);
p->sendCh = mSendChannel; p->sendCh = mSendChannel;
len = mRadio->getPayloadSize(); len = mRadio->getPayloadSize();
@ -212,13 +195,10 @@ void app::handleIntr(void) {
len = MAX_RF_PAYLOAD_SIZE; len = MAX_RF_PAYLOAD_SIZE;
mRadio->read(p->packet, len); mRadio->read(p->packet, len);
mBufCtrl->pushFront(p); mSys->BufCtrl.pushFront(p);
lostCnt = 0;
} }
else { else {
bool tx_ok, tx_fail, rx_ready; bool tx_ok, tx_fail, rx_ready;
if(lostCnt < 255)
lostCnt++;
mRadio->whatHappened(tx_ok, tx_fail, rx_ready); // reset interrupt status mRadio->whatHappened(tx_ok, tx_fail, rx_ready); // reset interrupt status
mRadio->flush_rx(); // drop the packet mRadio->flush_rx(); // drop the packet
} }
@ -254,27 +234,27 @@ void app::initRadio(void) {
Serial.println("Radio Config:"); Serial.println("Radio Config:");
mRadio->printPrettyDetails(); mRadio->printPrettyDetails();
mSendChannel = mHoymiles->getDefaultChannel(); mSendChannel = mSys->Radio.getDefaultChannel();
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void app::sendPacket(uint8_t buf[], uint8_t len) { void app::sendPacket(inverter_t *inv, uint8_t buf[], uint8_t len) {
DISABLE_IRQ; DISABLE_IRQ;
mRadio->stopListening(); mRadio->stopListening();
#ifdef CHANNEL_HOP #ifdef CHANNEL_HOP
//if(mSendCnt % 6 == 0) //if(mSendCnt % 6 == 0)
mSendChannel = mHoymiles->getNxtChannel(); mSendChannel = mSys->Radio.getNxtChannel();
//else //else
// mSendChannel = mHoymiles->getLastChannel(); // mSendChannel = mSys->Radio.getLastChannel();
#else #else
mSendChannel = mHoymiles->getDefaultChannel(); mSendChannel = mSys->Radio.getDefaultChannel();
#endif #endif
mRadio->setChannel(mSendChannel); mRadio->setChannel(mSendChannel);
//Serial.println("CH: " + String(mSendChannel)); //Serial.println("CH: " + String(mSendChannel));
mRadio->openWritingPipe(mHoymiles->mRadioId); mRadio->openWritingPipe(inv->radioId.u64);
mRadio->setCRCLength(RF24_CRC_16); mRadio->setCRCLength(RF24_CRC_16);
mRadio->enableDynamicPayloads(); mRadio->enableDynamicPayloads();
mRadio->setAutoAck(true); mRadio->setAutoAck(true);
@ -329,13 +309,32 @@ void app::showSetup(void) {
// PWD will be left at the default value (for protection) // PWD will be left at the default value (for protection)
// -> the PWD will only be changed if it does not match the placeholder "{PWD}" // -> the PWD will only be changed if it does not match the placeholder "{PWD}"
char addr[20] = {0};
sprintf(addr, "%02X:%02X:%02X:%02X:%02X:%02X", mHoymiles->mAddrBytes[0], mHoymiles->mAddrBytes[1], mHoymiles->mAddrBytes[2], mHoymiles->mAddrBytes[3], mHoymiles->mAddrBytes[4], mHoymiles->mAddrBytes[5]);
html.replace("{INV0_ADDR}", String(addr));
html.replace("{DEVICE}", String(mDeviceName)); html.replace("{DEVICE}", String(mDeviceName));
html.replace("{VERSION}", String(mVersion)); html.replace("{VERSION}", String(mVersion));
String inv;
inverter_t *pInv;
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
pInv = mSys->getInverterByPos(i);
inv += "<p class=\"subdes\">Inverter "+ String(i) + "</p>";
inv += "<label for=\"inv" + String(i) + "Addr\">Address</label>";
inv += "<input type=\"text\" class=\"text\" name=\"inv" + String(i) + "Addr\" value=\"";
inv += (NULL != pInv) ? String(mSys->getSerial(pInv), HEX) : "";
inv += "\"/>";
inv += "<label for=\"inv" + String(i) + "Name\">Name</label>";
inv += "<input type=\"text\" class=\"text\" name=\"inv" + String(i) + "Name\" value=\"";
inv += (NULL != pInv) ? String(pInv->name) : "";
inv += "\"/>";
inv += "<label for=\"inv" + String(i) + "Type\">Type</label>";
inv += "<input type=\"text\" class=\"text\" name=\"inv" + String(i) + "Name\" value=\"";
inv += (NULL != pInv) ? String(pInv->type) : "";
inv += "\"/>";
}
html.replace("{INVERTERS}", String(inv));
if(mSettingsValid) { if(mSettingsValid) {
mEep->read(ADDR_INV_INTERVAL, &interval); mEep->read(ADDR_INV_INTERVAL, &interval);
html.replace("{INV_INTERVAL}", String(interval)); html.replace("{INV_INTERVAL}", String(interval));
@ -402,28 +401,46 @@ void app::showHoymiles(void) {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void app::showLiveData(void) { void app::showLiveData(void) {
String modHtml = ""; String modHtml = "<pre>";
String unit[5] = {"V", "A", "W", "Wh", "kWh"}; char topic[20], val[10];
String info[5] = {"VOLTAGE", "CURRENT", "POWER", "YIELD DAY", "YIELD"}; for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
inverter_t *iv = mSys->getInverterByPos(id);
for(uint8_t i = 0; i < 4; i++) { if(NULL != iv) {
modHtml += "<div class=\"module\"><span class=\"header\">CHANNEL " + String(i) + "</span>"; /*uint8_t modNum;
for(uint8_t j = 0; j < 5; j++) { switch(iv->type) {
modHtml += "<span class=\"value\">"; default: modNum = 1; break;
switch(j) { case INV_TYPE_HM600: modNum = 2; break;
default: modHtml += String(mDecoder->mData.ch_dc[i/2].u); break; case INV_TYPE_HM1200: modNum = 4; break;
case 1: modHtml += String(mDecoder->mData.ch_dc[i].i); break; }
case 2: modHtml += String(mDecoder->mData.ch_dc[i].p); break;
case 3: modHtml += String(mDecoder->mData.ch_dc[i].y_d); break; for(uint8_t mod = 1; mod <= modNum; mod ++) {
case 4: modHtml += String(mDecoder->mData.ch_dc[i].y_t); break; modHtml += "<div class=\"module\"><span class=\"header\">CHANNEL " + String(i) + "</span>";
for(uint8_t j = 0; j < 5; j++) {
modHtml += "<span class=\"value\">";
switch(j) {
default: modHtml += String(mDecoder->mData.ch_dc[i/2].u); break;
case 1: modHtml += String(mDecoder->mData.ch_dc[i].i); break;
case 2: modHtml += String(mDecoder->mData.ch_dc[i].p); break;
case 3: modHtml += String(mDecoder->mData.ch_dc[i].y_d); break;
case 4: modHtml += String(mDecoder->mData.ch_dc[i].y_t); break;
}
modHtml += "<span class=\"unit\">" + unit[j] + "</span></span>";
modHtml += "<span class=\"info\">" + info[j] + "</span>";
}
modHtml += "</div>";
}*/
for(uint8_t i = 0; i < iv->listLen; i++) {
sprintf(topic, "%s/ch%d/%s", iv->name, iv->assign[i].ch, mSys->getFieldName(iv, i));
sprintf(val, "%.3f %s", mSys->getValue(iv, i), mSys->getUnit(iv, i));
modHtml += String(topic) + ": " + String(val) + "\n";
} }
modHtml += "<span class=\"unit\">" + unit[j] + "</span></span>";
modHtml += "<span class=\"info\">" + info[j] + "</span>";
} }
modHtml += "</div>";
} }
modHtml += "</pre>";
mWeb->send(200, "text/html", modHtml); mWeb->send(200, "text/html", modHtml);
} }
@ -443,20 +460,24 @@ void app::saveValues(bool webSend = true) {
if(mWeb->args() > 0) { if(mWeb->args() > 0) {
char *p; char *p;
char addr[20] = {0}; char buf[20] = {0};
uint8_t i = 0; uint8_t i = 0;
uint16_t interval; uint16_t interval;
// hoymiles // inverter
memset(mHoymiles->mAddrBytes, 0, 6); serial_u addr;
mWeb->arg("inv0Addr").toCharArray(addr, 20); mWeb->arg("inv0Addr").toCharArray(buf, 20);
p = strtok(addr, ":"); addr.u64 = Serial2u64(buf);
while(NULL != p) { mSys->updateSerial(mSys->getInverterByPos(0), addr.u64);
mHoymiles->mAddrBytes[i++] = strtol(p, NULL, 16);
p = strtok(NULL, ":"); for(uint8_t i = 0; i < 8; i++) {
Serial.print(String(addr.b[i], HEX) + " ");
} }
Serial.println();
Serial.println("addr: " + String(addr.u64, HEX));
interval = mWeb->arg("invInterval").toInt(); interval = mWeb->arg("invInterval").toInt();
mEep->write(ADDR_INV0_ADDR, mHoymiles->mAddrBytes, INV_ADDR_LEN); mEep->write(ADDR_INV0_ADDR, addr.u64);
mEep->write(ADDR_INV_INTERVAL, interval); mEep->write(ADDR_INV_INTERVAL, interval);
@ -465,9 +486,9 @@ void app::saveValues(bool webSend = true) {
char mqttUser[MQTT_USER_LEN]; char mqttUser[MQTT_USER_LEN];
char mqttPwd[MQTT_PWD_LEN]; char mqttPwd[MQTT_PWD_LEN];
char mqttTopic[MQTT_TOPIC_LEN]; char mqttTopic[MQTT_TOPIC_LEN];
mWeb->arg("mqttAddr").toCharArray(addr, 20); mWeb->arg("mqttAddr").toCharArray(buf, 20);
i = 0; i = 0;
p = strtok(addr, "."); p = strtok(buf, ".");
while(NULL != p) { while(NULL != p) {
mqttAddr[i++] = atoi(p); mqttAddr[i++] = atoi(p);
p = strtok(NULL, "."); p = strtok(NULL, ".");
@ -495,14 +516,3 @@ void app::saveValues(bool webSend = true) {
"<p>Error while saving</p></body></html>"); "<p>Error while saving</p></body></html>");
} }
} }
//-----------------------------------------------------------------------------
void app::dumpBuf(uint8_t buf[], uint8_t len) {
for(uint8_t i = 0; i < len; i ++) {
if((i % 8 == 0) && (i != 0))
Serial.println();
Serial.print(String(buf[i], HEX) + " ");
}
Serial.println();
}

38
tools/esp8266/app.h

@ -8,12 +8,15 @@
#include "main.h" #include "main.h"
#include "CircularBuffer.h" #include "CircularBuffer.h"
#include "hoymiles.h" #include "hmSystem.h"
#include "hm1200Decode.h"
#include "mqtt.h" #include "mqtt.h"
typedef HmRadio<RF24_CE_PIN, RF24_CS_PIN, RF24_IRQ_PIN> RadioType;
typedef CircularBuffer<packet_t, PACKET_BUFFER_SIZE> BufferType;
typedef HmSystem<RadioType, BufferType, MAX_NUM_INVERTERS, double> HmSystemType;
class app : public Main { class app : public Main {
public: public:
app(); app();
@ -25,7 +28,7 @@ class app : public Main {
private: private:
void initRadio(void); void initRadio(void);
void sendPacket(uint8_t data[], uint8_t length); void sendPacket(inverter_t *inv, uint8_t data[], uint8_t length);
void sendTicker(void); void sendTicker(void);
void mqttTicker(void); void mqttTicker(void);
@ -39,16 +42,35 @@ class app : public Main {
void showMqtt(void); void showMqtt(void);
void saveValues(bool webSend); void saveValues(bool webSend);
void dumpBuf(uint8_t buf[], uint8_t len);
void dumpBuf(const char *info, uint8_t buf[], uint8_t len) {
Serial.print(String(info));
for(uint8_t i = 0; i < len; i++) {
Serial.print(buf[i], HEX);
Serial.print(" ");
}
Serial.println();
}
uint64_t Serial2u64(const char *val) {
char tmp[3] = {0};
uint64_t ret = 0ULL;
uint64_t u64;
for(uint8_t i = 0; i < 6; i++) {
tmp[0] = val[i*2];
tmp[1] = val[i*2 + 1];
u64 = strtol(tmp, NULL, 16);
ret |= (u64 << ((5-i) << 3));
}
return ret;
}
uint8_t mState; uint8_t mState;
bool mKeyPressed; bool mKeyPressed;
RF24 *mRadio; RF24 *mRadio;
hoymiles *mHoymiles; packet_t mBuffer[PACKET_BUFFER_SIZE];
hm1200Decode *mDecoder; HmSystemType *mSys;
CircularBuffer<NRF24_packet_t> *mBufCtrl;
NRF24_packet_t mBuffer[PACKET_BUFFER_SIZE];
Ticker *mSendTicker; Ticker *mSendTicker;

38
tools/esp8266/debug.h

@ -0,0 +1,38 @@
#ifndef __DEBUG_H__
#define __DEBUG_H__
#ifdef NDEBUG
#define DPRINT(str)
#else
#ifndef DSERIAL
#define DSERIAL Serial
#endif
template <class T>
inline void DPRINT(T str) { DSERIAL.print(str); }
template <class T>
inline void DPRINTLN(T str) { DPRINT(str); DPRINT(F("\r\n")); }
inline void DHEX(uint8_t b) {
if( b<0x10 ) DSERIAL.print('0');
DSERIAL.print(b,HEX);
}
inline void DHEX(uint16_t b) {
if( b<0x10 ) DSERIAL.print(F("000"));
else if( b<0x100 ) DSERIAL.print(F("00"));
else if( b<0x1000 ) DSERIAL.print(F("0"));
DSERIAL.print(b,HEX);
}
inline void DHEX(uint32_t b) {
if( b<0x10 ) DSERIAL.print(F("0000000"));
else if( b<0x100 ) DSERIAL.print(F("000000"));
else if( b<0x1000 ) DSERIAL.print(F("00000"));
else if( b<0x10000 ) DSERIAL.print(F("0000"));
else if( b<0x100000 ) DSERIAL.print(F("000"));
else if( b<0x1000000 ) DSERIAL.print(F("00"));
else if( b<0x10000000 ) DSERIAL.print(F("0"));
DSERIAL.print(b,HEX);
}
#endif
#endif /*__DEBUG_H__*/

14
tools/esp8266/defines.h

@ -10,12 +10,20 @@
#define RF24_CS_PIN 15 #define RF24_CS_PIN 15
//-------------------------------------
// CONFIGURATION - COMPILE TIME
//-------------------------------------
#define PACKET_BUFFER_SIZE 30
#define MAX_NUM_INVERTERS 3
//------------------------------------- //-------------------------------------
// VERSION // VERSION
//------------------------------------- //-------------------------------------
#define VERSION_MAJOR 0 #define VERSION_MAJOR 0
#define VERSION_MINOR 1 #define VERSION_MINOR 2
#define VERSION_PATCH 12 #define VERSION_PATCH 1
//------------------------------------- //-------------------------------------
@ -26,7 +34,7 @@
#define DEVNAME_LEN 16 #define DEVNAME_LEN 16
#define CRC_LEN 2 #define CRC_LEN 2
#define INV_ADDR_LEN 6 #define INV_ADDR_LEN 8 // uint64_t
#define INV_INTERVAL_LEN 2 // uint16_t #define INV_INTERVAL_LEN 2 // uint16_t

130
tools/esp8266/eep.cpp

@ -1,130 +0,0 @@
#include "eep.h"
#include <EEPROM.h>
//-----------------------------------------------------------------------------
eep::eep() {
EEPROM.begin(500);
}
//-----------------------------------------------------------------------------
eep::~eep() {
EEPROM.end();
}
//-----------------------------------------------------------------------------
void eep::read(uint32_t addr, char *str, uint8_t length) {
for(uint8_t i = 0; i < length; i ++) {
*(str++) = (char)EEPROM.read(addr++);
}
}
//-----------------------------------------------------------------------------
void eep::read(uint32_t addr, float *value) {
uint8_t *p = (uint8_t*)value;
for(uint8_t i = 0; i < 4; i ++) {
*(p++) = (uint8_t)EEPROM.read(addr++);
}
}
//-----------------------------------------------------------------------------
void eep::read(uint32_t addr, bool *value) {
uint8_t intVal = 0x00;
intVal = EEPROM.read(addr++);
*value = (intVal == 0x01);
}
//-----------------------------------------------------------------------------
void eep::read(uint32_t addr, uint8_t *value) {
*value = (EEPROM.read(addr++));
}
//-----------------------------------------------------------------------------
void eep::read(uint32_t addr, uint8_t data[], uint8_t length) {
for(uint8_t i = 0; i < length; i ++) {
*(data++) = EEPROM.read(addr++);
}
}
//-----------------------------------------------------------------------------
void eep::read(uint32_t addr, uint16_t *value) {
*value = (EEPROM.read(addr++) << 8);
*value |= (EEPROM.read(addr++));
}
//-----------------------------------------------------------------------------
void eep::read(uint32_t addr, uint32_t *value) {
*value = (EEPROM.read(addr++) << 24);
*value |= (EEPROM.read(addr++) << 16);
*value |= (EEPROM.read(addr++) << 8);
*value |= (EEPROM.read(addr++));
}
//-----------------------------------------------------------------------------
void eep::write(uint32_t addr, const char *str, uint8_t length) {
for(uint8_t i = 0; i < length; i ++) {
EEPROM.write(addr++, str[i]);
}
EEPROM.commit();
}
//-----------------------------------------------------------------------------
void eep::write(uint32_t addr, uint8_t data[], uint8_t length) {
for(uint8_t i = 0; i < length; i ++) {
EEPROM.write(addr++, data[i]);
}
EEPROM.commit();
}
//-----------------------------------------------------------------------------
void eep::write(uint32_t addr, float value) {
uint8_t *p = (uint8_t*)&value;
for(uint8_t i = 0; i < 4; i ++) {
EEPROM.write(addr++, p[i]);
}
EEPROM.commit();
}
//-----------------------------------------------------------------------------
void eep::write(uint32_t addr, bool value) {
uint8_t intVal = (value) ? 0x01 : 0x00;
EEPROM.write(addr++, intVal);
EEPROM.commit();
}
//-----------------------------------------------------------------------------
void eep::write(uint32_t addr, uint8_t value) {
EEPROM.write(addr++, value);
EEPROM.commit();
}
//-----------------------------------------------------------------------------
void eep::write(uint32_t addr, uint16_t value) {
EEPROM.write(addr++, (value >> 8) & 0xff);
EEPROM.write(addr++, (value ) & 0xff);
EEPROM.commit();
}
//-----------------------------------------------------------------------------
void eep::write(uint32_t addr, uint32_t value) {
EEPROM.write(addr++, (value >> 24) & 0xff);
EEPROM.write(addr++, (value >> 16) & 0xff);
EEPROM.write(addr++, (value >> 8) & 0xff);
EEPROM.write(addr++, (value ) & 0xff);
EEPROM.commit();
}

141
tools/esp8266/eep.h

@ -2,29 +2,132 @@
#define __EEP_H__ #define __EEP_H__
#include "Arduino.h" #include "Arduino.h"
#include <EEPROM.h>
class eep { class eep {
public: public:
eep(); eep() {
~eep(); EEPROM.begin(500);
}
void read(uint32_t addr, char *str, uint8_t length); ~eep() {
void read(uint32_t addr, float *value); EEPROM.end();
void read(uint32_t addr, bool *value); }
void read(uint32_t addr, uint8_t *value);
void read(uint32_t addr, uint8_t data[], uint8_t length);
void read(uint32_t addr, uint16_t *value);
void read(uint32_t addr, uint32_t *value);
void write(uint32_t addr, const char *str, uint8_t length);
void write(uint32_t addr, uint8_t data[], uint8_t length);
void write(uint32_t addr, float value);
void write(uint32_t addr, bool value);
void write(uint32_t addr, uint8_t value);
void write(uint32_t addr, uint16_t value);
void write(uint32_t addr, uint32_t value);
private:
void read(uint32_t addr, char *str, uint8_t length) {
for(uint8_t i = 0; i < length; i ++) {
*(str++) = (char)EEPROM.read(addr++);
}
}
void read(uint32_t addr, float *value) {
uint8_t *p = (uint8_t*)value;
for(uint8_t i = 0; i < 4; i ++) {
*(p++) = (uint8_t)EEPROM.read(addr++);
}
}
void read(uint32_t addr, bool *value) {
uint8_t intVal = 0x00;
intVal = EEPROM.read(addr++);
*value = (intVal == 0x01);
}
void read(uint32_t addr, uint8_t *value) {
*value = (EEPROM.read(addr++));
}
void read(uint32_t addr, uint8_t data[], uint8_t length) {
for(uint8_t i = 0; i < length; i ++) {
*(data++) = EEPROM.read(addr++);
}
}
void read(uint32_t addr, uint16_t *value) {
*value = (EEPROM.read(addr++) << 8);
*value |= (EEPROM.read(addr++));
}
void read(uint32_t addr, uint32_t *value) {
*value = (EEPROM.read(addr++) << 24);
*value |= (EEPROM.read(addr++) << 16);
*value |= (EEPROM.read(addr++) << 8);
*value |= (EEPROM.read(addr++));
}
void read(uint32_t addr, uint64_t *value) {
read(addr, (uint32_t *)value);
*value <<= 32;
uint32_t tmp;
read(addr+4, &tmp);
*value |= tmp;
/**value = (EEPROM.read(addr++) << 56);
*value |= (EEPROM.read(addr++) << 48);
*value |= (EEPROM.read(addr++) << 40);
*value |= (EEPROM.read(addr++) << 32);
*value |= (EEPROM.read(addr++) << 24);
*value |= (EEPROM.read(addr++) << 16);
*value |= (EEPROM.read(addr++) << 8);
*value |= (EEPROM.read(addr++));*/
}
void write(uint32_t addr, const char *str, uint8_t length) {
for(uint8_t i = 0; i < length; i ++) {
EEPROM.write(addr++, str[i]);
}
EEPROM.commit();
}
void write(uint32_t addr, uint8_t data[], uint8_t length) {
for(uint8_t i = 0; i < length; i ++) {
EEPROM.write(addr++, data[i]);
}
EEPROM.commit();
}
void write(uint32_t addr, float value) {
uint8_t *p = (uint8_t*)&value;
for(uint8_t i = 0; i < 4; i ++) {
EEPROM.write(addr++, p[i]);
}
EEPROM.commit();
}
void write(uint32_t addr, bool value) {
uint8_t intVal = (value) ? 0x01 : 0x00;
EEPROM.write(addr++, intVal);
EEPROM.commit();
}
void write(uint32_t addr, uint8_t value) {
EEPROM.write(addr++, value);
EEPROM.commit();
}
void write(uint32_t addr, uint16_t value) {
EEPROM.write(addr++, (value >> 8) & 0xff);
EEPROM.write(addr++, (value ) & 0xff);
EEPROM.commit();
}
void write(uint32_t addr, uint32_t value) {
EEPROM.write(addr++, (value >> 24) & 0xff);
EEPROM.write(addr++, (value >> 16) & 0xff);
EEPROM.write(addr++, (value >> 8) & 0xff);
EEPROM.write(addr++, (value ) & 0xff);
EEPROM.commit();
}
void write(uint64_t addr, uint64_t value) {
EEPROM.write(addr++, (value >> 56) & 0xff);
EEPROM.write(addr++, (value >> 48) & 0xff);
EEPROM.write(addr++, (value >> 40) & 0xff);
EEPROM.write(addr++, (value >> 32) & 0xff);
EEPROM.write(addr++, (value >> 24) & 0xff);
EEPROM.write(addr++, (value >> 16) & 0xff);
EEPROM.write(addr++, (value >> 8) & 0xff);
EEPROM.write(addr++, (value ) & 0xff);
EEPROM.commit();
}
}; };
#endif /*__EEP_H__*/ #endif /*__EEP_H__*/

10
tools/esp8266/esp8266.ino

@ -1,3 +1,12 @@
#include "Arduino.h"
#include <ESP8266WiFi.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <Ticker.h>
#include <ESP8266HTTPUpdateServer.h>
#include "app.h" #include "app.h"
app myApp; app myApp;
@ -22,4 +31,3 @@ void loop() {
ICACHE_RAM_ATTR void handleIntr(void) { ICACHE_RAM_ATTR void handleIntr(void) {
myApp.handleIntr(); myApp.handleIntr();
} }

87
tools/esp8266/hm1200Decode.h

@ -1,87 +0,0 @@
#ifndef __HM1200_DECODE__
#define __HM1200_DECODE__
typedef struct {
double u;
double i;
double p;
uint16_t y_d; // yield day
double y_t; // yield total
} ch_t;
typedef struct {
ch_t ch_dc[4];
ch_t ch_ac;
double temp;
double pct;
double acFreq;
} hoyData_t;
class hm1200Decode {
public:
hm1200Decode() {
memset(&mData, 0, sizeof(hoyData_t));
}
~hm1200Decode() {}
void convert(uint8_t buf[], uint8_t len) {
switch(buf[0]) {
case 0x01: convCmd01(buf, len); break;
case 0x02: convCmd02(buf, len); break;
case 0x03: convCmd03(buf, len); break;
case 0x84: convCmd84(buf, len); break;
default: break;
}
}
hoyData_t mData;
private:
void convCmd01(uint8_t buf[], uint8_t len) {
mData.ch_dc[0].u = ((buf[ 3] << 8) | buf[ 4]) / 10.0f;
mData.ch_dc[0].i = ((buf[ 5] << 8) | buf[ 6]) / 100.0f;
mData.ch_dc[1].i = ((buf[ 7] << 8) | buf[ 8]) / 100.0f;
mData.ch_dc[0].p = ((buf[ 9] << 8) | buf[10]) / 10.0f;
mData.ch_dc[1].p = ((buf[11] << 8) | buf[12]) / 10.0f;
mData.ch_dc[0].y_t = ((buf[13] << 24) | (buf[14] << 16)
| (buf[15] << 8) | buf[16]) / 1000.0f;
}
void convCmd02(uint8_t buf[], uint8_t len) {
mData.ch_dc[1].y_t = ((buf[ 1] << 24) | (buf[ 2] << 16)
| (buf[ 3] << 8) | buf[ 4]) / 1000.0f;
mData.ch_dc[0].y_d = ((buf[ 5] << 8) | buf[ 6]);
mData.ch_dc[1].y_d = ((buf[ 7] << 8) | buf[ 8]);
mData.ch_dc[1].u = ((buf[ 9] << 8) | buf[10]) / 10.0f;
mData.ch_dc[2].i = ((buf[11] << 8) | buf[12]) / 100.0f;
mData.ch_dc[3].i = ((buf[13] << 8) | buf[14]) / 100.0f;
mData.ch_dc[2].p = ((buf[15] << 8) | buf[16]) / 10.0f;
}
void convCmd03(uint8_t buf[], uint8_t len) {
mData.ch_dc[3].p = ((buf[ 1] << 8) | buf[ 2]) / 10.0f;
mData.ch_dc[2].y_t = ((buf[ 3] << 24) | (buf[4] << 16)
| (buf[ 5] << 8) | buf[ 6]) / 1000.0f;
mData.ch_dc[3].y_t = ((buf[ 7] << 24) | (buf[8] << 16)
| (buf[ 9] << 8) | buf[10]) / 1000.0f;
mData.ch_dc[2].y_d = ((buf[11] << 8) | buf[12]);
mData.ch_dc[3].y_d = ((buf[13] << 8) | buf[14]);
mData.ch_ac.u = ((buf[15] << 8) | buf[16]) / 10.0f;
}
void convCmd84(uint8_t buf[], uint8_t len) {
mData.acFreq = ((buf[ 1] << 8) | buf[ 2]) / 100.0f;
mData.ch_ac.p = ((buf[ 3] << 8) | buf[ 4]) / 10.0f;
mData.ch_ac.i = ((buf[ 7] << 8) | buf[ 8]) / 100.0f;
mData.pct = ((buf[ 9] << 8) | buf[10]) / 10.0f;
mData.temp = ((buf[11] << 8) | buf[12]) / 10.0f;
}
// private member variables
};
#endif /*__HM1200_DECODE__*/

111
tools/esp8266/hmInverters.h

@ -0,0 +1,111 @@
#ifndef __HM_INVERTERS_H__
#define __HM_INVERTERS_H__
#include "debug.h"
#include <cstdint>
// units
enum {UNIT_V = 0, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_HZ, UNIT_C, UNIT_PCT};
const char* const units[] = {"V", "A", "W", "Wh", "kWh", "Hz", "°C", "%"};
// field types
enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT,
FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PCT};
const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal",
"U_AC", "I_AC", "P_AC", "Freq", "Temp", "Pct"};
// CH0 is default channel (freq, ac, temp)
enum {CH0 = 0, CH1, CH2, CH3, CH4};
enum {CMD01 = 0x01, CMD02, CMD03, CMD83 = 0x83, CMD84};
enum {INV_TYPE_HM600 = 0, INV_TYPE_HM1200};
typedef struct {
uint8_t fieldId; // field id
uint8_t unitId; // uint id
uint8_t ch; // channel 0 - 3
uint8_t cmdId; // received command id
uint8_t start; // pos of first byte in buffer
uint8_t num; // number of bytes in buffer
uint16_t div; // divisor
} byteAssign_t;
union serial_u {
uint64_t u64;
uint8_t b[8];
};
typedef struct {
uint8_t id; // unique id
char name[20]; // human readable name, eg. "HM-600.1"
uint8_t type; // integer which refers to inverter type
byteAssign_t* assign; // type of inverter
uint8_t listLen; // length of assignments
serial_u serial; // serial number as on barcode
serial_u radioId; // id converted to modbus
} inverter_t;
/**
* indices are built for the buffer starting with cmd-id in first byte
* (complete payload in buffer)
* */
//-------------------------------------
// HM600, HM700
//-------------------------------------
const byteAssign_t hm600assignment[] = {
{ FLD_UDC, UNIT_V, CH1, CMD01, 14, 2, 10 },
{ FLD_IDC, UNIT_A, CH1, CMD01, 16, 2, 100 },
{ FLD_PDC, UNIT_W, CH1, CMD01, 18, 2, 10 },
{ FLD_UDC, UNIT_V, CH2, CMD01, 20, 2, 10 },
{ FLD_IDC, UNIT_A, CH2, CMD01, 22, 2, 100 },
{ FLD_PDC, UNIT_W, CH2, CMD01, 24, 2, 10 },
{ FLD_YW, UNIT_WH, CH0, CMD02, 12, 2, 1 },
{ FLD_YT, UNIT_WH, CH0, CMD02, 14, 4, 1 },
{ FLD_YD, UNIT_WH, CH1, CMD02, 18, 2, 1 },
{ FLD_YD, UNIT_WH, CH2, CMD02, 20, 2, 1 },
{ FLD_UAC, UNIT_V, CH0, CMD02, 22, 2, 10 },
{ FLD_F, UNIT_HZ, CH0, CMD02, 24, 2, 100 },
{ FLD_IAC, UNIT_A, CH0, CMD02, 26, 2, 10 },
{ FLD_T, UNIT_C, CH0, CMD83, 18, 2, 10 }
};
#define HM600_LIST_LEN (sizeof(hm600assignment) / sizeof(byteAssign_t))
//-------------------------------------
// HM1200, HM1500?
//-------------------------------------
const byteAssign_t hm1200assignment[] = {
{ FLD_UDC, UNIT_V, CH1, CMD01, 3, 2, 10 },
{ FLD_IDC, UNIT_A, CH1, CMD01, 5, 2, 100 },
{ FLD_IDC, UNIT_A, CH2, CMD01, 7, 2, 100 },
{ FLD_PDC, UNIT_W, CH1, CMD01, 9, 2, 10 },
{ FLD_PDC, UNIT_W, CH2, CMD01, 11, 2, 10 },
{ FLD_YT, UNIT_KWH, CH1, CMD01, 13, 4, 1000 },
{ FLD_YT, UNIT_KWH, CH2, CMD02, 1, 4, 1000 },
{ FLD_YD, UNIT_WH, CH1, CMD02, 5, 2, 1 },
{ FLD_YD, UNIT_WH, CH2, CMD02, 7, 2, 1 },
{ FLD_UDC, UNIT_V, CH2, CMD02, 9, 2, 10 },
{ FLD_IDC, UNIT_A, CH3, CMD02, 11, 2, 100 },
{ FLD_IDC, UNIT_A, CH4, CMD02, 13, 2, 100 },
{ FLD_PDC, UNIT_W, CH3, CMD02, 15, 2, 10 },
{ FLD_PDC, UNIT_W, CH4, CMD03, 1, 2, 10 },
{ FLD_YT, UNIT_KWH, CH3, CMD03, 3, 4, 1000 },
{ FLD_YT, UNIT_KWH, CH4, CMD03, 7, 4, 1000 },
{ FLD_YD, UNIT_WH, CH3, CMD03, 11, 2, 1 },
{ FLD_YD, UNIT_WH, CH4, CMD03, 13, 2, 1 },
{ FLD_UAC, UNIT_V, CH0, CMD03, 15, 2, 10 },
{ FLD_F, UNIT_HZ, CH0, CMD84, 1, 2, 100 },
{ FLD_PAC, UNIT_W, CH0, CMD84, 3, 2, 10 },
{ FLD_IAC, UNIT_A, CH0, CMD84, 7, 2, 100 },
{ FLD_PCT, UNIT_PCT, CH0, CMD84, 9, 2, 10 },
{ FLD_T, UNIT_C, CH0, CMD84, 11, 2, 10 }
};
#define HM1200_LIST_LEN (sizeof(hm1200assignment) / sizeof(byteAssign_t))
#endif /*__HM_INVERTERS_H__*/

104
tools/esp8266/hoymiles.h → tools/esp8266/hmRadio.h

@ -1,5 +1,5 @@
#ifndef __HOYMILES_H__ #ifndef __RADIO_H__
#define __HOYMILES_H__ #define __RADIO_H__
#include <RF24.h> #include <RF24.h>
#include <RF24_config.h> #include <RF24_config.h>
@ -7,18 +7,15 @@
#define CHANNEL_HOP // switch between channels or use static channel to send #define CHANNEL_HOP // switch between channels or use static channel to send
#define luint64_t long long unsigned int
#define DEFAULT_RECV_CHANNEL 3 #define DEFAULT_RECV_CHANNEL 3
#define MAX_RF_PAYLOAD_SIZE 64 #define MAX_RF_PAYLOAD_SIZE 64
#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL)
#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL)
#define PACKET_BUFFER_SIZE 30 #define DTU_RADIO_ID ((uint64_t)0x1234567801ULL)
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// MACROS // MACROS
//-----------------------------------------------------------------------------
#define CP_U32_LittleEndian(buf, v) ({ \ #define CP_U32_LittleEndian(buf, v) ({ \
uint8_t *b = buf; \ uint8_t *b = buf; \
b[0] = ((v >> 24) & 0xff); \ b[0] = ((v >> 24) & 0xff); \
@ -39,64 +36,38 @@
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
union uint64Bytes { // HM Radio class
uint64_t ull;
uint8_t bytes[8];
};
typedef struct {
uint8_t sendCh;
uint8_t packet[MAX_RF_PAYLOAD_SIZE];
} NRF24_packet_t;
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
class hoymiles { template <uint8_t CE_PIN, uint8_t CS_PIN, uint8_t IRQ_PIN, uint64_t DTU_ID=DTU_RADIO_ID>
class HmRadio {
public: public:
hoymiles() { HmRadio() {
serial2RadioId(); pinMode(IRQ_PIN, INPUT_PULLUP);
calcDtuIdCrc(); //attachInterrupt(digitalPinToInterrupt(IRQ_PIN), handleIntr, FALLING);
mChannels[0] = 23; mSendChan[0] = 23;
mChannels[1] = 40; mSendChan[1] = 40;
mChannels[2] = 61; mSendChan[2] = 61;
mChannels[3] = 75; mSendChan[3] = 75;
mChanIdx = 1; mChanIdx = 1;
mLastCrc = 0x0000;
mRptCnt = 0;
} }
~HmRadio() {}
~hoymiles() {}
uint8_t getDefaultChannel(void) { uint8_t getDefaultChannel(void) {
return mChannels[2]; return mSendChan[2];
} }
uint8_t getLastChannel(void) { uint8_t getLastChannel(void) {
return mChannels[mChanIdx]; return mSendChan[mChanIdx];
} }
uint8_t getNxtChannel(void) { uint8_t getNxtChannel(void) {
if(++mChanIdx >= 4) if(++mChanIdx >= 4)
mChanIdx = 0; mChanIdx = 0;
return mChannels[mChanIdx]; return mSendChan[mChanIdx];
} }
void serial2RadioId(void) { uint8_t getTimePacket(const uint64_t *invId, uint8_t buf[], uint32_t ts) {
uint64Bytes id; getCmdPacket(invId, buf, 0x15, 0x80, false);
id.ull = 0ULL;
id.bytes[4] = mAddrBytes[5];
id.bytes[3] = mAddrBytes[4];
id.bytes[2] = mAddrBytes[3];
id.bytes[1] = mAddrBytes[2];
id.bytes[0] = 0x01;
mRadioId = id.ull;
}
uint8_t getTimePacket(uint8_t buf[], uint32_t ts) {
getCmdPacket(buf, 0x15, 0x80, false);
buf[10] = 0x0b; // cid buf[10] = 0x0b; // cid
buf[11] = 0x00; buf[11] = 0x00;
CP_U32_LittleEndian(&buf[12], ts); CP_U32_LittleEndian(&buf[12], ts);
@ -110,11 +81,11 @@ class hoymiles {
return 27; return 27;
} }
uint8_t getCmdPacket(uint8_t buf[], uint8_t mid, uint8_t cmd, bool calcCrc = true) { uint8_t getCmdPacket(const uint64_t *invId, uint8_t buf[], uint8_t mid, uint8_t cmd, bool calcCrc = true) {
memset(buf, 0, MAX_RF_PAYLOAD_SIZE); memset(buf, 0, MAX_RF_PAYLOAD_SIZE);
buf[0] = mid; // message id buf[0] = mid; // message id
CP_U32_BigEndian(&buf[1], (mRadioId >> 8)); CP_U32_BigEndian(&buf[1], (*invId >> 8));
CP_U32_BigEndian(&buf[5], (DTU_RADIO_ID >> 8)); CP_U32_BigEndian(&buf[5], (DTU_ID >> 8));
buf[9] = cmd; buf[9] = cmd;
if(calcCrc) if(calcCrc)
buf[10] = crc8(buf, 10); buf[10] = crc8(buf, 10);
@ -144,35 +115,22 @@ class hoymiles {
return valid; return valid;
} }
void dumpBuf(const char *info, uint8_t buf[], uint8_t len) { protected:
Serial.print(String(info)); void getDtuIdCrc(void) {
for(uint8_t i = 0; i < len; i++) {
Serial.print(buf[i], HEX);
Serial.print(" ");
}
Serial.println();
}
uint8_t mAddrBytes[6];
luint64_t mRadioId;
private:
void calcDtuIdCrc(void) {
uint64_t addr = DTU_RADIO_ID; uint64_t addr = DTU_RADIO_ID;
uint8_t dtuAddr[5]; uint8_t tmp[5];
for(int8_t i = 4; i >= 0; i--) { for(int8_t i = 4; i >= 0; i--) {
dtuAddr[i] = addr; tmp[i] = addr;
addr >>= 8; addr >>= 8;
} }
mDtuIdCrc = crc16nrf24(dtuAddr, BIT_CNT(5)); mDtuIdCrc = crc16nrf24(tmp, BIT_CNT(5));
} }
uint8_t mSendChan[4];
uint8_t mChannels[4];
uint8_t mChanIdx; uint8_t mChanIdx;
uint16_t mDtuIdCrc; uint16_t mDtuIdCrc;
uint16_t mLastCrc; uint16_t mLastCrc;
uint8_t mRptCnt; uint8_t mRptCnt;
}; };
#endif /*__HOYMILES_H__*/ #endif /*__RADIO_H__*/

149
tools/esp8266/hmSystem.h

@ -0,0 +1,149 @@
#ifndef __HM_SYSTEM_H__
#define __HM_SYSTEM_H__
#include "hmInverters.h"
#include "hmRadio.h"
typedef struct {
uint8_t sendCh;
uint8_t packet[MAX_RF_PAYLOAD_SIZE];
} packet_t;
template <class RADIO, class BUFFER, uint8_t MAX_INVERTER, class RECORDTYPE=float>
class HmSystem {
public:
typedef RADIO RadioType;
RadioType Radio;
typedef BUFFER BufferType;
BufferType BufCtrl;
HmSystem() {
mNumInv = 0;
}
~HmSystem() {
// TODO: cleanup
}
inverter_t *addInverter(const char *name, uint64_t serial, uint8_t type) {
if(MAX_INVERTER <= mNumInv) {
DPRINT("max number of inverters reached!");
return NULL;
}
inverter_t *p = &mInverter[mNumInv];
p->id = mNumInv++;
p->serial.u64 = serial;
p->type = type;
uint8_t len = strlen(name);
strncpy(p->name, name, (len > 20) ? 20 : len);
getAssignment(p);
toRadioId(p);
if(NULL == p->assign) {
DPRINT("no assignment for type found!");
return NULL;
}
else {
mRecord = new RECORDTYPE[p->listLen];
memset(mRecord, 0, sizeof(RECORDTYPE) * p->listLen);
return p;
}
}
inverter_t *findInverter(uint8_t buf[]) {
inverter_t *p;
for(uint8_t i = 0; i < mNumInv; i++) {
p = &mInverter[i];
if((p->serial.b[3] == buf[0])
&& (p->serial.b[2] == buf[1])
&& (p->serial.b[1] == buf[2])
&& (p->serial.b[0] == buf[3]))
return p;
}
return NULL;
}
inverter_t *getInverterByPos(uint8_t pos) {
if(mInverter[pos].serial.u64 != 0ULL)
return &mInverter[pos];
else
return NULL;
}
const char *getFieldName(inverter_t *p, uint8_t pos) {
return fields[p->assign[pos].fieldId];
}
const char *getUnit(inverter_t *p, uint8_t pos) {
return units[p->assign[pos].unitId];
}
uint64_t getSerial(inverter_t *p) {
return p->serial.u64;
}
void updateSerial(inverter_t *p, uint64_t serial) {
p->serial.u64 = serial;
}
uint8_t getChannel(inverter_t *p, uint8_t pos) {
return p->assign[pos].ch;
}
uint8_t getCmdId(inverter_t *p, uint8_t pos) {
return p->assign[pos].cmdId;
}
void addValue(inverter_t *p, uint8_t pos, uint8_t buf[]) {
uint8_t ptr = p->assign[pos].start;
uint8_t end = ptr + p->assign[pos].num;
uint16_t div = p->assign[pos].div;
uint32_t val = 0;
do {
val <<= 8;
val |= buf[ptr];
} while(++ptr != end);
mRecord[pos] = (RECORDTYPE)(val) / (RECORDTYPE)(div);
}
RECORDTYPE getValue(inverter_t *p, uint8_t pos) {
return mRecord[pos];
}
uint8_t getNumInverters(void) {
return mNumInv;
}
private:
void toRadioId(inverter_t *p) {
p->radioId.u64 = 0ULL;
p->radioId.b[4] = p->serial.b[0];
p->radioId.b[3] = p->serial.b[1];
p->radioId.b[2] = p->serial.b[2];
p->radioId.b[1] = p->serial.b[3];
p->radioId.b[0] = 0x01;
}
void getAssignment(inverter_t *p) {
if(INV_TYPE_HM600 == p->type) {
p->listLen = (uint8_t)(HM1200_LIST_LEN);
p->assign = (byteAssign_t*)hm600assignment;
}
else if(INV_TYPE_HM1200 == p->type) {
p->listLen = (uint8_t)(HM1200_LIST_LEN);
p->assign = (byteAssign_t*)hm1200assignment;
}
else {
p->listLen = 0;
p->assign = NULL;
}
}
inverter_t mInverter[MAX_INVERTER]; // TODO: only one inverter supported!!!
uint8_t mNumInv;
RECORDTYPE *mRecord;
};
#endif /*__HM_SYSTEM_H__*/

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

@ -1 +1 @@
String setup_html = "<!doctype html><html><head><title>Setup - {DEVICE}</title><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"></head><body><h1>Setup</h1><div id=\"setup\" class=\"content\"><div id=\"content\"><p> Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information. </p><form method=\"post\" action=\"/save\"><p class=\"des\">WiFi</p><div class=\"inputWrp\"><input type=\"text\" class=\"inputText\" name=\"ssid\" value=\"{SSID}\" required/><span class=\"floating_label\">SSID</span></div><div class=\"inputWrp\"><input type=\"password\" class=\"inputText\" name=\"pwd\" value=\"{PWD}\" required/><span class=\"floating_label\">PASSWORD</span></div><p class=\"des\">Device Host Name</p><div class=\"inputWrp\"><input type=\"text\" class=\"inputText\" name=\"device\" value=\"{DEVICE}\" required/><span class=\"floating_label\">DEVICE NAME</span></div><p class=\"des\">Inverter</p><div class=\"inputWrp\"><input type=\"text\" class=\"inputText\" name=\"inv0Addr\" value=\"{INV0_ADDR}\" required/><span class=\"floating_label\">INVERTER 0 ADDRESS (eg. 11:22:33:44:55:66)</span></div><div class=\"inputWrp\"><input type=\"text\" class=\"inputText\" name=\"invInterval\" value=\"{INV_INTERVAL}\" required/><span class=\"floating_label\">INTERVAL (ms)</span></div><p class=\"des\">MQTT</p><div class=\"inputWrp\"><input type=\"text\" class=\"inputText\" name=\"mqttAddr\" value=\"{MQTT_ADDR}\" required/><span class=\"floating_label\">BROKER (Server IP)</span></div><div class=\"inputWrp\"><input type=\"text\" class=\"inputText\" name=\"mqttUser\" value=\"{MQTT_USER}\"/><span class=\"floating_label\">USERNAME (optional)</span></div><div class=\"inputWrp\"><input type=\"text\" class=\"inputText\" name=\"mqttPwd\" value=\"{MQTT_PWD}\"/><span class=\"floating_label\">PASSWORD (optional)</span></div><div class=\"inputWrp\"><input type=\"text\" class=\"inputText\" name=\"mqttTopic\" value=\"{MQTT_TOPIC}\" required/><span class=\"floating_label\">TOPIC</span></div><div class=\"inputWrp\"><input type=\"text\" class=\"inputText\" name=\"mqttInterval\" value=\"{MQTT_INTERVAL}\" required/><span class=\"floating_label\">INTERVAL (ms)</span></div><input type=\"checkbox\" class=\"cb\" name=\"reboot\"/><label for=\"reboot\">Reboot device after successful save</label><input type=\"submit\" value=\"save\" class=\"button\" /></form></div></div><div id=\"footer\"><p class=\"left\"><a href=\"/\">Home</a></p><p class=\"left\"><a href=\"/update\">Update Firmware</a></p><p class=\"right\">AHOY - {VERSION}</p></div></body></html>"; String setup_html = "<!doctype html><html><head><title>Setup - {DEVICE}</title><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"></head><body><h1>Setup</h1><div id=\"setup\" class=\"content\"><div id=\"content\"><p> Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information. </p><form method=\"post\" action=\"/save\"><p class=\"des\">WiFi</p><label for=\"ssid\">SSID</label><input type=\"text\" class=\"text\" name=\"ssid\" value=\"{SSID}\"/><label for=\"pwd\">Password</label><input type=\"password\" class=\"text\" name=\"pwd\" value=\"{PWD}\"/><p class=\"des\">Device Host Name</p><label for=\"device\">Device Name</label><input type=\"text\" class=\"text\" name=\"device\" value=\"{DEVICE}\"/><p class=\"des\">Inverter</p> {INVERTERS}<br/><label for=\"invInterval\">Interval (ms)</label><input type=\"text\" class=\"text\" name=\"invInterval\" value=\"{INV_INTERVAL}\"/><p class=\"des\">MQTT</p><label for=\"mqttAddr\">Broker / Server IP</label><input type=\"text\" class=\"text\" name=\"mqttAddr\" value=\"{MQTT_ADDR}\"/><label for=\"mqttUser\">Username (optional)</label><input type=\"text\" class=\"text\" name=\"mqttUser\" value=\"{MQTT_USER}\"/><label for=\"mqttPwd\">Password (optional)</label><input type=\"text\" class=\"text\" name=\"mqttPwd\" value=\"{MQTT_PWD}\"/><label for=\"mqttTopic\">Topic</label><input type=\"text\" class=\"text\" name=\"mqttTopic\" value=\"{MQTT_TOPIC}\"/><label for=\"mqttInterval\">Interval (seconds)</label><input type=\"text\" class=\"text\" name=\"mqttInterval\" value=\"{MQTT_INTERVAL}\"/><p class=\"des\">&nbsp;</p><input type=\"checkbox\" class=\"cb\" name=\"reboot\"/><label for=\"reboot\">Reboot device after successful save</label><input type=\"submit\" value=\"save\" class=\"button\" /></form></div></div><div id=\"footer\"><p class=\"left\"><a href=\"/\">Home</a></p><p class=\"left\"><a href=\"/update\">Update Firmware</a></p><p class=\"right\">AHOY - {VERSION}</p></div></body></html>";

2
tools/esp8266/html/h/style_css.h

@ -1 +1 @@
String style_css = "h1 { margin: 0; padding: 20pt; font-size: 22pt; color: #fff; background-color: #006ec0; display: block; text-transform: uppercase; } html, body { font-family: Arial; margin: 0; padding: 0; } p { text-align: justify; font-size: 13pt; } .des { font-size: 14pt; color: #006ec0; padding-bottom: 0px !important; } .fw { width: 60px; display: block; float: left; } .color { width: 50px; height: 50px; border: 1px solid #ccc; } .range { width: 300px; } a:link, a:visited { text-decoration: none; font-size: 13pt; color: #006ec0; } a:hover, a:focus { color: #f00; } #content { padding: 15px 15px 60px 15px; } #footer { position: fixed; bottom: 0px; height: 45px; background-color: #006ec0; width: 100%; } #footer p { color: #fff; padding-left: 20px; padding-right: 20px; font-size: 10pt !important; } #footer a { color: #fff; } #footer a:hover { color: #f00; } div.content { background-color: #fff; padding-bottom: 65px; overflow: hidden; } span.warn { display: inline-block; padding-left: 20px; color: #ff9900; font-style: italic; } input { padding: 10px; font-size: 13pt; } input.button { background-color: #006ec0; color: #fff; border: 0px; float: right; text-transform: uppercase; } input.cb { margin-bottom: 20px; } label { font-size: 14pt; } .left { float: left; } .right { float: right; } .inputWrp { position: relative; } .inputWrp .inputText { height: 35px; width: 90%; margin-bottom: 20px; border: 1px solid #ccc; border-top: none; border-right: none; } .inputWrp .floating_label { position: absolute; pointer-events: none; top: 20px; left: 10px; transition: 0.2s ease all; } .inputWrp input:focus ~ .floating_label, .inputWrp input:not(:focus):valid ~ .floating_label { top: 0px; left: 20px; font-size: 10px; color: blue; opacity: 1; } div.module { display: block; width: 250px; height: 410px; background-color: #006ec0; display: inline-block; position: relative; margin-right: 20px; margin-bottom: 20px; } div.module .value, div.module .info, div.module .header { color: #fff; display: block; width: 100%; text-align: center; } div.module .unit { font-size: 19px; margin-left: 10px; } div.module .value { margin-top: 20px; font-size: 30px; } div.module .info { margin-top: 3px; font-size: 10px; } div.module .header { background-color: #003c80; padding: 10px 0 10px 0; } "; String style_css = "h1 { margin: 0; padding: 20pt; font-size: 22pt; color: #fff; background-color: #006ec0; display: block; text-transform: uppercase; } html, body { font-family: Arial; margin: 0; padding: 0; } p { text-align: justify; font-size: 13pt; } .des { margin-top: 35px; font-size: 14pt; color: #006ec0; } .subdes { font-size: 13pt; color: #006ec0; margin-left: 7px; } .fw { width: 60px; display: block; float: left; } .color { width: 50px; height: 50px; border: 1px solid #ccc; } .range { width: 300px; } a:link, a:visited { text-decoration: none; font-size: 13pt; color: #006ec0; } a:hover, a:focus { color: #f00; } #content { padding: 15px 15px 60px 15px; } #footer { position: fixed; bottom: 0px; height: 45px; background-color: #006ec0; width: 100%; } #footer p { color: #fff; padding-left: 20px; padding-right: 20px; font-size: 10pt !important; } #footer a { color: #fff; } div.content { background-color: #fff; padding-bottom: 65px; overflow: hidden; } input { padding: 7px; font-size: 13pt; } input.text, input.password { width: 70%; box-sizing: border-box; margin-bottom: 10px; /*float: right;*/ border: 1px solid #ccc; } input.button { background-color: #006ec0; color: #fff; border: 0px; float: right; text-transform: uppercase; } input.cb { margin-bottom: 20px; } label { width: 20%; display: inline-block; font-size: 12pt; padding-right: 10px; margin-left: 10px; } .left { float: left; } .right { float: right; } div.module { display: block; width: 250px; height: 410px; background-color: #006ec0; display: inline-block; position: relative; margin-right: 20px; margin-bottom: 20px; } div.module .value, div.module .info, div.module .header { color: #fff; display: block; width: 100%; text-align: center; } div.module .unit { font-size: 19px; margin-left: 10px; } div.module .value { margin-top: 20px; font-size: 30px; } div.module .info { margin-top: 3px; font-size: 10px; } div.module .header { background-color: #003c80; padding: 10px 0 10px 0; } ";

67
tools/esp8266/html/setup.html

@ -14,57 +14,32 @@
</p> </p>
<form method="post" action="/save"> <form method="post" action="/save">
<p class="des">WiFi</p> <p class="des">WiFi</p>
<div class="inputWrp"> <label for="ssid">SSID</label>
<input type="text" class="inputText" name="ssid" value="{SSID}" required/> <input type="text" class="text" name="ssid" value="{SSID}"/>
<span class="floating_label">SSID</span> <label for="pwd">Password</label>
</div> <input type="password" class="text" name="pwd" value="{PWD}"/>
<div class="inputWrp">
<input type="password" class="inputText" name="pwd" value="{PWD}" required/>
<span class="floating_label">PASSWORD</span>
</div>
<p class="des">Device Host Name</p> <p class="des">Device Host Name</p>
<div class="inputWrp"> <label for="device">Device Name</label>
<input type="text" class="inputText" name="device" value="{DEVICE}" required/> <input type="text" class="text" name="device" value="{DEVICE}"/>
<span class="floating_label">DEVICE NAME</span>
</div>
<p class="des">Inverter</p> <p class="des">Inverter</p>
<div class="inputWrp"> {INVERTERS}<br/>
<input type="text" class="inputText" name="inv0Addr" value="{INV0_ADDR}" required/> <label for="invInterval">Interval (ms)</label>
<span class="floating_label">INVERTER 0 ADDRESS (eg. 11:22:33:44:55:66)</span> <input type="text" class="text" name="invInterval" value="{INV_INTERVAL}"/>
</div>
<div class="inputWrp">
<input type="text" class="inputText" name="invInterval" value="{INV_INTERVAL}" required/>
<span class="floating_label">INTERVAL (ms)</span>
</div>
<p class="des">MQTT</p> <p class="des">MQTT</p>
<div class="inputWrp"> <label for="mqttAddr">Broker / Server IP</label>
<input type="text" class="inputText" name="mqttAddr" value="{MQTT_ADDR}" required/> <input type="text" class="text" name="mqttAddr" value="{MQTT_ADDR}"/>
<span class="floating_label">BROKER (Server IP)</span> <label for="mqttUser">Username (optional)</label>
</div> <input type="text" class="text" name="mqttUser" value="{MQTT_USER}"/>
<div class="inputWrp"> <label for="mqttPwd">Password (optional)</label>
<input type="text" class="inputText" name="mqttUser" value="{MQTT_USER}"/> <input type="text" class="text" name="mqttPwd" value="{MQTT_PWD}"/>
<span class="floating_label">USERNAME (optional)</span> <label for="mqttTopic">Topic</label>
</div> <input type="text" class="text" name="mqttTopic" value="{MQTT_TOPIC}"/>
<div class="inputWrp"> <label for="mqttInterval">Interval (seconds)</label>
<input type="text" class="inputText" name="mqttPwd" value="{MQTT_PWD}"/> <input type="text" class="text" name="mqttInterval" value="{MQTT_INTERVAL}"/>
<span class="floating_label">PASSWORD (optional)</span>
</div> <p class="des">&nbsp;</p>
<div class="inputWrp">
<input type="text" class="inputText" name="mqttTopic" value="{MQTT_TOPIC}" required/>
<span class="floating_label">TOPIC</span>
</div>
<div class="inputWrp">
<input type="text" class="inputText" name="mqttInterval" value="{MQTT_INTERVAL}" required/>
<span class="floating_label">INTERVAL (ms)</span>
</div>
<input type="checkbox" class="cb" name="reboot"/> <input type="checkbox" class="cb" name="reboot"/>
<label for="reboot">Reboot device after successful save</label> <label for="reboot">Reboot device after successful save</label>
<input type="submit" value="save" class="button" /> <input type="submit" value="save" class="button" />

65
tools/esp8266/html/style.css

@ -20,9 +20,15 @@ p {
} }
.des { .des {
margin-top: 35px;
font-size: 14pt; font-size: 14pt;
color: #006ec0; color: #006ec0;
padding-bottom: 0px !important; }
.subdes {
font-size: 13pt;
color: #006ec0;
margin-left: 7px;
} }
.fw { .fw {
@ -74,28 +80,25 @@ a:hover, a:focus {
color: #fff; color: #fff;
} }
#footer a:hover {
color: #f00;
}
div.content { div.content {
background-color: #fff; background-color: #fff;
padding-bottom: 65px; padding-bottom: 65px;
overflow: hidden; overflow: hidden;
} }
span.warn {
display: inline-block;
padding-left: 20px;
color: #ff9900;
font-style: italic;
}
input { input {
padding: 10px; padding: 7px;
font-size: 13pt; font-size: 13pt;
} }
input.text, input.password {
width: 70%;
box-sizing: border-box;
margin-bottom: 10px;
/*float: right;*/
border: 1px solid #ccc;
}
input.button { input.button {
background-color: #006ec0; background-color: #006ec0;
color: #fff; color: #fff;
@ -109,7 +112,11 @@ input.cb {
} }
label { label {
font-size: 14pt; width: 20%;
display: inline-block;
font-size: 12pt;
padding-right: 10px;
margin-left: 10px;
} }
.left { .left {
@ -120,36 +127,6 @@ label {
float: right; float: right;
} }
.inputWrp {
position: relative;
}
.inputWrp .inputText {
height: 35px;
width: 90%;
margin-bottom: 20px;
border: 1px solid #ccc;
border-top: none;
border-right: none;
}
.inputWrp .floating_label {
position: absolute;
pointer-events: none;
top: 20px;
left: 10px;
transition: 0.2s ease all;
}
.inputWrp input:focus ~ .floating_label,
.inputWrp input:not(:focus):valid ~ .floating_label {
top: 0px;
left: 20px;
font-size: 10px;
color: blue;
opacity: 1;
}
div.module { div.module {
display: block; display: block;
width: 250px; width: 250px;

2
tools/esp8266/mqtt.h

@ -51,7 +51,7 @@ class mqtt {
} }
char *getPwd(void) { char *getPwd(void) {
return mUser; return mPwd;
} }
char *getTopic(void) { char *getTopic(void) {

Loading…
Cancel
Save