mirror of https://github.com/lumapu/ahoy.git
53 changed files with 3017 additions and 605 deletions
@ -0,0 +1,437 @@ |
|||||
|
//-----------------------------------------------------------------------------
|
||||
|
// 2023 Ahoy, https://github.com/lumpapu/ahoy
|
||||
|
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
|
||||
|
#ifndef __CMT2300A_H__ |
||||
|
#define __CMT2300A_H__ |
||||
|
|
||||
|
#include "esp32_3wSpi.h" |
||||
|
|
||||
|
#define WORK_FREQ_KHZ 865000 // disired work frequency between DTU and
|
||||
|
// inverter in kHz
|
||||
|
#define HOY_BASE_FREQ_KHZ 860000 // in kHz
|
||||
|
#define HOY_MAX_FREQ_KHZ 923500 // 0xFE * 250kHz + Base_freq
|
||||
|
#define HOY_BOOT_FREQ_KHZ 868000 // Hoymiles boot/init frequency after power up inverter
|
||||
|
#define FREQ_STEP_KHZ 250 // channel step size in kHz
|
||||
|
#define FREQ_WARN_MIN_KHZ 863000 // for EU 863 - 870 MHz is allowed
|
||||
|
#define FREQ_WARN_MAX_KHZ 870000 // for EU 863 - 870 MHz is allowed
|
||||
|
|
||||
|
// detailed register infos from AN142_CMT2300AW_Quick_Start_Guide-Rev0.8.pdf
|
||||
|
|
||||
|
#define CMT2300A_MASK_CFG_RETAIN 0x10 |
||||
|
#define CMT2300A_MASK_RSTN_IN_EN 0x20 |
||||
|
#define CMT2300A_MASK_LOCKING_EN 0x20 |
||||
|
#define CMT2300A_MASK_CHIP_MODE_STA 0x0F |
||||
|
|
||||
|
#define CMT2300A_CUS_CMT10 0x09 |
||||
|
|
||||
|
#define CMT2300A_CUS_MODE_CTL 0x60 // [7] go_switch
|
||||
|
// [6] go_tx
|
||||
|
// [5] go_tfs
|
||||
|
// [4] go_sleep
|
||||
|
// [3] go_rx
|
||||
|
// [2] go_rfs
|
||||
|
// [1] go_stby
|
||||
|
// [0] n/a
|
||||
|
|
||||
|
#define CMT2300A_CUS_MODE_STA 0x61 // [3:0] 0x00 IDLE
|
||||
|
// 0x01 SLEEP
|
||||
|
// 0x02 STBY
|
||||
|
// 0x03 RFS
|
||||
|
// 0x04 TFS
|
||||
|
// 0x05 RX
|
||||
|
// 0x06 TX
|
||||
|
// 0x08 UNLOCKED/LOW_VDD
|
||||
|
// 0x09 CAL
|
||||
|
#define CMT2300A_CUS_EN_CTL 0x62 |
||||
|
#define CMT2300A_CUS_FREQ_CHNL 0x63 |
||||
|
|
||||
|
#define CMT2300A_CUS_IO_SEL 0x65 // [5:4] GPIO3
|
||||
|
// 0x00 CLKO
|
||||
|
// 0x01 DOUT / DIN
|
||||
|
// 0x02 INT2
|
||||
|
// 0x03 DCLK
|
||||
|
// [3:2] GPIO2
|
||||
|
// 0x00 INT1
|
||||
|
// 0x01 INT2
|
||||
|
// 0x02 DOUT / DIN
|
||||
|
// 0x03 DCLK
|
||||
|
// [1:0] GPIO1
|
||||
|
// 0x00 DOUT / DIN
|
||||
|
// 0x01 INT1
|
||||
|
// 0x02 INT2
|
||||
|
// 0x03 DCLK
|
||||
|
|
||||
|
#define CMT2300A_CUS_INT1_CTL 0x66 // [4:0] INT1_SEL
|
||||
|
// 0x00 RX active
|
||||
|
// 0x01 TX active
|
||||
|
// 0x02 RSSI VLD
|
||||
|
// 0x03 Pream OK
|
||||
|
// 0x04 SYNC OK
|
||||
|
// 0x05 NODE OK
|
||||
|
// 0x06 CRC OK
|
||||
|
// 0x07 PKT OK
|
||||
|
// 0x08 SL TMO
|
||||
|
// 0x09 RX TMO
|
||||
|
// 0x0A TX DONE
|
||||
|
// 0x0B RX FIFO NMTY
|
||||
|
// 0x0C RX FIFO TH
|
||||
|
// 0x0D RX FIFO FULL
|
||||
|
// 0x0E RX FIFO WBYTE
|
||||
|
// 0x0F RX FIFO OVF
|
||||
|
// 0x10 TX FIFO NMTY
|
||||
|
// 0x11 TX FIFO TH
|
||||
|
// 0x12 TX FIFO FULL
|
||||
|
// 0x13 STATE IS STBY
|
||||
|
// 0x14 STATE IS FS
|
||||
|
// 0x15 STATE IS RX
|
||||
|
// 0x16 STATE IS TX
|
||||
|
// 0x17 LED
|
||||
|
// 0x18 TRX ACTIVE
|
||||
|
// 0x19 PKT DONE
|
||||
|
|
||||
|
#define CMT2300A_CUS_INT2_CTL 0x67 // [4:0] INT2_SEL
|
||||
|
|
||||
|
#define CMT2300A_CUS_INT_EN 0x68 // [7] SL TMO EN
|
||||
|
// [6] RX TMO EN
|
||||
|
// [5] TX DONE EN
|
||||
|
// [4] PREAM OK EN
|
||||
|
// [3] SYNC_OK EN
|
||||
|
// [2] NODE OK EN
|
||||
|
// [1] CRC OK EN
|
||||
|
// [0] PKT DONE EN
|
||||
|
|
||||
|
#define CMT2300A_CUS_FIFO_CTL 0x69 // [7] TX DIN EN
|
||||
|
// [6:5] TX DIN SEL
|
||||
|
// 0x00 SEL GPIO1
|
||||
|
// 0x01 SEL GPIO2
|
||||
|
// 0x02 SEL GPIO3
|
||||
|
// [4] FIFO AUTO CLR DIS
|
||||
|
// [3] FIFO TX RD EN
|
||||
|
// [2] FIFO RX TX SEL
|
||||
|
// [1] FIFO MERGE EN
|
||||
|
// [0] SPI FIFO RD WR SEL
|
||||
|
|
||||
|
#define CMT2300A_CUS_INT_CLR1 0x6A // clear interrupts Bank1
|
||||
|
#define CMT2300A_CUS_INT_CLR2 0x6B // clear interrupts Bank2
|
||||
|
#define CMT2300A_CUS_FIFO_CLR 0x6C |
||||
|
|
||||
|
#define CMT2300A_CUS_INT_FLAG 0x6D // [7] LBD FLG
|
||||
|
// [6] COL ERR FLG
|
||||
|
// [5] PKT ERR FLG
|
||||
|
// [4] PREAM OK FLG
|
||||
|
// [3] SYNC OK FLG
|
||||
|
// [2] NODE OK FLG
|
||||
|
// [1] CRC OK FLG
|
||||
|
// [0] PKT OK FLG
|
||||
|
|
||||
|
#define CMT2300A_CUS_RSSI_DBM 0x70 |
||||
|
|
||||
|
#define CMT2300A_GO_SWITCH 0x80 |
||||
|
#define CMT2300A_GO_TX 0x40 |
||||
|
#define CMT2300A_GO_TFS 0x20 |
||||
|
#define CMT2300A_GO_SLEEP 0x10 |
||||
|
#define CMT2300A_GO_RX 0x08 |
||||
|
#define CMT2300A_GO_RFS 0x04 |
||||
|
#define CMT2300A_GO_STBY 0x02 |
||||
|
#define CMT2300A_GO_EEPROM 0x01 |
||||
|
|
||||
|
#define CMT2300A_STA_IDLE 0x00 |
||||
|
#define CMT2300A_STA_SLEEP 0x01 |
||||
|
#define CMT2300A_STA_STBY 0x02 |
||||
|
#define CMT2300A_STA_RFS 0x03 |
||||
|
#define CMT2300A_STA_TFS 0x04 |
||||
|
#define CMT2300A_STA_RX 0x05 |
||||
|
#define CMT2300A_STA_TX 0x06 |
||||
|
#define CMT2300A_STA_EEPROM 0x07 |
||||
|
#define CMT2300A_STA_ERROR 0x08 |
||||
|
#define CMT2300A_STA_CAL 0x09 |
||||
|
|
||||
|
#define CMT2300A_INT_SEL_TX_DONE 0x0A |
||||
|
|
||||
|
#define CMT2300A_MASK_TX_DONE_FLG 0x08 |
||||
|
#define CMT2300A_MASK_PKT_OK_FLG 0x01 |
||||
|
|
||||
|
// default CMT paramters
|
||||
|
static uint8_t cmtConfig[0x60] PROGMEM { |
||||
|
// 0x00 - 0x0f -- RSSI offset +- 0 and 13dBm
|
||||
|
0x00, 0x66, 0xEC, 0x1C, 0x70, 0x80, 0x14, 0x08, |
||||
|
0x11, 0x02, 0x02, 0x00, 0xAE, 0xE0, 0x35, 0x00, |
||||
|
// 0x10 - 0x1f
|
||||
|
0x00, 0xF4, 0x10, 0xE2, 0x42, 0x20, 0x0C, 0x81, |
||||
|
0x42, 0x32, 0xCF, 0x82, 0x42, 0x27, 0x76, 0x12, // 860MHz as default
|
||||
|
// 0x20 - 0x2f
|
||||
|
0xA6, 0xC9, 0x20, 0x20, 0xD2, 0x35, 0x0C, 0x0A, |
||||
|
0x9F, 0x4B, 0x29, 0x29, 0xC0, 0x14, 0x05, 0x53, |
||||
|
// 0x30 - 0x3f
|
||||
|
0x10, 0x00, 0xB4, 0x00, 0x00, 0x01, 0x00, 0x00, |
||||
|
0x12, 0x1E, 0x00, 0xAA, 0x06, 0x00, 0x00, 0x00, |
||||
|
// 0x40 - 0x4f
|
||||
|
0x00, 0x48, 0x5A, 0x48, 0x4D, 0x01, 0x1D, 0x00, |
||||
|
0x00, 0x00, 0x00, 0x00, 0xC3, 0x00, 0x00, 0x60, |
||||
|
// 0x50 - 0x5f
|
||||
|
0xFF, 0x00, 0x00, 0x1F, 0x10, 0x70, 0x4D, 0x06, |
||||
|
0x00, 0x07, 0x50, 0x00, 0x42, 0x0C, 0x3F, 0x7F // - TX 13dBm
|
||||
|
}; |
||||
|
|
||||
|
|
||||
|
enum {CMT_SUCCESS = 0, CMT_ERR_SWITCH_STATE, CMT_ERR_TX_PENDING, CMT_FIFO_EMPTY, CMT_ERR_RX_IN_FIFO}; |
||||
|
|
||||
|
template<class SPI> |
||||
|
class Cmt2300a { |
||||
|
typedef SPI SpiType; |
||||
|
public: |
||||
|
Cmt2300a() {} |
||||
|
|
||||
|
void setup(uint8_t pinCsb, uint8_t pinFcsb) { |
||||
|
mSpi.setup(pinCsb, pinFcsb); |
||||
|
init(); |
||||
|
} |
||||
|
|
||||
|
void setup() { |
||||
|
mSpi.setup(); |
||||
|
init(); |
||||
|
} |
||||
|
|
||||
|
// call as often as possible
|
||||
|
void loop() { |
||||
|
if(mTxPending) { |
||||
|
if(CMT2300A_MASK_TX_DONE_FLG == mSpi.readReg(CMT2300A_CUS_INT_CLR1)) { |
||||
|
if(cmtSwitchStatus(CMT2300A_GO_STBY, CMT2300A_STA_STBY)) { |
||||
|
mTxPending = false; |
||||
|
goRx(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
uint8_t goRx(void) { |
||||
|
if(mTxPending) |
||||
|
return CMT_ERR_TX_PENDING; |
||||
|
|
||||
|
if(mInRxMode) |
||||
|
return CMT_SUCCESS; |
||||
|
|
||||
|
mSpi.readReg(CMT2300A_CUS_INT1_CTL); |
||||
|
mSpi.writeReg(CMT2300A_CUS_INT1_CTL, CMT2300A_INT_SEL_TX_DONE); |
||||
|
|
||||
|
uint8_t tmp = mSpi.readReg(CMT2300A_CUS_INT_CLR1); |
||||
|
if(0x08 == tmp) // first time after TX a value of 0x08 is read
|
||||
|
mSpi.writeReg(CMT2300A_CUS_INT_CLR1, 0x04); |
||||
|
else |
||||
|
mSpi.writeReg(CMT2300A_CUS_INT_CLR1, 0x00); |
||||
|
|
||||
|
if(0x10 == tmp) |
||||
|
mSpi.writeReg(CMT2300A_CUS_INT_CLR2, 0x10); |
||||
|
else |
||||
|
mSpi.writeReg(CMT2300A_CUS_INT_CLR2, 0x00); |
||||
|
|
||||
|
mSpi.writeReg(CMT2300A_CUS_FIFO_CTL, 0x02); |
||||
|
mSpi.writeReg(CMT2300A_CUS_FIFO_CLR, 0x02); |
||||
|
mSpi.writeReg(0x16, 0x0C); // [4:3]: RSSI_DET_SEL, [2:0]: RSSI_AVG_MODE
|
||||
|
|
||||
|
if(!cmtSwitchStatus(CMT2300A_GO_RX, CMT2300A_STA_RX)) |
||||
|
return CMT_ERR_SWITCH_STATE; |
||||
|
|
||||
|
mInRxMode = true; |
||||
|
|
||||
|
return CMT_SUCCESS; |
||||
|
} |
||||
|
|
||||
|
uint8_t getRx(uint8_t buf[], uint8_t len, int8_t *rssi) { |
||||
|
if(mTxPending) |
||||
|
return CMT_ERR_TX_PENDING; |
||||
|
|
||||
|
if(0x1b != (mSpi.readReg(CMT2300A_CUS_INT_FLAG) & 0x1b)) |
||||
|
return CMT_FIFO_EMPTY; |
||||
|
|
||||
|
// receive ok (pream, sync, node, crc)
|
||||
|
if(!cmtSwitchStatus(CMT2300A_GO_STBY, CMT2300A_STA_STBY)) |
||||
|
return CMT_ERR_SWITCH_STATE; |
||||
|
|
||||
|
mSpi.readFifo(buf, len); |
||||
|
*rssi = mSpi.readReg(CMT2300A_CUS_RSSI_DBM) - 128; |
||||
|
|
||||
|
if(!cmtSwitchStatus(CMT2300A_GO_SLEEP, CMT2300A_STA_SLEEP)) |
||||
|
return CMT_ERR_SWITCH_STATE; |
||||
|
|
||||
|
if(!cmtSwitchStatus(CMT2300A_GO_STBY, CMT2300A_STA_STBY)) |
||||
|
return CMT_ERR_SWITCH_STATE; |
||||
|
|
||||
|
mInRxMode = false; |
||||
|
mCusIntFlag = mSpi.readReg(CMT2300A_CUS_INT_FLAG); |
||||
|
|
||||
|
return CMT_SUCCESS; |
||||
|
} |
||||
|
|
||||
|
uint8_t tx(uint8_t buf[], uint8_t len) { |
||||
|
if(mTxPending) |
||||
|
return CMT_ERR_TX_PENDING; |
||||
|
|
||||
|
if(mInRxMode) { |
||||
|
mInRxMode = false; |
||||
|
if(!cmtSwitchStatus(CMT2300A_GO_STBY, CMT2300A_STA_STBY)) |
||||
|
return CMT_ERR_SWITCH_STATE; |
||||
|
} |
||||
|
|
||||
|
mSpi.writeReg(CMT2300A_CUS_INT1_CTL, CMT2300A_INT_SEL_TX_DONE); |
||||
|
|
||||
|
// no data received
|
||||
|
mSpi.readReg(CMT2300A_CUS_INT_CLR1); |
||||
|
mSpi.writeReg(CMT2300A_CUS_INT_CLR1, 0x00); |
||||
|
mSpi.writeReg(CMT2300A_CUS_INT_CLR2, 0x00); |
||||
|
|
||||
|
mSpi.writeReg(CMT2300A_CUS_FIFO_CTL, 0x07); |
||||
|
mSpi.writeReg(CMT2300A_CUS_FIFO_CLR, 0x01); |
||||
|
|
||||
|
mSpi.writeReg(0x45, 0x01); |
||||
|
mSpi.writeReg(0x46, len); // payload length
|
||||
|
|
||||
|
mSpi.writeFifo(buf, len); |
||||
|
|
||||
|
if(0xff != mRqstCh) { |
||||
|
mCurCh = mRqstCh; |
||||
|
mRqstCh = 0xff; |
||||
|
mSpi.writeReg(CMT2300A_CUS_FREQ_CHNL, mCurCh); |
||||
|
} |
||||
|
|
||||
|
if(!cmtSwitchStatus(CMT2300A_GO_TX, CMT2300A_STA_TX)) |
||||
|
return CMT_ERR_SWITCH_STATE; |
||||
|
|
||||
|
// wait for tx done
|
||||
|
mTxPending = true; |
||||
|
|
||||
|
return CMT_SUCCESS; |
||||
|
} |
||||
|
|
||||
|
// initialize CMT2300A, returns true on success
|
||||
|
bool reset(void) { |
||||
|
mSpi.writeReg(0x7f, 0xff); // soft reset
|
||||
|
delay(30); |
||||
|
|
||||
|
if(!cmtSwitchStatus(CMT2300A_GO_STBY, CMT2300A_STA_STBY)) |
||||
|
return false; |
||||
|
|
||||
|
mSpi.writeReg(CMT2300A_CUS_MODE_STA, 0x52); |
||||
|
mSpi.writeReg(0x62, 0x20); |
||||
|
|
||||
|
for(uint8_t i = 0; i < 0x60; i++) { |
||||
|
mSpi.writeReg(i, cmtConfig[i]); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
mSpi.writeReg(CMT2300A_CUS_IO_SEL, 0x20); // -> GPIO3_SEL[1:0] = 0x02
|
||||
|
|
||||
|
// interrupt 1 control selection to TX DONE
|
||||
|
if(CMT2300A_INT_SEL_TX_DONE != mSpi.readReg(CMT2300A_CUS_INT1_CTL)) |
||||
|
mSpi.writeReg(CMT2300A_CUS_INT1_CTL, CMT2300A_INT_SEL_TX_DONE); |
||||
|
|
||||
|
// select interrupt 2
|
||||
|
if(0x07 != mSpi.readReg(CMT2300A_CUS_INT2_CTL)) |
||||
|
mSpi.writeReg(CMT2300A_CUS_INT2_CTL, 0x07); |
||||
|
|
||||
|
// interrupt enable (TX_DONE, PREAM_OK, SYNC_OK, CRC_OK, PKT_DONE)
|
||||
|
mSpi.writeReg(CMT2300A_CUS_INT_EN, 0x3B); |
||||
|
|
||||
|
mSpi.writeReg(0x64, 0x64); |
||||
|
|
||||
|
if(0x00 == mSpi.readReg(CMT2300A_CUS_FIFO_CTL)) |
||||
|
mSpi.writeReg(CMT2300A_CUS_FIFO_CTL, 0x02); // FIFO_MERGE_EN
|
||||
|
|
||||
|
if(!cmtSwitchStatus(CMT2300A_GO_SLEEP, CMT2300A_STA_SLEEP)) |
||||
|
return false; |
||||
|
|
||||
|
delayMicroseconds(95); |
||||
|
|
||||
|
if(!cmtSwitchStatus(CMT2300A_GO_STBY, CMT2300A_STA_STBY)) |
||||
|
return false; |
||||
|
|
||||
|
if(!cmtSwitchStatus(CMT2300A_GO_SLEEP, CMT2300A_STA_SLEEP)) |
||||
|
return false; |
||||
|
|
||||
|
if(!cmtSwitchStatus(CMT2300A_GO_STBY, CMT2300A_STA_STBY)) |
||||
|
return false; |
||||
|
|
||||
|
//switchDtuFreq(WORK_FREQ_KHZ);
|
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
inline uint8_t freq2Chan(const uint32_t freqKhz) { |
||||
|
if((freqKhz % FREQ_STEP_KHZ) != 0) { |
||||
|
DPRINT(DBG_WARN, F("swtich frequency to ")); |
||||
|
DBGPRINT(String(freqKhz)); |
||||
|
DBGPRINT(F("kHz not possible!")); |
||||
|
return 0xff; // error
|
||||
|
// apply the nearest frequency
|
||||
|
//freqKhz = (freqKhz + FREQ_STEP_KHZ/2) / FREQ_STEP_KHZ;
|
||||
|
//freqKhz *= FREQ_STEP_KHZ;
|
||||
|
} |
||||
|
|
||||
|
if((freqKhz < HOY_BASE_FREQ_KHZ) || (freqKhz > HOY_MAX_FREQ_KHZ)) |
||||
|
return 0xff; // error
|
||||
|
|
||||
|
if((freqKhz < FREQ_WARN_MIN_KHZ) || (freqKhz > FREQ_WARN_MAX_KHZ)) |
||||
|
DPRINTLN(DBG_WARN, F("Disired frequency is out of EU legal range! (863 - 870MHz)")); |
||||
|
|
||||
|
return (freqKhz - HOY_BASE_FREQ_KHZ) / FREQ_STEP_KHZ; |
||||
|
} |
||||
|
|
||||
|
inline void switchChannel(uint8_t ch) { |
||||
|
mRqstCh = ch; |
||||
|
} |
||||
|
|
||||
|
inline uint32_t getFreqKhz(void) { |
||||
|
if(0xff != mRqstCh) |
||||
|
return HOY_BASE_FREQ_KHZ + (mRqstCh * FREQ_STEP_KHZ); |
||||
|
else |
||||
|
return HOY_BASE_FREQ_KHZ + (mCurCh * FREQ_STEP_KHZ); |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
void init() { |
||||
|
mTxPending = false; |
||||
|
mInRxMode = false; |
||||
|
mCusIntFlag = 0x00; |
||||
|
mCnt = 0; |
||||
|
mRqstCh = 0xff; |
||||
|
mCurCh = 0x20; |
||||
|
} |
||||
|
|
||||
|
// CMT state machine, wait for next state, true on success
|
||||
|
bool cmtSwitchStatus(uint8_t cmd, uint8_t waitFor, uint16_t cycles = 40) { |
||||
|
mSpi.writeReg(CMT2300A_CUS_MODE_CTL, cmd); |
||||
|
while(cycles--) { |
||||
|
yield(); |
||||
|
delayMicroseconds(10); |
||||
|
if(waitFor == (getChipStatus() & waitFor)) |
||||
|
return true; |
||||
|
} |
||||
|
//Serial.println("status wait for: " + String(waitFor, HEX) + " read: " + String(getChipStatus(), HEX));
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
inline bool switchDtuFreq(const uint32_t freqKhz) { |
||||
|
uint8_t toCh = freq2Chan(freqKhz); |
||||
|
if(0xff == toCh) |
||||
|
return false; |
||||
|
|
||||
|
switchChannel(toCh); |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
inline uint8_t getChipStatus(void) { |
||||
|
return mSpi.readReg(CMT2300A_CUS_MODE_STA) & CMT2300A_MASK_CHIP_MODE_STA; |
||||
|
} |
||||
|
|
||||
|
SpiType mSpi; |
||||
|
uint8_t mCnt; |
||||
|
bool mTxPending; |
||||
|
bool mInRxMode; |
||||
|
uint8_t mCusIntFlag; |
||||
|
uint8_t mRqstCh, mCurCh; |
||||
|
}; |
||||
|
|
||||
|
#endif /*__CMT2300A_H__*/ |
@ -0,0 +1,189 @@ |
|||||
|
//-----------------------------------------------------------------------------
|
||||
|
// 2023 Ahoy, https://github.com/lumpapu/ahoy
|
||||
|
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
|
||||
|
#ifndef __ESP32_3WSPI_H__ |
||||
|
#define __ESP32_3WSPI_H__ |
||||
|
|
||||
|
#include "Arduino.h" |
||||
|
#if defined(ESP32) |
||||
|
#include "driver/spi_master.h" |
||||
|
#include "esp_rom_gpio.h" // for esp_rom_gpio_connect_out_signal |
||||
|
|
||||
|
#if CONFIG_IDF_TARGET_ESP32S3 |
||||
|
#define CLK_PIN 6 |
||||
|
#define MOSI_PIN 5 |
||||
|
#else |
||||
|
#define CLK_PIN 18 |
||||
|
#define MOSI_PIN 23 |
||||
|
#endif |
||||
|
|
||||
|
#define SPI_CLK 1 * 1000 * 1000 // 1MHz
|
||||
|
|
||||
|
#define SPI_PARAM_LOCK() \ |
||||
|
do { \ |
||||
|
} while (xSemaphoreTake(paramLock, portMAX_DELAY) != pdPASS) |
||||
|
#define SPI_PARAM_UNLOCK() xSemaphoreGive(paramLock) |
||||
|
|
||||
|
// for ESP32 this is the so-called HSPI
|
||||
|
// for ESP32-S2/S3/C3 this nomenclature does not really exist anymore,
|
||||
|
// it is simply the first externally usable hardware SPI master controller
|
||||
|
#define SPI_CMT SPI2_HOST |
||||
|
|
||||
|
template<uint8_t CSB_PIN=5, uint8_t FCSB_PIN=4> //, uint8_t GPIO3_PIN=15>
|
||||
|
class esp32_3wSpi { |
||||
|
public: |
||||
|
esp32_3wSpi() { |
||||
|
mInitialized = false; |
||||
|
} |
||||
|
|
||||
|
void setup(uint8_t pinCsb = CSB_PIN, uint8_t pinFcsb = FCSB_PIN) { //, uint8_t pinGpio3 = GPIO3_PIN) {
|
||||
|
paramLock = xSemaphoreCreateMutex(); |
||||
|
spi_bus_config_t buscfg = { |
||||
|
.mosi_io_num = MOSI_PIN, |
||||
|
.miso_io_num = -1, // single wire MOSI/MISO
|
||||
|
.sclk_io_num = CLK_PIN, |
||||
|
.quadwp_io_num = -1, |
||||
|
.quadhd_io_num = -1, |
||||
|
.max_transfer_sz = 32, |
||||
|
}; |
||||
|
spi_device_interface_config_t devcfg = { |
||||
|
.command_bits = 1, |
||||
|
.address_bits = 7, |
||||
|
.dummy_bits = 0, |
||||
|
.mode = 0, |
||||
|
.cs_ena_pretrans = 1, |
||||
|
.cs_ena_posttrans = 1, |
||||
|
.clock_speed_hz = SPI_CLK, |
||||
|
.spics_io_num = pinCsb, |
||||
|
.flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_3WIRE, |
||||
|
.queue_size = 1, |
||||
|
.pre_cb = NULL, |
||||
|
.post_cb = NULL, |
||||
|
}; |
||||
|
|
||||
|
ESP_ERROR_CHECK(spi_bus_initialize(SPI_CMT, &buscfg, SPI_DMA_DISABLED)); |
||||
|
ESP_ERROR_CHECK(spi_bus_add_device(SPI_CMT, &devcfg, &spi_reg)); |
||||
|
|
||||
|
// FiFo
|
||||
|
spi_device_interface_config_t devcfg2 = { |
||||
|
.command_bits = 0, |
||||
|
.address_bits = 0, |
||||
|
.dummy_bits = 0, |
||||
|
.mode = 0, |
||||
|
.cs_ena_pretrans = 2, |
||||
|
.cs_ena_posttrans = (uint8_t)(1 / (SPI_CLK * 10e6 * 2) + 2), // >2 us
|
||||
|
.clock_speed_hz = SPI_CLK, |
||||
|
.spics_io_num = pinFcsb, |
||||
|
.flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_3WIRE, |
||||
|
.queue_size = 1, |
||||
|
.pre_cb = NULL, |
||||
|
.post_cb = NULL, |
||||
|
}; |
||||
|
ESP_ERROR_CHECK(spi_bus_add_device(SPI_CMT, &devcfg2, &spi_fifo)); |
||||
|
|
||||
|
esp_rom_gpio_connect_out_signal(MOSI_PIN, spi_periph_signal[SPI_CMT].spid_out, true, false); |
||||
|
delay(100); |
||||
|
|
||||
|
//pinMode(pinGpio3, INPUT);
|
||||
|
mInitialized = true; |
||||
|
} |
||||
|
|
||||
|
void writeReg(uint8_t addr, uint8_t reg) { |
||||
|
if(!mInitialized) |
||||
|
return; |
||||
|
|
||||
|
uint8_t tx_data; |
||||
|
tx_data = ~reg; |
||||
|
spi_transaction_t t = { |
||||
|
.cmd = 1, |
||||
|
.addr = (uint64_t)(~addr), |
||||
|
.length = 8, |
||||
|
.tx_buffer = &tx_data, |
||||
|
.rx_buffer = NULL |
||||
|
}; |
||||
|
SPI_PARAM_LOCK(); |
||||
|
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_reg, &t)); |
||||
|
SPI_PARAM_UNLOCK(); |
||||
|
delayMicroseconds(100); |
||||
|
} |
||||
|
|
||||
|
uint8_t readReg(uint8_t addr) { |
||||
|
if(!mInitialized) |
||||
|
return 0; |
||||
|
|
||||
|
uint8_t rx_data; |
||||
|
spi_transaction_t t = { |
||||
|
.cmd = 0, |
||||
|
.addr = (uint64_t)(~addr), |
||||
|
.length = 8, |
||||
|
.rxlength = 8, |
||||
|
.tx_buffer = NULL, |
||||
|
.rx_buffer = &rx_data |
||||
|
}; |
||||
|
|
||||
|
SPI_PARAM_LOCK(); |
||||
|
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_reg, &t)); |
||||
|
SPI_PARAM_UNLOCK(); |
||||
|
delayMicroseconds(100); |
||||
|
return rx_data; |
||||
|
} |
||||
|
|
||||
|
void writeFifo(uint8_t buf[], uint8_t len) { |
||||
|
if(!mInitialized) |
||||
|
return; |
||||
|
uint8_t tx_data; |
||||
|
|
||||
|
spi_transaction_t t = { |
||||
|
.length = 8, |
||||
|
.tx_buffer = &tx_data, // reference to write data
|
||||
|
.rx_buffer = NULL |
||||
|
}; |
||||
|
|
||||
|
SPI_PARAM_LOCK(); |
||||
|
for(uint8_t i = 0; i < len; i++) { |
||||
|
tx_data = ~buf[i]; // negate buffer contents
|
||||
|
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_fifo, &t)); |
||||
|
delayMicroseconds(4); // > 4 us
|
||||
|
} |
||||
|
SPI_PARAM_UNLOCK(); |
||||
|
} |
||||
|
|
||||
|
void readFifo(uint8_t buf[], uint8_t len) { |
||||
|
if(!mInitialized) |
||||
|
return; |
||||
|
uint8_t rx_data; |
||||
|
|
||||
|
spi_transaction_t t = { |
||||
|
.length = 8, |
||||
|
.rxlength = 8, |
||||
|
.tx_buffer = NULL, |
||||
|
.rx_buffer = &rx_data |
||||
|
}; |
||||
|
|
||||
|
SPI_PARAM_LOCK(); |
||||
|
for(uint8_t i = 0; i < len; i++) { |
||||
|
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_fifo, &t)); |
||||
|
delayMicroseconds(4); // > 4 us
|
||||
|
buf[i] = rx_data; |
||||
|
} |
||||
|
SPI_PARAM_UNLOCK(); |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
spi_device_handle_t spi_reg, spi_fifo; |
||||
|
bool mInitialized; |
||||
|
SemaphoreHandle_t paramLock = NULL; |
||||
|
}; |
||||
|
#else |
||||
|
template<uint8_t CSB_PIN=5, uint8_t FCSB_PIN=4> |
||||
|
class esp32_3wSpi { |
||||
|
public: |
||||
|
esp32_3wSpi() {} |
||||
|
void setup() {} |
||||
|
void loop() {} |
||||
|
}; |
||||
|
#endif |
||||
|
|
||||
|
#endif /*__ESP32_3WSPI_H__*/ |
@ -0,0 +1,190 @@ |
|||||
|
//-----------------------------------------------------------------------------
|
||||
|
// 2023 Ahoy, https://github.com/lumpapu/ahoy
|
||||
|
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
|
||||
|
#ifndef __HMS_DEFINES_H__ |
||||
|
#define __HMS_DEFINES_H__ |
||||
|
|
||||
|
#include "../hm/hmDefines.h" |
||||
|
|
||||
|
//-------------------------------------
|
||||
|
// HMS-350, HMS-500
|
||||
|
//-------------------------------------
|
||||
|
const byteAssign_t hms1chAssignment[] = { |
||||
|
{ FLD_UDC, UNIT_V, CH1, 2, 2, 10 }, |
||||
|
{ FLD_IDC, UNIT_A, CH1, 4, 2, 100 }, |
||||
|
{ FLD_PDC, UNIT_W, CH1, 6, 2, 10 }, |
||||
|
{ FLD_YT, UNIT_KWH, CH1, 8, 4, 1000 }, |
||||
|
{ FLD_YD, UNIT_WH, CH1, 12, 2, 1 }, |
||||
|
{ FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC }, |
||||
|
|
||||
|
{ FLD_UAC, UNIT_V, CH0, 14, 2, 10 }, |
||||
|
{ FLD_F, UNIT_HZ, CH0, 16, 2, 100 }, |
||||
|
{ FLD_PAC, UNIT_W, CH0, 18, 2, 10 }, |
||||
|
{ FLD_Q, UNIT_VAR, CH0, 20, 2, 10 }, // signed!
|
||||
|
{ FLD_IAC, UNIT_A, CH0, 22, 2, 100 }, |
||||
|
{ FLD_PF, UNIT_NONE, CH0, 24, 2, 1000 }, // signed!
|
||||
|
{ FLD_T, UNIT_C, CH0, 26, 2, 10 }, // signed!
|
||||
|
{ FLD_EVT, UNIT_NONE, CH0, 28, 2, 1 }, |
||||
|
|
||||
|
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, |
||||
|
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, |
||||
|
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, |
||||
|
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } |
||||
|
}; |
||||
|
#define HMS1CH_LIST_LEN (sizeof(hms1chAssignment) / sizeof(byteAssign_t)) |
||||
|
#define HMS1CH_PAYLOAD_LEN 30 |
||||
|
|
||||
|
//-------------------------------------
|
||||
|
// HMS-800, HMS-1000
|
||||
|
//-------------------------------------
|
||||
|
const byteAssign_t hms2chAssignment[] = { |
||||
|
{ FLD_UDC, UNIT_V, CH1, 2, 2, 10 }, |
||||
|
{ FLD_IDC, UNIT_A, CH1, 6, 2, 100 }, |
||||
|
{ FLD_PDC, UNIT_W, CH1, 10, 2, 10 }, |
||||
|
{ FLD_YT, UNIT_KWH, CH1, 14, 4, 1000 }, |
||||
|
{ FLD_YD, UNIT_WH, CH1, 22, 2, 1 }, |
||||
|
{ FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC }, |
||||
|
|
||||
|
{ FLD_UDC, UNIT_V, CH2, 4, 2, 10 }, |
||||
|
{ FLD_IDC, UNIT_A, CH2, 8, 2, 100 }, |
||||
|
{ FLD_PDC, UNIT_W, CH2, 12, 2, 10 }, |
||||
|
{ FLD_YT, UNIT_KWH, CH2, 18, 4, 1000 }, |
||||
|
{ FLD_YD, UNIT_WH, CH2, 24, 2, 1 }, |
||||
|
{ FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC }, |
||||
|
|
||||
|
{ FLD_UAC, UNIT_V, CH0, 26, 2, 10 }, |
||||
|
{ FLD_F, UNIT_HZ, CH0, 28, 2, 100 }, |
||||
|
{ FLD_PAC, UNIT_W, CH0, 30, 2, 10 }, |
||||
|
{ FLD_Q, UNIT_VAR, CH0, 32, 2, 10 }, // signed!
|
||||
|
{ FLD_IAC, UNIT_A, CH0, 34, 2, 100 }, |
||||
|
{ FLD_PF, UNIT_NONE, CH0, 36, 2, 1000 }, // signed!
|
||||
|
{ FLD_T, UNIT_C, CH0, 38, 2, 10 }, // signed!
|
||||
|
{ FLD_EVT, UNIT_NONE, CH0, 40, 2, 1 }, |
||||
|
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, |
||||
|
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, |
||||
|
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, |
||||
|
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } |
||||
|
}; |
||||
|
#define HMS2CH_LIST_LEN (sizeof(hms2chAssignment) / sizeof(byteAssign_t)) |
||||
|
#define HMS2CH_PAYLOAD_LEN 42 |
||||
|
|
||||
|
//-------------------------------------
|
||||
|
// HMS-1800, HMS-2000
|
||||
|
//-------------------------------------
|
||||
|
const byteAssign_t hms4chAssignment[] = { |
||||
|
{ FLD_UDC, UNIT_V, CH1, 2, 2, 10 }, |
||||
|
{ FLD_IDC, UNIT_A, CH1, 6, 2, 100 }, |
||||
|
{ FLD_PDC, UNIT_W, CH1, 10, 2, 10 }, |
||||
|
{ FLD_YT, UNIT_KWH, CH1, 14, 4, 1000 }, |
||||
|
{ FLD_YD, UNIT_WH, CH1, 22, 2, 1 }, |
||||
|
{ FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC }, |
||||
|
|
||||
|
{ FLD_UDC, UNIT_V, CH2, 4, 2, 10 }, |
||||
|
{ FLD_IDC, UNIT_A, CH2, 8, 2, 100 }, |
||||
|
{ FLD_PDC, UNIT_W, CH2, 12, 2, 10 }, |
||||
|
{ FLD_YT, UNIT_KWH, CH2, 18, 4, 1000 }, |
||||
|
{ FLD_YD, UNIT_WH, CH2, 24, 2, 1 }, |
||||
|
{ FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC }, |
||||
|
|
||||
|
{ FLD_UDC, UNIT_V, CH3, 26, 2, 10 }, |
||||
|
{ FLD_IDC, UNIT_A, CH3, 30, 2, 100 }, |
||||
|
{ FLD_PDC, UNIT_W, CH3, 34, 2, 10 }, |
||||
|
{ FLD_YT, UNIT_KWH, CH3, 38, 4, 1000 }, |
||||
|
{ FLD_YD, UNIT_WH, CH3, 46, 2, 1 }, |
||||
|
{ FLD_IRR, UNIT_PCT, CH3, CALC_IRR_CH, CH3, CMD_CALC }, |
||||
|
|
||||
|
{ FLD_UDC, UNIT_V, CH4, 28, 2, 10 }, |
||||
|
{ FLD_IDC, UNIT_A, CH4, 32, 2, 100 }, |
||||
|
{ FLD_PDC, UNIT_W, CH4, 36, 2, 10 }, |
||||
|
{ FLD_YT, UNIT_KWH, CH4, 42, 4, 1000 }, |
||||
|
{ FLD_YD, UNIT_WH, CH4, 48, 2, 1 }, |
||||
|
{ FLD_IRR, UNIT_PCT, CH4, CALC_IRR_CH, CH4, CMD_CALC }, |
||||
|
|
||||
|
{ FLD_UAC, UNIT_V, CH0, 50, 2, 10 }, |
||||
|
{ FLD_F, UNIT_HZ, CH0, 52, 2, 100 }, |
||||
|
{ FLD_PAC, UNIT_W, CH0, 54, 2, 10 }, |
||||
|
{ FLD_Q, UNIT_VAR, CH0, 56, 2, 10 }, // signed!
|
||||
|
{ FLD_IAC, UNIT_A, CH0, 58, 2, 100 }, |
||||
|
{ FLD_PF, UNIT_NONE, CH0, 60, 2, 1000 }, // signed!
|
||||
|
{ FLD_T, UNIT_C, CH0, 62, 2, 10 }, // signed!
|
||||
|
{ FLD_EVT, UNIT_NONE, CH0, 64, 2, 1 }, |
||||
|
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, |
||||
|
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, |
||||
|
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, |
||||
|
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } |
||||
|
}; |
||||
|
#define HMS4CH_LIST_LEN (sizeof(hms4chAssignment) / sizeof(byteAssign_t)) |
||||
|
#define HMS4CH_PAYLOAD_LEN 66 |
||||
|
|
||||
|
//-------------------------------------
|
||||
|
// HMT-1800, HMT-2250
|
||||
|
//-------------------------------------
|
||||
|
const byteAssign_t hmt6chAssignment[] = { |
||||
|
{ FLD_UDC, UNIT_V, CH1, 2, 2, 10 }, |
||||
|
{ FLD_IDC, UNIT_A, CH1, 4, 2, 100 }, |
||||
|
{ FLD_PDC, UNIT_W, CH1, 8, 2, 10 }, |
||||
|
{ FLD_YT, UNIT_KWH, CH1, 12, 4, 1000 }, |
||||
|
{ FLD_YD, UNIT_WH, CH1, 20, 2, 1 }, |
||||
|
{ FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC }, |
||||
|
|
||||
|
{ FLD_UDC, UNIT_V, CH2, CALC_UDC_CH, CH1, CMD_CALC }, |
||||
|
{ FLD_IDC, UNIT_A, CH2, 6, 2, 100 }, |
||||
|
{ FLD_PDC, UNIT_W, CH2, 10, 2, 10 }, |
||||
|
{ FLD_YT, UNIT_KWH, CH2, 16, 4, 1000 }, |
||||
|
{ FLD_YD, UNIT_WH, CH2, 22, 2, 1 }, |
||||
|
{ FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC }, |
||||
|
|
||||
|
{ FLD_UDC, UNIT_V, CH3, 24, 2, 10 }, |
||||
|
{ FLD_IDC, UNIT_A, CH3, 26, 2, 100 }, |
||||
|
{ FLD_PDC, UNIT_W, CH3, 30, 2, 10 }, |
||||
|
{ FLD_YT, UNIT_KWH, CH3, 34, 4, 1000 }, |
||||
|
{ FLD_YD, UNIT_WH, CH3, 42, 2, 1 }, |
||||
|
{ FLD_IRR, UNIT_PCT, CH3, CALC_IRR_CH, CH3, CMD_CALC }, |
||||
|
|
||||
|
{ FLD_UDC, UNIT_V, CH4, CALC_UDC_CH, CH3, CMD_CALC }, |
||||
|
{ FLD_IDC, UNIT_A, CH4, 28, 2, 100 }, |
||||
|
{ FLD_PDC, UNIT_W, CH4, 32, 2, 10 }, |
||||
|
{ FLD_YT, UNIT_KWH, CH4, 38, 4, 1000 }, |
||||
|
{ FLD_YD, UNIT_WH, CH4, 44, 2, 1 }, |
||||
|
{ FLD_IRR, UNIT_PCT, CH4, CALC_IRR_CH, CH4, CMD_CALC }, |
||||
|
|
||||
|
{ FLD_UDC, UNIT_V, CH5, 46, 2, 10 }, |
||||
|
{ FLD_IDC, UNIT_A, CH5, 48, 2, 100 }, |
||||
|
{ FLD_PDC, UNIT_W, CH5, 52, 2, 10 }, |
||||
|
{ FLD_YT, UNIT_KWH, CH5, 56, 4, 1000 }, |
||||
|
{ FLD_YD, UNIT_WH, CH5, 64, 2, 1 }, |
||||
|
{ FLD_IRR, UNIT_PCT, CH5, CALC_IRR_CH, CH5, CMD_CALC }, |
||||
|
|
||||
|
{ FLD_UDC, UNIT_V, CH6, CALC_UDC_CH, CH5, CMD_CALC }, |
||||
|
{ FLD_IDC, UNIT_A, CH6, 50, 2, 100 }, |
||||
|
{ FLD_PDC, UNIT_W, CH6, 54, 2, 10 }, |
||||
|
{ FLD_YT, UNIT_KWH, CH6, 60, 4, 1000 }, |
||||
|
{ FLD_YD, UNIT_WH, CH6, 66, 2, 1 }, |
||||
|
{ FLD_IRR, UNIT_PCT, CH6, CALC_IRR_CH, CH6, CMD_CALC }, |
||||
|
|
||||
|
{ FLD_UAC_1N, UNIT_V, CH0, 68, 2, 10 }, |
||||
|
{ FLD_UAC_2N, UNIT_V, CH0, 70, 2, 10 }, |
||||
|
{ FLD_UAC_3N, UNIT_V, CH0, 72, 2, 10 }, |
||||
|
{ FLD_UAC_12, UNIT_V, CH0, 74, 2, 10 }, |
||||
|
{ FLD_UAC_23, UNIT_V, CH0, 76, 2, 10 }, |
||||
|
{ FLD_UAC_31, UNIT_V, CH0, 78, 2, 10 }, |
||||
|
{ FLD_F, UNIT_HZ, CH0, 80, 2, 100 }, |
||||
|
{ FLD_PAC, UNIT_W, CH0, 82, 2, 10 }, |
||||
|
{ FLD_Q, UNIT_VAR, CH0, 84, 2, 10 }, |
||||
|
{ FLD_IAC_1, UNIT_A, CH0, 86, 2, 100 }, |
||||
|
{ FLD_IAC_2, UNIT_A, CH0, 88, 2, 100 }, |
||||
|
{ FLD_IAC_3, UNIT_A, CH0, 90, 2, 100 }, |
||||
|
{ FLD_PF, UNIT_NONE, CH0, 92, 2, 1000 }, |
||||
|
{ FLD_T, UNIT_C, CH0, 94, 2, 10 }, |
||||
|
{ FLD_EVT, UNIT_NONE, CH0, 96, 2, 1 }, |
||||
|
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, |
||||
|
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, |
||||
|
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, |
||||
|
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } |
||||
|
}; |
||||
|
#define HMT6CH_LIST_LEN (sizeof(hmt6chAssignment) / sizeof(byteAssign_t)) |
||||
|
#define HMT6CH_PAYLOAD_LEN 98 |
||||
|
|
||||
|
#endif /*__HMS_DEFINES_H__*/ |
@ -0,0 +1,405 @@ |
|||||
|
//-----------------------------------------------------------------------------
|
||||
|
// 2023 Ahoy, https://ahoydtu.de
|
||||
|
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
|
||||
|
#ifndef __HMS_PAYLOAD_H__ |
||||
|
#define __HMS_PAYLOAD_H__ |
||||
|
|
||||
|
#include "../utils/dbg.h" |
||||
|
#include "../utils/crc.h" |
||||
|
#include "../config/config.h" |
||||
|
#include <Arduino.h> |
||||
|
|
||||
|
#define HMS_TIMEOUT_SEC 30 // 30s * 1000
|
||||
|
|
||||
|
typedef struct { |
||||
|
uint8_t txCmd; |
||||
|
uint8_t txId; |
||||
|
//uint8_t invId;
|
||||
|
uint32_t ts; |
||||
|
uint8_t data[MAX_PAYLOAD_ENTRIES][MAX_RF_PAYLOAD_SIZE]; |
||||
|
uint8_t len[MAX_PAYLOAD_ENTRIES]; |
||||
|
bool complete; |
||||
|
uint8_t maxPackId; |
||||
|
bool lastFound; |
||||
|
uint8_t retransmits; |
||||
|
bool requested; |
||||
|
bool gotFragment; |
||||
|
} hmsPayload_t; |
||||
|
|
||||
|
|
||||
|
typedef std::function<void(uint8_t, Inverter<> *)> payloadListenerType; |
||||
|
typedef std::function<void(uint16_t alarmCode, uint32_t start, uint32_t end)> alarmListenerType; |
||||
|
|
||||
|
|
||||
|
template<class HMSYSTEM, class RADIO> |
||||
|
class HmsPayload { |
||||
|
public: |
||||
|
HmsPayload() {} |
||||
|
|
||||
|
void setup(IApp *app, HMSYSTEM *sys, RADIO *radio, statistics_t *stat, uint8_t maxRetransmits, uint32_t *timestamp) { |
||||
|
mApp = app; |
||||
|
mSys = sys; |
||||
|
mRadio = radio; |
||||
|
mStat = stat; |
||||
|
mMaxRetrans = maxRetransmits; |
||||
|
mTimestamp = timestamp; |
||||
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { |
||||
|
reset(i); |
||||
|
mIvCmd56Cnt[i] = 0; |
||||
|
} |
||||
|
mSerialDebug = false; |
||||
|
//mHighPrioIv = NULL;
|
||||
|
mCbAlarm = NULL; |
||||
|
mCbPayload = NULL; |
||||
|
//mLastRx = 0;
|
||||
|
} |
||||
|
|
||||
|
void enableSerialDebug(bool enable) { |
||||
|
mSerialDebug = enable; |
||||
|
} |
||||
|
|
||||
|
void addPayloadListener(payloadListenerType cb) { |
||||
|
mCbPayload = cb; |
||||
|
} |
||||
|
|
||||
|
void addAlarmListener(alarmListenerType cb) { |
||||
|
mCbAlarm = cb; |
||||
|
} |
||||
|
|
||||
|
void loop() { |
||||
|
/*if(NULL != mHighPrioIv) {
|
||||
|
ivSend(mHighPrioIv, true); |
||||
|
mHighPrioIv = NULL; |
||||
|
}*/ |
||||
|
} |
||||
|
|
||||
|
void ivSendHighPrio(Inverter<> *iv) { |
||||
|
//mHighPrioIv = iv;
|
||||
|
} |
||||
|
|
||||
|
void ivSend(Inverter<> *iv, bool highPrio = false) { |
||||
|
if ((IV_HMS != iv->ivGen) && (IV_HMT != iv->ivGen)) // only process HMS inverters
|
||||
|
return; |
||||
|
|
||||
|
if(!highPrio) { |
||||
|
if (mPayload[iv->id].requested) { |
||||
|
if (!mPayload[iv->id].complete) |
||||
|
process(false); // no retransmit
|
||||
|
|
||||
|
if (!mPayload[iv->id].complete) { |
||||
|
if (MAX_PAYLOAD_ENTRIES == mPayload[iv->id].maxPackId) |
||||
|
mStat->rxFailNoAnser++; // got nothing
|
||||
|
else |
||||
|
mStat->rxFail++; // got fragments but not complete response
|
||||
|
|
||||
|
iv->setQueuedCmdFinished(); // command failed
|
||||
|
if (mSerialDebug) |
||||
|
DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout")); |
||||
|
/*if (mSerialDebug) {
|
||||
|
DPRINT(DBG_INFO, F("(#")); |
||||
|
DBGPRINT(String(iv->id)); |
||||
|
DBGPRINT(F(") no Payload received! (retransmits: ")); |
||||
|
DBGPRINT(String(mPayload[iv->id].retransmits)); |
||||
|
DBGPRINTLN(F(")")); |
||||
|
}*/ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
reset(iv->id); |
||||
|
mPayload[iv->id].requested = true; |
||||
|
|
||||
|
yield(); |
||||
|
if (mSerialDebug) { |
||||
|
DPRINT_IVID(DBG_INFO, iv->id); |
||||
|
DBGPRINT(F("Requesting Inv SN ")); |
||||
|
DBGPRINTLN(String(iv->config->serial.u64, HEX)); |
||||
|
} |
||||
|
|
||||
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); |
||||
|
if (iv->getDevControlRequest()) { |
||||
|
if (mSerialDebug) { |
||||
|
DPRINT_IVID(DBG_INFO, iv->id); |
||||
|
DBGPRINT(F("Devcontrol request 0x")); |
||||
|
DBGPRINT(String(iv->devControlCmd, HEX)); |
||||
|
DBGPRINT(F(" power limit ")); |
||||
|
DBGPRINTLN(String(iv->powerLimit[0])); |
||||
|
} |
||||
|
mRadio->sendControlPacket(&iv->radioId.u64, iv->devControlCmd, iv->powerLimit, false); |
||||
|
mPayload[iv->id].txCmd = iv->devControlCmd; |
||||
|
//iv->clearCmdQueue();
|
||||
|
//iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
|
||||
|
} else if(((rec->ts + HMS_TIMEOUT_SEC) < *mTimestamp) && (mIvCmd56Cnt[iv->id] < 3)) { |
||||
|
mRadio->switchFrequency(&iv->radioId.u64, HOY_BOOT_FREQ_KHZ, WORK_FREQ_KHZ); |
||||
|
mIvCmd56Cnt[iv->id]++; |
||||
|
} else { |
||||
|
if(++mIvCmd56Cnt[iv->id] == 10) |
||||
|
mIvCmd56Cnt[iv->id] = 0; |
||||
|
uint8_t cmd = iv->getQueuedCmd(); |
||||
|
DPRINT_IVID(DBG_INFO, iv->id); |
||||
|
DBGPRINT(F("prepareDevInformCmd 0x")); |
||||
|
DBGHEXLN(cmd); |
||||
|
mRadio->prepareDevInformCmd(&iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false); |
||||
|
mPayload[iv->id].txCmd = cmd; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void add(Inverter<> *iv, hmsPacket_t *p) { |
||||
|
if (p->data[1] == (TX_REQ_INFO + ALL_FRAMES)) { // response from get information command
|
||||
|
mPayload[iv->id].txId = p->data[1]; |
||||
|
DPRINTLN(DBG_DEBUG, F("Response from info request received")); |
||||
|
uint8_t *pid = &p->data[10]; |
||||
|
if (*pid == 0x00) { |
||||
|
DPRINT(DBG_DEBUG, F("fragment number zero received and ignored")); |
||||
|
} else { |
||||
|
DPRINTLN(DBG_DEBUG, "PID: 0x" + String(*pid, HEX)); |
||||
|
if ((*pid & 0x7F) < MAX_PAYLOAD_ENTRIES) { |
||||
|
memcpy(mPayload[iv->id].data[(*pid & 0x7F) - 1], &p->data[11], p->data[0] - 11); |
||||
|
mPayload[iv->id].len[(*pid & 0x7F) - 1] = p->data[0] -11; |
||||
|
mPayload[iv->id].gotFragment = true; |
||||
|
} |
||||
|
|
||||
|
if ((*pid & ALL_FRAMES) == ALL_FRAMES) { |
||||
|
// Last packet
|
||||
|
if (((*pid & 0x7f) > mPayload[iv->id].maxPackId) || (MAX_PAYLOAD_ENTRIES == mPayload[iv->id].maxPackId)) { |
||||
|
mPayload[iv->id].maxPackId = (*pid & 0x7f); |
||||
|
if (*pid > 0x81) |
||||
|
mPayload[iv->id].lastFound = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} else if (p->data[1] == (TX_REQ_DEVCONTROL + ALL_FRAMES)) { // response from dev control command
|
||||
|
DPRINTLN(DBG_DEBUG, F("Response from devcontrol request received")); |
||||
|
|
||||
|
mPayload[iv->id].txId = p->data[1]; |
||||
|
iv->clearDevControlRequest(); |
||||
|
|
||||
|
if ((p->data[13] == ActivePowerContr) && (p->data[14] == 0x00)) { |
||||
|
bool ok = true; |
||||
|
if((p->data[11] == 0x00) && (p->data[12] == 0x00)) |
||||
|
mApp->setMqttPowerLimitAck(iv); |
||||
|
else |
||||
|
ok = false; |
||||
|
DPRINT_IVID(DBG_INFO, iv->id); |
||||
|
DBGPRINT(F(" has ")); |
||||
|
if(!ok) DBGPRINT(F("not ")); |
||||
|
DBGPRINT(F("accepted power limit set point ")); |
||||
|
DBGPRINT(String(iv->powerLimit[0])); |
||||
|
DBGPRINT(F(" with PowerLimitControl ")); |
||||
|
DBGPRINTLN(String(iv->powerLimit[1])); |
||||
|
|
||||
|
iv->clearCmdQueue(); |
||||
|
iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
|
||||
|
} |
||||
|
iv->devControlCmd = Init; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void process(bool retransmit) { |
||||
|
for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { |
||||
|
Inverter<> *iv = mSys->getInverterByPos(id); |
||||
|
if (NULL == iv) |
||||
|
continue; // skip to next inverter
|
||||
|
|
||||
|
if ((IV_HMS != iv->ivGen) && (IV_HMT != iv->ivGen)) // only process HMS inverters
|
||||
|
continue; // skip to next inverter
|
||||
|
|
||||
|
if ((mPayload[iv->id].txId != (TX_REQ_INFO + ALL_FRAMES)) && (0 != mPayload[iv->id].txId)) { |
||||
|
// no processing needed if txId is not 0x95
|
||||
|
mPayload[iv->id].complete = true; |
||||
|
continue; // skip to next inverter
|
||||
|
} |
||||
|
|
||||
|
if (!mPayload[iv->id].complete) { |
||||
|
bool crcPass, pyldComplete; |
||||
|
crcPass = build(iv->id, &pyldComplete); |
||||
|
if (!crcPass && !pyldComplete) { // payload not complete
|
||||
|
if ((mPayload[iv->id].requested) && (retransmit)) { |
||||
|
if (mPayload[iv->id].retransmits < mMaxRetrans) { |
||||
|
mPayload[iv->id].retransmits++; |
||||
|
if (iv->devControlCmd == Restart || iv->devControlCmd == CleanState_LockAndAlarm) { |
||||
|
// This is required to prevent retransmissions without answer.
|
||||
|
DPRINTLN(DBG_INFO, F("Prevent retransmit on Restart / CleanState_LockAndAlarm...")); |
||||
|
mPayload[iv->id].retransmits = mMaxRetrans; |
||||
|
} else if(iv->devControlCmd == ActivePowerContr) { |
||||
|
DPRINTLN(DBG_INFO, F("retransmit power limit")); |
||||
|
mRadio->sendControlPacket(&iv->radioId.u64, iv->devControlCmd, iv->powerLimit, true); |
||||
|
} else { |
||||
|
if(false == mPayload[iv->id].gotFragment) { |
||||
|
|
||||
|
//DPRINTLN(DBG_WARN, F("nothing received: Request Complete Retransmit"));
|
||||
|
//mPayload[iv->id].txCmd = iv->getQueuedCmd();
|
||||
|
//DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") prepareDevInformCmd 0x") + String(mPayload[iv->id].txCmd, HEX));
|
||||
|
//mRadio->prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true);
|
||||
|
|
||||
|
DPRINT_IVID(DBG_INFO, iv->id); |
||||
|
DBGPRINTLN(F("nothing received")); |
||||
|
mPayload[iv->id].retransmits = mMaxRetrans; |
||||
|
} else { |
||||
|
for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId - 1); i++) { |
||||
|
if (mPayload[iv->id].len[i] == 0) { |
||||
|
DPRINT(DBG_WARN, F("Frame ")); |
||||
|
DBGPRINT(String(i + 1)); |
||||
|
DBGPRINTLN(F(" missing: Request Retransmit")); |
||||
|
//mRadio->sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME + i), true);
|
||||
|
break; // only request retransmit one frame per loop
|
||||
|
} |
||||
|
yield(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} /*else if(!crcPass && pyldComplete) { // crc error on complete Payload
|
||||
|
if (mPayload[iv->id].retransmits < mMaxRetrans) { |
||||
|
mPayload[iv->id].retransmits++; |
||||
|
DPRINTLN(DBG_WARN, F("CRC Error: Request Complete Retransmit")); |
||||
|
mPayload[iv->id].txCmd = iv->getQueuedCmd(); |
||||
|
DPRINT(DBG_INFO, F("(#")); |
||||
|
DBGPRINT(String(iv->id)); |
||||
|
DBGPRINT(F(") prepareDevInformCmd 0x")); |
||||
|
DBGPRINTLN(String(mPayload[iv->id].txCmd, HEX)); |
||||
|
mRadio->prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true); |
||||
|
} |
||||
|
}*/ else { // payload complete
|
||||
|
DPRINT(DBG_INFO, F("procPyld: cmd: 0x")); |
||||
|
DBGPRINTLN(String(mPayload[iv->id].txCmd, HEX)); |
||||
|
DPRINT(DBG_INFO, F("procPyld: txid: 0x")); |
||||
|
DBGPRINTLN(String(mPayload[iv->id].txId, HEX)); |
||||
|
DPRINTLN(DBG_DEBUG, F("procPyld: max: ") + String(mPayload[iv->id].maxPackId)); |
||||
|
record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd); // choose the parser
|
||||
|
mPayload[iv->id].complete = true; |
||||
|
|
||||
|
uint8_t payload[100]; |
||||
|
uint8_t payloadLen = 0; |
||||
|
|
||||
|
memset(payload, 0, 100); |
||||
|
|
||||
|
for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId); i++) { |
||||
|
memcpy(&payload[payloadLen], mPayload[iv->id].data[i], (mPayload[iv->id].len[i])); |
||||
|
payloadLen += (mPayload[iv->id].len[i]); |
||||
|
yield(); |
||||
|
} |
||||
|
payloadLen -= 2; |
||||
|
|
||||
|
if (mSerialDebug) { |
||||
|
DPRINT(DBG_INFO, F("Payload (")); |
||||
|
DBGPRINT(String(payloadLen)); |
||||
|
DBGPRINT(F("): ")); |
||||
|
ah::dumpBuf(payload, payloadLen); |
||||
|
} |
||||
|
|
||||
|
if (NULL == rec) { |
||||
|
DPRINTLN(DBG_ERROR, F("record is NULL!")); |
||||
|
} else if ((rec->pyldLen == payloadLen) || (0 == rec->pyldLen)) { |
||||
|
if (mPayload[iv->id].txId == (TX_REQ_INFO + ALL_FRAMES)) |
||||
|
mStat->rxSuccess++; |
||||
|
|
||||
|
rec->ts = mPayload[iv->id].ts; |
||||
|
for (uint8_t i = 0; i < rec->length; i++) { |
||||
|
iv->addValue(i, payload, rec); |
||||
|
yield(); |
||||
|
} |
||||
|
iv->doCalculations(); |
||||
|
notify(mPayload[iv->id].txCmd, iv); |
||||
|
|
||||
|
/*if(AlarmData == mPayload[iv->id].txCmd) {
|
||||
|
uint8_t i = 0; |
||||
|
uint16_t code; |
||||
|
uint32_t start, end; |
||||
|
while(1) { |
||||
|
code = iv->parseAlarmLog(i++, payload, payloadLen, &start, &end); |
||||
|
if(0 == code) |
||||
|
break; |
||||
|
if (NULL != mCbAlarm) |
||||
|
(mCbAlarm)(code, start, end); |
||||
|
yield(); |
||||
|
} |
||||
|
}*/ |
||||
|
} else { |
||||
|
DPRINT(DBG_ERROR, F("plausibility check failed, expected ")); |
||||
|
DBGPRINT(String(rec->pyldLen)); |
||||
|
DBGPRINTLN(F(" bytes")); |
||||
|
mStat->rxFail++; |
||||
|
} |
||||
|
|
||||
|
iv->setQueuedCmdFinished(); |
||||
|
} |
||||
|
} |
||||
|
yield(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
void notify(uint8_t val, Inverter<> *iv) { |
||||
|
if(NULL != mCbPayload) |
||||
|
(mCbPayload)(val, iv); |
||||
|
} |
||||
|
|
||||
|
void notify(uint16_t code, uint32_t start, uint32_t endTime) { |
||||
|
if (NULL != mCbAlarm) |
||||
|
(mCbAlarm)(code, start, endTime); |
||||
|
} |
||||
|
|
||||
|
bool build(uint8_t id, bool *complete) { |
||||
|
DPRINTLN(DBG_VERBOSE, F("build")); |
||||
|
uint16_t crc = 0xffff, crcRcv = 0x0000; |
||||
|
if (mPayload[id].maxPackId > MAX_PAYLOAD_ENTRIES) |
||||
|
mPayload[id].maxPackId = MAX_PAYLOAD_ENTRIES; |
||||
|
|
||||
|
// check if all fragments are there
|
||||
|
*complete = true; |
||||
|
for (uint8_t i = 0; i < mPayload[id].maxPackId; i++) { |
||||
|
if(mPayload[id].len[i] == 0) |
||||
|
*complete = false; |
||||
|
} |
||||
|
if(!*complete) |
||||
|
return false; |
||||
|
|
||||
|
for (uint8_t i = 0; i < mPayload[id].maxPackId; i++) { |
||||
|
if (mPayload[id].len[i] > 0) { |
||||
|
if (i == (mPayload[id].maxPackId - 1)) { |
||||
|
crc = ah::crc16(mPayload[id].data[i], mPayload[id].len[i] - 1, crc); |
||||
|
crcRcv = (mPayload[id].data[i][mPayload[id].len[i] - 2] << 8) | (mPayload[id].data[i][mPayload[id].len[i] - 1]); |
||||
|
} else |
||||
|
crc = ah::crc16(mPayload[id].data[i], mPayload[id].len[i], crc); |
||||
|
} |
||||
|
yield(); |
||||
|
} |
||||
|
|
||||
|
return (crc == crcRcv) ? true : false; |
||||
|
} |
||||
|
|
||||
|
void reset(uint8_t id) { |
||||
|
DPRINT(DBG_INFO, "resetPayload: id: "); |
||||
|
DBGPRINTLN(String(id)); |
||||
|
memset(&mPayload[id], 0, sizeof(hmsPayload_t)); |
||||
|
//mPayload[id].txCmd = 0;
|
||||
|
mPayload[id].gotFragment = false; |
||||
|
//mPayload[id].retransmits = 0;
|
||||
|
mPayload[id].maxPackId = MAX_PAYLOAD_ENTRIES; |
||||
|
mPayload[id].lastFound = false; |
||||
|
mPayload[id].complete = false; |
||||
|
mPayload[id].requested = false; |
||||
|
mPayload[id].ts = *mTimestamp; |
||||
|
} |
||||
|
|
||||
|
IApp *mApp; |
||||
|
HMSYSTEM *mSys; |
||||
|
RADIO *mRadio; |
||||
|
statistics_t *mStat; |
||||
|
uint8_t mMaxRetrans; |
||||
|
uint32_t *mTimestamp; |
||||
|
//uint32_t mLastRx;
|
||||
|
hmsPayload_t mPayload[MAX_NUM_INVERTERS]; |
||||
|
uint8_t mIvCmd56Cnt[MAX_NUM_INVERTERS]; |
||||
|
bool mSerialDebug; |
||||
|
Inverter<> *mHighPrioIv; |
||||
|
|
||||
|
alarmListenerType mCbAlarm; |
||||
|
payloadListenerType mCbPayload; |
||||
|
}; |
||||
|
|
||||
|
#endif /*__HMS_PAYLOAD_H__*/ |
@ -0,0 +1,213 @@ |
|||||
|
//-----------------------------------------------------------------------------
|
||||
|
// 2023 Ahoy, https://github.com/lumpapu/ahoy
|
||||
|
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
|
||||
|
#ifndef __HMS_RADIO_H__ |
||||
|
#define __HMS_RADIO_H__ |
||||
|
|
||||
|
#include "../utils/dbg.h" |
||||
|
#include "cmt2300a.h" |
||||
|
|
||||
|
typedef struct { |
||||
|
int8_t rssi; |
||||
|
uint8_t data[28]; |
||||
|
} hmsPacket_t; |
||||
|
|
||||
|
#define U32_B3(val) ((uint8_t)((val >> 24) & 0xff)) |
||||
|
#define U32_B2(val) ((uint8_t)((val >> 16) & 0xff)) |
||||
|
#define U32_B1(val) ((uint8_t)((val >> 8) & 0xff)) |
||||
|
#define U32_B0(val) ((uint8_t)((val ) & 0xff)) |
||||
|
|
||||
|
template<class SPI, uint32_t DTU_SN = 0x81001765> |
||||
|
class CmtRadio { |
||||
|
typedef SPI SpiType; |
||||
|
typedef Cmt2300a<SpiType> CmtType; |
||||
|
public: |
||||
|
CmtRadio() { |
||||
|
mDtuSn = DTU_SN; |
||||
|
} |
||||
|
|
||||
|
void setup(uint8_t pinCsb, uint8_t pinFcsb, bool genDtuSn = true) { |
||||
|
mCmt.setup(pinCsb, pinFcsb); |
||||
|
reset(genDtuSn); |
||||
|
} |
||||
|
|
||||
|
void setup(bool genDtuSn = true) { |
||||
|
mCmt.setup(); |
||||
|
reset(genDtuSn); |
||||
|
} |
||||
|
|
||||
|
bool loop() { |
||||
|
mCmt.loop(); |
||||
|
|
||||
|
if((!mIrqRcvd) && (!mRqstGetRx)) |
||||
|
return false; |
||||
|
getRx(); |
||||
|
if(CMT_SUCCESS == mCmt.goRx()) { |
||||
|
mIrqRcvd = false; |
||||
|
mRqstGetRx = false; |
||||
|
return true; |
||||
|
} else |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
void tickSecond() { |
||||
|
} |
||||
|
|
||||
|
void handleIntr(void) { |
||||
|
mIrqRcvd = true; |
||||
|
} |
||||
|
|
||||
|
void enableDebug() { |
||||
|
mSerialDebug = true; |
||||
|
} |
||||
|
|
||||
|
void sendControlPacket(const uint64_t *ivId, uint8_t cmd, uint16_t *data, bool isRetransmit) { |
||||
|
DPRINT(DBG_INFO, F("sendControlPacket cmd: 0x")); |
||||
|
DBGHEXLN(cmd); |
||||
|
initPacket(ivId, TX_REQ_DEVCONTROL, SINGLE_FRAME); |
||||
|
uint8_t cnt = 10; |
||||
|
|
||||
|
mTxBuf[cnt++] = cmd; // cmd -> 0 on, 1 off, 2 restart, 11 active power, 12 reactive power, 13 power factor
|
||||
|
mTxBuf[cnt++] = 0x00; |
||||
|
if(cmd >= ActivePowerContr && cmd <= PFSet) { // ActivePowerContr, ReactivePowerContr, PFSet
|
||||
|
mTxBuf[cnt++] = ((data[0] * 10) >> 8) & 0xff; // power limit
|
||||
|
mTxBuf[cnt++] = ((data[0] * 10) ) & 0xff; // power limit
|
||||
|
mTxBuf[cnt++] = ((data[1] ) >> 8) & 0xff; // setting for persistens handlings
|
||||
|
mTxBuf[cnt++] = ((data[1] ) ) & 0xff; // setting for persistens handling
|
||||
|
} |
||||
|
|
||||
|
sendPacket(cnt, isRetransmit); |
||||
|
} |
||||
|
|
||||
|
bool switchFrequency(const uint64_t *ivId, uint32_t fromkHz, uint32_t tokHz) { |
||||
|
uint8_t fromCh = mCmt.freq2Chan(fromkHz); |
||||
|
uint8_t toCh = mCmt.freq2Chan(tokHz); |
||||
|
|
||||
|
if((0xff == fromCh) || (0xff == toCh)) |
||||
|
return false; |
||||
|
|
||||
|
mCmt.switchChannel(fromCh); |
||||
|
sendSwitchChCmd(ivId, toCh); |
||||
|
mCmt.switchChannel(toCh); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
void prepareDevInformCmd(const uint64_t *ivId, uint8_t cmd, uint32_t ts, uint16_t alarmMesId, bool isRetransmit, uint8_t reqfld=TX_REQ_INFO) { // might not be necessary to add additional arg.
|
||||
|
initPacket(ivId, reqfld, ALL_FRAMES); |
||||
|
mTxBuf[10] = cmd; |
||||
|
CP_U32_LittleEndian(&mTxBuf[12], ts); |
||||
|
/*if (cmd == RealTimeRunData_Debug || cmd == AlarmData ) {
|
||||
|
mTxBuf[18] = (alarmMesId >> 8) & 0xff; |
||||
|
mTxBuf[19] = (alarmMesId ) & 0xff; |
||||
|
}*/ |
||||
|
sendPacket(24, isRetransmit); |
||||
|
} |
||||
|
|
||||
|
void sendPacket(uint8_t len, bool isRetransmit) { |
||||
|
if (len > 14) { |
||||
|
uint16_t crc = ah::crc16(&mTxBuf[10], len - 10); |
||||
|
mTxBuf[len++] = (crc >> 8) & 0xff; |
||||
|
mTxBuf[len++] = (crc ) & 0xff; |
||||
|
} |
||||
|
mTxBuf[len] = ah::crc8(mTxBuf, len); |
||||
|
len++; |
||||
|
|
||||
|
if(mSerialDebug) { |
||||
|
DPRINT(DBG_INFO, F("TX ")); |
||||
|
DBGPRINT(String(mCmt.getFreqKhz()/1000.0f)); |
||||
|
DBGPRINT(F("Mhz | ")); |
||||
|
ah::dumpBuf(mTxBuf, len); |
||||
|
} |
||||
|
|
||||
|
uint8_t status = mCmt.tx(mTxBuf, len); |
||||
|
if(CMT_SUCCESS != status) { |
||||
|
DPRINT(DBG_WARN, F("CMT TX failed, code: ")); |
||||
|
DBGPRINTLN(String(status)); |
||||
|
if(CMT_ERR_RX_IN_FIFO == status) |
||||
|
mIrqRcvd = true; |
||||
|
} |
||||
|
|
||||
|
if(isRetransmit) |
||||
|
mRetransmits++; |
||||
|
else |
||||
|
mSendCnt++; |
||||
|
} |
||||
|
|
||||
|
uint32_t mSendCnt; |
||||
|
uint32_t mRetransmits; |
||||
|
std::queue<hmsPacket_t> mBufCtrl; |
||||
|
|
||||
|
private: |
||||
|
inline void reset(bool genDtuSn) { |
||||
|
if(genDtuSn) |
||||
|
generateDtuSn(); |
||||
|
if(!mCmt.reset()) |
||||
|
DPRINTLN(DBG_WARN, F("Initializing CMT2300A failed!")); |
||||
|
else |
||||
|
mCmt.goRx(); |
||||
|
|
||||
|
mSendCnt = 0; |
||||
|
mRetransmits = 0; |
||||
|
mSerialDebug = false; |
||||
|
mIrqRcvd = false; |
||||
|
mRqstGetRx = false; |
||||
|
} |
||||
|
|
||||
|
inline void sendSwitchChCmd(const uint64_t *ivId, uint8_t ch) { |
||||
|
/** ch:
|
||||
|
* 0x00: 860.00 MHz |
||||
|
* 0x01: 860.25 MHz |
||||
|
* 0x02: 860.50 MHz |
||||
|
* ... |
||||
|
* 0x14: 865.00 MHz |
||||
|
* ... |
||||
|
* 0x28: 870.00 MHz |
||||
|
* */ |
||||
|
initPacket(ivId, 0x56, 0x02); |
||||
|
mTxBuf[10] = 0x15; |
||||
|
mTxBuf[11] = 0x21; |
||||
|
mTxBuf[12] = ch; |
||||
|
mTxBuf[13] = 0x14; |
||||
|
sendPacket(14, false); |
||||
|
mRqstGetRx = true; |
||||
|
} |
||||
|
|
||||
|
void initPacket(const uint64_t *ivId, uint8_t mid, uint8_t pid) { |
||||
|
mTxBuf[0] = mid; |
||||
|
CP_U32_BigEndian(&mTxBuf[1], (*ivId) >> 8); |
||||
|
CP_U32_LittleEndian(&mTxBuf[5], mDtuSn); |
||||
|
mTxBuf[9] = pid; |
||||
|
memset(&mTxBuf[10], 0x00, 17); |
||||
|
} |
||||
|
|
||||
|
inline void generateDtuSn(void) { |
||||
|
uint32_t chipID = 0; |
||||
|
#ifdef ESP32 |
||||
|
uint64_t MAC = ESP.getEfuseMac(); |
||||
|
chipID = ((MAC >> 8) & 0xFF0000) | ((MAC >> 24) & 0xFF00) | ((MAC >> 40) & 0xFF); |
||||
|
#endif |
||||
|
mDtuSn = 0x80000000; // the first digit is an 8 for DTU production year 2022, the rest is filled with the ESP chipID in decimal
|
||||
|
for(int i = 0; i < 7; i++) { |
||||
|
mDtuSn |= (chipID % 10) << (i * 4); |
||||
|
chipID /= 10; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
inline void getRx(void) { |
||||
|
hmsPacket_t p; |
||||
|
uint8_t status = mCmt.getRx(p.data, 28, &p.rssi); |
||||
|
if(CMT_SUCCESS == status) |
||||
|
mBufCtrl.push(p); |
||||
|
} |
||||
|
|
||||
|
CmtType mCmt; |
||||
|
uint32_t mDtuSn; |
||||
|
uint8_t mTxBuf[27]; |
||||
|
bool mSerialDebug; |
||||
|
bool mIrqRcvd; |
||||
|
bool mRqstGetRx; |
||||
|
}; |
||||
|
|
||||
|
#endif /*__HMS_RADIO_H__*/ |
@ -0,0 +1,134 @@ |
|||||
|
//-----------------------------------------------------------------------------
|
||||
|
// 2023 Ahoy, https://ahoydtu.de
|
||||
|
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
|
||||
|
#pragma once |
||||
|
#include "Display_Mono.h" |
||||
|
|
||||
|
class DisplayMono64X48 : public DisplayMono { |
||||
|
public: |
||||
|
DisplayMono64X48() : DisplayMono() { |
||||
|
mEnPowerSafe = true; |
||||
|
mEnScreenSaver = false; |
||||
|
mLuminance = 20; |
||||
|
mExtra = 0; |
||||
|
mDispY = 0; |
||||
|
mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds)
|
||||
|
mUtcTs = NULL; |
||||
|
mType = 0; |
||||
|
} |
||||
|
|
||||
|
void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char *version) { |
||||
|
|
||||
|
u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0); |
||||
|
mType = type; |
||||
|
|
||||
|
// Wemos OLed Shield is not defined in u8 lib -> use nearest compatible
|
||||
|
mDisplay = new U8G2_SSD1306_64X48_ER_F_HW_I2C(rot, reset, clock, data); |
||||
|
|
||||
|
mUtcTs = utcTs; |
||||
|
|
||||
|
mDisplay->begin(); |
||||
|
calcLinePositions(); |
||||
|
|
||||
|
mDisplay->clearBuffer(); |
||||
|
mDisplay->setContrast(mLuminance); |
||||
|
|
||||
|
printText("AHOY!", 0); |
||||
|
printText("ahoydtu.de", 1); |
||||
|
printText(version, 2); |
||||
|
mDisplay->sendBuffer(); |
||||
|
} |
||||
|
|
||||
|
void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) { |
||||
|
mEnPowerSafe = enPowerSafe; |
||||
|
mEnScreenSaver = enScreenSaver; |
||||
|
mLuminance = lum; |
||||
|
} |
||||
|
|
||||
|
void loop(void) { |
||||
|
if (mEnPowerSafe) { |
||||
|
if (mTimeout != 0) |
||||
|
mTimeout--; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) { |
||||
|
mDisplay->clearBuffer(); |
||||
|
|
||||
|
// set Contrast of the Display to raise the lifetime
|
||||
|
mDisplay->setContrast(mLuminance); |
||||
|
|
||||
|
if ((totalPower > 0) && (isprod > 0)) { |
||||
|
mTimeout = DISP_DEFAULT_TIMEOUT; |
||||
|
mDisplay->setPowerSave(false); |
||||
|
|
||||
|
if (totalPower > 999) |
||||
|
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (totalPower / 1000)); |
||||
|
else |
||||
|
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%3.0f W", totalPower); |
||||
|
|
||||
|
printText(mFmtText, 0); |
||||
|
} else { |
||||
|
printText("offline", 0); |
||||
|
// check if it's time to enter power saving mode
|
||||
|
if (mTimeout == 0) |
||||
|
mDisplay->setPowerSave(mEnPowerSafe); |
||||
|
} |
||||
|
|
||||
|
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "D: %4.0f Wh", totalYieldDay); |
||||
|
printText(mFmtText, 1); |
||||
|
|
||||
|
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "T: %4.0f kWh", totalYieldTotal); |
||||
|
printText(mFmtText, 2); |
||||
|
|
||||
|
IPAddress ip = WiFi.localIP(); |
||||
|
if (!(mExtra % 10) && (ip)) |
||||
|
printText(ip.toString().c_str(), 3); |
||||
|
else if (!(mExtra % 5)) { |
||||
|
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "active Inv: %d", isprod); |
||||
|
printText(mFmtText, 3); |
||||
|
} else if (NULL != mUtcTs) |
||||
|
printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); |
||||
|
|
||||
|
mDisplay->sendBuffer(); |
||||
|
|
||||
|
mExtra++; |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
void calcLinePositions() { |
||||
|
uint8_t yOff = 0; |
||||
|
for (uint8_t i = 0; i < 4; i++) { |
||||
|
setFont(i); |
||||
|
yOff += (mDisplay->getMaxCharHeight()); |
||||
|
mLineYOffsets[i] = yOff; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
inline void setFont(uint8_t line) { |
||||
|
switch (line) { |
||||
|
case 0: |
||||
|
mDisplay->setFont(u8g2_font_fur11_tf); |
||||
|
break; |
||||
|
case 1: |
||||
|
case 2: |
||||
|
mDisplay->setFont(u8g2_font_6x10_tf); |
||||
|
break; |
||||
|
case 3: |
||||
|
mDisplay->setFont(u8g2_font_4x6_tr); |
||||
|
break; |
||||
|
case 4: |
||||
|
mDisplay->setFont(u8g2_font_4x6_tr); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void printText(const char *text, uint8_t line) { |
||||
|
uint8_t dispX = 0; //small display, use all we have
|
||||
|
dispX += (mEnScreenSaver) ? (mExtra % 4) : 0; |
||||
|
setFont(line); |
||||
|
mDisplay->drawStr(dispX, mLineYOffsets[line], text); |
||||
|
} |
||||
|
}; |
@ -1,3 +1,4 @@ |
|||||
#include "dbg.h" |
#include "dbg.h" |
||||
|
|
||||
DBG_CB mCb = NULL; |
DBG_CB mCb = NULL; |
||||
|
bool mDebugEn = true; |
||||
|
@ -0,0 +1,221 @@ |
|||||
|
//-----------------------------------------------------------------------------
|
||||
|
// 2023 Ahoy, https://github.com/lumpapu/ahoy
|
||||
|
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
|
||||
|
#ifndef __IMPROV_H__ |
||||
|
#define __IMPROV_H__ |
||||
|
|
||||
|
#include <cstring> |
||||
|
#include <functional> |
||||
|
#include "dbg.h" |
||||
|
#include "AsyncJson.h" |
||||
|
|
||||
|
// https://www.improv-wifi.com/serial/
|
||||
|
// https://github.com/jnthas/improv-wifi-demo/blob/main/src/esp32-wifiimprov/esp32-wifiimprov.ino
|
||||
|
|
||||
|
// configure ESP through Serial interface
|
||||
|
#if !defined(ETHERNET) |
||||
|
class Improv { |
||||
|
public: |
||||
|
void setup(IApp *app, const char *devName, const char *version) { |
||||
|
mApp = app; |
||||
|
mDevName = devName; |
||||
|
mVersion = version; |
||||
|
|
||||
|
mScanRunning = false; |
||||
|
} |
||||
|
|
||||
|
void tickSerial(void) { |
||||
|
if(mScanRunning) |
||||
|
getNetworks(); |
||||
|
|
||||
|
if(Serial.available() == 0) |
||||
|
return; |
||||
|
|
||||
|
uint8_t buf[40]; |
||||
|
uint8_t len = Serial.readBytes(buf, 40); |
||||
|
|
||||
|
if(!checkPaket(&buf[0], len, [this](uint8_t type, uint8_t buf[], uint8_t len) { |
||||
|
parsePayload(type, buf, len); |
||||
|
})) { |
||||
|
DBGPRINTLN(F("check paket failed")); |
||||
|
} |
||||
|
dumpBuf(buf, len); |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
enum State : uint8_t { |
||||
|
STATE_STOPPED = 0x00, |
||||
|
STATE_AWAITING_AUTHORIZATION = 0x01, |
||||
|
STATE_AUTHORIZED = 0x02, |
||||
|
STATE_PROVISIONING = 0x03, |
||||
|
STATE_PROVISIONED = 0x04, |
||||
|
}; |
||||
|
|
||||
|
enum Command : uint8_t { |
||||
|
UNKNOWN = 0x00, |
||||
|
WIFI_SETTINGS = 0x01, |
||||
|
IDENTIFY = 0x02, |
||||
|
GET_CURRENT_STATE = 0x02, |
||||
|
GET_DEVICE_INFO = 0x03, |
||||
|
GET_WIFI_NETWORKS = 0x04, |
||||
|
BAD_CHECKSUM = 0xFF, |
||||
|
}; |
||||
|
|
||||
|
enum ImprovSerialType : uint8_t { |
||||
|
TYPE_CURRENT_STATE = 0x01, |
||||
|
TYPE_ERROR_STATE = 0x02, |
||||
|
TYPE_RPC = 0x03, |
||||
|
TYPE_RPC_RESPONSE = 0x04 |
||||
|
}; |
||||
|
|
||||
|
void dumpBuf(uint8_t buf[], uint8_t len) { |
||||
|
for(uint8_t i = 0; i < len; i++) { |
||||
|
DHEX(buf[i], false); |
||||
|
DBGPRINT(" ", false); |
||||
|
} |
||||
|
DBGPRINTLN("", false); |
||||
|
} |
||||
|
|
||||
|
inline uint8_t buildChecksum(uint8_t buf[], uint8_t len) { |
||||
|
uint8_t calc = 0; |
||||
|
for(uint8_t i = 0; i < len; i++) { |
||||
|
calc += buf[i]; |
||||
|
} |
||||
|
return calc; |
||||
|
} |
||||
|
|
||||
|
inline bool checkChecksum(uint8_t buf[], uint8_t len) { |
||||
|
/*DHEX(buf[len], false);
|
||||
|
DBGPRINT(F(" == "), false); |
||||
|
DBGHEXLN(buildChecksum(buf, len), false);*/ |
||||
|
return ((buildChecksum(buf, len)) == buf[len]); |
||||
|
} |
||||
|
|
||||
|
bool checkPaket(uint8_t buf[], uint8_t len, std::function<void(uint8_t type, uint8_t b[], uint8_t l)> cb) { |
||||
|
if(len < 11) |
||||
|
return false; |
||||
|
|
||||
|
if(0 != strncmp((char*)buf, "IMPROV", 6)) |
||||
|
return false; |
||||
|
|
||||
|
// verison check (only version 1 is supported!)
|
||||
|
if(0x01 != buf[6]) |
||||
|
return false; |
||||
|
|
||||
|
if(!checkChecksum(buf, (9 + buf[8]))) |
||||
|
return false; |
||||
|
|
||||
|
cb(buf[7], &buf[9], buf[8]); |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
uint8_t char2Improv(const char *str, uint8_t buf[]) { |
||||
|
uint8_t len = strlen(str); |
||||
|
buf[0] = len; |
||||
|
for(uint8_t i = 1; i <= len; i++) { |
||||
|
buf[i] = (uint8_t)str[i-1]; |
||||
|
} |
||||
|
return len + 1; |
||||
|
} |
||||
|
|
||||
|
void sendDevInfo(void) { |
||||
|
uint8_t buf[50]; |
||||
|
buf[7] = TYPE_RPC_RESPONSE; |
||||
|
buf[9] = GET_DEVICE_INFO; // repsonse to cmd
|
||||
|
uint8_t p = 11; |
||||
|
// firmware name
|
||||
|
p += char2Improv("AhoyDTU", &buf[p]); |
||||
|
// firmware version
|
||||
|
p += char2Improv(mVersion, &buf[p]); |
||||
|
// chip variant
|
||||
|
#if defined(ESP32) |
||||
|
p += char2Improv("ESP32", &buf[p]); |
||||
|
#else |
||||
|
p += char2Improv("ESP8266", &buf[p]); |
||||
|
#endif |
||||
|
// device name
|
||||
|
p += char2Improv(mDevName, &buf[p]); |
||||
|
|
||||
|
buf[10] = p - 11; // sub length
|
||||
|
buf[8] = p - 9; // paket length
|
||||
|
|
||||
|
sendPaket(buf, p); |
||||
|
} |
||||
|
|
||||
|
void getNetworks(void) { |
||||
|
if(!mScanRunning) |
||||
|
mApp->scanAvailNetworks(); |
||||
|
|
||||
|
JsonObject obj; |
||||
|
if(!mApp->getAvailNetworks(obj)) |
||||
|
return; |
||||
|
|
||||
|
mScanRunning = false; |
||||
|
|
||||
|
uint8_t buf[50]; |
||||
|
buf[7] = TYPE_RPC_RESPONSE; |
||||
|
buf[9] = GET_WIFI_NETWORKS; // repsonse to cmd
|
||||
|
uint8_t p = 11; |
||||
|
|
||||
|
JsonArray arr = obj[F("networks")]; |
||||
|
for(uint8_t i = 0; i < arr.size(); i++) { |
||||
|
buf[p++] = strlen(arr[i][F("ssid")]); |
||||
|
// ssid
|
||||
|
p += char2Improv(arr[i][F("ssid")], &buf[p]); |
||||
|
buf[p++] = String(arr[i][F("rssi")]).length(); |
||||
|
// rssi
|
||||
|
p += char2Improv(String(arr[i][F("rssi")]).c_str(), &buf[p]); |
||||
|
|
||||
|
buf[10] = p - 11; // sub length
|
||||
|
buf[8] = p - 9; // paket length
|
||||
|
|
||||
|
sendPaket(buf, p); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void setState(uint8_t state) { |
||||
|
uint8_t buf[20]; |
||||
|
buf[7] = TYPE_CURRENT_STATE; |
||||
|
buf[8] = 0x01; |
||||
|
buf[9] = state; |
||||
|
sendPaket(buf, 10); |
||||
|
} |
||||
|
|
||||
|
void sendPaket(uint8_t buf[], uint8_t len) { |
||||
|
buf[0] = 'I'; |
||||
|
buf[1] = 'M'; |
||||
|
buf[2] = 'P'; |
||||
|
buf[3] = 'R'; |
||||
|
buf[4] = 'O'; |
||||
|
buf[5] = 'V'; |
||||
|
buf[6] = 1; // protocol version
|
||||
|
|
||||
|
buf[len] = buildChecksum(buf, len); |
||||
|
len++; |
||||
|
Serial.write(buf, len); |
||||
|
dumpBuf(buf, len); |
||||
|
} |
||||
|
|
||||
|
void parsePayload(uint8_t type, uint8_t buf[], uint8_t len) { |
||||
|
if(TYPE_RPC == type) { |
||||
|
if(GET_CURRENT_STATE == buf[0]) { |
||||
|
setDebugEn(false); |
||||
|
setState(STATE_AUTHORIZED); |
||||
|
} |
||||
|
else if(GET_DEVICE_INFO == buf[0]) |
||||
|
sendDevInfo(); |
||||
|
else if(GET_WIFI_NETWORKS == buf[0]) |
||||
|
getNetworks(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
IApp *mApp; |
||||
|
const char *mDevName, *mVersion; |
||||
|
bool mScanRunning; |
||||
|
}; |
||||
|
#endif |
||||
|
|
||||
|
#endif /*__IMPROV_H__*/ |
Loading…
Reference in new issue