diff --git a/tools/esp8266/CircularBuffer.h b/tools/esp8266/CircularBuffer.h index ab29e96a..aca8e417 100644 --- a/tools/esp8266/CircularBuffer.h +++ b/tools/esp8266/CircularBuffer.h @@ -1,21 +1,21 @@ /* - CircularBuffer - An Arduino circular buffering library for arbitrary types. - - Created by Ivo Pullens, Emmission, 2014 -- www.emmission.nl - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - 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, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + CircularBuffer - An Arduino circular buffering library for arbitrary types. + + Created by Ivo Pullens, Emmission, 2014 -- www.emmission.nl + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + 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, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef CircularBuffer_h @@ -26,133 +26,132 @@ #define RESTORE_IRQ interrupts() #else #define DISABLE_IRQ \ - uint8_t sreg = SREG; \ - cli(); + uint8_t sreg = SREG; \ + cli(); #define RESTORE_IRQ \ - SREG = sreg; + SREG = sreg; #endif -template class CircularBuffer -{ - public: - /** Constructor - * @param buffer Preallocated buffer of at least size records. - * @param size Number of records available in the buffer. - */ - CircularBuffer(T* buffer, const uint8_t size ) - : m_size(size), m_buff(buffer) - { - clear(); - } - - /** Clear all entries in the circular buffer. */ - void clear(void) - { - m_front = 0; - m_fill = 0; - } - - /** Test if the circular buffer is empty */ - inline bool empty(void) const - { - return !m_fill; - } - - /** Return the number of records stored in the buffer */ - inline uint8_t available(void) const - { - return m_fill; - } - - /** Test if the circular buffer is full */ - inline bool full(void) const - { - return m_fill == m_size; - } - - /** Aquire record on front of the buffer, for writing. - * After filling the record, it has to be pushed to actually - * add it to the buffer. - * @return Pointer to record, or NULL when buffer is full. - */ - T* getFront(void) const - { - DISABLE_IRQ; - T* f = NULL; - if (!full()) - f = get(m_front); - RESTORE_IRQ; - return f; - } - - /** Push record to front of the buffer - * @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. - * @return True, when record was pushed successfully. - */ - bool pushFront(T* record) - { - bool ok = false; - DISABLE_IRQ; - if (!full()) - { - T* f = get(m_front); - if (f != record) - *f = *record; - m_front = (m_front+1) % m_size; - m_fill++; - ok = true; - } - RESTORE_IRQ; - return ok; - } - - /** Aquire record on back of the buffer, for reading. - * After reading the record, it has to be pop'ed to actually - * remove it from the buffer. - * @return Pointer to record, or NULL when buffer is empty. - */ - T* getBack(void) const - { - T* b = NULL; - DISABLE_IRQ; - if (!empty()) - b = get(back()); - RESTORE_IRQ; - return b; - } - - /** Remove record from back of the buffer. - * @return True, when record was pop'ed successfully. - */ - bool popBack(void) - { - bool ok = false; - DISABLE_IRQ; - if (!empty()) - { - m_fill--; - ok = true; - } - RESTORE_IRQ; - return ok; - } - - protected: - inline T * get(const uint8_t idx) const - { - return &(m_buff[idx]); - } - inline uint8_t back(void) const - { - return (m_front - m_fill + m_size) % m_size; - } - - const uint8_t m_size; // Total number of records that can be stored in the buffer. - 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_fill; // Amount of records currently pushed. +template +class CircularBuffer { + + typedef BUFFERTYPE BufferType; + BufferType Buffer[BUFFERSIZE]; + + public: + CircularBuffer() : m_buff(Buffer) { + m_size = BUFFERSIZE; + clear(); + } + + /** Clear all entries in the circular buffer. */ + void clear(void) + { + m_front = 0; + m_fill = 0; + } + + /** Test if the circular buffer is empty */ + inline bool empty(void) const + { + return !m_fill; + } + + /** Return the number of records stored in the buffer */ + inline uint8_t available(void) const + { + return m_fill; + } + + /** Test if the circular buffer is full */ + inline bool full(void) const + { + return m_fill == m_size; + } + + /** Aquire record on front of the buffer, for writing. + * After filling the record, it has to be pushed to actually + * add it to the buffer. + * @return Pointer to record, or NULL when buffer is full. + */ + BUFFERTYPE* getFront(void) const + { + DISABLE_IRQ; + BUFFERTYPE* f = NULL; + if (!full()) + f = get(m_front); + RESTORE_IRQ; + return f; + } + + /** Push record to front of the buffer + * @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. + * @return True, when record was pushed successfully. + */ + bool pushFront(BUFFERTYPE* record) + { + bool ok = false; + DISABLE_IRQ; + if (!full()) + { + BUFFERTYPE* f = get(m_front); + if (f != record) + *f = *record; + m_front = (m_front+1) % m_size; + m_fill++; + ok = true; + } + RESTORE_IRQ; + return ok; + } + + /** Aquire record on back of the buffer, for reading. + * After reading the record, it has to be pop'ed to actually + * remove it from the buffer. + * @return Pointer to record, or NULL when buffer is empty. + */ + BUFFERTYPE* getBack(void) const + { + BUFFERTYPE* b = NULL; + DISABLE_IRQ; + if (!empty()) + b = get(back()); + RESTORE_IRQ; + return b; + } + + /** Remove record from back of the buffer. + * @return True, when record was pop'ed successfully. + */ + bool popBack(void) + { + bool ok = false; + DISABLE_IRQ; + if (!empty()) + { + m_fill--; + ok = true; + } + RESTORE_IRQ; + return ok; + } + + protected: + inline BUFFERTYPE * get(const uint8_t idx) const + { + return &(m_buff[idx]); + } + inline uint8_t back(void) const + { + return (m_front - m_fill + m_size) % m_size; + } + + uint8_t m_size; // Total number of records that can be stored in the buffer. + BUFFERTYPE* const m_buff; + volatile uint8_t m_front; // Index of front element (not pushed yet). + volatile uint8_t m_fill; // Amount of records currently pushed. }; #endif // CircularBuffer_h diff --git a/tools/esp8266/README.md b/tools/esp8266/README.md index f8690dec..66660bcd 100644 --- a/tools/esp8266/README.md +++ b/tools/esp8266/README.md @@ -1,12 +1,9 @@ ## OVERVIEW -This code was tested on a ESP8266 - ESP-07 module. Many parts of the code are based on 'Hubi's code, which can be found here: +This code is intended to run on a Wemos D1mini or similar. The code is based on 'Hubi's code, which can be found here: -The NRF24L01+ radio module is connected to the standard SPI pins. Additional there are 3 pins, which can be set individual: - -- IRQ - Pin 4 -- CE - Pin 5 -- CS - Pin 15 +The NRF24L01+ radio module is connected to the standard SPI pins. Additional there are 3 pins, which can be set individual: CS, CE and IRQ +These pins can be changed from the /setup URL ## Compile @@ -29,14 +26,22 @@ This code can be compiled using Arduino. The settings were: ## Usage -Connect the ESP to power and to your serial console. The webinterface is currently only used for OTA and config. -The serial console will print all information which is send and received. +Connect the ESP to power and to your serial console. The webinterface has the following abilities: + +- OTA Update (over the air update) +- Configuration (Wifi, inverter(s), Pinout, MQTT) +- visual display of the connected inverters / modules +- some statistics about communication (debug) + +The serial console will print the converted values which were read out of the inverter(s) -## Known Issues +## Compatiblity -- only command 0x81 is received +For now the following inverters should work out of the box: +- HM600 +- HM1200 ## USED LIBRARIES diff --git a/tools/esp8266/app.cpp b/tools/esp8266/app.cpp index 0b6db0a0..0c6c499e 100644 --- a/tools/esp8266/app.cpp +++ b/tools/esp8266/app.cpp @@ -1,20 +1,22 @@ #include "app.h" #include "html/h/index_html.h" -extern String setup_html; +#include "html/h/setup_html.h" +#include "html/h/hoymiles_html.h" + //----------------------------------------------------------------------------- app::app() : Main() { - mHoymiles = new hoymiles(); - - mBufCtrl = new CircularBuffer(mBuffer, PACKET_BUFFER_SIZE); - - mSendCnt = 0; mSendTicker = new Ticker(); - mFlagSend = false; + mFlagSend = false; + + mMqttTicker = NULL; + mMqttEvt = false; memset(mCmds, 0, sizeof(uint32_t)); memset(mChannelStat, 0, sizeof(uint32_t)); + + mSys = new HmSystemType(); } @@ -28,22 +30,69 @@ app::~app(void) { void app::setup(const char *ssid, const char *pwd, uint32_t timeout) { Main::setup(ssid, pwd, timeout); - mWeb->on("/", std::bind(&app::showIndex, this)); - mWeb->on("/setup", std::bind(&app::showSetup, this)); - mWeb->on("/save", std::bind(&app::showSave, this)); - mWeb->on("/cmdstat", std::bind(&app::showCmdStatistics, this)); + mWeb->on("/", std::bind(&app::showIndex, this)); + mWeb->on("/setup", std::bind(&app::showSetup, this)); + mWeb->on("/save", std::bind(&app::showSave, this)); + mWeb->on("/cmdstat", std::bind(&app::showCmdStatistics, this)); + mWeb->on("/hoymiles", std::bind(&app::showHoymiles, this)); + mWeb->on("/livedata", std::bind(&app::showLiveData, this)); + mWeb->on("/mqttstate", std::bind(&app::showMqtt, this)); + + if(mSettingsValid) { + uint16_t interval; + uint64_t invSerial; + char invName[MAX_NAME_LENGTH + 1] = {0}; + uint8_t invType; + + // inverter + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { + mEep->read(ADDR_INV_ADDR + (i * 8), &invSerial); + mEep->read(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), invName, MAX_NAME_LENGTH); + mEep->read(ADDR_INV_TYPE + i, &invType); + if(0ULL != invSerial) { + mSys->addInverter(invName, invSerial, invType); + Serial.println("add inverter: " + String(invName) + ", SN: " + String(invSerial, HEX) + ", type: " + String(invType)); + } + } + + mEep->read(ADDR_INV_INTERVAL, &interval); + if(interval < 1000) + interval = 1000; + mSendTicker->attach_ms(interval, std::bind(&app::sendTicker, this)); + + + // pinout + mEep->read(ADDR_PINOUT, &mSys->Radio.pinCs); + mEep->read(ADDR_PINOUT+1, &mSys->Radio.pinCe); + mEep->read(ADDR_PINOUT+2, &mSys->Radio.pinIrq); + + + // mqtt + uint8_t mqttAddr[MQTT_ADDR_LEN]; + char mqttUser[MQTT_USER_LEN]; + char mqttPwd[MQTT_PWD_LEN]; + char mqttTopic[MQTT_TOPIC_LEN]; + mEep->read(ADDR_MQTT_ADDR, mqttAddr, MQTT_ADDR_LEN); + mEep->read(ADDR_MQTT_USER, mqttUser, MQTT_USER_LEN); + mEep->read(ADDR_MQTT_PWD, mqttPwd, MQTT_PWD_LEN); + mEep->read(ADDR_MQTT_TOPIC, mqttTopic, MQTT_TOPIC_LEN); + mEep->read(ADDR_MQTT_INTERVAL, &interval); - if(mSettingsValid) - mEep->read(ADDR_HOY_ADDR, mHoymiles->mAddrBytes, HOY_ADDR_LEN); - else - memset(mHoymiles->mAddrBytes, 0, 6); - mHoymiles->serial2RadioId(); + char addr[16] = {0}; + sprintf(addr, "%d.%d.%d.%d", mqttAddr[0], mqttAddr[1], mqttAddr[2], mqttAddr[3]); - initRadio(); + if(interval < 1000) + interval = 1000; + mMqtt.setup(addr, mqttTopic, mqttUser, mqttPwd); + mMqttTicker = new Ticker(); + mMqttTicker->attach_ms(interval, std::bind(&app::mqttTicker, this)); - if(mSettingsValid) - mSendTicker->attach_ms(1000, std::bind(&app::sendTicker, this)); - else + mMqtt.sendMsg("version", mVersion); + } + + mSys->setup(); + + if(!mSettingsValid) Serial.println("Warn: your settings are not valid! check [IP]/setup"); } @@ -52,24 +101,32 @@ void app::setup(const char *ssid, const char *pwd, uint32_t timeout) { void app::loop(void) { Main::loop(); - if(!mBufCtrl->empty()) { + if(!mSys->BufCtrl.empty()) { uint8_t len, rptCnt; - NRF24_packet_t *p = mBufCtrl->getBack(); + packet_t *p = mSys->BufCtrl.getBack(); + //mSys->Radio.dumpBuf("RAW ", p->packet, MAX_RF_PAYLOAD_SIZE); - //mHoymiles->dumpBuf("RAW ", p->packet, PACKET_BUFFER_SIZE); - - if(mHoymiles->checkCrc(p->packet, &len, &rptCnt)) { + if(mSys->Radio.checkCrc(p->packet, &len, &rptCnt)) { // process buffer only on first occurrence if((0 != len) && (0 == rptCnt)) { - Serial.println("CMD " + String(p->packet[11], HEX)); - mHoymiles->dumpBuf("Payload ", p->packet, len); - // @TODO: do analysis here - if(p->packet[11] == 0x01) mCmds[0]++; - else if(p->packet[11] == 0x02) mCmds[1]++; - else if(p->packet[11] == 0x03) mCmds[2]++; - else if(p->packet[11] == 0x81) mCmds[3]++; - else if(p->packet[11] == 0x84) mCmds[4]++; - else mCmds[5]++; + uint8_t *cmd = &p->packet[11]; + //Serial.println("CMD " + String(*cmd, HEX)); + //mSys->Radio.dumpBuf("Payload ", p->packet, len); + + 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(*cmd == 0x01) mCmds[0]++; + else if(*cmd == 0x02) mCmds[1]++; + else if(*cmd == 0x03) mCmds[2]++; + else if(*cmd == 0x81) mCmds[3]++; + else if(*cmd == 0x84) mCmds[4]++; + else mCmds[5]++; if(p->sendCh == 23) mChannelStat[0]++; else if(p->sendCh == 40) mChannelStat[1]++; @@ -77,146 +134,81 @@ void app::loop(void) { else mChannelStat[3]++; } } - mBufCtrl->popBack(); + mSys->BufCtrl.popBack(); } if(mFlagSend) { mFlagSend = false; - - uint8_t size = 0; - if((mSendCnt % 6) == 0) - size = mHoymiles->getTimePacket(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)); - //dumpBuf(mSendBuf, size); - sendPacket(mSendBuf, size); - - mSendCnt++; + inverter_t *inv; + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { + inv = mSys->getInverterByPos(i); + if(NULL != inv) { + mSys->Radio.sendTimePacket(inv->radioId.u64, mTimestamp); + delay(20); + } + } } -} -//----------------------------------------------------------------------------- -void app::handleIntr(void) { - uint8_t lostCnt = 0, pipe, len; - NRF24_packet_t *p; - - DISABLE_IRQ; - - while(mRadio->available(&pipe)) { - if(!mBufCtrl->full()) { - p = mBufCtrl->getFront(); - memset(p->packet, 0xcc, MAX_RF_PAYLOAD_SIZE); - p->sendCh = mSendChannel; - len = mRadio->getPayloadSize(); - if(len > MAX_RF_PAYLOAD_SIZE) - len = MAX_RF_PAYLOAD_SIZE; - - mRadio->read(p->packet, len); - mBufCtrl->pushFront(p); - lostCnt = 0; + // mqtt + mMqtt.loop(); + if(mMqttEvt) { + mMqttEvt = false; + mMqtt.isConnected(true); + char topic[30], 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)) { + snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, fields[iv->assign[i].fieldId]); + snprintf(val, 10, "%.3f", mSys->getValue(iv, i)); + mMqtt.sendMsg(topic, val); + delay(20); + } + } + } } - else { - bool tx_ok, tx_fail, rx_ready; - if(lostCnt < 255) - lostCnt++; - mRadio->whatHappened(tx_ok, tx_fail, rx_ready); // reset interrupt status - mRadio->flush_rx(); // drop the packet + + // Serial debug + //char topic[30], 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)) { + snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, mSys->getFieldName(iv, i)); + snprintf(val, 10, "%.3f %s", mSys->getValue(iv, i), mSys->getUnit(iv, i)); + Serial.println(String(topic) + ": " + String(val)); + } + } + } } } - - RESTORE_IRQ; } //----------------------------------------------------------------------------- -void app::initRadio(void) { - mRadio = new RF24(RF24_CE_PIN, RF24_CS_PIN); - - mRadio->begin(); - mRadio->setAutoAck(false); - mRadio->setRetries(0, 0); - - mRadio->setChannel(DEFAULT_RECV_CHANNEL); - mRadio->setDataRate(RF24_250KBPS); - mRadio->disableCRC(); - mRadio->setAutoAck(false); - mRadio->setPayloadSize(MAX_RF_PAYLOAD_SIZE); - mRadio->setAddressWidth(5); - mRadio->openReadingPipe(1, DTU_RADIO_ID); - - // enable only receiving interrupts - mRadio->maskIRQ(true, true, false); - - // Use lo PA level, as a higher level will disturb CH340 serial usb adapter - mRadio->setPALevel(RF24_PA_MAX); - mRadio->startListening(); - - Serial.println("Radio Config:"); - mRadio->printPrettyDetails(); - - mSendChannel = mHoymiles->getDefaultChannel(); +void app::handleIntr(void) { + mSys->Radio.handleIntr(); } //----------------------------------------------------------------------------- -void app::sendPacket(uint8_t buf[], uint8_t len) { - DISABLE_IRQ; - mRadio->stopListening(); - -#ifdef CHANNEL_HOP - if(mSendCnt % 6 == 0) - mSendChannel = mHoymiles->getNxtChannel(); - else - mSendChannel = mHoymiles->getLastChannel(); -#else - mSendChannel = mHoymiles->getDefaultChannel(); -#endif - mRadio->setChannel(mSendChannel); - //Serial.println("CH: " + String(mSendChannel)); - - mRadio->openWritingPipe(mHoymiles->mRadioId); - mRadio->setCRCLength(RF24_CRC_16); - mRadio->enableDynamicPayloads(); - mRadio->setAutoAck(true); - mRadio->setRetries(3, 15); - - mRadio->write(buf, len); - - // Try to avoid zero payload acks (has no effect) - mRadio->openWritingPipe(DUMMY_RADIO_ID); // TODO: why dummy radio id? - - mRadio->setAutoAck(false); - mRadio->setRetries(0, 0); - mRadio->disableDynamicPayloads(); - mRadio->setCRCLength(RF24_CRC_DISABLED); - - mRadio->setChannel(DEFAULT_RECV_CHANNEL); - mRadio->startListening(); - - RESTORE_IRQ; +void app::sendTicker(void) { + mFlagSend = true; } //----------------------------------------------------------------------------- -void app::sendTicker(void) { - mFlagSend = true; +void app::mqttTicker(void) { + mMqttEvt = true; } //----------------------------------------------------------------------------- void app::showIndex(void) { - String html = index_html; + String html = FPSTR(index_html); html.replace("{DEVICE}", mDeviceName); html.replace("{VERSION}", mVersion); mWeb->send(200, "text/html", html); @@ -227,18 +219,95 @@ void app::showIndex(void) { void app::showSetup(void) { // overrides same method in main.cpp - String html = setup_html; + uint16_t interval; + + String html = FPSTR(setup_html); html.replace("{SSID}", mStationSsid); // PWD will be left at the default value (for protection) // -> 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("{HOY_ADDR}", String(addr)); - html.replace("{DEVICE}", String(mDeviceName)); html.replace("{VERSION}", String(mVersion)); + String inv; + uint64_t invSerial; + char invName[MAX_NAME_LENGTH + 1] = {0}; + uint8_t invType; + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { + mEep->read(ADDR_INV_ADDR + (i * 8), &invSerial); + mEep->read(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), invName, MAX_NAME_LENGTH); + mEep->read(ADDR_INV_TYPE + i, &invType); + inv += "

Inverter "+ String(i) + "

"; + + inv += ""; + inv += ""; + + inv += ""; + inv += ""; + + inv += ""; + inv += ""; + } + html.replace("{INVERTERS}", String(inv)); + + + // pinout + String pinout; + for(uint8_t i = 0; i < 3; i++) { + pinout += ""; + pinout += ""; + } + html.replace("{PINOUT}", String(pinout)); + + + if(mSettingsValid) { + mEep->read(ADDR_INV_INTERVAL, &interval); + html.replace("{INV_INTERVAL}", String(interval)); + + uint8_t mqttAddr[MQTT_ADDR_LEN] = {0}; + mEep->read(ADDR_MQTT_ADDR, mqttAddr, MQTT_ADDR_LEN); + mEep->read(ADDR_MQTT_INTERVAL, &interval); + + char addr[16] = {0}; + sprintf(addr, "%d.%d.%d.%d", mqttAddr[0], mqttAddr[1], mqttAddr[2], mqttAddr[3]); + html.replace("{MQTT_ADDR}", String(addr)); + html.replace("{MQTT_USER}", String(mMqtt.getUser())); + html.replace("{MQTT_PWD}", String(mMqtt.getPwd())); + html.replace("{MQTT_TOPIC}", String(mMqtt.getTopic())); + html.replace("{MQTT_INTERVAL}", String(interval)); + } + else { + html.replace("{INV_INTERVAL}", "1000"); + + html.replace("{MQTT_ADDR}", ""); + html.replace("{MQTT_USER}", ""); + html.replace("{MQTT_PWD}", ""); + html.replace("{MQTT_TOPIC}", "/inverter"); + html.replace("{MQTT_INTERVAL}", "10000"); + } + mWeb->send(200, "text/html", html); } @@ -268,25 +337,137 @@ void app::showCmdStatistics(void) { } +//----------------------------------------------------------------------------- +void app::showHoymiles(void) { + String html = FPSTR(hoymiles_html); + html.replace("{DEVICE}", mDeviceName); + html.replace("{VERSION}", mVersion); + mWeb->send(200, "text/html", html); +} + + +//----------------------------------------------------------------------------- +void app::showLiveData(void) { + String modHtml; + for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { + inverter_t *iv = mSys->getInverterByPos(id); + if(NULL != iv) { +#ifdef LIVEDATA_VISUALIZED + uint8_t modNum, pos; + switch(iv->type) { + default: modNum = 1; break; + case INV_TYPE_HM600: modNum = 2; break; + case INV_TYPE_HM1200: modNum = 4; break; + } + + for(uint8_t ch = 1; ch <= modNum; ch ++) { + modHtml += "
CHANNEL " + String(ch) + ""; + for(uint8_t j = 0; j < 5; j++) { + switch(j) { + default: pos = (mSys->getPosByChField(iv, ch, FLD_UDC)); break; + case 1: pos = (mSys->getPosByChField(iv, ch, FLD_IDC)); break; + case 2: pos = (mSys->getPosByChField(iv, ch, FLD_PDC)); break; + case 3: pos = (mSys->getPosByChField(iv, ch, FLD_YD)); break; + case 4: pos = (mSys->getPosByChField(iv, ch, FLD_YT)); break; + } + if(0xff != pos) { + modHtml += "" + String(mSys->getValue(iv, pos)); + modHtml += "" + String(mSys->getUnit(iv, pos)) + ""; + modHtml += "" + String(mSys->getFieldName(iv, pos)) + ""; + } + } + modHtml += "
"; + } +#else + // dump all data to web frontend + modHtml = "
";
+            char topic[30], val[10];
+            for(uint8_t i = 0; i < iv->listLen; i++) {
+                snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, mSys->getFieldName(iv, i));
+                snprintf(val, 10, "%.3f %s", mSys->getValue(iv, i), mSys->getUnit(iv, i));
+                modHtml += String(topic) + ": " + String(val) + "\n";
+            }
+            modHtml += "
"; +#endif + } + } + + + mWeb->send(200, "text/html", modHtml); +} + + +//----------------------------------------------------------------------------- +void app::showMqtt(void) { + String txt = "connected"; + if(mMqtt.isConnected()) + txt = "not " + txt; + mWeb->send(200, "text/plain", txt); +} + + //----------------------------------------------------------------------------- void app::saveValues(bool webSend = true) { Main::saveValues(false); // general configuration if(mWeb->args() > 0) { char *p; - char addr[20] = {0}; + char buf[20] = {0}; uint8_t i = 0; + uint16_t interval; + + // inverter + serial_u addr; + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { + // address + mWeb->arg("inv" + String(i) + "Addr").toCharArray(buf, 20); + if(strlen(buf) == 0) + snprintf(buf, 20, "\0"); + addr.u64 = Serial2u64(buf); + mEep->write(ADDR_INV_ADDR + (i * 8), addr.u64); + + // name + mWeb->arg("inv" + String(i) + "Name").toCharArray(buf, 20); + mEep->write(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), buf, MAX_NAME_LENGTH); + + // type + mWeb->arg("inv" + String(i) + "Type").toCharArray(buf, 20); + uint8_t type = atoi(buf); + mEep->write(ADDR_INV_TYPE + (i * MAX_NAME_LENGTH), type); + } - memset(mHoymiles->mAddrBytes, 0, 6); - mWeb->arg("hoy_addr").toCharArray(addr, 20); + interval = mWeb->arg("invInterval").toInt(); + mEep->write(ADDR_INV_INTERVAL, interval); - p = strtok(addr, ":"); - while(NULL != p) { - mHoymiles->mAddrBytes[i++] = strtol(p, NULL, 16); - p = strtok(NULL, ":"); + + // pinout + for(uint8_t i = 0; i < 3; i ++) { + uint8_t pin = mWeb->arg(String(pinArgNames[i])).toInt(); + mEep->write(ADDR_PINOUT + i, pin); } - mEep->write(ADDR_HOY_ADDR, mHoymiles->mAddrBytes, HOY_ADDR_LEN); + + // mqtt + uint8_t mqttAddr[MQTT_ADDR_LEN] = {0}; + char mqttUser[MQTT_USER_LEN]; + char mqttPwd[MQTT_PWD_LEN]; + char mqttTopic[MQTT_TOPIC_LEN]; + mWeb->arg("mqttAddr").toCharArray(buf, 20); + i = 0; + p = strtok(buf, "."); + while(NULL != p) { + mqttAddr[i++] = atoi(p); + p = strtok(NULL, "."); + } + mWeb->arg("mqttUser").toCharArray(mqttUser, MQTT_USER_LEN); + mWeb->arg("mqttPwd").toCharArray(mqttPwd, MQTT_PWD_LEN); + mWeb->arg("mqttTopic").toCharArray(mqttTopic, MQTT_TOPIC_LEN); + interval = mWeb->arg("mqttInterval").toInt(); + mEep->write(ADDR_MQTT_ADDR, mqttAddr, MQTT_ADDR_LEN); + mEep->write(ADDR_MQTT_USER, mqttUser, MQTT_USER_LEN); + mEep->write(ADDR_MQTT_PWD, mqttPwd, MQTT_PWD_LEN); + mEep->write(ADDR_MQTT_TOPIC, mqttTopic, MQTT_TOPIC_LEN); + mEep->write(ADDR_MQTT_INTERVAL, interval); updateCrc(); if((mWeb->arg("reboot") == "on")) @@ -304,11 +485,11 @@ void app::saveValues(bool webSend = true) { //----------------------------------------------------------------------------- -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(); +void app::updateCrc(void) { + Main::updateCrc(); + + uint16_t crc; + crc = buildEEpCrc(ADDR_START_SETTINGS, (ADDR_NEXT - ADDR_START_SETTINGS)); + //Serial.println("new CRC: " + String(crc, HEX)); + mEep->write(ADDR_SETTINGS_CRC, crc); } diff --git a/tools/esp8266/app.h b/tools/esp8266/app.h index 01883643..c4cabcdd 100644 --- a/tools/esp8266/app.h +++ b/tools/esp8266/app.h @@ -8,8 +8,19 @@ #include "main.h" #include "CircularBuffer.h" -#include "hoymiles.h" +#include "hmSystem.h" +#include "mqtt.h" +typedef CircularBuffer BufferType; +typedef HmRadio RadioType; +typedef HmSystem HmSystemType; + +const char* const wemosPins[] = {"D3 (GPIO0)", "TX (GPIO1)", "D4 (GPIO2)", "RX (GPIO3)", + "D2 (GPIO4)", "D1 (GPIO5)", "GPIO6", "GPIO7", "GPIO8", + "GPIO9", "GPIO10", "GPIO11", "D6 (GPIO12)", "D7 (GPIO13)", + "D5 (GPIO14)", "D8 (GPIO15)", "D0 (GPIO16)"}; +const char* const pinNames[] = {"CS", "CE", "IRQ"}; +const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq"}; class app : public Main { public: @@ -20,37 +31,56 @@ class app : public Main { void loop(void); void handleIntr(void); - private: - void initRadio(void); - void sendPacket(uint8_t data[], uint8_t length); + uint8_t getIrqPin(void) { + return mSys->Radio.pinIrq; + } + private: void sendTicker(void); + void mqttTicker(void); void showIndex(void); void showSetup(void); void showSave(void); void showCmdStatistics(void); + void showHoymiles(void); + void showLiveData(void); + void showMqtt(void); void saveValues(bool webSend); - void dumpBuf(uint8_t buf[], uint8_t len); + void updateCrc(void); + + 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]; + if((tmp[0] == '\0') || (tmp[1] == '\0')) + break; + u64 = strtol(tmp, NULL, 16); + ret |= (u64 << ((5-i) << 3)); + } + return ret; + } uint8_t mState; bool mKeyPressed; - RF24 *mRadio; - hoymiles *mHoymiles; - CircularBuffer *mBufCtrl; - NRF24_packet_t mBuffer[PACKET_BUFFER_SIZE]; - + HmSystemType *mSys; Ticker *mSendTicker; - uint32_t mSendCnt; - uint8_t mSendBuf[MAX_RF_PAYLOAD_SIZE]; bool mFlagSend; - uint8_t mSendChannel; uint32_t mCmds[6]; uint32_t mChannelStat[4]; + uint32_t mRecCnt; + + // mqtt + mqtt mMqtt; + Ticker *mMqttTicker; + bool mMqttEvt; }; #endif /*__APP_H__*/ diff --git a/tools/esp8266/debug.h b/tools/esp8266/debug.h new file mode 100644 index 00000000..11e8e071 --- /dev/null +++ b/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 + inline void DPRINT(T str) { DSERIAL.print(str); } + template + 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__*/ diff --git a/tools/esp8266/defines.h b/tools/esp8266/defines.h index 4939da5b..765ba2b2 100644 --- a/tools/esp8266/defines.h +++ b/tools/esp8266/defines.h @@ -5,37 +5,84 @@ //------------------------------------- // PINOUT //------------------------------------- -#define RF24_IRQ_PIN 4 -#define RF24_CE_PIN 5 #define RF24_CS_PIN 15 +#define RF24_CE_PIN 2 +#define RF24_IRQ_PIN 0 + + +//------------------------------------- +// CONFIGURATION - COMPILE TIME +//------------------------------------- +#define PACKET_BUFFER_SIZE 30 +#define MAX_NUM_INVERTERS 3 +#define MAX_NAME_LENGTH 16 +#define MAX_RF_PAYLOAD_SIZE 64 +#define LIVEDATA_VISUALIZED // show live data pv-module wise or as dump //------------------------------------- // VERSION //------------------------------------- #define VERSION_MAJOR 0 -#define VERSION_MINOR 1 -#define VERSION_PATCH 9 +#define VERSION_MINOR 2 +#define VERSION_PATCH 4 + + +//------------------------------------- +typedef struct { + uint8_t sendCh; + uint8_t packet[MAX_RF_PAYLOAD_SIZE]; +} packet_t; //------------------------------------- // EEPROM //------------------------------------- #define SSID_LEN 32 -#define PWD_LEN 64 -#define DEVNAME_LEN 32 -#define CRC_LEN 2 +#define PWD_LEN 32 +#define DEVNAME_LEN 16 +#define CRC_LEN 2 // uint16_t + +#define INV_ADDR_LEN MAX_NUM_INVERTERS * 8 // uint64_t +#define INV_NAME_LEN MAX_NUM_INVERTERS * MAX_NAME_LENGTH // char[] +#define INV_TYPE_LEN MAX_NUM_INVERTERS * 1 // uint8_t +#define INV_INTERVAL_LEN 2 // uint16_t + +#define PINOUT_LEN 3 // 3 pins: CS, CE, IRQ + +#define MQTT_ADDR_LEN 4 // IP +#define MQTT_USER_LEN 16 +#define MQTT_PWD_LEN 32 +#define MQTT_TOPIC_LEN 32 +#define MQTT_INTERVAL_LEN 2 // uint16_t -#define HOY_ADDR_LEN 6 #define ADDR_START 0 #define ADDR_SSID ADDR_START #define ADDR_PWD ADDR_SSID + SSID_LEN #define ADDR_DEVNAME ADDR_PWD + PWD_LEN -#define ADDR_HOY_ADDR ADDR_DEVNAME + DEVNAME_LEN +#define ADDR_WIFI_CRC ADDR_DEVNAME + DEVNAME_LEN +#define ADDR_START_SETTINGS ADDR_WIFI_CRC + CRC_LEN + +#define ADDR_PINOUT ADDR_START_SETTINGS + +#define ADDR_INV_ADDR ADDR_PINOUT + PINOUT_LEN +#define ADDR_INV_NAME ADDR_INV_ADDR + INV_ADDR_LEN +#define ADDR_INV_TYPE ADDR_INV_NAME + INV_NAME_LEN +#define ADDR_INV_INTERVAL ADDR_INV_TYPE + INV_TYPE_LEN + +#define ADDR_MQTT_ADDR ADDR_INV_INTERVAL + INV_INTERVAL_LEN +#define ADDR_MQTT_USER ADDR_MQTT_ADDR + MQTT_ADDR_LEN +#define ADDR_MQTT_PWD ADDR_MQTT_USER + MQTT_USER_LEN +#define ADDR_MQTT_TOPIC ADDR_MQTT_PWD + MQTT_PWD_LEN +#define ADDR_MQTT_INTERVAL ADDR_MQTT_TOPIC + MQTT_TOPIC_LEN + +#define ADDR_NEXT ADDR_MQTT_INTERVAL + MQTT_INTERVAL_LEN -#define ADDR_NEXT ADDR_HOY_ADDR + HOY_ADDR_LEN +#define ADDR_SETTINGS_CRC 400 -#define ADDR_SETTINGS_CRC 200 +#if(ADDR_SETTINGS_CRC <= ADDR_NEXT) +#error address overlap! +#endif #endif /*__DEFINES_H__*/ diff --git a/tools/esp8266/eep.cpp b/tools/esp8266/eep.cpp deleted file mode 100644 index 97a3c600..00000000 --- a/tools/esp8266/eep.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#include "eep.h" -#include - - -//----------------------------------------------------------------------------- -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(); -} diff --git a/tools/esp8266/eep.h b/tools/esp8266/eep.h index 36b03914..da1450b0 100644 --- a/tools/esp8266/eep.h +++ b/tools/esp8266/eep.h @@ -2,29 +2,132 @@ #define __EEP_H__ #include "Arduino.h" +#include class eep { public: - eep(); - ~eep(); - - void read(uint32_t addr, char *str, uint8_t length); - void read(uint32_t addr, float *value); - 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: + eep() { + EEPROM.begin(500); + } + ~eep() { + EEPROM.end(); + } + 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[], uint16_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[], uint16_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__*/ diff --git a/tools/esp8266/esp8266.ino b/tools/esp8266/esp8266.ino index 2dcc84c3..5f22245e 100644 --- a/tools/esp8266/esp8266.ino +++ b/tools/esp8266/esp8266.ino @@ -1,14 +1,23 @@ + +#include "Arduino.h" + +#include +#include +#include +#include + +#include #include "app.h" app myApp; //----------------------------------------------------------------------------- void setup() { - pinMode(RF24_IRQ_PIN, INPUT_PULLUP); - attachInterrupt(digitalPinToInterrupt(RF24_IRQ_PIN), handleIntr, FALLING); - // AP name, password, timeout myApp.setup("ESP AHOY", "esp_8266", 15); + + // TODO: move to HmRadio + attachInterrupt(digitalPinToInterrupt(myApp.getIrqPin()), handleIntr, FALLING); } @@ -22,4 +31,3 @@ void loop() { ICACHE_RAM_ATTR void handleIntr(void) { myApp.handleIntr(); } - diff --git a/tools/esp8266/hmInverters.h b/tools/esp8266/hmInverters.h new file mode 100644 index 00000000..1da3f62c --- /dev/null +++ b/tools/esp8266/hmInverters.h @@ -0,0 +1,130 @@ +#ifndef __HM_INVERTERS_H__ +#define __HM_INVERTERS_H__ + +#include "debug.h" +#include + +// 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, CMD82 = 0x82, CMD83, CMD84}; + +enum {INV_TYPE_HM600 = 0, INV_TYPE_HM1200, INV_TYPE_HM400}; +const char* const invTypes[] = {"HM600", "HM1200", "HM400"}; +#define NUM_INVERTER_TYPES 3 + +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[MAX_NAME_LENGTH]; // 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) + * */ + +//------------------------------------- +// HM400 HM350?, HM300? +//------------------------------------- +const byteAssign_t hm400assignment[] = { + { 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_YT, UNIT_KWH, CH1, CMD01, 20, 4, 1000 }, + { FLD_YD, UNIT_WH, CH1, CMD01, 24, 2, 1000 }, + { FLD_UAC, UNIT_V, CH0, CMD01, 26, 2, 10 }, + { FLD_F, UNIT_HZ, CH0, CMD82, 12, 2, 100 }, + { FLD_PAC, UNIT_W, CH0, CMD82, 14, 2, 10 }, + { FLD_IAC, UNIT_A, CH0, CMD82, 18, 2, 100 }, + { FLD_T, UNIT_C, CH0, CMD82, 22, 2, 10 } +}; +#define HM400_LIST_LEN (sizeof(hm400assignment) / sizeof(byteAssign_t)) + + +//------------------------------------- +// 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_PDC, UNIT_W, CH1, CMD01, 9, 2, 10 }, + { FLD_YD, UNIT_WH, CH1, CMD02, 5, 2, 1 }, + { FLD_YT, UNIT_KWH, CH1, CMD01, 13, 4, 1000 }, + { FLD_UDC, UNIT_V, CH2, CMD02, 9, 2, 10 }, + { FLD_IDC, UNIT_A, CH2, CMD01, 7, 2, 100 }, + { FLD_PDC, UNIT_W, CH2, CMD01, 11, 2, 10 }, + { FLD_YD, UNIT_WH, CH2, CMD02, 7, 2, 1 }, + { FLD_YT, UNIT_KWH, CH2, CMD02, 1, 4, 1000 }, + { FLD_IDC, UNIT_A, CH3, CMD02, 11, 2, 100 }, + { FLD_PDC, UNIT_W, CH3, CMD02, 15, 2, 10 }, + { FLD_YD, UNIT_WH, CH3, CMD03, 11, 2, 1 }, + { FLD_YT, UNIT_KWH, CH3, CMD03, 3, 4, 1000 }, + { FLD_IDC, UNIT_A, CH4, CMD02, 13, 2, 100 }, + { FLD_PDC, UNIT_W, CH4, CMD03, 1, 2, 10 }, + { FLD_YD, UNIT_WH, CH4, CMD03, 13, 2, 1 }, + { FLD_YT, UNIT_KWH, CH4, CMD03, 7, 4, 1000 }, + { FLD_UAC, UNIT_V, CH0, CMD03, 15, 2, 10 }, + { FLD_IAC, UNIT_A, CH0, CMD84, 7, 2, 100 }, + { FLD_PAC, UNIT_W, CH0, CMD84, 3, 2, 10 }, + { FLD_F, UNIT_HZ, CH0, CMD84, 1, 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__*/ diff --git a/tools/esp8266/hmRadio.h b/tools/esp8266/hmRadio.h new file mode 100644 index 00000000..70e710e0 --- /dev/null +++ b/tools/esp8266/hmRadio.h @@ -0,0 +1,255 @@ +#ifndef __RADIO_H__ +#define __RADIO_H__ + +#include +#include +#include "crc.h" + +//#define CHANNEL_HOP // switch between channels or use static channel to send + +#define DEFAULT_RECV_CHANNEL 3 + +#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL) +#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL) + + +//----------------------------------------------------------------------------- +// MACROS +//----------------------------------------------------------------------------- +#define CP_U32_LittleEndian(buf, v) ({ \ + uint8_t *b = buf; \ + b[0] = ((v >> 24) & 0xff); \ + b[1] = ((v >> 16) & 0xff); \ + b[2] = ((v >> 8) & 0xff); \ + b[3] = ((v ) & 0xff); \ +}) + +#define CP_U32_BigEndian(buf, v) ({ \ + uint8_t *b = buf; \ + b[3] = ((v >> 24) & 0xff); \ + b[2] = ((v >> 16) & 0xff); \ + b[1] = ((v >> 8) & 0xff); \ + b[0] = ((v ) & 0xff); \ +}) + +#define BIT_CNT(x) ((x)<<3) + + +//----------------------------------------------------------------------------- +// HM Radio class +//----------------------------------------------------------------------------- +template +class HmRadio { + public: + HmRadio() : mNrf24(CE_PIN, CS_PIN) { + mChanOut[0] = 23; + mChanOut[1] = 40; + mChanOut[2] = 61; + mChanOut[3] = 75; + mChanIdx = 1; + + calcDtuCrc(); + + pinCs = CS_PIN; + pinCe = CE_PIN; + pinIrq = IRQ_PIN; + + mSendCnt = 0; + } + ~HmRadio() {} + + void setup(BUFFER *ctrl) { + //Serial.println("HmRadio::setup, pins: " + String(pinCs) + ", " + String(pinCe) + ", " + String(pinIrq)); + pinMode(pinIrq, INPUT_PULLUP); + + mBufCtrl = ctrl; + + mNrf24.begin(pinCe, pinCs); + mNrf24.setAutoAck(false); + mNrf24.setRetries(0, 0); + + mNrf24.setChannel(DEFAULT_RECV_CHANNEL); + mNrf24.setDataRate(RF24_250KBPS); + mNrf24.disableCRC(); + mNrf24.setAutoAck(false); + mNrf24.setPayloadSize(MAX_RF_PAYLOAD_SIZE); + mNrf24.setAddressWidth(5); + mNrf24.openReadingPipe(1, DTU_RADIO_ID); + + // enable only receiving interrupts + mNrf24.maskIRQ(true, true, false); + + // Use lo PA level, as a higher level will disturb CH340 serial usb adapter + mNrf24.setPALevel(RF24_PA_MAX); + mNrf24.startListening(); + + Serial.println("Radio Config:"); + mNrf24.printPrettyDetails(); + + mSendChannel = getDefaultChannel(); + } + + void handleIntr(void) { + uint8_t pipe, len; + packet_t *p; + + DISABLE_IRQ; + while(mNrf24.available(&pipe)) { + if(!mBufCtrl->full()) { + p = mBufCtrl->getFront(); + memset(p->packet, 0xcc, MAX_RF_PAYLOAD_SIZE); + p->sendCh = mSendChannel; + len = mNrf24.getPayloadSize(); + if(len > MAX_RF_PAYLOAD_SIZE) + len = MAX_RF_PAYLOAD_SIZE; + + mNrf24.read(p->packet, len); + mBufCtrl->pushFront(p); + } + else { + bool tx_ok, tx_fail, rx_ready; + mNrf24.whatHappened(tx_ok, tx_fail, rx_ready); // reset interrupt status + mNrf24.flush_rx(); // drop the packet + } + } + RESTORE_IRQ; + } + + uint8_t getDefaultChannel(void) { + return mChanOut[2]; + } + uint8_t getLastChannel(void) { + return mChanOut[mChanIdx]; + } + + uint8_t getNxtChannel(void) { + if(++mChanIdx >= 4) + mChanIdx = 0; + return mChanOut[mChanIdx]; + } + + void sendTimePacket(uint64_t invId, uint32_t ts) { + sendCmdPacket(invId, 0x15, 0x80, false); + mSendBuf[10] = 0x0b; // cid + mSendBuf[11] = 0x00; + CP_U32_LittleEndian(&mSendBuf[12], ts); + mSendBuf[19] = 0x05; + + uint16_t crc = crc16(&mSendBuf[10], 14); + mSendBuf[24] = (crc >> 8) & 0xff; + mSendBuf[25] = (crc ) & 0xff; + mSendBuf[26] = crc8(mSendBuf, 26); + + sendPacket(invId, mSendBuf, 27); + } + + void sendCmdPacket(uint64_t invId, uint8_t mid, uint8_t cmd, bool calcCrc = true) { + memset(mSendBuf, 0, MAX_RF_PAYLOAD_SIZE); + mSendBuf[0] = mid; // message id + CP_U32_BigEndian(&mSendBuf[1], (invId >> 8)); + CP_U32_BigEndian(&mSendBuf[5], (DTU_ID >> 8)); + mSendBuf[9] = cmd; + if(calcCrc) { + mSendBuf[10] = crc8(mSendBuf, 10); + sendPacket(invId, mSendBuf, 11); + } + } + + bool checkCrc(uint8_t buf[], uint8_t *len, uint8_t *rptCnt) { + *len = (buf[0] >> 2); + for (int16_t i = MAX_RF_PAYLOAD_SIZE - 1; i >= 0; i--) { + buf[i] = ((buf[i] >> 7) | ((i > 0) ? (buf[i-1] << 1) : 0x00)); + } + uint16_t crc = crc16nrf24(buf, BIT_CNT(*len + 2), 7, mDtuIdCrc); + + bool valid = (crc == ((buf[*len+2] << 8) | (buf[*len+3]))); + + if(valid) { + if(mLastCrc == crc) + *rptCnt = (++mRptCnt); + else { + mRptCnt = 0; + *rptCnt = 0; + mLastCrc = crc; + } + } + + return valid; + } + + uint8_t pinCs; + uint8_t pinCe; + uint8_t pinIrq; + + private: + void sendPacket(uint64_t invId, uint8_t buf[], uint8_t len) { + //Serial.println("sent packet: #" + String(mSendCnt)); + //dumpBuf("SEN ", buf, len); + + DISABLE_IRQ; + mNrf24.stopListening(); + + #ifdef CHANNEL_HOP + mSendChannel = getNxtChannel(); + #else + mSendChannel = getDefaultChannel(); + #endif + mNrf24.setChannel(mSendChannel); + //Serial.println("CH: " + String(mSendChannel)); + + mNrf24.openWritingPipe(invId); // TODO: deprecated + mNrf24.setCRCLength(RF24_CRC_16); + mNrf24.enableDynamicPayloads(); + mNrf24.setAutoAck(true); + mNrf24.setRetries(3, 15); + + mNrf24.write(buf, len); + + // Try to avoid zero payload acks (has no effect) + mNrf24.openWritingPipe(DUMMY_RADIO_ID); // TODO: why dummy radio id?, deprecated + + mNrf24.setAutoAck(false); + mNrf24.setRetries(0, 0); + mNrf24.disableDynamicPayloads(); + mNrf24.setCRCLength(RF24_CRC_DISABLED); + + mNrf24.setChannel(DEFAULT_RECV_CHANNEL); + mNrf24.startListening(); + + RESTORE_IRQ; + mSendCnt++; + } + + void calcDtuCrc(void) { + uint64_t addr = DTU_RADIO_ID; + uint8_t tmp[5]; + for(int8_t i = 4; i >= 0; i--) { + tmp[i] = addr; + addr >>= 8; + } + mDtuIdCrc = crc16nrf24(tmp, BIT_CNT(5)); + } + + 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(); + } + + uint8_t mChanOut[4]; + uint8_t mChanIdx; + uint16_t mDtuIdCrc; + uint16_t mLastCrc; + uint8_t mRptCnt; + + RF24 mNrf24; + uint8_t mSendChannel; + BUFFER *mBufCtrl; + uint32_t mSendCnt; + uint8_t mSendBuf[MAX_RF_PAYLOAD_SIZE]; +}; + +#endif /*__RADIO_H__*/ diff --git a/tools/esp8266/hmSystem.h b/tools/esp8266/hmSystem.h new file mode 100644 index 00000000..0d40803f --- /dev/null +++ b/tools/esp8266/hmSystem.h @@ -0,0 +1,163 @@ +#ifndef __HM_SYSTEM_H__ +#define __HM_SYSTEM_H__ + +#include "hmInverters.h" +#include "hmRadio.h" + + + +template +class HmSystem { + public: + typedef RADIO RadioType; + RadioType Radio; + typedef BUFFER BufferType; + BufferType BufCtrl; + + HmSystem() { + mNumInv = 0; + } + ~HmSystem() { + // TODO: cleanup + } + + void setup() { + Radio.setup(&BufCtrl); + } + + 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 > MAX_NAME_LENGTH) ? MAX_NAME_LENGTH : len); + getAssignment(p); + toRadioId(p); + + if(NULL == p->assign) { + DPRINT("no assignment for type found!"); + return NULL; + } + else { + mRecord[p->id] = new RECORDTYPE[p->listLen]; + memset(mRecord[p->id], 0, sizeof(RECORDTYPE) * p->listLen); + mNumInv ++; + 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[p->id][pos] = (RECORDTYPE)(val) / (RECORDTYPE)(div); + } + + RECORDTYPE getValue(inverter_t *p, uint8_t pos) { + return mRecord[p->id][pos]; + } + + uint8_t getPosByChField(inverter_t *p, uint8_t channel, uint8_t fieldId) { + uint8_t pos = 0; + for(; pos < p->listLen; pos++) { + if((p->assign[pos].ch == channel) && (p->assign[pos].fieldId == fieldId)) + break; + } + return (pos >= p->listLen) ? 0xff : 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 if(INV_TYPE_HM400 == p->type) { + p->listLen = (uint8_t)(HM400_LIST_LEN); + p->assign = (byteAssign_t*)hm400assignment; + } + else { + p->listLen = 0; + p->assign = NULL; + } + } + + inverter_t mInverter[MAX_INVERTER]; + uint8_t mNumInv; + RECORDTYPE *mRecord[MAX_INVERTER]; +}; + +#endif /*__HM_SYSTEM_H__*/ diff --git a/tools/esp8266/hoymiles.h b/tools/esp8266/hoymiles.h deleted file mode 100644 index 66b80c3c..00000000 --- a/tools/esp8266/hoymiles.h +++ /dev/null @@ -1,178 +0,0 @@ -#ifndef __HOYMILES_H__ -#define __HOYMILES_H__ - -#include -#include -#include "crc.h" - -#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 MAX_RF_PAYLOAD_SIZE 32 -#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL) -#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL) - -#define PACKET_BUFFER_SIZE 30 - - -//----------------------------------------------------------------------------- -// MACROS -#define CP_U32_LittleEndian(buf, v) ({ \ - uint8_t *b = buf; \ - b[0] = ((v >> 24) & 0xff); \ - b[1] = ((v >> 16) & 0xff); \ - b[2] = ((v >> 8) & 0xff); \ - b[3] = ((v ) & 0xff); \ -}) - -#define CP_U32_BigEndian(buf, v) ({ \ - uint8_t *b = buf; \ - b[3] = ((v >> 24) & 0xff); \ - b[2] = ((v >> 16) & 0xff); \ - b[1] = ((v >> 8) & 0xff); \ - b[0] = ((v ) & 0xff); \ -}) - -#define BIT_CNT(x) ((x)<<3) - - -//----------------------------------------------------------------------------- -union uint64Bytes { - uint64_t ull; - uint8_t bytes[8]; -}; - -typedef struct { - uint8_t sendCh; - uint8_t packet[MAX_RF_PAYLOAD_SIZE]; -} NRF24_packet_t; - - -//----------------------------------------------------------------------------- -class hoymiles { - public: - hoymiles() { - serial2RadioId(); - calcDtuIdCrc(); - - mChannels[0] = 23; - mChannels[1] = 40; - mChannels[2] = 61; - mChannels[3] = 75; - mChanIdx = 1; - - mLastCrc = 0x0000; - mRptCnt = 0; - } - - ~hoymiles() {} - - uint8_t getDefaultChannel(void) { - return mChannels[2]; - } - uint8_t getLastChannel(void) { - return mChannels[mChanIdx]; - } - - uint8_t getNxtChannel(void) { - if(++mChanIdx >= 4) - mChanIdx = 0; - return mChannels[mChanIdx]; - } - - void serial2RadioId(void) { - uint64Bytes id; - - 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[11] = 0x00; - CP_U32_LittleEndian(&buf[12], ts); - buf[19] = 0x05; - - uint16_t crc = crc16(&buf[10], 14); - buf[24] = (crc >> 8) & 0xff; - buf[25] = (crc ) & 0xff; - buf[26] = crc8(buf, 26); - - return 27; - } - - uint8_t getCmdPacket(uint8_t buf[], uint8_t mid, uint8_t cmd, bool calcCrc = true) { - memset(buf, 0, MAX_RF_PAYLOAD_SIZE); - buf[0] = mid; // message id - CP_U32_BigEndian(&buf[1], (mRadioId >> 8)); - CP_U32_BigEndian(&buf[5], (DTU_RADIO_ID >> 8)); - buf[9] = cmd; - if(calcCrc) - buf[10] = crc8(buf, 10); - - return 11; - } - - bool checkCrc(uint8_t buf[], uint8_t *len, uint8_t *rptCnt) { - *len = (buf[0] >> 2); - for (int16_t i = MAX_RF_PAYLOAD_SIZE - 1; i >= 0; i--) { - buf[i] = ((buf[i] >> 7) | ((i > 0) ? (buf[i-1] << 1) : 0x00)); - } - uint16_t crc = crc16nrf24(buf, BIT_CNT(*len + 2), 7, mDtuIdCrc); - - bool valid = (crc == ((buf[*len+2] << 8) | (buf[*len+3]))); - - if(valid) { - if(mLastCrc == crc) - *rptCnt = (++mRptCnt); - else { - mRptCnt = 0; - *rptCnt = 0; - mLastCrc = crc; - } - } - - return valid; - } - - 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(); - } - - uint8_t mAddrBytes[6]; - luint64_t mRadioId; - - private: - void calcDtuIdCrc(void) { - uint64_t addr = DTU_RADIO_ID; - uint8_t dtuAddr[5]; - for(int8_t i = 4; i >= 0; i--) { - dtuAddr[i] = addr; - addr >>= 8; - } - mDtuIdCrc = crc16nrf24(dtuAddr, BIT_CNT(5)); - } - - - uint8_t mChannels[4]; - uint8_t mChanIdx; - uint16_t mDtuIdCrc; - uint16_t mLastCrc; - uint8_t mRptCnt; -}; - -#endif /*__HOYMILES_H__*/ diff --git a/tools/esp8266/html/conv.bat b/tools/esp8266/html/conv.bat deleted file mode 100644 index 46587400..00000000 --- a/tools/esp8266/html/conv.bat +++ /dev/null @@ -1,4 +0,0 @@ -..\tools\fileConv.exe index.html h\index_html.h index_html -..\tools\fileConv.exe setup.html h\setup_html.h setup_html -..\tools\fileConv.exe style.css h\style_css.h style_css -pause diff --git a/tools/esp8266/html/convert.py b/tools/esp8266/html/convert.py new file mode 100644 index 00000000..4b58681f --- /dev/null +++ b/tools/esp8266/html/convert.py @@ -0,0 +1,28 @@ +import re + +def convert2Header(inFile): + outName = "h/" + inFile.replace(".", "_") + ".h" + fileType = inFile.split(".")[1] + + f = open(inFile, "r") + data = f.read().replace('\n', '') + f.close() + if fileType == "html": + data = re.sub(r"\>\s+\<", '><', data) # whitespaces between xml tags + data = re.sub(r"(\;|\}|\>|\{)\s+", r'\1', data) # whitespaces inner javascript + data = re.sub(r"\"", '\\\"', data) # escape quotation marks + else: + data = re.sub(r"(\;|\}|\:|\{)\s+", r'\1', data) # whitespaces inner css + + define = inFile.split(".")[0].upper() + f = open(outName, "w") + f.write("#ifndef __{}_H__\n".format(define)) + f.write("#define __{}_H__\n".format(define)) + f.write("const char {}[] PROGMEM = \"{}\";\n".format(inFile.replace(".", "_"), data)) + f.write("#endif /*__{}_H__*/\n".format(define)) + f.close() + +convert2Header("index.html") +convert2Header("setup.html") +convert2Header("hoymiles.html") +convert2Header("style.css") diff --git a/tools/esp8266/html/h/hoymiles_html.h b/tools/esp8266/html/h/hoymiles_html.h new file mode 100644 index 00000000..594e4b25 --- /dev/null +++ b/tools/esp8266/html/h/hoymiles_html.h @@ -0,0 +1,4 @@ +#ifndef __HOYMILES_H__ +#define __HOYMILES_H__ +const char hoymiles_html[] PROGMEM = "Index - {DEVICE}

AHOY - {DEVICE}

Home

Every 10 seconds the values are updated

© 2022

AHOY :: {VERSION}

"; +#endif /*__HOYMILES_H__*/ diff --git a/tools/esp8266/html/h/index_html.h b/tools/esp8266/html/h/index_html.h index 8c2921e0..53eeda89 100644 --- a/tools/esp8266/html/h/index_html.h +++ b/tools/esp8266/html/h/index_html.h @@ -1 +1,4 @@ -String index_html = "Index - {DEVICE}

AHOY - {DEVICE}

Update

Setup
Reboot

Uptime:

Time:

Statistics:

© 2022

AHOY :: {VERSION}

"; +#ifndef __INDEX_H__ +#define __INDEX_H__ +const char index_html[] PROGMEM = "Index - {DEVICE}

AHOY - {DEVICE}

Hoymiles
Update

Setup
Reboot

Uptime:

Time:

MQTT:

Statistics:

© 2022

AHOY :: {VERSION}

"; +#endif /*__INDEX_H__*/ diff --git a/tools/esp8266/html/h/setup_html.h b/tools/esp8266/html/h/setup_html.h index 24440e88..f4e99a1a 100644 --- a/tools/esp8266/html/h/setup_html.h +++ b/tools/esp8266/html/h/setup_html.h @@ -1 +1,4 @@ -String setup_html = "Setup - {DEVICE}

Setup

Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.

WiFi

SSID
PASSWORD

Device Host Name

DEVICE NAME

General

HOYMILES ADDRESS (eg. 11:22:33:44:55:66)

Home

Update Firmware

AHOY - {VERSION}

"; +#ifndef __SETUP_H__ +#define __SETUP_H__ +const char setup_html[] PROGMEM = "Setup - {DEVICE}

Setup

Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.

WiFi

Device Host Name

Inverter

{INVERTERS}

General

Pinout (Wemos)

{PINOUT}

MQTT

 

Home

Update Firmware

AHOY - {VERSION}

"; +#endif /*__SETUP_H__*/ diff --git a/tools/esp8266/html/h/style_css.h b/tools/esp8266/html/h/style_css.h index be1e0761..cf8ac694 100644 --- a/tools/esp8266/html/h/style_css.h +++ b/tools/esp8266/html/h/style_css.h @@ -1 +1,4 @@ -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; } "; +#ifndef __STYLE_H__ +#define __STYLE_H__ +const char style_css[] PROGMEM = "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, select {padding:7px;font-size:13pt;}input.text, select {width:70%;box-sizing:border-box;margin-bottom:10px;border:1px solid #ccc;}input.btn {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.ch {width:250px;height:410px;background-color:#006ec0;display:inline-block;margin-right:20px;margin-bottom:20px;}div.ch .value, div.ch .info, div.ch .head {color:#fff;display:block;width:100%;text-align:center;}div.ch .unit {font-size:19px;margin-left:10px;}div.ch .value {margin-top:20px;font-size:30px;}div.ch .info {margin-top:3px;font-size:10px;}div.ch .head {background-color:#003c80;padding:10px 0 10px 0;}"; +#endif /*__STYLE_H__*/ diff --git a/tools/esp8266/html/hoymiles.html b/tools/esp8266/html/hoymiles.html new file mode 100644 index 00000000..4ac61f25 --- /dev/null +++ b/tools/esp8266/html/hoymiles.html @@ -0,0 +1,42 @@ + + + + Index - {DEVICE} + + + + + + +

AHOY - {DEVICE}

+
+

Home

+
+

Every 10 seconds the values are updated

+
+ + + diff --git a/tools/esp8266/html/index.html b/tools/esp8266/html/index.html index 2cd0b1b6..3eee3360 100644 --- a/tools/esp8266/html/index.html +++ b/tools/esp8266/html/index.html @@ -7,6 +7,7 @@