Merge remote-tracking branch 'tbnobody/OpenDTU/master' into development

This commit is contained in:
helgeerbe 2023-04-17 11:08:55 +02:00
commit 40cee1f9ca
86 changed files with 4404 additions and 752 deletions

View File

@ -2,6 +2,9 @@
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"DavidAnson.vscode-markdownlint",
"Vue.volar",
"Vue.vscode-typescript-vue-plugin",
"platformio.platformio-ide"
],
"unwantedRecommendations": [

View File

@ -193,28 +193,41 @@ Topics for 3 phases of a power meter is configurable. Given is an example for th
## Currently supported Inverters
* Hoymiles HM-300
* Hoymiles HM-350
* Hoymiles HM-400
* Hoymiles HM-600
* Hoymiles HM-700
* Hoymiles HM-800
* Hoymiles HM-1000
* Hoymiles HM-1200
* Hoymiles HM-1500
* Solenso SOL-H350
* Solenso SOL-H400
* Solenso SOL-H800
* TSUN TSOL-M350 (Maybe depending on firmware/serial number on the inverter)
* TSUN TSOL-M800 (Maybe depending on firmware/serial number on the inverter)
* TSUN TSOL-M1600 (Maybe depending on firmware/serial number on the inverter)
| Model | Required RF Module | DC Inputs | MPP-Tracker | AC Phases |
| --------------------| ------------------ | --------- | ----------- | --------- |
| Hoymiles HM-300 | NRF24L01+ | 1 | 1 | 1 |
| Hoymiles HM-350 | NRF24L01+ | 1 | 1 | 1 |
| Hoymiles HM-400 | NRF24L01+ | 1 | 1 | 1 |
| Hoymiles HM-600 | NRF24L01+ | 2 | 2 | 1 |
| Hoymiles HM-700 | NRF24L01+ | 2 | 2 | 1 |
| Hoymiles HM-800 | NRF24L01+ | 2 | 2 | 1 |
| Hoymiles HM-1000 | NRF24L01+ | 4 | 2 | 1 |
| Hoymiles HM-1200 | NRF24L01+ | 4 | 2 | 1 |
| Hoymiles HM-1500 | NRF24L01+ | 4 | 2 | 1 |
| Hoymiles HMS-300 | CMT2300A | 1 | 1 | 1 |
| Hoymiles HMS-350 | CMT2300A | 1 | 1 | 1 |
| Hoymiles HMS-400 | CMT2300A | 1 | 1 | 1 |
| Hoymiles HMS-450 | CMT2300A | 1 | 1 | 1 |
| Hoymiles HMS-500 | CMT2300A | 1 | 1 | 1 |
| Hoymiles HMS-600 | CMT2300A | 2 | 2 | 1 |
| Hoymiles HMS-700 | CMT2300A | 2 | 2 | 1 |
| Hoymiles HMS-800 | CMT2300A | 2 | 2 | 1 |
| Hoymiles HMS-900 | CMT2300A | 2 | 2 | 1 |
| Hoymiles HMS-1000 | CMT2300A | 2 | 2 | 1 |
| Hoymiles HMS-1600 | CMT2300A | 4 | 4 | 1 |
| Hoymiles HMS-1800 | CMT2300A | 4 | 4 | 1 |
| Hoymiles HMS-2000 | CMT2300A | 4 | 4 | 1 |
| Hoymiles HMT-1800 | CMT2300A | 6 | 3 | 3 |
| Hoymiles HMT-2250 | CMT2300A | 6 | 3 | 3 |
| Solenso SOL-H350 | NRF24L01+ | 1 | 1 | 1 |
| Solenso SOL-H400 | NRF24L01+ | 1 | 1 | 1 |
| Solenso SOL-H800 | NRF24L01+ | 2 | 2 | 1 |
| TSUN TSOL-M350 | NRF24L01+ | 1 | 1 | 1 |
| TSUN TSOL-M800 | NRF24L01+ | 2 | 2 | 1 |
| TSUN TSOL-M1600 | NRF24L01+ | 4 | 2 | 1 |
**TSUN compatibility remark:**
Compatibility with OpenDTU seems to be related to serial numbers. Current findings indicate that TSUN inverters with a serial number starting with "11" are supported, whereby inverters with a serial number starting with "10" are not.
Firmware version seems to play not a significant role and cannot be read from the stickers. For completeness, the following firmware version have been reported to work with OpenDTU:
* v1.0.8, v1.0.10 TSOL-M800 (DE)
* v1.0.12 TSOL-M1600
Compatibility with OpenDTU is most likly related to the serial number of the inverter. Current findings indicate that TSUN inverters with a serial number starting with "11" are supported, whereby inverters with a serial number starting with "10" are not.
## Features for end users
@ -236,6 +249,8 @@ Firmware version seems to play not a significant role and cannot be read from th
* Prometheus API endpoint (/api/prometheus/metrics)
* English, german and french web interface
* Displays (SSD1306, SH1106, PCD8544)
* Status LEDs
* Konfiguration management (export / import configurations)
* Dark Theme
## Features for developers
@ -273,7 +288,7 @@ Sample Picture:
Also supported: Board with Ethernet-Connector and Power-over-Ethernet [Olimex ESP32-POE](https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/open-source-hardware)
### NRF24L01+ radio board
### NRF24L01+ radio board (See inverter table above for supported inverters)
The PLUS sign is IMPORTANT! There are different variants available, with antenna on the printed circuit board or external antenna.
@ -292,11 +307,19 @@ A heavily incomplete list of trusted hardware shops in germany is:
This list is for your convenience only, the project is not related to any of these shops.
### CMT2300A radio board (See inverter table above for supported inverters)
It is important to get a module which supports SPI communicatiton. The following modules are currently supported:
* EBYTE E49-900M20S
The CMT2300A uses 3-Wire half duplex SPI communication. Due to this fact it currently requires a separate SPI bus. If you want to run the CMT2300A module on the same ESP32 as a NRF24L01+ module or a PCD8544 display make sure you get a ESP which supports 2 SPI busses. Currently the SPI bus host is hardcoded to number 2. This may change in future.
### Power supply
Use a power suppy with 5 V and 1 A. The USB cable connected to your PC/Notebook may be powerful enough or may be not.
## Wiring up
## Wiring up the NRF24L01+ module
### Schematic

View File

@ -91,6 +91,12 @@ The json file can contain multiple profiles. Each profile requires a name and di
| nrf24.irq | number | Interrupt Pin |
| nrf24.en | number | Enable Pin |
| nrf24.cs | number | Chip Select Pin |
| cmt.sdio | number | SDIO Pin |
| cmt.clk | number | CLK Pin |
| cmt.cs | number | CS Pin |
| cmt.fcs | number | FCS Pin |
| cmt.gpio2 | number | GPIO2 Pin (optional) |
| cmt.gpio3 | number | GPIO3 Pin (optional) |
| eth.enabled | boolean | Enable/Disable the ethernet stack |
| eth.phy_addr | number | Unique PHY addr |
| eth.power | number | Power Pin (if available). Use -1 for not assigned pins. |

View File

@ -1,6 +1,6 @@
[
{
"name": "LEDs, Display",
"name": "NRF, LEDs, Display",
"nrf24": {
"miso": 19,
"mosi": 23,
@ -20,7 +20,35 @@
}
},
{
"name": "Only Display",
"name": "CMT, LEDs, Display",
"nrf24": {
"miso": -1,
"mosi": -1,
"clk": -1,
"irq": -1,
"en": -1,
"cs": -1
},
"cmt": {
"clk": 18,
"cs": 4,
"fcs": 5,
"sdio": 23,
"gpio2": 19,
"gpio3": 16
},
"display": {
"type": 3,
"data": 21,
"clk": 22
},
"led": {
"led0": 25,
"led1": 26
}
},
{
"name": "NRF, Display",
"nrf24": {
"miso": 19,
"mosi": 23,
@ -36,7 +64,31 @@
}
},
{
"name": "Only LEDs",
"name": "CMT, Display",
"nrf24": {
"miso": -1,
"mosi": -1,
"clk": -1,
"irq": -1,
"en": -1,
"cs": -1
},
"cmt": {
"clk": 18,
"cs": 4,
"fcs": 5,
"sdio": 23,
"gpio2": 19,
"gpio3": 16
},
"display": {
"type": 3,
"data": 21,
"clk": 22
}
},
{
"name": "NRF, LEDs",
"nrf24": {
"miso": 19,
"mosi": 23,
@ -51,7 +103,30 @@
}
},
{
"name": "No Output",
"name": "CMT, LEDs",
"nrf24": {
"miso": -1,
"mosi": -1,
"clk": -1,
"irq": -1,
"en": -1,
"cs": -1
},
"cmt": {
"clk": 18,
"cs": 4,
"fcs": 5,
"sdio": 23,
"gpio2": 19,
"gpio3": 16
},
"led": {
"led0": 25,
"led1": 26
}
},
{
"name": "NRF",
"nrf24": {
"miso": 19,
"mosi": 23,
@ -60,5 +135,24 @@
"en": 4,
"cs": 5
}
},
{
"name": "CMT",
"nrf24": {
"miso": -1,
"mosi": -1,
"clk": -1,
"irq": -1,
"en": -1,
"cs": -1
},
"cmt": {
"clk": 18,
"cs": 4,
"fcs": 5,
"sdio": 23,
"gpio2": 19,
"gpio3": 16
}
}
]

View File

@ -19,6 +19,34 @@
"clk_mode": 0
}
},
{
"name": "Generic NodeMCU 32 with CMT2300A",
"nrf24": {
"miso": -1,
"mosi": -1,
"clk": -1,
"irq": -1,
"en": -1,
"cs": -1
},
"cmt": {
"clk": 18,
"cs": 4,
"fcs": 5,
"sdio": 23,
"gpio2": 19,
"gpio3": 16
},
"eth": {
"enabled": false,
"phy_addr": -1,
"power": -1,
"mdc": -1,
"mdio": -1,
"type": 0,
"clk_mode": 0
}
},
{
"name": "Generic NodeMCU 32 with SSD1306",
"nrf24": {
@ -68,5 +96,24 @@
"data": 21,
"clk": 22
}
},
{
"name": "Generic NodeMCU 32 with NRF + CMT Module",
"nrf24": {
"miso": 19,
"mosi": 23,
"clk": 18,
"irq": 16,
"en": 4,
"cs": 5
},
"cmt": {
"clk": 12,
"sdio": 14,
"cs": 27,
"fcs": 26,
"gpio2": -1,
"gpio3": -1
}
}
]

View File

@ -4,7 +4,7 @@
#include <Arduino.h>
#define CONFIG_FILENAME "/config.json"
#define CONFIG_VERSION 0x00011800 // 0.1.24 // make sure to clean all after change
#define CONFIG_VERSION 0x00011900 // 0.1.24 // make sure to clean all after change
#define WIFI_MAX_SSID_STRLEN 32
#define WIFI_MAX_PASSWORD_STRLEN 64
@ -23,7 +23,7 @@
#define INV_MAX_NAME_STRLEN 31
#define INV_MAX_COUNT 10
#define INV_MAX_CHAN_COUNT 4
#define INV_MAX_CHAN_COUNT 6
#define CHAN_MAX_NAME_STRLEN 31
@ -98,7 +98,9 @@ struct CONFIG_T {
uint64_t Dtu_Serial;
uint32_t Dtu_PollInterval;
uint8_t Dtu_PaLevel;
uint8_t Dtu_NrfPaLevel;
int8_t Dtu_CmtPaLevel;
uint32_t Dtu_CmtFrequency;
bool Mqtt_Hass_Enabled;
bool Mqtt_Hass_Retain;

View File

@ -18,6 +18,14 @@ struct PinMapping_t {
int8_t nrf24_irq;
int8_t nrf24_en;
int8_t nrf24_cs;
int8_t cmt_clk;
int8_t cmt_cs;
int8_t cmt_fcs;
int8_t cmt_gpio2;
int8_t cmt_gpio3;
int8_t cmt_sdio;
int8_t eth_phy_addr;
bool eth_enabled;
int eth_power;
@ -50,6 +58,7 @@ public:
PinMapping_t& get();
bool isValidNrf24Config();
bool isValidCmt2300Config();
bool isValidEthConfig();
bool isValidVictronConfig();
bool isValidBatteryConfig();

View File

@ -13,6 +13,7 @@ enum WebApiError {
DtuSerialZero,
DtuPollZero,
DtuInvalidPowerLevel,
DtuInvalidCmtFrequency,
ConfigBase = 3000,
ConfigNotDeleted,

View File

@ -76,7 +76,9 @@
#define DTU_SERIAL 0x99978563412
#define DTU_POLL_INTERVAL 5
#define DTU_PA_LEVEL 0
#define DTU_NRF_PA_LEVEL 0
#define DTU_CMT_PA_LEVEL 0
#define DTU_CMT_FREQUENCY 865000
#define MQTT_HASS_ENABLED false
#define MQTT_HASS_EXPIRE true

778
lib/CMT2300a/cmt2300a.c Normal file
View File

@ -0,0 +1,778 @@
/*
* THE FOLLOWING FIRMWARE IS PROVIDED: (1) "AS IS" WITH NO WARRANTY; AND
* (2)TO ENABLE ACCESS TO CODING INFORMATION TO GUIDE AND FACILITATE CUSTOMER.
* CONSEQUENTLY, CMOSTEK SHALL NOT BE HELD LIABLE FOR ANY DIRECT, INDIRECT OR
* CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE CONTENT
* OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING INFORMATION
* CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
*
* Copyright (C) CMOSTEK SZ.
*/
/*!
* @file cmt2300a.c
* @brief CMT2300A transceiver RF chip driver
*
* @version 1.3
* @date Jul 17 2017
* @author CMOSTEK R@D
*/
#include "cmt2300a.h"
/*! ********************************************************
* @name CMT2300A_SoftReset
* @desc Soft reset.
* *********************************************************/
void CMT2300A_SoftReset(void)
{
CMT2300A_WriteReg(0x7F, 0xFF);
}
/*! ********************************************************
* @name CMT2300A_GetChipStatus
* @desc Get the chip status.
* @return
* CMT2300A_STA_PUP
* CMT2300A_STA_SLEEP
* CMT2300A_STA_STBY
* CMT2300A_STA_RFS
* CMT2300A_STA_TFS
* CMT2300A_STA_RX
* CMT2300A_STA_TX
* CMT2300A_STA_EEPROM
* CMT2300A_STA_ERROR
* CMT2300A_STA_CAL
* *********************************************************/
uint8_t CMT2300A_GetChipStatus(void)
{
return CMT2300A_ReadReg(CMT2300A_CUS_MODE_STA) & CMT2300A_MASK_CHIP_MODE_STA;
}
/*! ********************************************************
* @name CMT2300A_AutoSwitchStatus
* @desc Auto switch the chip status, and 10 ms as timeout.
* @param nGoCmd: the chip next status
* @return TRUE or FALSE
* *********************************************************/
bool CMT2300A_AutoSwitchStatus(uint8_t nGoCmd)
{
#ifdef ENABLE_AUTO_SWITCH_CHIP_STATUS
uint32_t nBegTick = CMT2300A_GetTickCount();
uint8_t nWaitStatus = 0;
switch (nGoCmd) {
case CMT2300A_GO_SLEEP:
nWaitStatus = CMT2300A_STA_SLEEP;
break;
case CMT2300A_GO_STBY:
nWaitStatus = CMT2300A_STA_STBY;
break;
case CMT2300A_GO_TFS:
nWaitStatus = CMT2300A_STA_TFS;
break;
case CMT2300A_GO_TX:
nWaitStatus = CMT2300A_STA_TX;
break;
case CMT2300A_GO_RFS:
nWaitStatus = CMT2300A_STA_RFS;
break;
case CMT2300A_GO_RX:
nWaitStatus = CMT2300A_STA_RX;
break;
}
CMT2300A_WriteReg(CMT2300A_CUS_MODE_CTL, nGoCmd);
while (CMT2300A_GetTickCount() - nBegTick < 10) {
CMT2300A_DelayUs(100);
if (nWaitStatus == CMT2300A_GetChipStatus())
return true;
if (CMT2300A_GO_TX == nGoCmd) {
CMT2300A_DelayUs(100);
if (CMT2300A_MASK_TX_DONE_FLG & CMT2300A_ReadReg(CMT2300A_CUS_INT_CLR1))
return true;
}
if (CMT2300A_GO_RX == nGoCmd) {
CMT2300A_DelayUs(100);
if (CMT2300A_MASK_PKT_OK_FLG & CMT2300A_ReadReg(CMT2300A_CUS_INT_FLAG))
return true;
}
}
return false;
#else
CMT2300A_WriteReg(CMT2300A_CUS_MODE_CTL, nGoCmd);
return true;
#endif
}
/*! ********************************************************
* @name CMT2300A_GoSleep
* @desc Entry SLEEP mode.
* @return TRUE or FALSE
* *********************************************************/
bool CMT2300A_GoSleep(void)
{
return CMT2300A_AutoSwitchStatus(CMT2300A_GO_SLEEP);
}
/*! ********************************************************
* @name CMT2300A_GoStby
* @desc Entry Sleep mode.
* @return TRUE or FALSE
* *********************************************************/
bool CMT2300A_GoStby(void)
{
return CMT2300A_AutoSwitchStatus(CMT2300A_GO_STBY);
}
/*! ********************************************************
* @name CMT2300A_GoTFS
* @desc Entry TFS mode.
* @return TRUE or FALSE
* *********************************************************/
bool CMT2300A_GoTFS(void)
{
return CMT2300A_AutoSwitchStatus(CMT2300A_GO_TFS);
}
/*! ********************************************************
* @name CMT2300A_GoRFS
* @desc Entry RFS mode.
* @return TRUE or FALSE
* *********************************************************/
bool CMT2300A_GoRFS(void)
{
return CMT2300A_AutoSwitchStatus(CMT2300A_GO_RFS);
}
/*! ********************************************************
* @name CMT2300A_GoTx
* @desc Entry Tx mode.
* @return TRUE or FALSE
* *********************************************************/
bool CMT2300A_GoTx(void)
{
return CMT2300A_AutoSwitchStatus(CMT2300A_GO_TX);
}
/*! ********************************************************
* @name CMT2300A_GoRx
* @desc Entry Rx mode.
* @return TRUE or FALSE
* *********************************************************/
bool CMT2300A_GoRx(void)
{
return CMT2300A_AutoSwitchStatus(CMT2300A_GO_RX);
}
/*! ********************************************************
* @name CMT2300A_ConfigGpio
* @desc Config GPIO pins mode.
* @param nGpioSel: GPIO1_SEL | GPIO2_SEL | GPIO3_SEL | GPIO4_SEL
* GPIO1_SEL:
* CMT2300A_GPIO1_SEL_DOUT/DIN
* CMT2300A_GPIO1_SEL_INT1
* CMT2300A_GPIO1_SEL_INT2
* CMT2300A_GPIO1_SEL_DCLK
*
* GPIO2_SEL:
* CMT2300A_GPIO2_SEL_INT1
* CMT2300A_GPIO2_SEL_INT2
* CMT2300A_GPIO2_SEL_DOUT/DIN
* CMT2300A_GPIO2_SEL_DCLK
*
* GPIO3_SEL:
* CMT2300A_GPIO3_SEL_CLKO
* CMT2300A_GPIO3_SEL_DOUT/DIN
* CMT2300A_GPIO3_SEL_INT2
* CMT2300A_GPIO3_SEL_DCLK
*
* GPIO4_SEL:
* CMT2300A_GPIO4_SEL_RSTIN
* CMT2300A_GPIO4_SEL_INT1
* CMT2300A_GPIO4_SEL_DOUT
* CMT2300A_GPIO4_SEL_DCLK
* *********************************************************/
void CMT2300A_ConfigGpio(uint8_t nGpioSel)
{
CMT2300A_WriteReg(CMT2300A_CUS_IO_SEL, nGpioSel);
}
/*! ********************************************************
* @name CMT2300A_ConfigInterrupt
* @desc Config interrupt on INT1 and INT2.
* @param nInt1Sel, nInt2Sel
* CMT2300A_INT_SEL_RX_ACTIVE
* CMT2300A_INT_SEL_TX_ACTIVE
* CMT2300A_INT_SEL_RSSI_VLD
* CMT2300A_INT_SEL_PREAM_OK
* CMT2300A_INT_SEL_SYNC_OK
* CMT2300A_INT_SEL_NODE_OK
* CMT2300A_INT_SEL_CRC_OK
* CMT2300A_INT_SEL_PKT_OK
* CMT2300A_INT_SEL_SL_TMO
* CMT2300A_INT_SEL_RX_TMO
* CMT2300A_INT_SEL_TX_DONE
* CMT2300A_INT_SEL_RX_FIFO_NMTY
* CMT2300A_INT_SEL_RX_FIFO_TH
* CMT2300A_INT_SEL_RX_FIFO_FULL
* CMT2300A_INT_SEL_RX_FIFO_WBYTE
* CMT2300A_INT_SEL_RX_FIFO_OVF
* CMT2300A_INT_SEL_TX_FIFO_NMTY
* CMT2300A_INT_SEL_TX_FIFO_TH
* CMT2300A_INT_SEL_TX_FIFO_FULL
* CMT2300A_INT_SEL_STATE_IS_STBY
* CMT2300A_INT_SEL_STATE_IS_FS
* CMT2300A_INT_SEL_STATE_IS_RX
* CMT2300A_INT_SEL_STATE_IS_TX
* CMT2300A_INT_SEL_LED
* CMT2300A_INT_SEL_TRX_ACTIVE
* CMT2300A_INT_SEL_PKT_DONE
* *********************************************************/
void CMT2300A_ConfigInterrupt(uint8_t nInt1Sel, uint8_t nInt2Sel)
{
nInt1Sel &= CMT2300A_MASK_INT1_SEL;
nInt1Sel |= (~CMT2300A_MASK_INT1_SEL) & CMT2300A_ReadReg(CMT2300A_CUS_INT1_CTL);
CMT2300A_WriteReg(CMT2300A_CUS_INT1_CTL, nInt1Sel);
nInt2Sel &= CMT2300A_MASK_INT2_SEL;
nInt2Sel |= (~CMT2300A_MASK_INT2_SEL) & CMT2300A_ReadReg(CMT2300A_CUS_INT2_CTL);
CMT2300A_WriteReg(CMT2300A_CUS_INT2_CTL, nInt2Sel);
}
/*! ********************************************************
* @name CMT2300A_SetInterruptPolar
* @desc Set the polarity of the interrupt.
* @param bEnable(TRUE): active-high (default)
* bEnable(FALSE): active-low
* *********************************************************/
void CMT2300A_SetInterruptPolar(bool bActiveHigh)
{
uint8_t tmp = CMT2300A_ReadReg(CMT2300A_CUS_INT1_CTL);
if (bActiveHigh)
tmp &= ~CMT2300A_MASK_INT_POLAR;
else
tmp |= CMT2300A_MASK_INT_POLAR;
CMT2300A_WriteReg(CMT2300A_CUS_INT1_CTL, tmp);
}
/*! ********************************************************
* @name CMT2300A_SetFifoThreshold
* @desc Set FIFO threshold.
* @param nFifoThreshold
* *********************************************************/
void CMT2300A_SetFifoThreshold(uint8_t nFifoThreshold)
{
uint8_t tmp = CMT2300A_ReadReg(CMT2300A_CUS_PKT29);
tmp &= ~CMT2300A_MASK_FIFO_TH;
tmp |= nFifoThreshold & CMT2300A_MASK_FIFO_TH;
CMT2300A_WriteReg(CMT2300A_CUS_PKT29, tmp);
}
/*! ********************************************************
* @name CMT2300A_EnableAntennaSwitch
* @desc Enable antenna switch, output TX_ACTIVE/RX_ACTIVE
* via GPIO1/GPIO2.
* @param nMode
* 0: RF_SWT1_EN=1, RF_SWT2_EN=0
* GPIO1: RX_ACTIVE, GPIO2: TX_ACTIVE
* 1: RF_SWT1_EN=0, RF_SWT2_EN=1
* GPIO1: RX_ACTIVE, GPIO2: ~RX_ACTIVE
* *********************************************************/
void CMT2300A_EnableAntennaSwitch(uint8_t nMode)
{
uint8_t tmp = CMT2300A_ReadReg(CMT2300A_CUS_INT1_CTL);
if (0 == nMode) {
tmp |= CMT2300A_MASK_RF_SWT1_EN;
tmp &= ~CMT2300A_MASK_RF_SWT2_EN;
} else if (1 == nMode) {
tmp &= ~CMT2300A_MASK_RF_SWT1_EN;
tmp |= CMT2300A_MASK_RF_SWT2_EN;
}
CMT2300A_WriteReg(CMT2300A_CUS_INT1_CTL, tmp);
}
/*! ********************************************************
* @name CMT2300A_EnableInterrupt
* @desc Enable interrupt.
* @param nEnable
* CMT2300A_MASK_SL_TMO_EN |
* CMT2300A_MASK_RX_TMO_EN |
* CMT2300A_MASK_TX_DONE_EN |
* CMT2300A_MASK_PREAM_OK_EN |
* CMT2300A_MASK_SYNC_OK_EN |
* CMT2300A_MASK_NODE_OK_EN |
* CMT2300A_MASK_CRC_OK_EN |
* CMT2300A_MASK_PKT_DONE_EN
* *********************************************************/
void CMT2300A_EnableInterrupt(uint8_t nEnable)
{
CMT2300A_WriteReg(CMT2300A_CUS_INT_EN, nEnable);
}
/*! ********************************************************
* @name CMT2300A_EnableRxFifoAutoClear
* @desc Auto clear Rx FIFO before entry Rx mode.
* @param bEnable(TRUE): Enable it(default)
* bEnable(FALSE): Disable it
* *********************************************************/
void CMT2300A_EnableRxFifoAutoClear(bool bEnable)
{
uint8_t tmp = CMT2300A_ReadReg(CMT2300A_CUS_FIFO_CTL);
if (bEnable)
tmp &= ~CMT2300A_MASK_FIFO_AUTO_CLR_DIS;
else
tmp |= CMT2300A_MASK_FIFO_AUTO_CLR_DIS;
CMT2300A_WriteReg(CMT2300A_CUS_FIFO_CTL, tmp);
}
/*! ********************************************************
* @name CMT2300A_EnableFifoMerge
* @desc Enable FIFO merge.
* @param bEnable(TRUE): use a single 64-byte FIFO for either Tx or Rx
* bEnable(FALSE): use a 32-byte FIFO for Tx and another 32-byte FIFO for Rx(default)
* *********************************************************/
void CMT2300A_EnableFifoMerge(bool bEnable)
{
uint8_t tmp = CMT2300A_ReadReg(CMT2300A_CUS_FIFO_CTL);
if (bEnable)
tmp |= CMT2300A_MASK_FIFO_MERGE_EN;
else
tmp &= ~CMT2300A_MASK_FIFO_MERGE_EN;
CMT2300A_WriteReg(CMT2300A_CUS_FIFO_CTL, tmp);
}
/*! ********************************************************
* @name CMT2300A_EnableReadFifo
* @desc Enable SPI to read the FIFO.
* *********************************************************/
void CMT2300A_EnableReadFifo(void)
{
uint8_t tmp = CMT2300A_ReadReg(CMT2300A_CUS_FIFO_CTL);
tmp &= ~CMT2300A_MASK_SPI_FIFO_RD_WR_SEL;
tmp &= ~CMT2300A_MASK_FIFO_RX_TX_SEL;
CMT2300A_WriteReg(CMT2300A_CUS_FIFO_CTL, tmp);
}
/*! ********************************************************
* @name CMT2300A_EnableWriteFifo
* @desc Enable SPI to write the FIFO.
* *********************************************************/
void CMT2300A_EnableWriteFifo(void)
{
uint8_t tmp = CMT2300A_ReadReg(CMT2300A_CUS_FIFO_CTL);
tmp |= CMT2300A_MASK_SPI_FIFO_RD_WR_SEL;
tmp |= CMT2300A_MASK_FIFO_RX_TX_SEL;
CMT2300A_WriteReg(CMT2300A_CUS_FIFO_CTL, tmp);
}
/*! ********************************************************
* @name CMT2300A_RestoreFifo
* @desc Restore the FIFO.
* *********************************************************/
void CMT2300A_RestoreFifo(void)
{
CMT2300A_WriteReg(CMT2300A_CUS_FIFO_CLR, CMT2300A_MASK_FIFO_RESTORE);
}
/*! ********************************************************
* @name CMT2300A_ClearFifo
* @desc Clear the Tx FIFO.
* @return FIFO flags
* CMT2300A_MASK_RX_FIFO_FULL_FLG |
* CMT2300A_MASK_RX_FIFO_NMTY_FLG |
* CMT2300A_MASK_RX_FIFO_TH_FLG |
* CMT2300A_MASK_RX_FIFO_OVF_FLG |
* CMT2300A_MASK_TX_FIFO_FULL_FLG |
* CMT2300A_MASK_TX_FIFO_NMTY_FLG |
* CMT2300A_MASK_TX_FIFO_TH_FLG
* *********************************************************/
uint8_t CMT2300A_ClearTxFifo(void)
{
uint8_t tmp = CMT2300A_ReadReg(CMT2300A_CUS_FIFO_FLAG);
CMT2300A_WriteReg(CMT2300A_CUS_FIFO_CLR, CMT2300A_MASK_FIFO_CLR_TX);
return tmp;
}
/*! ********************************************************
* @name CMT2300A_ClearFifo
* @desc Clear the Rx FIFO.
* @return FIFO flags
* CMT2300A_MASK_RX_FIFO_FULL_FLG |
* CMT2300A_MASK_RX_FIFO_NMTY_FLG |
* CMT2300A_MASK_RX_FIFO_TH_FLG |
* CMT2300A_MASK_RX_FIFO_OVF_FLG |
* CMT2300A_MASK_TX_FIFO_FULL_FLG |
* CMT2300A_MASK_TX_FIFO_NMTY_FLG |
* CMT2300A_MASK_TX_FIFO_TH_FLG
* *********************************************************/
uint8_t CMT2300A_ClearRxFifo(void)
{
uint8_t tmp = CMT2300A_ReadReg(CMT2300A_CUS_FIFO_FLAG);
CMT2300A_WriteReg(CMT2300A_CUS_FIFO_CLR, CMT2300A_MASK_FIFO_CLR_RX);
return tmp;
}
/*! ********************************************************
* @name CMT2300A_ClearInterruptFlags
* @desc Clear all interrupt flags.
* @return Some interrupt flags
* CMT2300A_MASK_SL_TMO_EN |
* CMT2300A_MASK_RX_TMO_EN |
* CMT2300A_MASK_TX_DONE_EN |
* CMT2300A_MASK_PREAM_OK_FLG |
* CMT2300A_MASK_SYNC_OK_FLG |
* CMT2300A_MASK_NODE_OK_FLG |
* CMT2300A_MASK_CRC_OK_FLG |
* CMT2300A_MASK_PKT_OK_FLG
* *********************************************************/
uint8_t CMT2300A_ClearInterruptFlags(void)
{
uint8_t nFlag1, nFlag2;
uint8_t nClr1 = 0;
uint8_t nClr2 = 0;
uint8_t nRet = 0;
uint8_t nIntPolar;
nIntPolar = CMT2300A_ReadReg(CMT2300A_CUS_INT1_CTL);
nIntPolar = (nIntPolar & CMT2300A_MASK_INT_POLAR) ? 1 : 0;
nFlag1 = CMT2300A_ReadReg(CMT2300A_CUS_INT_FLAG);
nFlag2 = CMT2300A_ReadReg(CMT2300A_CUS_INT_CLR1);
if (nIntPolar) {
/* Interrupt flag active-low */
nFlag1 = ~nFlag1;
nFlag2 = ~nFlag2;
}
if (CMT2300A_MASK_LBD_FLG & nFlag1) {
nClr2 |= CMT2300A_MASK_LBD_CLR; /* Clear LBD_FLG */
}
if (CMT2300A_MASK_COL_ERR_FLG & nFlag1) {
nClr2 |= CMT2300A_MASK_PKT_DONE_CLR; /* Clear COL_ERR_FLG by PKT_DONE_CLR */
}
if (CMT2300A_MASK_PKT_ERR_FLG & nFlag1) {
nClr2 |= CMT2300A_MASK_PKT_DONE_CLR; /* Clear PKT_ERR_FLG by PKT_DONE_CLR */
}
if (CMT2300A_MASK_PREAM_OK_FLG & nFlag1) {
nClr2 |= CMT2300A_MASK_PREAM_OK_CLR; /* Clear PREAM_OK_FLG */
nRet |= CMT2300A_MASK_PREAM_OK_FLG; /* Return PREAM_OK_FLG */
}
if (CMT2300A_MASK_SYNC_OK_FLG & nFlag1) {
nClr2 |= CMT2300A_MASK_SYNC_OK_CLR; /* Clear SYNC_OK_FLG */
nRet |= CMT2300A_MASK_SYNC_OK_FLG; /* Return SYNC_OK_FLG */
}
if (CMT2300A_MASK_NODE_OK_FLG & nFlag1) {
nClr2 |= CMT2300A_MASK_NODE_OK_CLR; /* Clear NODE_OK_FLG */
nRet |= CMT2300A_MASK_NODE_OK_FLG; /* Return NODE_OK_FLG */
}
if (CMT2300A_MASK_CRC_OK_FLG & nFlag1) {
nClr2 |= CMT2300A_MASK_CRC_OK_CLR; /* Clear CRC_OK_FLG */
nRet |= CMT2300A_MASK_CRC_OK_FLG; /* Return CRC_OK_FLG */
}
if (CMT2300A_MASK_PKT_OK_FLG & nFlag1) {
nClr2 |= CMT2300A_MASK_PKT_DONE_CLR; /* Clear PKT_OK_FLG */
nRet |= CMT2300A_MASK_PKT_OK_FLG; /* Return PKT_OK_FLG */
}
if (CMT2300A_MASK_SL_TMO_FLG & nFlag2) {
nClr1 |= CMT2300A_MASK_SL_TMO_CLR; /* Clear SL_TMO_FLG */
nRet |= CMT2300A_MASK_SL_TMO_EN; /* Return SL_TMO_FLG by SL_TMO_EN */
}
if (CMT2300A_MASK_RX_TMO_FLG & nFlag2) {
nClr1 |= CMT2300A_MASK_RX_TMO_CLR; /* Clear RX_TMO_FLG */
nRet |= CMT2300A_MASK_RX_TMO_EN; /* Return RX_TMO_FLG by RX_TMO_EN */
}
if (CMT2300A_MASK_TX_DONE_FLG & nFlag2) {
nClr1 |= CMT2300A_MASK_TX_DONE_CLR; /* Clear TX_DONE_FLG */
nRet |= CMT2300A_MASK_TX_DONE_EN; /* Return TX_DONE_FLG by TX_DONE_EN */
}
CMT2300A_WriteReg(CMT2300A_CUS_INT_CLR1, nClr1);
CMT2300A_WriteReg(CMT2300A_CUS_INT_CLR2, nClr2);
if (nIntPolar) {
/* Interrupt flag active-low */
nRet = ~nRet;
}
return nRet;
}
/*! ********************************************************
* @name CMT2300A_ConfigTxDin
* @desc Used to select whether to use GPIO1 or GPIO2 or GPIO3
* as DIN in the direct mode. It only takes effect when
* call CMT2300A_EnableTxDin(TRUE) in the direct mode.
* @param nDinSel
* CMT2300A_TX_DIN_SEL_GPIO1
* CMT2300A_TX_DIN_SEL_GPIO2
* CMT2300A_TX_DIN_SEL_GPIO3
* *********************************************************/
void CMT2300A_ConfigTxDin(uint8_t nDinSel)
{
uint8_t tmp = CMT2300A_ReadReg(CMT2300A_CUS_FIFO_CTL);
tmp &= ~CMT2300A_MASK_TX_DIN_SEL;
tmp |= nDinSel;
CMT2300A_WriteReg(CMT2300A_CUS_FIFO_CTL, tmp);
}
/*! ********************************************************
* @name CMT2300A_EnableTxDin
* @desc Used to change GPIO1/GPIO2/GPIO3 between DOUT and DIN.
* @param bEnable(TRUE): used as DIN
* bEnable(FALSE): used as DOUT(default)
* *********************************************************/
void CMT2300A_EnableTxDin(bool bEnable)
{
uint8_t tmp = CMT2300A_ReadReg(CMT2300A_CUS_FIFO_CTL);
if (bEnable)
tmp |= CMT2300A_MASK_TX_DIN_EN;
else
tmp &= ~CMT2300A_MASK_TX_DIN_EN;
CMT2300A_WriteReg(CMT2300A_CUS_FIFO_CTL, tmp);
}
/*! ********************************************************
* @name CMT2300A_EnableTxDinInvert
* @desc Used to invert DIN data in direct mode.
* @param bEnable(TRUE): invert DIN
* bEnable(FALSE): not invert DIN(default)
* *********************************************************/
void CMT2300A_EnableTxDinInvert(bool bEnable)
{
uint8_t tmp = CMT2300A_ReadReg(CMT2300A_CUS_INT2_CTL);
if (bEnable)
tmp |= CMT2300A_MASK_TX_DIN_INV;
else
tmp &= ~CMT2300A_MASK_TX_DIN_INV;
CMT2300A_WriteReg(CMT2300A_CUS_INT2_CTL, tmp);
}
/*! ********************************************************
* @name CMT2300A_IsExist
* @desc Chip indentify.
* @return TRUE: chip is exist, FALSE: chip not found
* *********************************************************/
bool CMT2300A_IsExist(void)
{
uint8_t back, dat;
back = CMT2300A_ReadReg(CMT2300A_CUS_PKT17);
CMT2300A_WriteReg(CMT2300A_CUS_PKT17, 0xAA);
dat = CMT2300A_ReadReg(CMT2300A_CUS_PKT17);
CMT2300A_WriteReg(CMT2300A_CUS_PKT17, back);
if (0xAA == dat)
return true;
else
return false;
}
/*! ********************************************************
* @name CMT2300A_GetRssiCode
* @desc Get RSSI code.
* @return RSSI code
* *********************************************************/
uint8_t CMT2300A_GetRssiCode(void)
{
return CMT2300A_ReadReg(CMT2300A_CUS_RSSI_CODE);
}
/*! ********************************************************
* @name CMT2300A_GetRssiDBm
* @desc Get RSSI dBm.
* @return dBm
* *********************************************************/
int CMT2300A_GetRssiDBm(void)
{
return (int)CMT2300A_ReadReg(CMT2300A_CUS_RSSI_DBM) - 128;
}
/*! ********************************************************
* @name CMT2300A_SetFrequencyChannel
* @desc This defines up to 255 frequency channel
* for fast frequency hopping operation.
* @param nChann: the frequency channel
* *********************************************************/
void CMT2300A_SetFrequencyChannel(uint8_t nChann)
{
CMT2300A_WriteReg(CMT2300A_CUS_FREQ_CHNL, nChann);
}
/*! ********************************************************
* @name CMT2300A_SetFrequencyStep
* @desc This defines the frequency channel step size
* for fast frequency hopping operation.
* One step size is 2.5 kHz.
* @param nOffset: the frequency step
* *********************************************************/
void CMT2300A_SetFrequencyStep(uint8_t nOffset)
{
CMT2300A_WriteReg(CMT2300A_CUS_FREQ_OFS, nOffset);
}
/*! ********************************************************
* @name CMT2300A_SetPayloadLength
* @desc Set payload length.
* @param nLength
* *********************************************************/
void CMT2300A_SetPayloadLength(uint16_t nLength)
{
uint8_t tmp = CMT2300A_ReadReg(CMT2300A_CUS_PKT14);
tmp &= ~CMT2300A_MASK_PAYLOAD_LENG_10_8;
tmp |= (nLength >> 4) & CMT2300A_MASK_PAYLOAD_LENG_10_8;
CMT2300A_WriteReg(CMT2300A_CUS_PKT14, tmp);
tmp = nLength & CMT2300A_MASK_PAYLOAD_LENG_7_0;
CMT2300A_WriteReg(CMT2300A_CUS_PKT15, tmp);
}
/*! ********************************************************
* @name CMT2300A_EnableLfosc
* @desc If you need use sleep timer, you should enable LFOSC.
* @param bEnable(TRUE): Enable it(default)
* bEnable(FALSE): Disable it
* *********************************************************/
void CMT2300A_EnableLfosc(bool bEnable)
{
uint8_t tmp = CMT2300A_ReadReg(CMT2300A_CUS_SYS2);
if (bEnable) {
tmp |= CMT2300A_MASK_LFOSC_RECAL_EN;
tmp |= CMT2300A_MASK_LFOSC_CAL1_EN;
tmp |= CMT2300A_MASK_LFOSC_CAL2_EN;
} else {
tmp &= ~CMT2300A_MASK_LFOSC_RECAL_EN;
tmp &= ~CMT2300A_MASK_LFOSC_CAL1_EN;
tmp &= ~CMT2300A_MASK_LFOSC_CAL2_EN;
}
CMT2300A_WriteReg(CMT2300A_CUS_SYS2, tmp);
}
/*! ********************************************************
* @name CMT2300A_EnableLfoscOutput
* @desc LFOSC clock is output via GPIO3.
* @param bEnable(TRUE): Enable it
* bEnable(FALSE): Disable it(default)
* *********************************************************/
void CMT2300A_EnableLfoscOutput(bool bEnable)
{
uint8_t tmp = CMT2300A_ReadReg(CMT2300A_CUS_INT2_CTL);
if (bEnable)
tmp |= CMT2300A_MASK_LFOSC_OUT_EN;
else
tmp &= ~CMT2300A_MASK_LFOSC_OUT_EN;
CMT2300A_WriteReg(CMT2300A_CUS_INT2_CTL, tmp);
}
/*! ********************************************************
* @name CMT2300A_EnableAfc
* @desc AFC enable or disanble.
* @param bEnable(TRUE): Enable it
* bEnable(FALSE): Disable it(default)
* *********************************************************/
void CMT2300A_EnableAfc(bool bEnable)
{
uint8_t tmp = CMT2300A_ReadReg(CMT2300A_CUS_FSK5);
if (bEnable)
tmp |= 0x10;
else
tmp &= ~0x10;
CMT2300A_WriteReg(CMT2300A_CUS_FSK5, tmp);
}
/*! ********************************************************
* @name CMT2300A_SetAfcOvfTh
* @desc This is optional, only needed when using Rx fast frequency hopping.
* @param afcOvfTh: AFC_OVF_TH see AN142 and AN197 for details.
* *********************************************************/
void CMT2300A_SetAfcOvfTh(uint8_t afcOvfTh)
{
CMT2300A_WriteReg(CMT2300A_CUS_FSK4, afcOvfTh);
}
/*! ********************************************************
* @name CMT2300A_Init
* @desc Initialize chip status.
* *********************************************************/
bool CMT2300A_Init(void)
{
uint8_t tmp;
CMT2300A_SoftReset();
CMT2300A_DelayMs(20);
if (!CMT2300A_GoStby())
return false; // CMT2300A not switched to standby mode!
if (!CMT2300A_IsExist())
return false; // CMT2300A not found!
tmp = CMT2300A_ReadReg(CMT2300A_CUS_MODE_STA);
tmp |= CMT2300A_MASK_CFG_RETAIN; /* Enable CFG_RETAIN */
tmp &= ~CMT2300A_MASK_RSTN_IN_EN; /* Disable RSTN_IN */
CMT2300A_WriteReg(CMT2300A_CUS_MODE_STA, tmp);
tmp = CMT2300A_ReadReg(CMT2300A_CUS_EN_CTL);
tmp |= CMT2300A_MASK_LOCKING_EN; /* Enable LOCKING_EN */
CMT2300A_WriteReg(CMT2300A_CUS_EN_CTL, tmp);
CMT2300A_EnableLfosc(false); /* Disable LFOSC */
CMT2300A_ClearInterruptFlags();
return true;
}
/*! ********************************************************
* @name CMT2300A_ConfigRegBank
* @desc Config one register bank.
* *********************************************************/
bool CMT2300A_ConfigRegBank(uint8_t base_addr, const uint8_t bank[], uint8_t len)
{
uint8_t i;
for (i = 0; i < len; i++)
CMT2300A_WriteReg(i + base_addr, bank[i]);
return true;
}

96
lib/CMT2300a/cmt2300a.h Normal file
View File

@ -0,0 +1,96 @@
/*
* THE FOLLOWING FIRMWARE IS PROVIDED: (1) "AS IS" WITH NO WARRANTY; AND
* (2)TO ENABLE ACCESS TO CODING INFORMATION TO GUIDE AND FACILITATE CUSTOMER.
* CONSEQUENTLY, CMOSTEK SHALL NOT BE HELD LIABLE FOR ANY DIRECT, INDIRECT OR
* CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE CONTENT
* OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING INFORMATION
* CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
*
* Copyright (C) CMOSTEK SZ.
*/
/*!
* @file cmt2300a.h
* @brief CMT2300A transceiver RF chip driver
*
* @version 1.3
* @date Jul 17 2017
* @author CMOSTEK R@D
*/
#ifndef __CMT2300A_H
#define __CMT2300A_H
#include "cmt2300a_defs.h"
#include "cmt2300a_hal.h"
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define ENABLE_AUTO_SWITCH_CHIP_STATUS /* Enable the auto switch chip status */
/* ************************************************************************
The following are for chip status controls.
* ************************************************************************ */
void CMT2300A_SoftReset(void);
uint8_t CMT2300A_GetChipStatus(void);
bool CMT2300A_AutoSwitchStatus(uint8_t nGoCmd);
bool CMT2300A_GoSleep(void);
bool CMT2300A_GoStby(void);
bool CMT2300A_GoTFS(void);
bool CMT2300A_GoRFS(void);
bool CMT2300A_GoTx(void);
bool CMT2300A_GoRx(void);
/* ************************************************************************
* The following are for chip interrupts, GPIO, FIFO operations.
* ************************************************************************ */
void CMT2300A_ConfigGpio(uint8_t nGpioSel);
void CMT2300A_ConfigInterrupt(uint8_t nInt1Sel, uint8_t nInt2Sel);
void CMT2300A_SetInterruptPolar(bool bActiveHigh);
void CMT2300A_SetFifoThreshold(uint8_t nFifoThreshold);
void CMT2300A_EnableAntennaSwitch(uint8_t nMode);
void CMT2300A_EnableInterrupt(uint8_t nEnable);
void CMT2300A_EnableRxFifoAutoClear(bool bEnable);
void CMT2300A_EnableFifoMerge(bool bEnable);
void CMT2300A_EnableReadFifo(void);
void CMT2300A_EnableWriteFifo(void);
void CMT2300A_RestoreFifo(void);
uint8_t CMT2300A_ClearTxFifo(void);
uint8_t CMT2300A_ClearRxFifo(void);
uint8_t CMT2300A_ClearInterruptFlags(void);
/* ************************************************************************
* The following are for Tx DIN operations in direct mode.
* ************************************************************************ */
void CMT2300A_ConfigTxDin(uint8_t nDinSel);
void CMT2300A_EnableTxDin(bool bEnable);
void CMT2300A_EnableTxDinInvert(bool bEnable);
/* ************************************************************************
* The following are general operations.
* ************************************************************************ */
bool CMT2300A_IsExist(void);
uint8_t CMT2300A_GetRssiCode(void);
int CMT2300A_GetRssiDBm(void);
void CMT2300A_SetFrequencyChannel(uint8_t nChann);
void CMT2300A_SetFrequencyStep(uint8_t nOffset);
void CMT2300A_SetPayloadLength(uint16_t nLength);
void CMT2300A_EnableLfosc(bool bEnable);
void CMT2300A_EnableLfoscOutput(bool bEnable);
void CMT2300A_EnableAfc(bool bEnable);
void CMT2300A_SetAfcOvfTh(uint8_t afcOvfTh);
/* ************************************************************************
* The following are for chip initializes.
* ************************************************************************ */
bool CMT2300A_Init(void);
bool CMT2300A_ConfigRegBank(uint8_t base_addr, const uint8_t bank[], uint8_t len);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,606 @@
/*
* THE FOLLOWING FIRMWARE IS PROVIDED: (1) "AS IS" WITH NO WARRANTY; AND
* (2)TO ENABLE ACCESS TO CODING INFORMATION TO GUIDE AND FACILITATE CUSTOMER.
* CONSEQUENTLY, CMOSTEK SHALL NOT BE HELD LIABLE FOR ANY DIRECT, INDIRECT OR
* CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE CONTENT
* OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING INFORMATION
* CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
*
* Copyright (C) CMOSTEK SZ.
*/
/*!
* @file cmt2300a_defs.h
* @brief CMT2300A registers defines
*
* @version 1.2
* @date Jul 17 2017
* @author CMOSTEK R@D
*/
#ifndef __CMT2300A_DEFS_H
#define __CMT2300A_DEFS_H
/* ---------- CMT bank defines ---------- */
#define CMT2300A_CMT_BANK_ADDR 0x00
#define CMT2300A_CMT_BANK_SIZE 12
#define CMT2300A_CUS_CMT1 0x00
#define CMT2300A_CUS_CMT2 0x01
#define CMT2300A_CUS_CMT3 0x02
#define CMT2300A_CUS_CMT4 0x03
#define CMT2300A_CUS_CMT5 0x04
#define CMT2300A_CUS_CMT6 0x05
#define CMT2300A_CUS_CMT7 0x06
#define CMT2300A_CUS_CMT8 0x07
#define CMT2300A_CUS_CMT9 0x08
#define CMT2300A_CUS_CMT10 0x09
#define CMT2300A_CUS_CMT11 0x0A
#define CMT2300A_CUS_RSSI 0x0B
/* ---------- System bank defines ---------- */
#define CMT2300A_SYSTEM_BANK_ADDR 0x0C
#define CMT2300A_SYSTEM_BANK_SIZE 12
#define CMT2300A_CUS_SYS1 0x0C
#define CMT2300A_CUS_SYS2 0x0D
#define CMT2300A_CUS_SYS3 0x0E
#define CMT2300A_CUS_SYS4 0x0F
#define CMT2300A_CUS_SYS5 0x10
#define CMT2300A_CUS_SYS6 0x11
#define CMT2300A_CUS_SYS7 0x12
#define CMT2300A_CUS_SYS8 0x13
#define CMT2300A_CUS_SYS9 0x14
#define CMT2300A_CUS_SYS10 0x15
#define CMT2300A_CUS_SYS11 0x16
#define CMT2300A_CUS_SYS12 0x17
/* ---------- Frequency bank defines ---------- */
#define CMT2300A_FREQUENCY_BANK_ADDR 0x18
#define CMT2300A_FREQUENCY_BANK_SIZE 8
#define CMT2300A_CUS_RF1 0x18
#define CMT2300A_CUS_RF2 0x19
#define CMT2300A_CUS_RF3 0x1A
#define CMT2300A_CUS_RF4 0x1B
#define CMT2300A_CUS_RF5 0x1C
#define CMT2300A_CUS_RF6 0x1D
#define CMT2300A_CUS_RF7 0x1E
#define CMT2300A_CUS_RF8 0x1F
/* ---------- Data rate bank defines ---------- */
#define CMT2300A_DATA_RATE_BANK_ADDR 0x20
#define CMT2300A_DATA_RATE_BANK_SIZE 24
#define CMT2300A_CUS_RF9 0x20
#define CMT2300A_CUS_RF10 0x21
#define CMT2300A_CUS_RF11 0x22
#define CMT2300A_CUS_RF12 0x23
#define CMT2300A_CUS_FSK1 0x24
#define CMT2300A_CUS_FSK2 0x25
#define CMT2300A_CUS_FSK3 0x26
#define CMT2300A_CUS_FSK4 0x27
#define CMT2300A_CUS_FSK5 0x28
#define CMT2300A_CUS_FSK6 0x29
#define CMT2300A_CUS_FSK7 0x2A
#define CMT2300A_CUS_CDR1 0x2B
#define CMT2300A_CUS_CDR2 0x2C
#define CMT2300A_CUS_CDR3 0x2D
#define CMT2300A_CUS_CDR4 0x2E
#define CMT2300A_CUS_AGC1 0x2F
#define CMT2300A_CUS_AGC2 0x30
#define CMT2300A_CUS_AGC3 0x31
#define CMT2300A_CUS_AGC4 0x32
#define CMT2300A_CUS_OOK1 0x33
#define CMT2300A_CUS_OOK2 0x34
#define CMT2300A_CUS_OOK3 0x35
#define CMT2300A_CUS_OOK4 0x36
#define CMT2300A_CUS_OOK5 0x37
/* ---------- Baseband bank defines ---------- */
#define CMT2300A_BASEBAND_BANK_ADDR 0x38
#define CMT2300A_BASEBAND_BANK_SIZE 29
#define CMT2300A_CUS_PKT1 0x38
#define CMT2300A_CUS_PKT2 0x39
#define CMT2300A_CUS_PKT3 0x3A
#define CMT2300A_CUS_PKT4 0x3B
#define CMT2300A_CUS_PKT5 0x3C
#define CMT2300A_CUS_PKT6 0x3D
#define CMT2300A_CUS_PKT7 0x3E
#define CMT2300A_CUS_PKT8 0x3F
#define CMT2300A_CUS_PKT9 0x40
#define CMT2300A_CUS_PKT10 0x41
#define CMT2300A_CUS_PKT11 0x42
#define CMT2300A_CUS_PKT12 0x43
#define CMT2300A_CUS_PKT13 0x44
#define CMT2300A_CUS_PKT14 0x45
#define CMT2300A_CUS_PKT15 0x46
#define CMT2300A_CUS_PKT16 0x47
#define CMT2300A_CUS_PKT17 0x48
#define CMT2300A_CUS_PKT18 0x49
#define CMT2300A_CUS_PKT19 0x4A
#define CMT2300A_CUS_PKT20 0x4B
#define CMT2300A_CUS_PKT21 0x4C
#define CMT2300A_CUS_PKT22 0x4D
#define CMT2300A_CUS_PKT23 0x4E
#define CMT2300A_CUS_PKT24 0x4F
#define CMT2300A_CUS_PKT25 0x50
#define CMT2300A_CUS_PKT26 0x51
#define CMT2300A_CUS_PKT27 0x52
#define CMT2300A_CUS_PKT28 0x53
#define CMT2300A_CUS_PKT29 0x54
/* ---------- Tx bank defines ---------- */
#define CMT2300A_TX_BANK_ADDR 0x55
#define CMT2300A_TX_BANK_SIZE 11
#define CMT2300A_CUS_TX1 0x55
#define CMT2300A_CUS_TX2 0x56
#define CMT2300A_CUS_TX3 0x57
#define CMT2300A_CUS_TX4 0x58
#define CMT2300A_CUS_TX5 0x59
#define CMT2300A_CUS_TX6 0x5A
#define CMT2300A_CUS_TX7 0x5B
#define CMT2300A_CUS_TX8 0x5C
#define CMT2300A_CUS_TX9 0x5D
#define CMT2300A_CUS_TX10 0x5E
#define CMT2300A_CUS_LBD 0x5F
/* ---------- Control1 bank defines ---------- */
#define CMT2300A_CONTROL1_BANK_ADDR 0x60
#define CMT2300A_CONTROL1_BANK_SIZE 11
#define CMT2300A_CUS_MODE_CTL 0x60
#define CMT2300A_CUS_MODE_STA 0x61
#define CMT2300A_CUS_EN_CTL 0x62
#define CMT2300A_CUS_FREQ_CHNL 0x63
#define CMT2300A_CUS_FREQ_OFS 0x64
#define CMT2300A_CUS_IO_SEL 0x65
#define CMT2300A_CUS_INT1_CTL 0x66
#define CMT2300A_CUS_INT2_CTL 0x67
#define CMT2300A_CUS_INT_EN 0x68
#define CMT2300A_CUS_FIFO_CTL 0x69
#define CMT2300A_CUS_INT_CLR1 0x6A
/* ---------- Control2 bank defines ---------- */
#define CMT2300A_CONTROL2_BANK_ADDR 0x6B
#define CMT2300A_CONTROL2_BANK_SIZE 7
#define CMT2300A_CUS_INT_CLR2 0x6B
#define CMT2300A_CUS_FIFO_CLR 0x6C
#define CMT2300A_CUS_INT_FLAG 0x6D
#define CMT2300A_CUS_FIFO_FLAG 0x6E
#define CMT2300A_CUS_RSSI_CODE 0x6F
#define CMT2300A_CUS_RSSI_DBM 0x70
#define CMT2300A_CUS_LBD_RESULT 0x71
/* ********** CMT2300A_CUS_CMT2 registers ********** */
#define CMT2300A_MASK_PRODUCT_ID 0xFF
/* ********** CMT2300A_CUS_CMT5 registers ********** */
#define CMT2300A_MASK_LMT_CODE 0xC0
/* ********** CMT2300A_CUS_CMT9 registers ********** */
#define CMT2300A_MASK_RSSI_OFFSET_SIGN 0x80
#define CMT2300A_MASK_DIG_CLKDIV 0x1F
/* ********** CMT2300A_CUS_RSSI registers ********** */
#define CMT2300A_MASK_RSSI_OFFSET 0xF8
#define CMT2300A_MASK_RSSI_SLOPE 0x07
/* ********** CMT2300A_CUS_SYS1 registers ********** */
#define CMT2300A_MASK_LMT_VTR 0xC0
#define CMT2300A_MASK_MIXER_BIAS 0x30
#define CMT2300A_MASK_LNA_MODE 0x0C
#define CMT2300A_MASK_LNA_BIAS 0x03
/* ********** CMT2300A_CUS_SYS2 registers ********** */
#define CMT2300A_MASK_LFOSC_RECAL_EN 0x80
#define CMT2300A_MASK_LFOSC_CAL1_EN 0x40
#define CMT2300A_MASK_LFOSC_CAL2_EN 0x20
#define CMT2300A_MASK_RX_TIMER_EN 0x10
#define CMT2300A_MASK_SLEEP_TIMER_EN 0x08
#define CMT2300A_MASK_TX_DC_EN 0x04
#define CMT2300A_MASK_RX_DC_EN 0x02
#define CMT2300A_MASK_DC_PAUSE 0x01
/* ********** CMT2300A_CUS_SYS3 registers ********** */
#define CMT2300A_MASK_SLEEP_BYPASS_EN 0x80
#define CMT2300A_MASK_XTAL_STB_TIME 0x70
#define CMT2300A_MASK_TX_EXIT_STATE 0x0C
#define CMT2300A_MASK_RX_EXIT_STATE 0x03
/* ********** CMT2300A_CUS_SYS4 registers ********** */
#define CMT2300A_MASK_SLEEP_TIMER_M_7_0 0xFF
/* ********** CMT2300A_CUS_SYS5 registers ********** */
#define CMT2300A_MASK_SLEEP_TIMER_M_10_8 0x70
#define CMT2300A_MASK_SLEEP_TIMER_R 0x0F
/* ********** CMT2300A_CUS_SYS6 registers ********** */
#define CMT2300A_MASK_RX_TIMER_T1_M_7_0 0xFF
/* ********** CMT2300A_CUS_SYS7 registers ********** */
#define CMT2300A_MASK_RX_TIMER_T1_M_10_8 0x70
#define CMT2300A_MASK_RX_TIMER_T1_R 0x0F
/* ********** CMT2300A_CUS_SYS8 registers ********** */
#define CMT2300A_MASK_RX_TIMER_T2_M_7_0 0xFF
/* ********** CMT2300A_CUS_SYS9 registers ********** */
#define CMT2300A_MASK_RX_TIMER_T2_M_10_8 0x70
#define CMT2300A_MASK_RX_TIMER_T2_R 0x0F
/* ********** CMT2300A_CUS_SYS10 registers ********** */
#define CMT2300A_MASK_COL_DET_EN 0x80
#define CMT2300A_MASK_COL_OFS_SEL 0x40
#define CMT2300A_MASK_RX_AUTO_EXIT_DIS 0x20
#define CMT2300A_MASK_DOUT_MUTE 0x10
#define CMT2300A_MASK_RX_EXTEND_MODE 0x0F
/* ********** CMT2300A_CUS_SYS11 registers ********** */
#define CMT2300A_MASK_PJD_TH_SEL 0x80
#define CMT2300A_MASK_CCA_INT_SEL 0x60
#define CMT2300A_MASK_RSSI_DET_SEL 0x18
#define CMT2300A_MASK_RSSI_AVG_MODE 0x07
/* ********** CMT2300A_CUS_SYS12 registers ********** */
#define CMT2300A_MASK_PJD_WIN_SEL 0xC0
#define CMT2300A_MASK_CLKOUT_EN 0x20
#define CMT2300A_MASK_CLKOUT_DIV 0x1F
/* ********** CMT2300A_CUS_RF1 registers ********** */
#define CMT2300A_MASK_FREQ_RX_N 0xFF
/* ********** CMT2300A_CUS_RF2 registers ********** */
#define CMT2300A_MASK_FREQ_RX_K_7_0 0xFF
/* ********** CMT2300A_CUS_RF3 registers ********** */
#define CMT2300A_MASK_FREQ_RX_K_15_8 0xFF
/* ********** CMT2300A_CUS_RF4 registers ********** */
#define CMT2300A_MASK_FREQ_PALDO_SEL 0x80
#define CMT2300A_MASK_FREQ_DIVX_CODE 0x70
#define CMT2300A_MASK_FREQ_RX_K_19_16 0x0F
/* ********** CMT2300A_CUS_RF5 registers ********** */
#define CMT2300A_MASK_FREQ_TX_N 0xFF
/* ********** CMT2300A_CUS_RF6 registers ********** */
#define CMT2300A_MASK_FREQ_TX_K_7_0 0xFF
/* ********** CMT2300A_CUS_RF7 registers ********** */
#define CMT2300A_MASK_FREQ_TX_K_15_8 0xFF
/* ********** CMT2300A_CUS_RF8 registers ********** */
#define CMT2300A_MASK_FSK_SWT 0x80
#define CMT2300A_MASK_FREQ_VCO_BANK 0x70
#define CMT2300A_MASK_FREQ_TX_K_19_16 0x0F
/* ********** CMT2300A_CUS_PKT1 registers ********** */
#define CMT2300A_MASK_RX_PREAM_SIZE 0xF8
#define CMT2300A_MASK_PREAM_LENG_UNIT 0x04
#define CMT2300A_MASK_DATA_MODE 0x03
/* CMT2300A_MASK_PREAM_LENG_UNIT options */
#define CMT2300A_PREAM_LENG_UNIT_8_BITS 0x00
#define CMT2300A_PREAM_LENG_UNIT_4_BITS 0x04
/* CMT2300A_MASK_DATA_MODE options */
#define CMT2300A_DATA_MODE_DIRECT 0x00
#define CMT2300A_DATA_MODE_PACKET 0x02
/* ********** CMT2300A_CUS_PKT2 registers ********** */
#define CMT2300A_MASK_TX_PREAM_SIZE_7_0 0xFF
/* ********** CMT2300A_CUS_PKT3 registers ********** */
#define CMT2300A_MASK_TX_PREAM_SIZE_15_8 0xFF
/* ********** CMT2300A_CUS_PKT4 registers ********** */
#define CMT2300A_MASK_PREAM_VALUE 0xFF
/* ********** CMT2300A_CUS_PKT5 registers ********** */
#define CMT2300A_MASK_SYNC_TOL 0x70
#define CMT2300A_MASK_SYNC_SIZE 0x0E
#define CMT2300A_MASK_SYNC_MAN_EN 0x01
/* ********** CMT2300A_CUS_PKT6 registers ********** */
#define CMT2300A_MASK_SYNC_VALUE_7_0 0xFF
/* ********** CMT2300A_CUS_PKT7 registers ********** */
#define CMT2300A_MASK_SYNC_VALUE_15_8 0xFF
/* ********** CMT2300A_CUS_PKT8 registers ********** */
#define CMT2300A_MASK_SYNC_VALUE_23_16 0xFF
/* ********** CMT2300A_CUS_PKT9 registers ********** */
#define CMT2300A_MASK_SYNC_VALUE_31_24 0xFF
/* ********** CMT2300A_CUS_PKT10 registers ********** */
#define CMT2300A_MASK_SYNC_VALUE_39_32 0xFF
/* ********** CMT2300A_CUS_PKT11 registers ********** */
#define CMT2300A_MASK_SYNC_VALUE_47_40 0xFF
/* ********** CMT2300A_CUS_PKT12 registers ********** */
#define CMT2300A_MASK_SYNC_VALUE_55_48 0xFF
/* ********** CMT2300A_CUS_PKT13 registers ********** */
#define CMT2300A_MASK_SYNC_VALUE_63_56 0xFF
/* ********** CMT2300A_CUS_PKT14 registers ********** */
#define CMT2300A_MASK_PAYLOAD_LENG_10_8 0x70
#define CMT2300A_MASK_AUTO_ACK_EN 0x08
#define CMT2300A_MASK_NODE_LENG_POS_SEL 0x04
#define CMT2300A_MASK_PAYLOAD_BIT_ORDER 0x02
#define CMT2300A_MASK_PKT_TYPE 0x01
/* CMT2300A_MASK_NODE_LENG_POS_SEL options */
#define CMT2300A_NODE_LENG_FIRST_NODE 0x00
#define CMT2300A_NODE_LENG_FIRST_LENGTH 0x04
/* CMT2300A_MASK_PAYLOAD_BIT_ORDER options */
#define CMT2300A_PAYLOAD_BIT_ORDER_MSB 0x00
#define CMT2300A_PAYLOAD_BIT_ORDER_LSB 0x02
/* CMT2300A_MASK_PKT_TYPE options */
#define CMT2300A_PKT_TYPE_FIXED 0x00
#define CMT2300A_PKT_TYPE_VARIABLE 0x01
/* ********** CMT2300A_CUS_PKT15 registers ********** */
#define CMT2300A_MASK_PAYLOAD_LENG_7_0 0xFF
/* ********** CMT2300A_CUS_PKT16 registers ********** */
#define CMT2300A_MASK_NODE_FREE_EN 0x20
#define CMT2300A_MASK_NODE_ERR_MASK 0x10
#define CMT2300A_MASK_NODE_SIZE 0x0C
#define CMT2300A_MASK_NODE_DET_MODE 0x03
/* CMT2300A_MASK_NODE_DET_MODE options */
#define CMT2300A_NODE_DET_NODE 0x00
#define CMT2300A_NODE_DET_VALUE 0x01
#define CMT2300A_NODE_DET_VALUE_0 0x02
#define CMT2300A_NODE_DET_VALUE_0_1 0x03
/* ********** CMT2300A_CUS_PKT17 registers ********** */
#define CMT2300A_MASK_NODE_VALUE_7_0 0xFF
/* ********** CMT2300A_CUS_PKT18 registers ********** */
#define CMT2300A_MASK_NODE_VALUE_15_8 0xFF
/* ********** CMT2300A_CUS_PKT19 registers ********** */
#define CMT2300A_MASK_NODE_VALUE_23_16 0xFF
/* ********** CMT2300A_CUS_PKT20 registers ********** */
#define CMT2300A_MASK_NODE_VALUE_31_24 0xFF
/* ********** CMT2300A_CUS_PKT21 registers ********** */
#define CMT2300A_MASK_FEC_TYPE 0x80
#define CMT2300A_MASK_FEC_EN 0x40
#define CMT2300A_MASK_CRC_BYTE_SWAP 0x20
#define CMT2300A_MASK_CRC_BIT_INV 0x10
#define CMT2300A_MASK_CRC_RANGE 0x08
#define CMT2300A_MASK_CRC_TYPE 0x06
#define CMT2300A_MASK_CRC_EN 0x01
/* CMT2300A_MASK_CRC_BYTE_SWAP options */
#define CMT2300A_CRC_ORDER_HBYTE 0x00
#define CMT2300A_CRC_ORDER_LBYTE 0x20
/* CMT2300A_MASK_CRC_RANGE options */
#define CMT2300A_CRC_RANGE_PAYLOAD 0x00
#define CMT2300A_CRC_RANGE_DATA 0x08
/* CMT2300A_MASK_CRC_TYPE options */
#define CMT2300A_CRC_TYPE_CCITT16 0x00
#define CMT2300A_CRC_TYPE_IBM16 0x02
#define CMT2300A_CRC_TYPE_ITuint16_t 0x04
/* ********** CMT2300A_CUS_PKT22 registers ********** */
#define CMT2300A_MASK_CRC_SEED_7_0 0xFF
/* ********** CMT2300A_CUS_PKT23 registers ********** */
#define CMT2300A_MASK_CRC_SEED_15_8 0xFF
/* ********** CMT2300A_CUS_PKT24 registers ********** */
#define CMT2300A_MASK_CRC_BIT_ORDER 0x80
#define CMT2300A_MASK_WHITEN_SEED_8_8 0x40
#define CMT2300A_MASK_WHITEN_SEED_TYPE 0x20
#define CMT2300A_MASK_WHITEN_TYPE 0x18
#define CMT2300A_MASK_WHITEN_EN 0x04
#define CMT2300A_MASK_MANCH_TYPE 0x02
#define CMT2300A_MASK_MANCH_EN 0x01
/* CMT2300A_MASK_CRC_BIT_ORDER options */
#define CMT2300A_CRC_BIT_ORDER_MSB 0x00
#define CMT2300A_CRC_BIT_ORDER_LSB 0x80
/* CMT2300A_MASK_WHITEN_SEED_TYPE options */
#define CMT2300A_WHITEN_SEED_TYPE_1 0x00
#define CMT2300A_WHITEN_SEED_TYPE_2 0x20
/* CMT2300A_MASK_WHITEN_TYPE options */
#define CMT2300A_WHITEN_TYPE_PN9_CCITT 0x00
#define CMT2300A_WHITEN_TYPE_PN9_IBM 0x08
#define CMT2300A_WHITEN_TYPE_PN7 0x10
/* CMT2300A_MASK_MANCH_TYPE options */
#define CMT2300A_MANCH_TYPE_ONE_01 0x00
#define CMT2300A_MANCH_TYPE_ONE_10 0x02
/* ********** CMT2300A_CUS_PKT25 registers ********** */
#define CMT2300A_MASK_WHITEN_SEED_7_0 0xFF
/* ********** CMT2300A_CUS_PKT26 registers ********** */
#define CMT2300A_MASK_TX_PREFIX_TYPE 0x03
/* ********** CMT2300A_CUS_PKT27 registers ********** */
#define CMT2300A_MASK_TX_PKT_NUM 0xFF
/* ********** CMT2300A_CUS_PKT28 registers ********** */
#define CMT2300A_MASK_TX_PKT_GAP 0xFF
/* ********** CMT2300A_CUS_PKT29 registers ********** */
#define CMT2300A_MASK_FIFO_AUTO_RES_EN 0x80
#define CMT2300A_MASK_FIFO_TH 0x7F
/* ********** CMT2300A_CUS_MODE_CTL registers ********** */
#define CMT2300A_MASK_CHIP_MODE_SWT 0xFF
/* CMT2300A_MASK_CHIP_MODE_SWT options */
#define CMT2300A_GO_EEPROM 0x01
#define CMT2300A_GO_STBY 0x02
#define CMT2300A_GO_RFS 0x04
#define CMT2300A_GO_RX 0x08
#define CMT2300A_GO_SLEEP 0x10
#define CMT2300A_GO_TFS 0x20
#define CMT2300A_GO_TX 0x40
#define CMT2300A_GO_SWITCH 0x80
/* ********** CMT2300A_CUS_MODE_STA registers ********** */
#define CMT2300A_MASK_RSTN_IN_EN 0x20
#define CMT2300A_MASK_CFG_RETAIN 0x10
#define CMT2300A_MASK_CHIP_MODE_STA 0x0F
/* CMT2300A_MASK_CHIP_MODE_STA options */
#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
/* ********** CMT2300A_CUS_EN_CTL registers ********** */
#define CMT2300A_MASK_LOCKING_EN 0x20
/* ********** CMT2300A_CUS_FREQ_CHNL registers ********** */
#define CMT2300A_MASK_FH_CHANNEL 0xFF
/* ********** CMT2300A_CUS_FREQ_OFS registers ********** */
#define CMT2300A_MASK_FH_OFFSET 0xFF
/* ********** CMT2300A_CUS_IO_SEL registers ********** */
#define CMT2300A_MASK_GPIO4_SEL 0xC0
#define CMT2300A_MASK_GPIO3_SEL 0x30
#define CMT2300A_MASK_GPIO2_SEL 0x0C
#define CMT2300A_MASK_GPIO1_SEL 0x03
/* CMT2300A_MASK_GPIO4_SEL options */
#define CMT2300A_GPIO4_SEL_RSTIN 0x00
#define CMT2300A_GPIO4_SEL_INT1 0x40
#define CMT2300A_GPIO4_SEL_DOUT 0x80
#define CMT2300A_GPIO4_SEL_DCLK 0xC0
/* CMT2300A_MASK_GPIO3_SEL options */
#define CMT2300A_GPIO3_SEL_CLKO 0x00
#define CMT2300A_GPIO3_SEL_DOUT 0x10
#define CMT2300A_GPIO3_SEL_DIN 0x10
#define CMT2300A_GPIO3_SEL_INT2 0x20
#define CMT2300A_GPIO3_SEL_DCLK 0x30
/* CMT2300A_MASK_GPIO2_SEL options */
#define CMT2300A_GPIO2_SEL_INT1 0x00
#define CMT2300A_GPIO2_SEL_INT2 0x04
#define CMT2300A_GPIO2_SEL_DOUT 0x08
#define CMT2300A_GPIO2_SEL_DIN 0x08
#define CMT2300A_GPIO2_SEL_DCLK 0x0C
/* CMT2300A_MASK_GPIO1_SEL options */
#define CMT2300A_GPIO1_SEL_DOUT 0x00
#define CMT2300A_GPIO1_SEL_DIN 0x00
#define CMT2300A_GPIO1_SEL_INT1 0x01
#define CMT2300A_GPIO1_SEL_INT2 0x02
#define CMT2300A_GPIO1_SEL_DCLK 0x03
/* ********** CMT2300A_CUS_INT1_CTL registers ********** */
#define CMT2300A_MASK_RF_SWT1_EN 0x80
#define CMT2300A_MASK_RF_SWT2_EN 0x40
#define CMT2300A_MASK_INT_POLAR 0x20
#define CMT2300A_MASK_INT1_SEL 0x1F
/* CMT2300A_MASK_INT_POLAR options */
#define CMT2300A_INT_POLAR_SEL_0 0x00
#define CMT2300A_INT_POLAR_SEL_1 0x20
/* CMT2300A_MASK_INT1_SEL options */
#define CMT2300A_INT_SEL_RX_ACTIVE 0x00
#define CMT2300A_INT_SEL_TX_ACTIVE 0x01
#define CMT2300A_INT_SEL_RSSI_VLD 0x02
#define CMT2300A_INT_SEL_PREAM_OK 0x03
#define CMT2300A_INT_SEL_SYNC_OK 0x04
#define CMT2300A_INT_SEL_NODE_OK 0x05
#define CMT2300A_INT_SEL_CRC_OK 0x06
#define CMT2300A_INT_SEL_PKT_OK 0x07
#define CMT2300A_INT_SEL_SL_TMO 0x08
#define CMT2300A_INT_SEL_RX_TMO 0x09
#define CMT2300A_INT_SEL_TX_DONE 0x0A
#define CMT2300A_INT_SEL_RX_FIFO_NMTY 0x0B
#define CMT2300A_INT_SEL_RX_FIFO_TH 0x0C
#define CMT2300A_INT_SEL_RX_FIFO_FULL 0x0D
#define CMT2300A_INT_SEL_RX_FIFO_WBYTE 0x0E
#define CMT2300A_INT_SEL_RX_FIFO_OVF 0x0F
#define CMT2300A_INT_SEL_TX_FIFO_NMTY 0x10
#define CMT2300A_INT_SEL_TX_FIFO_TH 0x11
#define CMT2300A_INT_SEL_TX_FIFO_FULL 0x12
#define CMT2300A_INT_SEL_STATE_IS_STBY 0x13
#define CMT2300A_INT_SEL_STATE_IS_FS 0x14
#define CMT2300A_INT_SEL_STATE_IS_RX 0x15
#define CMT2300A_INT_SEL_STATE_IS_TX 0x16
#define CMT2300A_INT_SEL_LED 0x17
#define CMT2300A_INT_SEL_TRX_ACTIVE 0x18
#define CMT2300A_INT_SEL_PKT_DONE 0x19
/* ********** CMT2300A_CUS_INT2_CTL registers ********** */
#define CMT2300A_MASK_LFOSC_OUT_EN 0x40
#define CMT2300A_MASK_TX_DIN_INV 0x20
#define CMT2300A_MASK_INT2_SEL 0x1F
/* ********** CMT2300A_CUS_INT_EN registers ********** */
#define CMT2300A_MASK_SL_TMO_EN 0x80
#define CMT2300A_MASK_RX_TMO_EN 0x40
#define CMT2300A_MASK_TX_DONE_EN 0x20
#define CMT2300A_MASK_PREAM_OK_EN 0x10
#define CMT2300A_MASK_SYNC_OK_EN 0x08
#define CMT2300A_MASK_NODE_OK_EN 0x04
#define CMT2300A_MASK_CRC_OK_EN 0x02
#define CMT2300A_MASK_PKT_DONE_EN 0x01
/* ********** CMT2300A_CUS_FIFO_CTL registers ********** */
#define CMT2300A_MASK_TX_DIN_EN 0x80
#define CMT2300A_MASK_TX_DIN_SEL 0x60
#define CMT2300A_MASK_FIFO_AUTO_CLR_DIS 0x10
#define CMT2300A_MASK_FIFO_TX_RD_EN 0x08
#define CMT2300A_MASK_FIFO_RX_TX_SEL 0x04
#define CMT2300A_MASK_FIFO_MERGE_EN 0x02
#define CMT2300A_MASK_SPI_FIFO_RD_WR_SEL 0x01
/* CMT2300A_MASK_TX_DIN_SEL options */
#define CMT2300A_TX_DIN_SEL_GPIO1 0x00
#define CMT2300A_TX_DIN_SEL_GPIO2 0x20
#define CMT2300A_TX_DIN_SEL_GPIO3 0x40
/* ********** CMT2300A_CUS_INT_CLR1 registers ********** */
#define CMT2300A_MASK_SL_TMO_FLG 0x20
#define CMT2300A_MASK_RX_TMO_FLG 0x10
#define CMT2300A_MASK_TX_DONE_FLG 0x08
#define CMT2300A_MASK_TX_DONE_CLR 0x04
#define CMT2300A_MASK_SL_TMO_CLR 0x02
#define CMT2300A_MASK_RX_TMO_CLR 0x01
/* ********** CMT2300A_CUS_INT_CLR2 registers ********** */
#define CMT2300A_MASK_LBD_CLR 0x20
#define CMT2300A_MASK_PREAM_OK_CLR 0x10
#define CMT2300A_MASK_SYNC_OK_CLR 0x08
#define CMT2300A_MASK_NODE_OK_CLR 0x04
#define CMT2300A_MASK_CRC_OK_CLR 0x02
#define CMT2300A_MASK_PKT_DONE_CLR 0x01
/* ********** CMT2300A_CUS_FIFO_CLR registers ********** */
#define CMT2300A_MASK_FIFO_RESTORE 0x04
#define CMT2300A_MASK_FIFO_CLR_RX 0x02
#define CMT2300A_MASK_FIFO_CLR_TX 0x01
/* ********** CMT2300A_CUS_INT_FLAG registers ********** */
#define CMT2300A_MASK_LBD_FLG 0x80
#define CMT2300A_MASK_COL_ERR_FLG 0x40
#define CMT2300A_MASK_PKT_ERR_FLG 0x20
#define CMT2300A_MASK_PREAM_OK_FLG 0x10
#define CMT2300A_MASK_SYNC_OK_FLG 0x08
#define CMT2300A_MASK_NODE_OK_FLG 0x04
#define CMT2300A_MASK_CRC_OK_FLG 0x02
#define CMT2300A_MASK_PKT_OK_FLG 0x01
/* ********** CMT2300A_CUS_FIFO_FLAG registers ********** */
#define CMT2300A_MASK_RX_FIFO_FULL_FLG 0x40
#define CMT2300A_MASK_RX_FIFO_NMTY_FLG 0x20
#define CMT2300A_MASK_RX_FIFO_TH_FLG 0x10
#define CMT2300A_MASK_RX_FIFO_OVF_FLG 0x08
#define CMT2300A_MASK_TX_FIFO_FULL_FLG 0x04
#define CMT2300A_MASK_TX_FIFO_NMTY_FLG 0x02
#define CMT2300A_MASK_TX_FIFO_TH_FLG 0x01
/* ********** CMT2300A_CUS_RSSI_CODE registers ********** */
#define CMT2300A_MASK_RSSI_CODE 0xFF
/* ********** CMT2300A_CUS_RSSI_DBM registers ********** */
#define CMT2300A_MASK_RSSI_DBM 0xFF
/* ********** CMT2300A_CUS_LBD_RESULT registers ********** */
#define CMT2300A_MASK_LBD_RESULT 0xFF
#endif

View File

@ -0,0 +1,76 @@
/*
* THE FOLLOWING FIRMWARE IS PROVIDED: (1) "AS IS" WITH NO WARRANTY; AND
* (2)TO ENABLE ACCESS TO CODING INFORMATION TO GUIDE AND FACILITATE CUSTOMER.
* CONSEQUENTLY, CMOSTEK SHALL NOT BE HELD LIABLE FOR ANY DIRECT, INDIRECT OR
* CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE CONTENT
* OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING INFORMATION
* CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
*
* Copyright (C) CMOSTEK SZ.
*/
/*!
* @file cmt2300a_hal.c
* @brief CMT2300A hardware abstraction layer
*
* @version 1.2
* @date Jul 17 2017
* @author CMOSTEK R@D
*/
#include "cmt2300a_hal.h"
#include "cmt_spi3.h"
#include <Arduino.h>
/*! ********************************************************
* @name CMT2300A_InitSpi
* @desc Initializes the CMT2300A SPI interface.
* *********************************************************/
void CMT2300A_InitSpi(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8_t pin_fcs, uint32_t spi_speed)
{
cmt_spi3_init(pin_sdio, pin_clk, pin_cs, pin_fcs, spi_speed);
}
/*! ********************************************************
* @name CMT2300A_ReadReg
* @desc Read the CMT2300A register at the specified address.
* @param addr: register address
* @return Register value
* *********************************************************/
uint8_t CMT2300A_ReadReg(uint8_t addr)
{
return cmt_spi3_read(addr);
}
/*! ********************************************************
* @name CMT2300A_WriteReg
* @desc Write the CMT2300A register at the specified address.
* @param addr: register address
* dat: register value
* *********************************************************/
void CMT2300A_WriteReg(uint8_t addr, uint8_t dat)
{
cmt_spi3_write(addr, dat);
}
/*! ********************************************************
* @name CMT2300A_ReadFifo
* @desc Reads the contents of the CMT2300A FIFO.
* @param buf: buffer where to copy the FIFO read data
* len: number of bytes to be read from the FIFO
* *********************************************************/
void CMT2300A_ReadFifo(uint8_t buf[], uint16_t len)
{
cmt_spi3_read_fifo(buf, len);
}
/*! ********************************************************
* @name CMT2300A_WriteFifo
* @desc Writes the buffer contents to the CMT2300A FIFO.
* @param buf: buffer containing data to be put on the FIFO
* len: number of bytes to be written to the FIFO
* *********************************************************/
void CMT2300A_WriteFifo(const uint8_t buf[], uint16_t len)
{
cmt_spi3_write_fifo(buf, len);
}

View File

@ -0,0 +1,51 @@
/*
* THE FOLLOWING FIRMWARE IS PROVIDED: (1) "AS IS" WITH NO WARRANTY; AND
* (2)TO ENABLE ACCESS TO CODING INFORMATION TO GUIDE AND FACILITATE CUSTOMER.
* CONSEQUENTLY, CMOSTEK SHALL NOT BE HELD LIABLE FOR ANY DIRECT, INDIRECT OR
* CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE CONTENT
* OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING INFORMATION
* CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
*
* Copyright (C) CMOSTEK SZ.
*/
/*!
* @file cmt2300a_hal.h
* @brief CMT2300A hardware abstraction layer
*
* @version 1.2
* @date Jul 17 2017
* @author CMOSTEK R@D
*/
#ifndef __CMT2300A_HAL_H
#define __CMT2300A_HAL_H
#include <stdint.h>
#include <Arduino.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ************************************************************************
* The following need to be modified by user
* ************************************************************************ */
#define CMT2300A_DelayMs(ms) delay(ms)
#define CMT2300A_DelayUs(us) delayMicroseconds(us)
#define CMT2300A_GetTickCount() millis()
/* ************************************************************************ */
void CMT2300A_InitSpi(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8_t pin_fcs, uint32_t spi_speed);
uint8_t CMT2300A_ReadReg(uint8_t addr);
void CMT2300A_WriteReg(uint8_t addr, uint8_t dat);
void CMT2300A_ReadFifo(uint8_t buf[], uint16_t len);
void CMT2300A_WriteFifo(const uint8_t buf[], uint16_t len);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,214 @@
/*
;---------------------------------------
; CMT2300A Configuration File
; Generated by CMOSTEK RFPDK 1.46
; 2023.03.17 23:16
;---------------------------------------
; Mode = Advanced
; Part Number = CMT2300A
; Frequency = 860.000 MHz
; Xtal Frequency = 26.0000 MHz
; Demodulation = GFSK
; AGC = On
; Data Rate = 20.0 kbps
; Deviation = 20.0 kHz
; Tx Xtal Tol. = 20 ppm
; Rx Xtal Tol. = 20 ppm
; TRx Matching Network Type = 20 dBm
; Tx Power = +13 dBm
; Gaussian BT = 0.5
; Bandwidth = Auto-Select kHz
; CDR Type = Counting
; CDR DR Range = NA
; AFC = On
; AFC Method = Auto-Select
; Data Representation = 0:F-low 1:F-high
; Rx Duty-Cycle = Off
; Tx Duty-Cycle = Off
; Sleep Timer = Off
; Sleep Time = NA
; Rx Timer = Off
; Rx Time T1 = NA
; Rx Time T2 = NA
; Rx Exit State = STBY
; Tx Exit State = STBY
; SLP Mode = Disable
; RSSI Valid Source = PJD
; PJD Window = 8 Jumps
; LFOSC Calibration = On
; Xtal Stable Time = 155 us
; RSSI Compare TH = NA
; Data Mode = Packet
; Whitening = Disable
; Whiten Type = NA
; Whiten Seed Type = NA
; Whiten Seed = NA
; Manchester = Disable
; Manchester Type = NA
; FEC = Enable
; FEC Type = x^3+x^2+1
; Tx Prefix Type = 0
; Tx Packet Number = 1
; Tx Packet Gap = 32
; Packet Type = Variable Length
; Node-Length Position = First Node, then Length
; Payload Bit Order = Start from msb
; Preamble Rx Size = 2
; Preamble Tx Size = 30
; Preamble Value = 170
; Preamble Unit = 8-bit
; Sync Size = 4-byte
; Sync Value = 1296587336
; Sync Tolerance = None
; Sync Manchester = Disable
; Node ID Size = NA
; Node ID Value = NA
; Node ID Mode = None
; Node ID Err Mask = Disable
; Node ID Free = Disable
; Payload Length = 32
; CRC Options = IBM-16
; CRC Seed = 0 crc_seed
; CRC Range = Entire Payload
; CRC Swap = Start from MSB
; CRC Bit Invert = Normal
; CRC Bit Order = Start from bit 15
; Dout Mute = Off
; Dout Adjust Mode = Disable
; Dout Adjust Percentage = NA
; Collision Detect = Off
; Collision Detect Offset = NA
; RSSI Detect Mode = At PREAM_OK
; RSSI Filter Setting = 32-tap
; RF Performance = High
; LBD Threshold = 2.4 V
; RSSI Offset = 0
; RSSI Offset Sign = 0
*/
#ifndef __CMT2300A_PARAMS_H
#define __CMT2300A_PARAMS_H
#include "cmt2300a_defs.h"
#include <stdint.h>
/* [CMT Bank] with RSSI offset of +- 0 (and Tx power double bit not set) */
static uint8_t g_cmt2300aCmtBank[CMT2300A_CMT_BANK_SIZE] = {
0x00,
0x66,
0xEC,
0x1C,
0x70,
0x80,
0x14,
0x08,
0x11,
0x02,
0x02,
0x00,
};
/* [System Bank] */
static uint8_t g_cmt2300aSystemBank[CMT2300A_SYSTEM_BANK_SIZE] = {
0xAE,
0xE0,
0x35,
0x00,
0x00,
0xF4,
0x10,
0xE2,
0x42,
0x20,
0x0C,
0x81,
};
/* [Frequency Bank] 860 MHz */
static uint8_t g_cmt2300aFrequencyBank[CMT2300A_FREQUENCY_BANK_SIZE] = {
0x42,
0x32,
0xCF,
0x82,
0x42,
0x27,
0x76,
0x12,
};
/* [Data Rate Bank] */
static uint8_t g_cmt2300aDataRateBank[CMT2300A_DATA_RATE_BANK_SIZE] = {
0xA6,
0xC9,
0x20,
0x20,
0xD2,
0x35,
0x0C,
0x0A,
0x9F,
0x4B,
0x29,
0x29,
0xC0,
0x14,
0x05,
0x53,
0x10,
0x00,
0xB4,
0x00,
0x00,
0x01,
0x00,
0x00,
};
/* [Baseband Bank] - EU */
static uint8_t g_cmt2300aBasebandBank[CMT2300A_BASEBAND_BANK_SIZE] = {
0x12,
0x1E,
0x00,
0xAA,
0x06,
0x00,
0x00,
0x00,
0x00,
0x48,
0x5A,
0x48,
0x4D,
0x01,
0x1F,
0x00,
0x00,
0x00,
0x00,
0x00,
0xC3,
0x00,
0x00,
0x60,
0xFF,
0x00,
0x00,
0x1F,
0x10,
};
/* [Tx Bank] 13 dBm */
static uint8_t g_cmt2300aTxBank[CMT2300A_TX_BANK_SIZE] = {
0x70,
0x4D,
0x06,
0x00,
0x07,
0x50,
0x00,
0x53,
0x09,
0x3F,
0x7F,
};
#endif

View File

@ -0,0 +1,301 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Thomas Basler and others
*/
#include "cmt2300wrapper.h"
#include "cmt2300a.h"
#include "cmt2300a_params.h"
CMT2300A::CMT2300A(uint8_t pin_sdio, uint8_t pin_clk, uint8_t pin_cs, uint8_t pin_fcs, uint32_t spi_speed)
{
_pin_sdio = pin_sdio;
_pin_clk = pin_clk;
_pin_cs = pin_cs;
_pin_fcs = pin_fcs;
_spi_speed = spi_speed;
}
bool CMT2300A::begin(void)
{
return _init_pins() && _init_radio();
}
bool CMT2300A::isChipConnected()
{
return CMT2300A_IsExist();
}
bool CMT2300A::startListening(void)
{
CMT2300A_GoStby();
CMT2300A_ClearInterruptFlags();
/* Must clear FIFO after enable SPI to read or write the FIFO */
CMT2300A_EnableReadFifo();
CMT2300A_ClearRxFifo();
if (!CMT2300A_GoRx()) {
return false;
} else {
return true;
}
}
bool CMT2300A::stopListening(void)
{
CMT2300A_ClearInterruptFlags();
return CMT2300A_GoSleep();
}
bool CMT2300A::available(void)
{
return (
CMT2300A_MASK_PREAM_OK_FLG |
CMT2300A_MASK_SYNC_OK_FLG |
CMT2300A_MASK_CRC_OK_FLG |
CMT2300A_MASK_PKT_OK_FLG
) & CMT2300A_ReadReg(CMT2300A_CUS_INT_FLAG);
}
void CMT2300A::read(void* buf, uint8_t len)
{
// Fetch the payload
CMT2300A_ReadFifo(static_cast<uint8_t*>(buf), len);
CMT2300A_ClearInterruptFlags();
}
bool CMT2300A::write(const uint8_t* buf, uint8_t len)
{
CMT2300A_GoStby();
CMT2300A_ClearInterruptFlags();
/* Must clear FIFO after enable SPI to read or write the FIFO */
CMT2300A_EnableWriteFifo();
CMT2300A_ClearTxFifo();
CMT2300A_WriteReg(CMT2300A_CUS_PKT15, len); // set Tx length
/* The length need be smaller than 32 */
CMT2300A_WriteFifo(buf, len);
if (!(CMT2300A_ReadReg(CMT2300A_CUS_FIFO_FLAG) & CMT2300A_MASK_TX_FIFO_NMTY_FLG)) {
return false;
}
if (!CMT2300A_GoTx()) {
return false;
}
uint32_t timer = millis();
while (!(CMT2300A_MASK_TX_DONE_FLG & CMT2300A_ReadReg(CMT2300A_CUS_INT_CLR1))) {
if (millis() - timer > 95) {
return false;
}
}
CMT2300A_ClearInterruptFlags();
CMT2300A_GoSleep();
return true;
}
void CMT2300A::setChannel(uint8_t channel)
{
CMT2300A_SetFrequencyChannel(channel);
}
uint8_t CMT2300A::getChannel(void)
{
return CMT2300A_ReadReg(CMT2300A_CUS_FREQ_CHNL);
}
uint8_t CMT2300A::getDynamicPayloadSize(void)
{
uint8_t result;
CMT2300A_ReadFifo(&result, 1); // first byte in FiFo is length
return result;
}
int CMT2300A::getRssiDBm()
{
return CMT2300A_GetRssiDBm();
}
bool CMT2300A::setPALevel(int8_t level)
{
uint16_t Tx_dBm_word;
switch (level) {
// for TRx Matching Network Type: 20 dBm
case -10:
Tx_dBm_word = 0x0501;
break;
case -9:
Tx_dBm_word = 0x0601;
break;
case -8:
Tx_dBm_word = 0x0701;
break;
case -7:
Tx_dBm_word = 0x0801;
break;
case -6:
Tx_dBm_word = 0x0901;
break;
case -5:
Tx_dBm_word = 0x0A01;
break;
case -4:
Tx_dBm_word = 0x0B01;
break;
case -3:
Tx_dBm_word = 0x0C01;
break;
case -2:
Tx_dBm_word = 0x0D01;
break;
case -1:
Tx_dBm_word = 0x0E01;
break;
case 0:
Tx_dBm_word = 0x1002;
break;
case 1:
Tx_dBm_word = 0x1302;
break;
case 2:
Tx_dBm_word = 0x1602;
break;
case 3:
Tx_dBm_word = 0x1902;
break;
case 4:
Tx_dBm_word = 0x1C02;
break;
case 5:
Tx_dBm_word = 0x1F03;
break;
case 6:
Tx_dBm_word = 0x2403;
break;
case 7:
Tx_dBm_word = 0x2804;
break;
case 8:
Tx_dBm_word = 0x2D04;
break;
case 9:
Tx_dBm_word = 0x3305;
break;
case 10:
Tx_dBm_word = 0x3906;
break;
case 11:
Tx_dBm_word = 0x4107;
break;
case 12:
Tx_dBm_word = 0x4908;
break;
case 13:
Tx_dBm_word = 0x5309;
break;
case 14:
Tx_dBm_word = 0x5E0B;
break;
case 15:
Tx_dBm_word = 0x6C0C;
break;
case 16:
Tx_dBm_word = 0x7D0C;
break;
// the following values require the double bit:
case 17:
Tx_dBm_word = 0x4A0C;
break;
case 18:
Tx_dBm_word = 0x580F;
break;
case 19:
Tx_dBm_word = 0x6B12;
break;
case 20:
Tx_dBm_word = 0x8A18;
break;
default:
return false;
}
if (level > 16) { // set bit for double Tx value
CMT2300A_WriteReg(CMT2300A_CUS_CMT4, CMT2300A_ReadReg(CMT2300A_CUS_CMT4) | 0x01); // set bit0
} else {
CMT2300A_WriteReg(CMT2300A_CUS_CMT4, CMT2300A_ReadReg(CMT2300A_CUS_CMT4) & 0xFE); // reset bit0
}
CMT2300A_WriteReg(CMT2300A_CUS_TX8, Tx_dBm_word >> 8);
CMT2300A_WriteReg(CMT2300A_CUS_TX9, Tx_dBm_word & 0xFF);
return true;
}
bool CMT2300A::rxFifoAvailable()
{
return (
CMT2300A_MASK_PKT_OK_FLG
) & CMT2300A_ReadReg(CMT2300A_CUS_INT_FLAG);
}
void CMT2300A::flush_rx(void)
{
CMT2300A_ClearRxFifo();
}
bool CMT2300A::_init_pins()
{
CMT2300A_InitSpi(_pin_sdio, _pin_clk, _pin_cs, _pin_fcs, _spi_speed);
return true; // assuming pins are connected properly
}
bool CMT2300A::_init_radio()
{
if (!CMT2300A_Init()) {
return false;
}
/* config registers */
CMT2300A_ConfigRegBank(CMT2300A_CMT_BANK_ADDR, g_cmt2300aCmtBank, CMT2300A_CMT_BANK_SIZE);
CMT2300A_ConfigRegBank(CMT2300A_SYSTEM_BANK_ADDR, g_cmt2300aSystemBank, CMT2300A_SYSTEM_BANK_SIZE);
CMT2300A_ConfigRegBank(CMT2300A_FREQUENCY_BANK_ADDR, g_cmt2300aFrequencyBank, CMT2300A_FREQUENCY_BANK_SIZE);
CMT2300A_ConfigRegBank(CMT2300A_DATA_RATE_BANK_ADDR, g_cmt2300aDataRateBank, CMT2300A_DATA_RATE_BANK_SIZE);
CMT2300A_ConfigRegBank(CMT2300A_BASEBAND_BANK_ADDR, g_cmt2300aBasebandBank, CMT2300A_BASEBAND_BANK_SIZE);
CMT2300A_ConfigRegBank(CMT2300A_TX_BANK_ADDR, g_cmt2300aTxBank, CMT2300A_TX_BANK_SIZE);
// xosc_aac_code[2:0] = 2
uint8_t tmp;
tmp = (~0x07) & CMT2300A_ReadReg(CMT2300A_CUS_CMT10);
CMT2300A_WriteReg(CMT2300A_CUS_CMT10, tmp | 0x02);
/* Config GPIOs */
CMT2300A_ConfigGpio(
CMT2300A_GPIO2_SEL_INT1 | CMT2300A_GPIO3_SEL_INT2);
/* Config interrupt */
CMT2300A_ConfigInterrupt(
CMT2300A_INT_SEL_TX_DONE, /* Config INT1 */
CMT2300A_INT_SEL_PKT_OK /* Config INT2 */
);
/* Enable interrupt */
CMT2300A_EnableInterrupt(
CMT2300A_MASK_TX_DONE_EN | CMT2300A_MASK_PREAM_OK_EN | CMT2300A_MASK_SYNC_OK_EN | CMT2300A_MASK_CRC_OK_EN | CMT2300A_MASK_PKT_DONE_EN);
CMT2300A_SetFrequencyStep(FH_OFFSET); // set FH_OFFSET (frequency = base freq + 2.5kHz*FH_OFFSET*FH_CHANNEL)
/* Use a single 64-byte FIFO for either Tx or Rx */
CMT2300A_EnableFifoMerge(true);
/* Go to sleep for configuration to take effect */
if (!CMT2300A_GoSleep()) {
return false; // CMT2300A not switched to sleep mode!
}
return true;
}

View File

@ -0,0 +1,112 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <stdint.h>
#define CMT2300A_ONE_STEP_SIZE 2500 // frequency channel step size for fast frequency hopping operation: One step size is 2.5 kHz.
#define CMT_BASE_FREQ 860000000 // from Frequency Bank in cmt2300a_params.h
#define FH_OFFSET 100 // value * CMT2300A_ONE_STEP_SIZE = channel frequency offset
#define CMT_SPI_SPEED 4000000 // 4 MHz
class CMT2300A {
public:
CMT2300A(uint8_t pin_sdio, uint8_t pin_clk, uint8_t pin_cs, uint8_t pin_fcs, uint32_t _spi_speed = CMT_SPI_SPEED);
bool begin(void);
/**
* Checks if the chip is connected to the SPI bus
*/
bool isChipConnected();
bool startListening(void);
bool stopListening(void);
/**
* Check whether there are bytes available to be read
* @code
* if(radio.available()){
* radio.read(&data,sizeof(data));
* }
* @endcode
*
* @see available(uint8_t*)
*
* @return True if there is a payload available, false if none is
*/
bool available(void);
/**
* Read payload data from the RX FIFO buffer(s).
*
* The length of data read is usually the next available payload's length
* @see
* - getDynamicPayloadSize()
*
* @note I specifically chose `void*` as a data type to make it easier
* for beginners to use. No casting needed.
*
* @param buf Pointer to a buffer where the data should be written
* @param len Maximum number of bytes to read into the buffer. This
* value should match the length of the object referenced using the
* `buf` parameter. The absolute maximum number of bytes that can be read
* in one call is 32 (for dynamic payload lengths) or whatever number was
* previously passed to setPayloadSize() (for static payload lengths).
*/
void read(void* buf, uint8_t len);
bool write(const uint8_t* buf, uint8_t len);
/**
* Set RF communication channel. The frequency used by a channel is
* @param channel Which RF channel to communicate on, 0-254
*/
void setChannel(uint8_t channel);
/**
* Get RF communication channel
* @return The currently configured RF Channel
*/
uint8_t getChannel(void);
/**
* Get Dynamic Payload Size
*
* For dynamic payloads, this pulls the size of the payload off
* the chip
*
* @return Payload length of last-received dynamic payload
*/
uint8_t getDynamicPayloadSize(void);
int getRssiDBm();
bool setPALevel(int8_t level);
bool rxFifoAvailable();
/**
* Empty the RX (receive) FIFO buffers.
*/
void flush_rx(void);
private:
/**
* initialize the GPIO pins
*/
bool _init_pins();
/**
* initialize radio.
* @warning This function assumes the SPI bus object's begin() method has been
* previously called.
*/
bool _init_radio();
int8_t _pin_sdio;
int8_t _pin_clk;
int8_t _pin_cs;
int8_t _pin_fcs;
uint32_t _spi_speed;
};

118
lib/CMT2300a/cmt_spi3.c Normal file
View File

@ -0,0 +1,118 @@
#include "cmt_spi3.h"
#include <Arduino.h>
#include <driver/spi_master.h>
#include <esp_rom_gpio.h> // for esp_rom_gpio_connect_out_signal
spi_device_handle_t spi_reg, spi_fifo;
void cmt_spi3_init(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8_t pin_fcs, uint32_t spi_speed)
{
spi_bus_config_t buscfg = {
.mosi_io_num = pin_sdio,
.miso_io_num = -1, // single wire MOSI/MISO
.sclk_io_num = pin_clk,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 32,
};
spi_device_interface_config_t devcfg = {
.command_bits = 0,
.address_bits = 0,
.dummy_bits = 0,
.mode = 0, // SPI mode 0
.clock_speed_hz = spi_speed,
.spics_io_num = pin_cs,
.flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_3WIRE,
.queue_size = 1,
.pre_cb = NULL,
.post_cb = NULL,
};
ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, 0));
ESP_ERROR_CHECK(spi_bus_add_device(SPI2_HOST, &devcfg, &spi_reg));
// FiFo
spi_device_interface_config_t devcfg2 = {
.command_bits = 0,
.address_bits = 0,
.dummy_bits = 0,
.mode = 0, // SPI mode 0
.cs_ena_pretrans = 2,
.cs_ena_posttrans = (uint8_t)(1 / (spi_speed * 10e6 * 2) + 2), // >2 us
.clock_speed_hz = spi_speed,
.spics_io_num = pin_fcs,
.flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_3WIRE,
.queue_size = 1,
.pre_cb = NULL,
.post_cb = NULL,
};
ESP_ERROR_CHECK(spi_bus_add_device(SPI2_HOST, &devcfg2, &spi_fifo));
esp_rom_gpio_connect_out_signal(pin_sdio, spi_periph_signal[SPI2_HOST].spid_out, true, false);
delay(100);
}
void cmt_spi3_write(uint8_t addr, uint8_t dat)
{
uint8_t tx_data[2];
tx_data[0] = ~addr;
tx_data[1] = ~dat;
spi_transaction_t t = {
.length = 2 * 8,
.tx_buffer = &tx_data,
.rx_buffer = NULL
};
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_reg, &t));
delayMicroseconds(100);
}
uint8_t cmt_spi3_read(uint8_t addr)
{
uint8_t tx_data, rx_data;
tx_data = ~(addr | 0x80); // negation and MSB high (read command)
spi_transaction_t t = {
.length = 8,
.rxlength = 8,
.tx_buffer = &tx_data,
.rx_buffer = &rx_data
};
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_reg, &t));
delayMicroseconds(100);
return rx_data;
}
void cmt_spi3_write_fifo(const uint8_t* buf, uint16_t len)
{
uint8_t tx_data;
spi_transaction_t t = {
.flags = SPI_TRANS_MODE_OCT,
.length = 8,
.tx_buffer = &tx_data, // reference to write data
.rx_buffer = NULL
};
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
}
}
void cmt_spi3_read_fifo(uint8_t* buf, uint16_t len)
{
uint8_t rx_data;
spi_transaction_t t = {
.length = 8,
.rxlength = 8,
.tx_buffer = NULL,
.rx_buffer = &rx_data
};
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;
}
}

14
lib/CMT2300a/cmt_spi3.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef __CMT_SPI3_H
#define __CMT_SPI3_H
#include <stdint.h>
void cmt_spi3_init(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8_t pin_fcs, uint32_t spi_speed);
void cmt_spi3_write(uint8_t addr, uint8_t dat);
uint8_t cmt_spi3_read(uint8_t addr);
void cmt_spi3_write_fifo(const uint8_t* p_buf, uint16_t len);
void cmt_spi3_read_fifo(uint8_t* p_buf, uint16_t len);
#endif

View File

@ -3,6 +3,10 @@
* Copyright (C) 2022 Thomas Basler and others
*/
#include "Hoymiles.h"
#include "inverters/HMS_1CH.h"
#include "inverters/HMS_2CH.h"
#include "inverters/HMS_4CH.h"
#include "inverters/HMT_6CH.h"
#include "inverters/HM_1CH.h"
#include "inverters/HM_2CH.h"
#include "inverters/HM_4CH.h"
@ -13,69 +17,89 @@
HoymilesClass Hoymiles;
void HoymilesClass::init(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t pinIRQ)
void HoymilesClass::init()
{
_xSemaphore = xSemaphoreCreateMutex();
HOY_SEMAPHORE_GIVE(); // release before first use
HOY_SEMAPHORE_GIVE(); // release before first use
_pollInterval = 0;
_radio.reset(new HoymilesRadio());
_radio->init(initialisedSpiBus, pinCE, pinIRQ);
_radioNrf.reset(new HoymilesRadio_NRF());
_radioCmt.reset(new HoymilesRadio_CMT());
}
void HoymilesClass::initNRF(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t pinIRQ)
{
_radioNrf->init(initialisedSpiBus, pinCE, pinIRQ);
}
void HoymilesClass::initCMT(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8_t pin_fcs, int8_t pin_gpio2, int8_t pin_gpio3)
{
_radioCmt->init(pin_sdio, pin_clk, pin_cs, pin_fcs, pin_gpio2, pin_gpio3);
}
void HoymilesClass::loop()
{
HOY_SEMAPHORE_TAKE();
_radio->loop();
_radioNrf->loop();
_radioCmt->loop();
if (getNumInverters() > 0) {
if (millis() - _lastPoll > (_pollInterval * 1000)) {
static uint8_t inverterPos = 0;
if (_radio->isIdle()) {
std::shared_ptr<InverterAbstract> iv = getInverterByPos(inverterPos);
if (iv != nullptr) {
_messageOutput->print("Fetch inverter: ");
_messageOutput->println(iv->serial(), HEX);
iv->sendStatsRequest(_radio.get());
// Fetch event log
bool force = iv->EventLog()->getLastAlarmRequestSuccess() == CMD_NOK;
iv->sendAlarmLogRequest(_radio.get(), force);
// Fetch limit
if ((iv->SystemConfigPara()->getLastLimitRequestSuccess() == CMD_NOK)
|| ((millis() - iv->SystemConfigPara()->getLastUpdateRequest() > HOY_SYSTEM_CONFIG_PARA_POLL_INTERVAL)
&& (millis() - iv->SystemConfigPara()->getLastUpdateCommand() > HOY_SYSTEM_CONFIG_PARA_POLL_MIN_DURATION))) {
_messageOutput->println("Request SystemConfigPara");
iv->sendSystemConfigParaRequest(_radio.get());
}
// Set limit if required
if (iv->SystemConfigPara()->getLastLimitCommandSuccess() == CMD_NOK) {
_messageOutput->println("Resend ActivePowerControl");
iv->resendActivePowerControlRequest(_radio.get());
}
// Set power status if required
if (iv->PowerCommand()->getLastPowerCommandSuccess() == CMD_NOK) {
_messageOutput->println("Resend PowerCommand");
iv->resendPowerControlRequest(_radio.get());
}
// Fetch dev info (but first fetch stats)
if (iv->Statistics()->getLastUpdate() > 0 && (iv->DevInfo()->getLastUpdateAll() == 0 || iv->DevInfo()->getLastUpdateSimple() == 0)) {
_messageOutput->println("Request device info");
iv->sendDevInfoRequest(_radio.get());
}
}
std::shared_ptr<InverterAbstract> iv = getInverterByPos(inverterPos);
if ((iv == nullptr) || ((iv != nullptr) && (!iv->getRadio()->isInitialized()))) {
if (++inverterPos >= getNumInverters()) {
inverterPos = 0;
}
}
_lastPoll = millis();
if (iv != nullptr && iv->getRadio()->isInitialized() && iv->getRadio()->isIdle()) {
_messageOutput->print("Fetch inverter: ");
_messageOutput->println(iv->serial(), HEX);
if (!iv->isReachable()) {
iv->sendChangeChannelRequest();
}
iv->sendStatsRequest();
// Fetch event log
bool force = iv->EventLog()->getLastAlarmRequestSuccess() == CMD_NOK;
iv->sendAlarmLogRequest(force);
// Fetch limit
if ((iv->SystemConfigPara()->getLastLimitRequestSuccess() == CMD_NOK)
|| ((millis() - iv->SystemConfigPara()->getLastUpdateRequest() > HOY_SYSTEM_CONFIG_PARA_POLL_INTERVAL)
&& (millis() - iv->SystemConfigPara()->getLastUpdateCommand() > HOY_SYSTEM_CONFIG_PARA_POLL_MIN_DURATION))) {
_messageOutput->println("Request SystemConfigPara");
iv->sendSystemConfigParaRequest();
}
// Set limit if required
if (iv->SystemConfigPara()->getLastLimitCommandSuccess() == CMD_NOK) {
_messageOutput->println("Resend ActivePowerControl");
iv->resendActivePowerControlRequest();
}
// Set power status if required
if (iv->PowerCommand()->getLastPowerCommandSuccess() == CMD_NOK) {
_messageOutput->println("Resend PowerCommand");
iv->resendPowerControlRequest();
}
// Fetch dev info (but first fetch stats)
if (iv->Statistics()->getLastUpdate() > 0 && (iv->DevInfo()->getLastUpdateAll() == 0 || iv->DevInfo()->getLastUpdateSimple() == 0)) {
_messageOutput->println("Request device info");
iv->sendDevInfoRequest();
}
if (++inverterPos >= getNumInverters()) {
inverterPos = 0;
}
_lastPoll = millis();
}
}
}
@ -85,12 +109,20 @@ void HoymilesClass::loop()
std::shared_ptr<InverterAbstract> HoymilesClass::addInverter(const char* name, uint64_t serial)
{
std::shared_ptr<InverterAbstract> i = nullptr;
if (HM_4CH::isValidSerial(serial)) {
i = std::make_shared<HM_4CH>(serial);
if (HMT_6CH::isValidSerial(serial)) {
i = std::make_shared<HMT_6CH>(_radioCmt.get(), serial);
} else if (HMS_4CH::isValidSerial(serial)) {
i = std::make_shared<HMS_4CH>(_radioCmt.get(), serial);
} else if (HMS_2CH::isValidSerial(serial)) {
i = std::make_shared<HMS_2CH>(_radioCmt.get(), serial);
} else if (HMS_1CH::isValidSerial(serial)) {
i = std::make_shared<HMS_1CH>(_radioCmt.get(), serial);
} else if (HM_4CH::isValidSerial(serial)) {
i = std::make_shared<HM_4CH>(_radioNrf.get(), serial);
} else if (HM_2CH::isValidSerial(serial)) {
i = std::make_shared<HM_2CH>(serial);
i = std::make_shared<HM_2CH>(_radioNrf.get(), serial);
} else if (HM_1CH::isValidSerial(serial)) {
i = std::make_shared<HM_1CH>(serial);
i = std::make_shared<HM_1CH>(_radioNrf.get(), serial);
}
if (i) {
@ -162,9 +194,19 @@ size_t HoymilesClass::getNumInverters()
return _inverters.size();
}
HoymilesRadio* HoymilesClass::getRadio()
HoymilesRadio_NRF* HoymilesClass::getRadioNrf()
{
return _radio.get();
return _radioNrf.get();
}
HoymilesRadio_CMT* HoymilesClass::getRadioCmt()
{
return _radioCmt.get();
}
bool HoymilesClass::isAllRadioIdle()
{
return _radioNrf.get()->isIdle() && _radioCmt.get()->isIdle();
}
uint32_t HoymilesClass::PollInterval()

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "HoymilesRadio.h"
#include "HoymilesRadio_NRF.h"
#include "HoymilesRadio_CMT.h"
#include "inverters/InverterAbstract.h"
#include "types.h"
#include <Print.h>
@ -14,7 +15,9 @@
class HoymilesClass {
public:
void init(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t pinIRQ);
void init();
void initNRF(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t pinIRQ);
void initCMT(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8_t pin_fcs, int8_t pin_gpio2, int8_t pin_gpio3);
void loop();
void setMessageOutput(Print* output);
@ -27,14 +30,18 @@ public:
void removeInverterBySerial(uint64_t serial);
size_t getNumInverters();
HoymilesRadio* getRadio();
HoymilesRadio_NRF* getRadioNrf();
HoymilesRadio_CMT* getRadioCmt();
uint32_t PollInterval();
void setPollInterval(uint32_t interval);
bool isAllRadioIdle();
private:
std::vector<std::shared_ptr<InverterAbstract>> _inverters;
std::unique_ptr<HoymilesRadio> _radio;
std::unique_ptr<HoymilesRadio_NRF> _radioNrf;
std::unique_ptr<HoymilesRadio_CMT> _radioCmt;
SemaphoreHandle_t _xSemaphore;

View File

@ -1,158 +1,10 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler and others
* Copyright (C) 2023 Thomas Basler and others
*/
#include "HoymilesRadio.h"
#include "Hoymiles.h"
#include "commands/RequestFrameCommand.h"
#include "crc.h"
#include <Every.h>
#include <FunctionalInterrupt.h>
void HoymilesRadio::init(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t pinIRQ)
{
_dtuSerial.u64 = 0;
_spiPtr.reset(initialisedSpiBus);
_radio.reset(new RF24(pinCE, initialisedSpiBus->pinSS()));
_radio->begin(_spiPtr.get());
_radio->setDataRate(RF24_250KBPS);
_radio->enableDynamicPayloads();
_radio->setCRCLength(RF24_CRC_16);
_radio->setAddressWidth(5);
_radio->setRetries(0, 0);
_radio->maskIRQ(true, true, false); // enable only receiving interrupts
if (_radio->isChipConnected()) {
Hoymiles.getMessageOutput()->println("Connection successful");
} else {
Hoymiles.getMessageOutput()->println("Connection error!!");
}
attachInterrupt(digitalPinToInterrupt(pinIRQ), std::bind(&HoymilesRadio::handleIntr, this), FALLING);
openReadingPipe();
_radio->startListening();
}
void HoymilesRadio::loop()
{
EVERY_N_MILLIS(4)
{
switchRxCh();
}
if (_packetReceived) {
Hoymiles.getMessageOutput()->println("Interrupt received");
while (_radio->available()) {
if (!(_rxBuffer.size() > FRAGMENT_BUFFER_SIZE)) {
fragment_t f;
memset(f.fragment, 0xcc, MAX_RF_PAYLOAD_SIZE);
f.len = _radio->getDynamicPayloadSize();
f.channel = _radio->getChannel();
if (f.len > MAX_RF_PAYLOAD_SIZE)
f.len = MAX_RF_PAYLOAD_SIZE;
_radio->read(f.fragment, f.len);
_rxBuffer.push(f);
} else {
Hoymiles.getMessageOutput()->println("Buffer full");
_radio->flush_rx();
}
}
_packetReceived = false;
} else {
// Perform package parsing only if no packages are received
if (!_rxBuffer.empty()) {
fragment_t f = _rxBuffer.back();
if (checkFragmentCrc(&f)) {
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterByFragment(&f);
if (nullptr != inv) {
// Save packet in inverter rx buffer
char buf[30];
snprintf(buf, sizeof(buf), "RX Channel: %d --> ", f.channel);
dumpBuf(buf, f.fragment, f.len);
inv->addRxFragment(f.fragment, f.len);
} else {
Hoymiles.getMessageOutput()->println("Inverter Not found!");
}
} else {
Hoymiles.getMessageOutput()->println("Frame kaputt");
}
// Remove paket from buffer even it was corrupted
_rxBuffer.pop();
}
}
if (_busyFlag && _rxTimeout.occured()) {
Hoymiles.getMessageOutput()->println("RX Period End");
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterBySerial(_commandQueue.front().get()->getTargetAddress());
if (nullptr != inv) {
CommandAbstract* cmd = _commandQueue.front().get();
uint8_t verifyResult = inv->verifyAllFragments(cmd);
if (verifyResult == FRAGMENT_ALL_MISSING_RESEND) {
Hoymiles.getMessageOutput()->println("Nothing received, resend whole request");
sendLastPacketAgain();
} else if (verifyResult == FRAGMENT_ALL_MISSING_TIMEOUT) {
Hoymiles.getMessageOutput()->println("Nothing received, resend count exeeded");
_commandQueue.pop();
_busyFlag = false;
} else if (verifyResult == FRAGMENT_RETRANSMIT_TIMEOUT) {
Hoymiles.getMessageOutput()->println("Retransmit timeout");
_commandQueue.pop();
_busyFlag = false;
} else if (verifyResult == FRAGMENT_HANDLE_ERROR) {
Hoymiles.getMessageOutput()->println("Packet handling error");
_commandQueue.pop();
_busyFlag = false;
} else if (verifyResult > 0) {
// Perform Retransmit
Hoymiles.getMessageOutput()->print("Request retransmit: ");
Hoymiles.getMessageOutput()->println(verifyResult);
sendRetransmitPacket(verifyResult);
} else {
// Successful received all packages
Hoymiles.getMessageOutput()->println("Success");
_commandQueue.pop();
_busyFlag = false;
}
} else {
// If inverter was not found, assume the command is invalid
Hoymiles.getMessageOutput()->println("RX: Invalid inverter found");
_commandQueue.pop();
_busyFlag = false;
}
} else if (!_busyFlag) {
// Currently in idle mode --> send packet if one is in the queue
if (!_commandQueue.empty()) {
CommandAbstract* cmd = _commandQueue.front().get();
auto inv = Hoymiles.getInverterBySerial(cmd->getTargetAddress());
if (nullptr != inv) {
inv->clearRxFragmentBuffer();
sendEsbPacket(cmd);
} else {
Hoymiles.getMessageOutput()->println("TX: Invalid inverter found");
_commandQueue.pop();
}
}
}
}
void HoymilesRadio::setPALevel(rf24_pa_dbm_e paLevel)
{
_radio->setPALevel(paLevel);
}
serial_u HoymilesRadio::DtuSerial()
{
@ -162,62 +14,6 @@ serial_u HoymilesRadio::DtuSerial()
void HoymilesRadio::setDtuSerial(uint64_t serial)
{
_dtuSerial.u64 = serial;
openReadingPipe();
}
bool HoymilesRadio::isIdle()
{
return !_busyFlag;
}
bool HoymilesRadio::isConnected()
{
return _radio->isChipConnected();
}
bool HoymilesRadio::isPVariant()
{
return _radio->isPVariant();
}
void HoymilesRadio::openReadingPipe()
{
serial_u s;
s = convertSerialToRadioId(_dtuSerial);
_radio->openReadingPipe(1, s.u64);
}
void HoymilesRadio::openWritingPipe(serial_u serial)
{
serial_u s;
s = convertSerialToRadioId(serial);
_radio->openWritingPipe(s.u64);
}
void ARDUINO_ISR_ATTR HoymilesRadio::handleIntr()
{
_packetReceived = true;
}
uint8_t HoymilesRadio::getRxNxtChannel()
{
if (++_rxChIdx >= sizeof(_rxChLst))
_rxChIdx = 0;
return _rxChLst[_rxChIdx];
}
uint8_t HoymilesRadio::getTxNxtChannel()
{
if (++_txChIdx >= sizeof(_txChLst))
_txChIdx = 0;
return _txChLst[_txChIdx];
}
void HoymilesRadio::switchRxCh()
{
_radio->stopListening();
_radio->setChannel(getRxNxtChannel());
_radio->startListening();
}
serial_u HoymilesRadio::convertSerialToRadioId(serial_u serial)
@ -238,36 +34,6 @@ bool HoymilesRadio::checkFragmentCrc(fragment_t* fragment)
return (crc == fragment->fragment[fragment->len - 1]);
}
void HoymilesRadio::sendEsbPacket(CommandAbstract* cmd)
{
cmd->incrementSendCount();
cmd->setRouterAddress(DtuSerial().u64);
_radio->stopListening();
_radio->setChannel(getTxNxtChannel());
serial_u s;
s.u64 = cmd->getTargetAddress();
openWritingPipe(s);
_radio->setRetries(3, 15);
Hoymiles.getMessageOutput()->print("TX ");
Hoymiles.getMessageOutput()->print(cmd->getCommandName());
Hoymiles.getMessageOutput()->print(" Channel: ");
Hoymiles.getMessageOutput()->print(_radio->getChannel());
Hoymiles.getMessageOutput()->print(" --> ");
cmd->dumpDataPayload(Hoymiles.getMessageOutput());
_radio->write(cmd->getDataPayload(), cmd->getDataSize());
_radio->setRetries(0, 0);
openReadingPipe();
_radio->setChannel(getRxNxtChannel());
_radio->startListening();
_busyFlag = true;
_rxTimeout.set(cmd->getTimeout());
}
void HoymilesRadio::sendRetransmitPacket(uint8_t fragment_id)
{
CommandAbstract* cmd = _commandQueue.front().get();
@ -285,14 +51,22 @@ void HoymilesRadio::sendLastPacketAgain()
sendEsbPacket(cmd);
}
void HoymilesRadio::dumpBuf(const char* info, uint8_t buf[], uint8_t len)
void HoymilesRadio::dumpBuf(const uint8_t buf[], uint8_t len, bool appendNewline)
{
if (NULL != info)
Hoymiles.getMessageOutput()->print(String(info));
for (uint8_t i = 0; i < len; i++) {
Hoymiles.getMessageOutput()->printf("%02X ", buf[i]);
}
Hoymiles.getMessageOutput()->println("");
if (appendNewline) {
Hoymiles.getMessageOutput()->println("");
}
}
bool HoymilesRadio::isInitialized()
{
return _isInitialized;
}
bool HoymilesRadio::isIdle()
{
return !_busyFlag;
}

View File

@ -1,29 +1,18 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "TimeoutHelper.h"
#include "commands/CommandAbstract.h"
#include "types.h"
#include <RF24.h>
#include <memory>
#include <nRF24L01.h>
#include <queue>
// number of fragments hold in buffer
#define FRAGMENT_BUFFER_SIZE 30
class HoymilesRadio {
public:
void init(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t pinIRQ);
void loop();
void setPALevel(rf24_pa_dbm_e paLevel);
serial_u DtuSerial();
void setDtuSerial(uint64_t serial);
virtual void setDtuSerial(uint64_t serial);
bool isIdle();
bool isConnected();
bool isPVariant();
bool isInitialized();
template <typename T>
T* enqueCommand()
@ -32,37 +21,17 @@ public:
return static_cast<T*>(_commandQueue.back().get());
}
private:
void ARDUINO_ISR_ATTR handleIntr();
protected:
static serial_u convertSerialToRadioId(serial_u serial);
uint8_t getRxNxtChannel();
uint8_t getTxNxtChannel();
void switchRxCh();
void openReadingPipe();
void openWritingPipe(serial_u serial);
bool checkFragmentCrc(fragment_t* fragment);
void dumpBuf(const char* info, uint8_t buf[], uint8_t len);
void dumpBuf(const uint8_t buf[], uint8_t len, bool appendNewline = true);
void sendEsbPacket(CommandAbstract* cmd);
bool checkFragmentCrc(fragment_t* fragment);
virtual void sendEsbPacket(CommandAbstract* cmd) = 0;
void sendRetransmitPacket(uint8_t fragment_id);
void sendLastPacketAgain();
std::unique_ptr<SPIClass> _spiPtr;
std::unique_ptr<RF24> _radio;
uint8_t _rxChLst[5] = { 3, 23, 40, 61, 75 };
uint8_t _rxChIdx = 0;
uint8_t _txChLst[5] = { 3, 23, 40, 61, 75 };
uint8_t _txChIdx = 0;
volatile bool _packetReceived = false;
std::queue<fragment_t> _rxBuffer;
TimeoutHelper _rxTimeout;
serial_u _dtuSerial;
bool _busyFlag = false;
std::queue<std::shared_ptr<CommandAbstract>> _commandQueue;
bool _isInitialized = false;
bool _busyFlag = false;
};

View File

@ -0,0 +1,285 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Thomas Basler and others
*/
#include "HoymilesRadio_CMT.h"
#include "Hoymiles.h"
#include "crc.h"
#include <FunctionalInterrupt.h>
#define HOY_BOOT_FREQ 868000000 // Hoymiles boot/init frequency after power up inverter or connection lost for 15 min
#define HOY_BASE_FREQ 860000000
// offset from initalized CMT base frequency to Hoy base frequency in channels
#define CMT_BASE_CH_OFFSET860 ((CMT_BASE_FREQ - HOY_BASE_FREQ) / CMT2300A_ONE_STEP_SIZE / FH_OFFSET)
// frequency can not be lower than actual initailized base freq
#define MIN_FREQ_KHZ ((HOY_BASE_FREQ + (CMT_BASE_CH_OFFSET860 >= 1 ? CMT_BASE_CH_OFFSET860 : 1) * CMT2300A_ONE_STEP_SIZE * FH_OFFSET) / 1000)
// =923500, 0xFF does not work
#define MAX_FREQ_KHZ ((HOY_BASE_FREQ + 0xFE * CMT2300A_ONE_STEP_SIZE * FH_OFFSET) / 1000)
float HoymilesRadio_CMT::getFrequencyFromChannel(const uint8_t channel)
{
return (CMT_BASE_FREQ + (CMT_BASE_CH_OFFSET860 + channel) * FH_OFFSET * CMT2300A_ONE_STEP_SIZE) / 1000000.0;
}
uint8_t HoymilesRadio_CMT::getChannelFromFrequency(const uint32_t freq_kHz)
{
if ((freq_kHz % 250) != 0) {
Hoymiles.getMessageOutput()->printf("%.3f MHz is not divisible by 250 kHz!\r\n", freq_kHz / 1000.0);
return 0xFF; // ERROR
}
if (freq_kHz < MIN_FREQ_KHZ || freq_kHz > MAX_FREQ_KHZ) {
Hoymiles.getMessageOutput()->printf("%.2f MHz is out of Hoymiles/CMT range! (%.2f MHz - %.2f MHz)\r\n",
freq_kHz / 1000.0, MIN_FREQ_KHZ / 1000.0, MAX_FREQ_KHZ / 1000.0);
return 0xFF; // ERROR
}
if (freq_kHz < 863000 || freq_kHz > 870000) {
Hoymiles.getMessageOutput()->printf("!!! caution: %.2f MHz is out of EU legal range! (863 - 870 MHz)\r\n",
freq_kHz / 1000.0);
}
return (freq_kHz * 1000 - CMT_BASE_FREQ) / CMT2300A_ONE_STEP_SIZE / FH_OFFSET - CMT_BASE_CH_OFFSET860; // frequency to channel
}
bool HoymilesRadio_CMT::cmtSwitchDtuFreq(const uint32_t to_freq_kHz)
{
const uint8_t toChannel = getChannelFromFrequency(to_freq_kHz);
if (toChannel == 0xFF) {
return false;
}
_radio->setChannel(toChannel);
return true;
}
void HoymilesRadio_CMT::init(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8_t pin_fcs, int8_t pin_gpio2, int8_t pin_gpio3)
{
_dtuSerial.u64 = 0;
_radio.reset(new CMT2300A(pin_sdio, pin_clk, pin_cs, pin_fcs));
_radio->begin();
cmtSwitchDtuFreq(_inverterTargetFrequency); // start dtu at work freqency, for fast Rx if inverter is already on and frequency switched
if (_radio->isChipConnected()) {
Hoymiles.getMessageOutput()->println("Connection successful");
} else {
Hoymiles.getMessageOutput()->println("Connection error!!");
}
if (pin_gpio2 >= 0) {
attachInterrupt(digitalPinToInterrupt(pin_gpio2), std::bind(&HoymilesRadio_CMT::handleInt1, this), RISING);
_gpio2_configured = true;
}
if (pin_gpio3 >= 0) {
attachInterrupt(digitalPinToInterrupt(pin_gpio3), std::bind(&HoymilesRadio_CMT::handleInt2, this), RISING);
_gpio3_configured = true;
}
_isInitialized = true;
}
void HoymilesRadio_CMT::loop()
{
if (!_isInitialized) {
return;
}
if (!_gpio3_configured) {
if (_radio->rxFifoAvailable()) { // read INT2, PKT_OK flag
_packetReceived = true;
}
}
if (_packetReceived) {
Hoymiles.getMessageOutput()->println("Interrupt received");
while (_radio->available()) {
if (!(_rxBuffer.size() > FRAGMENT_BUFFER_SIZE)) {
fragment_t f;
memset(f.fragment, 0xcc, MAX_RF_PAYLOAD_SIZE);
f.len = _radio->getDynamicPayloadSize();
f.channel = _radio->getChannel();
f.rssi = _radio->getRssiDBm();
if (f.len > MAX_RF_PAYLOAD_SIZE) {
f.len = MAX_RF_PAYLOAD_SIZE;
}
_radio->read(f.fragment, f.len);
_rxBuffer.push(f);
} else {
Hoymiles.getMessageOutput()->println("Buffer full");
_radio->flush_rx();
}
}
_radio->flush_rx();
_packetReceived = false;
} else {
// Perform package parsing only if no packages are received
if (!_rxBuffer.empty()) {
fragment_t f = _rxBuffer.back();
if (checkFragmentCrc(&f)) {
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterByFragment(&f);
if (nullptr != inv) {
// Save packet in inverter rx buffer
Hoymiles.getMessageOutput()->printf("RX %.2f MHz --> ", getFrequencyFromChannel(f.channel));
dumpBuf(f.fragment, f.len, false);
Hoymiles.getMessageOutput()->printf("| %d dBm\r\n", f.rssi);
inv->addRxFragment(f.fragment, f.len);
} else {
Hoymiles.getMessageOutput()->println("Inverter Not found!");
}
} else {
Hoymiles.getMessageOutput()->println("Frame kaputt"); // ;-)
}
// Remove paket from buffer even it was corrupted
_rxBuffer.pop();
}
}
if (_busyFlag && _rxTimeout.occured()) {
Hoymiles.getMessageOutput()->println("RX Period End");
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterBySerial(_commandQueue.front().get()->getTargetAddress());
if (nullptr != inv) {
CommandAbstract* cmd = _commandQueue.front().get();
uint8_t verifyResult = inv->verifyAllFragments(cmd);
if (verifyResult == FRAGMENT_ALL_MISSING_RESEND) {
Hoymiles.getMessageOutput()->println("Nothing received, resend whole request");
sendLastPacketAgain();
} else if (verifyResult == FRAGMENT_ALL_MISSING_TIMEOUT) {
Hoymiles.getMessageOutput()->println("Nothing received, resend count exeeded");
_commandQueue.pop();
_busyFlag = false;
} else if (verifyResult == FRAGMENT_RETRANSMIT_TIMEOUT) {
Hoymiles.getMessageOutput()->println("Retransmit timeout");
_commandQueue.pop();
_busyFlag = false;
} else if (verifyResult == FRAGMENT_HANDLE_ERROR) {
Hoymiles.getMessageOutput()->println("Packet handling error");
_commandQueue.pop();
_busyFlag = false;
} else if (verifyResult > 0) {
// Perform Retransmit
Hoymiles.getMessageOutput()->print("Request retransmit: ");
Hoymiles.getMessageOutput()->println(verifyResult);
sendRetransmitPacket(verifyResult);
} else {
// Successful received all packages
Hoymiles.getMessageOutput()->println("Success");
_commandQueue.pop();
_busyFlag = false;
}
} else {
// If inverter was not found, assume the command is invalid
Hoymiles.getMessageOutput()->println("RX: Invalid inverter found");
_commandQueue.pop();
_busyFlag = false;
}
} else if (!_busyFlag) {
// Currently in idle mode --> send packet if one is in the queue
if (!_commandQueue.empty()) {
CommandAbstract* cmd = _commandQueue.front().get();
auto inv = Hoymiles.getInverterBySerial(cmd->getTargetAddress());
if (nullptr != inv) {
inv->clearRxFragmentBuffer();
sendEsbPacket(cmd);
} else {
Hoymiles.getMessageOutput()->println("TX: Invalid inverter found");
_commandQueue.pop();
}
}
}
}
void HoymilesRadio_CMT::setPALevel(int8_t paLevel)
{
if (!_isInitialized) {
return;
}
if (_radio->setPALevel(paLevel)) {
Hoymiles.getMessageOutput()->printf("CMT TX power set to %d dBm\r\n", paLevel);
} else {
Hoymiles.getMessageOutput()->printf("CMT TX power %d dBm is not defined! (min: -10 dBm, max: 20 dBm)\r\n", paLevel);
}
}
void HoymilesRadio_CMT::setInverterTargetFrequency(uint32_t frequency)
{
_inverterTargetFrequency = frequency;
if (!_isInitialized) {
return;
}
cmtSwitchDtuFreq(_inverterTargetFrequency);
}
uint32_t HoymilesRadio_CMT::getInverterTargetFrequency()
{
return _inverterTargetFrequency;
}
bool HoymilesRadio_CMT::isConnected()
{
if (!_isInitialized) {
return false;
}
return _radio->isChipConnected();
}
uint32_t HoymilesRadio_CMT::getMinFrequency()
{
return MIN_FREQ_KHZ;
}
uint32_t HoymilesRadio_CMT::getMaxFrequency()
{
return MAX_FREQ_KHZ;
}
void ARDUINO_ISR_ATTR HoymilesRadio_CMT::handleInt1()
{
_packetSent = true;
}
void ARDUINO_ISR_ATTR HoymilesRadio_CMT::handleInt2()
{
_packetReceived = true;
}
void HoymilesRadio_CMT::sendEsbPacket(CommandAbstract* cmd)
{
cmd->incrementSendCount();
cmd->setRouterAddress(DtuSerial().u64);
_radio->stopListening();
if (cmd->getDataPayload()[0] == 0x56) { // @todo(tbnobody) Bad hack to identify ChannelChange Command
cmtSwitchDtuFreq(HOY_BOOT_FREQ / 1000);
}
Hoymiles.getMessageOutput()->printf("TX %s %.2f MHz --> ",
cmd->getCommandName().c_str(), getFrequencyFromChannel(_radio->getChannel()));
cmd->dumpDataPayload(Hoymiles.getMessageOutput());
if (!_radio->write(cmd->getDataPayload(), cmd->getDataSize())) {
Hoymiles.getMessageOutput()->println("TX SPI Timeout");
}
cmtSwitchDtuFreq(_inverterTargetFrequency);
_radio->startListening();
_busyFlag = true;
_rxTimeout.set(cmd->getTimeout());
}

View File

@ -0,0 +1,57 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "HoymilesRadio.h"
#include "TimeoutHelper.h"
#include "commands/CommandAbstract.h"
#include "types.h"
#include <Arduino.h>
#include <cmt2300wrapper.h>
#include <memory>
#include <queue>
// number of fragments hold in buffer
#define FRAGMENT_BUFFER_SIZE 30
#ifndef HOYMILES_CMT_WORK_FREQ
#define HOYMILES_CMT_WORK_FREQ 865000
#endif
class HoymilesRadio_CMT : public HoymilesRadio {
public:
void init(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8_t pin_fcs, int8_t pin_gpio2, int8_t pin_gpio3);
void loop();
void setPALevel(int8_t paLevel);
void setInverterTargetFrequency(uint32_t frequency);
uint32_t getInverterTargetFrequency();
bool isConnected();
static uint32_t getMinFrequency();
static uint32_t getMaxFrequency();
static float getFrequencyFromChannel(const uint8_t channel);
static uint8_t getChannelFromFrequency(const uint32_t freq_kHz);
private:
void ARDUINO_ISR_ATTR handleInt1();
void ARDUINO_ISR_ATTR handleInt2();
void sendEsbPacket(CommandAbstract* cmd);
std::unique_ptr<CMT2300A> _radio;
volatile bool _packetReceived = false;
volatile bool _packetSent = false;
bool _gpio2_configured = false;
bool _gpio3_configured = false;
std::queue<fragment_t> _rxBuffer;
TimeoutHelper _rxTimeout;
TimeoutHelper _txTimeout;
uint32_t _inverterTargetFrequency = HOYMILES_CMT_WORK_FREQ;
bool cmtSwitchDtuFreq(const uint32_t to_freq_kHz);
};

View File

@ -0,0 +1,254 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler and others
*/
#include "HoymilesRadio_NRF.h"
#include "Hoymiles.h"
#include "commands/RequestFrameCommand.h"
#include <Every.h>
#include <FunctionalInterrupt.h>
void HoymilesRadio_NRF::init(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t pinIRQ)
{
_dtuSerial.u64 = 0;
_spiPtr.reset(initialisedSpiBus);
_radio.reset(new RF24(pinCE, initialisedSpiBus->pinSS()));
_radio->begin(_spiPtr.get());
_radio->setDataRate(RF24_250KBPS);
_radio->enableDynamicPayloads();
_radio->setCRCLength(RF24_CRC_16);
_radio->setAddressWidth(5);
_radio->setRetries(0, 0);
_radio->maskIRQ(true, true, false); // enable only receiving interrupts
if (_radio->isChipConnected()) {
Hoymiles.getMessageOutput()->println("Connection successful");
} else {
Hoymiles.getMessageOutput()->println("Connection error!!");
}
attachInterrupt(digitalPinToInterrupt(pinIRQ), std::bind(&HoymilesRadio_NRF::handleIntr, this), FALLING);
openReadingPipe();
_radio->startListening();
_isInitialized = true;
}
void HoymilesRadio_NRF::loop()
{
if (!_isInitialized) {
return;
}
EVERY_N_MILLIS(4)
{
switchRxCh();
}
if (_packetReceived) {
Hoymiles.getMessageOutput()->println("Interrupt received");
while (_radio->available()) {
if (!(_rxBuffer.size() > FRAGMENT_BUFFER_SIZE)) {
fragment_t f;
memset(f.fragment, 0xcc, MAX_RF_PAYLOAD_SIZE);
f.len = _radio->getDynamicPayloadSize();
f.channel = _radio->getChannel();
if (f.len > MAX_RF_PAYLOAD_SIZE)
f.len = MAX_RF_PAYLOAD_SIZE;
_radio->read(f.fragment, f.len);
_rxBuffer.push(f);
} else {
Hoymiles.getMessageOutput()->println("Buffer full");
_radio->flush_rx();
}
}
_packetReceived = false;
} else {
// Perform package parsing only if no packages are received
if (!_rxBuffer.empty()) {
fragment_t f = _rxBuffer.back();
if (checkFragmentCrc(&f)) {
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterByFragment(&f);
if (nullptr != inv) {
// Save packet in inverter rx buffer
Hoymiles.getMessageOutput()->printf("RX Channel: %d --> ", f.channel);
dumpBuf(f.fragment, f.len);
inv->addRxFragment(f.fragment, f.len);
} else {
Hoymiles.getMessageOutput()->println("Inverter Not found!");
}
} else {
Hoymiles.getMessageOutput()->println("Frame kaputt");
}
// Remove paket from buffer even it was corrupted
_rxBuffer.pop();
}
}
if (_busyFlag && _rxTimeout.occured()) {
Hoymiles.getMessageOutput()->println("RX Period End");
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterBySerial(_commandQueue.front().get()->getTargetAddress());
if (nullptr != inv) {
CommandAbstract* cmd = _commandQueue.front().get();
uint8_t verifyResult = inv->verifyAllFragments(cmd);
if (verifyResult == FRAGMENT_ALL_MISSING_RESEND) {
Hoymiles.getMessageOutput()->println("Nothing received, resend whole request");
sendLastPacketAgain();
} else if (verifyResult == FRAGMENT_ALL_MISSING_TIMEOUT) {
Hoymiles.getMessageOutput()->println("Nothing received, resend count exeeded");
_commandQueue.pop();
_busyFlag = false;
} else if (verifyResult == FRAGMENT_RETRANSMIT_TIMEOUT) {
Hoymiles.getMessageOutput()->println("Retransmit timeout");
_commandQueue.pop();
_busyFlag = false;
} else if (verifyResult == FRAGMENT_HANDLE_ERROR) {
Hoymiles.getMessageOutput()->println("Packet handling error");
_commandQueue.pop();
_busyFlag = false;
} else if (verifyResult > 0) {
// Perform Retransmit
Hoymiles.getMessageOutput()->print("Request retransmit: ");
Hoymiles.getMessageOutput()->println(verifyResult);
sendRetransmitPacket(verifyResult);
} else {
// Successful received all packages
Hoymiles.getMessageOutput()->println("Success");
_commandQueue.pop();
_busyFlag = false;
}
} else {
// If inverter was not found, assume the command is invalid
Hoymiles.getMessageOutput()->println("RX: Invalid inverter found");
_commandQueue.pop();
_busyFlag = false;
}
} else if (!_busyFlag) {
// Currently in idle mode --> send packet if one is in the queue
if (!_commandQueue.empty()) {
CommandAbstract* cmd = _commandQueue.front().get();
auto inv = Hoymiles.getInverterBySerial(cmd->getTargetAddress());
if (nullptr != inv) {
inv->clearRxFragmentBuffer();
sendEsbPacket(cmd);
} else {
Hoymiles.getMessageOutput()->println("TX: Invalid inverter found");
_commandQueue.pop();
}
}
}
}
void HoymilesRadio_NRF::setPALevel(rf24_pa_dbm_e paLevel)
{
if (!_isInitialized) {
return;
}
_radio->setPALevel(paLevel);
}
void HoymilesRadio_NRF::setDtuSerial(uint64_t serial)
{
HoymilesRadio::setDtuSerial(serial);
if (!_isInitialized) {
return;
}
openReadingPipe();
}
bool HoymilesRadio_NRF::isConnected()
{
if (!_isInitialized) {
return false;
}
return _radio->isChipConnected();
}
bool HoymilesRadio_NRF::isPVariant()
{
if (!_isInitialized) {
return false;
}
return _radio->isPVariant();
}
void HoymilesRadio_NRF::openReadingPipe()
{
serial_u s;
s = convertSerialToRadioId(_dtuSerial);
_radio->openReadingPipe(1, s.u64);
}
void HoymilesRadio_NRF::openWritingPipe(serial_u serial)
{
serial_u s;
s = convertSerialToRadioId(serial);
_radio->openWritingPipe(s.u64);
}
void ARDUINO_ISR_ATTR HoymilesRadio_NRF::handleIntr()
{
_packetReceived = true;
}
uint8_t HoymilesRadio_NRF::getRxNxtChannel()
{
if (++_rxChIdx >= sizeof(_rxChLst))
_rxChIdx = 0;
return _rxChLst[_rxChIdx];
}
uint8_t HoymilesRadio_NRF::getTxNxtChannel()
{
if (++_txChIdx >= sizeof(_txChLst))
_txChIdx = 0;
return _txChLst[_txChIdx];
}
void HoymilesRadio_NRF::switchRxCh()
{
_radio->stopListening();
_radio->setChannel(getRxNxtChannel());
_radio->startListening();
}
void HoymilesRadio_NRF::sendEsbPacket(CommandAbstract* cmd)
{
cmd->incrementSendCount();
cmd->setRouterAddress(DtuSerial().u64);
_radio->stopListening();
_radio->setChannel(getTxNxtChannel());
serial_u s;
s.u64 = cmd->getTargetAddress();
openWritingPipe(s);
_radio->setRetries(3, 15);
Hoymiles.getMessageOutput()->printf("TX %s Channel: %d --> ",
cmd->getCommandName().c_str(), _radio->getChannel());
cmd->dumpDataPayload(Hoymiles.getMessageOutput());
_radio->write(cmd->getDataPayload(), cmd->getDataSize());
_radio->setRetries(0, 0);
openReadingPipe();
_radio->setChannel(getRxNxtChannel());
_radio->startListening();
_busyFlag = true;
_rxTimeout.set(cmd->getTimeout());
}

View File

@ -0,0 +1,48 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "HoymilesRadio.h"
#include "TimeoutHelper.h"
#include "commands/CommandAbstract.h"
#include <RF24.h>
#include <memory>
#include <nRF24L01.h>
#include <queue>
// number of fragments hold in buffer
#define FRAGMENT_BUFFER_SIZE 30
class HoymilesRadio_NRF : public HoymilesRadio {
public:
void init(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t pinIRQ);
void loop();
void setPALevel(rf24_pa_dbm_e paLevel);
virtual void setDtuSerial(uint64_t serial);
bool isConnected();
bool isPVariant();
private:
void ARDUINO_ISR_ATTR handleIntr();
uint8_t getRxNxtChannel();
uint8_t getTxNxtChannel();
void switchRxCh();
void openReadingPipe();
void openWritingPipe(serial_u serial);
void sendEsbPacket(CommandAbstract* cmd);
std::unique_ptr<SPIClass> _spiPtr;
std::unique_ptr<RF24> _radio;
uint8_t _rxChLst[5] = { 3, 23, 40, 61, 75 };
uint8_t _rxChIdx = 0;
uint8_t _txChLst[5] = { 3, 23, 40, 61, 75 };
uint8_t _txChIdx = 0;
volatile bool _packetReceived = false;
std::queue<fragment_t> _rxBuffer;
TimeoutHelper _rxTimeout;
};

View File

@ -10,7 +10,7 @@ AlarmDataCommand::AlarmDataCommand(uint64_t target_address, uint64_t router_addr
{
setTime(time);
setDataType(0x11);
setTimeout(600);
setTimeout(750);
}
String AlarmDataCommand::getCommandName()

View File

@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Thomas Basler and others
*/
#include "ChannelChangeCommand.h"
ChannelChangeCommand::ChannelChangeCommand(uint64_t target_address, uint64_t router_address, uint8_t channel)
: CommandAbstract(target_address, router_address)
{
_payload[0] = 0x56;
_payload[9] = 0x02;
_payload[10] = 0x15;
_payload[11] = 0x21;
_payload[13] = 0x14;
_payload_size = 14;
setChannel(channel);
setTimeout(10);
}
String ChannelChangeCommand::getCommandName()
{
return "ChannelChangeCommand";
}
void ChannelChangeCommand::setChannel(uint8_t channel)
{
_payload[12] = channel;
}
uint8_t ChannelChangeCommand::getChannel()
{
return _payload[12];
}
bool ChannelChangeCommand::handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id)
{
return true;
}
uint8_t ChannelChangeCommand::getMaxResendCount()
{
// This command will never retrieve an answer. Therefor it's not required to repeat it
return 0;
}

View File

@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "CommandAbstract.h"
class ChannelChangeCommand : public CommandAbstract {
public:
explicit ChannelChangeCommand(uint64_t target_address = 0, uint64_t router_address = 0, uint8_t channel = 0);
virtual String getCommandName();
void setChannel(uint8_t channel);
uint8_t getChannel();
virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id);
virtual uint8_t getMaxResendCount();
};

View File

@ -101,3 +101,13 @@ void CommandAbstract::convertSerialToPacketId(uint8_t buffer[], uint64_t serial)
void CommandAbstract::gotTimeout(InverterAbstract* inverter)
{
}
uint8_t CommandAbstract::getMaxResendCount()
{
return MAX_RESEND_COUNT;
}
uint8_t CommandAbstract::getMaxRetransmitCount()
{
return MAX_RETRANSMIT_COUNT;
}

View File

@ -6,6 +6,8 @@
#include <cstdint>
#define RF_LEN 32
#define MAX_RESEND_COUNT 4 // Used if all packages are missing
#define MAX_RETRANSMIT_COUNT 5 // Used to send the retransmit package
class InverterAbstract;
@ -39,6 +41,12 @@ public:
virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id) = 0;
virtual void gotTimeout(InverterAbstract* inverter);
// Sets the amount how often the specific command is resent if all fragments where missing
virtual uint8_t getMaxResendCount();
// Sets the amount how often a missing fragment is re-requested if it was not available
virtual uint8_t getMaxRetransmitCount();
protected:
uint8_t _payload[RF_LEN];
uint8_t _payload_size;

View File

@ -13,3 +13,4 @@
* ParaSetCommand
* SingleDataCommand
* RequestFrameCommand
* ChannelChangeCommand

View File

@ -10,7 +10,7 @@ RealTimeRunDataCommand::RealTimeRunDataCommand(uint64_t target_address, uint64_t
{
setTime(time);
setDataType(0x0b);
setTimeout(200);
setTimeout(500);
}
String RealTimeRunDataCommand::getCommandName()

View File

@ -0,0 +1,25 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Thomas Basler and others
*/
#include "HMS_1CH.h"
HMS_1CH::HMS_1CH(HoymilesRadio* radio, uint64_t serial)
: HMS_Abstract(radio, serial) {};
bool HMS_1CH::isValidSerial(uint64_t serial)
{
// serial >= 0x112400000000 && serial <= 0x112499999999
uint16_t preSerial = (serial >> 32) & 0xffff;
return preSerial == 0x1124;
}
String HMS_1CH::typeName()
{
return "HMS-300, HMS-350, HMS-400, HMS-450, HMS-500";
}
const std::list<byteAssign_t>* HMS_1CH::getByteAssignment()
{
return &byteAssignment;
}

View File

@ -0,0 +1,38 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "HMS_Abstract.h"
#include <list>
class HMS_1CH : public HMS_Abstract {
public:
explicit HMS_1CH(HoymilesRadio* radio, uint64_t serial);
static bool isValidSerial(uint64_t serial);
String typeName();
const std::list<byteAssign_t>* getByteAssignment();
private:
const std::list<byteAssign_t> byteAssignment = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 6, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 12, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 8, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 14, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 22, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 18, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_PRA, UNIT_VA, 20, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 16, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 24, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 26, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 28, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
};

View File

@ -0,0 +1,25 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Thomas Basler and others
*/
#include "HMS_2CH.h"
HMS_2CH::HMS_2CH(HoymilesRadio* radio, uint64_t serial)
: HMS_Abstract(radio, serial) {};
bool HMS_2CH::isValidSerial(uint64_t serial)
{
// serial >= 0x114400000000 && serial <= 0x114499999999
uint16_t preSerial = (serial >> 32) & 0xffff;
return preSerial == 0x1144;
}
String HMS_2CH::typeName()
{
return "HMS-600, HMS-700, HMS-800, HMS-900, HMS-1000";
}
const std::list<byteAssign_t>* HMS_2CH::getByteAssignment()
{
return &byteAssignment;
}

View File

@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "HMS_Abstract.h"
#include <list>
class HMS_2CH : public HMS_Abstract {
public:
explicit HMS_2CH(HoymilesRadio* radio, uint64_t serial);
static bool isValidSerial(uint64_t serial);
String typeName();
const std::list<byteAssign_t>* getByteAssignment();
private:
const std::list<byteAssign_t> byteAssignment = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 6, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 10, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 14, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ TYPE_DC, CH1, FLD_UDC, UNIT_V, 4, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_IDC, UNIT_A, 8, 2, 100, false, 2 },
{ TYPE_DC, CH1, FLD_PDC, UNIT_W, 12, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_YT, UNIT_KWH, 18, 4, 1000, false, 3 },
{ TYPE_DC, CH1, FLD_YD, UNIT_WH, 24, 2, 1, false, 0 },
{ TYPE_DC, CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 26, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 34, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 30, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_PRA, UNIT_VA, 32, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 28, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 36, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 38, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 40, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
};

View File

@ -0,0 +1,25 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Thomas Basler and others
*/
#include "HMS_4CH.h"
HMS_4CH::HMS_4CH(HoymilesRadio* radio, uint64_t serial)
: HMS_Abstract(radio, serial) {};
bool HMS_4CH::isValidSerial(uint64_t serial)
{
// serial >= 0x114400000000 && serial <= 0x114499999999
uint16_t preSerial = (serial >> 32) & 0xffff;
return preSerial == 0x1164;
}
String HMS_4CH::typeName()
{
return "HMS-1600, HMS-1800, HMS-2000";
}
const std::list<byteAssign_t>* HMS_4CH::getByteAssignment()
{
return &byteAssignment;
}

View File

@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "HMS_Abstract.h"
class HMS_4CH : public HMS_Abstract {
public:
explicit HMS_4CH(HoymilesRadio* radio, uint64_t serial);
static bool isValidSerial(uint64_t serial);
String typeName();
const std::list<byteAssign_t>* getByteAssignment();
private:
const std::list<byteAssign_t> byteAssignment = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 6, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 10, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 14, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ TYPE_DC, CH1, FLD_UDC, UNIT_V, 4, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_IDC, UNIT_A, 8, 2, 100, false, 2 },
{ TYPE_DC, CH1, FLD_PDC, UNIT_W, 12, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_YD, UNIT_WH, 24, 2, 1, false, 0 },
{ TYPE_DC, CH1, FLD_YT, UNIT_KWH, 18, 4, 1000, false, 3 },
{ TYPE_DC, CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
{ TYPE_DC, CH2, FLD_UDC, UNIT_V, 26, 2, 10, false, 1 },
{ TYPE_DC, CH2, FLD_IDC, UNIT_A, 30, 2, 100, false, 2 },
{ TYPE_DC, CH2, FLD_PDC, UNIT_W, 34, 2, 10, false, 1 },
{ TYPE_DC, CH2, FLD_YD, UNIT_WH, 46, 2, 1, false, 0 },
{ TYPE_DC, CH2, FLD_YT, UNIT_KWH, 38, 4, 1000, false, 3 },
{ TYPE_DC, CH2, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH2, CMD_CALC, false, 3 },
{ TYPE_DC, CH3, FLD_UDC, UNIT_V, 28, 2, 10, false, 1 },
{ TYPE_DC, CH3, FLD_IDC, UNIT_A, 32, 2, 100, false, 2 },
{ TYPE_DC, CH3, FLD_PDC, UNIT_W, 36, 2, 10, false, 1 },
{ TYPE_DC, CH3, FLD_YD, UNIT_WH, 48, 2, 1, false, 0 },
{ TYPE_DC, CH3, FLD_YT, UNIT_KWH, 42, 4, 1000, false, 3 },
{ TYPE_DC, CH3, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH3, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 50, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 58, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 54, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_PRA, UNIT_VA, 56, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 52, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 60, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 62, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 64, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
};

View File

@ -0,0 +1,26 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Thomas Basler and others
*/
#include "HMS_Abstract.h"
#include "Hoymiles.h"
#include "HoymilesRadio_CMT.h"
#include "commands/ChannelChangeCommand.h"
HMS_Abstract::HMS_Abstract(HoymilesRadio* radio, uint64_t serial)
: HM_Abstract(radio, serial)
{
}
bool HMS_Abstract::sendChangeChannelRequest()
{
if (!(getEnableCommands() && getEnablePolling())) {
return false;
}
ChannelChangeCommand* cmdChannel = _radio->enqueCommand<ChannelChangeCommand>();
cmdChannel->setChannel(HoymilesRadio_CMT::getChannelFromFrequency(Hoymiles.getRadioCmt()->getInverterTargetFrequency()));
cmdChannel->setTargetAddress(serial());
return true;
};

View File

@ -0,0 +1,11 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "HM_Abstract.h"
class HMS_Abstract : public HM_Abstract {
public:
explicit HMS_Abstract(HoymilesRadio* radio, uint64_t serial);
virtual bool sendChangeChannelRequest();
};

View File

@ -0,0 +1,25 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Thomas Basler and others
*/
#include "HMT_6CH.h"
HMT_6CH::HMT_6CH(HoymilesRadio* radio, uint64_t serial)
: HMT_Abstract(radio, serial) {};
bool HMT_6CH::isValidSerial(uint64_t serial)
{
// serial >= 0x138200000000 && serial <= 0x138299999999
uint16_t preSerial = (serial >> 32) & 0xffff;
return preSerial == 0x1382;
}
String HMT_6CH::typeName()
{
return F("HMT-1800, HMT-2250");
}
const std::list<byteAssign_t>* HMT_6CH::getByteAssignment()
{
return &byteAssignment;
}

View File

@ -0,0 +1,81 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "HMT_Abstract.h"
class HMT_6CH : public HMT_Abstract {
public:
explicit HMT_6CH(HoymilesRadio* radio, uint64_t serial);
static bool isValidSerial(uint64_t serial);
String typeName();
const std::list<byteAssign_t>* getByteAssignment();
private:
const std::list<byteAssign_t> byteAssignment = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 8, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 12, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 20, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ TYPE_DC, CH1, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_IDC, UNIT_A, 6, 2, 100, false, 2 },
{ TYPE_DC, CH1, FLD_PDC, UNIT_W, 10, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_YT, UNIT_KWH, 16, 4, 1000, false, 3 },
{ TYPE_DC, CH1, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
{ TYPE_DC, CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
{ TYPE_DC, CH2, FLD_UDC, UNIT_V, 24, 2, 10, false, 1 },
{ TYPE_DC, CH2, FLD_IDC, UNIT_A, 26, 2, 100, false, 2 },
{ TYPE_DC, CH2, FLD_PDC, UNIT_W, 30, 2, 10, false, 1 },
{ TYPE_DC, CH2, FLD_YT, UNIT_KWH, 34, 4, 1000, false, 3 },
{ TYPE_DC, CH2, FLD_YD, UNIT_WH, 42, 2, 1, false, 0 },
{ TYPE_DC, CH2, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH2, CMD_CALC, false, 3 },
{ TYPE_DC, CH3, FLD_UDC, UNIT_V, 24, 2, 10, false, 1 },
{ TYPE_DC, CH3, FLD_IDC, UNIT_A, 28, 2, 100, false, 2 },
{ TYPE_DC, CH3, FLD_PDC, UNIT_W, 32, 2, 10, false, 1 },
{ TYPE_DC, CH3, FLD_YT, UNIT_KWH, 38, 4, 1000, false, 3 },
{ TYPE_DC, CH3, FLD_YD, UNIT_WH, 44, 2, 1, false, 0 },
{ TYPE_DC, CH3, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH3, CMD_CALC, false, 3 },
{ TYPE_DC, CH4, FLD_UDC, UNIT_V, 46, 2, 10, false, 1 },
{ TYPE_DC, CH4, FLD_IDC, UNIT_A, 48, 2, 100, false, 2 },
{ TYPE_DC, CH4, FLD_PDC, UNIT_W, 52, 2, 10, false, 1 },
{ TYPE_DC, CH4, FLD_YT, UNIT_KWH, 56, 4, 1000, false, 3 },
{ TYPE_DC, CH4, FLD_YD, UNIT_WH, 64, 2, 1, false, 0 },
{ TYPE_DC, CH4, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH4, CMD_CALC, false, 3 },
{ TYPE_DC, CH5, FLD_UDC, UNIT_V, 46, 2, 10, false, 1 },
{ TYPE_DC, CH5, FLD_IDC, UNIT_A, 50, 2, 100, false, 2 },
{ TYPE_DC, CH5, FLD_PDC, UNIT_W, 54, 2, 10, false, 1 },
{ TYPE_DC, CH5, FLD_YT, UNIT_KWH, 60, 4, 1000, false, 3 },
{ TYPE_DC, CH5, FLD_YD, UNIT_WH, 66, 2, 1, false, 0 },
{ TYPE_DC, CH5, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH5, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 74, 2, 10, false, 1 }, // dummy
{ TYPE_AC, CH0, FLD_UAC_1N, UNIT_V, 68, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_UAC_2N, UNIT_V, 70, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_UAC_3N, UNIT_V, 72, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_UAC_12, UNIT_V, 74, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_UAC_23, UNIT_V, 76, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_UAC_31, UNIT_V, 78, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 80, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 82, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_PRA, UNIT_VA, 84, 2, 10, true, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 86, 2, 100, false, 2 }, // dummy
{ TYPE_AC, CH0, FLD_IAC_1, UNIT_A, 86, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_IAC_2, UNIT_A, 88, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_IAC_3, UNIT_A, 90, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 92, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 94, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 96, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
};

View File

@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Thomas Basler and others
*/
#include "HMT_Abstract.h"
#include "Hoymiles.h"
#include "HoymilesRadio_CMT.h"
#include "commands/ChannelChangeCommand.h"
#include "parser/AlarmLogParser.h"
HMT_Abstract::HMT_Abstract(HoymilesRadio* radio, uint64_t serial)
: HM_Abstract(radio, serial)
{
EventLog()->setMessageType(AlarmMessageType_t::HMT);
};
bool HMT_Abstract::sendChangeChannelRequest()
{
if (!(getEnableCommands() && getEnablePolling())) {
return false;
}
ChannelChangeCommand* cmdChannel = _radio->enqueCommand<ChannelChangeCommand>();
cmdChannel->setChannel(HoymilesRadio_CMT::getChannelFromFrequency(Hoymiles.getRadioCmt()->getInverterTargetFrequency()));
cmdChannel->setTargetAddress(serial());
return true;
};

View File

@ -0,0 +1,11 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "HM_Abstract.h"
class HMT_Abstract : public HM_Abstract {
public:
explicit HMT_Abstract(HoymilesRadio* radio, uint64_t serial);
virtual bool sendChangeChannelRequest();
};

View File

@ -4,8 +4,8 @@
*/
#include "HM_1CH.h"
HM_1CH::HM_1CH(uint64_t serial)
: HM_Abstract(serial) {};
HM_1CH::HM_1CH(HoymilesRadio* radio, uint64_t serial)
: HM_Abstract(radio, serial) {};
bool HM_1CH::isValidSerial(uint64_t serial)
{

View File

@ -6,7 +6,7 @@
class HM_1CH : public HM_Abstract {
public:
explicit HM_1CH(uint64_t serial);
explicit HM_1CH(HoymilesRadio* radio, uint64_t serial);
static bool isValidSerial(uint64_t serial);
String typeName();
const std::list<byteAssign_t>* getByteAssignment();

View File

@ -5,8 +5,8 @@
*/
#include "HM_2CH.h"
HM_2CH::HM_2CH(uint64_t serial)
: HM_Abstract(serial) {};
HM_2CH::HM_2CH(HoymilesRadio* radio, uint64_t serial)
: HM_Abstract(radio, serial) {};
bool HM_2CH::isValidSerial(uint64_t serial)
{

View File

@ -5,7 +5,7 @@
class HM_2CH : public HM_Abstract {
public:
explicit HM_2CH(uint64_t serial);
explicit HM_2CH(HoymilesRadio* radio, uint64_t serial);
static bool isValidSerial(uint64_t serial);
String typeName();
const std::list<byteAssign_t>* getByteAssignment();

View File

@ -4,8 +4,8 @@
*/
#include "HM_4CH.h"
HM_4CH::HM_4CH(uint64_t serial)
: HM_Abstract(serial) {};
HM_4CH::HM_4CH(HoymilesRadio* radio, uint64_t serial)
: HM_Abstract(radio, serial) {};
bool HM_4CH::isValidSerial(uint64_t serial)
{

View File

@ -5,7 +5,7 @@
class HM_4CH : public HM_Abstract {
public:
explicit HM_4CH(uint64_t serial);
explicit HM_4CH(HoymilesRadio* radio, uint64_t serial);
static bool isValidSerial(uint64_t serial);
String typeName();
const std::list<byteAssign_t>* getByteAssignment();

View File

@ -12,10 +12,10 @@
#include "commands/RealTimeRunDataCommand.h"
#include "commands/SystemConfigParaCommand.h"
HM_Abstract::HM_Abstract(uint64_t serial)
: InverterAbstract(serial) {};
HM_Abstract::HM_Abstract(HoymilesRadio* radio, uint64_t serial)
: InverterAbstract(radio, serial) {};
bool HM_Abstract::sendStatsRequest(HoymilesRadio* radio)
bool HM_Abstract::sendStatsRequest()
{
if (!getEnablePolling()) {
return false;
@ -29,14 +29,14 @@ bool HM_Abstract::sendStatsRequest(HoymilesRadio* radio)
time_t now;
time(&now);
RealTimeRunDataCommand* cmd = radio->enqueCommand<RealTimeRunDataCommand>();
RealTimeRunDataCommand* cmd = _radio->enqueCommand<RealTimeRunDataCommand>();
cmd->setTime(now);
cmd->setTargetAddress(serial());
return true;
}
bool HM_Abstract::sendAlarmLogRequest(HoymilesRadio* radio, bool force)
bool HM_Abstract::sendAlarmLogRequest(bool force)
{
if (!getEnablePolling()) {
return false;
@ -60,7 +60,7 @@ bool HM_Abstract::sendAlarmLogRequest(HoymilesRadio* radio, bool force)
time_t now;
time(&now);
AlarmDataCommand* cmd = radio->enqueCommand<AlarmDataCommand>();
AlarmDataCommand* cmd = _radio->enqueCommand<AlarmDataCommand>();
cmd->setTime(now);
cmd->setTargetAddress(serial());
EventLog()->setLastAlarmRequestSuccess(CMD_PENDING);
@ -68,7 +68,7 @@ bool HM_Abstract::sendAlarmLogRequest(HoymilesRadio* radio, bool force)
return true;
}
bool HM_Abstract::sendDevInfoRequest(HoymilesRadio* radio)
bool HM_Abstract::sendDevInfoRequest()
{
if (!getEnablePolling()) {
return false;
@ -82,18 +82,18 @@ bool HM_Abstract::sendDevInfoRequest(HoymilesRadio* radio)
time_t now;
time(&now);
DevInfoAllCommand* cmdAll = radio->enqueCommand<DevInfoAllCommand>();
DevInfoAllCommand* cmdAll = _radio->enqueCommand<DevInfoAllCommand>();
cmdAll->setTime(now);
cmdAll->setTargetAddress(serial());
DevInfoSimpleCommand* cmdSimple = radio->enqueCommand<DevInfoSimpleCommand>();
DevInfoSimpleCommand* cmdSimple = _radio->enqueCommand<DevInfoSimpleCommand>();
cmdSimple->setTime(now);
cmdSimple->setTargetAddress(serial());
return true;
}
bool HM_Abstract::sendSystemConfigParaRequest(HoymilesRadio* radio)
bool HM_Abstract::sendSystemConfigParaRequest()
{
if (!getEnablePolling()) {
return false;
@ -107,7 +107,7 @@ bool HM_Abstract::sendSystemConfigParaRequest(HoymilesRadio* radio)
time_t now;
time(&now);
SystemConfigParaCommand* cmd = radio->enqueCommand<SystemConfigParaCommand>();
SystemConfigParaCommand* cmd = _radio->enqueCommand<SystemConfigParaCommand>();
cmd->setTime(now);
cmd->setTargetAddress(serial());
SystemConfigPara()->setLastLimitRequestSuccess(CMD_PENDING);
@ -115,7 +115,7 @@ bool HM_Abstract::sendSystemConfigParaRequest(HoymilesRadio* radio)
return true;
}
bool HM_Abstract::sendActivePowerControlRequest(HoymilesRadio* radio, float limit, PowerLimitControlType type)
bool HM_Abstract::sendActivePowerControlRequest(float limit, PowerLimitControlType type)
{
if (!getEnableCommands()) {
return false;
@ -128,7 +128,7 @@ bool HM_Abstract::sendActivePowerControlRequest(HoymilesRadio* radio, float limi
_activePowerControlLimit = limit;
_activePowerControlType = type;
ActivePowerControlCommand* cmd = radio->enqueCommand<ActivePowerControlCommand>();
ActivePowerControlCommand* cmd = _radio->enqueCommand<ActivePowerControlCommand>();
cmd->setActivePowerLimit(limit, type);
cmd->setTargetAddress(serial());
SystemConfigPara()->setLastLimitCommandSuccess(CMD_PENDING);
@ -136,12 +136,12 @@ bool HM_Abstract::sendActivePowerControlRequest(HoymilesRadio* radio, float limi
return true;
}
bool HM_Abstract::resendActivePowerControlRequest(HoymilesRadio* radio)
bool HM_Abstract::resendActivePowerControlRequest()
{
return sendActivePowerControlRequest(radio, _activePowerControlLimit, _activePowerControlType);
return sendActivePowerControlRequest(_activePowerControlLimit, _activePowerControlType);
}
bool HM_Abstract::sendPowerControlRequest(HoymilesRadio* radio, bool turnOn)
bool HM_Abstract::sendPowerControlRequest(bool turnOn)
{
if (!getEnableCommands()) {
return false;
@ -153,7 +153,7 @@ bool HM_Abstract::sendPowerControlRequest(HoymilesRadio* radio, bool turnOn)
_powerState = 0;
}
PowerControlCommand* cmd = radio->enqueCommand<PowerControlCommand>();
PowerControlCommand* cmd = _radio->enqueCommand<PowerControlCommand>();
cmd->setPowerOn(turnOn);
cmd->setTargetAddress(serial());
PowerCommand()->setLastPowerCommandSuccess(CMD_PENDING);
@ -161,7 +161,7 @@ bool HM_Abstract::sendPowerControlRequest(HoymilesRadio* radio, bool turnOn)
return true;
}
bool HM_Abstract::sendRestartControlRequest(HoymilesRadio* radio)
bool HM_Abstract::sendRestartControlRequest()
{
if (!getEnableCommands()) {
return false;
@ -169,7 +169,7 @@ bool HM_Abstract::sendRestartControlRequest(HoymilesRadio* radio)
_powerState = 2;
PowerControlCommand* cmd = radio->enqueCommand<PowerControlCommand>();
PowerControlCommand* cmd = _radio->enqueCommand<PowerControlCommand>();
cmd->setRestart();
cmd->setTargetAddress(serial());
PowerCommand()->setLastPowerCommandSuccess(CMD_PENDING);
@ -177,17 +177,17 @@ bool HM_Abstract::sendRestartControlRequest(HoymilesRadio* radio)
return true;
}
bool HM_Abstract::resendPowerControlRequest(HoymilesRadio* radio)
bool HM_Abstract::resendPowerControlRequest()
{
switch (_powerState) {
case 0:
return sendPowerControlRequest(radio, false);
return sendPowerControlRequest(false);
break;
case 1:
return sendPowerControlRequest(radio, true);
return sendPowerControlRequest(true);
break;
case 2:
return sendRestartControlRequest(radio);
return sendRestartControlRequest();
break;
default:

View File

@ -5,16 +5,16 @@
class HM_Abstract : public InverterAbstract {
public:
explicit HM_Abstract(uint64_t serial);
bool sendStatsRequest(HoymilesRadio* radio);
bool sendAlarmLogRequest(HoymilesRadio* radio, bool force = false);
bool sendDevInfoRequest(HoymilesRadio* radio);
bool sendSystemConfigParaRequest(HoymilesRadio* radio);
bool sendActivePowerControlRequest(HoymilesRadio* radio, float limit, PowerLimitControlType type);
bool resendActivePowerControlRequest(HoymilesRadio* radio);
bool sendPowerControlRequest(HoymilesRadio* radio, bool turnOn);
bool sendRestartControlRequest(HoymilesRadio* radio);
bool resendPowerControlRequest(HoymilesRadio* radio);
explicit HM_Abstract(HoymilesRadio* radio, uint64_t serial);
bool sendStatsRequest();
bool sendAlarmLogRequest(bool force = false);
bool sendDevInfoRequest();
bool sendSystemConfigParaRequest();
bool sendActivePowerControlRequest(float limit, PowerLimitControlType type);
bool resendActivePowerControlRequest();
bool sendPowerControlRequest(bool turnOn);
bool sendRestartControlRequest();
bool resendPowerControlRequest();
private:
uint8_t _lastAlarmLogCnt = 0;

View File

@ -7,9 +7,10 @@
#include "crc.h"
#include <cstring>
InverterAbstract::InverterAbstract(uint64_t serial)
InverterAbstract::InverterAbstract(HoymilesRadio *radio, uint64_t serial)
{
_serial.u64 = serial;
_radio = radio;
char serial_buff[sizeof(uint64_t) * 8 + 1];
snprintf(serial_buff, sizeof(serial_buff), "%0x%08x",
@ -95,6 +96,16 @@ bool InverterAbstract::getEnableCommands()
return _enableCommands;
}
bool InverterAbstract::sendChangeChannelRequest()
{
return false;
}
HoymilesRadio* InverterAbstract::getRadio()
{
return _radio;
}
AlarmLogParser* InverterAbstract::EventLog()
{
return _alarmLogParser.get();
@ -170,7 +181,7 @@ uint8_t InverterAbstract::verifyAllFragments(CommandAbstract* cmd)
// All missing
if (_rxFragmentLastPacketId == 0) {
Hoymiles.getMessageOutput()->println("All missing");
if (cmd->getSendCount() <= MAX_RESEND_COUNT) {
if (cmd->getSendCount() <= cmd->getMaxResendCount()) {
return FRAGMENT_ALL_MISSING_RESEND;
} else {
cmd->gotTimeout(this);
@ -181,7 +192,7 @@ uint8_t InverterAbstract::verifyAllFragments(CommandAbstract* cmd)
// Last fragment is missing (the one with 0x80)
if (_rxFragmentMaxPacketId == 0) {
Hoymiles.getMessageOutput()->println("Last missing");
if (_rxFragmentRetransmitCnt++ < MAX_RETRANSMIT_COUNT) {
if (_rxFragmentRetransmitCnt++ < cmd->getMaxRetransmitCount()) {
return _rxFragmentLastPacketId + 1;
} else {
cmd->gotTimeout(this);
@ -193,7 +204,7 @@ uint8_t InverterAbstract::verifyAllFragments(CommandAbstract* cmd)
for (uint8_t i = 0; i < _rxFragmentMaxPacketId - 1; i++) {
if (!_rxFragmentBuffer[i].wasReceived) {
Hoymiles.getMessageOutput()->println("Middle missing");
if (_rxFragmentRetransmitCnt++ < MAX_RETRANSMIT_COUNT) {
if (_rxFragmentRetransmitCnt++ < cmd->getMaxRetransmitCount()) {
return i + 1;
} else {
cmd->gotTimeout(this);

View File

@ -24,15 +24,13 @@ enum {
};
#define MAX_RF_FRAGMENT_COUNT 13
#define MAX_RETRANSMIT_COUNT 5 // Used to send the retransmit package
#define MAX_RESEND_COUNT 4 // Used if all packages are missing
#define MAX_ONLINE_FAILURE_COUNT 2
class CommandAbstract;
class InverterAbstract {
public:
explicit InverterAbstract(uint64_t serial);
explicit InverterAbstract(HoymilesRadio* radio, uint64_t serial);
void init();
uint64_t serial();
const String& serialString();
@ -54,15 +52,18 @@ public:
void addRxFragment(uint8_t fragment[], uint8_t len);
uint8_t verifyAllFragments(CommandAbstract* cmd);
virtual bool sendStatsRequest(HoymilesRadio* radio) = 0;
virtual bool sendAlarmLogRequest(HoymilesRadio* radio, bool force = false) = 0;
virtual bool sendDevInfoRequest(HoymilesRadio* radio) = 0;
virtual bool sendSystemConfigParaRequest(HoymilesRadio* radio) = 0;
virtual bool sendActivePowerControlRequest(HoymilesRadio* radio, float limit, PowerLimitControlType type) = 0;
virtual bool resendActivePowerControlRequest(HoymilesRadio* radio) = 0;
virtual bool sendPowerControlRequest(HoymilesRadio* radio, bool turnOn) = 0;
virtual bool sendRestartControlRequest(HoymilesRadio* radio) = 0;
virtual bool resendPowerControlRequest(HoymilesRadio* radio) = 0;
virtual bool sendStatsRequest() = 0;
virtual bool sendAlarmLogRequest(bool force = false) = 0;
virtual bool sendDevInfoRequest() = 0;
virtual bool sendSystemConfigParaRequest() = 0;
virtual bool sendActivePowerControlRequest(float limit, PowerLimitControlType type) = 0;
virtual bool resendActivePowerControlRequest() = 0;
virtual bool sendPowerControlRequest(bool turnOn) = 0;
virtual bool sendRestartControlRequest() = 0;
virtual bool resendPowerControlRequest() = 0;
virtual bool sendChangeChannelRequest();
HoymilesRadio* getRadio();
AlarmLogParser* EventLog();
DevInfoParser* DevInfo();
@ -70,6 +71,9 @@ public:
StatisticsParser* Statistics();
SystemConfigParaParser* SystemConfigPara();
protected:
HoymilesRadio* _radio;
private:
serial_u _serial;
String _serialString;

View File

@ -1,11 +1,90 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler and others
* Copyright (C) 2022-2023 Thomas Basler and others
*/
#include "AlarmLogParser.h"
#include "../Hoymiles.h"
#include <cstring>
const std::array<const AlarmMessage_t, 76> AlarmLogParser::_alarmMessages = {{
{ AlarmMessageType_t::ALL, 1, "Inverter start" },
{ AlarmMessageType_t::ALL, 2, "DTU command failed" },
{ AlarmMessageType_t::ALL, 121, "Over temperature protection" },
{ AlarmMessageType_t::ALL, 124, "Shut down by remote control" },
{ AlarmMessageType_t::ALL, 125, "Grid configuration parameter error" },
{ AlarmMessageType_t::ALL, 126, "Software error code 126" },
{ AlarmMessageType_t::ALL, 127, "Firmware error" },
{ AlarmMessageType_t::ALL, 128, "Software error code 128" },
{ AlarmMessageType_t::ALL, 129, "Abnormal bias" },
{ AlarmMessageType_t::ALL, 130, "Offline" },
{ AlarmMessageType_t::ALL, 141, "Grid: Grid overvoltage" },
{ AlarmMessageType_t::ALL, 142, "Grid: 10 min value grid overvoltage" },
{ AlarmMessageType_t::ALL, 143, "Grid: Grid undervoltage" },
{ AlarmMessageType_t::ALL, 144, "Grid: Grid overfrequency" },
{ AlarmMessageType_t::ALL, 145, "Grid: Grid underfrequency" },
{ AlarmMessageType_t::ALL, 146, "Grid: Rapid grid frequency change rate" },
{ AlarmMessageType_t::ALL, 147, "Grid: Power grid outage" },
{ AlarmMessageType_t::ALL, 148, "Grid: Grid disconnection" },
{ AlarmMessageType_t::ALL, 149, "Grid: Island detected" },
{ AlarmMessageType_t::HMT, 171, "Grid: Abnormal phase difference between phase to phase" },
{ AlarmMessageType_t::ALL, 205, "MPPT-A: Input overvoltage" },
{ AlarmMessageType_t::ALL, 206, "MPPT-B: Input overvoltage" },
{ AlarmMessageType_t::ALL, 207, "MPPT-A: Input undervoltage" },
{ AlarmMessageType_t::ALL, 208, "MPPT-B: Input undervoltage" },
{ AlarmMessageType_t::ALL, 209, "PV-1: No input" },
{ AlarmMessageType_t::ALL, 210, "PV-2: No input" },
{ AlarmMessageType_t::ALL, 211, "PV-3: No input" },
{ AlarmMessageType_t::ALL, 212, "PV-4: No input" },
{ AlarmMessageType_t::ALL, 213, "MPPT-A: PV-1 & PV-2 abnormal wiring" },
{ AlarmMessageType_t::ALL, 214, "MPPT-B: PV-3 & PV-4 abnormal wiring" },
{ AlarmMessageType_t::ALL, 215, "PV-1: Input overvoltage" },
{ AlarmMessageType_t::HMT, 215, "MPPT-C: Input overvoltage" },
{ AlarmMessageType_t::ALL, 216, "PV-1: Input undervoltage" },
{ AlarmMessageType_t::HMT, 216, "MPPT-C: Input undervoltage" },
{ AlarmMessageType_t::ALL, 217, "PV-2: Input overvoltage" },
{ AlarmMessageType_t::HMT, 217, "PV-5: No input" },
{ AlarmMessageType_t::ALL, 218, "PV-2: Input undervoltage" },
{ AlarmMessageType_t::HMT, 218, "PV-6: No input" },
{ AlarmMessageType_t::ALL, 219, "PV-3: Input overvoltage" },
{ AlarmMessageType_t::HMT, 219, "MPPT-C: PV-5 & PV-6 abnormal wiring" },
{ AlarmMessageType_t::ALL, 220, "PV-3: Input undervoltage" },
{ AlarmMessageType_t::ALL, 221, "PV-4: Input overvoltage" },
{ AlarmMessageType_t::HMT, 221, "Abnormal wiring of grid neutral line" },
{ AlarmMessageType_t::ALL, 222, "PV-4: Input undervoltage" },
{ AlarmMessageType_t::ALL, 301, "Hardware error code 301" },
{ AlarmMessageType_t::ALL, 302, "Hardware error code 302" },
{ AlarmMessageType_t::ALL, 303, "Hardware error code 303" },
{ AlarmMessageType_t::ALL, 304, "Hardware error code 304" },
{ AlarmMessageType_t::ALL, 305, "Hardware error code 305" },
{ AlarmMessageType_t::ALL, 306, "Hardware error code 306" },
{ AlarmMessageType_t::ALL, 307, "Hardware error code 307" },
{ AlarmMessageType_t::ALL, 308, "Hardware error code 308" },
{ AlarmMessageType_t::ALL, 309, "Hardware error code 309" },
{ AlarmMessageType_t::ALL, 310, "Hardware error code 310" },
{ AlarmMessageType_t::ALL, 311, "Hardware error code 311" },
{ AlarmMessageType_t::ALL, 312, "Hardware error code 312" },
{ AlarmMessageType_t::ALL, 313, "Hardware error code 313" },
{ AlarmMessageType_t::ALL, 314, "Hardware error code 314" },
{ AlarmMessageType_t::ALL, 5041, "Error code-04 Port 1" },
{ AlarmMessageType_t::ALL, 5042, "Error code-04 Port 2" },
{ AlarmMessageType_t::ALL, 5043, "Error code-04 Port 3" },
{ AlarmMessageType_t::ALL, 5044, "Error code-04 Port 4" },
{ AlarmMessageType_t::ALL, 5051, "PV Input 1 Overvoltage/Undervoltage" },
{ AlarmMessageType_t::ALL, 5052, "PV Input 2 Overvoltage/Undervoltage" },
{ AlarmMessageType_t::ALL, 5053, "PV Input 3 Overvoltage/Undervoltage" },
{ AlarmMessageType_t::ALL, 5054, "PV Input 4 Overvoltage/Undervoltage" },
{ AlarmMessageType_t::ALL, 5060, "Abnormal bias" },
{ AlarmMessageType_t::ALL, 5070, "Over temperature protection" },
{ AlarmMessageType_t::ALL, 5080, "Grid Overvoltage/Undervoltage" },
{ AlarmMessageType_t::ALL, 5090, "Grid Overfrequency/Underfrequency" },
{ AlarmMessageType_t::ALL, 5100, "Island detected" },
{ AlarmMessageType_t::ALL, 5120, "EEPROM reading and writing error" },
{ AlarmMessageType_t::ALL, 5150, "10 min value grid overvoltage" },
{ AlarmMessageType_t::ALL, 5200, "Firmware error" },
{ AlarmMessageType_t::ALL, 8310, "Shut down" },
{ AlarmMessageType_t::ALL, 9000, "Microinverter is suspected of being stolen" },
}};
void AlarmLogParser::clearBuffer()
{
memset(_payloadAlarmLog, 0, ALARM_LOG_PAYLOAD_SIZE);
@ -37,6 +116,11 @@ LastCommandSuccess AlarmLogParser::getLastAlarmRequestSuccess()
return _lastAlarmRequestSuccess;
}
void AlarmLogParser::setMessageType(AlarmMessageType_t type)
{
_messageType = type;
}
void AlarmLogParser::getLogEntry(uint8_t entryId, AlarmLogEntry_t* entry)
{
uint8_t entryStartOffset = 2 + entryId * ALARM_LOG_ENTRY_SIZE;
@ -62,217 +146,16 @@ void AlarmLogParser::getLogEntry(uint8_t entryId, AlarmLogEntry_t* entry)
entry->EndTime += (endTimeOffset + timezoneOffset);
}
switch (entry->MessageId) {
case 1:
entry->Message = "Inverter start";
break;
case 2:
entry->Message = "DTU command failed";
break;
case 121:
entry->Message = "Over temperature protection";
break;
case 124:
entry->Message = "Shut down by remote control";
break;
case 125:
entry->Message = "Grid configuration parameter error";
break;
case 126:
entry->Message = "Software error code 126";
break;
case 127:
entry->Message = "Firmware error";
break;
case 128:
entry->Message = "Software error code 128";
break;
case 129:
entry->Message = "Abnormal bias";
break;
case 130:
entry->Message = "Offline";
break;
case 141:
entry->Message = "Grid: Grid overvoltage";
break;
case 142:
entry->Message = "Grid: 10 min value grid overvoltage";
break;
case 143:
entry->Message = "Grid: Grid undervoltage";
break;
case 144:
entry->Message = "Grid: Grid overfrequency";
break;
case 145:
entry->Message = "Grid: Grid underfrequency";
break;
case 146:
entry->Message = "Grid: Rapid grid frequency change rate";
break;
case 147:
entry->Message = "Grid: Power grid outage";
break;
case 148:
entry->Message = "Grid: Grid disconnection";
break;
case 149:
entry->Message = "Grid: Island detected";
break;
case 205:
entry->Message = "MPPT-A: Input overvoltage";
break;
case 206:
entry->Message = "MPPT-B: Input overvoltage";
break;
case 207:
entry->Message = "MPPT-A: Input undervoltage";
break;
case 208:
entry->Message = "MPPT-B: Input undervoltage";
break;
case 209:
entry->Message = "PV-1: No input";
break;
case 210:
entry->Message = "PV-2: No input";
break;
case 211:
entry->Message = "PV-3: No input";
break;
case 212:
entry->Message = "PV-4: No input";
break;
case 213:
entry->Message = "MPPT-A: PV-1 & PV-2 abnormal wiring";
break;
case 214:
entry->Message = "MPPT-B: PV-3 & PV-4 abnormal wiring";
break;
case 215:
entry->Message = "PV-1: Input overvoltage";
break;
case 216:
entry->Message = "PV-1: Input undervoltage";
break;
case 217:
entry->Message = "PV-2: Input overvoltage";
break;
case 218:
entry->Message = "PV-2: Input undervoltage";
break;
case 219:
entry->Message = "PV-3: Input overvoltage";
break;
case 220:
entry->Message = "PV-3: Input undervoltage";
break;
case 221:
entry->Message = "PV-4: Input overvoltage";
break;
case 222:
entry->Message = "PV-4: Input undervoltage";
break;
case 301:
entry->Message = "Hardware error code 301";
break;
case 302:
entry->Message = "Hardware error code 302";
break;
case 303:
entry->Message = "Hardware error code 303";
break;
case 304:
entry->Message = "Hardware error code 304";
break;
case 305:
entry->Message = "Hardware error code 305";
break;
case 306:
entry->Message = "Hardware error code 306";
break;
case 307:
entry->Message = "Hardware error code 307";
break;
case 308:
entry->Message = "Hardware error code 308";
break;
case 309:
entry->Message = "Hardware error code 309";
break;
case 310:
entry->Message = "Hardware error code 310";
break;
case 311:
entry->Message = "Hardware error code 311";
break;
case 312:
entry->Message = "Hardware error code 312";
break;
case 313:
entry->Message = "Hardware error code 313";
break;
case 314:
entry->Message = "Hardware error code 314";
break;
case 5041:
entry->Message = "Error code-04 Port 1";
break;
case 5042:
entry->Message = "Error code-04 Port 2";
break;
case 5043:
entry->Message = "Error code-04 Port 3";
break;
case 5044:
entry->Message = "Error code-04 Port 4";
break;
case 5051:
entry->Message = "PV Input 1 Overvoltage/Undervoltage";
break;
case 5052:
entry->Message = "PV Input 2 Overvoltage/Undervoltage";
break;
case 5053:
entry->Message = "PV Input 3 Overvoltage/Undervoltage";
break;
case 5054:
entry->Message = "PV Input 4 Overvoltage/Undervoltage";
break;
case 5060:
entry->Message = "Abnormal bias";
break;
case 5070:
entry->Message = "Over temperature protection";
break;
case 5080:
entry->Message = "Grid Overvoltage/Undervoltage";
break;
case 5090:
entry->Message = "Grid Overfrequency/Underfrequency";
break;
case 5100:
entry->Message = "Island detected";
break;
case 5120:
entry->Message = "EEPROM reading and writing error";
break;
case 5150:
entry->Message = "10 min value grid overvoltage";
break;
case 5200:
entry->Message = "Firmware error";
break;
case 8310:
entry->Message = "Shut down";
break;
case 9000:
entry->Message = "Microinverter is suspected of being stolen";
break;
default:
entry->Message = "Unknown";
break;
entry->Message = "Unknown";
for (auto& msg : _alarmMessages) {
if (msg.MessageId == entry->MessageId) {
if (msg.InverterType == _messageType) {
entry->Message = msg.Message;
break;
} else if (msg.InverterType == AlarmMessageType_t::ALL) {
entry->Message = msg.Message;
}
}
}
}

View File

@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Parser.h"
#include <cstdint>
#include <Arduino.h>
#include <cstdint>
#include <array>
#define ALARM_LOG_ENTRY_COUNT 15
#define ALARM_LOG_ENTRY_SIZE 12
@ -15,6 +16,17 @@ struct AlarmLogEntry_t {
time_t EndTime;
};
enum class AlarmMessageType_t {
ALL = 0,
HMT
};
typedef struct {
AlarmMessageType_t InverterType;
uint16_t MessageId;
String Message;
} AlarmMessage_t;
class AlarmLogParser : public Parser {
public:
void clearBuffer();
@ -26,6 +38,8 @@ public:
void setLastAlarmRequestSuccess(LastCommandSuccess status);
LastCommandSuccess getLastAlarmRequestSuccess();
void setMessageType(AlarmMessageType_t type);
private:
static int getTimezoneOffset();
@ -33,4 +47,8 @@ private:
uint8_t _alarmLogLength;
LastCommandSuccess _lastAlarmRequestSuccess = CMD_NOK; // Set to NOK to fetch at startup
AlarmMessageType_t _messageType = AlarmMessageType_t::ALL;
static const std::array<const AlarmMessage_t, 76> _alarmMessages;
};

View File

@ -27,6 +27,17 @@ const devInfo_t devInfo[] = {
{ { 0x10, 0x02, 0x30, ALL }, 1500, "MI-1500 Gen3" },
{ { 0x10, 0x12, 0x30, ALL }, 1500, "HM-1500" },
{ { 0x10, 0x10, 0x10, 0x15 }, static_cast<uint16_t>(300 * 0.7), "HM-300" }, // HM-300 factory limitted to 70%
{ { 0x10, 0x20, 0x21, ALL }, 350, "HMS-350" }, // 00
{ { 0x10, 0x10, 0x71, ALL }, 500, "HMS-500" }, // 02
{ { 0x10, 0x21, 0x41, ALL }, 800, "HMS-800" }, // 00
{ { 0x10, 0x21, 0x71, ALL }, 1000, "HMS-1000" }, // 05
{ { 0x10, 0x12, 0x51, ALL }, 1800, "HMS-1800" }, // 01
{ { 0x10, 0x22, 0x51, ALL }, 1800, "HMS-1800" }, // 16
{ { 0x10, 0x12, 0x71, ALL }, 2000, "HMS-2000" }, // 01
{ { 0x10, 0x33, 0x11, ALL }, 1800, "HMT-1800" }, // 01
{ { 0x10, 0x33, 0x31, ALL }, 2250, "HMT-2250" } // 01
};
void DevInfoParser::clearBufferAll()

View File

@ -5,7 +5,7 @@
#include <cstdint>
#include <list>
#define STATISTIC_PACKET_SIZE (4 * 16)
#define STATISTIC_PACKET_SIZE (7 * 16)
// units
enum UnitId_t {
@ -38,10 +38,21 @@ enum FieldId_t {
FLD_EFF,
FLD_IRR,
FLD_PRA,
FLD_EVT_LOG
FLD_EVT_LOG,
// HMT only
FLD_UAC_1N,
FLD_UAC_2N,
FLD_UAC_3N,
FLD_UAC_12,
FLD_UAC_23,
FLD_UAC_31,
FLD_IAC_1,
FLD_IAC_2,
FLD_IAC_3
};
const char* const fields[] = { "Voltage", "Current", "Power", "YieldDay", "YieldTotal",
"Voltage", "Current", "Power", "Frequency", "Temperature", "PowerFactor", "Efficiency", "Irradiation", "ReactivePower", "EventLogCount" };
"Voltage", "Current", "Power", "Frequency", "Temperature", "PowerFactor", "Efficiency", "Irradiation", "ReactivePower", "EventLogCount",
"Voltage Ph1-N", "Voltage Ph2-N", "Voltage Ph3-N", "Voltage Ph1-Ph2", "Voltage Ph2-Ph3", "Voltage Ph3-Ph1", "Current Ph1", "Current Ph2", "Current Ph3" };
// indices to calculation functions, defined in hmInverter.h
enum {
@ -60,7 +71,9 @@ enum ChannelNum_t {
CH1,
CH2,
CH3,
CH4
CH4,
CH5,
CH_CNT
};
enum ChannelType_t {
@ -72,7 +85,7 @@ const char* const channelsTypes[] = { "AC", "DC", "INV" };
typedef struct {
ChannelType_t type;
ChannelNum_t ch; // channel 0 - 4
ChannelNum_t ch; // channel 0 - 5
FieldId_t fieldId; // field id
UnitId_t unitId; // uint id
uint8_t start; // pos of first byte in buffer
@ -84,7 +97,7 @@ typedef struct {
typedef struct {
ChannelType_t type;
ChannelNum_t ch; // channel 0 - 4
ChannelNum_t ch; // channel 0 - 5
FieldId_t fieldId; // field id
float offset; // offset (positive/negative) to be applied on the fetched value
} fieldSettings_t;
@ -122,7 +135,7 @@ public:
private:
uint8_t _payloadStatistic[STATISTIC_PACKET_SIZE] = {};
uint8_t _statisticLength = 0;
uint16_t _stringMaxPower[CH4];
uint16_t _stringMaxPower[CH_CNT];
const std::list<byteAssign_t>* _byteAssignment;
std::list<fieldSettings_t> _fieldSettings;

View File

@ -16,5 +16,6 @@ typedef struct {
uint8_t fragment[MAX_RF_PAYLOAD_SIZE];
uint8_t len;
uint8_t channel;
int8_t rssi;
bool wasReceived;
} fragment_t;

View File

@ -79,7 +79,9 @@ bool ConfigurationClass::write()
JsonObject dtu = doc.createNestedObject("dtu");
dtu["serial"] = config.Dtu_Serial;
dtu["poll_interval"] = config.Dtu_PollInterval;
dtu["pa_level"] = config.Dtu_PaLevel;
dtu["nrf_pa_level"] = config.Dtu_NrfPaLevel;
dtu["cmt_pa_level"] = config.Dtu_CmtPaLevel;
dtu["cmt_frequency"] = config.Dtu_CmtFrequency;
JsonObject security = doc.createNestedObject("security");
security["password"] = config.Security_Password;
@ -271,7 +273,9 @@ bool ConfigurationClass::read()
JsonObject dtu = doc["dtu"];
config.Dtu_Serial = dtu["serial"] | DTU_SERIAL;
config.Dtu_PollInterval = dtu["poll_interval"] | DTU_POLL_INTERVAL;
config.Dtu_PaLevel = dtu["pa_level"] | DTU_PA_LEVEL;
config.Dtu_NrfPaLevel = dtu["nrf_pa_level"] | DTU_NRF_PA_LEVEL;
config.Dtu_CmtPaLevel = dtu["cmt_pa_level"] | DTU_CMT_PA_LEVEL;
config.Dtu_CmtFrequency = dtu["cmt_frequency"] | DTU_CMT_FREQUENCY;
JsonObject security = doc["security"];
strlcpy(config.Security_Password, security["password"] | ACCESS_POINT_PASSWORD, sizeof(config.Security_Password));
@ -394,6 +398,11 @@ void ConfigurationClass::migrate()
config.Mqtt_PublishInterval = mqtt["publish_invterval"];
}
if (config.Cfg_Version < 0x00011900) {
JsonObject dtu = doc["dtu"];
config.Dtu_NrfPaLevel = dtu["pa_level"];
}
f.close();
config.Cfg_Version = CONFIG_VERSION;

View File

@ -22,17 +22,29 @@ void InverterSettingsClass::init()
// Initialize inverter communication
MessageOutput.print("Initialize Hoymiles interface... ");
if (PinMapping.isValidNrf24Config()) {
SPIClass* spiClass = new SPIClass(VSPI);
spiClass->begin(pin.nrf24_clk, pin.nrf24_miso, pin.nrf24_mosi, pin.nrf24_cs);
if (PinMapping.isValidNrf24Config() || PinMapping.isValidCmt2300Config()) {
Hoymiles.setMessageOutput(&MessageOutput);
Hoymiles.init(spiClass, pin.nrf24_en, pin.nrf24_irq);
Hoymiles.init();
if (PinMapping.isValidNrf24Config()) {
SPIClass* spiClass = new SPIClass(VSPI);
spiClass->begin(pin.nrf24_clk, pin.nrf24_miso, pin.nrf24_mosi, pin.nrf24_cs);
Hoymiles.initNRF(spiClass, pin.nrf24_en, pin.nrf24_irq);
}
if (PinMapping.isValidCmt2300Config()) {
Hoymiles.initCMT(pin.cmt_sdio, pin.cmt_clk, pin.cmt_cs, pin.cmt_fcs, pin.cmt_gpio2, pin.cmt_gpio3);
MessageOutput.println(F(" Setting CMT target frequency... "));
Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu_CmtFrequency);
}
MessageOutput.println(" Setting radio PA level... ");
Hoymiles.getRadio()->setPALevel((rf24_pa_dbm_e)config.Dtu_PaLevel);
Hoymiles.getRadioNrf()->setPALevel((rf24_pa_dbm_e)config.Dtu_NrfPaLevel);
Hoymiles.getRadioCmt()->setPALevel(config.Dtu_CmtPaLevel);
MessageOutput.println(" Setting DTU serial... ");
Hoymiles.getRadio()->setDtuSerial(config.Dtu_Serial);
Hoymiles.getRadioNrf()->setDtuSerial(config.Dtu_Serial);
Hoymiles.getRadioCmt()->setDtuSerial(config.Dtu_Serial);
MessageOutput.println(" Setting poll interval... ");
Hoymiles.setPollInterval(config.Dtu_PollInterval);

View File

@ -16,7 +16,7 @@ void MqttHandleDtuClass::init()
void MqttHandleDtuClass::loop()
{
if (!MqttSettings.getConnected() || !Hoymiles.getRadio()->isIdle()) {
if (!MqttSettings.getConnected() || !Hoymiles.isAllRadioIdle()) {
return;
}

View File

@ -41,7 +41,7 @@ void MqttHandleHassClass::publishConfig()
return;
}
if (!MqttSettings.getConnected() && Hoymiles.getRadio()->isIdle()) {
if (!MqttSettings.getConnected() && Hoymiles.isAllRadioIdle()) {
return;
}
@ -58,8 +58,8 @@ void MqttHandleHassClass::publishConfig()
publishInverterNumber(inv, "Limit NonPersistent Relative", "mdi:speedometer", "config", "cmd/limit_nonpersistent_relative", "status/limit_relative", "%");
publishInverterNumber(inv, "Limit Persistent Relative", "mdi:speedometer", "config", "cmd/limit_persistent_relative", "status/limit_relative", "%");
publishInverterNumber(inv, "Limit NonPersistent Absolute", "mdi:speedometer", "config", "cmd/limit_nonpersistent_absolute", "status/limit_absolute", "W", 10, 1500);
publishInverterNumber(inv, "Limit Persistent Absolute", "mdi:speedometer", "config", "cmd/limit_persistent_absolute", "status/limit_absolute", "W", 10, 1500);
publishInverterNumber(inv, "Limit NonPersistent Absolute", "mdi:speedometer", "config", "cmd/limit_nonpersistent_absolute", "status/limit_absolute", "W", 10, 2250);
publishInverterNumber(inv, "Limit Persistent Absolute", "mdi:speedometer", "config", "cmd/limit_persistent_absolute", "status/limit_absolute", "W", 10, 2250);
publishInverterBinarySensor(inv, "Reachable", "status/reachable", "1", "0");
publishInverterBinarySensor(inv, "Producing", "status/producing", "1", "0");

View File

@ -36,7 +36,7 @@ void MqttHandleInverterClass::init()
void MqttHandleInverterClass::loop()
{
if (!MqttSettings.getConnected() || !Hoymiles.getRadio()->isIdle()) {
if (!MqttSettings.getConnected() || !Hoymiles.isAllRadioIdle()) {
return;
}
@ -208,18 +208,18 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE)) {
// Set inverter limit relative persistent
MessageOutput.printf("Limit Persistent: %d %%\r\n", payload_val);
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), payload_val, PowerLimitControlType::RelativPersistent);
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::RelativPersistent);
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE)) {
// Set inverter limit absolute persistent
MessageOutput.printf("Limit Persistent: %d W\r\n", payload_val);
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), payload_val, PowerLimitControlType::AbsolutPersistent);
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::AbsolutPersistent);
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE)) {
// Set inverter limit relative non persistent
MessageOutput.printf("Limit Non-Persistent: %d %%\r\n", payload_val);
if (!properties.retain) {
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), payload_val, PowerLimitControlType::RelativNonPersistent);
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::RelativNonPersistent);
} else {
MessageOutput.println("Ignored because retained");
}
@ -228,7 +228,7 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
// Set inverter limit absolute non persistent
MessageOutput.printf("Limit Non-Persistent: %d W\r\n", payload_val);
if (!properties.retain) {
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), payload_val, PowerLimitControlType::AbsolutNonPersistent);
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::AbsolutNonPersistent);
} else {
MessageOutput.println("Ignored because retained");
}
@ -236,13 +236,13 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
} else if (!strcmp(setting, TOPIC_SUB_POWER)) {
// Turn inverter on or off
MessageOutput.printf("Set inverter power to: %d\r\n", payload_val);
inv->sendPowerControlRequest(Hoymiles.getRadio(), payload_val > 0);
inv->sendPowerControlRequest(payload_val > 0);
} else if (!strcmp(setting, TOPIC_SUB_RESTART)) {
// Restart inverter
MessageOutput.printf("Restart inverter\r\n");
if (!properties.retain && payload_val == 1) {
inv->sendRestartControlRequest(Hoymiles.getRadio());
inv->sendRestartControlRequest();
} else {
MessageOutput.println("Ignored because retained");
}

View File

@ -38,6 +38,30 @@
#define LED1 -1
#endif
#ifndef CMT_CLK
#define CMT_CLK -1
#endif
#ifndef CMT_CS
#define CMT_CS -1
#endif
#ifndef CMT_FCS
#define CMT_FCS -1
#endif
#ifndef CMT_GPIO2
#define CMT_GPIO2 -1
#endif
#ifndef CMT_GPIO3
#define CMT_GPIO3 -1
#endif
#ifndef CMT_SDIO
#define CMT_SDIO -1
#endif
PinMappingClass PinMapping;
PinMappingClass::PinMappingClass()
@ -50,6 +74,13 @@ PinMappingClass::PinMappingClass()
_pinMapping.nrf24_miso = HOYMILES_PIN_MISO;
_pinMapping.nrf24_mosi = HOYMILES_PIN_MOSI;
_pinMapping.cmt_clk = CMT_CLK;
_pinMapping.cmt_cs = CMT_CS;
_pinMapping.cmt_fcs = CMT_FCS;
_pinMapping.cmt_gpio2 = CMT_GPIO2;
_pinMapping.cmt_gpio3 = CMT_GPIO3;
_pinMapping.cmt_sdio = CMT_SDIO;
#ifdef OPENDTU_ETHERNET
_pinMapping.eth_enabled = true;
#else
@ -116,6 +147,13 @@ bool PinMappingClass::init(const String& deviceMapping)
_pinMapping.nrf24_miso = doc[i]["nrf24"]["miso"] | HOYMILES_PIN_MISO;
_pinMapping.nrf24_mosi = doc[i]["nrf24"]["mosi"] | HOYMILES_PIN_MOSI;
_pinMapping.cmt_clk = doc[i]["cmt"]["clk"] | CMT_CLK;
_pinMapping.cmt_cs = doc[i]["cmt"]["cs"] | CMT_CS;
_pinMapping.cmt_fcs = doc[i]["cmt"]["fcs"] | CMT_FCS;
_pinMapping.cmt_gpio2 = doc[i]["cmt"]["gpio2"] | CMT_GPIO2;
_pinMapping.cmt_gpio3 = doc[i]["cmt"]["gpio3"] | CMT_GPIO3;
_pinMapping.cmt_sdio = doc[i]["cmt"]["sdio"] | CMT_SDIO;
#ifdef OPENDTU_ETHERNET
_pinMapping.eth_enabled = doc[i]["eth"]["enabled"] | true;
#else
@ -168,6 +206,14 @@ bool PinMappingClass::isValidNrf24Config()
&& _pinMapping.nrf24_mosi >= 0;
}
bool PinMappingClass::isValidCmt2300Config()
{
return _pinMapping.cmt_clk >= 0
&& _pinMapping.cmt_cs >= 0
&& _pinMapping.cmt_fcs >= 0
&& _pinMapping.cmt_sdio >= 0;
}
bool PinMappingClass::isValidEthConfig()
{
return _pinMapping.eth_enabled;

View File

@ -25,7 +25,7 @@ void PowerLimiterClass::loop()
if (!config.PowerLimiter_Enabled
|| !config.PowerMeter_Enabled
|| !Hoymiles.getRadio()->isIdle()
|| !Hoymiles.isAllRadioIdle()
|| (millis() - _lastCommandSent) < (config.PowerLimiter_Interval * 1000)
|| (millis() - _lastLoop) < (config.PowerLimiter_Interval * 1000)) {
if (!config.PowerLimiter_Enabled)
@ -219,17 +219,17 @@ void PowerLimiterClass::setNewPowerLimit(std::shared_ptr<InverterAbstract> inver
if (newPowerLimit < config.PowerLimiter_LowerPowerLimit) {
if (inverter->isProducing()) {
MessageOutput.println("[PowerLimiterClass::loop] Stopping inverter...");
inverter->sendPowerControlRequest(Hoymiles.getRadio(), false);
inverter->sendPowerControlRequest(false);
_lastCommandSent = millis();
}
newPowerLimit = config.PowerLimiter_LowerPowerLimit;
} else if (!inverter->isProducing()) {
MessageOutput.println("[PowerLimiterClass::loop] Starting up inverter...");
inverter->sendPowerControlRequest(Hoymiles.getRadio(), true);
inverter->sendPowerControlRequest(true);
_lastCommandSent = millis();
}
MessageOutput.printf("[PowerLimiterClass::loop] Limit Non-Persistent: %d W\r\n", newPowerLimit);
inverter->sendActivePowerControlRequest(Hoymiles.getRadio(), newPowerLimit, PowerLimitControlType::AbsolutNonPersistent);
inverter->sendActivePowerControlRequest(newPowerLimit, PowerLimitControlType::AbsolutNonPersistent);
_lastRequestedPowerLimit = newPowerLimit;
// wait for the next inverter update (+ 3 seconds to make sure the limit got applied)
_lastLimitSetTime = millis();

View File

@ -47,6 +47,14 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
nrfPinObj["miso"] = pin.nrf24_miso;
nrfPinObj["mosi"] = pin.nrf24_mosi;
JsonObject cmtPinObj = curPin.createNestedObject("cmt");
cmtPinObj["clk"] = pin.cmt_clk;
cmtPinObj["cs"] = pin.cmt_cs;
cmtPinObj["fcs"] = pin.cmt_fcs;
cmtPinObj["sdio"] = pin.cmt_sdio;
cmtPinObj["gpio2"] = pin.cmt_gpio2;
cmtPinObj["gpio3"] = pin.cmt_gpio3;
JsonObject ethPinObj = curPin.createNestedObject("eth");
ethPinObj["enabled"] = pin.eth_enabled;
ethPinObj["phy_addr"] = pin.eth_phy_addr;

View File

@ -38,9 +38,13 @@ void WebApiDtuClass::onDtuAdminGet(AsyncWebServerRequest* request)
snprintf(buffer, sizeof(buffer), "%0x%08x",
((uint32_t)((config.Dtu_Serial >> 32) & 0xFFFFFFFF)),
((uint32_t)(config.Dtu_Serial & 0xFFFFFFFF)));
root["dtu_serial"] = buffer;
root["dtu_pollinterval"] = config.Dtu_PollInterval;
root["dtu_palevel"] = config.Dtu_PaLevel;
root["serial"] = buffer;
root["pollinterval"] = config.Dtu_PollInterval;
root["nrf_enabled"] = Hoymiles.getRadioNrf()->isInitialized();
root["nrf_palevel"] = config.Dtu_NrfPaLevel;
root["cmt_enabled"] = Hoymiles.getRadioCmt()->isInitialized();
root["cmt_palevel"] = config.Dtu_CmtPaLevel;
root["cmt_frequency"] = config.Dtu_CmtFrequency;
response->setLength();
request->send(response);
@ -85,7 +89,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
return;
}
if (!(root.containsKey("dtu_serial") && root.containsKey("dtu_pollinterval") && root.containsKey("dtu_palevel"))) {
if (!(root.containsKey("serial") && root.containsKey("pollinterval") && root.containsKey("nrf_palevel") && root.containsKey("cmt_palevel") && root.containsKey("cmt_frequency"))) {
retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength();
@ -93,7 +97,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
return;
}
if (root["dtu_serial"].as<uint64_t>() == 0) {
if (root["serial"].as<uint64_t>() == 0) {
retMsg["message"] = "Serial cannot be zero!";
retMsg["code"] = WebApiError::DtuSerialZero;
response->setLength();
@ -101,7 +105,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
return;
}
if (root["dtu_pollinterval"].as<uint32_t>() == 0) {
if (root["pollinterval"].as<uint32_t>() == 0) {
retMsg["message"] = "Poll interval must be greater zero!";
retMsg["code"] = WebApiError::DtuPollZero;
response->setLength();
@ -109,7 +113,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
return;
}
if (root["dtu_palevel"].as<uint8_t>() > 3) {
if (root["nrf_palevel"].as<uint8_t>() > 3) {
retMsg["message"] = "Invalid power level setting!";
retMsg["code"] = WebApiError::DtuInvalidPowerLevel;
response->setLength();
@ -117,12 +121,35 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
return;
}
if (root["cmt_palevel"].as<int8_t>() < -10 || root["cmt_palevel"].as<int8_t>() > 20) {
retMsg["message"] = "Invalid power level setting!";
retMsg["code"] = WebApiError::DtuInvalidPowerLevel;
response->setLength();
request->send(response);
return;
}
if (root["cmt_frequency"].as<uint32_t>() < Hoymiles.getRadioCmt()->getMinFrequency()
|| root["cmt_frequency"].as<uint32_t>() > Hoymiles.getRadioCmt()->getMaxFrequency()
|| root["cmt_frequency"].as<uint32_t>() % 250 > 0) {
retMsg["message"] = "Invalid CMT frequency setting!";
retMsg["code"] = WebApiError::DtuInvalidCmtFrequency;
retMsg["param"]["min"] = Hoymiles.getRadioCmt()->getMinFrequency();
retMsg["param"]["max"] = Hoymiles.getRadioCmt()->getMaxFrequency();
response->setLength();
request->send(response);
return;
}
CONFIG_T& config = Configuration.get();
// Interpret the string as a hex value and convert it to uint64_t
config.Dtu_Serial = strtoll(root["dtu_serial"].as<String>().c_str(), NULL, 16);
config.Dtu_PollInterval = root["dtu_pollinterval"].as<uint32_t>();
config.Dtu_PaLevel = root["dtu_palevel"].as<uint8_t>();
config.Dtu_Serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
config.Dtu_PollInterval = root["pollinterval"].as<uint32_t>();
config.Dtu_NrfPaLevel = root["nrf_palevel"].as<uint8_t>();
config.Dtu_CmtPaLevel = root["cmt_palevel"].as<int8_t>();
config.Dtu_CmtFrequency = root["cmt_frequency"].as<uint32_t>();
Configuration.write();
retMsg["type"] = "success";
@ -132,7 +159,10 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
response->setLength();
request->send(response);
Hoymiles.getRadio()->setPALevel((rf24_pa_dbm_e)config.Dtu_PaLevel);
Hoymiles.getRadio()->setDtuSerial(config.Dtu_Serial);
Hoymiles.getRadioNrf()->setPALevel((rf24_pa_dbm_e)config.Dtu_NrfPaLevel);
Hoymiles.getRadioCmt()->setPALevel(config.Dtu_CmtPaLevel);
Hoymiles.getRadioNrf()->setDtuSerial(config.Dtu_Serial);
Hoymiles.getRadioCmt()->setDtuSerial(config.Dtu_Serial);
Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu_CmtFrequency);
Hoymiles.setPollInterval(config.Dtu_PollInterval);
}

View File

@ -112,10 +112,10 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
return;
}
if (root["limit_value"].as<uint16_t>() == 0 || root["limit_value"].as<uint16_t>() > 1500) {
retMsg["message"] = "Limit must between 1 and 1500!";
if (root["limit_value"].as<uint16_t>() == 0 || root["limit_value"].as<uint16_t>() > 2250) {
retMsg["message"] = "Limit must between 1 and 2250!";
retMsg["code"] = WebApiError::LimitInvalidLimit;
retMsg["param"]["max"] = 1500;
retMsg["param"]["max"] = 2250;
response->setLength();
request->send(response);
return;
@ -146,7 +146,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
return;
}
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), limit, type);
inv->sendActivePowerControlRequest(limit, type);
retMsg["type"] = "success";
retMsg["message"] = "Settings saved!";

View File

@ -118,10 +118,10 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request)
if (root.containsKey("power")) {
uint16_t power = root["power"].as<bool>();
inv->sendPowerControlRequest(Hoymiles.getRadio(), power);
inv->sendPowerControlRequest(power);
} else {
if (root["restart"].as<bool>()) {
inv->sendRestartControlRequest(Hoymiles.getRadio());
inv->sendRestartControlRequest();
}
}

View File

@ -69,8 +69,12 @@ void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request)
root["uptime"] = esp_timer_get_time() / 1000000;
root["radio_connected"] = Hoymiles.getRadio()->isConnected();
root["radio_pvariant"] = Hoymiles.getRadio()->isPVariant();
root["nrf_configured"] = Hoymiles.getRadioNrf()->isInitialized();
root["nrf_connected"] = Hoymiles.getRadioNrf()->isConnected();
root["nrf_pvariant"] = Hoymiles.getRadioNrf()->isPVariant();
root["cmt_configured"] = Hoymiles.getRadioCmt()->isInitialized();
root["cmt_connected"] = Hoymiles.getRadioCmt()->isConnected();
response->setLength();
request->send(response);

View File

@ -179,7 +179,9 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
JsonObject hintObj = root.createNestedObject("hints");
struct tm timeinfo;
hintObj["time_sync"] = !getLocalTime(&timeinfo, 5);
hintObj["radio_problem"] = (!Hoymiles.getRadio()->isConnected() || !Hoymiles.getRadio()->isPVariant());
hintObj["radio_problem"] =
(Hoymiles.getRadioNrf()->isInitialized() && (!Hoymiles.getRadioNrf()->isConnected() || !Hoymiles.getRadioNrf()->isPVariant())) ||
(Hoymiles.getRadioCmt()->isInitialized() && (!Hoymiles.getRadioCmt()->isConnected()));
if (!strcmp(Configuration.get().Security_Password, ACCESS_POINT_PASSWORD)) {
hintObj["default_password"] = true;
} else {

View File

@ -30,7 +30,7 @@
"@vue/eslint-config-typescript": "^11.0.2",
"@vue/tsconfig": "^0.1.3",
"eslint": "^8.38.0",
"eslint-plugin-vue": "^9.10.0",
"eslint-plugin-vue": "^9.11.0",
"npm-run-all": "^4.1.5",
"sass": "^1.62.0",
"terser": "^5.16.9",

View File

@ -4,27 +4,61 @@
<table class="table table-hover table-condensed">
<tbody>
<tr>
<th>{{ $t('radioinfo.ChipStatus') }}</th>
<th>{{ $t('radioinfo.Status', { module: "nRF24" }) }}</th>
<td>
<StatusBadge :status="systemStatus.radio_connected" true_text="radioinfo.Connected" false_text="radioinfo.NotConnected" />
<StatusBadge :status="systemStatus.nrf_configured" true_text="radioinfo.Configured" false_text="radioinfo.NotConfigured" false_class="text-bg-secondary" />
</td>
</tr>
<tr>
<th>{{ $t('radioinfo.ChipType') }}</th>
<th>{{ $t('radioinfo.ChipStatus', { module: "nRF24" }) }}</th>
<td>
<span class="badge" :class="{
'text-bg-danger': systemStatus.radio_connected && !systemStatus.radio_pvariant,
'text-bg-success': systemStatus.radio_connected && systemStatus.radio_pvariant,
'text-bg-secondary': !systemStatus.radio_connected,
'text-bg-danger': systemStatus.nrf_configured && !systemStatus.nrf_connected,
'text-bg-success': systemStatus.nrf_configured && systemStatus.nrf_connected,
}">
<template
v-if="systemStatus.radio_connected && systemStatus.radio_pvariant">nRF24L01+</template>
v-if="systemStatus.nrf_configured && systemStatus.nrf_connected">{{ $t('radioinfo.Connected') }}</template>
<template
v-else-if="systemStatus.radio_connected && !systemStatus.radio_pvariant">nRF24L01</template>
v-else-if="systemStatus.nrf_configured && !systemStatus.nrf_connected">{{ $t('radioinfo.NotConnected') }}</template>
</span>
</td>
</tr>
<tr>
<th>{{ $t('radioinfo.ChipType', { module: "nRF24" }) }}</th>
<td>
<span class="badge" :class="{
'text-bg-danger': systemStatus.nrf_connected && !systemStatus.nrf_pvariant,
'text-bg-success': systemStatus.nrf_connected && systemStatus.nrf_pvariant,
'text-bg-secondary': !systemStatus.nrf_connected,
}">
<template
v-if="systemStatus.nrf_connected && systemStatus.nrf_pvariant">nRF24L01+</template>
<template
v-else-if="systemStatus.nrf_connected && !systemStatus.nrf_pvariant">nRF24L01</template>
<template v-else>{{ $t('radioinfo.Unknown') }}</template>
</span>
</td>
</tr>
<tr>
<th>{{ $t('radioinfo.Status', { module: "CMT2300A" }) }}</th>
<td>
<StatusBadge :status="systemStatus.cmt_configured" true_text="radioinfo.Configured" false_text="radioinfo.NotConfigured" false_class="text-bg-secondary" />
</td>
</tr>
<tr>
<th>{{ $t('radioinfo.ChipStatus', { module: "CMT2300A" }) }}</th>
<td>
<span class="badge" :class="{
'text-bg-danger': systemStatus.cmt_configured && !systemStatus.cmt_connected,
'text-bg-success': systemStatus.cmt_configured && systemStatus.cmt_connected,
}">
<template
v-if="systemStatus.cmt_configured && systemStatus.cmt_connected">{{ $t('radioinfo.Connected') }}</template>
<template
v-else-if="systemStatus.cmt_configured && !systemStatus.cmt_connected">{{ $t('radioinfo.NotConnected') }}</template>
</span>
</td>
</tr>
</tbody>
</table>
</div>

View File

@ -45,6 +45,7 @@
"2001": "Die Seriennummer darf nicht 0 sein!",
"2002": "Das Abfraginterval muss größer als 0 sein!",
"2003": "Ungültige Sendeleistung angegeben!",
"2004": "Die Frequenz muss zwischen {min} und {max} kHz liegen und ein vielfaches von 250kHz betragen!",
"3001": "Nichts gelöscht!",
"3002": "Konfiguration zurückgesetzt. Starte jetzt neu...",
"4001": "@:apiresponse.2001",
@ -220,10 +221,13 @@
},
"radioinfo": {
"RadioInformation": "Funkmodulinformationen",
"ChipStatus": "Chip-Status",
"ChipType": "Chip-Typ",
"Status": "{module} Status",
"ChipStatus": "{module} Chip-Status",
"ChipType": "{module} Chip-Type",
"Connected": "verbunden",
"NotConnected": "nicht verbunden",
"Configured": "konfiguriert",
"NotConfigured": "nicht konfiguriert",
"Unknown": "unbekannt"
},
"networkinfo": {
@ -366,13 +370,20 @@
"SerialHint": "Sowohl der Wechselrichter als auch die DTU haben eine Seriennummer. Die DTU-Seriennummer wird beim ersten Start zufällig generiert und muss normalerweise nicht geändert werden.",
"PollInterval": "Abfrageintervall:",
"Seconds": "Sekunden",
"PaLevel": "Sendeleistung:",
"PaLevelHint": "Stellen Sie sicher, dass Ihre Stromversorgung stabil genug ist, bevor Sie die Sendeleistung erhöhen.",
"NrfPaLevel": "NRF24 Sendeleistung:",
"CmtPaLevel": "CMT2300A Sendeleistung:",
"NrfPaLevelHint": "Verwendet für HM-Wechselrichter. Stellen Sie sicher, dass Ihre Stromversorgung stabil genug ist, bevor Sie die Sendeleistung erhöhen.",
"CmtPaLevelHint": "Verwendet für HMS/HMT-Wechselrichter. Stellen Sie sicher, dass Ihre Stromversorgung stabil genug ist, bevor Sie die Sendeleistung erhöhen.",
"CmtFrequency": "CMT2300A Frequenz:",
"CmtFrequencyHint": "Stelle sicher, dass du nur Frequenzen verwendet werden welche im entsprechenden Land erlaubt sind! Nach einer Frequenzänderung kann es bis zu 15min dauern bis eine Verbindung hergestellt wird.",
"CmtFrequencyWarning": "Die ausgewählte Frequenz befindet außerhalb des in der EU zugelassenen Bereiches. Vergewissere dich, dass mit dieser Auswahl keine lokalen Regularien verletzt werden.",
"MHz": "{mhz} MHz",
"dBm": "{dbm} dBm",
"Save": "Speichern",
"Min": "Minimum (-18 dBm)",
"Low": "Niedrig (-12 dBm)",
"High": "Hoch (-6 dBm)",
"Max": "Maximum (0 dBm)"
"Min": "Minimum ({db} dBm)",
"Low": "Niedrig ({db} dBm)",
"High": "Hoch ({db} dBm)",
"Max": "Maximum ({db} dBm)"
},
"securityadmin": {
"SecuritySettings": "Sicherheitseinstellungen",
@ -626,7 +637,7 @@
"DiscussionBody": "Diskutieren Sie mit uns auf <a href=\"https://discord.gg/WzhxEY62mB\" target=\"_blank\">Discord</a> oder <a href=\"https://github.com/tbnobody/OpenDTU/discussions\" target=\"_blank\">Github</a>"
},
"hints": {
"RadioProblem": "Es konnte keine Verbindung zu einem NRF24L01+ Funkmodul hergestellt werden. Bitte überprüfen Sie die Verdrahtung.",
"RadioProblem": "Es konnte keine Verbindung zu einem der konfigurierten Funkmodule hergestellt werden. Bitte überprüfen Sie die Verdrahtung.",
"TimeSync": "Die Uhr wurde noch nicht synchronisiert. Ohne eine korrekt eingestellte Uhr werden keine Anfragen an den Wechselrichter gesendet. Dies ist kurz nach dem Start normal. Nach einer längeren Laufzeit (>1 Minute) bedeutet es jedoch, dass der NTP-Server nicht erreichbar ist.",
"TimeSyncLink": "Bitte überprüfen Sie Ihre Zeiteinstellungen.",
"DefaultPassword": "Sie verwenden das Standardpasswort für die Weboberfläche und den Notfall Access Point. Dies ist potenziell unsicher.",

View File

@ -45,6 +45,7 @@
"2001": "Serial cannot be zero!",
"2002": "Poll interval must be greater zero!",
"2003": "Invalid power level setting!",
"2004": "The frequency must be set between {min} and {max} kHz and must be a multiple of 250kHz!",
"3001": "Not deleted anything!",
"3002": "Configuration resettet. Rebooting now...",
"4001": "@:apiresponse.2001",
@ -220,10 +221,13 @@
},
"radioinfo": {
"RadioInformation": "Radio Information",
"ChipStatus": "Chip Status",
"ChipType": "Chip Type",
"Status": "{module} Status",
"ChipStatus": "{module} Chip Status",
"ChipType": "{module} Chip Type",
"Connected": "connected",
"NotConnected": "not connected",
"Configured": "configured",
"NotConfigured": "not configured",
"Unknown": "Unknown"
},
"networkinfo": {
@ -366,13 +370,20 @@
"SerialHint": "Both the inverter and the DTU have a serial number. The DTU serial number is randomly generated at the first start and does not normally need to be changed.",
"PollInterval": "Poll Interval:",
"Seconds": "Seconds",
"PaLevel": "PA Level:",
"PaLevelHint": "Make sure your power supply is stable enough before increasing the transmit power.",
"NrfPaLevel": "NRF24 Transmitting power:",
"CmtPaLevel": "CMT2300A Transmitting power:",
"NrfPaLevelHint": "Used for HM-Inverters. Make sure your power supply is stable enough before increasing the transmit power.",
"CmtPaLevelHint": "Used for HMS/HMT-Inverters. Make sure your power supply is stable enough before increasing the transmit power.",
"CmtFrequency": "CMT2300A Frequency:",
"CmtFrequencyHint": "Make sure to only use frequencies that are allowed in the respective country! After a frequency change, it can take up to 15min until a connection is established.",
"CmtFrequencyWarning": "The selected frequency is outside the range allowed in the EU. Make sure that this selection does not violate any local regulations.",
"MHz": "{mhz} MHz",
"dBm": "{dbm} dBm",
"Save": "Save",
"Min": "Minimum (-18 dBm)",
"Low": "Low (-12 dBm)",
"High": "High (-6 dBm)",
"Max": "Maximum (0 dBm)"
"Min": "Minimum ({db} dBm)",
"Low": "Low ({db} dBm)",
"High": "High ({db} dBm)",
"Max": "Maximum ({db} dBm)"
},
"securityadmin": {
"SecuritySettings": "Security Settings",
@ -551,7 +562,7 @@
"Status": "Status",
"Send": "Send",
"Receive": "Receive",
"StatusHint": "<b>Hint:</b> The inverter is power by it's DC input. If there is no sun, the inverter is off. Requests can still be sent.",
"StatusHint": "<b>Hint:</b> The inverter is powered by its DC input. If there is no sun, the inverter is off. Requests can still be sent.",
"Type": "Type",
"Action": "Action",
"DeleteInverter": "Delete inverter",
@ -630,7 +641,7 @@
"DiscussionBody": "Discuss with us on <a href=\"https://discord.gg/WzhxEY62mB\" target=\"_blank\">Discord</a> or <a href=\"https://github.com/tbnobody/OpenDTU/discussions\" target=\"_blank\">Github</a>"
},
"hints": {
"RadioProblem": "Could not connect to a correct NRF24L01+ radio module. Please check the wiring.",
"RadioProblem": "Could not connect to a configured radio module. Please check the wiring.",
"TimeSync": "The clock has not yet been synchronised. Without a correctly set clock, no requests are made to the inverter. This is normal shortly after the start. However, after a longer runtime (>1 minute), it indicates that the NTP server is not accessible.",
"TimeSyncLink": "Please check your time settings.",
"DefaultPassword": "You are using the default password for the web interface and the emergency access point. This is potentially insecure.",

View File

@ -45,6 +45,7 @@
"2001": "Le numéro de série ne peut pas être nul !",
"2002": "L'intervalle de sondage doit être supérieur à zéro !",
"2003": "Réglage du niveau de puissance invalide !",
"2004": "The frequency must be set between {min} and {max} kHz and must be a multiple of 250kHz!",
"3001": "Rien n'a été supprimé !",
"3002": "Configuration réinitialisée. Redémarrage maintenant...",
"4001": "@:apiresponse.2001",
@ -219,10 +220,13 @@
},
"radioinfo": {
"RadioInformation": "Informations sur la radio",
"ChipStatus": "État de la puce",
"ChipType": "Type de puce",
"Status": "{module} Status",
"ChipStatus": "{module} sÉtat de la puce",
"ChipType": "{module} Type de puce",
"Connected": "connectée",
"NotConnected": "non connectée",
"Configured": "configured",
"NotConfigured": "not configured",
"Unknown": "Inconnue"
},
"networkinfo": {
@ -365,13 +369,20 @@
"SerialHint": "L'onduleur et le DTU ont tous deux un numéro de série. Le numéro de série du DTU est généré de manière aléatoire lors du premier démarrage et ne doit normalement pas être modifié.",
"PollInterval": "Intervalle de sondage",
"Seconds": "Secondes",
"PaLevel": "Niveau de puissance d'émission",
"PaLevelHint": "Assurez-vous que votre alimentation est suffisamment stable avant d'augmenter la puissance d'émission.",
"NrfPaLevel": "NRF24 Niveau de puissance d'émission",
"CmtPaLevel": "CMT2300A Niveau de puissance d'émission",
"NrfPaLevelHint": "Used for HM-Inverters. Assurez-vous que votre alimentation est suffisamment stable avant d'augmenter la puissance d'émission.",
"CmtPaLevelHint": "Used for HMS/HMT-Inverters. Assurez-vous que votre alimentation est suffisamment stable avant d'augmenter la puissance d'émission.",
"CmtFrequency": "CMT2300A Frequency:",
"CmtFrequencyHint": "Make sure to only use frequencies that are allowed in the respective country! After a frequency change, it can take up to 15min until a connection is established.",
"CmtFrequencyWarning": "The selected frequency is outside the range allowed in the EU. Make sure that this selection does not violate any local regulations.",
"MHz": "{mhz} MHz",
"dBm": "{dbm} dBm",
"Save": "Sauvegarder",
"Min": "Minimum (-18 dBm)",
"Low": "Bas (-12 dBm)",
"High": "Haut (-6 dBm)",
"Max": "Maximum (0 dBm)"
"Min": "Minimum ({db} dBm)",
"Low": "Bas ({db} dBm)",
"High": "Haut ({db} dBm)",
"Max": "Maximum ({db} dBm)"
},
"securityadmin": {
"SecuritySettings": "Paramètres de sécurité",
@ -553,7 +564,7 @@
"DiscussionBody": "Discutez avec nous sur <a href=\"https://discord.gg/WzhxEY62mB\" target=\"_blank\">Discord</a> ou sur <a href=\"https://github.com/tbnobody/OpenDTU/discussions\" target=\"_blank\">Github</a>"
},
"hints": {
"RadioProblem": "Impossible de se connecter à un module radio NRF24L01+ correct. Veuillez vérifier le câblage.",
"RadioProblem": "Impossible de se connecter à un module radio configuré.. Veuillez vérifier le câblage.",
"TimeSync": "L'horloge n'a pas encore été synchronisée. Sans une horloge correctement réglée, aucune demande n'est adressée à l'onduleur. Ceci est normal peu de temps après le démarrage. Cependant, après un temps de fonctionnement plus long (>1 minute), cela indique que le serveur NTP n'est pas accessible.",
"TimeSyncLink": "Veuillez vérifier vos paramètres horaires.",
"DefaultPassword": "Vous utilisez le mot de passe par défaut pour l'interface Web et le point d'accès d'urgence. Ceci est potentiellement non sécurisé.",

View File

@ -1,5 +1,9 @@
export interface DtuConfig {
dtu_serial: number;
dtu_pollinterval: number;
dtu_palevel: number;
serial: number;
pollinterval: number;
nrf_enabled: boolean;
nrf_palevel: number;
cmt_enabled: boolean;
cmt_palevel: number;
cmt_frequency: number;
}

View File

@ -7,6 +7,15 @@ export interface Nrf24 {
cs: number;
}
export interface Cmt2300 {
clk: number;
cs: number;
fcs: number;
sdio: number;
gpio2: number;
gpio3: number;
}
export interface Ethernet {
enabled: boolean;
phy_addr: number;
@ -38,6 +47,7 @@ export interface Battery {
export interface Device {
name: string;
nrf24: Nrf24;
cmt: Cmt2300;
eth: Ethernet;
display: Display;
victron: Victron;

View File

@ -25,6 +25,9 @@ export interface SystemStatus {
sketch_total: number;
sketch_used: number;
// RadioInfo
radio_connected: boolean;
radio_pvariant: boolean;
nrf_configured: boolean;
nrf_connected: boolean;
nrf_pvariant: boolean;
cmt_configured: boolean;
cmt_connected: boolean;
}

View File

@ -7,28 +7,64 @@
<form @submit="saveDtuConfig">
<CardElement :text="$t('dtuadmin.DtuConfiguration')" textVariant="text-bg-primary">
<InputElement :label="$t('dtuadmin.Serial')"
v-model="dtuConfigList.dtu_serial"
v-model="dtuConfigList.serial"
type="number" min="1" max="199999999999"
:tooltip="$t('dtuadmin.SerialHint')"/>
<InputElement :label="$t('dtuadmin.PollInterval')"
v-model="dtuConfigList.dtu_pollinterval"
v-model="dtuConfigList.pollinterval"
type="number" min="1" max="86400"
:postfix="$t('dtuadmin.Seconds')"/>
<div class="row mb-3">
<label for="inputTimezone" class="col-sm-2 col-form-label">
{{ $t('dtuadmin.PaLevel') }}
<BIconInfoCircle v-tooltip :title="$t('dtuadmin.PaLevelHint')" />
<div class="row mb-3" v-if="dtuConfigList.nrf_enabled">
<label for="inputNrfPaLevel" class="col-sm-2 col-form-label">
{{ $t('dtuadmin.NrfPaLevel') }}
<BIconInfoCircle v-tooltip :title="$t('dtuadmin.NrfPaLevelHint')" />
</label>
<div class="col-sm-10">
<select class="form-select" v-model="dtuConfigList.dtu_palevel">
<option v-for="palevel in palevelList" :key="palevel.key" :value="palevel.key">
{{ $t(`dtuadmin.` + palevel.value) }}
<select id="inputNrfPaLevel" class="form-select" v-model="dtuConfigList.nrf_palevel">
<option v-for="palevel in nrfpalevelList" :key="palevel.key" :value="palevel.key">
{{ $t(`dtuadmin.` + palevel.value, { db: palevel.db }) }}
</option>
</select>
</div>
</div>
<div class="row mb-3" v-if="dtuConfigList.cmt_enabled">
<label for="inputCmtPaLevel" class="col-sm-2 col-form-label">
{{ $t('dtuadmin.CmtPaLevel') }}
<BIconInfoCircle v-tooltip :title="$t('dtuadmin.CmtPaLevelHint')" />
</label>
<div class="col-sm-10">
<div class="input-group mb-3">
<input type="range" class="form-control form-range"
v-model="dtuConfigList.cmt_palevel"
min="-10" max="20"
id="inputCmtPaLevel" aria-describedby="basic-addon1"
style="height: unset;" />
<span class="input-group-text" id="basic-addon1">{{ cmtPaLevelText }}</span>
</div>
</div>
</div>
<div class="row mb-3" v-if="dtuConfigList.cmt_enabled">
<label for="cmtFrequency" class="col-sm-2 col-form-label">
{{ $t('dtuadmin.CmtFrequency') }}
<BIconInfoCircle v-tooltip :title="$t('dtuadmin.CmtFrequencyHint')" />
</label>
<div class="col-sm-10">
<div class="input-group mb-3">
<input type="range" class="form-control form-range"
v-model="dtuConfigList.cmt_frequency"
min="860250" max="923500" step="250"
id="cmtFrequency" aria-describedby="basic-addon2"
style="height: unset;" />
<span class="input-group-text" id="basic-addon2">{{ cmtFrequencyText }}</span>
</div>
<div class="alert alert-danger" role="alert" v-html="$t('dtuadmin.CmtFrequencyWarning')" v-if="cmtIsOutOfEu"></div>
</div>
</div>
</CardElement>
<button type="submit" class="btn btn-primary mb-3">{{ $t('dtuadmin.Save') }}</button>
</form>
@ -57,11 +93,11 @@ export default defineComponent({
return {
dataLoading: true,
dtuConfigList: {} as DtuConfig,
palevelList: [
{ key: 0, value: 'Min' },
{ key: 1, value: 'Low' },
{ key: 2, value: 'High' },
{ key: 3, value: 'Max' },
nrfpalevelList: [
{ key: 0, value: 'Min', db: "-18" },
{ key: 1, value: 'Low', db: "-12" },
{ key: 2, value: 'High', db: "-6" },
{ key: 3, value: 'Max', db: "0" },
],
alertMessage: "",
alertType: "info",
@ -71,6 +107,17 @@ export default defineComponent({
created() {
this.getDtuConfig();
},
computed: {
cmtFrequencyText() {
return this.$t("dtuadmin.MHz", { mhz: this.$n(this.dtuConfigList.cmt_frequency / 1000, "decimalTwoDigits") });
},
cmtPaLevelText() {
return this.$t("dtuadmin.dBm", { dbm: this.$n(this.dtuConfigList.cmt_palevel * 1) });
},
cmtIsOutOfEu() {
return this.dtuConfigList.cmt_frequency < 863000 || this.dtuConfigList.cmt_frequency > 870000;
}
},
methods: {
getDtuConfig() {
this.dataLoading = true;

View File

@ -625,7 +625,7 @@ export default defineComponent({
} else {
this.targetLimitTypeText = this.$t('home.Absolute');
this.targetLimitMin = 10;
this.targetLimitMax = (this.currentLimitList.max_power > 0 ? this.currentLimitList.max_power : 1500);
this.targetLimitMax = (this.currentLimitList.max_power > 0 ? this.currentLimitList.max_power : 2250);
}
this.targetLimitType = type;
},

View File

@ -1006,10 +1006,10 @@ escodegen@^2.0.0:
optionalDependencies:
source-map "~0.6.1"
eslint-plugin-vue@^9.10.0:
version "9.10.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.10.0.tgz#bb6423166e6eab800344245b6eef6ce9480c78a7"
integrity sha512-2MgP31OBf8YilUvtakdVMc8xVbcMp7z7/iQj8LHVpXrSXHPXSJRUIGSPFI6b6pyCx/buKaFJ45ycqfHvQRiW2g==
eslint-plugin-vue@^9.11.0:
version "9.11.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.11.0.tgz#99a247455c02181f24d9240d422380fd16dd630c"
integrity sha512-bBCJAZnkBV7ATH4Z1E7CvN3nmtS4H7QUU3UBxPdo8WohRU+yHjnQRALpTbxMVcz0e4Mx3IyxIdP5HYODMxK9cQ==
dependencies:
"@eslint-community/eslint-utils" "^4.3.0"
natural-compare "^1.4.0"

Binary file not shown.