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" |
|||
|
|||
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