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.
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 T> 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 BUFFERTYPE, uint8_t BUFFERSIZE>
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

278
tools/esp8266/app.cpp

@ -4,12 +4,11 @@
#include "html/h/hoymiles_html.h"
extern String setup_html;
#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL)
//-----------------------------------------------------------------------------
app::app() : Main() {
mHoymiles = new hoymiles();
mDecoder = new hm1200Decode();
mBufCtrl = new CircularBuffer(mBuffer, PACKET_BUFFER_SIZE);
mSendCnt = 0;
mSendTicker = new Ticker();
mFlagSend = false;
@ -19,6 +18,8 @@ app::app() : Main() {
memset(mCmds, 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) {
uint16_t interval;
uint64_t invSerial;
// hoymiles
mEep->read(ADDR_INV0_ADDR, mHoymiles->mAddrBytes, INV_ADDR_LEN);
mEep->read(ADDR_INV0_ADDR, &invSerial);
mEep->read(ADDR_INV_INTERVAL, &interval);
mSys->addInverter("HM1200", invSerial, INV_TYPE_HM1200);
if(interval < 1000)
interval = 1000;
@ -74,10 +77,6 @@ void app::setup(const char *ssid, const char *pwd, uint32_t timeout) {
mMqtt.sendMsg("version", mVersion);
}
else {
memset(mHoymiles->mAddrBytes, 0, 6);
}
mHoymiles->serial2RadioId();
initRadio();
@ -90,26 +89,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();
//dumpBuf("RAW ", p->packet, MAX_RF_PAYLOAD_SIZE);
//mHoymiles->dumpBuf("RAW ", p->packet, MAX_RF_PAYLOAD_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);
mDecoder->convert(&p->packet[11], len);
//Serial.println("CMD " + String(*cmd, HEX));
//dumpBuf("Payload ", p->packet, 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]++;
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]++;
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]++;
@ -117,94 +122,72 @@ 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);*/
inverter_t *inv = mSys->getInverterByPos(0);
size = mSys->Radio.getTimePacket(&inv->radioId.u64, mSendBuf, mTimestamp);
//Serial.println("sent packet: #" + String(mSendCnt));
//dumpBuf(mSendBuf, size);
sendPacket(mSendBuf, size);
sendPacket(inv, mSendBuf, size);
mSendCnt++;
}
// mqtt
mMqtt.loop();
//mMqtt.loop();
if(mMqttEvt) {
mMqttEvt = false;
mMqtt.isConnected(true);
/*mMqtt.isConnected(true);
char topic[20], val[10];
for(uint8_t i = 0; i < 4; i++) {
for(uint8_t j = 0; j < 5; j++) {
switch(j) {
default:
sprintf(topic, "ch%d/%s", i, "voltage");
sprintf(val, "%.3f", mDecoder->mData.ch_dc[i/2].u);
break;
case 1:
sprintf(topic, "ch%d/%s", i, "current");
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;
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, fields[iv->assign[i].fieldId]);
sprintf(val, "%.3f", mSys->getValue(iv, i));
mMqtt.sendMsg(topic, val);
delay(10);
}
}
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) {
uint8_t lostCnt = 0, pipe, len;
NRF24_packet_t *p;
uint8_t pipe, len;
packet_t *p;
DISABLE_IRQ;
while(mRadio->available(&pipe)) {
if(!mBufCtrl->full()) {
p = mBufCtrl->getFront();
if(!mSys->BufCtrl.full()) {
p = mSys->BufCtrl.getFront();
memset(p->packet, 0xcc, MAX_RF_PAYLOAD_SIZE);
p->sendCh = mSendChannel;
len = mRadio->getPayloadSize();
@ -212,13 +195,10 @@ void app::handleIntr(void) {
len = MAX_RF_PAYLOAD_SIZE;
mRadio->read(p->packet, len);
mBufCtrl->pushFront(p);
lostCnt = 0;
mSys->BufCtrl.pushFront(p);
}
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
}
@ -254,27 +234,27 @@ void app::initRadio(void) {
Serial.println("Radio Config:");
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;
mRadio->stopListening();
#ifdef CHANNEL_HOP
//if(mSendCnt % 6 == 0)
mSendChannel = mHoymiles->getNxtChannel();
mSendChannel = mSys->Radio.getNxtChannel();
//else
// mSendChannel = mHoymiles->getLastChannel();
// mSendChannel = mSys->Radio.getLastChannel();
#else
mSendChannel = mHoymiles->getDefaultChannel();
mSendChannel = mSys->Radio.getDefaultChannel();
#endif
mRadio->setChannel(mSendChannel);
//Serial.println("CH: " + String(mSendChannel));
mRadio->openWritingPipe(mHoymiles->mRadioId);
mRadio->openWritingPipe(inv->radioId.u64);
mRadio->setCRCLength(RF24_CRC_16);
mRadio->enableDynamicPayloads();
mRadio->setAutoAck(true);
@ -329,13 +309,32 @@ void app::showSetup(void) {
// 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("{INV0_ADDR}", String(addr));
html.replace("{DEVICE}", String(mDeviceName));
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) {
mEep->read(ADDR_INV_INTERVAL, &interval);
html.replace("{INV_INTERVAL}", String(interval));
@ -402,28 +401,46 @@ void app::showHoymiles(void) {
//-----------------------------------------------------------------------------
void app::showLiveData(void) {
String modHtml = "";
String unit[5] = {"V", "A", "W", "Wh", "kWh"};
String info[5] = {"VOLTAGE", "CURRENT", "POWER", "YIELD DAY", "YIELD"};
for(uint8_t i = 0; i < 4; i++) {
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;
String modHtml = "<pre>";
char topic[20], val[10];
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
inverter_t *iv = mSys->getInverterByPos(id);
if(NULL != iv) {
/*uint8_t modNum;
switch(iv->type) {
default: modNum = 1; break;
case INV_TYPE_HM600: modNum = 2; break;
case INV_TYPE_HM1200: modNum = 4; break;
}
for(uint8_t mod = 1; mod <= modNum; mod ++) {
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);
}
@ -443,20 +460,24 @@ void app::saveValues(bool webSend = true) {
if(mWeb->args() > 0) {
char *p;
char addr[20] = {0};
char buf[20] = {0};
uint8_t i = 0;
uint16_t interval;
// hoymiles
memset(mHoymiles->mAddrBytes, 0, 6);
mWeb->arg("inv0Addr").toCharArray(addr, 20);
p = strtok(addr, ":");
while(NULL != p) {
mHoymiles->mAddrBytes[i++] = strtol(p, NULL, 16);
p = strtok(NULL, ":");
// inverter
serial_u addr;
mWeb->arg("inv0Addr").toCharArray(buf, 20);
addr.u64 = Serial2u64(buf);
mSys->updateSerial(mSys->getInverterByPos(0), addr.u64);
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();
mEep->write(ADDR_INV0_ADDR, mHoymiles->mAddrBytes, INV_ADDR_LEN);
mEep->write(ADDR_INV0_ADDR, addr.u64);
mEep->write(ADDR_INV_INTERVAL, interval);
@ -465,9 +486,9 @@ void app::saveValues(bool webSend = true) {
char mqttUser[MQTT_USER_LEN];
char mqttPwd[MQTT_PWD_LEN];
char mqttTopic[MQTT_TOPIC_LEN];
mWeb->arg("mqttAddr").toCharArray(addr, 20);
mWeb->arg("mqttAddr").toCharArray(buf, 20);
i = 0;
p = strtok(addr, ".");
p = strtok(buf, ".");
while(NULL != p) {
mqttAddr[i++] = atoi(p);
p = strtok(NULL, ".");
@ -495,14 +516,3 @@ void app::saveValues(bool webSend = true) {
"<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 "CircularBuffer.h"
#include "hoymiles.h"
#include "hm1200Decode.h"
#include "hmSystem.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 {
public:
app();
@ -25,7 +28,7 @@ class app : public Main {
private:
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 mqttTicker(void);
@ -39,16 +42,35 @@ class app : public Main {
void showMqtt(void);
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;
bool mKeyPressed;
RF24 *mRadio;
hoymiles *mHoymiles;
hm1200Decode *mDecoder;
CircularBuffer<NRF24_packet_t> *mBufCtrl;
NRF24_packet_t mBuffer[PACKET_BUFFER_SIZE];
packet_t mBuffer[PACKET_BUFFER_SIZE];
HmSystemType *mSys;
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
//-------------------------------------
// CONFIGURATION - COMPILE TIME
//-------------------------------------
#define PACKET_BUFFER_SIZE 30
#define MAX_NUM_INVERTERS 3
//-------------------------------------
// VERSION
//-------------------------------------
#define VERSION_MAJOR 0
#define VERSION_MINOR 1
#define VERSION_PATCH 12
#define VERSION_MINOR 2
#define VERSION_PATCH 1
//-------------------------------------
@ -26,7 +34,7 @@
#define DEVNAME_LEN 16
#define CRC_LEN 2
#define INV_ADDR_LEN 6
#define INV_ADDR_LEN 8 // uint64_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__
#include "Arduino.h"
#include <EEPROM.h>
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[], 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__*/

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"
app myApp;
@ -22,4 +31,3 @@ void loop() {
ICACHE_RAM_ATTR void handleIntr(void) {
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__
#define __HOYMILES_H__
#ifndef __RADIO_H__
#define __RADIO_H__
#include <RF24.h>
#include <RF24_config.h>
@ -7,18 +7,15 @@
#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 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
//-----------------------------------------------------------------------------
#define CP_U32_LittleEndian(buf, v) ({ \
uint8_t *b = buf; \
b[0] = ((v >> 24) & 0xff); \
@ -39,64 +36,38 @@
//-----------------------------------------------------------------------------
union uint64Bytes {
uint64_t ull;
uint8_t bytes[8];
};
typedef struct {
uint8_t sendCh;
uint8_t packet[MAX_RF_PAYLOAD_SIZE];
} NRF24_packet_t;
// HM Radio class
//-----------------------------------------------------------------------------
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:
hoymiles() {
serial2RadioId();
calcDtuIdCrc();
mChannels[0] = 23;
mChannels[1] = 40;
mChannels[2] = 61;
mChannels[3] = 75;
HmRadio() {
pinMode(IRQ_PIN, INPUT_PULLUP);
//attachInterrupt(digitalPinToInterrupt(IRQ_PIN), handleIntr, FALLING);
mSendChan[0] = 23;
mSendChan[1] = 40;
mSendChan[2] = 61;
mSendChan[3] = 75;
mChanIdx = 1;
mLastCrc = 0x0000;
mRptCnt = 0;
}
~hoymiles() {}
~HmRadio() {}
uint8_t getDefaultChannel(void) {
return mChannels[2];
return mSendChan[2];
}
uint8_t getLastChannel(void) {
return mChannels[mChanIdx];
return mSendChan[mChanIdx];
}
uint8_t getNxtChannel(void) {
if(++mChanIdx >= 4)
mChanIdx = 0;
return mChannels[mChanIdx];
return mSendChan[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);
uint8_t getTimePacket(const uint64_t *invId, uint8_t buf[], uint32_t ts) {
getCmdPacket(invId, buf, 0x15, 0x80, false);
buf[10] = 0x0b; // cid
buf[11] = 0x00;
CP_U32_LittleEndian(&buf[12], ts);
@ -110,11 +81,11 @@ class hoymiles {
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);
buf[0] = mid; // message id
CP_U32_BigEndian(&buf[1], (mRadioId >> 8));
CP_U32_BigEndian(&buf[5], (DTU_RADIO_ID >> 8));
CP_U32_BigEndian(&buf[1], (*invId >> 8));
CP_U32_BigEndian(&buf[5], (DTU_ID >> 8));
buf[9] = cmd;
if(calcCrc)
buf[10] = crc8(buf, 10);
@ -144,35 +115,22 @@ class hoymiles {
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) {
protected:
void getDtuIdCrc(void) {
uint64_t addr = DTU_RADIO_ID;
uint8_t dtuAddr[5];
uint8_t tmp[5];
for(int8_t i = 4; i >= 0; i--) {
dtuAddr[i] = addr;
tmp[i] = addr;
addr >>= 8;
}
mDtuIdCrc = crc16nrf24(dtuAddr, BIT_CNT(5));
mDtuIdCrc = crc16nrf24(tmp, BIT_CNT(5));
}
uint8_t mChannels[4];
uint8_t mSendChan[4];
uint8_t mChanIdx;
uint16_t mDtuIdCrc;
uint16_t mLastCrc;
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>
<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>
<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>
<div class="inputWrp">
<input type="text" class="inputText" name="device" value="{DEVICE}" required/>
<span class="floating_label">DEVICE NAME</span>
</div>
<label for="device">Device Name</label>
<input type="text" class="text" name="device" value="{DEVICE}"/>
<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>
{INVERTERS}<br/>
<label for="invInterval">Interval (ms)</label>
<input type="text" class="text" name="invInterval" value="{INV_INTERVAL}"/>
<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>
<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" />

65
tools/esp8266/html/style.css

@ -20,9 +20,15 @@ p {
}
.des {
margin-top: 35px;
font-size: 14pt;
color: #006ec0;
padding-bottom: 0px !important;
}
.subdes {
font-size: 13pt;
color: #006ec0;
margin-left: 7px;
}
.fw {
@ -74,28 +80,25 @@ a:hover, a:focus {
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;
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;
@ -109,7 +112,11 @@ input.cb {
}
label {
font-size: 14pt;
width: 20%;
display: inline-block;
font-size: 12pt;
padding-right: 10px;
margin-left: 10px;
}
.left {
@ -120,36 +127,6 @@ label {
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;

2
tools/esp8266/mqtt.h

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

Loading…
Cancel
Save