mirror of
https://github.com/lumapu/ahoy.git
synced 2025-05-10 07:26:38 +02:00
* pinout can be saved using the web setup
* html / css files are now located inside PROGMEM * conversion of files is done with python script (OS independent, open source) * moved as much as possible for now to the hm* modules - the app should only be the body * successfully tested with HM1200
This commit is contained in:
parent
a95069e65c
commit
d195eee498
16 changed files with 240 additions and 215 deletions
|
@ -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: <https://www.mikrocontroller.net/topic/525778?page=3#7033371>
|
||||
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: <https://www.mikrocontroller.net/topic/525778?page=3#7033371>
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
#include "app.h"
|
||||
|
||||
#include "html/h/index_html.h"
|
||||
#include "html/h/setup_html.h"
|
||||
#include "html/h/hoymiles_html.h"
|
||||
extern String setup_html;
|
||||
|
||||
|
||||
#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
app::app() : Main() {
|
||||
mSendCnt = 0;
|
||||
mSendTicker = new Ticker();
|
||||
mFlagSend = false;
|
||||
|
||||
|
@ -93,7 +90,7 @@ void app::setup(const char *ssid, const char *pwd, uint32_t timeout) {
|
|||
mMqtt.sendMsg("version", mVersion);
|
||||
}
|
||||
|
||||
initRadio();
|
||||
mSys->setup();
|
||||
|
||||
if(!mSettingsValid)
|
||||
Serial.println("Warn: your settings are not valid! check [IP]/setup");
|
||||
|
@ -107,15 +104,15 @@ void app::loop(void) {
|
|||
if(!mSys->BufCtrl.empty()) {
|
||||
uint8_t len, rptCnt;
|
||||
packet_t *p = mSys->BufCtrl.getBack();
|
||||
//dumpBuf("RAW ", p->packet, MAX_RF_PAYLOAD_SIZE);
|
||||
//mSys->Radio.dumpBuf("RAW ", p->packet, MAX_RF_PAYLOAD_SIZE);
|
||||
|
||||
if(mSys->Radio.checkCrc(p->packet, &len, &rptCnt)) {
|
||||
// process buffer only on first occurrence
|
||||
if((0 != len) && (0 == rptCnt)) {
|
||||
//Serial.println("CMD " + String(*cmd, HEX));
|
||||
//dumpBuf("Payload ", p->packet, len);
|
||||
|
||||
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++) {
|
||||
|
@ -142,31 +139,11 @@ void app::loop(void) {
|
|||
|
||||
if(mFlagSend) {
|
||||
mFlagSend = false;
|
||||
|
||||
uint8_t size = 0;
|
||||
inverter_t *inv;
|
||||
|
||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
||||
inv = mSys->getInverterByPos(i);
|
||||
if(NULL != inv) {
|
||||
//if((mSendCnt % 6) == 0)
|
||||
size = mSys->Radio.getTimePacket(&inv->radioId.u64, mSendBuf, mTimestamp);
|
||||
/*else if((mSendCnt % 6) == 1)
|
||||
size = mSys->Radio.getCmdPacket(&inv->radioId.u64, mSendBuf, 0x15, 0x81);
|
||||
else if((mSendCnt % 6) == 2)
|
||||
size = mSys->Radio.getCmdPacket(&inv->radioId.u64, mSendBuf, 0x15, 0x80);
|
||||
else if((mSendCnt % 6) == 3)
|
||||
size = mSys->Radio.getCmdPacket(&inv->radioId.u64, mSendBuf, 0x15, 0x83);
|
||||
else if((mSendCnt % 6) == 4)
|
||||
size = mSys->Radio.getCmdPacket(&inv->radioId.u64, mSendBuf, 0x15, 0x82);
|
||||
else if((mSendCnt % 6) == 5)
|
||||
size = mSys->Radio.getCmdPacket(&inv->radioId.u64, mSendBuf, 0x15, 0x84);*/
|
||||
|
||||
//Serial.println("sent packet: #" + String(mSendCnt));
|
||||
//dumpBuf("SEN ", mSendBuf, size);
|
||||
sendPacket(inv, mSendBuf, size);
|
||||
mSendCnt++;
|
||||
|
||||
mSys->Radio.sendTimePacket(inv->radioId.u64, mTimestamp);
|
||||
delay(20);
|
||||
}
|
||||
}
|
||||
|
@ -213,100 +190,7 @@ void app::loop(void) {
|
|||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::handleIntr(void) {
|
||||
uint8_t pipe, len;
|
||||
packet_t *p;
|
||||
|
||||
DISABLE_IRQ;
|
||||
|
||||
while(mRadio->available(&pipe)) {
|
||||
if(!mSys->BufCtrl.full()) {
|
||||
p = mSys->BufCtrl.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);
|
||||
mSys->BufCtrl.pushFront(p);
|
||||
}
|
||||
else {
|
||||
bool tx_ok, tx_fail, rx_ready;
|
||||
mRadio->whatHappened(tx_ok, tx_fail, rx_ready); // reset interrupt status
|
||||
mRadio->flush_rx(); // drop the packet
|
||||
}
|
||||
}
|
||||
|
||||
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 = mSys->Radio.getDefaultChannel();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::sendPacket(inverter_t *inv, uint8_t buf[], uint8_t len) {
|
||||
DISABLE_IRQ;
|
||||
mRadio->stopListening();
|
||||
|
||||
#ifdef CHANNEL_HOP
|
||||
//if(mSendCnt % 6 == 0)
|
||||
mSendChannel = mSys->Radio.getNxtChannel();
|
||||
//else
|
||||
// mSendChannel = mSys->Radio.getLastChannel();
|
||||
#else
|
||||
mSendChannel = mSys->Radio.getDefaultChannel();
|
||||
#endif
|
||||
mRadio->setChannel(mSendChannel);
|
||||
//Serial.println("CH: " + String(mSendChannel));
|
||||
|
||||
mRadio->openWritingPipe(inv->radioId.u64);
|
||||
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;
|
||||
mSys->Radio.handleIntr();
|
||||
}
|
||||
|
||||
|
||||
|
@ -324,7 +208,7 @@ void app::mqttTicker(void) {
|
|||
|
||||
//-----------------------------------------------------------------------------
|
||||
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);
|
||||
|
@ -337,7 +221,7 @@ void app::showSetup(void) {
|
|||
|
||||
uint16_t interval;
|
||||
|
||||
String html = setup_html;
|
||||
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}"
|
||||
|
@ -455,7 +339,7 @@ void app::showCmdStatistics(void) {
|
|||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::showHoymiles(void) {
|
||||
String html = hoymiles_html;
|
||||
String html = FPSTR(hoymiles_html);
|
||||
html.replace("{DEVICE}", mDeviceName);
|
||||
html.replace("{VERSION}", mVersion);
|
||||
mWeb->send(200, "text/html", html);
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
#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 HmRadio<RF24_CE_PIN, RF24_CS_PIN, RF24_IRQ_PIN, BufferType> RadioType;
|
||||
typedef HmSystem<RadioType, BufferType, MAX_NUM_INVERTERS, float> HmSystemType;
|
||||
|
||||
const char* const wemosPins[] = {"D3 (GPIO0)", "TX (GPIO1)", "D4 (GPIO2)", "RX (GPIO3)",
|
||||
|
@ -31,10 +31,11 @@ class app : public Main {
|
|||
void loop(void);
|
||||
void handleIntr(void);
|
||||
|
||||
private:
|
||||
void initRadio(void);
|
||||
void sendPacket(inverter_t *inv, uint8_t data[], uint8_t length);
|
||||
uint8_t getIrqPin(void) {
|
||||
return mSys->Radio.pinIrq;
|
||||
}
|
||||
|
||||
private:
|
||||
void sendTicker(void);
|
||||
void mqttTicker(void);
|
||||
|
||||
|
@ -49,15 +50,6 @@ class app : public Main {
|
|||
void saveValues(bool webSend);
|
||||
void updateCrc(void);
|
||||
|
||||
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;
|
||||
|
@ -76,16 +68,10 @@ class app : public Main {
|
|||
uint8_t mState;
|
||||
bool mKeyPressed;
|
||||
|
||||
RF24 *mRadio;
|
||||
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];
|
||||
|
|
|
@ -6,9 +6,8 @@
|
|||
// PINOUT
|
||||
//-------------------------------------
|
||||
#define RF24_CS_PIN 15
|
||||
#define RF24_CE_PIN 2 //5
|
||||
#define RF24_IRQ_PIN 0 //4
|
||||
|
||||
#define RF24_CE_PIN 2
|
||||
#define RF24_IRQ_PIN 0
|
||||
|
||||
|
||||
//-------------------------------------
|
||||
|
@ -17,6 +16,7 @@
|
|||
#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
|
||||
|
||||
|
||||
|
@ -28,6 +28,13 @@
|
|||
#define VERSION_PATCH 4
|
||||
|
||||
|
||||
//-------------------------------------
|
||||
typedef struct {
|
||||
uint8_t sendCh;
|
||||
uint8_t packet[MAX_RF_PAYLOAD_SIZE];
|
||||
} packet_t;
|
||||
|
||||
|
||||
//-------------------------------------
|
||||
// EEPROM
|
||||
//-------------------------------------
|
||||
|
|
|
@ -36,7 +36,7 @@ class eep {
|
|||
*value = (EEPROM.read(addr++));
|
||||
}
|
||||
|
||||
void read(uint32_t addr, uint8_t data[], uint8_t length) {
|
||||
void read(uint32_t addr, uint8_t data[], uint16_t length) {
|
||||
for(uint8_t i = 0; i < length; i ++) {
|
||||
*(data++) = EEPROM.read(addr++);
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ class eep {
|
|||
EEPROM.commit();
|
||||
}
|
||||
|
||||
void write(uint32_t addr, uint8_t data[], uint8_t length) {
|
||||
void write(uint32_t addr, uint8_t data[], uint16_t length) {
|
||||
for(uint8_t i = 0; i < length; i ++) {
|
||||
EEPROM.write(addr++, data[i]);
|
||||
}
|
||||
|
|
|
@ -13,12 +13,11 @@ app myApp;
|
|||
|
||||
//-----------------------------------------------------------------------------
|
||||
void setup() {
|
||||
// TODO: move to HmRadio
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
//#define CHANNEL_HOP // switch between channels or use static channel to send
|
||||
|
||||
#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)
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -38,17 +38,14 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// HM Radio class
|
||||
//-----------------------------------------------------------------------------
|
||||
template <uint8_t CE_PIN, uint8_t CS_PIN, uint8_t IRQ_PIN, uint64_t DTU_ID=DTU_RADIO_ID>
|
||||
template <uint8_t CE_PIN, uint8_t CS_PIN, uint8_t IRQ_PIN, class BUFFER, uint64_t DTU_ID=DTU_RADIO_ID>
|
||||
class HmRadio {
|
||||
public:
|
||||
HmRadio() {
|
||||
//pinMode(IRQ_PIN, INPUT_PULLUP);
|
||||
//attachInterrupt(digitalPinToInterrupt(IRQ_PIN), handleIntr, FALLING);
|
||||
|
||||
mSendChan[0] = 23;
|
||||
mSendChan[1] = 40;
|
||||
mSendChan[2] = 61;
|
||||
mSendChan[3] = 75;
|
||||
HmRadio() : mNrf24(CE_PIN, CS_PIN) {
|
||||
mChanOut[0] = 23;
|
||||
mChanOut[1] = 40;
|
||||
mChanOut[2] = 61;
|
||||
mChanOut[3] = 75;
|
||||
mChanIdx = 1;
|
||||
|
||||
calcDtuCrc();
|
||||
|
@ -56,47 +53,106 @@ class HmRadio {
|
|||
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 mSendChan[2];
|
||||
return mChanOut[2];
|
||||
}
|
||||
uint8_t getLastChannel(void) {
|
||||
return mSendChan[mChanIdx];
|
||||
return mChanOut[mChanIdx];
|
||||
}
|
||||
|
||||
uint8_t getNxtChannel(void) {
|
||||
if(++mChanIdx >= 4)
|
||||
mChanIdx = 0;
|
||||
return mSendChan[mChanIdx];
|
||||
return mChanOut[mChanIdx];
|
||||
}
|
||||
|
||||
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);
|
||||
buf[19] = 0x05;
|
||||
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(&buf[10], 14);
|
||||
buf[24] = (crc >> 8) & 0xff;
|
||||
buf[25] = (crc ) & 0xff;
|
||||
buf[26] = crc8(buf, 26);
|
||||
uint16_t crc = crc16(&mSendBuf[10], 14);
|
||||
mSendBuf[24] = (crc >> 8) & 0xff;
|
||||
mSendBuf[25] = (crc ) & 0xff;
|
||||
mSendBuf[26] = crc8(mSendBuf, 26);
|
||||
|
||||
return 27;
|
||||
sendPacket(invId, mSendBuf, 27);
|
||||
}
|
||||
|
||||
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], ((*invId) >> 8));
|
||||
CP_U32_BigEndian(&buf[5], (DTU_ID >> 8));
|
||||
buf[9] = cmd;
|
||||
if(calcCrc)
|
||||
buf[10] = crc8(buf, 10);
|
||||
|
||||
return 11;
|
||||
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) {
|
||||
|
@ -126,6 +182,44 @@ class HmRadio {
|
|||
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];
|
||||
|
@ -136,11 +230,26 @@ class HmRadio {
|
|||
mDtuIdCrc = crc16nrf24(tmp, BIT_CNT(5));
|
||||
}
|
||||
|
||||
uint8_t mSendChan[4];
|
||||
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__*/
|
||||
|
|
|
@ -4,10 +4,6 @@
|
|||
#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>
|
||||
|
@ -25,6 +21,10 @@ class 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!");
|
||||
|
|
|
@ -1,5 +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 hoymiles.html h\hoymiles_html.h hoymiles_html
|
||||
..\tools\fileConv.exe style.css h\style_css.h style_css
|
||||
pause
|
28
tools/esp8266/html/convert.py
Normal file
28
tools/esp8266/html/convert.py
Normal file
|
@ -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")
|
|
@ -1 +1,4 @@
|
|||
String hoymiles_html = "<!doctype html><html><head><title>Index - {DEVICE}</title><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><script type=\"text/javascript\"> getAjax('/livedata', 'livedata'); window.setInterval(\"getAjax('/livedata', 'livedata')\", 10000); function getAjax(url, resid) { var http = null; http = new XMLHttpRequest(); if(http != null) { http.open(\"GET\", url, true); http.onreadystatechange = print; http.send(null); } function print() { if(http.readyState == 4) { document.getElementById(resid).innerHTML = http.responseText; } } } </script><style type=\"text/css\"></style></head><body><h1>AHOY - {DEVICE}</h1><div id=\"content\" class=\"content\"><p><a href=\"/\">Home</a><br/></p><div id=\"livedata\"></div><p>Every 10 seconds the values are updated</p></div><div id=\"footer\"><p class=\"left\">© 2022</p><p class=\"right\">AHOY :: {VERSION}</p></div></body></html>";
|
||||
#ifndef __HOYMILES_H__
|
||||
#define __HOYMILES_H__
|
||||
const char hoymiles_html[] PROGMEM = "<!doctype html><html><head><title>Index - {DEVICE}</title><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><script type=\"text/javascript\">getAjax('/livedata', 'livedata');window.setInterval(\"getAjax('/livedata', 'livedata')\", 10000);function getAjax(url, resid) {var http = null;http = new XMLHttpRequest();if(http != null) {http.open(\"GET\", url, true);http.onreadystatechange = print;http.send(null);}function print() {if(http.readyState == 4) {document.getElementById(resid).innerHTML = http.responseText;}}}</script><style type=\"text/css\"></style></head><body><h1>AHOY - {DEVICE}</h1><div id=\"content\" class=\"content\"><p><a href=\"/\">Home</a><br/></p><div id=\"livedata\"></div><p>Every 10 seconds the values are updated</p></div><div id=\"footer\"><p class=\"left\">© 2022</p><p class=\"right\">AHOY :: {VERSION}</p></div></body></html>";
|
||||
#endif /*__HOYMILES_H__*/
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
String index_html = "<!doctype html><html><head><title>Index - {DEVICE}</title><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><script type=\"text/javascript\"> window.setInterval(\"getAjax('/uptime', 'uptime')\", 1000); window.setInterval(\"getAjax('/time', 'time')\", 1000); window.setInterval(\"getAjax('/mqttstate', 'mqtt')\", 2000); window.setInterval(\"getAjax('/cmdstat', 'cmds')\", 2000); function getAjax(url, resid) { var http = null; http = new XMLHttpRequest(); if(http != null) { http.open(\"GET\", url, true); http.onreadystatechange = print; http.send(null); } function print() { if(http.readyState == 4) { document.getElementById(resid).innerHTML = http.responseText; } } } </script></head><body><h1>AHOY - {DEVICE}</h1><div id=\"content\" class=\"content\"><p><a href=\"/hoymiles\">Hoymiles</a><br/><a href=\"/update\">Update</a><br/><br/><a href=\"/setup\">Setup</a><br/><a href=\"/reboot\">Reboot</a></p><p><span class=\"des\">Uptime: </span><span id=\"uptime\"></span></p><p><span class=\"des\">Time: </span><span id=\"time\"></span></p><p><span class=\"des\">MQTT: </span><span id=\"mqtt\"></span></p><p><span class=\"des\">Statistics: </span><pre id=\"cmds\"></pre></p></div><div id=\"footer\"><p class=\"left\">© 2022</p><p class=\"right\">AHOY :: {VERSION}</p></div></body></html>";
|
||||
#ifndef __INDEX_H__
|
||||
#define __INDEX_H__
|
||||
const char index_html[] PROGMEM = "<!doctype html><html><head><title>Index - {DEVICE}</title><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><script type=\"text/javascript\">window.setInterval(\"getAjax('/uptime', 'uptime')\", 1000);window.setInterval(\"getAjax('/time', 'time')\", 1000);window.setInterval(\"getAjax('/mqttstate', 'mqtt')\", 2000);window.setInterval(\"getAjax('/cmdstat', 'cmds')\", 2000);function getAjax(url, resid) {var http = null;http = new XMLHttpRequest();if(http != null) {http.open(\"GET\", url, true);http.onreadystatechange = print;http.send(null);}function print() {if(http.readyState == 4) {document.getElementById(resid).innerHTML = http.responseText;}}}</script></head><body><h1>AHOY - {DEVICE}</h1><div id=\"content\" class=\"content\"><p><a href=\"/hoymiles\">Hoymiles</a><br/><a href=\"/update\">Update</a><br/><br/><a href=\"/setup\">Setup</a><br/><a href=\"/reboot\">Reboot</a></p><p><span class=\"des\">Uptime: </span><span id=\"uptime\"></span></p><p><span class=\"des\">Time: </span><span id=\"time\"></span></p><p><span class=\"des\">MQTT: </span><span id=\"mqtt\"></span></p><p><span class=\"des\">Statistics: </span><pre id=\"cmds\"></pre></p></div><div id=\"footer\"><p class=\"left\">© 2022</p><p class=\"right\">AHOY :: {VERSION}</p></div></body></html>";
|
||||
#endif /*__INDEX_H__*/
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
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/><p class=\"subdes\">General</p><label for=\"invInterval\">Interval (ms)</label><input type=\"text\" class=\"text\" name=\"invInterval\" value=\"{INV_INTERVAL}\"/><p class=\"des\">Pinout</p> {PINOUT} <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\"> </p><input type=\"checkbox\" class=\"cb\" name=\"reboot\"/><label for=\"reboot\">Reboot device after successful save</label><input type=\"submit\" value=\"save\" class=\"btn\" /></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>";
|
||||
#ifndef __SETUP_H__
|
||||
#define __SETUP_H__
|
||||
const char setup_html[] PROGMEM = "<!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/><p class=\"subdes\">General</p><label for=\"invInterval\">Interval (ms)</label><input type=\"text\" class=\"text\" name=\"invInterval\" value=\"{INV_INTERVAL}\"/><p class=\"des\">Pinout (Wemos)</p>{PINOUT}<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\"> </p><input type=\"checkbox\" class=\"cb\" name=\"reboot\"/><label for=\"reboot\">Reboot device after successful save</label><input type=\"submit\" value=\"save\" class=\"btn\" /></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>";
|
||||
#endif /*__SETUP_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 { 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; } ";
|
||||
#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__*/
|
||||
|
|
|
@ -160,7 +160,7 @@ bool Main::setupStation(uint32_t timeout) {
|
|||
|
||||
//-----------------------------------------------------------------------------
|
||||
void Main::showSetup(void) {
|
||||
String html = setup_html;
|
||||
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 default "{PWD}"
|
||||
|
@ -173,7 +173,7 @@ void Main::showSetup(void) {
|
|||
|
||||
//-----------------------------------------------------------------------------
|
||||
void Main::showCss(void) {
|
||||
mWeb->send(200, "text/css", style_css);
|
||||
mWeb->send(200, "text/css", FPSTR(style_css));
|
||||
}
|
||||
|
||||
|
||||
|
|
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue