mirror of https://github.com/lumapu/ahoy.git
grindylow
3 years ago
committed by
GitHub
27 changed files with 1603 additions and 757 deletions
@ -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__*/ |
@ -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(); |
|
||||
} |
|
@ -0,0 +1,130 @@ |
|||||
|
#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, CMD82 = 0x82, CMD83, CMD84}; |
||||
|
|
||||
|
enum {INV_TYPE_HM600 = 0, INV_TYPE_HM1200, INV_TYPE_HM400}; |
||||
|
const char* const invTypes[] = {"HM600", "HM1200", "HM400"}; |
||||
|
#define NUM_INVERTER_TYPES 3 |
||||
|
|
||||
|
typedef struct { |
||||
|
uint8_t fieldId; // field id
|
||||
|
uint8_t unitId; // uint id
|
||||
|
uint8_t ch; // channel 0 - 3
|
||||
|
uint8_t cmdId; // received command id
|
||||
|
uint8_t start; // pos of first byte in buffer
|
||||
|
uint8_t num; // number of bytes in buffer
|
||||
|
uint16_t div; // divisor
|
||||
|
} byteAssign_t; |
||||
|
|
||||
|
|
||||
|
union serial_u { |
||||
|
uint64_t u64; |
||||
|
uint8_t b[8]; |
||||
|
}; |
||||
|
|
||||
|
typedef struct { |
||||
|
uint8_t id; // unique id
|
||||
|
char name[MAX_NAME_LENGTH]; // human readable name, eg. "HM-600.1"
|
||||
|
uint8_t type; // integer which refers to inverter type
|
||||
|
byteAssign_t* assign; // type of inverter
|
||||
|
uint8_t listLen; // length of assignments
|
||||
|
serial_u serial; // serial number as on barcode
|
||||
|
serial_u radioId; // id converted to modbus
|
||||
|
} inverter_t; |
||||
|
|
||||
|
|
||||
|
/**
|
||||
|
* indices are built for the buffer starting with cmd-id in first byte |
||||
|
* (complete payload in buffer) |
||||
|
* */ |
||||
|
|
||||
|
//-------------------------------------
|
||||
|
// HM400 HM350?, HM300?
|
||||
|
//-------------------------------------
|
||||
|
const byteAssign_t hm400assignment[] = { |
||||
|
{ FLD_UDC, UNIT_V, CH1, CMD01, 14, 2, 10 }, |
||||
|
{ FLD_IDC, UNIT_A, CH1, CMD01, 16, 2, 100 }, |
||||
|
{ FLD_PDC, UNIT_W, CH1, CMD01, 18, 2, 10 }, |
||||
|
{ FLD_YT, UNIT_KWH, CH1, CMD01, 20, 4, 1000 }, |
||||
|
{ FLD_YD, UNIT_WH, CH1, CMD01, 24, 2, 1000 }, |
||||
|
{ FLD_UAC, UNIT_V, CH0, CMD01, 26, 2, 10 }, |
||||
|
{ FLD_F, UNIT_HZ, CH0, CMD82, 12, 2, 100 }, |
||||
|
{ FLD_PAC, UNIT_W, CH0, CMD82, 14, 2, 10 }, |
||||
|
{ FLD_IAC, UNIT_A, CH0, CMD82, 18, 2, 100 }, |
||||
|
{ FLD_T, UNIT_C, CH0, CMD82, 22, 2, 10 } |
||||
|
}; |
||||
|
#define HM400_LIST_LEN (sizeof(hm400assignment) / sizeof(byteAssign_t)) |
||||
|
|
||||
|
|
||||
|
//-------------------------------------
|
||||
|
// HM600, HM700
|
||||
|
//-------------------------------------
|
||||
|
const byteAssign_t hm600assignment[] = { |
||||
|
{ FLD_UDC, UNIT_V, CH1, CMD01, 14, 2, 10 }, |
||||
|
{ FLD_IDC, UNIT_A, CH1, CMD01, 16, 2, 100 }, |
||||
|
{ FLD_PDC, UNIT_W, CH1, CMD01, 18, 2, 10 }, |
||||
|
{ FLD_UDC, UNIT_V, CH2, CMD01, 20, 2, 10 }, |
||||
|
{ FLD_IDC, UNIT_A, CH2, CMD01, 22, 2, 100 }, |
||||
|
{ FLD_PDC, UNIT_W, CH2, CMD01, 24, 2, 10 }, |
||||
|
{ FLD_YW, UNIT_WH, CH0, CMD02, 12, 2, 1 }, |
||||
|
{ FLD_YT, UNIT_WH, CH0, CMD02, 14, 4, 1 }, |
||||
|
{ FLD_YD, UNIT_WH, CH1, CMD02, 18, 2, 1 }, |
||||
|
{ FLD_YD, UNIT_WH, CH2, CMD02, 20, 2, 1 }, |
||||
|
{ FLD_UAC, UNIT_V, CH0, CMD02, 22, 2, 10 }, |
||||
|
{ FLD_F, UNIT_HZ, CH0, CMD02, 24, 2, 100 }, |
||||
|
{ FLD_IAC, UNIT_A, CH0, CMD02, 26, 2, 10 }, |
||||
|
{ FLD_T, UNIT_C, CH0, CMD83, 18, 2, 10 } |
||||
|
}; |
||||
|
#define HM600_LIST_LEN (sizeof(hm600assignment) / sizeof(byteAssign_t)) |
||||
|
|
||||
|
|
||||
|
//-------------------------------------
|
||||
|
// HM1200, HM1500?
|
||||
|
//-------------------------------------
|
||||
|
const byteAssign_t hm1200assignment[] = { |
||||
|
{ FLD_UDC, UNIT_V, CH1, CMD01, 3, 2, 10 }, |
||||
|
{ FLD_IDC, UNIT_A, CH1, CMD01, 5, 2, 100 }, |
||||
|
{ FLD_PDC, UNIT_W, CH1, CMD01, 9, 2, 10 }, |
||||
|
{ FLD_YD, UNIT_WH, CH1, CMD02, 5, 2, 1 }, |
||||
|
{ FLD_YT, UNIT_KWH, CH1, CMD01, 13, 4, 1000 }, |
||||
|
{ FLD_UDC, UNIT_V, CH2, CMD02, 9, 2, 10 }, |
||||
|
{ FLD_IDC, UNIT_A, CH2, CMD01, 7, 2, 100 }, |
||||
|
{ FLD_PDC, UNIT_W, CH2, CMD01, 11, 2, 10 }, |
||||
|
{ FLD_YD, UNIT_WH, CH2, CMD02, 7, 2, 1 }, |
||||
|
{ FLD_YT, UNIT_KWH, CH2, CMD02, 1, 4, 1000 }, |
||||
|
{ FLD_IDC, UNIT_A, CH3, CMD02, 11, 2, 100 }, |
||||
|
{ FLD_PDC, UNIT_W, CH3, CMD02, 15, 2, 10 }, |
||||
|
{ FLD_YD, UNIT_WH, CH3, CMD03, 11, 2, 1 }, |
||||
|
{ FLD_YT, UNIT_KWH, CH3, CMD03, 3, 4, 1000 }, |
||||
|
{ FLD_IDC, UNIT_A, CH4, CMD02, 13, 2, 100 }, |
||||
|
{ FLD_PDC, UNIT_W, CH4, CMD03, 1, 2, 10 }, |
||||
|
{ FLD_YD, UNIT_WH, CH4, CMD03, 13, 2, 1 }, |
||||
|
{ FLD_YT, UNIT_KWH, CH4, CMD03, 7, 4, 1000 }, |
||||
|
{ FLD_UAC, UNIT_V, CH0, CMD03, 15, 2, 10 }, |
||||
|
{ FLD_IAC, UNIT_A, CH0, CMD84, 7, 2, 100 }, |
||||
|
{ FLD_PAC, UNIT_W, CH0, CMD84, 3, 2, 10 }, |
||||
|
{ FLD_F, UNIT_HZ, CH0, CMD84, 1, 2, 100 }, |
||||
|
{ FLD_PCT, UNIT_PCT, CH0, CMD84, 9, 2, 10 }, |
||||
|
{ FLD_T, UNIT_C, CH0, CMD84, 11, 2, 10 } |
||||
|
}; |
||||
|
#define HM1200_LIST_LEN (sizeof(hm1200assignment) / sizeof(byteAssign_t)) |
||||
|
|
||||
|
#endif /*__HM_INVERTERS_H__*/ |
@ -0,0 +1,255 @@ |
|||||
|
#ifndef __RADIO_H__ |
||||
|
#define __RADIO_H__ |
||||
|
|
||||
|
#include <RF24.h> |
||||
|
#include <RF24_config.h> |
||||
|
#include "crc.h" |
||||
|
|
||||
|
//#define CHANNEL_HOP // switch between channels or use static channel to send
|
||||
|
|
||||
|
#define DEFAULT_RECV_CHANNEL 3 |
||||
|
|
||||
|
#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL) |
||||
|
#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL) |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
// MACROS
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
#define CP_U32_LittleEndian(buf, v) ({ \ |
||||
|
uint8_t *b = buf; \ |
||||
|
b[0] = ((v >> 24) & 0xff); \ |
||||
|
b[1] = ((v >> 16) & 0xff); \ |
||||
|
b[2] = ((v >> 8) & 0xff); \ |
||||
|
b[3] = ((v ) & 0xff); \ |
||||
|
}) |
||||
|
|
||||
|
#define CP_U32_BigEndian(buf, v) ({ \ |
||||
|
uint8_t *b = buf; \ |
||||
|
b[3] = ((v >> 24) & 0xff); \ |
||||
|
b[2] = ((v >> 16) & 0xff); \ |
||||
|
b[1] = ((v >> 8) & 0xff); \ |
||||
|
b[0] = ((v ) & 0xff); \ |
||||
|
}) |
||||
|
|
||||
|
#define BIT_CNT(x) ((x)<<3) |
||||
|
|
||||
|
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
// HM Radio class
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
template <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() : mNrf24(CE_PIN, CS_PIN) { |
||||
|
mChanOut[0] = 23; |
||||
|
mChanOut[1] = 40; |
||||
|
mChanOut[2] = 61; |
||||
|
mChanOut[3] = 75; |
||||
|
mChanIdx = 1; |
||||
|
|
||||
|
calcDtuCrc(); |
||||
|
|
||||
|
pinCs = CS_PIN; |
||||
|
pinCe = CE_PIN; |
||||
|
pinIrq = IRQ_PIN; |
||||
|
|
||||
|
mSendCnt = 0; |
||||
|
} |
||||
|
~HmRadio() {} |
||||
|
|
||||
|
void setup(BUFFER *ctrl) { |
||||
|
//Serial.println("HmRadio::setup, pins: " + String(pinCs) + ", " + String(pinCe) + ", " + String(pinIrq));
|
||||
|
pinMode(pinIrq, INPUT_PULLUP); |
||||
|
|
||||
|
mBufCtrl = ctrl; |
||||
|
|
||||
|
mNrf24.begin(pinCe, pinCs); |
||||
|
mNrf24.setAutoAck(false); |
||||
|
mNrf24.setRetries(0, 0); |
||||
|
|
||||
|
mNrf24.setChannel(DEFAULT_RECV_CHANNEL); |
||||
|
mNrf24.setDataRate(RF24_250KBPS); |
||||
|
mNrf24.disableCRC(); |
||||
|
mNrf24.setAutoAck(false); |
||||
|
mNrf24.setPayloadSize(MAX_RF_PAYLOAD_SIZE); |
||||
|
mNrf24.setAddressWidth(5); |
||||
|
mNrf24.openReadingPipe(1, DTU_RADIO_ID); |
||||
|
|
||||
|
// enable only receiving interrupts
|
||||
|
mNrf24.maskIRQ(true, true, false); |
||||
|
|
||||
|
// Use lo PA level, as a higher level will disturb CH340 serial usb adapter
|
||||
|
mNrf24.setPALevel(RF24_PA_MAX); |
||||
|
mNrf24.startListening(); |
||||
|
|
||||
|
Serial.println("Radio Config:"); |
||||
|
mNrf24.printPrettyDetails(); |
||||
|
|
||||
|
mSendChannel = getDefaultChannel(); |
||||
|
} |
||||
|
|
||||
|
void handleIntr(void) { |
||||
|
uint8_t pipe, len; |
||||
|
packet_t *p; |
||||
|
|
||||
|
DISABLE_IRQ; |
||||
|
while(mNrf24.available(&pipe)) { |
||||
|
if(!mBufCtrl->full()) { |
||||
|
p = mBufCtrl->getFront(); |
||||
|
memset(p->packet, 0xcc, MAX_RF_PAYLOAD_SIZE); |
||||
|
p->sendCh = mSendChannel; |
||||
|
len = mNrf24.getPayloadSize(); |
||||
|
if(len > MAX_RF_PAYLOAD_SIZE) |
||||
|
len = MAX_RF_PAYLOAD_SIZE; |
||||
|
|
||||
|
mNrf24.read(p->packet, len); |
||||
|
mBufCtrl->pushFront(p); |
||||
|
} |
||||
|
else { |
||||
|
bool tx_ok, tx_fail, rx_ready; |
||||
|
mNrf24.whatHappened(tx_ok, tx_fail, rx_ready); // reset interrupt status
|
||||
|
mNrf24.flush_rx(); // drop the packet
|
||||
|
} |
||||
|
} |
||||
|
RESTORE_IRQ; |
||||
|
} |
||||
|
|
||||
|
uint8_t getDefaultChannel(void) { |
||||
|
return mChanOut[2]; |
||||
|
} |
||||
|
uint8_t getLastChannel(void) { |
||||
|
return mChanOut[mChanIdx]; |
||||
|
} |
||||
|
|
||||
|
uint8_t getNxtChannel(void) { |
||||
|
if(++mChanIdx >= 4) |
||||
|
mChanIdx = 0; |
||||
|
return mChanOut[mChanIdx]; |
||||
|
} |
||||
|
|
||||
|
void sendTimePacket(uint64_t invId, uint32_t ts) { |
||||
|
sendCmdPacket(invId, 0x15, 0x80, false); |
||||
|
mSendBuf[10] = 0x0b; // cid
|
||||
|
mSendBuf[11] = 0x00; |
||||
|
CP_U32_LittleEndian(&mSendBuf[12], ts); |
||||
|
mSendBuf[19] = 0x05; |
||||
|
|
||||
|
uint16_t crc = crc16(&mSendBuf[10], 14); |
||||
|
mSendBuf[24] = (crc >> 8) & 0xff; |
||||
|
mSendBuf[25] = (crc ) & 0xff; |
||||
|
mSendBuf[26] = crc8(mSendBuf, 26); |
||||
|
|
||||
|
sendPacket(invId, mSendBuf, 27); |
||||
|
} |
||||
|
|
||||
|
void sendCmdPacket(uint64_t invId, uint8_t mid, uint8_t cmd, bool calcCrc = true) { |
||||
|
memset(mSendBuf, 0, MAX_RF_PAYLOAD_SIZE); |
||||
|
mSendBuf[0] = mid; // message id
|
||||
|
CP_U32_BigEndian(&mSendBuf[1], (invId >> 8)); |
||||
|
CP_U32_BigEndian(&mSendBuf[5], (DTU_ID >> 8)); |
||||
|
mSendBuf[9] = cmd; |
||||
|
if(calcCrc) { |
||||
|
mSendBuf[10] = crc8(mSendBuf, 10); |
||||
|
sendPacket(invId, mSendBuf, 11); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool checkCrc(uint8_t buf[], uint8_t *len, uint8_t *rptCnt) { |
||||
|
*len = (buf[0] >> 2); |
||||
|
for (int16_t i = MAX_RF_PAYLOAD_SIZE - 1; i >= 0; i--) { |
||||
|
buf[i] = ((buf[i] >> 7) | ((i > 0) ? (buf[i-1] << 1) : 0x00)); |
||||
|
} |
||||
|
uint16_t crc = crc16nrf24(buf, BIT_CNT(*len + 2), 7, mDtuIdCrc); |
||||
|
|
||||
|
bool valid = (crc == ((buf[*len+2] << 8) | (buf[*len+3]))); |
||||
|
|
||||
|
if(valid) { |
||||
|
if(mLastCrc == crc) |
||||
|
*rptCnt = (++mRptCnt); |
||||
|
else { |
||||
|
mRptCnt = 0; |
||||
|
*rptCnt = 0; |
||||
|
mLastCrc = crc; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return valid; |
||||
|
} |
||||
|
|
||||
|
uint8_t pinCs; |
||||
|
uint8_t pinCe; |
||||
|
uint8_t pinIrq; |
||||
|
|
||||
|
private: |
||||
|
void sendPacket(uint64_t invId, uint8_t buf[], uint8_t len) { |
||||
|
//Serial.println("sent packet: #" + String(mSendCnt));
|
||||
|
//dumpBuf("SEN ", buf, len);
|
||||
|
|
||||
|
DISABLE_IRQ; |
||||
|
mNrf24.stopListening(); |
||||
|
|
||||
|
#ifdef CHANNEL_HOP |
||||
|
mSendChannel = getNxtChannel(); |
||||
|
#else |
||||
|
mSendChannel = getDefaultChannel(); |
||||
|
#endif |
||||
|
mNrf24.setChannel(mSendChannel); |
||||
|
//Serial.println("CH: " + String(mSendChannel));
|
||||
|
|
||||
|
mNrf24.openWritingPipe(invId); // TODO: deprecated
|
||||
|
mNrf24.setCRCLength(RF24_CRC_16); |
||||
|
mNrf24.enableDynamicPayloads(); |
||||
|
mNrf24.setAutoAck(true); |
||||
|
mNrf24.setRetries(3, 15); |
||||
|
|
||||
|
mNrf24.write(buf, len); |
||||
|
|
||||
|
// Try to avoid zero payload acks (has no effect)
|
||||
|
mNrf24.openWritingPipe(DUMMY_RADIO_ID); // TODO: why dummy radio id?, deprecated
|
||||
|
|
||||
|
mNrf24.setAutoAck(false); |
||||
|
mNrf24.setRetries(0, 0); |
||||
|
mNrf24.disableDynamicPayloads(); |
||||
|
mNrf24.setCRCLength(RF24_CRC_DISABLED); |
||||
|
|
||||
|
mNrf24.setChannel(DEFAULT_RECV_CHANNEL); |
||||
|
mNrf24.startListening(); |
||||
|
|
||||
|
RESTORE_IRQ; |
||||
|
mSendCnt++; |
||||
|
} |
||||
|
|
||||
|
void calcDtuCrc(void) { |
||||
|
uint64_t addr = DTU_RADIO_ID; |
||||
|
uint8_t tmp[5]; |
||||
|
for(int8_t i = 4; i >= 0; i--) { |
||||
|
tmp[i] = addr; |
||||
|
addr >>= 8; |
||||
|
} |
||||
|
mDtuIdCrc = crc16nrf24(tmp, BIT_CNT(5)); |
||||
|
} |
||||
|
|
||||
|
void dumpBuf(const char *info, uint8_t buf[], uint8_t len) { |
||||
|
Serial.print(String(info)); |
||||
|
for(uint8_t i = 0; i < len; i++) { |
||||
|
Serial.print(buf[i], HEX); |
||||
|
Serial.print(" "); |
||||
|
} |
||||
|
Serial.println(); |
||||
|
} |
||||
|
|
||||
|
uint8_t mChanOut[4]; |
||||
|
uint8_t mChanIdx; |
||||
|
uint16_t mDtuIdCrc; |
||||
|
uint16_t mLastCrc; |
||||
|
uint8_t mRptCnt; |
||||
|
|
||||
|
RF24 mNrf24; |
||||
|
uint8_t mSendChannel; |
||||
|
BUFFER *mBufCtrl; |
||||
|
uint32_t mSendCnt; |
||||
|
uint8_t mSendBuf[MAX_RF_PAYLOAD_SIZE]; |
||||
|
}; |
||||
|
|
||||
|
#endif /*__RADIO_H__*/ |
@ -0,0 +1,163 @@ |
|||||
|
#ifndef __HM_SYSTEM_H__ |
||||
|
#define __HM_SYSTEM_H__ |
||||
|
|
||||
|
#include "hmInverters.h" |
||||
|
#include "hmRadio.h" |
||||
|
|
||||
|
|
||||
|
|
||||
|
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
|
||||
|
} |
||||
|
|
||||
|
void setup() { |
||||
|
Radio.setup(&BufCtrl); |
||||
|
} |
||||
|
|
||||
|
inverter_t *addInverter(const char *name, uint64_t serial, uint8_t type) { |
||||
|
if(MAX_INVERTER <= mNumInv) { |
||||
|
DPRINT("max number of inverters reached!"); |
||||
|
return NULL; |
||||
|
} |
||||
|
inverter_t *p = &mInverter[mNumInv]; |
||||
|
p->id = mNumInv; |
||||
|
p->serial.u64 = serial; |
||||
|
p->type = type; |
||||
|
uint8_t len = strlen(name); |
||||
|
strncpy(p->name, name, (len > MAX_NAME_LENGTH) ? MAX_NAME_LENGTH : len); |
||||
|
getAssignment(p); |
||||
|
toRadioId(p); |
||||
|
|
||||
|
if(NULL == p->assign) { |
||||
|
DPRINT("no assignment for type found!"); |
||||
|
return NULL; |
||||
|
} |
||||
|
else { |
||||
|
mRecord[p->id] = new RECORDTYPE[p->listLen]; |
||||
|
memset(mRecord[p->id], 0, sizeof(RECORDTYPE) * p->listLen); |
||||
|
mNumInv ++; |
||||
|
return p; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
inverter_t *findInverter(uint8_t buf[]) { |
||||
|
inverter_t *p; |
||||
|
for(uint8_t i = 0; i < mNumInv; i++) { |
||||
|
p = &mInverter[i]; |
||||
|
if((p->serial.b[3] == buf[0]) |
||||
|
&& (p->serial.b[2] == buf[1]) |
||||
|
&& (p->serial.b[1] == buf[2]) |
||||
|
&& (p->serial.b[0] == buf[3])) |
||||
|
return p; |
||||
|
} |
||||
|
return NULL; |
||||
|
} |
||||
|
|
||||
|
inverter_t *getInverterByPos(uint8_t pos) { |
||||
|
if(mInverter[pos].serial.u64 != 0ULL) |
||||
|
return &mInverter[pos]; |
||||
|
else |
||||
|
return NULL; |
||||
|
} |
||||
|
|
||||
|
const char *getFieldName(inverter_t *p, uint8_t pos) { |
||||
|
return fields[p->assign[pos].fieldId]; |
||||
|
} |
||||
|
|
||||
|
const char *getUnit(inverter_t *p, uint8_t pos) { |
||||
|
return units[p->assign[pos].unitId]; |
||||
|
} |
||||
|
|
||||
|
uint64_t getSerial(inverter_t *p) { |
||||
|
return p->serial.u64; |
||||
|
} |
||||
|
|
||||
|
void updateSerial(inverter_t *p, uint64_t serial) { |
||||
|
p->serial.u64 = serial; |
||||
|
} |
||||
|
|
||||
|
uint8_t getChannel(inverter_t *p, uint8_t pos) { |
||||
|
return p->assign[pos].ch; |
||||
|
} |
||||
|
|
||||
|
uint8_t getCmdId(inverter_t *p, uint8_t pos) { |
||||
|
return p->assign[pos].cmdId; |
||||
|
} |
||||
|
|
||||
|
void addValue(inverter_t *p, uint8_t pos, uint8_t buf[]) { |
||||
|
uint8_t ptr = p->assign[pos].start; |
||||
|
uint8_t end = ptr + p->assign[pos].num; |
||||
|
uint16_t div = p->assign[pos].div; |
||||
|
|
||||
|
uint32_t val = 0; |
||||
|
do { |
||||
|
val <<= 8; |
||||
|
val |= buf[ptr]; |
||||
|
} while(++ptr != end); |
||||
|
|
||||
|
mRecord[p->id][pos] = (RECORDTYPE)(val) / (RECORDTYPE)(div); |
||||
|
} |
||||
|
|
||||
|
RECORDTYPE getValue(inverter_t *p, uint8_t pos) { |
||||
|
return mRecord[p->id][pos]; |
||||
|
} |
||||
|
|
||||
|
uint8_t getPosByChField(inverter_t *p, uint8_t channel, uint8_t fieldId) { |
||||
|
uint8_t pos = 0; |
||||
|
for(; pos < p->listLen; pos++) { |
||||
|
if((p->assign[pos].ch == channel) && (p->assign[pos].fieldId == fieldId)) |
||||
|
break; |
||||
|
} |
||||
|
return (pos >= p->listLen) ? 0xff : pos; |
||||
|
} |
||||
|
|
||||
|
uint8_t getNumInverters(void) { |
||||
|
return mNumInv; |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
void toRadioId(inverter_t *p) { |
||||
|
p->radioId.u64 = 0ULL; |
||||
|
p->radioId.b[4] = p->serial.b[0]; |
||||
|
p->radioId.b[3] = p->serial.b[1]; |
||||
|
p->radioId.b[2] = p->serial.b[2]; |
||||
|
p->radioId.b[1] = p->serial.b[3]; |
||||
|
p->radioId.b[0] = 0x01; |
||||
|
} |
||||
|
|
||||
|
void getAssignment(inverter_t *p) { |
||||
|
if(INV_TYPE_HM600 == p->type) { |
||||
|
p->listLen = (uint8_t)(HM1200_LIST_LEN); |
||||
|
p->assign = (byteAssign_t*)hm600assignment; |
||||
|
} |
||||
|
else if(INV_TYPE_HM1200 == p->type) { |
||||
|
p->listLen = (uint8_t)(HM1200_LIST_LEN); |
||||
|
p->assign = (byteAssign_t*)hm1200assignment; |
||||
|
} |
||||
|
else if(INV_TYPE_HM400 == p->type) { |
||||
|
p->listLen = (uint8_t)(HM400_LIST_LEN); |
||||
|
p->assign = (byteAssign_t*)hm400assignment; |
||||
|
} |
||||
|
else { |
||||
|
p->listLen = 0; |
||||
|
p->assign = NULL; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
inverter_t mInverter[MAX_INVERTER]; |
||||
|
uint8_t mNumInv; |
||||
|
RECORDTYPE *mRecord[MAX_INVERTER]; |
||||
|
}; |
||||
|
|
||||
|
#endif /*__HM_SYSTEM_H__*/ |
@ -1,178 +0,0 @@ |
|||||
#ifndef __HOYMILES_H__ |
|
||||
#define __HOYMILES_H__ |
|
||||
|
|
||||
#include <RF24.h> |
|
||||
#include <RF24_config.h> |
|
||||
#include "crc.h" |
|
||||
|
|
||||
#define CHANNEL_HOP // switch between channels or use static channel to send
|
|
||||
|
|
||||
#define luint64_t long long unsigned int |
|
||||
|
|
||||
#define DEFAULT_RECV_CHANNEL 3 |
|
||||
#define MAX_RF_PAYLOAD_SIZE 32 |
|
||||
#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL) |
|
||||
#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL) |
|
||||
|
|
||||
#define PACKET_BUFFER_SIZE 30 |
|
||||
|
|
||||
|
|
||||
//-----------------------------------------------------------------------------
|
|
||||
// MACROS
|
|
||||
#define CP_U32_LittleEndian(buf, v) ({ \ |
|
||||
uint8_t *b = buf; \ |
|
||||
b[0] = ((v >> 24) & 0xff); \ |
|
||||
b[1] = ((v >> 16) & 0xff); \ |
|
||||
b[2] = ((v >> 8) & 0xff); \ |
|
||||
b[3] = ((v ) & 0xff); \ |
|
||||
}) |
|
||||
|
|
||||
#define CP_U32_BigEndian(buf, v) ({ \ |
|
||||
uint8_t *b = buf; \ |
|
||||
b[3] = ((v >> 24) & 0xff); \ |
|
||||
b[2] = ((v >> 16) & 0xff); \ |
|
||||
b[1] = ((v >> 8) & 0xff); \ |
|
||||
b[0] = ((v ) & 0xff); \ |
|
||||
}) |
|
||||
|
|
||||
#define BIT_CNT(x) ((x)<<3) |
|
||||
|
|
||||
|
|
||||
//-----------------------------------------------------------------------------
|
|
||||
union uint64Bytes { |
|
||||
uint64_t ull; |
|
||||
uint8_t bytes[8]; |
|
||||
}; |
|
||||
|
|
||||
typedef struct { |
|
||||
uint8_t sendCh; |
|
||||
uint8_t packet[MAX_RF_PAYLOAD_SIZE]; |
|
||||
} NRF24_packet_t; |
|
||||
|
|
||||
|
|
||||
//-----------------------------------------------------------------------------
|
|
||||
class hoymiles { |
|
||||
public: |
|
||||
hoymiles() { |
|
||||
serial2RadioId(); |
|
||||
calcDtuIdCrc(); |
|
||||
|
|
||||
mChannels[0] = 23; |
|
||||
mChannels[1] = 40; |
|
||||
mChannels[2] = 61; |
|
||||
mChannels[3] = 75; |
|
||||
mChanIdx = 1; |
|
||||
|
|
||||
mLastCrc = 0x0000; |
|
||||
mRptCnt = 0; |
|
||||
} |
|
||||
|
|
||||
~hoymiles() {} |
|
||||
|
|
||||
uint8_t getDefaultChannel(void) { |
|
||||
return mChannels[2]; |
|
||||
} |
|
||||
uint8_t getLastChannel(void) { |
|
||||
return mChannels[mChanIdx]; |
|
||||
} |
|
||||
|
|
||||
uint8_t getNxtChannel(void) { |
|
||||
if(++mChanIdx >= 4) |
|
||||
mChanIdx = 0; |
|
||||
return mChannels[mChanIdx]; |
|
||||
} |
|
||||
|
|
||||
void serial2RadioId(void) { |
|
||||
uint64Bytes id; |
|
||||
|
|
||||
id.ull = 0ULL; |
|
||||
id.bytes[4] = mAddrBytes[5]; |
|
||||
id.bytes[3] = mAddrBytes[4]; |
|
||||
id.bytes[2] = mAddrBytes[3]; |
|
||||
id.bytes[1] = mAddrBytes[2]; |
|
||||
id.bytes[0] = 0x01; |
|
||||
|
|
||||
mRadioId = id.ull; |
|
||||
} |
|
||||
|
|
||||
uint8_t getTimePacket(uint8_t buf[], uint32_t ts) { |
|
||||
getCmdPacket(buf, 0x15, 0x80, false); |
|
||||
buf[10] = 0x0b; // cid
|
|
||||
buf[11] = 0x00; |
|
||||
CP_U32_LittleEndian(&buf[12], ts); |
|
||||
buf[19] = 0x05; |
|
||||
|
|
||||
uint16_t crc = crc16(&buf[10], 14); |
|
||||
buf[24] = (crc >> 8) & 0xff; |
|
||||
buf[25] = (crc ) & 0xff; |
|
||||
buf[26] = crc8(buf, 26); |
|
||||
|
|
||||
return 27; |
|
||||
} |
|
||||
|
|
||||
uint8_t getCmdPacket(uint8_t buf[], uint8_t mid, uint8_t cmd, bool calcCrc = true) { |
|
||||
memset(buf, 0, MAX_RF_PAYLOAD_SIZE); |
|
||||
buf[0] = mid; // message id
|
|
||||
CP_U32_BigEndian(&buf[1], (mRadioId >> 8)); |
|
||||
CP_U32_BigEndian(&buf[5], (DTU_RADIO_ID >> 8)); |
|
||||
buf[9] = cmd; |
|
||||
if(calcCrc) |
|
||||
buf[10] = crc8(buf, 10); |
|
||||
|
|
||||
return 11; |
|
||||
} |
|
||||
|
|
||||
bool checkCrc(uint8_t buf[], uint8_t *len, uint8_t *rptCnt) { |
|
||||
*len = (buf[0] >> 2); |
|
||||
for (int16_t i = MAX_RF_PAYLOAD_SIZE - 1; i >= 0; i--) { |
|
||||
buf[i] = ((buf[i] >> 7) | ((i > 0) ? (buf[i-1] << 1) : 0x00)); |
|
||||
} |
|
||||
uint16_t crc = crc16nrf24(buf, BIT_CNT(*len + 2), 7, mDtuIdCrc); |
|
||||
|
|
||||
bool valid = (crc == ((buf[*len+2] << 8) | (buf[*len+3]))); |
|
||||
|
|
||||
if(valid) { |
|
||||
if(mLastCrc == crc) |
|
||||
*rptCnt = (++mRptCnt); |
|
||||
else { |
|
||||
mRptCnt = 0; |
|
||||
*rptCnt = 0; |
|
||||
mLastCrc = crc; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return valid; |
|
||||
} |
|
||||
|
|
||||
void dumpBuf(const char *info, uint8_t buf[], uint8_t len) { |
|
||||
Serial.print(String(info)); |
|
||||
for(uint8_t i = 0; i < len; i++) { |
|
||||
Serial.print(buf[i], HEX); |
|
||||
Serial.print(" "); |
|
||||
} |
|
||||
Serial.println(); |
|
||||
} |
|
||||
|
|
||||
uint8_t mAddrBytes[6]; |
|
||||
luint64_t mRadioId; |
|
||||
|
|
||||
private: |
|
||||
void calcDtuIdCrc(void) { |
|
||||
uint64_t addr = DTU_RADIO_ID; |
|
||||
uint8_t dtuAddr[5]; |
|
||||
for(int8_t i = 4; i >= 0; i--) { |
|
||||
dtuAddr[i] = addr; |
|
||||
addr >>= 8; |
|
||||
} |
|
||||
mDtuIdCrc = crc16nrf24(dtuAddr, BIT_CNT(5)); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
uint8_t mChannels[4]; |
|
||||
uint8_t mChanIdx; |
|
||||
uint16_t mDtuIdCrc; |
|
||||
uint16_t mLastCrc; |
|
||||
uint8_t mRptCnt; |
|
||||
}; |
|
||||
|
|
||||
#endif /*__HOYMILES_H__*/ |
|
@ -1,4 +0,0 @@ |
|||||
..\tools\fileConv.exe index.html h\index_html.h index_html |
|
||||
..\tools\fileConv.exe setup.html h\setup_html.h setup_html |
|
||||
..\tools\fileConv.exe style.css h\style_css.h style_css |
|
||||
pause |
|
@ -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") |
@ -0,0 +1,4 @@ |
|||||
|
#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('/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=\"/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\">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><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\">General</p><div class=\"inputWrp\"><input type=\"text\" class=\"inputText\" name=\"hoy_addr\" value=\"{HOY_ADDR}\" required/><span class=\"floating_label\">HOYMILES ADDRESS (eg. 11:22:33:44:55:66)</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>"; |
#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 { font-size: 14pt; color: #006ec0; padding-bottom: 0px !important; } .fw { width: 60px; display: block; float: left; } .color { width: 50px; height: 50px; border: 1px solid #ccc; } .range { width: 300px; } a:link, a:visited { text-decoration: none; font-size: 13pt; color: #006ec0; } a:hover, a:focus { color: #f00; } #content { padding: 15px 15px 60px 15px; } #footer { position: fixed; bottom: 0px; height: 45px; background-color: #006ec0; width: 100%; } #footer p { color: #fff; padding-left: 20px; padding-right: 20px; font-size: 10pt !important; } #footer a { color: #fff; } #footer a:hover { color: #f00; } div.content { background-color: #fff; padding-bottom: 65px; overflow: hidden; } span.warn { display: inline-block; padding-left: 20px; color: #ff9900; font-style: italic; } input { padding: 10px; font-size: 13pt; } input.button { background-color: #006ec0; color: #fff; border: 0px; float: right; text-transform: uppercase; } input.cb { margin-bottom: 20px; } label { font-size: 14pt; } .left { float: left; } .right { float: right; } .inputWrp { position: relative; } .inputWrp .inputText { height: 35px; width: 90%; margin-bottom: 20px; border: 1px solid #ccc; border-top: none; border-right: none; } .inputWrp .floating_label { position: absolute; pointer-events: none; top: 20px; left: 10px; transition: 0.2s ease all; } .inputWrp input:focus ~ .floating_label, .inputWrp input:not(:focus):valid ~ .floating_label { top: 0px; left: 20px; font-size: 10px; color: blue; opacity: 1; } "; |
#ifndef __STYLE_H__ |
||||
|
#define __STYLE_H__ |
||||
|
const char style_css[] PROGMEM = "h1 {margin:0;padding:20pt;font-size:22pt;color:#fff;background-color:#006ec0;display:block;text-transform:uppercase;}html, body {font-family:Arial;margin:0;padding:0;}p {text-align:justify;font-size:13pt;}.des {margin-top:35px;font-size:14pt;color:#006ec0;}.subdes {font-size:13pt;color:#006ec0;margin-left:7px;}.fw {width:60px;display:block;float:left;}.color {width:50px;height:50px;border:1px solid #ccc;}.range {width:300px;}a:link, a:visited {text-decoration:none;font-size:13pt;color:#006ec0;}a:hover, a:focus {color:#f00;}#content {padding:15px 15px 60px 15px;}#footer {position:fixed;bottom:0px;height:45px;background-color:#006ec0;width:100%;}#footer p {color:#fff;padding-left:20px;padding-right:20px;font-size:10pt !important;}#footer a {color:#fff;}div.content {background-color:#fff;padding-bottom:65px;overflow:hidden;}input, select {padding:7px;font-size:13pt;}input.text, select {width:70%;box-sizing:border-box;margin-bottom:10px;border:1px solid #ccc;}input.btn {background-color:#006ec0;color:#fff;border:0px;float:right;text-transform:uppercase;}input.cb {margin-bottom:20px;}label {width:20%;display:inline-block;font-size:12pt;padding-right:10px;margin-left:10px;}.left {float:left;}.right {float:right;}div.ch {width:250px;height:410px;background-color:#006ec0;display:inline-block;margin-right:20px;margin-bottom:20px;}div.ch .value, div.ch .info, div.ch .head {color:#fff;display:block;width:100%;text-align:center;}div.ch .unit {font-size:19px;margin-left:10px;}div.ch .value {margin-top:20px;font-size:30px;}div.ch .info {margin-top:3px;font-size:10px;}div.ch .head {background-color:#003c80;padding:10px 0 10px 0;}"; |
||||
|
#endif /*__STYLE_H__*/ |
||||
|
@ -0,0 +1,42 @@ |
|||||
|
<!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> |
@ -0,0 +1,87 @@ |
|||||
|
#ifndef __MQTT_H__ |
||||
|
#define __MQTT_H__ |
||||
|
|
||||
|
#include <ESP8266WiFi.h> |
||||
|
#include <PubSubClient.h> |
||||
|
#include "defines.h" |
||||
|
|
||||
|
class mqtt { |
||||
|
public: |
||||
|
mqtt() { |
||||
|
mClient = new PubSubClient(mEspClient); |
||||
|
mAddressSet = false; |
||||
|
|
||||
|
memset(mUser, 0, MQTT_USER_LEN); |
||||
|
memset(mPwd, 0, MQTT_PWD_LEN); |
||||
|
memset(mTopic, 0, MQTT_TOPIC_LEN); |
||||
|
} |
||||
|
|
||||
|
~mqtt() { |
||||
|
delete mClient; |
||||
|
} |
||||
|
|
||||
|
void setup(const char *broker, const char *topic, const char *user, const char *pwd) { |
||||
|
mAddressSet = true; |
||||
|
mClient->setServer(broker, 1883); |
||||
|
|
||||
|
snprintf(mUser, MQTT_USER_LEN, "%s", user); |
||||
|
snprintf(mPwd, MQTT_PWD_LEN, "%s", pwd); |
||||
|
snprintf(mTopic, MQTT_TOPIC_LEN, "%s", topic); |
||||
|
} |
||||
|
|
||||
|
void sendMsg(const char *topic, const char *msg) { |
||||
|
if(mAddressSet) { |
||||
|
char top[64]; |
||||
|
snprintf(top, 64, "%s/%s", mTopic, topic); |
||||
|
|
||||
|
if(!mClient->connected()) |
||||
|
reconnect(); |
||||
|
mClient->publish(top, msg); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool isConnected(bool doRecon = false) { |
||||
|
if(doRecon) |
||||
|
reconnect(); |
||||
|
return mClient->connected(); |
||||
|
} |
||||
|
|
||||
|
char *getUser(void) { |
||||
|
return mUser; |
||||
|
} |
||||
|
|
||||
|
char *getPwd(void) { |
||||
|
return mPwd; |
||||
|
} |
||||
|
|
||||
|
char *getTopic(void) { |
||||
|
return mTopic; |
||||
|
} |
||||
|
|
||||
|
void loop() { |
||||
|
//if(!mClient->connected())
|
||||
|
// reconnect();
|
||||
|
mClient->loop(); |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
void reconnect(void) { |
||||
|
if(!mClient->connected()) { |
||||
|
String mqttId = "ESP-" + String(random(0xffff), HEX); |
||||
|
if((strlen(mUser) > 0) && (strlen(mPwd) > 0)) |
||||
|
mClient->connect(mqttId.c_str(), mUser, mPwd); |
||||
|
else |
||||
|
mClient->connect(mqttId.c_str()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
WiFiClient mEspClient; |
||||
|
PubSubClient *mClient; |
||||
|
|
||||
|
bool mAddressSet; |
||||
|
char mUser[MQTT_USER_LEN]; |
||||
|
char mPwd[MQTT_PWD_LEN]; |
||||
|
char mTopic[MQTT_TOPIC_LEN]; |
||||
|
}; |
||||
|
|
||||
|
#endif /*__MQTT_H_*/ |
Binary file not shown.
Loading…
Reference in new issue