diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 080e70d08..d14c6bfee 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -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": [ diff --git a/README.md b/README.md index cfbf13f5b..ba06e032b 100644 --- a/README.md +++ b/README.md @@ -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 @@ -478,7 +501,7 @@ A documentation of the Web API can be found here: [Web-API Documentation](docs/W * OpenDTU needs access to a working NTP server to get the current date & time. * If your problem persists, check the [Issues on Github](https://github.com/tbnobody/OpenDTU/issues). Please inspect not only the open issues, also the closed issues contain useful information. * Another source of information are the [Discussions](https://github.com/tbnobody/OpenDTU/discussions/) -* When flashing with VSCode Plattform.IO fails and also with ESPRESSIF tool a demo bin file cannot be flashed to the ESP32 with error message "A fatal error occurred: MD5 of file does not match data in flash!" than un-wire/unconnect ESP32 from the NRF24L01+ board. Try to flash again and rewire afterwards. +* When flashing with VSCode Plattform.IO fails and also with ESPRESSIF tool a demo bin file cannot be flashed to the ESP32 with error message "A fatal error occurred: MD5 of file does not match data in flash!" than un-wire/unconnect ESP32 from the NRF24L01+ board. Try to flash again and rewire afterwards. ## Related Projects diff --git a/docs/DeviceProfiles.md b/docs/DeviceProfiles.md index 099e60967..5e4a708a0 100644 --- a/docs/DeviceProfiles.md +++ b/docs/DeviceProfiles.md @@ -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. | diff --git a/docs/DeviceProfiles/blinkyparts_esp32.json b/docs/DeviceProfiles/blinkyparts_esp32.json index 8a7da533d..0ee922bfe 100644 --- a/docs/DeviceProfiles/blinkyparts_esp32.json +++ b/docs/DeviceProfiles/blinkyparts_esp32.json @@ -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 + } } ] \ No newline at end of file diff --git a/docs/DeviceProfiles/nodemcu_esp32.json b/docs/DeviceProfiles/nodemcu_esp32.json index b5f3d0de8..a42af3719 100644 --- a/docs/DeviceProfiles/nodemcu_esp32.json +++ b/docs/DeviceProfiles/nodemcu_esp32.json @@ -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 + } } ] \ No newline at end of file diff --git a/include/Configuration.h b/include/Configuration.h index 463911349..f8673edb7 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -4,7 +4,7 @@ #include #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; diff --git a/include/PinMapping.h b/include/PinMapping.h index 6178795ea..d527d77d2 100644 --- a/include/PinMapping.h +++ b/include/PinMapping.h @@ -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 @@ class PinMappingClass { PinMapping_t& get(); bool isValidNrf24Config(); + bool isValidCmt2300Config(); bool isValidEthConfig(); bool isValidVictronConfig(); bool isValidBatteryConfig(); diff --git a/include/WebApi_errors.h b/include/WebApi_errors.h index 9a53759b4..e4ec78814 100644 --- a/include/WebApi_errors.h +++ b/include/WebApi_errors.h @@ -13,6 +13,7 @@ enum WebApiError { DtuSerialZero, DtuPollZero, DtuInvalidPowerLevel, + DtuInvalidCmtFrequency, ConfigBase = 3000, ConfigNotDeleted, diff --git a/include/defaults.h b/include/defaults.h index 8b3a08cb3..1e6865110 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -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 diff --git a/lib/CMT2300a/cmt2300a.c b/lib/CMT2300a/cmt2300a.c new file mode 100644 index 000000000..45b09f5e7 --- /dev/null +++ b/lib/CMT2300a/cmt2300a.c @@ -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; +} diff --git a/lib/CMT2300a/cmt2300a.h b/lib/CMT2300a/cmt2300a.h new file mode 100644 index 000000000..e6d484d81 --- /dev/null +++ b/lib/CMT2300a/cmt2300a.h @@ -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 + +#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 diff --git a/lib/CMT2300a/cmt2300a_defs.h b/lib/CMT2300a/cmt2300a_defs.h new file mode 100644 index 000000000..c2c6ef107 --- /dev/null +++ b/lib/CMT2300a/cmt2300a_defs.h @@ -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 diff --git a/lib/CMT2300a/cmt2300a_hal.c b/lib/CMT2300a/cmt2300a_hal.c new file mode 100644 index 000000000..7bf1b60ff --- /dev/null +++ b/lib/CMT2300a/cmt2300a_hal.c @@ -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 + +/*! ******************************************************** + * @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); +} diff --git a/lib/CMT2300a/cmt2300a_hal.h b/lib/CMT2300a/cmt2300a_hal.h new file mode 100644 index 000000000..1d6e1f4f3 --- /dev/null +++ b/lib/CMT2300a/cmt2300a_hal.h @@ -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 +#include + +#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 diff --git a/lib/CMT2300a/cmt2300a_params.h b/lib/CMT2300a/cmt2300a_params.h new file mode 100644 index 000000000..4e10f6a25 --- /dev/null +++ b/lib/CMT2300a/cmt2300a_params.h @@ -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 + +/* [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 diff --git a/lib/CMT2300a/cmt2300wrapper.cpp b/lib/CMT2300a/cmt2300wrapper.cpp new file mode 100644 index 000000000..21265f50a --- /dev/null +++ b/lib/CMT2300a/cmt2300wrapper.cpp @@ -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(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; +} diff --git a/lib/CMT2300a/cmt2300wrapper.h b/lib/CMT2300a/cmt2300wrapper.h new file mode 100644 index 000000000..5f76b06f2 --- /dev/null +++ b/lib/CMT2300a/cmt2300wrapper.h @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +#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; +}; \ No newline at end of file diff --git a/lib/CMT2300a/cmt_spi3.c b/lib/CMT2300a/cmt_spi3.c new file mode 100644 index 000000000..7092b528d --- /dev/null +++ b/lib/CMT2300a/cmt_spi3.c @@ -0,0 +1,118 @@ +#include "cmt_spi3.h" +#include +#include +#include // 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; + } +} diff --git a/lib/CMT2300a/cmt_spi3.h b/lib/CMT2300a/cmt_spi3.h new file mode 100644 index 000000000..0e77e311f --- /dev/null +++ b/lib/CMT2300a/cmt_spi3.h @@ -0,0 +1,14 @@ +#ifndef __CMT_SPI3_H +#define __CMT_SPI3_H + +#include + +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 diff --git a/lib/Hoymiles/src/Hoymiles.cpp b/lib/Hoymiles/src/Hoymiles.cpp index 32074e8ab..4ae3223d8 100644 --- a/lib/Hoymiles/src/Hoymiles.cpp +++ b/lib/Hoymiles/src/Hoymiles.cpp @@ -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 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 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 HoymilesClass::addInverter(const char* name, uint64_t serial) { std::shared_ptr i = nullptr; - if (HM_4CH::isValidSerial(serial)) { - i = std::make_shared(serial); + if (HMT_6CH::isValidSerial(serial)) { + i = std::make_shared(_radioCmt.get(), serial); + } else if (HMS_4CH::isValidSerial(serial)) { + i = std::make_shared(_radioCmt.get(), serial); + } else if (HMS_2CH::isValidSerial(serial)) { + i = std::make_shared(_radioCmt.get(), serial); + } else if (HMS_1CH::isValidSerial(serial)) { + i = std::make_shared(_radioCmt.get(), serial); + } else if (HM_4CH::isValidSerial(serial)) { + i = std::make_shared(_radioNrf.get(), serial); } else if (HM_2CH::isValidSerial(serial)) { - i = std::make_shared(serial); + i = std::make_shared(_radioNrf.get(), serial); } else if (HM_1CH::isValidSerial(serial)) { - i = std::make_shared(serial); + i = std::make_shared(_radioNrf.get(), serial); } if (i) { @@ -162,9 +194,19 @@ size_t HoymilesClass::getNumInverters() return _inverters.size(); } -HoymilesRadio* HoymilesClass::getRadio() +HoymilesRadio_NRF* HoymilesClass::getRadioNrf() +{ + return _radioNrf.get(); +} + +HoymilesRadio_CMT* HoymilesClass::getRadioCmt() +{ + return _radioCmt.get(); +} + +bool HoymilesClass::isAllRadioIdle() { - return _radio.get(); + return _radioNrf.get()->isIdle() && _radioCmt.get()->isIdle(); } uint32_t HoymilesClass::PollInterval() diff --git a/lib/Hoymiles/src/Hoymiles.h b/lib/Hoymiles/src/Hoymiles.h index 27e3d0338..66bc0c13d 100644 --- a/lib/Hoymiles/src/Hoymiles.h +++ b/lib/Hoymiles/src/Hoymiles.h @@ -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 @@ -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 @@ class HoymilesClass { 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> _inverters; - std::unique_ptr _radio; + std::unique_ptr _radioNrf; + std::unique_ptr _radioCmt; SemaphoreHandle_t _xSemaphore; diff --git a/lib/Hoymiles/src/HoymilesRadio.cpp b/lib/Hoymiles/src/HoymilesRadio.cpp index eb25d5586..117d45e95 100644 --- a/lib/Hoymiles/src/HoymilesRadio.cpp +++ b/lib/Hoymiles/src/HoymilesRadio.cpp @@ -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 -#include - -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 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 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; } \ No newline at end of file diff --git a/lib/Hoymiles/src/HoymilesRadio.h b/lib/Hoymiles/src/HoymilesRadio.h index 6963361c6..056b61c34 100644 --- a/lib/Hoymiles/src/HoymilesRadio.h +++ b/lib/Hoymiles/src/HoymilesRadio.h @@ -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 #include -#include #include -// 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 T* enqueCommand() @@ -32,37 +21,17 @@ class HoymilesRadio { return static_cast(_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 _spiPtr; - std::unique_ptr _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 _rxBuffer; - TimeoutHelper _rxTimeout; - serial_u _dtuSerial; - - bool _busyFlag = false; - std::queue> _commandQueue; + bool _isInitialized = false; + bool _busyFlag = false; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/HoymilesRadio_CMT.cpp b/lib/Hoymiles/src/HoymilesRadio_CMT.cpp new file mode 100644 index 000000000..c69a4ac83 --- /dev/null +++ b/lib/Hoymiles/src/HoymilesRadio_CMT.cpp @@ -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 + +#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 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 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()); +} diff --git a/lib/Hoymiles/src/HoymilesRadio_CMT.h b/lib/Hoymiles/src/HoymilesRadio_CMT.h new file mode 100644 index 000000000..b1cfa7c4c --- /dev/null +++ b/lib/Hoymiles/src/HoymilesRadio_CMT.h @@ -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 +#include +#include +#include + +// 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 _radio; + + volatile bool _packetReceived = false; + volatile bool _packetSent = false; + + bool _gpio2_configured = false; + bool _gpio3_configured = false; + + std::queue _rxBuffer; + TimeoutHelper _rxTimeout; + TimeoutHelper _txTimeout; + + uint32_t _inverterTargetFrequency = HOYMILES_CMT_WORK_FREQ; + + bool cmtSwitchDtuFreq(const uint32_t to_freq_kHz); +}; \ No newline at end of file diff --git a/lib/Hoymiles/src/HoymilesRadio_NRF.cpp b/lib/Hoymiles/src/HoymilesRadio_NRF.cpp new file mode 100644 index 000000000..76c78c272 --- /dev/null +++ b/lib/Hoymiles/src/HoymilesRadio_NRF.cpp @@ -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 +#include + +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 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 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()); +} diff --git a/lib/Hoymiles/src/HoymilesRadio_NRF.h b/lib/Hoymiles/src/HoymilesRadio_NRF.h new file mode 100644 index 000000000..88c0d2f90 --- /dev/null +++ b/lib/Hoymiles/src/HoymilesRadio_NRF.h @@ -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 +#include +#include +#include + +// 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 _spiPtr; + std::unique_ptr _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 _rxBuffer; + TimeoutHelper _rxTimeout; +}; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/AlarmDataCommand.cpp b/lib/Hoymiles/src/commands/AlarmDataCommand.cpp index d11585974..46b62e7c6 100644 --- a/lib/Hoymiles/src/commands/AlarmDataCommand.cpp +++ b/lib/Hoymiles/src/commands/AlarmDataCommand.cpp @@ -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() diff --git a/lib/Hoymiles/src/commands/ChannelChangeCommand.cpp b/lib/Hoymiles/src/commands/ChannelChangeCommand.cpp new file mode 100644 index 000000000..139bbea34 --- /dev/null +++ b/lib/Hoymiles/src/commands/ChannelChangeCommand.cpp @@ -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; +} \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/ChannelChangeCommand.h b/lib/Hoymiles/src/commands/ChannelChangeCommand.h new file mode 100644 index 000000000..b646217c2 --- /dev/null +++ b/lib/Hoymiles/src/commands/ChannelChangeCommand.h @@ -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(); +}; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/CommandAbstract.cpp b/lib/Hoymiles/src/commands/CommandAbstract.cpp index e7fb4ab58..78d8d07d7 100644 --- a/lib/Hoymiles/src/commands/CommandAbstract.cpp +++ b/lib/Hoymiles/src/commands/CommandAbstract.cpp @@ -100,4 +100,14 @@ void CommandAbstract::convertSerialToPacketId(uint8_t buffer[], uint64_t serial) void CommandAbstract::gotTimeout(InverterAbstract* inverter) { -} \ No newline at end of file +} + +uint8_t CommandAbstract::getMaxResendCount() +{ + return MAX_RESEND_COUNT; +} + +uint8_t CommandAbstract::getMaxRetransmitCount() +{ + return MAX_RETRANSMIT_COUNT; +} diff --git a/lib/Hoymiles/src/commands/CommandAbstract.h b/lib/Hoymiles/src/commands/CommandAbstract.h index 3baa348ed..8029ac872 100644 --- a/lib/Hoymiles/src/commands/CommandAbstract.h +++ b/lib/Hoymiles/src/commands/CommandAbstract.h @@ -6,6 +6,8 @@ #include #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 @@ class CommandAbstract { 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; diff --git a/lib/Hoymiles/src/commands/README.md b/lib/Hoymiles/src/commands/README.md index 7e34a2106..90ca62d39 100644 --- a/lib/Hoymiles/src/commands/README.md +++ b/lib/Hoymiles/src/commands/README.md @@ -13,3 +13,4 @@ * ParaSetCommand * SingleDataCommand * RequestFrameCommand + * ChannelChangeCommand diff --git a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp index 97d0cebb7..6a7db92a9 100644 --- a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp +++ b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp @@ -10,7 +10,7 @@ RealTimeRunDataCommand::RealTimeRunDataCommand(uint64_t target_address, uint64_t { setTime(time); setDataType(0x0b); - setTimeout(200); + setTimeout(500); } String RealTimeRunDataCommand::getCommandName() diff --git a/lib/Hoymiles/src/inverters/HMS_1CH.cpp b/lib/Hoymiles/src/inverters/HMS_1CH.cpp new file mode 100644 index 000000000..9fe5406c8 --- /dev/null +++ b/lib/Hoymiles/src/inverters/HMS_1CH.cpp @@ -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* HMS_1CH::getByteAssignment() +{ + return &byteAssignment; +} \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMS_1CH.h b/lib/Hoymiles/src/inverters/HMS_1CH.h new file mode 100644 index 000000000..5902e7c18 --- /dev/null +++ b/lib/Hoymiles/src/inverters/HMS_1CH.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include "HMS_Abstract.h" +#include + +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* getByteAssignment(); + +private: + const std::list 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 } + }; +}; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMS_2CH.cpp b/lib/Hoymiles/src/inverters/HMS_2CH.cpp new file mode 100644 index 000000000..2b1ea4909 --- /dev/null +++ b/lib/Hoymiles/src/inverters/HMS_2CH.cpp @@ -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* HMS_2CH::getByteAssignment() +{ + return &byteAssignment; +} \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMS_2CH.h b/lib/Hoymiles/src/inverters/HMS_2CH.h new file mode 100644 index 000000000..d5e76c51b --- /dev/null +++ b/lib/Hoymiles/src/inverters/HMS_2CH.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include "HMS_Abstract.h" +#include + +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* getByteAssignment(); + +private: + const std::list 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 } + }; +}; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMS_4CH.cpp b/lib/Hoymiles/src/inverters/HMS_4CH.cpp new file mode 100644 index 000000000..db2df1483 --- /dev/null +++ b/lib/Hoymiles/src/inverters/HMS_4CH.cpp @@ -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* HMS_4CH::getByteAssignment() +{ + return &byteAssignment; +} \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMS_4CH.h b/lib/Hoymiles/src/inverters/HMS_4CH.h new file mode 100644 index 000000000..090bea59c --- /dev/null +++ b/lib/Hoymiles/src/inverters/HMS_4CH.h @@ -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* getByteAssignment(); + +private: + const std::list 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 } + }; +}; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMS_Abstract.cpp b/lib/Hoymiles/src/inverters/HMS_Abstract.cpp new file mode 100644 index 000000000..30de00385 --- /dev/null +++ b/lib/Hoymiles/src/inverters/HMS_Abstract.cpp @@ -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(); + cmdChannel->setChannel(HoymilesRadio_CMT::getChannelFromFrequency(Hoymiles.getRadioCmt()->getInverterTargetFrequency())); + cmdChannel->setTargetAddress(serial()); + + return true; +}; diff --git a/lib/Hoymiles/src/inverters/HMS_Abstract.h b/lib/Hoymiles/src/inverters/HMS_Abstract.h new file mode 100644 index 000000000..6d363f6ec --- /dev/null +++ b/lib/Hoymiles/src/inverters/HMS_Abstract.h @@ -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(); +}; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMT_6CH.cpp b/lib/Hoymiles/src/inverters/HMT_6CH.cpp new file mode 100644 index 000000000..d50963b9f --- /dev/null +++ b/lib/Hoymiles/src/inverters/HMT_6CH.cpp @@ -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* HMT_6CH::getByteAssignment() +{ + return &byteAssignment; +} \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMT_6CH.h b/lib/Hoymiles/src/inverters/HMT_6CH.h new file mode 100644 index 000000000..119d2ec1e --- /dev/null +++ b/lib/Hoymiles/src/inverters/HMT_6CH.h @@ -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* getByteAssignment(); + +private: + const std::list 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 } + }; +}; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMT_Abstract.cpp b/lib/Hoymiles/src/inverters/HMT_Abstract.cpp new file mode 100644 index 000000000..9aa2d0938 --- /dev/null +++ b/lib/Hoymiles/src/inverters/HMT_Abstract.cpp @@ -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(); + cmdChannel->setChannel(HoymilesRadio_CMT::getChannelFromFrequency(Hoymiles.getRadioCmt()->getInverterTargetFrequency())); + cmdChannel->setTargetAddress(serial()); + + return true; +}; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMT_Abstract.h b/lib/Hoymiles/src/inverters/HMT_Abstract.h new file mode 100644 index 000000000..9e10a2c39 --- /dev/null +++ b/lib/Hoymiles/src/inverters/HMT_Abstract.h @@ -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(); +}; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HM_1CH.cpp b/lib/Hoymiles/src/inverters/HM_1CH.cpp index 031f87d62..df3a8442a 100644 --- a/lib/Hoymiles/src/inverters/HM_1CH.cpp +++ b/lib/Hoymiles/src/inverters/HM_1CH.cpp @@ -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) { diff --git a/lib/Hoymiles/src/inverters/HM_1CH.h b/lib/Hoymiles/src/inverters/HM_1CH.h index 941864670..11dcbe24d 100644 --- a/lib/Hoymiles/src/inverters/HM_1CH.h +++ b/lib/Hoymiles/src/inverters/HM_1CH.h @@ -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* getByteAssignment(); diff --git a/lib/Hoymiles/src/inverters/HM_2CH.cpp b/lib/Hoymiles/src/inverters/HM_2CH.cpp index bdd18cc3f..87c5f3944 100644 --- a/lib/Hoymiles/src/inverters/HM_2CH.cpp +++ b/lib/Hoymiles/src/inverters/HM_2CH.cpp @@ -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) { diff --git a/lib/Hoymiles/src/inverters/HM_2CH.h b/lib/Hoymiles/src/inverters/HM_2CH.h index 6e8672ca7..a79a854bc 100644 --- a/lib/Hoymiles/src/inverters/HM_2CH.h +++ b/lib/Hoymiles/src/inverters/HM_2CH.h @@ -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* getByteAssignment(); diff --git a/lib/Hoymiles/src/inverters/HM_4CH.cpp b/lib/Hoymiles/src/inverters/HM_4CH.cpp index 6994d5333..cc52b4824 100644 --- a/lib/Hoymiles/src/inverters/HM_4CH.cpp +++ b/lib/Hoymiles/src/inverters/HM_4CH.cpp @@ -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) { diff --git a/lib/Hoymiles/src/inverters/HM_4CH.h b/lib/Hoymiles/src/inverters/HM_4CH.h index eccc60f27..dc5ea550f 100644 --- a/lib/Hoymiles/src/inverters/HM_4CH.h +++ b/lib/Hoymiles/src/inverters/HM_4CH.h @@ -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* getByteAssignment(); diff --git a/lib/Hoymiles/src/inverters/HM_Abstract.cpp b/lib/Hoymiles/src/inverters/HM_Abstract.cpp index a07dec936..097fffeb4 100644 --- a/lib/Hoymiles/src/inverters/HM_Abstract.cpp +++ b/lib/Hoymiles/src/inverters/HM_Abstract.cpp @@ -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* cmd = _radio->enqueCommand(); 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* cmd = _radio->enqueCommand(); 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* cmdAll = _radio->enqueCommand(); cmdAll->setTime(now); cmdAll->setTargetAddress(serial()); - DevInfoSimpleCommand* cmdSimple = radio->enqueCommand(); + DevInfoSimpleCommand* cmdSimple = _radio->enqueCommand(); 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* cmd = _radio->enqueCommand(); 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* cmd = _radio->enqueCommand(); 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* cmd = _radio->enqueCommand(); 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* cmd = _radio->enqueCommand(); 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: diff --git a/lib/Hoymiles/src/inverters/HM_Abstract.h b/lib/Hoymiles/src/inverters/HM_Abstract.h index fe89dd24d..ea44c2427 100644 --- a/lib/Hoymiles/src/inverters/HM_Abstract.h +++ b/lib/Hoymiles/src/inverters/HM_Abstract.h @@ -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; diff --git a/lib/Hoymiles/src/inverters/InverterAbstract.cpp b/lib/Hoymiles/src/inverters/InverterAbstract.cpp index d537f3c17..3833aac69 100644 --- a/lib/Hoymiles/src/inverters/InverterAbstract.cpp +++ b/lib/Hoymiles/src/inverters/InverterAbstract.cpp @@ -7,9 +7,10 @@ #include "crc.h" #include -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); diff --git a/lib/Hoymiles/src/inverters/InverterAbstract.h b/lib/Hoymiles/src/inverters/InverterAbstract.h index 079bf7f41..755414f24 100644 --- a/lib/Hoymiles/src/inverters/InverterAbstract.h +++ b/lib/Hoymiles/src/inverters/InverterAbstract.h @@ -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 @@ class InverterAbstract { 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 @@ class InverterAbstract { StatisticsParser* Statistics(); SystemConfigParaParser* SystemConfigPara(); +protected: + HoymilesRadio* _radio; + private: serial_u _serial; String _serialString; diff --git a/lib/Hoymiles/src/parser/AlarmLogParser.cpp b/lib/Hoymiles/src/parser/AlarmLogParser.cpp index 33075a043..1ac701be5 100644 --- a/lib/Hoymiles/src/parser/AlarmLogParser.cpp +++ b/lib/Hoymiles/src/parser/AlarmLogParser.cpp @@ -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 +const std::array 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; + } + } } } diff --git a/lib/Hoymiles/src/parser/AlarmLogParser.h b/lib/Hoymiles/src/parser/AlarmLogParser.h index 4910abc07..b57948be4 100644 --- a/lib/Hoymiles/src/parser/AlarmLogParser.h +++ b/lib/Hoymiles/src/parser/AlarmLogParser.h @@ -1,8 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include "Parser.h" -#include #include +#include +#include #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 @@ class AlarmLogParser : public Parser { void setLastAlarmRequestSuccess(LastCommandSuccess status); LastCommandSuccess getLastAlarmRequestSuccess(); + void setMessageType(AlarmMessageType_t type); + private: static int getTimezoneOffset(); @@ -33,4 +47,8 @@ class AlarmLogParser : public Parser { uint8_t _alarmLogLength; LastCommandSuccess _lastAlarmRequestSuccess = CMD_NOK; // Set to NOK to fetch at startup + + AlarmMessageType_t _messageType = AlarmMessageType_t::ALL; + + static const std::array _alarmMessages; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/parser/DevInfoParser.cpp b/lib/Hoymiles/src/parser/DevInfoParser.cpp index 343cdd0e2..2e89df2c8 100644 --- a/lib/Hoymiles/src/parser/DevInfoParser.cpp +++ b/lib/Hoymiles/src/parser/DevInfoParser.cpp @@ -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(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() diff --git a/lib/Hoymiles/src/parser/StatisticsParser.h b/lib/Hoymiles/src/parser/StatisticsParser.h index c52514ad0..1b09395e6 100644 --- a/lib/Hoymiles/src/parser/StatisticsParser.h +++ b/lib/Hoymiles/src/parser/StatisticsParser.h @@ -5,7 +5,7 @@ #include #include -#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 @@ class StatisticsParser : public Parser { private: uint8_t _payloadStatistic[STATISTIC_PACKET_SIZE] = {}; uint8_t _statisticLength = 0; - uint16_t _stringMaxPower[CH4]; + uint16_t _stringMaxPower[CH_CNT]; const std::list* _byteAssignment; std::list _fieldSettings; diff --git a/lib/Hoymiles/src/types.h b/lib/Hoymiles/src/types.h index 9a43d2f1c..9ab3f6c81 100644 --- a/lib/Hoymiles/src/types.h +++ b/lib/Hoymiles/src/types.h @@ -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; diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 25f5095a7..b66098c1a 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -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; diff --git a/src/InverterSettings.cpp b/src/InverterSettings.cpp index 39719d23c..13862f158 100644 --- a/src/InverterSettings.cpp +++ b/src/InverterSettings.cpp @@ -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); diff --git a/src/MqttHandleDtu.cpp b/src/MqttHandleDtu.cpp index 818a1b409..dfca23406 100644 --- a/src/MqttHandleDtu.cpp +++ b/src/MqttHandleDtu.cpp @@ -16,7 +16,7 @@ void MqttHandleDtuClass::init() void MqttHandleDtuClass::loop() { - if (!MqttSettings.getConnected() || !Hoymiles.getRadio()->isIdle()) { + if (!MqttSettings.getConnected() || !Hoymiles.isAllRadioIdle()) { return; } diff --git a/src/MqttHandleHass.cpp b/src/MqttHandleHass.cpp index 9aa3e1343..a2f38f0ea 100644 --- a/src/MqttHandleHass.cpp +++ b/src/MqttHandleHass.cpp @@ -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"); diff --git a/src/MqttHandleInverter.cpp b/src/MqttHandleInverter.cpp index 449cc0e46..ed620dc83 100644 --- a/src/MqttHandleInverter.cpp +++ b/src/MqttHandleInverter.cpp @@ -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"); } diff --git a/src/PinMapping.cpp b/src/PinMapping.cpp index cc678a40c..0e5fa797d 100644 --- a/src/PinMapping.cpp +++ b/src/PinMapping.cpp @@ -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; diff --git a/src/PowerLimiter.cpp b/src/PowerLimiter.cpp index af5ef2943..18a0a6b50 100644 --- a/src/PowerLimiter.cpp +++ b/src/PowerLimiter.cpp @@ -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 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(); diff --git a/src/WebApi_device.cpp b/src/WebApi_device.cpp index 2a5f07b4a..4c226b8b8 100644 --- a/src/WebApi_device.cpp +++ b/src/WebApi_device.cpp @@ -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; diff --git a/src/WebApi_dtu.cpp b/src/WebApi_dtu.cpp index 93365a41f..fc4dadef2 100644 --- a/src/WebApi_dtu.cpp +++ b/src/WebApi_dtu.cpp @@ -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() == 0) { + if (root["serial"].as() == 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() == 0) { + if (root["pollinterval"].as() == 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() > 3) { + if (root["nrf_palevel"].as() > 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() < -10 || root["cmt_palevel"].as() > 20) { + retMsg["message"] = "Invalid power level setting!"; + retMsg["code"] = WebApiError::DtuInvalidPowerLevel; + response->setLength(); + request->send(response); + return; + } + + if (root["cmt_frequency"].as() < Hoymiles.getRadioCmt()->getMinFrequency() + || root["cmt_frequency"].as() > Hoymiles.getRadioCmt()->getMaxFrequency() + || root["cmt_frequency"].as() % 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().c_str(), NULL, 16); - config.Dtu_PollInterval = root["dtu_pollinterval"].as(); - config.Dtu_PaLevel = root["dtu_palevel"].as(); + config.Dtu_Serial = strtoll(root["serial"].as().c_str(), NULL, 16); + config.Dtu_PollInterval = root["pollinterval"].as(); + config.Dtu_NrfPaLevel = root["nrf_palevel"].as(); + config.Dtu_CmtPaLevel = root["cmt_palevel"].as(); + config.Dtu_CmtFrequency = root["cmt_frequency"].as(); 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); } \ No newline at end of file diff --git a/src/WebApi_limit.cpp b/src/WebApi_limit.cpp index d69dba1ab..9470e4cac 100644 --- a/src/WebApi_limit.cpp +++ b/src/WebApi_limit.cpp @@ -112,10 +112,10 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request) return; } - if (root["limit_value"].as() == 0 || root["limit_value"].as() > 1500) { - retMsg["message"] = "Limit must between 1 and 1500!"; + if (root["limit_value"].as() == 0 || root["limit_value"].as() > 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!"; diff --git a/src/WebApi_power.cpp b/src/WebApi_power.cpp index d545ffecd..6be68a88e 100644 --- a/src/WebApi_power.cpp +++ b/src/WebApi_power.cpp @@ -118,10 +118,10 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request) if (root.containsKey("power")) { uint16_t power = root["power"].as(); - inv->sendPowerControlRequest(Hoymiles.getRadio(), power); + inv->sendPowerControlRequest(power); } else { if (root["restart"].as()) { - inv->sendRestartControlRequest(Hoymiles.getRadio()); + inv->sendRestartControlRequest(); } } diff --git a/src/WebApi_sysstatus.cpp b/src/WebApi_sysstatus.cpp index f70cdd875..cca3c2969 100644 --- a/src/WebApi_sysstatus.cpp +++ b/src/WebApi_sysstatus.cpp @@ -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); diff --git a/src/WebApi_ws_live.cpp b/src/WebApi_ws_live.cpp index 9e76fe686..46e97d5c2 100644 --- a/src/WebApi_ws_live.cpp +++ b/src/WebApi_ws_live.cpp @@ -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 { diff --git a/webapp/package.json b/webapp/package.json index 0b82c7ea4..a06f3ba46 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -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", diff --git a/webapp/src/components/RadioInfo.vue b/webapp/src/components/RadioInfo.vue index 8f16a212b..2ebf7219f 100644 --- a/webapp/src/components/RadioInfo.vue +++ b/webapp/src/components/RadioInfo.vue @@ -4,27 +4,61 @@ - + - + + + + + + + + + + + + +
{{ $t('radioinfo.ChipStatus') }}{{ $t('radioinfo.Status', { module: "nRF24" }) }} - +
{{ $t('radioinfo.ChipType') }}{{ $t('radioinfo.ChipStatus', { module: "nRF24" }) }} + v-if="systemStatus.nrf_configured && systemStatus.nrf_connected">{{ $t('radioinfo.Connected') }} + v-else-if="systemStatus.nrf_configured && !systemStatus.nrf_connected">{{ $t('radioinfo.NotConnected') }} + +
{{ $t('radioinfo.ChipType', { module: "nRF24" }) }} + + +
{{ $t('radioinfo.Status', { module: "CMT2300A" }) }} + +
{{ $t('radioinfo.ChipStatus', { module: "CMT2300A" }) }} + + + + +
diff --git a/webapp/src/components/VedirectView.vue b/webapp/src/components/VedirectView.vue index 9af9f8e00..a4a4f48b3 100644 --- a/webapp/src/components/VedirectView.vue +++ b/webapp/src/components/VedirectView.vue @@ -59,47 +59,49 @@
{{ $t('vedirecthome.DeviceInfo') }}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ $t('vedirecthome.Property') }}{{ $t('vedirecthome.Value') }}{{ $t('vedirecthome.Unit') }}
{{ $t('vedirecthome.LoadOutputState') }}{{vedirectData.LOAD}}
{{ $t('vedirecthome.StateOfOperation') }}{{vedirectData.CS}}
{{ $t('vedirecthome.TrackerOperationMode') }}{{vedirectData.MPPT}}
{{ $t('vedirecthome.OffReason') }}{{vedirectData.OR}}
{{ $t('vedirecthome.ErrorCode') }}{{vedirectData.ERR}}
{{ $t('vedirecthome.DaySequenceNumber') }}{{vedirectData.HSDS.v}}{{vedirectData.HSDS.u}}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ $t('vedirecthome.Property') }}{{ $t('vedirecthome.Value') }}{{ $t('vedirecthome.Unit') }}
{{ $t('vedirecthome.LoadOutputState') }}{{vedirectData.LOAD}}
{{ $t('vedirecthome.StateOfOperation') }}{{vedirectData.CS}}
{{ $t('vedirecthome.TrackerOperationMode') }}{{vedirectData.MPPT}}
{{ $t('vedirecthome.OffReason') }}{{vedirectData.OR}}
{{ $t('vedirecthome.ErrorCode') }}{{vedirectData.ERR}}
{{ $t('vedirecthome.DaySequenceNumber') }}{{vedirectData.HSDS.v}}{{vedirectData.HSDS.u}}
+
@@ -107,27 +109,29 @@
{{ $t('vedirecthome.Battery') }}
- - - - - - - - - - - - - - - - - - - - -
{{ $t('vedirecthome.Property') }}{{ $t('vedirecthome.Value') }}{{ $t('vedirecthome.Unit') }}
{{ $t('vedirecthome.BatteryVoltage') }}{{formatNumber(vedirectData.V.v)}}{{vedirectData.V.u}}
{{ $t('vedirecthome.BatteryCurrent') }}{{formatNumber(vedirectData.I.v)}}{{vedirectData.I.u}}
+
+ + + + + + + + + + + + + + + + + + + + +
{{ $t('vedirecthome.Property') }}{{ $t('vedirecthome.Value') }}{{ $t('vedirecthome.Unit') }}
{{ $t('vedirecthome.BatteryVoltage') }}{{formatNumber(vedirectData.V.v)}}{{vedirectData.V.u}}
{{ $t('vedirecthome.BatteryCurrent') }}{{formatNumber(vedirectData.I.v)}}{{vedirectData.I.u}}
+
@@ -135,52 +139,54 @@
{{ $t('vedirecthome.Panel') }}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ $t('vedirecthome.Property') }}{{ $t('vedirecthome.Value') }}{{ $t('vedirecthome.Unit') }}
{{ $t('vedirecthome.PanelVoltage') }}{{formatNumber(vedirectData.VPV.v)}}{{vedirectData.VPV.u}}
{{ $t('vedirecthome.PanelPower') }}{{formatNumber(vedirectData.PPV.v)}}{{vedirectData.PPV.u}}
{{ $t('vedirecthome.YieldTotal') }}{{formatNumber(vedirectData.H19.v)}}{{vedirectData.H19.u}}
{{ $t('vedirecthome.YieldToday') }}{{formatNumber(vedirectData.H20.v)}}{{vedirectData.H20.u}}
{{ $t('vedirecthome.MaximumPowerToday') }}{{formatNumber(vedirectData.H21.v)}}{{vedirectData.H21.u}}
{{ $t('vedirecthome.YieldYesterday') }}{{formatNumber(vedirectData.H22.v)}}{{vedirectData.H22.u}}
{{ $t('vedirecthome.MaximumPowerYesterday') }}{{formatNumber(vedirectData.H23.v)}}{{vedirectData.H23.u}}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ $t('vedirecthome.Property') }}{{ $t('vedirecthome.Value') }}{{ $t('vedirecthome.Unit') }}
{{ $t('vedirecthome.PanelVoltage') }}{{formatNumber(vedirectData.VPV.v)}}{{vedirectData.VPV.u}}
{{ $t('vedirecthome.PanelPower') }}{{formatNumber(vedirectData.PPV.v)}}{{vedirectData.PPV.u}}
{{ $t('vedirecthome.YieldTotal') }}{{formatNumber(vedirectData.H19.v)}}{{vedirectData.H19.u}}
{{ $t('vedirecthome.YieldToday') }}{{formatNumber(vedirectData.H20.v)}}{{vedirectData.H20.u}}
{{ $t('vedirecthome.MaximumPowerToday') }}{{formatNumber(vedirectData.H21.v)}}{{vedirectData.H21.u}}
{{ $t('vedirecthome.YieldYesterday') }}{{formatNumber(vedirectData.H22.v)}}{{vedirectData.H22.u}}
{{ $t('vedirecthome.MaximumPowerYesterday') }}{{formatNumber(vedirectData.H23.v)}}{{vedirectData.H23.u}}
+
diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 33e5e25ed..435839d53 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -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 Discord oder Github" }, "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.", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 09d2e6223..8141019b2 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -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": "Hint: The inverter is power by it's DC input. If there is no sun, the inverter is off. Requests can still be sent.", + "StatusHint": "Hint: 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 Discord or Github" }, "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.", @@ -726,4 +737,4 @@ "highVoltage": "High voltage", "bmsInternal": "BMS internal" } -} \ No newline at end of file +} diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index d2c1314cc..d7110822b 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -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 Discord ou sur Github" }, "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é.", diff --git a/webapp/src/types/DtuConfig.ts b/webapp/src/types/DtuConfig.ts index 51e45fa3f..035478809 100644 --- a/webapp/src/types/DtuConfig.ts +++ b/webapp/src/types/DtuConfig.ts @@ -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; } \ No newline at end of file diff --git a/webapp/src/types/PinMapping.ts b/webapp/src/types/PinMapping.ts index 41ae0572f..4691cd1cb 100644 --- a/webapp/src/types/PinMapping.ts +++ b/webapp/src/types/PinMapping.ts @@ -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; diff --git a/webapp/src/types/SystemStatus.ts b/webapp/src/types/SystemStatus.ts index 6be634cd5..80b86c0d9 100644 --- a/webapp/src/types/SystemStatus.ts +++ b/webapp/src/types/SystemStatus.ts @@ -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; } \ No newline at end of file diff --git a/webapp/src/views/DtuAdminView.vue b/webapp/src/views/DtuAdminView.vue index 2ab7f966c..1a5fc079d 100644 --- a/webapp/src/views/DtuAdminView.vue +++ b/webapp/src/views/DtuAdminView.vue @@ -7,28 +7,64 @@
-
-