diff --git a/.github/workflows/cpplint.yml b/.github/workflows/cpplint.yml index f8d6847ab..979b5f0a2 100644 --- a/.github/workflows/cpplint.yml +++ b/.github/workflows/cpplint.yml @@ -19,7 +19,9 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install cpplint + pip install cpplint==1.6.1 - name: Linting run: | - cpplint --repository=. --recursive --filter=-build/c++11,-runtime/references,-readability/braces,-whitespace,-legal,-build/include ./src ./include ./lib/Hoymiles ./lib/MqttSubscribeParser ./lib/TimeoutHelper ./lib/ResetReason ./lib/ETHSPI + cpplint --repository=. --recursive \ + --filter=-build/c++11,-runtime/references,-readability/braces,-whitespace,-legal,-build/include \ + ./src ./include ./lib/Hoymiles ./lib/MqttSubscribeParser ./lib/TimeoutHelper ./lib/ResetReason diff --git a/README.md b/README.md index 02aa0d674..0d44c5965 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ - [Documentation](#documentation) - [State of the project](#state-of-the-project) - [History of the project](#history-of-the-project) - - [Acknowledgment](#acknowledgment) + - [Acknowledgments](#acknowledgments) # OpenDTU-OnBattery @@ -14,9 +14,9 @@ disabled while "create release badge" action is broken, see .github/build.yml ![GitHub tag (latest SemVer)](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/helgeerbe/68b47cc8c8994d04ab3a4fa9d8aee5e6/raw/openDTUcoreRelease.json) ---> -[![OpenDTU-OnBattery Build](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/build.yml/badge.svg)](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/build.yml) -[![cpplint](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/cpplint.yml/badge.svg)](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/cpplint.yml) -[![Yarn Linting](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/yarnlint.yml/badge.svg)](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/yarnlint.yml) +[![OpenDTU-OnBattery Build](https://github.com/hoylabs/OpenDTU-OnBattery/actions/workflows/build.yml/badge.svg)](https://github.com/hoylabs/OpenDTU-OnBattery/actions/workflows/build.yml) +[![cpplint](https://github.com/hoylabs/OpenDTU-OnBattery/actions/workflows/cpplint.yml/badge.svg)](https://github.com/hoylabs/OpenDTU-OnBattery/actions/workflows/cpplint.yml) +[![Yarn Linting](https://github.com/hoylabs/OpenDTU-OnBattery/actions/workflows/yarnlint.yml/badge.svg)](https://github.com/hoylabs/OpenDTU-OnBattery/actions/workflows/yarnlint.yml) ## What is OpenDTU-OnBattery @@ -33,10 +33,10 @@ The canonical documentation of OpenDTU-OnBattery is hosted at You may find additional helpful information in the project's community-maintained [Github -Wiki](https://github.com/helgeerbe/OpenDTU-OnBattery/wiki). +Wiki](https://github.com/hoylabs/OpenDTU-OnBattery/wiki). To find out what's new or improved have a look at the changelog of the -[releases](https://github.com/helgeerbe/OpenDTU-OnBattery/releases). +[releases](https://github.com/hoylabs/OpenDTU-OnBattery/releases). ## State of the project @@ -57,10 +57,15 @@ didn't like the idea to set up a separate ESP32 to receive the charger's data. He decided to fork OpenDTU and extend it with battery charger support and a Dynamic Power Limiter. -## Acknowledgment +In early October 2024, the project moved to the newly founded GitHub +organisation `hoylabs` and is since maintained by multiple community members. -A special Thank to Thomas Basler (tbnobody) the author of the original [OpenDTU](https://github.com/tbnobody/OpenDTU) project. You are doing a great job! +## Acknowledgments -@helgeerbe: Last but not least, I would like to thank all the contributors. -With your ideas and enhancements, you have made OpenDTU-OnBattery much more -than I originally had in mind. +* Special thanks to Thomas Basler (@tbnobody), the author of the [upstream + project](https://github.com/tbnobody/OpenDTU), for hist continued effort! +* Thanks to @helgeerbe for starting OpenDTU-OnBattery and his dedication to the + project, as well as his trust in the current maintainers of the project, + which act as part of the `hoylabs` GitHub organisation. +* We like to thank all contributors. With your ideas and enhancements, you have + made OpenDTU-OnBattery much more than @helgeerbe originally had in mind. diff --git a/docs/DeviceProfiles/opendtu_fusion.json b/docs/DeviceProfiles/opendtu_fusion.json index b74058a44..990f4c46b 100644 --- a/docs/DeviceProfiles/opendtu_fusion.json +++ b/docs/DeviceProfiles/opendtu_fusion.json @@ -1,6 +1,9 @@ [ { "name": "OpenDTU Fusion v1", + "links": [ + {"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"} + ], "nrf24": { "miso": 48, "mosi": 35, @@ -25,6 +28,9 @@ }, { "name": "OpenDTU Fusion v1 with SSD1306 Display", + "links": [ + {"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"} + ], "nrf24": { "miso": 48, "mosi": 35, @@ -54,6 +60,9 @@ }, { "name": "OpenDTU Fusion v1 with SH1106 Display", + "links": [ + {"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"} + ], "nrf24": { "miso": 48, "mosi": 35, @@ -83,6 +92,9 @@ }, { "name": "OpenDTU Fusion v2 with CMT2300A and NRF24", + "links": [ + {"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"} + ], "nrf24": { "miso": 48, "mosi": 35, @@ -115,6 +127,9 @@ }, { "name": "OpenDTU Fusion v2 with CMT2300A, NRF24 and SH1106 Display", + "links": [ + {"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"} + ], "nrf24": { "miso": 48, "mosi": 35, @@ -152,6 +167,9 @@ }, { "name": "OpenDTU Fusion v2 with CMT2300A, NRF24 and SSD1306 Display", + "links": [ + {"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"} + ], "nrf24": { "miso": 48, "mosi": 35, @@ -188,7 +206,10 @@ } }, { - "name": "OpenDTU Fusion v2 with NRF24 and W5500 ethernet", + "name": "OpenDTU Fusion v2 PoE", + "links": [ + {"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"} + ], "nrf24": { "miso": 48, "mosi": 35, @@ -198,35 +219,43 @@ "cs": 37 }, "cmt": { - "clk": -1, - "cs": -1, - "fcs": -1, - "sdio": -1, - "gpio2": -1, - "gpio3": -1 - }, - "led": { - "led0": 17, - "led1": 18 + "clk": 6, + "cs": 4, + "fcs": 21, + "sdio": 5, + "gpio2": 3, + "gpio3": 8 }, "w5500": { - "sclk": 39, "mosi": 40, "miso": 41, + "sclk": 39, "cs": 42, - "rst": 43, - "int": 44 + "int": 44, + "rst": 43 + }, + "led": { + "led0": 17, + "led1": 18 + }, + "display": { + "type": 0, + "data": 2, + "clk": 1 } }, { - "name": "OpenDTU Fusion v2 with CMT2300A and W5500 ethernet", + "name": "OpenDTU Fusion v2 PoE with SH1106 Display", + "links": [ + {"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"} + ], "nrf24": { - "miso": -1, - "mosi": -1, - "clk": -1, - "irq": -1, - "en": -1, - "cs": -1 + "miso": 48, + "mosi": 35, + "clk": 36, + "irq": 47, + "en": 38, + "cs": 37 }, "cmt": { "clk": 6, @@ -236,17 +265,61 @@ "gpio2": 3, "gpio3": 8 }, + "w5500": { + "mosi": 40, + "miso": 41, + "sclk": 39, + "cs": 42, + "int": 44, + "rst": 43 + }, "led": { "led0": 17, "led1": 18 }, + "display": { + "type": 3, + "data": 2, + "clk": 1 + } + }, + { + "name": "OpenDTU Fusion v2 PoE with SSD1306 Display", + "links": [ + {"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"} + ], + "nrf24": { + "miso": 48, + "mosi": 35, + "clk": 36, + "irq": 47, + "en": 38, + "cs": 37 + }, + "cmt": { + "clk": 6, + "cs": 4, + "fcs": 21, + "sdio": 5, + "gpio2": 3, + "gpio3": 8 + }, "w5500": { - "sclk": 39, "mosi": 40, "miso": 41, + "sclk": 39, "cs": 42, - "rst": 43, - "int": 44 + "int": 44, + "rst": 43 + }, + "led": { + "led0": 17, + "led1": 18 + }, + "display": { + "type": 2, + "data": 2, + "clk": 1 } } ] diff --git a/include/MqttHandleHass.h b/include/MqttHandleHass.h index 7f6737d57..86a8e3fb0 100644 --- a/include/MqttHandleHass.h +++ b/include/MqttHandleHass.h @@ -13,7 +13,6 @@ enum DeviceClassType { DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, - DEVICE_CLS_TEMP, DEVICE_CLS_POWER_FACTOR, DEVICE_CLS_REACTIVE_POWER, DEVICE_CLS_CONNECTIVITY, @@ -22,7 +21,7 @@ enum DeviceClassType { DEVICE_CLS_TEMPERATURE, DEVICE_CLS_RESTART }; -const char* const deviceClass_name[] = { 0, "current", "energy", "power", "voltage", "frequency", "temperature", "power_factor", "reactive_power", "connectivity", "duration", "signal_strength", "temperature", "restart" }; +const char* const deviceClass_name[] = { 0, "current", "energy", "power", "voltage", "frequency", "power_factor", "reactive_power", "connectivity", "duration", "signal_strength", "temperature", "restart" }; enum StateClassType { STATE_CLS_NONE = 0, @@ -55,7 +54,7 @@ const byteAssign_fieldDeviceClass_t deviceFieldAssignment[] = { { FLD_IAC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT }, { FLD_PAC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT }, { FLD_F, DEVICE_CLS_FREQ, STATE_CLS_MEASUREMENT }, - { FLD_T, DEVICE_CLS_TEMP, STATE_CLS_MEASUREMENT }, + { FLD_T, DEVICE_CLS_TEMPERATURE, STATE_CLS_MEASUREMENT }, { FLD_PF, DEVICE_CLS_POWER_FACTOR, STATE_CLS_MEASUREMENT }, { FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE }, { FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE }, @@ -78,21 +77,21 @@ class MqttHandleHassClass { static void publish(const String& subtopic, const String& payload); static void publish(const String& subtopic, const JsonDocument& doc); - static void addCommonMetadata(JsonDocument& doc, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const CategoryType category); + static void addCommonMetadata(JsonDocument& doc, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category); // Binary Sensor - static void publishBinarySensor(JsonDocument& doc, const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const CategoryType category); - static void publishDtuBinarySensor(const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const CategoryType category); - static void publishInverterBinarySensor(std::shared_ptr inv, const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const CategoryType category); + static void publishBinarySensor(JsonDocument& doc, const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category); + static void publishDtuBinarySensor(const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category); + static void publishInverterBinarySensor(std::shared_ptr inv, const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category); // Sensor - static void publishSensor(JsonDocument& doc, const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const CategoryType category); - static void publishDtuSensor(const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const CategoryType category); - static void publishInverterSensor(std::shared_ptr inv, const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const CategoryType category); + static void publishSensor(JsonDocument& doc, const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category); + static void publishDtuSensor(const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category); + static void publishInverterSensor(std::shared_ptr inv, const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category); static void publishInverterField(std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const byteAssign_fieldDeviceClass_t fieldType, const bool clear = false); - static void publishInverterButton(std::shared_ptr inv, const String& name, const String& state_topic, const String& payload, const String& icon, const DeviceClassType device_class, const CategoryType category); - static void publishInverterNumber(std::shared_ptr inv, const String& name, const String& state_topic, const String& command_topic, const int16_t min, const int16_t max, float step, const String& unit_of_measure, const String& icon, const CategoryType category); + static void publishInverterButton(std::shared_ptr inv, const String& name, const String& state_topic, const String& payload, const String& icon, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category); + static void publishInverterNumber(std::shared_ptr inv, const String& name, const String& state_topic, const String& command_topic, const int16_t min, const int16_t max, float step, const String& unit_of_measure, const String& icon, const StateClassType state_class, const CategoryType category); static void createInverterInfo(JsonDocument& doc, std::shared_ptr inv); static void createDtuInfo(JsonDocument& doc); diff --git a/include/NetworkSettings.h b/include/NetworkSettings.h index 7c0db3589..90d3962b4 100644 --- a/include/NetworkSettings.h +++ b/include/NetworkSettings.h @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include "W5500.h" #include #include #include @@ -23,18 +24,18 @@ enum class network_event { NETWORK_EVENT_MAX }; -typedef std::function NetworkEventCb; +typedef std::function DtuNetworkEventCb; -typedef struct NetworkEventCbList { - NetworkEventCb cb; +typedef struct DtuNetworkEventCbList { + DtuNetworkEventCb cb; network_event event; - NetworkEventCbList() + DtuNetworkEventCbList() : cb(nullptr) , event(network_event::NETWORK_UNKNOWN) { } -} NetworkEventCbList_t; +} DtuNetworkEventCbList_t; class NetworkSettingsClass { public: @@ -53,7 +54,7 @@ class NetworkSettingsClass { bool isConnected() const; network_mode NetworkMode() const; - bool onEvent(NetworkEventCb cbEvent, const network_event event = network_event::NETWORK_EVENT_MAX); + bool onEvent(DtuNetworkEventCb cbEvent, const network_event event = network_event::NETWORK_EVENT_MAX); void raiseEvent(const network_event event); private: @@ -81,9 +82,9 @@ class NetworkSettingsClass { bool _dnsServerStatus = false; network_mode _networkMode = network_mode::Undefined; bool _ethConnected = false; - std::vector _cbEventList; + std::vector _cbEventList; bool _lastMdnsEnabled = false; - bool _spiEth = false; + std::unique_ptr _w5500; }; extern NetworkSettingsClass NetworkSettings; diff --git a/include/PinMapping.h b/include/PinMapping.h index 0603b4c4d..bd7c9b0ce 100644 --- a/include/PinMapping.h +++ b/include/PinMapping.h @@ -12,6 +12,7 @@ struct PinMapping_t { char name[MAPPING_NAME_STRLEN + 1]; + int8_t nrf24_miso; int8_t nrf24_mosi; int8_t nrf24_clk; @@ -26,13 +27,14 @@ struct PinMapping_t { int8_t cmt_gpio3; int8_t cmt_sdio; - int8_t w5500_sclk; int8_t w5500_mosi; int8_t w5500_miso; + int8_t w5500_sclk; int8_t w5500_cs; int8_t w5500_int; int8_t w5500_rst; +#if CONFIG_ETH_USE_ESP32_EMAC int8_t eth_phy_addr; bool eth_enabled; int eth_power; @@ -40,11 +42,14 @@ struct PinMapping_t { int eth_mdio; eth_phy_type_t eth_type; eth_clock_mode_t eth_clk_mode; +#endif + uint8_t display_type; uint8_t display_data; uint8_t display_clk; uint8_t display_cs; uint8_t display_reset; + int8_t led[PINMAPPING_LED_COUNT]; // OpenDTU-OnBattery-specific pins below @@ -80,7 +85,9 @@ class PinMappingClass { bool isValidNrf24Config() const; bool isValidCmt2300Config() const; bool isValidW5500Config() const; +#if CONFIG_ETH_USE_ESP32_EMAC bool isValidEthConfig() const; +#endif bool isValidHuaweiConfig() const; private: diff --git a/include/SPIPortManager.h b/include/SPIPortManager.h deleted file mode 100644 index d3d58896f..000000000 --- a/include/SPIPortManager.h +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#pragma once - -#include -#include -#include -#include - -/** - * SPI# to SPI ID and SPI_HOST mapping - * - * ESP32 - * | SPI # | SPI ID | SPI_HOST | - * | 2 | 2 | 1 | - * | 3 | 3 | 2 | - * - * ESP32-S3 - * | SPI # | SPI ID | SPI_HOST | - * | 2 | 0 | 1 | - * | 3 | 1 | 2 | - * - * ESP32-C3 - * | SPI # | SPI ID | SPI_HOST | - * | 2 | 0 | 1 | - * - */ - -class SPIPortManagerClass { -public: - void init(); - - std::optional allocatePort(std::string const& owner); - void freePort(std::string const& owner); - spi_host_device_t SPIhostNum(uint8_t spi_num); - -private: - // the amount of SPIs available on supported ESP32 chips - #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S3 - static size_t constexpr _num_controllers = 4; - #else - static size_t constexpr _num_controllers = 3; - #endif - - #if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 - static int8_t constexpr _offset_spi_num = -2; // FSPI=0, HSPI=1 - static int8_t constexpr _offset_spi_host = 1; // SPI1_HOST=0 but not usable, SPI2_HOST=1 and SPI3_HOST=2, first usable is SPI2_HOST - #else - static int8_t constexpr _offset_spi_num = 0; // HSPI=2, VSPI=3 - static int8_t constexpr _offset_spi_host = -1; // SPI1_HOST=0 but not usable, SPI2_HOST=1 and SPI3_HOST=2, first usable is SPI2_HOST - #endif - - std::array _ports = { "" }; -}; - -extern SPIPortManagerClass SPIPortManager; diff --git a/include/W5500.h b/include/W5500.h new file mode 100644 index 000000000..d85cb016c --- /dev/null +++ b/include/W5500.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include +#include // required for esp_eth_handle_t +#include + +#include + +class W5500 { +private: + explicit W5500(spi_device_handle_t spi, gpio_num_t pin_int); + +public: + W5500(const W5500&) = delete; + W5500& operator=(const W5500&) = delete; + ~W5500(); + + static std::unique_ptr setup(int8_t pin_mosi, int8_t pin_miso, int8_t pin_sclk, int8_t pin_cs, int8_t pin_int, int8_t pin_rst); + String macAddress(); + +private: + static bool connection_check_spi(spi_device_handle_t spi); + static bool connection_check_interrupt(gpio_num_t pin_int); + + esp_eth_handle_t eth_handle; + esp_netif_t* eth_netif; +}; diff --git a/include/WebApi.h b/include/WebApi.h index 0a433b8c3..c995ecfca 100644 --- a/include/WebApi.h +++ b/include/WebApi.h @@ -38,6 +38,7 @@ class WebApiClass { public: WebApiClass(); void init(Scheduler& scheduler); + void reload(); static bool checkCredentials(AsyncWebServerRequest* request); static bool checkCredentialsReadonly(AsyncWebServerRequest* request); diff --git a/include/WebApi_ws_Huawei.h b/include/WebApi_ws_Huawei.h index 43e528e83..9ab9c8b7c 100644 --- a/include/WebApi_ws_Huawei.h +++ b/include/WebApi_ws_Huawei.h @@ -10,6 +10,7 @@ class WebApiWsHuaweiLiveClass { public: WebApiWsHuaweiLiveClass(); void init(AsyncWebServer& server, Scheduler& scheduler); + void reload(); private: void generateCommonJsonResponse(JsonVariant& root); @@ -18,6 +19,7 @@ class WebApiWsHuaweiLiveClass { AsyncWebServer* _server; AsyncWebSocket _ws; + AuthenticationMiddleware _simpleDigestAuth; std::mutex _mutex; diff --git a/include/WebApi_ws_battery.h b/include/WebApi_ws_battery.h index d89e01aec..bc014d390 100644 --- a/include/WebApi_ws_battery.h +++ b/include/WebApi_ws_battery.h @@ -10,6 +10,7 @@ class WebApiWsBatteryLiveClass { public: WebApiWsBatteryLiveClass(); void init(AsyncWebServer& server, Scheduler& scheduler); + void reload(); private: void generateCommonJsonResponse(JsonVariant& root); @@ -18,6 +19,7 @@ class WebApiWsBatteryLiveClass { AsyncWebServer* _server; AsyncWebSocket _ws; + AuthenticationMiddleware _simpleDigestAuth; uint32_t _lastUpdateCheck = 0; static constexpr uint16_t _responseSize = 1024 + 512; diff --git a/include/WebApi_ws_console.h b/include/WebApi_ws_console.h index cf7beecce..b3194319d 100644 --- a/include/WebApi_ws_console.h +++ b/include/WebApi_ws_console.h @@ -8,9 +8,11 @@ class WebApiWsConsoleClass { public: WebApiWsConsoleClass(); void init(AsyncWebServer& server, Scheduler& scheduler); + void reload(); private: AsyncWebSocket _ws; + AuthenticationMiddleware _simpleDigestAuth; Task _wsCleanupTask; void wsCleanupTaskCb(); diff --git a/include/WebApi_ws_live.h b/include/WebApi_ws_live.h index 4a29fff5b..e02f9a8c1 100644 --- a/include/WebApi_ws_live.h +++ b/include/WebApi_ws_live.h @@ -11,6 +11,7 @@ class WebApiWsLiveClass { public: WebApiWsLiveClass(); void init(AsyncWebServer& server, Scheduler& scheduler); + void reload(); private: static void generateInverterCommonJsonResponse(JsonObject& root, std::shared_ptr inv); @@ -27,6 +28,7 @@ class WebApiWsLiveClass { void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len); AsyncWebSocket _ws; + AuthenticationMiddleware _simpleDigestAuth; uint32_t _lastPublishOnBatteryFull = 0; uint32_t _lastPublishVictron = 0; diff --git a/include/WebApi_ws_vedirect_live.h b/include/WebApi_ws_vedirect_live.h index b9890834e..7c3bedf60 100644 --- a/include/WebApi_ws_vedirect_live.h +++ b/include/WebApi_ws_vedirect_live.h @@ -12,6 +12,7 @@ class WebApiWsVedirectLiveClass { public: WebApiWsVedirectLiveClass(); void init(AsyncWebServer& server, Scheduler& scheduler); + void reload(); private: void generateCommonJsonResponse(JsonVariant& root, bool fullUpdate); @@ -22,6 +23,7 @@ class WebApiWsVedirectLiveClass { AsyncWebServer* _server; AsyncWebSocket _ws; + AuthenticationMiddleware _simpleDigestAuth; uint32_t _lastFullPublish = 0; uint32_t _lastPublish = 0; diff --git a/lib/CMT2300a/cmt2300a_hal.c b/lib/CMT2300a/cmt2300a_hal.c index 73f6cab71..7b07d499f 100644 --- a/lib/CMT2300a/cmt2300a_hal.c +++ b/lib/CMT2300a/cmt2300a_hal.c @@ -26,9 +26,9 @@ * @name CMT2300A_InitSpi * @desc Initializes the CMT2300A SPI interface. * *********************************************************/ -void CMT2300A_InitSpi(const spi_host_device_t spi_host, const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const uint32_t spi_speed) +void CMT2300A_InitSpi(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const uint32_t spi_speed) { - cmt_spi3_init(spi_host, pin_sdio, pin_clk, pin_cs, pin_fcs, spi_speed); + cmt_spi3_init(pin_sdio, pin_clk, pin_cs, pin_fcs, spi_speed); } /*! ******************************************************** diff --git a/lib/CMT2300a/cmt2300a_hal.h b/lib/CMT2300a/cmt2300a_hal.h index 150e10f09..a465b1490 100644 --- a/lib/CMT2300a/cmt2300a_hal.h +++ b/lib/CMT2300a/cmt2300a_hal.h @@ -23,7 +23,6 @@ #include #include -#include #ifdef __cplusplus extern "C" { @@ -37,7 +36,7 @@ extern "C" { #define CMT2300A_GetTickCount() millis() /* ************************************************************************ */ -void CMT2300A_InitSpi(const spi_host_device_t spi_host, const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const uint32_t spi_speed); +void CMT2300A_InitSpi(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const uint32_t spi_speed); uint8_t CMT2300A_ReadReg(const uint8_t addr); void CMT2300A_WriteReg(const uint8_t addr, const uint8_t dat); diff --git a/lib/CMT2300a/cmt2300wrapper.cpp b/lib/CMT2300a/cmt2300wrapper.cpp index f04bdeea9..016ef56fd 100644 --- a/lib/CMT2300a/cmt2300wrapper.cpp +++ b/lib/CMT2300a/cmt2300wrapper.cpp @@ -7,9 +7,8 @@ #include "cmt2300a_params_860.h" #include "cmt2300a_params_900.h" -CMT2300A::CMT2300A(const spi_host_device_t spi_host, const uint8_t pin_sdio, const uint8_t pin_clk, const uint8_t pin_cs, const uint8_t pin_fcs, const uint32_t spi_speed) +CMT2300A::CMT2300A(const uint8_t pin_sdio, const uint8_t pin_clk, const uint8_t pin_cs, const uint8_t pin_fcs, const uint32_t spi_speed) { - _spi_host = spi_host; _pin_sdio = pin_sdio; _pin_clk = pin_clk; _pin_cs = pin_cs; @@ -267,7 +266,7 @@ void CMT2300A::flush_rx(void) bool CMT2300A::_init_pins() { - CMT2300A_InitSpi(_spi_host, _pin_sdio, _pin_clk, _pin_cs, _pin_fcs, _spi_speed); + CMT2300A_InitSpi(_pin_sdio, _pin_clk, _pin_cs, _pin_fcs, _spi_speed); return true; // assuming pins are connected properly } diff --git a/lib/CMT2300a/cmt2300wrapper.h b/lib/CMT2300a/cmt2300wrapper.h index b818d972b..d1639fe9b 100644 --- a/lib/CMT2300a/cmt2300wrapper.h +++ b/lib/CMT2300a/cmt2300wrapper.h @@ -2,7 +2,6 @@ #pragma once #include -#include #define CMT2300A_ONE_STEP_SIZE 2500 // frequency channel step size for fast frequency hopping operation: One step size is 2.5 kHz. #define FH_OFFSET 100 // value * CMT2300A_ONE_STEP_SIZE = channel frequency offset @@ -19,7 +18,7 @@ enum FrequencyBand_t { class CMT2300A { public: - CMT2300A(const spi_host_device_t spi_host, const uint8_t pin_sdio, const uint8_t pin_clk, const uint8_t pin_cs, const uint8_t pin_fcs, const uint32_t _spi_speed = CMT_SPI_SPEED); + CMT2300A(const uint8_t pin_sdio, const uint8_t pin_clk, const uint8_t pin_cs, const uint8_t pin_fcs, const uint32_t _spi_speed = CMT_SPI_SPEED); bool begin(void); @@ -129,7 +128,6 @@ class CMT2300A { */ bool _init_radio(); - spi_host_device_t _spi_host; int8_t _pin_sdio; int8_t _pin_clk; int8_t _pin_cs; diff --git a/lib/CMT2300a/cmt_spi3.c b/lib/CMT2300a/cmt_spi3.c deleted file mode 100644 index 7263f4d17..000000000 --- a/lib/CMT2300a/cmt_spi3.c +++ /dev/null @@ -1,136 +0,0 @@ -#include "cmt_spi3.h" -#include -#include // for esp_rom_gpio_connect_out_signal - -SemaphoreHandle_t paramLock = NULL; -#define SPI_PARAM_LOCK() \ - do { \ - } while (xSemaphoreTake(paramLock, portMAX_DELAY) != pdPASS) -#define SPI_PARAM_UNLOCK() xSemaphoreGive(paramLock) - -spi_device_handle_t spi_reg, spi_fifo; - -void cmt_spi3_init(const spi_host_device_t spi_host, const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const uint32_t spi_speed) -{ - paramLock = xSemaphoreCreateMutex(); - - 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 = 1, - .address_bits = 7, - .dummy_bits = 0, - .mode = 0, // SPI mode 0 - .cs_ena_pretrans = 1, - .cs_ena_posttrans = 1, - .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(spi_host, &buscfg, SPI_DMA_DISABLED)); - ESP_ERROR_CHECK(spi_bus_add_device(spi_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(spi_host, &devcfg2, &spi_fifo)); - - esp_rom_gpio_connect_out_signal(pin_sdio, spi_periph_signal[spi_host].spid_out, true, false); - delay(100); -} - -void cmt_spi3_write(const uint8_t addr, const uint8_t dat) -{ - uint8_t tx_data; - tx_data = ~dat; - spi_transaction_t t = { - .cmd = 1, - .addr = ~addr, - .length = 8, - .tx_buffer = &tx_data, - .rx_buffer = NULL - }; - SPI_PARAM_LOCK(); - ESP_ERROR_CHECK(spi_device_polling_transmit(spi_reg, &t)); - SPI_PARAM_UNLOCK(); - delayMicroseconds(100); -} - -uint8_t cmt_spi3_read(const uint8_t addr) -{ - uint8_t rx_data; - spi_transaction_t t = { - .cmd = 0, - .addr = ~addr, - .length = 8, - .rxlength = 8, - .tx_buffer = NULL, - .rx_buffer = &rx_data - }; - SPI_PARAM_LOCK(); - ESP_ERROR_CHECK(spi_device_polling_transmit(spi_reg, &t)); - SPI_PARAM_UNLOCK(); - delayMicroseconds(100); - return rx_data; -} - -void cmt_spi3_write_fifo(const uint8_t* buf, const uint16_t len) -{ - uint8_t tx_data; - - spi_transaction_t t = { - .length = 8, - .tx_buffer = &tx_data, // reference to write data - .rx_buffer = NULL - }; - - SPI_PARAM_LOCK(); - for (uint8_t i = 0; i < len; i++) { - tx_data = ~buf[i]; // negate buffer contents - ESP_ERROR_CHECK(spi_device_polling_transmit(spi_fifo, &t)); - delayMicroseconds(4); // > 4 us - } - SPI_PARAM_UNLOCK(); -} - -void cmt_spi3_read_fifo(uint8_t* buf, const uint16_t len) -{ - uint8_t rx_data; - - spi_transaction_t t = { - .length = 8, - .rxlength = 8, - .tx_buffer = NULL, - .rx_buffer = &rx_data - }; - - SPI_PARAM_LOCK(); - for (uint8_t i = 0; i < len; i++) { - ESP_ERROR_CHECK(spi_device_polling_transmit(spi_fifo, &t)); - delayMicroseconds(4); // > 4 us - buf[i] = rx_data; - } - SPI_PARAM_UNLOCK(); -} diff --git a/lib/CMT2300a/cmt_spi3.cpp b/lib/CMT2300a/cmt_spi3.cpp new file mode 100644 index 000000000..28fdc8aee --- /dev/null +++ b/lib/CMT2300a/cmt_spi3.cpp @@ -0,0 +1,155 @@ +#include "cmt_spi3.h" +#include +#include +#include + +SemaphoreHandle_t paramLock = NULL; +#define SPI_PARAM_LOCK() \ + do { \ + } while (xSemaphoreTake(paramLock, portMAX_DELAY) != pdPASS) +#define SPI_PARAM_UNLOCK() xSemaphoreGive(paramLock) + +static void IRAM_ATTR pre_cb(spi_transaction_t *trans) { + gpio_set_level(*reinterpret_cast(trans->user), 0); +} + +static void IRAM_ATTR post_cb(spi_transaction_t *trans) { + gpio_set_level(*reinterpret_cast(trans->user), 1); +} + +spi_device_handle_t spi; +gpio_num_t cs_reg, cs_fifo; + +void cmt_spi3_init(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const int32_t spi_speed) +{ + paramLock = xSemaphoreCreateMutex(); + + auto bus_config = std::make_shared( + static_cast(pin_sdio), + GPIO_NUM_NC, + static_cast(pin_clk) + ); + + spi_device_interface_config_t device_config { + .command_bits = 0, // set by transactions individually + .address_bits = 0, // set by transactions individually + .dummy_bits = 0, + .mode = 0, // SPI mode 0 + .duty_cycle_pos = 0, + .cs_ena_pretrans = 2, // only 1 pre and post cycle would be required for register access + .cs_ena_posttrans = static_cast(2 * spi_speed / 1000000), // >2 us + .clock_speed_hz = spi_speed, + .input_delay_ns = 0, + .spics_io_num = -1, // CS handled by callbacks + .flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_3WIRE, + .queue_size = 1, + .pre_cb = pre_cb, + .post_cb = post_cb, + }; + + spi = SpiManagerInst.alloc_device("", bus_config, device_config); + if (!spi) + ESP_ERROR_CHECK(ESP_FAIL); + + cs_reg = static_cast(pin_cs); + ESP_ERROR_CHECK(gpio_reset_pin(cs_reg)); + ESP_ERROR_CHECK(gpio_set_level(cs_reg, 1)); + ESP_ERROR_CHECK(gpio_set_direction(cs_reg, GPIO_MODE_OUTPUT)); + + cs_fifo = static_cast(pin_fcs); + ESP_ERROR_CHECK(gpio_reset_pin(cs_fifo)); + ESP_ERROR_CHECK(gpio_set_level(cs_fifo, 1)); + ESP_ERROR_CHECK(gpio_set_direction(cs_fifo, GPIO_MODE_OUTPUT)); +} + +void cmt_spi3_write(const uint8_t addr, const uint8_t data) +{ + spi_transaction_ext_t trans { + .base { + .flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR, + .cmd = 0, + .addr = addr, + .length = 8, + .rxlength = 0, + .user = &cs_reg, // CS for register access + .tx_buffer = &data, + .rx_buffer = nullptr, + }, + .command_bits = 1, + .address_bits = 7, + .dummy_bits = 0, + }; + SPI_PARAM_LOCK(); + ESP_ERROR_CHECK(spi_device_polling_transmit(spi, reinterpret_cast(&trans))); + SPI_PARAM_UNLOCK(); +} + +uint8_t cmt_spi3_read(const uint8_t addr) +{ + uint8_t data; + spi_transaction_ext_t trans { + .base { + .flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR, + .cmd = 1, + .addr = addr, + .length = 0, + .rxlength = 8, + .user = &cs_reg, // CS for register access + .tx_buffer = nullptr, + .rx_buffer = &data, + }, + .command_bits = 1, + .address_bits = 7, + .dummy_bits = 0, + }; + SPI_PARAM_LOCK(); + ESP_ERROR_CHECK(spi_device_polling_transmit(spi, reinterpret_cast(&trans))); + SPI_PARAM_UNLOCK(); + return data; +} + +void cmt_spi3_write_fifo(const uint8_t* buf, const uint16_t len) +{ + spi_transaction_t trans { + .flags = 0, + .cmd = 0, + .addr = 0, + .length = 8, + .rxlength = 0, + .user = &cs_fifo, // CS for FIFO access + .tx_buffer = nullptr, + .rx_buffer = nullptr, + }; + + SPI_PARAM_LOCK(); + spi_device_acquire_bus(spi, portMAX_DELAY); + for (uint8_t i = 0; i < len; i++) { + trans.tx_buffer = buf + i; + ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &trans)); + } + spi_device_release_bus(spi); + SPI_PARAM_UNLOCK(); +} + +void cmt_spi3_read_fifo(uint8_t* buf, const uint16_t len) +{ + spi_transaction_t trans { + .flags = 0, + .cmd = 0, + .addr = 0, + .length = 0, + .rxlength = 8, + .user = &cs_fifo, // CS for FIFO access + .tx_buffer = nullptr, + .rx_buffer = nullptr, + }; + + SPI_PARAM_LOCK(); + spi_device_acquire_bus(spi, portMAX_DELAY); + for (uint8_t i = 0; i < len; i++) { + trans.rx_buffer = buf + i; + ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &trans)); + } + spi_device_release_bus(spi); + SPI_PARAM_UNLOCK(); +} diff --git a/lib/CMT2300a/cmt_spi3.h b/lib/CMT2300a/cmt_spi3.h index 5cce47db7..16655dbad 100644 --- a/lib/CMT2300a/cmt_spi3.h +++ b/lib/CMT2300a/cmt_spi3.h @@ -2,9 +2,12 @@ #define __CMT_SPI3_H #include -#include -void cmt_spi3_init(const spi_host_device_t spi_host, const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const uint32_t spi_speed); +#ifdef __cplusplus +extern "C" { +#endif + +void cmt_spi3_init(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const int32_t spi_speed); void cmt_spi3_write(const uint8_t addr, const uint8_t dat); uint8_t cmt_spi3_read(const uint8_t addr); @@ -12,4 +15,8 @@ uint8_t cmt_spi3_read(const uint8_t addr); void cmt_spi3_write_fifo(const uint8_t* p_buf, const uint16_t len); void cmt_spi3_read_fifo(uint8_t* p_buf, const uint16_t len); +#ifdef __cplusplus +} +#endif + #endif diff --git a/lib/ETHSPI/src/ETHSPI.cpp b/lib/ETHSPI/src/ETHSPI.cpp deleted file mode 100644 index 2e2ff8189..000000000 --- a/lib/ETHSPI/src/ETHSPI.cpp +++ /dev/null @@ -1,127 +0,0 @@ -//----------------------------------------------------------------------------- -// 2024 Ahoy, https://ahoydtu.de -// adapted to OpenDTU-OnBattery -// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed -//----------------------------------------------------------------------------- - -#include "ETHSPI.h" - -#include - -// Functions from WiFiGeneric -void tcpipInit(); -void add_esp_interface_netif(esp_interface_t interface, esp_netif_t* esp_netif); - -ETHSPIClass::ETHSPIClass() : - eth_handle(nullptr), - eth_netif(nullptr) -{ - -} - -void ETHSPIClass::begin(int8_t pin_sclk, int8_t pin_mosi, int8_t pin_miso, int8_t pin_cs, int8_t pin_int, int8_t pin_rst, spi_host_device_t host_id) -{ - gpio_reset_pin(static_cast(pin_rst)); - gpio_set_direction(static_cast(pin_rst), GPIO_MODE_OUTPUT); - gpio_set_level(static_cast(pin_rst), 0); - - gpio_reset_pin(static_cast(pin_sclk)); - gpio_reset_pin(static_cast(pin_mosi)); - gpio_reset_pin(static_cast(pin_miso)); - gpio_reset_pin(static_cast(pin_cs)); - gpio_set_pull_mode(static_cast(pin_miso), GPIO_PULLUP_ONLY); - - // Workaround, because calling gpio_install_isr_service directly causes issues with attachInterrupt later - attachInterrupt(digitalPinToInterrupt(pin_int), nullptr, CHANGE); - detachInterrupt(digitalPinToInterrupt(pin_int)); - gpio_reset_pin(static_cast(pin_int)); - gpio_set_pull_mode(static_cast(pin_int), GPIO_PULLUP_ONLY); - - spi_bus_config_t buscfg = { - .mosi_io_num = pin_mosi, - .miso_io_num = pin_miso, - .sclk_io_num = pin_sclk, - .quadwp_io_num = -1, - .quadhd_io_num = -1, - .data4_io_num = -1, - .data5_io_num = -1, - .data6_io_num = -1, - .data7_io_num = -1, - .max_transfer_sz = 0, // uses default value internally - .flags = 0, - .intr_flags = 0 - }; - - ESP_ERROR_CHECK(spi_bus_initialize(host_id, &buscfg, SPI_DMA_CH_AUTO)); - - spi_device_interface_config_t devcfg = { - .command_bits = 16, // actually address phase - .address_bits = 8, // actually command phase - .dummy_bits = 0, - .mode = 0, - .duty_cycle_pos = 0, - .cs_ena_pretrans = 0, // only 0 supported - .cs_ena_posttrans = 0, // only 0 supported - .clock_speed_hz = 20000000, // stable with on OpenDTU Fusion Shield - .input_delay_ns = 0, - .spics_io_num = pin_cs, - .flags = 0, - .queue_size = 20, - .pre_cb = nullptr, - .post_cb = nullptr - }; - - spi_device_handle_t spi; - ESP_ERROR_CHECK(spi_bus_add_device(host_id, &devcfg, &spi)); - - // Reset sequence - delayMicroseconds(500); - gpio_set_level(static_cast(pin_rst), 1); - delayMicroseconds(1000); - - // Arduino function to start networking stack if not already started - tcpipInit(); - - ESP_ERROR_CHECK(tcpip_adapter_set_default_eth_handlers()); // ? - - eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi); - w5500_config.int_gpio_num = pin_int; - - eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); - mac_config.rx_task_stack_size = 4096; - esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config); - - eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); - phy_config.reset_gpio_num = -1; - esp_eth_phy_t *phy = esp_eth_phy_new_w5500(&phy_config); - - esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy); - ESP_ERROR_CHECK(esp_eth_driver_install(ð_config, ð_handle)); - - // Configure MAC address - uint8_t mac_addr[6]; - ESP_ERROR_CHECK(esp_efuse_mac_get_default(mac_addr)); - mac_addr[5] |= 0x03; // derive ethernet MAC address from base MAC address - ESP_ERROR_CHECK(esp_eth_ioctl(eth_handle, ETH_CMD_S_MAC_ADDR, mac_addr)); - - esp_netif_config_t netif_config = ESP_NETIF_DEFAULT_ETH(); - eth_netif = esp_netif_new(&netif_config); - - ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle))); - - // Add to Arduino - add_esp_interface_netif(ESP_IF_ETH, eth_netif); - - ESP_ERROR_CHECK(esp_eth_start(eth_handle)); -} - -String ETHSPIClass::macAddress() -{ - uint8_t mac_addr[6] = {0, 0, 0, 0, 0, 0}; - esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr); - char mac_addr_str[24]; - snprintf(mac_addr_str, sizeof(mac_addr_str), "%02X:%02X:%02X:%02X:%02X:%02X", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); - return String(mac_addr_str); -} - -ETHSPIClass ETHSPI; diff --git a/lib/ETHSPI/src/ETHSPI.h b/lib/ETHSPI/src/ETHSPI.h deleted file mode 100644 index 38174b2fa..000000000 --- a/lib/ETHSPI/src/ETHSPI.h +++ /dev/null @@ -1,26 +0,0 @@ -//----------------------------------------------------------------------------- -// 2024 Ahoy, https://ahoydtu.de -// adapted to OpenDTU-OnBattery -// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed -//----------------------------------------------------------------------------- - -#pragma once - -#include -#include -#include - -class ETHSPIClass -{ -private: - esp_eth_handle_t eth_handle; - esp_netif_t *eth_netif; - -public: - ETHSPIClass(); - - void begin(int8_t pin_sclk, int8_t pin_mosi, int8_t pin_miso, int8_t pin_cs, int8_t pin_int, int8_t pin_rst, spi_host_device_t host_id); - String macAddress(); -}; - -extern ETHSPIClass ETHSPI; diff --git a/lib/Hoymiles/src/Hoymiles.cpp b/lib/Hoymiles/src/Hoymiles.cpp index a0c28cdec..7273648c5 100644 --- a/lib/Hoymiles/src/Hoymiles.cpp +++ b/lib/Hoymiles/src/Hoymiles.cpp @@ -32,9 +32,9 @@ void HoymilesClass::initNRF(SPIClass* initialisedSpiBus, const uint8_t pinCE, co _radioNrf->init(initialisedSpiBus, pinCE, pinIRQ); } -void HoymilesClass::initCMT(const spi_host_device_t spi_host, const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const int8_t pin_gpio2, const int8_t pin_gpio3) +void HoymilesClass::initCMT(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const int8_t pin_gpio2, const int8_t pin_gpio3) { - _radioCmt->init(spi_host, pin_sdio, pin_clk, pin_cs, pin_fcs, pin_gpio2, pin_gpio3); + _radioCmt->init(pin_sdio, pin_clk, pin_cs, pin_fcs, pin_gpio2, pin_gpio3); } void HoymilesClass::loop() diff --git a/lib/Hoymiles/src/Hoymiles.h b/lib/Hoymiles/src/Hoymiles.h index 9c578f3ac..86a7d6ca6 100644 --- a/lib/Hoymiles/src/Hoymiles.h +++ b/lib/Hoymiles/src/Hoymiles.h @@ -9,7 +9,6 @@ #include #include #include -#include #define HOY_SYSTEM_CONFIG_PARA_POLL_INTERVAL (2 * 60 * 1000) // 2 minutes #define HOY_SYSTEM_CONFIG_PARA_POLL_MIN_DURATION (4 * 60 * 1000) // at least 4 minutes between sending limit command and read request. Otherwise eventlog entry @@ -18,7 +17,7 @@ class HoymilesClass { public: void init(); void initNRF(SPIClass* initialisedSpiBus, const uint8_t pinCE, const uint8_t pinIRQ); - void initCMT(const spi_host_device_t spi_host, const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const int8_t pin_gpio2, const int8_t pin_gpio3); + void initCMT(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const int8_t pin_gpio2, const int8_t pin_gpio3); void loop(); void setMessageOutput(Print* output); @@ -55,4 +54,4 @@ class HoymilesClass { Print* _messageOutput = &Serial; }; -extern HoymilesClass Hoymiles; +extern HoymilesClass Hoymiles; \ No newline at end of file diff --git a/lib/Hoymiles/src/HoymilesRadio_CMT.cpp b/lib/Hoymiles/src/HoymilesRadio_CMT.cpp index 1a203cb41..f6a6f45ac 100644 --- a/lib/Hoymiles/src/HoymilesRadio_CMT.cpp +++ b/lib/Hoymiles/src/HoymilesRadio_CMT.cpp @@ -34,7 +34,7 @@ uint32_t HoymilesRadio_CMT::getFrequencyFromChannel(const uint8_t channel) const uint8_t HoymilesRadio_CMT::getChannelFromFrequency(const uint32_t frequency) const { if ((frequency % getChannelWidth()) != 0) { - Hoymiles.getMessageOutput()->printf("%.3f MHz is not divisible by %d kHz!\r\n", frequency / 1000000.0, getChannelWidth()); + Hoymiles.getMessageOutput()->printf("%.3f MHz is not divisible by %" PRId32 " kHz!\r\n", frequency / 1000000.0, getChannelWidth()); return 0xFF; // ERROR } if (frequency < getMinFrequency() || frequency > getMaxFrequency()) { @@ -43,7 +43,7 @@ uint8_t HoymilesRadio_CMT::getChannelFromFrequency(const uint32_t frequency) con return 0xFF; // ERROR } if (frequency < countryDefinition.at(_countryMode).Freq_Legal_Min || frequency > countryDefinition.at(_countryMode).Freq_Legal_Max) { - Hoymiles.getMessageOutput()->printf("!!! caution: %.2f MHz is out of region legal range! (%d - %d MHz)\r\n", + Hoymiles.getMessageOutput()->printf("!!! caution: %.2f MHz is out of region legal range! (%" PRId32 " - %" PRId32 " MHz)\r\n", frequency / 1000000.0, static_cast(countryDefinition.at(_countryMode).Freq_Legal_Min / 1e6), static_cast(countryDefinition.at(_countryMode).Freq_Legal_Max / 1e6)); @@ -83,11 +83,11 @@ bool HoymilesRadio_CMT::cmtSwitchDtuFreq(const uint32_t to_frequency) return true; } -void HoymilesRadio_CMT::init(const spi_host_device_t spi_host, const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const int8_t pin_gpio2, const int8_t pin_gpio3) +void HoymilesRadio_CMT::init(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const int8_t pin_gpio2, const int8_t pin_gpio3) { _dtuSerial.u64 = 0; - _radio.reset(new CMT2300A(spi_host, pin_sdio, pin_clk, pin_cs, pin_fcs)); + _radio.reset(new CMT2300A(pin_sdio, pin_clk, pin_cs, pin_fcs)); _radio->begin(); @@ -167,9 +167,9 @@ void HoymilesRadio_CMT::loop() // Save packet in inverter rx buffer Hoymiles.getVerboseMessageOutput()->printf("RX %.2f MHz --> ", getFrequencyFromChannel(f.channel) / 1000000.0); dumpBuf(f.fragment, f.len, false); - Hoymiles.getVerboseMessageOutput()->printf("| %d dBm\r\n", f.rssi); + Hoymiles.getVerboseMessageOutput()->printf("| %" PRId8 " dBm\r\n", f.rssi); - inv->addRxFragment(f.fragment, f.len); + inv->addRxFragment(f.fragment, f.len, f.rssi); } else { Hoymiles.getMessageOutput()->println("Inverter Not found!"); } @@ -194,9 +194,9 @@ void HoymilesRadio_CMT::setPALevel(const int8_t paLevel) } if (_radio->setPALevel(paLevel)) { - Hoymiles.getMessageOutput()->printf("CMT TX power set to %d dBm\r\n", paLevel); + Hoymiles.getMessageOutput()->printf("CMT TX power set to %" PRId8 " 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); + Hoymiles.getMessageOutput()->printf("CMT TX power %" PRId8 " dBm is not defined! (min: -10 dBm, max: 20 dBm)\r\n", paLevel); } } diff --git a/lib/Hoymiles/src/HoymilesRadio_CMT.h b/lib/Hoymiles/src/HoymilesRadio_CMT.h index b6e54430e..770617fe3 100644 --- a/lib/Hoymiles/src/HoymilesRadio_CMT.h +++ b/lib/Hoymiles/src/HoymilesRadio_CMT.h @@ -9,7 +9,6 @@ #include #include #include -#include // number of fragments hold in buffer #define FRAGMENT_BUFFER_SIZE 30 @@ -42,7 +41,7 @@ struct CountryFrequencyList_t { class HoymilesRadio_CMT : public HoymilesRadio { public: - void init(const spi_host_device_t spi_host, const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const int8_t pin_gpio2, const int8_t pin_gpio3); + void init(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const int8_t pin_gpio2, const int8_t pin_gpio3); void loop(); void setPALevel(const int8_t paLevel); void setInverterTargetFrequency(const uint32_t frequency); diff --git a/lib/Hoymiles/src/HoymilesRadio_NRF.cpp b/lib/Hoymiles/src/HoymilesRadio_NRF.cpp index 635014d65..14ce2a49e 100644 --- a/lib/Hoymiles/src/HoymilesRadio_NRF.cpp +++ b/lib/Hoymiles/src/HoymilesRadio_NRF.cpp @@ -76,11 +76,11 @@ void HoymilesRadio_NRF::loop() if (nullptr != inv) { // Save packet in inverter rx buffer - Hoymiles.getVerboseMessageOutput()->printf("RX Channel: %d --> ", f.channel); + Hoymiles.getVerboseMessageOutput()->printf("RX Channel: %" PRId8 " --> ", f.channel); dumpBuf(f.fragment, f.len, false); - Hoymiles.getVerboseMessageOutput()->printf("| %d dBm\r\n", f.rssi); + Hoymiles.getVerboseMessageOutput()->printf("| %" PRId8 " dBm\r\n", f.rssi); - inv->addRxFragment(f.fragment, f.len); + inv->addRxFragment(f.fragment, f.len, f.rssi); } else { Hoymiles.getMessageOutput()->println("Inverter Not found!"); } @@ -183,7 +183,7 @@ void HoymilesRadio_NRF::sendEsbPacket(CommandAbstract& cmd) openWritingPipe(s); _radio->setRetries(3, 15); - Hoymiles.getVerboseMessageOutput()->printf("TX %s Channel: %d --> ", + Hoymiles.getVerboseMessageOutput()->printf("TX %s Channel: %" PRId8 " --> ", cmd.getCommandName().c_str(), _radio->getChannel()); cmd.dumpDataPayload(Hoymiles.getVerboseMessageOutput()); _radio->write(cmd.getDataPayload(), cmd.getDataSize()); diff --git a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp index b1396a4dd..9f5563bf9 100644 --- a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp +++ b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp @@ -48,7 +48,7 @@ bool RealTimeRunDataCommand::handleResponse(const fragment_t fragment[], const u const uint8_t fragmentsSize = getTotalFragmentSize(fragment, max_fragment_id); const uint8_t expectedSize = _inv->Statistics()->getExpectedByteCount(); if (fragmentsSize < expectedSize) { - Hoymiles.getMessageOutput()->printf("ERROR in %s: Received fragment size: %d, min expected size: %d\r\n", + Hoymiles.getMessageOutput()->printf("ERROR in %s: Received fragment size: %" PRId8 ", min expected size: %" PRId8 "\r\n", getCommandName().c_str(), fragmentsSize, expectedSize); return false; diff --git a/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp b/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp index 0c142afc8..70dcffa98 100644 --- a/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp +++ b/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp @@ -48,7 +48,7 @@ bool SystemConfigParaCommand::handleResponse(const fragment_t fragment[], const const uint8_t fragmentsSize = getTotalFragmentSize(fragment, max_fragment_id); const uint8_t expectedSize = _inv->SystemConfigPara()->getExpectedByteCount(); if (fragmentsSize < expectedSize) { - Hoymiles.getMessageOutput()->printf("ERROR in %s: Received fragment size: %d, min expected size: %d\r\n", + Hoymiles.getMessageOutput()->printf("ERROR in %s: Received fragment size: %" PRId8 ", min expected size: %" PRId8 "\r\n", getCommandName().c_str(), fragmentsSize, expectedSize); return false; diff --git a/lib/Hoymiles/src/inverters/InverterAbstract.cpp b/lib/Hoymiles/src/inverters/InverterAbstract.cpp index 3e51bbd71..5d52a380c 100644 --- a/lib/Hoymiles/src/inverters/InverterAbstract.cpp +++ b/lib/Hoymiles/src/inverters/InverterAbstract.cpp @@ -137,6 +137,11 @@ bool InverterAbstract::getClearEventlogOnMidnight() const return _clearEventlogOnMidnight; } +int8_t InverterAbstract::getLastRssi() const +{ + return _lastRssi; +} + bool InverterAbstract::sendChangeChannelRequest() { return false; @@ -185,8 +190,10 @@ void InverterAbstract::clearRxFragmentBuffer() _rxFragmentRetransmitCnt = 0; } -void InverterAbstract::addRxFragment(const uint8_t fragment[], const uint8_t len) +void InverterAbstract::addRxFragment(const uint8_t fragment[], const uint8_t len, const int8_t rssi) { + _lastRssi = rssi; + if (len < 11) { Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) fragment too short\r\n", __FILE__, __LINE__); return; @@ -208,7 +215,7 @@ void InverterAbstract::addRxFragment(const uint8_t fragment[], const uint8_t len } if (fragmentId >= MAX_RF_FRAGMENT_COUNT) { - Hoymiles.getMessageOutput()->printf("ERROR: fragment id %d is too large for buffer and ignored\r\n", fragmentId); + Hoymiles.getMessageOutput()->printf("ERROR: fragment id %" PRId8 " is too large for buffer and ignored\r\n", fragmentId); return; } diff --git a/lib/Hoymiles/src/inverters/InverterAbstract.h b/lib/Hoymiles/src/inverters/InverterAbstract.h index f139fab3d..29fba12fa 100644 --- a/lib/Hoymiles/src/inverters/InverterAbstract.h +++ b/lib/Hoymiles/src/inverters/InverterAbstract.h @@ -61,8 +61,10 @@ class InverterAbstract { void setClearEventlogOnMidnight(const bool enabled); bool getClearEventlogOnMidnight() const; + int8_t getLastRssi() const; + void clearRxFragmentBuffer(); - void addRxFragment(const uint8_t fragment[], const uint8_t len); + void addRxFragment(const uint8_t fragment[], const uint8_t len, const int8_t rssi); uint8_t verifyAllFragments(CommandAbstract& cmd); void performDailyTask(); @@ -131,6 +133,8 @@ class InverterAbstract { bool _zeroYieldDayOnMidnight = false; bool _clearEventlogOnMidnight = false; + int8_t _lastRssi = -127; + std::unique_ptr _alarmLogParser; std::unique_ptr _devInfoParser; std::unique_ptr _gridProfileParser; diff --git a/lib/SpiManager/library.json b/lib/SpiManager/library.json new file mode 100644 index 000000000..22e5ddc99 --- /dev/null +++ b/lib/SpiManager/library.json @@ -0,0 +1,13 @@ +{ + "name": "SpiManager", + "keywords": "spi", + "description": "Library for managing the allocation of dedicated or shared SPI buses on the ESP32.", + "authors": { + "name": "Lennart Ferlemann" + }, + "version": "0.0.1", + "frameworks": "arduino", + "platforms": [ + "espressif32" + ] +} diff --git a/lib/SpiManager/src/SpiBus.cpp b/lib/SpiManager/src/SpiBus.cpp new file mode 100644 index 000000000..0dcb5e4f4 --- /dev/null +++ b/lib/SpiManager/src/SpiBus.cpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include "SpiBus.h" +#include "SpiBusConfig.h" +#include "SpiCallback.h" + +SpiBus::SpiBus(const std::string& _id, spi_host_device_t _host_device) + : id(_id) + , host_device(_host_device) + , cur_config(nullptr) +{ + spi_bus_config_t bus_config { + .mosi_io_num = -1, + .miso_io_num = -1, + .sclk_io_num = -1, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .data4_io_num = -1, + .data5_io_num = -1, + .data6_io_num = -1, + .data7_io_num = -1, + .max_transfer_sz = SPI_MAX_DMA_LEN, + .flags = 0, + .intr_flags = 0 + }; + ESP_ERROR_CHECK(spi_bus_initialize(host_device, &bus_config, SPI_DMA_CH_AUTO)); +} + +SpiBus::~SpiBus() +{ + ESP_ERROR_CHECK(spi_bus_free(host_device)); +} + +spi_device_handle_t SpiBus::add_device(const std::shared_ptr& bus_config, spi_device_interface_config_t& device_config) +{ + if (!SpiCallback::patch(shared_from_this(), bus_config, device_config)) + return nullptr; + + spi_device_handle_t device; + ESP_ERROR_CHECK(spi_bus_add_device(host_device, &device_config, &device)); + return device; +} + +// TODO: add remove_device (with spi_device_acquire_bus) + +void SpiBus::apply_config(SpiBusConfig* config) +{ + if (cur_config) + cur_config->unpatch(host_device); + cur_config = config; + if (cur_config) + cur_config->patch(host_device); +} diff --git a/lib/SpiManager/src/SpiBus.h b/lib/SpiManager/src/SpiBus.h new file mode 100644 index 000000000..1ca79c7ca --- /dev/null +++ b/lib/SpiManager/src/SpiBus.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include +#include + +class SpiBusConfig; + +class SpiBus : public std::enable_shared_from_this { +public: + explicit SpiBus(const std::string& id, spi_host_device_t host_device); + SpiBus(const SpiBus&) = delete; + SpiBus& operator=(const SpiBus&) = delete; + ~SpiBus(); + + inline __attribute__((always_inline)) void require_config(SpiBusConfig* config) + { + if (config == cur_config) + return; + apply_config(config); + } + + inline __attribute__((always_inline)) void free_config(SpiBusConfig* config) + { + if (config != cur_config) + return; + apply_config(nullptr); + } + + inline const std::string& get_id() const + { + return id; + } + + inline spi_host_device_t get_host_device() const + { + return host_device; + } + + spi_device_handle_t add_device(const std::shared_ptr& bus_config, spi_device_interface_config_t& device_config); + +private: + void apply_config(SpiBusConfig* config); + + std::string id; + spi_host_device_t host_device; + SpiBusConfig* cur_config; +}; diff --git a/lib/SpiManager/src/SpiBusConfig.cpp b/lib/SpiManager/src/SpiBusConfig.cpp new file mode 100644 index 000000000..64234d658 --- /dev/null +++ b/lib/SpiManager/src/SpiBusConfig.cpp @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include "SpiBusConfig.h" + +#include +#include +#include + +SpiBusConfig::SpiBusConfig(gpio_num_t _pin_mosi, gpio_num_t _pin_miso, gpio_num_t _pin_sclk) + : pin_mosi(_pin_mosi) + , pin_miso(_pin_miso) + , pin_sclk(_pin_sclk) +{ + if (pin_mosi != GPIO_NUM_NC) { + ESP_ERROR_CHECK(gpio_reset_pin(pin_mosi)); + ESP_ERROR_CHECK(gpio_set_direction(pin_mosi, GPIO_MODE_INPUT_OUTPUT)); + } + + if (pin_miso != GPIO_NUM_NC) { + ESP_ERROR_CHECK(gpio_reset_pin(pin_miso)); + ESP_ERROR_CHECK(gpio_set_direction(pin_miso, GPIO_MODE_INPUT)); + } + + if (pin_sclk != GPIO_NUM_NC) { + ESP_ERROR_CHECK(gpio_reset_pin(pin_sclk)); + ESP_ERROR_CHECK(gpio_set_direction(pin_sclk, GPIO_MODE_INPUT_OUTPUT)); + } +} + +SpiBusConfig::~SpiBusConfig() +{ + if (pin_mosi != GPIO_NUM_NC) + ESP_ERROR_CHECK(gpio_reset_pin(pin_mosi)); + + if (pin_miso != GPIO_NUM_NC) + ESP_ERROR_CHECK(gpio_reset_pin(pin_miso)); + + if (pin_sclk != GPIO_NUM_NC) + ESP_ERROR_CHECK(gpio_reset_pin(pin_sclk)); +} + +void SpiBusConfig::patch(spi_host_device_t host_device) +{ + if (pin_mosi != GPIO_NUM_NC) { + esp_rom_gpio_connect_out_signal(pin_mosi, spi_periph_signal[host_device].spid_out, false, false); + esp_rom_gpio_connect_in_signal(pin_mosi, spi_periph_signal[host_device].spid_in, false); + } + + if (pin_miso != GPIO_NUM_NC) + esp_rom_gpio_connect_in_signal(pin_miso, spi_periph_signal[host_device].spiq_in, false); + + if (pin_sclk != GPIO_NUM_NC) { + esp_rom_gpio_connect_out_signal(pin_sclk, spi_periph_signal[host_device].spiclk_out, false, false); + esp_rom_gpio_connect_in_signal(pin_sclk, spi_periph_signal[host_device].spiclk_in, false); + } +} + +void SpiBusConfig::unpatch(spi_host_device_t host_device) +{ + if (pin_mosi != GPIO_NUM_NC) { + esp_rom_gpio_connect_out_signal(pin_mosi, SIG_GPIO_OUT_IDX, false, false); + esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, spi_periph_signal[host_device].spid_in, false); + } + + if (pin_miso != GPIO_NUM_NC) + esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, spi_periph_signal[host_device].spiq_in, false); + + if (pin_sclk != GPIO_NUM_NC) { + esp_rom_gpio_connect_out_signal(pin_sclk, SIG_GPIO_OUT_IDX, false, false); + esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, spi_periph_signal[host_device].spiclk_in, false); + } +} diff --git a/lib/SpiManager/src/SpiBusConfig.h b/lib/SpiManager/src/SpiBusConfig.h new file mode 100644 index 000000000..736b89519 --- /dev/null +++ b/lib/SpiManager/src/SpiBusConfig.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include + +class SpiBusConfig { +public: + explicit SpiBusConfig(gpio_num_t pin_mosi, gpio_num_t pin_miso, gpio_num_t pin_sclk); + SpiBusConfig(const SpiBusConfig&) = delete; + SpiBusConfig& operator=(const SpiBusConfig&) = delete; + ~SpiBusConfig(); + + void patch(spi_host_device_t host_device); + void unpatch(spi_host_device_t host_device); + +private: + gpio_num_t pin_mosi; + gpio_num_t pin_miso; + gpio_num_t pin_sclk; +}; diff --git a/lib/SpiManager/src/SpiCallback.cpp b/lib/SpiManager/src/SpiCallback.cpp new file mode 100644 index 000000000..e353d04bf --- /dev/null +++ b/lib/SpiManager/src/SpiCallback.cpp @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include "SpiCallback.h" + +#include "SpiBus.h" +#include +#include + +namespace SpiCallback { +namespace { + struct CallbackData { + std::shared_ptr bus; + std::shared_ptr config; + transaction_cb_t inner_pre_cb; + transaction_cb_t inner_post_cb; + }; + + std::array, SPI_MANAGER_CALLBACK_COUNT> instances; + + template + void IRAM_ATTR fn_pre_cb(spi_transaction_t* trans) + { + instances[N]->bus->require_config(instances[N]->config.get()); + if (instances[N]->inner_pre_cb) + instances[N]->inner_pre_cb(trans); + } + + template + void IRAM_ATTR fn_post_cb(spi_transaction_t* trans) + { + if (instances[N]->inner_post_cb) + instances[N]->inner_post_cb(trans); + } + + template + inline __attribute__((always_inline)) bool alloc(CallbackData*& instance, transaction_cb_t& pre_cb, transaction_cb_t& post_cb) + { + if constexpr (N > 0) { + if (alloc(instance, pre_cb, post_cb)) + return true; + if (!instances[N - 1]) { + instances[N - 1].emplace(); + instance = &*instances[N - 1]; + pre_cb = fn_pre_cb; + post_cb = fn_post_cb; + return true; + } + } + return false; + } +} + +bool patch(const std::shared_ptr& bus, const std::shared_ptr& bus_config, spi_device_interface_config_t& device_config) +{ + CallbackData* instance; + transaction_cb_t pre_cb; + transaction_cb_t post_cb; + if (!alloc(instance, pre_cb, post_cb)) + return false; + + instance->bus = bus; + instance->config = bus_config; + instance->inner_pre_cb = device_config.pre_cb; + instance->inner_post_cb = device_config.post_cb; + device_config.pre_cb = pre_cb; + device_config.post_cb = post_cb; + + return true; +} +} diff --git a/lib/SpiManager/src/SpiCallback.h b/lib/SpiManager/src/SpiCallback.h new file mode 100644 index 000000000..98222b1a9 --- /dev/null +++ b/lib/SpiManager/src/SpiCallback.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include + +// Pre and post callbacks for 2 buses with 3 devices each +#define SPI_MANAGER_CALLBACK_COUNT 6 + +class SpiBus; +class SpiBusConfig; + +namespace SpiCallback { +bool patch(const std::shared_ptr& bus, const std::shared_ptr& bus_config, spi_device_interface_config_t& device_config); +} diff --git a/lib/SpiManager/src/SpiManager.cpp b/lib/SpiManager/src/SpiManager.cpp new file mode 100644 index 000000000..d727a96ef --- /dev/null +++ b/lib/SpiManager/src/SpiManager.cpp @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include "SpiManager.h" + +#ifdef ARDUINO +#include +#endif + +SpiManager::SpiManager() +{ +} + +#ifdef ARDUINO + +std::optional SpiManager::to_arduino(spi_host_device_t host_device) +{ + switch (host_device) { +#if CONFIG_IDF_TARGET_ESP32 + case SPI1_HOST: + return FSPI; + case SPI2_HOST: + return HSPI; + case SPI3_HOST: + return VSPI; +#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + case SPI2_HOST: + return FSPI; + case SPI3_HOST: + return HSPI; +#elif CONFIG_IDF_TARGET_ESP32C3 + case SPI2_HOST: + return FSPI; +#endif + default: + return std::nullopt; + } +} + +#endif + +bool SpiManager::register_bus(spi_host_device_t host_device) +{ + for (int i = 0; i < SPI_MANAGER_NUM_BUSES; ++i) { + if (available_buses[i]) + continue; + + available_buses[i] = host_device; + return true; + } + + return false; +} + +bool SpiManager::claim_bus(spi_host_device_t& host_device) +{ + for (int i = SPI_MANAGER_NUM_BUSES - 1; i >= 0; --i) { + if (!available_buses[i]) + continue; + + host_device = *available_buses[i]; + available_buses[i].reset(); + return true; + } + + return false; +} + +#ifdef ARDUINO + +std::optional SpiManager::claim_bus_arduino() +{ + spi_host_device_t host_device; + if (!claim_bus(host_device)) + return std::nullopt; + return to_arduino(host_device); +} + +#endif + +spi_device_handle_t SpiManager::alloc_device(const std::string& bus_id, const std::shared_ptr& bus_config, spi_device_interface_config_t& device_config) +{ + std::shared_ptr shared_bus = get_shared_bus(bus_id); + if (!shared_bus) + return nullptr; + + return shared_bus->add_device(bus_config, device_config); +} + +std::shared_ptr SpiManager::get_shared_bus(const std::string& bus_id) +{ + // look for existing shared bus + for (int i = 0; i < SPI_MANAGER_NUM_BUSES; ++i) { + if (!shared_buses[i]) + continue; + if (shared_buses[i]->get_id() == bus_id) + return shared_buses[i]; + } + + // create new shared bus + for (int i = 0; i < SPI_MANAGER_NUM_BUSES; ++i) { + if (shared_buses[i]) + continue; + + spi_host_device_t host_device; + if (!claim_bus(host_device)) + return nullptr; + + shared_buses[i] = std::make_shared(bus_id, host_device); + return shared_buses[i]; + } + + return nullptr; +} + +SpiManager SpiManagerInst; diff --git a/lib/SpiManager/src/SpiManager.h b/lib/SpiManager/src/SpiManager.h new file mode 100644 index 000000000..1e8f6e1b0 --- /dev/null +++ b/lib/SpiManager/src/SpiManager.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include "SpiBus.h" +#include "SpiBusConfig.h" + +#include + +#include +#include +#include +#include + +#define SPI_MANAGER_NUM_BUSES SOC_SPI_PERIPH_NUM + +class SpiManager { +public: + explicit SpiManager(); + SpiManager(const SpiManager&) = delete; + SpiManager& operator=(const SpiManager&) = delete; + +#ifdef ARDUINO + static std::optional to_arduino(spi_host_device_t host_device); +#endif + + bool register_bus(spi_host_device_t host_device); + bool claim_bus(spi_host_device_t& host_device); +#ifdef ARDUINO + std::optional claim_bus_arduino(); +#endif + + spi_device_handle_t alloc_device(const std::string& bus_id, const std::shared_ptr& bus_config, spi_device_interface_config_t& device_config); + +private: + std::shared_ptr get_shared_bus(const std::string& bus_id); + + std::array, SPI_MANAGER_NUM_BUSES> available_buses; + std::array, SPI_MANAGER_NUM_BUSES> shared_buses; +}; + +extern SpiManager SpiManagerInst; diff --git a/platformio.ini b/platformio.ini index ddae0e7e8..651141f2d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,7 +19,7 @@ extra_configs = custom_ci_action = generic_esp32_4mb_no_ota,generic_esp32_8mb,generic_esp32s3,generic_esp32s3_usb framework = arduino -platform = espressif32@6.8.1 +platform = espressif32@6.9.0 platform_packages = platformio/tool-mklittlefs @@ -41,7 +41,7 @@ build_unflags = -std=gnu++11 lib_deps = - mathieucarbou/ESPAsyncWebServer @ 3.3.1 + mathieucarbou/ESPAsyncWebServer @ 3.3.12 bblanchon/ArduinoJson @ 7.2.0 https://github.com/bertmelis/espMqttClient.git#v1.7.0 nrf24/RF24 @ 1.4.9 @@ -86,6 +86,7 @@ board_build.partitions = partitions_custom_4mb.csv [env:generic_esp32_8mb] board = esp32dev +board_upload.flash_size = 8MB build_flags = ${env.build_flags} @@ -131,6 +132,7 @@ build_flags = ${env.build_flags} [env:olimex_esp32_poe] ; https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/open-source-hardware board = esp32-poe +board_upload.flash_size = 8MB build_flags = ${env.build_flags} -DHOYMILES_PIN_MISO=15 -DHOYMILES_PIN_MOSI=2 @@ -243,6 +245,7 @@ build_flags = ${env.build_flags} -DLED0=17 -DLED1=18 -DARDUINO_USB_MODE=1 + -DARDUINO_USB_CDC_ON_BOOT=1 [env:opendtufusionv2] board = esp32-s3-devkitc-1 @@ -266,3 +269,32 @@ build_flags = ${env.build_flags} -DCMT_SDIO=5 -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 + +[env:opendtufusionv2_poe] +board = esp32-s3-devkitc-1 +upload_protocol = esp-builtin +debug_tool = esp-builtin +debug_speed = 12000 +build_flags = ${env.build_flags} + -DHOYMILES_PIN_MISO=48 + -DHOYMILES_PIN_MOSI=35 + -DHOYMILES_PIN_SCLK=36 + -DHOYMILES_PIN_IRQ=47 + -DHOYMILES_PIN_CE=38 + -DHOYMILES_PIN_CS=37 + -DLED0=17 + -DLED1=18 + -DCMT_CLK=6 + -DCMT_CS=4 + -DCMT_FCS=21 + -DCMT_GPIO2=3 + -DCMT_GPIO3=8 + -DCMT_SDIO=5 + -DW5500_MOSI=40 + -DW5500_MISO=41 + -DW5500_SCLK=39 + -DW5500_CS=42 + -DW5500_INT=44 + -DW5500_RST=43 + -DARDUINO_USB_MODE=1 + -DARDUINO_USB_CDC_ON_BOOT=1 diff --git a/src/Display_Graphic_Diagram.cpp b/src/Display_Graphic_Diagram.cpp index b52968829..fb0b68fea 100644 --- a/src/Display_Graphic_Diagram.cpp +++ b/src/Display_Graphic_Diagram.cpp @@ -87,7 +87,7 @@ void DisplayGraphicDiagramClass::redraw(uint8_t screenSaverOffsetX, uint8_t xPos if (maxWatts > 999) { snprintf(fmtText, sizeof(fmtText), "%2.1fkW", maxWatts / 1000); } else { - snprintf(fmtText, sizeof(fmtText), "%dW", static_cast(maxWatts)); + snprintf(fmtText, sizeof(fmtText), "%" PRId16 "W", static_cast(maxWatts)); } if (isFullscreen) { diff --git a/src/Huawei_can.cpp b/src/Huawei_can.cpp index adab23f0e..c215ddb16 100644 --- a/src/Huawei_can.cpp +++ b/src/Huawei_can.cpp @@ -9,8 +9,7 @@ #include "PowerLimiter.h" #include "Configuration.h" #include "Battery.h" -#include "SPIPortManager.h" -#include +#include "SpiManager.h" #include #include @@ -37,10 +36,11 @@ void HuaweiCanCommunicationTask(void* parameter) { bool HuaweiCanCommClass::init(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk, uint8_t huawei_irq, uint8_t huawei_cs, uint32_t frequency) { - auto oSPInum = SPIPortManager.allocatePort("Huawei CAN"); - if (!oSPInum) { return false; } + auto spi_bus = SpiManagerInst.claim_bus_arduino(); + if (!spi_bus) { return false; } + + SPI = new SPIClass(*spi_bus); - SPI = new SPIClass(*oSPInum); SPI->begin(huawei_clk, huawei_miso, huawei_mosi, huawei_cs); pinMode(huawei_cs, OUTPUT); digitalWrite(huawei_cs, HIGH); diff --git a/src/InverterSettings.cpp b/src/InverterSettings.cpp index ffe4d8455..d2d929196 100644 --- a/src/InverterSettings.cpp +++ b/src/InverterSettings.cpp @@ -7,8 +7,8 @@ #include "MessageOutput.h" #include "PinMapping.h" #include "SunPosition.h" -#include "SPIPortManager.h" #include +#include InverterSettingsClass InverterSettings; @@ -24,32 +24,27 @@ void InverterSettingsClass::init(Scheduler& scheduler) const PinMapping_t& pin = PinMapping.get(); // Initialize inverter communication - MessageOutput.println("Initialize Hoymiles interface... "); + MessageOutput.print("Initialize Hoymiles interface... "); Hoymiles.setMessageOutput(&MessageOutput); Hoymiles.init(); if (PinMapping.isValidNrf24Config() || PinMapping.isValidCmt2300Config()) { if (PinMapping.isValidNrf24Config()) { - auto oSPInum = SPIPortManager.allocatePort("NRF24"); + auto spi_bus = SpiManagerInst.claim_bus_arduino(); + ESP_ERROR_CHECK(spi_bus ? ESP_OK : ESP_FAIL); - if (oSPInum) { - SPIClass* spiClass = new SPIClass(*oSPInum); - spiClass->begin(pin.nrf24_clk, pin.nrf24_miso, pin.nrf24_mosi, pin.nrf24_cs); - Hoymiles.initNRF(spiClass, pin.nrf24_en, pin.nrf24_irq); - } + SPIClass* spiClass = new SPIClass(*spi_bus); + 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()) { - auto oSPInum = SPIPortManager.allocatePort("CMT2300A"); - - if (oSPInum) { - Hoymiles.initCMT(SPIPortManager.SPIhostNum(*oSPInum), pin.cmt_sdio, pin.cmt_clk, pin.cmt_cs, pin.cmt_fcs, pin.cmt_gpio2, pin.cmt_gpio3); - MessageOutput.println(" Setting country mode... "); - Hoymiles.getRadioCmt()->setCountryMode(static_cast(config.Dtu.Cmt.CountryMode)); - MessageOutput.println(" Setting CMT target frequency... "); - Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu.Cmt.Frequency); - } + Hoymiles.initCMT(pin.cmt_sdio, pin.cmt_clk, pin.cmt_cs, pin.cmt_fcs, pin.cmt_gpio2, pin.cmt_gpio3); + MessageOutput.println(" Setting country mode... "); + Hoymiles.getRadioCmt()->setCountryMode(static_cast(config.Dtu.Cmt.CountryMode)); + MessageOutput.println(" Setting CMT target frequency... "); + Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu.Cmt.Frequency); } MessageOutput.println(" Setting radio PA level... "); diff --git a/src/MqttHandleHass.cpp b/src/MqttHandleHass.cpp index ab4a2ad1f..6491c9baf 100644 --- a/src/MqttHandleHass.cpp +++ b/src/MqttHandleHass.cpp @@ -58,45 +58,46 @@ void MqttHandleHassClass::publishConfig() const CONFIG_T& config = Configuration.get(); // publish DTU sensors - publishDtuSensor("IP", "dtu/ip", "", "mdi:network-outline", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); - publishDtuSensor("WiFi Signal", "dtu/rssi", "dBm", "", DEVICE_CLS_SIGNAL_STRENGTH, CATEGORY_DIAGNOSTIC); - publishDtuSensor("Uptime", "dtu/uptime", "s", "", DEVICE_CLS_DURATION, CATEGORY_DIAGNOSTIC); - publishDtuSensor("Temperature", "dtu/temperature", "°C", "mdi:thermometer", DEVICE_CLS_TEMPERATURE, CATEGORY_DIAGNOSTIC); - publishDtuSensor("Heap Size", "dtu/heap/size", "Bytes", "mdi:memory", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); - publishDtuSensor("Heap Free", "dtu/heap/free", "Bytes", "mdi:memory", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); - publishDtuSensor("Largest Free Heap Block", "dtu/heap/maxalloc", "Bytes", "mdi:memory", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); - publishDtuSensor("Lifetime Minimum Free Heap", "dtu/heap/minfree", "Bytes", "mdi:memory", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishDtuSensor("IP", "dtu/ip", "", "mdi:network-outline", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishDtuSensor("WiFi Signal", "dtu/rssi", "dBm", "", DEVICE_CLS_SIGNAL_STRENGTH, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishDtuSensor("Uptime", "dtu/uptime", "s", "", DEVICE_CLS_DURATION, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishDtuSensor("Temperature", "dtu/temperature", "°C", "", DEVICE_CLS_TEMPERATURE, STATE_CLS_MEASUREMENT, CATEGORY_DIAGNOSTIC); + publishDtuSensor("Heap Size", "dtu/heap/size", "Bytes", "mdi:memory", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishDtuSensor("Heap Free", "dtu/heap/free", "Bytes", "mdi:memory", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishDtuSensor("Largest Free Heap Block", "dtu/heap/maxalloc", "Bytes", "mdi:memory", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishDtuSensor("Lifetime Minimum Free Heap", "dtu/heap/minfree", "Bytes", "mdi:memory", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); - publishDtuSensor("Yield Total", "ac/yieldtotal", "kWh", "", DEVICE_CLS_ENERGY, CATEGORY_NONE); - publishDtuSensor("Yield Day", "ac/yieldday", "Wh", "", DEVICE_CLS_ENERGY, CATEGORY_NONE); - publishDtuSensor("AC Power", "ac/power", "W", "", DEVICE_CLS_PWR, CATEGORY_NONE); + publishDtuSensor("Yield Total", "ac/yieldtotal", "kWh", "", DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING, CATEGORY_NONE); + publishDtuSensor("Yield Day", "ac/yieldday", "Wh", "", DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING, CATEGORY_NONE); + publishDtuSensor("AC Power", "ac/power", "W", "", DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT, CATEGORY_NONE); - publishDtuBinarySensor("Status", config.Mqtt.Lwt.Topic, config.Mqtt.Lwt.Value_Online, config.Mqtt.Lwt.Value_Offline, DEVICE_CLS_CONNECTIVITY, CATEGORY_DIAGNOSTIC); + publishDtuBinarySensor("Status", config.Mqtt.Lwt.Topic, config.Mqtt.Lwt.Value_Online, config.Mqtt.Lwt.Value_Offline, DEVICE_CLS_CONNECTIVITY, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); // Loop all inverters for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { auto inv = Hoymiles.getInverterByPos(i); - publishInverterButton(inv, "Turn Inverter Off", "cmd/power", "0", "mdi:power-plug-off", DEVICE_CLS_NONE, CATEGORY_CONFIG); - publishInverterButton(inv, "Turn Inverter On", "cmd/power", "1", "mdi:power-plug", DEVICE_CLS_NONE, CATEGORY_CONFIG); - publishInverterButton(inv, "Restart Inverter", "cmd/restart", "1", "", DEVICE_CLS_RESTART, CATEGORY_CONFIG); - publishInverterButton(inv, "Reset Radio Statistics", "cmd/reset_rf_stats", "1", "", DEVICE_CLS_NONE, CATEGORY_CONFIG); + publishInverterButton(inv, "Turn Inverter Off", "cmd/power", "0", "mdi:power-plug-off", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_CONFIG); + publishInverterButton(inv, "Turn Inverter On", "cmd/power", "1", "mdi:power-plug", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_CONFIG); + publishInverterButton(inv, "Restart Inverter", "cmd/restart", "1", "", DEVICE_CLS_RESTART, STATE_CLS_NONE, CATEGORY_CONFIG); + publishInverterButton(inv, "Reset Radio Statistics", "cmd/reset_rf_stats", "1", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_CONFIG); - publishInverterNumber(inv, "Limit NonPersistent Relative", "status/limit_relative", "cmd/limit_nonpersistent_relative", 0, 100, 0.1, "%", "mdi:speedometer", CATEGORY_CONFIG); - publishInverterNumber(inv, "Limit Persistent Relative", "status/limit_relative", "cmd/limit_persistent_relative", 0, 100, 0.1, "%", "mdi:speedometer", CATEGORY_CONFIG); + publishInverterNumber(inv, "Limit NonPersistent Relative", "status/limit_relative", "cmd/limit_nonpersistent_relative", 0, 100, 0.1, "%", "mdi:speedometer", STATE_CLS_NONE, CATEGORY_CONFIG); + publishInverterNumber(inv, "Limit Persistent Relative", "status/limit_relative", "cmd/limit_persistent_relative", 0, 100, 0.1, "%", "mdi:speedometer", STATE_CLS_NONE, CATEGORY_CONFIG); - publishInverterNumber(inv, "Limit NonPersistent Absolute", "status/limit_absolute", "cmd/limit_nonpersistent_absolute", 0, MAX_INVERTER_LIMIT, 1, "W", "mdi:speedometer", CATEGORY_CONFIG); - publishInverterNumber(inv, "Limit Persistent Absolute", "status/limit_absolute", "cmd/limit_persistent_absolute", 0, MAX_INVERTER_LIMIT, 1, "W", "mdi:speedometer", CATEGORY_CONFIG); + publishInverterNumber(inv, "Limit NonPersistent Absolute", "status/limit_absolute", "cmd/limit_nonpersistent_absolute", 0, MAX_INVERTER_LIMIT, 1, "W", "mdi:speedometer", STATE_CLS_NONE, CATEGORY_CONFIG); + publishInverterNumber(inv, "Limit Persistent Absolute", "status/limit_absolute", "cmd/limit_persistent_absolute", 0, MAX_INVERTER_LIMIT, 1, "W", "mdi:speedometer", STATE_CLS_NONE, CATEGORY_CONFIG); - publishInverterBinarySensor(inv, "Reachable", "status/reachable", "1", "0", DEVICE_CLS_CONNECTIVITY, CATEGORY_DIAGNOSTIC); - publishInverterBinarySensor(inv, "Producing", "status/producing", "1", "0", DEVICE_CLS_NONE, CATEGORY_NONE); + publishInverterBinarySensor(inv, "Reachable", "status/reachable", "1", "0", DEVICE_CLS_CONNECTIVITY, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishInverterBinarySensor(inv, "Producing", "status/producing", "1", "0", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_NONE); - publishInverterSensor(inv, "TX Requests", "radio/tx_request", "", "", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); - publishInverterSensor(inv, "RX Success", "radio/rx_success", "", "", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); - publishInverterSensor(inv, "RX Fail Receive Nothing", "radio/rx_fail_nothing", "", "", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); - publishInverterSensor(inv, "RX Fail Receive Partial", "radio/rx_fail_partial", "", "", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); - publishInverterSensor(inv, "RX Fail Receive Corrupt", "radio/rx_fail_corrupt", "", "", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); - publishInverterSensor(inv, "TX Re-Request Fragment", "radio/tx_re_request", "", "", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishInverterSensor(inv, "TX Requests", "radio/tx_request", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishInverterSensor(inv, "RX Success", "radio/rx_success", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishInverterSensor(inv, "RX Fail Receive Nothing", "radio/rx_fail_nothing", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishInverterSensor(inv, "RX Fail Receive Partial", "radio/rx_fail_partial", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishInverterSensor(inv, "RX Fail Receive Corrupt", "radio/rx_fail_corrupt", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishInverterSensor(inv, "TX Re-Request Fragment", "radio/tx_re_request", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishInverterSensor(inv, "RSSI", "radio/rssi", "dBm", "", DEVICE_CLS_SIGNAL_STRENGTH, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); // Loop all channels for (auto& t : inv->Statistics()->getChannelTypes()) { @@ -142,7 +143,6 @@ void MqttHandleHassClass::publishInverterField(std::shared_ptr if (!clear) { const String stateTopic = MqttSettings.getPrefix() + MqttHandleInverter.getTopic(inv, type, channel, fieldType.fieldId); - const char* stateCls = stateClass_name[fieldType.stateClsId]; String name; if (type != TYPE_DC) { @@ -155,7 +155,7 @@ void MqttHandleHassClass::publishInverterField(std::shared_ptr JsonDocument root; createInverterInfo(root, inv); - addCommonMetadata(root, unit_of_measure, "", fieldType.deviceClsId, CATEGORY_NONE); + addCommonMetadata(root, unit_of_measure, "", fieldType.deviceClsId, fieldType.stateClsId, CATEGORY_NONE); root["name"] = name; root["stat_t"] = stateTopic; @@ -164,9 +164,6 @@ void MqttHandleHassClass::publishInverterField(std::shared_ptr if (Configuration.get().Mqtt.Hass.Expire) { root["exp_aft"] = Hoymiles.getNumInverters() * max(Hoymiles.PollInterval(), Configuration.get().Mqtt.PublishInterval) * inv->getReachableThreshold(); } - if (stateCls != 0) { - root["stat_cla"] = stateCls; - } publish(configTopic, root); } else { @@ -174,7 +171,10 @@ void MqttHandleHassClass::publishInverterField(std::shared_ptr } } -void MqttHandleHassClass::publishInverterButton(std::shared_ptr inv, const String& name, const String& state_topic, const String& payload, const String& icon, const DeviceClassType device_class, const CategoryType category) +void MqttHandleHassClass::publishInverterButton( + std::shared_ptr inv, const String& name, const String& state_topic, const String& payload, + const String& icon, + const DeviceClassType device_class, const StateClassType state_class, const CategoryType category) { const String serial = inv->serialString(); @@ -190,7 +190,7 @@ void MqttHandleHassClass::publishInverterButton(std::shared_ptr inv, const String& name, const String& stateTopic, const String& command_topic, const int16_t min, const int16_t max, float step, - const String& unit_of_measure, const String& icon, const CategoryType category) + const String& unit_of_measure, const String& icon, + const StateClassType state_class, const CategoryType category) { const String serial = inv->serialString(); @@ -221,7 +222,7 @@ void MqttHandleHassClass::publishInverterNumber( JsonDocument root; createInverterInfo(root, inv); - addCommonMetadata(root, unit_of_measure, icon, DEVICE_CLS_NONE, category); + addCommonMetadata(root, unit_of_measure, icon, DEVICE_CLS_NONE, state_class, category); root["name"] = name; root["uniq_id"] = serial + "_" + buttonId; @@ -307,7 +308,10 @@ void MqttHandleHassClass::publish(const String& subtopic, const JsonDocument& do publish(subtopic, buffer); } -void MqttHandleHassClass::addCommonMetadata(JsonDocument& doc, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const CategoryType category) +void MqttHandleHassClass::addCommonMetadata( + JsonDocument& doc, + const String& unit_of_measure, const String& icon, + const DeviceClassType device_class, const StateClassType state_class, const CategoryType category) { if (unit_of_measure != "") { doc["unit_of_meas"] = unit_of_measure; @@ -318,12 +322,18 @@ void MqttHandleHassClass::addCommonMetadata(JsonDocument& doc, const String& uni if (device_class != DEVICE_CLS_NONE) { doc["dev_cla"] = deviceClass_name[device_class]; } + if (state_class != STATE_CLS_NONE) { + doc["stat_cla"] = stateClass_name[state_class];; + } if (category != CATEGORY_NONE) { doc["ent_cat"] = category_name[category]; } } -void MqttHandleHassClass::publishBinarySensor(JsonDocument& doc, const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const CategoryType category) +void MqttHandleHassClass::publishBinarySensor( + JsonDocument& doc, + const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, const String& payload_on, const String& payload_off, + const DeviceClassType device_class, const StateClassType state_class, const CategoryType category) { String sensor_id = name; sensor_id.toLowerCase(); @@ -335,31 +345,39 @@ void MqttHandleHassClass::publishBinarySensor(JsonDocument& doc, const String& r doc["pl_on"] = payload_on; doc["pl_off"] = payload_off; - addCommonMetadata(doc, "", "", device_class, category); + addCommonMetadata(doc, "", "", device_class, state_class, category); const String configTopic = "binary_sensor/" + root_device + "/" + sensor_id + "/config"; publish(configTopic, doc); } -void MqttHandleHassClass::publishDtuBinarySensor(const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const CategoryType category) +void MqttHandleHassClass::publishDtuBinarySensor( + const String& name, const String& state_topic, const String& payload_on, const String& payload_off, + const DeviceClassType device_class, const StateClassType state_class, const CategoryType category) { const String dtuId = getDtuUniqueId(); JsonDocument root; createDtuInfo(root); - publishBinarySensor(root, dtuId, dtuId, name, state_topic, payload_on, payload_off, device_class, category); + publishBinarySensor(root, dtuId, dtuId, name, state_topic, payload_on, payload_off, device_class, state_class, category); } -void MqttHandleHassClass::publishInverterBinarySensor(std::shared_ptr inv, const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const CategoryType category) +void MqttHandleHassClass::publishInverterBinarySensor( + std::shared_ptr inv, const String& name, const String& state_topic, const String& payload_on, const String& payload_off, + const DeviceClassType device_class, const StateClassType state_class, const CategoryType category) { const String serial = inv->serialString(); JsonDocument root; createInverterInfo(root, inv); - publishBinarySensor(root, "dtu_" + serial, serial, name, serial + "/" + state_topic, payload_on, payload_off, device_class, category); + publishBinarySensor(root, "dtu_" + serial, serial, name, serial + "/" + state_topic, payload_on, payload_off, device_class, state_class, category); } -void MqttHandleHassClass::publishSensor(JsonDocument& doc, const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const CategoryType category) +void MqttHandleHassClass::publishSensor( + JsonDocument& doc, + const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, + const String& unit_of_measure, const String& icon, + const DeviceClassType device_class, const StateClassType state_class, const CategoryType category) { String sensor_id = name; sensor_id.toLowerCase(); @@ -369,7 +387,7 @@ void MqttHandleHassClass::publishSensor(JsonDocument& doc, const String& root_de doc["uniq_id"] = unique_id_prefix + "_" + sensor_id; doc["stat_t"] = MqttSettings.getPrefix() + state_topic; - addCommonMetadata(doc, unit_of_measure, icon, device_class, category); + addCommonMetadata(doc, unit_of_measure, icon, device_class, state_class, category); const CONFIG_T& config = Configuration.get(); doc["avty_t"] = MqttSettings.getPrefix() + config.Mqtt.Lwt.Topic; @@ -380,20 +398,26 @@ void MqttHandleHassClass::publishSensor(JsonDocument& doc, const String& root_de publish(configTopic, doc); } -void MqttHandleHassClass::publishDtuSensor(const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const CategoryType category) +void MqttHandleHassClass::publishDtuSensor( + const String& name, const String& state_topic, + const String& unit_of_measure, const String& icon, + const DeviceClassType device_class, const StateClassType state_class, const CategoryType category) { const String dtuId = getDtuUniqueId(); JsonDocument root; createDtuInfo(root); - publishSensor(root, dtuId, dtuId, name, state_topic, unit_of_measure, icon, device_class, category); + publishSensor(root, dtuId, dtuId, name, state_topic, unit_of_measure, icon, device_class, state_class, category); } -void MqttHandleHassClass::publishInverterSensor(std::shared_ptr inv, const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const CategoryType category) +void MqttHandleHassClass::publishInverterSensor( + std::shared_ptr inv, const String& name, const String& state_topic, + const String& unit_of_measure, const String& icon, + const DeviceClassType device_class, const StateClassType state_class, const CategoryType category) { const String serial = inv->serialString(); JsonDocument root; createInverterInfo(root, inv); - publishSensor(root, "dtu_" + serial, serial, name, serial + "/" + state_topic, unit_of_measure, icon, device_class, category); + publishSensor(root, "dtu_" + serial, serial, name, serial + "/" + state_topic, unit_of_measure, icon, device_class, state_class, category); } diff --git a/src/MqttHandleInverter.cpp b/src/MqttHandleInverter.cpp index 8e7206635..70a7222d2 100644 --- a/src/MqttHandleInverter.cpp +++ b/src/MqttHandleInverter.cpp @@ -50,6 +50,7 @@ void MqttHandleInverterClass::loop() MqttSettings.publish(subtopic + "/radio/rx_fail_nothing", String(inv->RadioStats.RxFailNoAnswer)); MqttSettings.publish(subtopic + "/radio/rx_fail_partial", String(inv->RadioStats.RxFailPartialAnswer)); MqttSettings.publish(subtopic + "/radio/rx_fail_corrupt", String(inv->RadioStats.RxFailCorruptData)); + MqttSettings.publish(subtopic + "/radio/rssi", String(inv->getLastRssi())); if (inv->DevInfo()->getLastUpdate() > 0) { // Bootloader Version @@ -217,7 +218,7 @@ void MqttHandleInverterClass::onMqttMessage(Topic t, const espMqttClientTypes::M case Topic::Power: // Turn inverter on or off - MessageOutput.printf("Set inverter power to: %d\r\n", static_cast(payload_val)); + MessageOutput.printf("Set inverter power to: %" PRId32 "\r\n", static_cast(payload_val)); inv->sendPowerControlRequest(static_cast(payload_val) > 0); break; diff --git a/src/NetworkSettings.cpp b/src/NetworkSettings.cpp index 507a5cc12..94020f8e1 100644 --- a/src/NetworkSettings.cpp +++ b/src/NetworkSettings.cpp @@ -8,12 +8,10 @@ #include "SyslogLogger.h" #include "PinMapping.h" #include "Utils.h" -#include "SPIPortManager.h" +#include "__compiled_constants.h" #include "defaults.h" #include -#include #include -#include "__compiled_constants.h" NetworkSettingsClass::NetworkSettingsClass() : _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&NetworkSettingsClass::loop, this)) @@ -35,20 +33,24 @@ void NetworkSettingsClass::init(Scheduler& scheduler) WiFi.onEvent(std::bind(&NetworkSettingsClass::NetworkEvent, this, _1, _2)); - if (PinMapping.isValidEthConfig()) { + if (PinMapping.isValidW5500Config()) { PinMapping_t& pin = PinMapping.get(); + _w5500 = W5500::setup(pin.w5500_mosi, pin.w5500_miso, pin.w5500_sclk, pin.w5500_cs, pin.w5500_int, pin.w5500_rst); + if (_w5500) + MessageOutput.println("W5500: Connection successful"); + else + MessageOutput.println("W5500: Connection error!!"); + } +#if CONFIG_ETH_USE_ESP32_EMAC + else if (PinMapping.isValidEthConfig()) { + PinMapping_t& pin = PinMapping.get(); +#if ESP_ARDUINO_VERSION_MAJOR < 3 ETH.begin(pin.eth_phy_addr, pin.eth_power, pin.eth_mdc, pin.eth_mdio, pin.eth_type, pin.eth_clk_mode); - } else if (PinMapping.isValidW5500Config()) { - auto oSPInum = SPIPortManager.allocatePort("ETHSPI"); - - if (oSPInum) { - spi_host_device_t host_id = SPIPortManager.SPIhostNum(*oSPInum); - PinMapping_t& pin = PinMapping.get(); - ETHSPI.begin(pin.w5500_sclk, pin.w5500_mosi, pin.w5500_miso, pin.w5500_cs, pin.w5500_int, pin.w5500_rst, - host_id); - _spiEth = true; - } +#else + ETH.begin(pin.eth_type, pin.eth_phy_addr, pin.eth_mdc, pin.eth_mdio, pin.eth_power, pin.eth_clk_mode); +#endif } +#endif setupMode(); @@ -99,7 +101,7 @@ void NetworkSettingsClass::NetworkEvent(const WiFiEvent_t event, WiFiEventInfo_t break; case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: // Reason codes can be found here: https://github.com/espressif/esp-idf/blob/5454d37d496a8c58542eb450467471404c606501/components/esp_wifi/include/esp_wifi_types_generic.h#L79-L141 - MessageOutput.printf("WiFi disconnected: %d\r\n", info.wifi_sta_disconnected.reason); + MessageOutput.printf("WiFi disconnected: %" PRId8 "\r\n", info.wifi_sta_disconnected.reason); if (_networkMode == network_mode::WiFi) { MessageOutput.println("Try reconnecting"); WiFi.disconnect(true, false); @@ -118,12 +120,12 @@ void NetworkSettingsClass::NetworkEvent(const WiFiEvent_t event, WiFiEventInfo_t } } -bool NetworkSettingsClass::onEvent(NetworkEventCb cbEvent, const network_event event) +bool NetworkSettingsClass::onEvent(DtuNetworkEventCb cbEvent, const network_event event) { if (!cbEvent) { return pdFALSE; } - NetworkEventCbList_t newEventHandler; + DtuNetworkEventCbList_t newEventHandler; newEventHandler.cb = cbEvent; newEventHandler.event = event; _cbEventList.push_back(newEventHandler); @@ -227,7 +229,7 @@ void NetworkSettingsClass::loop() if (_adminEnabled && _adminTimeoutCounterMax > 0) { _adminTimeoutCounter++; if (_adminTimeoutCounter % 10 == 0) { - MessageOutput.printf("Admin AP remaining seconds: %d / %d\r\n", _adminTimeoutCounter, _adminTimeoutCounterMax); + MessageOutput.printf("Admin AP remaining seconds: %" PRId32 " / %" PRId32 "\r\n", _adminTimeoutCounter, _adminTimeoutCounterMax); } } _connectTimeoutTimer++; @@ -418,8 +420,8 @@ String NetworkSettingsClass::macAddress() const { switch (_networkMode) { case network_mode::Ethernet: - if (_spiEth) { - return ETHSPI.macAddress(); + if (_w5500) { + return _w5500->macAddress(); } return ETH.macAddress(); break; diff --git a/src/PinMapping.cpp b/src/PinMapping.cpp index 48ddcd117..9f6272173 100644 --- a/src/PinMapping.cpp +++ b/src/PinMapping.cpp @@ -84,6 +84,58 @@ #define CMT_SDIO -1 #endif +#ifndef W5500_MOSI +#define W5500_MOSI -1 +#endif + +#ifndef W5500_MISO +#define W5500_MISO -1 +#endif + +#ifndef W5500_SCLK +#define W5500_SCLK -1 +#endif + +#ifndef W5500_CS +#define W5500_CS -1 +#endif + +#ifndef W5500_INT +#define W5500_INT -1 +#endif + +#ifndef W5500_RST +#define W5500_RST -1 +#endif + +#if CONFIG_ETH_USE_ESP32_EMAC + +#ifndef ETH_PHY_ADDR +#define ETH_PHY_ADDR -1 +#endif + +#ifndef ETH_PHY_POWER +#define ETH_PHY_POWER -1 +#endif + +#ifndef ETH_PHY_MDC +#define ETH_PHY_MDC -1 +#endif + +#ifndef ETH_PHY_MDIO +#define ETH_PHY_MDIO -1 +#endif + +#ifndef ETH_PHY_TYPE +#define ETH_PHY_TYPE ETH_PHY_LAN8720 +#endif + +#ifndef ETH_CLK_MODE +#define ETH_CLK_MODE ETH_CLOCK_GPIO0_IN +#endif + +#endif // CONFIG_ETH_USE_ESP32_EMAC + #ifndef VICTRON_PIN_TX #define VICTRON_PIN_TX -1 #endif @@ -178,30 +230,6 @@ #define POWERMETER_PIN_RXEN -1 #endif -#ifndef W5500_SCLK -#define W5500_SCLK -1 -#endif - -#ifndef W5500_MOSI -#define W5500_MOSI -1 -#endif - -#ifndef W5500_MISO -#define W5500_MISO -1 -#endif - -#ifndef W5500_CS -#define W5500_CS -1 -#endif - -#ifndef W5500_INT -#define W5500_INT -1 -#endif - -#ifndef W5500_RST -#define W5500_RST -1 -#endif - PinMappingClass PinMapping; PinMappingClass::PinMappingClass() @@ -221,25 +249,26 @@ PinMappingClass::PinMappingClass() _pinMapping.cmt_gpio3 = CMT_GPIO3; _pinMapping.cmt_sdio = CMT_SDIO; - _pinMapping.w5500_sclk = W5500_SCLK; _pinMapping.w5500_mosi = W5500_MOSI; _pinMapping.w5500_miso = W5500_MISO; + _pinMapping.w5500_sclk = W5500_SCLK; _pinMapping.w5500_cs = W5500_CS; _pinMapping.w5500_int = W5500_INT; _pinMapping.w5500_rst = W5500_RST; +#if CONFIG_ETH_USE_ESP32_EMAC #ifdef OPENDTU_ETHERNET _pinMapping.eth_enabled = true; #else _pinMapping.eth_enabled = false; #endif - _pinMapping.eth_phy_addr = ETH_PHY_ADDR; _pinMapping.eth_power = ETH_PHY_POWER; _pinMapping.eth_mdc = ETH_PHY_MDC; _pinMapping.eth_mdio = ETH_PHY_MDIO; _pinMapping.eth_type = ETH_PHY_TYPE; _pinMapping.eth_clk_mode = ETH_CLK_MODE; +#endif _pinMapping.display_type = DISPLAY_TYPE; _pinMapping.display_data = DISPLAY_DATA; @@ -317,25 +346,26 @@ bool PinMappingClass::init(const String& deviceMapping) _pinMapping.cmt_gpio3 = doc[i]["cmt"]["gpio3"] | CMT_GPIO3; _pinMapping.cmt_sdio = doc[i]["cmt"]["sdio"] | CMT_SDIO; - _pinMapping.w5500_sclk = doc[i]["w5500"]["sclk"] | W5500_SCLK; _pinMapping.w5500_mosi = doc[i]["w5500"]["mosi"] | W5500_MOSI; _pinMapping.w5500_miso = doc[i]["w5500"]["miso"] | W5500_MISO; + _pinMapping.w5500_sclk = doc[i]["w5500"]["sclk"] | W5500_SCLK; _pinMapping.w5500_cs = doc[i]["w5500"]["cs"] | W5500_CS; _pinMapping.w5500_int = doc[i]["w5500"]["int"] | W5500_INT; _pinMapping.w5500_rst = doc[i]["w5500"]["rst"] | W5500_RST; +#if CONFIG_ETH_USE_ESP32_EMAC #ifdef OPENDTU_ETHERNET _pinMapping.eth_enabled = doc[i]["eth"]["enabled"] | true; #else _pinMapping.eth_enabled = doc[i]["eth"]["enabled"] | false; #endif - _pinMapping.eth_phy_addr = doc[i]["eth"]["phy_addr"] | ETH_PHY_ADDR; _pinMapping.eth_power = doc[i]["eth"]["power"] | ETH_PHY_POWER; _pinMapping.eth_mdc = doc[i]["eth"]["mdc"] | ETH_PHY_MDC; _pinMapping.eth_mdio = doc[i]["eth"]["mdio"] | ETH_PHY_MDIO; _pinMapping.eth_type = doc[i]["eth"]["type"] | ETH_PHY_TYPE; _pinMapping.eth_clk_mode = doc[i]["eth"]["clk_mode"] | ETH_CLK_MODE; +#endif _pinMapping.display_type = doc[i]["display"]["type"] | DISPLAY_TYPE; _pinMapping.display_data = doc[i]["display"]["data"] | DISPLAY_DATA; @@ -399,18 +429,22 @@ bool PinMappingClass::isValidCmt2300Config() const bool PinMappingClass::isValidW5500Config() const { - return _pinMapping.w5500_sclk >= 0 - && _pinMapping.w5500_mosi >= 0 + return _pinMapping.w5500_mosi >= 0 && _pinMapping.w5500_miso >= 0 + && _pinMapping.w5500_sclk >= 0 && _pinMapping.w5500_cs >= 0 && _pinMapping.w5500_int >= 0 && _pinMapping.w5500_rst >= 0; } +#if CONFIG_ETH_USE_ESP32_EMAC bool PinMappingClass::isValidEthConfig() const { - return _pinMapping.eth_enabled; + return _pinMapping.eth_enabled + && _pinMapping.eth_mdc >= 0 + && _pinMapping.eth_mdio >= 0; } +#endif bool PinMappingClass::isValidHuaweiConfig() const { diff --git a/src/SPIPortManager.cpp b/src/SPIPortManager.cpp deleted file mode 100644 index 8b64c423c..000000000 --- a/src/SPIPortManager.cpp +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#include "SPIPortManager.h" -#include "MessageOutput.h" - -SPIPortManagerClass SPIPortManager; -static constexpr char TAG[] = "[SPIPortManager]"; - -void SPIPortManagerClass::init() { - MessageOutput.printf("%s SPI0 and SPI1 reserved by 'Flash and PSRAM'\r\n", TAG); - _ports[0] = "Flash"; - _ports[1] = "PSRAM"; -} - -std::optional SPIPortManagerClass::allocatePort(std::string const& owner) -{ - for (size_t i = 0; i < _ports.size(); ++i) { - if (_ports[i] != "") { - MessageOutput.printf("%s SPI%d already in use by '%s'\r\n", TAG, i, _ports[i].c_str()); - continue; - } - - _ports[i] = owner; - - MessageOutput.printf("%s SPI%d now in use by '%s'\r\n", TAG, i, owner.c_str()); - - return i + _offset_spi_num; - } - - MessageOutput.printf("%s Cannot assign another SPI port to '%s'\r\n", TAG, owner.c_str()); - return std::nullopt; -} - -void SPIPortManagerClass::freePort(std::string const& owner) -{ - for (size_t i = 0; i < _ports.size(); ++i) { - if (_ports[i] != owner) { continue; } - - MessageOutput.printf("%s Freeing SPI%d, owner was '%s'\r\n", TAG, i + _offset_spi_num, owner.c_str()); - _ports[i] = ""; - } -} - -spi_host_device_t SPIPortManagerClass::SPIhostNum(uint8_t spi_num) -{ - return (spi_host_device_t)(spi_num + _offset_spi_host); -} diff --git a/src/Utils.cpp b/src/Utils.cpp index 66585e086..cd5263ba5 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -60,7 +60,7 @@ int Utils::getTimezoneOffset() bool Utils::checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line) { if (doc.overflowed()) { - MessageOutput.printf("Alloc failed: %s, %d\r\n", function, line); + MessageOutput.printf("Alloc failed: %s, %" PRId16 "\r\n", function, line); return false; } diff --git a/src/W5500.cpp b/src/W5500.cpp new file mode 100644 index 000000000..bf5394340 --- /dev/null +++ b/src/W5500.cpp @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2024 Thomas Basler and others + */ + +#include "W5500.h" + +#include +#include + +// Internal Arduino functions from WiFiGeneric +void tcpipInit(); +void add_esp_interface_netif(esp_interface_t interface, esp_netif_t* esp_netif); + +W5500::W5500(spi_device_handle_t spi, gpio_num_t pin_int) + : eth_handle(nullptr) + , eth_netif(nullptr) +{ + // Arduino function to start networking stack if not already started + tcpipInit(); + + ESP_ERROR_CHECK(tcpip_adapter_set_default_eth_handlers()); + + eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi); + w5500_config.int_gpio_num = pin_int; + + eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); + mac_config.rx_task_stack_size = 4096; + esp_eth_mac_t* mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config); + + eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); + phy_config.reset_gpio_num = -1; + esp_eth_phy_t* phy = esp_eth_phy_new_w5500(&phy_config); + + esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy); + ESP_ERROR_CHECK(esp_eth_driver_install(ð_config, ð_handle)); + + // Configure MAC address + uint8_t mac_addr[6]; + ESP_ERROR_CHECK(esp_read_mac(mac_addr, ESP_MAC_ETH)); + ESP_ERROR_CHECK(esp_eth_ioctl(eth_handle, ETH_CMD_S_MAC_ADDR, mac_addr)); + + esp_netif_config_t netif_config = ESP_NETIF_DEFAULT_ETH(); + eth_netif = esp_netif_new(&netif_config); + + ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle))); + + // Add to Arduino + add_esp_interface_netif(ESP_IF_ETH, eth_netif); + + ESP_ERROR_CHECK(esp_eth_start(eth_handle)); +} + +W5500::~W5500() +{ + // TODO(LennartF22): support cleanup at some point? +} + +std::unique_ptr W5500::setup(int8_t pin_mosi, int8_t pin_miso, int8_t pin_sclk, int8_t pin_cs, int8_t pin_int, int8_t pin_rst) +{ + gpio_reset_pin(static_cast(pin_rst)); + gpio_set_level(static_cast(pin_rst), 0); + gpio_set_direction(static_cast(pin_rst), GPIO_MODE_OUTPUT); + + gpio_reset_pin(static_cast(pin_cs)); + gpio_reset_pin(static_cast(pin_int)); + + auto bus_config = std::make_shared( + static_cast(pin_mosi), + static_cast(pin_miso), + static_cast(pin_sclk)); + + spi_device_interface_config_t device_config { + .command_bits = 16, // actually address phase + .address_bits = 8, // actually command phase + .dummy_bits = 0, + .mode = 0, + .duty_cycle_pos = 0, + .cs_ena_pretrans = 0, // only 0 supported + .cs_ena_posttrans = 0, // only 0 supported + .clock_speed_hz = 20000000, // stable with OpenDTU Fusion shield + .input_delay_ns = 0, + .spics_io_num = pin_cs, + .flags = 0, + .queue_size = 20, + .pre_cb = nullptr, + .post_cb = nullptr, + }; + + spi_device_handle_t spi = SpiManagerInst.alloc_device("", bus_config, device_config); + if (!spi) + return nullptr; + + // Reset sequence + delayMicroseconds(500); + gpio_set_level(static_cast(pin_rst), 1); + delayMicroseconds(1000); + + if (!connection_check_spi(spi)) + return nullptr; + if (!connection_check_interrupt(static_cast(pin_int))) + return nullptr; + + // Use Arduino functions to temporarily attach interrupt to enable the GPIO ISR service + // (if we used ESP-IDF functions, a warning would be printed the first time anyone uses attachInterrupt) + attachInterrupt(pin_int, nullptr, FALLING); + detachInterrupt(pin_int); + + // Return to default state once again after connection check and temporary interrupt registration + gpio_reset_pin(static_cast(pin_int)); + + return std::unique_ptr(new W5500(spi, static_cast(pin_int))); +} + +String W5500::macAddress() +{ + uint8_t mac_addr[6] = {}; + esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr); + + char mac_addr_str[18]; + snprintf( + mac_addr_str, sizeof(mac_addr_str), "%02X:%02X:%02X:%02X:%02X:%02X", + mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); + return String(mac_addr_str); +} + +bool W5500::connection_check_spi(spi_device_handle_t spi) +{ + spi_transaction_t trans = { + .flags = SPI_TRANS_USE_RXDATA, + .cmd = 0x0039, // actually address (VERSIONR) + .addr = (0b00000 << 3) | (0 << 2) | (0b00 < 0), // actually command (common register, read, VDM) + .length = 8, + .rxlength = 8, + .user = nullptr, + .tx_buffer = nullptr, + .rx_data = {}, + }; + ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &trans)); + + // Version number (VERSIONR) is always 0x04 + return *reinterpret_cast(&trans.rx_data) == 0x04; +} + +bool W5500::connection_check_interrupt(gpio_num_t pin_int) +{ + gpio_set_direction(pin_int, GPIO_MODE_INPUT); + gpio_set_pull_mode(pin_int, GPIO_PULLDOWN_ONLY); + int level = gpio_get_level(pin_int); + + // Interrupt line must be high + return level == 1; +} diff --git a/src/WebApi.cpp b/src/WebApi.cpp index c2dcf3f10..7d23e5fb5 100644 --- a/src/WebApi.cpp +++ b/src/WebApi.cpp @@ -47,6 +47,15 @@ void WebApiClass::init(Scheduler& scheduler) _server.begin(); } +void WebApiClass::reload() +{ + _webApiWsConsole.reload(); + _webApiWsLive.reload(); + _webApiWsBatteryLive.reload(); + _webApiWsVedirectLive.reload(); + _webApiWsHuaweiLive.reload(); +} + bool WebApiClass::checkCredentials(AsyncWebServerRequest* request) { CONFIG_T& config = Configuration.get(); @@ -139,7 +148,7 @@ bool WebApiClass::sendJsonResponse(AsyncWebServerRequest* request, AsyncJsonResp root["code"] = WebApiError::GenericInternalServerError; root["type"] = "danger"; response->setCode(500); - MessageOutput.printf("WebResponse failed: %s, %d\r\n", function, line); + MessageOutput.printf("WebResponse failed: %s, %" PRId16 "\r\n", function, line); ret_val = false; } diff --git a/src/WebApi_device.cpp b/src/WebApi_device.cpp index 405c1dea1..405602241 100644 --- a/src/WebApi_device.cpp +++ b/src/WebApi_device.cpp @@ -58,6 +58,7 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) w5500PinObj["int"] = pin.w5500_int; w5500PinObj["rst"] = pin.w5500_rst; +#if CONFIG_ETH_USE_ESP32_EMAC auto ethPinObj = curPin["eth"].to(); ethPinObj["enabled"] = pin.eth_enabled; ethPinObj["phy_addr"] = pin.eth_phy_addr; @@ -66,6 +67,7 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) ethPinObj["mdio"] = pin.eth_mdio; ethPinObj["type"] = pin.eth_type; ethPinObj["clk_mode"] = pin.eth_clk_mode; +#endif auto displayPinObj = curPin["display"].to(); displayPinObj["type"] = pin.display_type; diff --git a/src/WebApi_dtu.cpp b/src/WebApi_dtu.cpp index 7fd12271e..8d9f1c431 100644 --- a/src/WebApi_dtu.cpp +++ b/src/WebApi_dtu.cpp @@ -49,7 +49,7 @@ void WebApiDtuClass::onDtuAdminGet(AsyncWebServerRequest* request) // DTU Serial is read as HEX char buffer[sizeof(uint64_t) * 8 + 1]; - snprintf(buffer, sizeof(buffer), "%0x%08x", + snprintf(buffer, sizeof(buffer), "%0" PRIx32 "%08" PRIx32, ((uint32_t)((config.Dtu.Serial >> 32) & 0xFFFFFFFF)), ((uint32_t)(config.Dtu.Serial & 0xFFFFFFFF))); root["serial"] = buffer; diff --git a/src/WebApi_inverter.cpp b/src/WebApi_inverter.cpp index 79c8a8e9c..ef353158f 100644 --- a/src/WebApi_inverter.cpp +++ b/src/WebApi_inverter.cpp @@ -45,7 +45,7 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request) // Inverter Serial is read as HEX char buffer[sizeof(uint64_t) * 8 + 1]; - snprintf(buffer, sizeof(buffer), "%0x%08x", + snprintf(buffer, sizeof(buffer), "%0" PRIx32 "%08" PRIx32, ((uint32_t)((config.Inverter[i].Serial >> 32) & 0xFFFFFFFF)), ((uint32_t)(config.Inverter[i].Serial & 0xFFFFFFFF))); obj["serial"] = buffer; diff --git a/src/WebApi_prometheus.cpp b/src/WebApi_prometheus.cpp index a1c1e91d9..9e29da1b5 100644 --- a/src/WebApi_prometheus.cpp +++ b/src/WebApi_prometheus.cpp @@ -42,23 +42,23 @@ void WebApiPrometheusClass::onPrometheusMetricsGet(AsyncWebServerRequest* reques stream->print("# HELP opendtu_heap_size System memory size\n"); stream->print("# TYPE opendtu_heap_size gauge\n"); - stream->printf("opendtu_heap_size %zu\n", ESP.getHeapSize()); + stream->printf("opendtu_heap_size %" PRId32 "\n", ESP.getHeapSize()); stream->print("# HELP opendtu_free_heap_size System free memory\n"); stream->print("# TYPE opendtu_free_heap_size gauge\n"); - stream->printf("opendtu_free_heap_size %zu\n", ESP.getFreeHeap()); + stream->printf("opendtu_free_heap_size %" PRId32 "\n", ESP.getFreeHeap()); stream->print("# HELP opendtu_biggest_heap_block Biggest free heap block\n"); stream->print("# TYPE opendtu_biggest_heap_block gauge\n"); - stream->printf("opendtu_biggest_heap_block %zu\n", ESP.getMaxAllocHeap()); + stream->printf("opendtu_biggest_heap_block %" PRId32 "\n", ESP.getMaxAllocHeap()); stream->print("# HELP opendtu_heap_min_free Minimum free memory since boot\n"); stream->print("# TYPE opendtu_heap_min_free gauge\n"); - stream->printf("opendtu_heap_min_free %zu\n", ESP.getMinFreeHeap()); + stream->printf("opendtu_heap_min_free %" PRId32 "\n", ESP.getMinFreeHeap()); stream->print("# HELP wifi_rssi WiFi RSSI\n"); stream->print("# TYPE wifi_rssi gauge\n"); - stream->printf("wifi_rssi %d\n", WiFi.RSSI()); + stream->printf("wifi_rssi %" PRId8 "\n", WiFi.RSSI()); stream->print("# HELP wifi_station WiFi Station info\n"); stream->print("# TYPE wifi_station gauge\n"); @@ -73,14 +73,14 @@ void WebApiPrometheusClass::onPrometheusMetricsGet(AsyncWebServerRequest* reques stream->print("# HELP opendtu_last_update last update from inverter in s\n"); stream->print("# TYPE opendtu_last_update gauge\n"); } - stream->printf("opendtu_last_update{serial=\"%s\",unit=\"%d\",name=\"%s\"} %d\n", + stream->printf("opendtu_last_update{serial=\"%s\",unit=\"%" PRId8 "\",name=\"%s\"} %" PRId32 "\n", serial.c_str(), i, name, inv->Statistics()->getLastUpdate() / 1000); if (i == 0) { stream->print("# HELP opendtu_inverter_limit_relative current relative limit of the inverter\n"); stream->print("# TYPE opendtu_inverter_limit_relative gauge\n"); } - stream->printf("opendtu_inverter_limit_relative{serial=\"%s\",unit=\"%d\",name=\"%s\"} %f\n", + stream->printf("opendtu_inverter_limit_relative{serial=\"%s\",unit=\"%" PRId8 "\",name=\"%s\"} %f\n", serial.c_str(), i, name, inv->SystemConfigPara()->getLimitPercent() / 100.0); if (inv->DevInfo()->getMaxPower() > 0) { @@ -88,7 +88,7 @@ void WebApiPrometheusClass::onPrometheusMetricsGet(AsyncWebServerRequest* reques stream->print("# HELP opendtu_inverter_limit_absolute current relative limit of the inverter\n"); stream->print("# TYPE opendtu_inverter_limit_absolute gauge\n"); } - stream->printf("opendtu_inverter_limit_absolute{serial=\"%s\",unit=\"%d\",name=\"%s\"} %f\n", + stream->printf("opendtu_inverter_limit_absolute{serial=\"%s\",unit=\"%" PRId8 "\",name=\"%s\"} %f\n", serial.c_str(), i, name, inv->SystemConfigPara()->getLimitPercent() * inv->DevInfo()->getMaxPower() / 100.0); } @@ -126,7 +126,7 @@ void WebApiPrometheusClass::addField(AsyncResponseStream* stream, const String& stream->printf("# HELP opendtu_%s in %s\n", chanName, inv->Statistics()->getChannelFieldUnit(type, channel, fieldId)); stream->printf("# TYPE opendtu_%s %s\n", chanName, metricName); } - stream->printf("opendtu_%s{serial=\"%s\",unit=\"%d\",name=\"%s\",type=\"%s\",channel=\"%d\"} %s\n", + stream->printf("opendtu_%s{serial=\"%s\",unit=\"%" PRId8 "\",name=\"%s\",type=\"%s\",channel=\"%d\"} %s\n", chanName, serial.c_str(), idx, @@ -150,7 +150,7 @@ void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, const Stri stream->print("# HELP opendtu_PanelInfo panel information\n"); stream->print("# TYPE opendtu_PanelInfo gauge\n"); } - stream->printf("opendtu_PanelInfo{serial=\"%s\",unit=\"%d\",name=\"%s\",channel=\"%d\",panelname=\"%s\"} 1\n", + stream->printf("opendtu_PanelInfo{serial=\"%s\",unit=\"%" PRId8 "\",name=\"%s\",channel=\"%d\",panelname=\"%s\"} 1\n", serial.c_str(), idx, inv->name(), @@ -161,7 +161,7 @@ void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, const Stri stream->print("# HELP opendtu_MaxPower panel maximum output power\n"); stream->print("# TYPE opendtu_MaxPower gauge\n"); } - stream->printf("opendtu_MaxPower{serial=\"%s\",unit=\"%d\",name=\"%s\",channel=\"%d\"} %d\n", + stream->printf("opendtu_MaxPower{serial=\"%s\",unit=\"%" PRId8 "\",name=\"%s\",channel=\"%d\"} %d\n", serial.c_str(), idx, inv->name(), @@ -172,7 +172,7 @@ void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, const Stri stream->print("# HELP opendtu_YieldTotalOffset panel yield offset (for used inverters)\n"); stream->print("# TYPE opendtu_YieldTotalOffset gauge\n"); } - stream->printf("opendtu_YieldTotalOffset{serial=\"%s\",unit=\"%d\",name=\"%s\",channel=\"%d\"} %f\n", + stream->printf("opendtu_YieldTotalOffset{serial=\"%s\",unit=\"%" PRId8 "\",name=\"%s\",channel=\"%" PRId16 "\"} %f\n", serial.c_str(), idx, inv->name(), diff --git a/src/WebApi_security.cpp b/src/WebApi_security.cpp index ddd8bb507..6be21ca6a 100644 --- a/src/WebApi_security.cpp +++ b/src/WebApi_security.cpp @@ -71,6 +71,8 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request) WebApi.writeConfig(retMsg); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); + + WebApi.reload(); } void WebApiSecurityClass::onAuthenticateGet(AsyncWebServerRequest* request) diff --git a/src/WebApi_sysstatus.cpp b/src/WebApi_sysstatus.cpp index 3a6b8f440..c25db2303 100644 --- a/src/WebApi_sysstatus.cpp +++ b/src/WebApi_sysstatus.cpp @@ -52,6 +52,20 @@ void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request) root["chipcores"] = ESP.getChipCores(); root["flashsize"] = ESP.getFlashChipSize(); + JsonArray taskDetails = root["task_details"].to(); + static std::array constexpr task_names = { + "IDLE0", "IDLE1", "wifi", "tiT", "loopTask", "async_tcp", "mqttclient", + "HUAWEI_CAN_0", "PM:SDM", "PM:HTTP+JSON", "PM:SML", "PM:HTTP+SML" + }; + for (char const* task_name : task_names) { + TaskHandle_t const handle = xTaskGetHandle(task_name); + if (!handle) { continue; } + JsonObject task = taskDetails.add(); + task["name"] = task_name; + task["stack_watermark"] = uxTaskGetStackHighWaterMark(handle); + task["priority"] = uxTaskPriorityGet(handle); + } + String reason; reason = ResetReason::get_reset_reason_verbose(0); root["resetreason_0"] = reason; diff --git a/src/WebApi_ws_Huawei.cpp b/src/WebApi_ws_Huawei.cpp index f171a18ab..cf4819914 100644 --- a/src/WebApi_ws_Huawei.cpp +++ b/src/WebApi_ws_Huawei.cpp @@ -42,6 +42,26 @@ void WebApiWsHuaweiLiveClass::init(AsyncWebServer& server, Scheduler& scheduler) _sendDataTask.setIterations(TASK_FOREVER); _sendDataTask.setInterval(1 * TASK_SECOND); _sendDataTask.enable(); + + _simpleDigestAuth.setUsername(AUTH_USERNAME); + _simpleDigestAuth.setRealm("AC charger websocket"); + + reload(); +} + +void WebApiWsHuaweiLiveClass::reload() +{ + _ws.removeMiddleware(&_simpleDigestAuth); + + auto const& config = Configuration.get(); + + if (config.Security.AllowReadonly) { return; } + + _ws.enable(false); + _simpleDigestAuth.setPassword(config.Security.Password); + _ws.addMiddleware(&_simpleDigestAuth); + _ws.closeAll(); + _ws.enable(true); } void WebApiWsHuaweiLiveClass::wsCleanupTaskCb() diff --git a/src/WebApi_ws_battery.cpp b/src/WebApi_ws_battery.cpp index 42913abc6..b932bfc4e 100644 --- a/src/WebApi_ws_battery.cpp +++ b/src/WebApi_ws_battery.cpp @@ -42,6 +42,26 @@ void WebApiWsBatteryLiveClass::init(AsyncWebServer& server, Scheduler& scheduler _sendDataTask.setIterations(TASK_FOREVER); _sendDataTask.setInterval(1 * TASK_SECOND); _sendDataTask.enable(); + + _simpleDigestAuth.setUsername(AUTH_USERNAME); + _simpleDigestAuth.setRealm("battery websocket"); + + reload(); +} + +void WebApiWsBatteryLiveClass::reload() +{ + _ws.removeMiddleware(&_simpleDigestAuth); + + auto const& config = Configuration.get(); + + if (config.Security.AllowReadonly) { return; } + + _ws.enable(false); + _simpleDigestAuth.setPassword(config.Security.Password); + _ws.addMiddleware(&_simpleDigestAuth); + _ws.closeAll(); + _ws.enable(true); } void WebApiWsBatteryLiveClass::wsCleanupTaskCb() diff --git a/src/WebApi_ws_console.cpp b/src/WebApi_ws_console.cpp index 1f1efcb20..51035f6fa 100644 --- a/src/WebApi_ws_console.cpp +++ b/src/WebApi_ws_console.cpp @@ -21,16 +21,30 @@ void WebApiWsConsoleClass::init(AsyncWebServer& server, Scheduler& scheduler) scheduler.addTask(_wsCleanupTask); _wsCleanupTask.enable(); + + _simpleDigestAuth.setUsername(AUTH_USERNAME); + _simpleDigestAuth.setRealm("console websocket"); + + reload(); +} + +void WebApiWsConsoleClass::reload() +{ + _ws.removeMiddleware(&_simpleDigestAuth); + + auto const& config = Configuration.get(); + + if (config.Security.AllowReadonly) { return; } + + _ws.enable(false); + _simpleDigestAuth.setPassword(config.Security.Password); + _ws.addMiddleware(&_simpleDigestAuth); + _ws.closeAll(); + _ws.enable(true); } void WebApiWsConsoleClass::wsCleanupTaskCb() { // see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients _ws.cleanupClients(); - - if (Configuration.get().Security.AllowReadonly) { - _ws.setAuthentication("", ""); - } else { - _ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security.Password); - } } diff --git a/src/WebApi_ws_live.cpp b/src/WebApi_ws_live.cpp index aacb9659f..e3fc7570f 100644 --- a/src/WebApi_ws_live.cpp +++ b/src/WebApi_ws_live.cpp @@ -40,18 +40,31 @@ void WebApiWsLiveClass::init(AsyncWebServer& server, Scheduler& scheduler) scheduler.addTask(_sendDataTask); _sendDataTask.enable(); + _simpleDigestAuth.setUsername(AUTH_USERNAME); + _simpleDigestAuth.setRealm("live websocket"); + + reload(); +} + +void WebApiWsLiveClass::reload() +{ + _ws.removeMiddleware(&_simpleDigestAuth); + + auto const& config = Configuration.get(); + + if (config.Security.AllowReadonly) { return; } + + _ws.enable(false); + _simpleDigestAuth.setPassword(config.Security.Password); + _ws.addMiddleware(&_simpleDigestAuth); + _ws.closeAll(); + _ws.enable(true); } void WebApiWsLiveClass::wsCleanupTaskCb() { // see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients _ws.cleanupClients(); - - if (Configuration.get().Security.AllowReadonly) { - _ws.setAuthentication("", ""); - } else { - _ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security.Password); - } } void WebApiWsLiveClass::generateOnBatteryJsonResponse(JsonVariant& root, bool all) @@ -235,6 +248,7 @@ void WebApiWsLiveClass::generateInverterCommonJsonResponse(JsonObject& root, std root["radio_stats"]["rx_fail_nothing"] = inv->RadioStats.RxFailNoAnswer; root["radio_stats"]["rx_fail_partial"] = inv->RadioStats.RxFailPartialAnswer; root["radio_stats"]["rx_fail_corrupt"] = inv->RadioStats.RxFailCorruptData; + root["radio_stats"]["rssi"] = inv->getLastRssi(); } void WebApiWsLiveClass::generateInverterChannelJsonResponse(JsonObject& root, std::shared_ptr inv) diff --git a/src/WebApi_ws_vedirect_live.cpp b/src/WebApi_ws_vedirect_live.cpp index abf3376eb..473db8390 100644 --- a/src/WebApi_ws_vedirect_live.cpp +++ b/src/WebApi_ws_vedirect_live.cpp @@ -44,6 +44,26 @@ void WebApiWsVedirectLiveClass::init(AsyncWebServer& server, Scheduler& schedule _sendDataTask.setIterations(TASK_FOREVER); _sendDataTask.setInterval(500 * TASK_MILLISECOND); _sendDataTask.enable(); + + _simpleDigestAuth.setUsername(AUTH_USERNAME); + _simpleDigestAuth.setRealm("vedirect websocket"); + + reload(); +} + +void WebApiWsVedirectLiveClass::reload() +{ + _ws.removeMiddleware(&_simpleDigestAuth); + + auto const& config = Configuration.get(); + + if (config.Security.AllowReadonly) { return; } + + _ws.enable(false); + _simpleDigestAuth.setPassword(config.Security.Password); + _ws.addMiddleware(&_simpleDigestAuth); + _ws.closeAll(); + _ws.enable(true); } void WebApiWsVedirectLiveClass::wsCleanupTaskCb() diff --git a/src/main.cpp b/src/main.cpp index b9f451afd..9e5f93c3d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,7 +9,6 @@ #include "Led_Single.h" #include "MessageOutput.h" #include "SerialPortManager.h" -#include "SPIPortManager.h" #include "VictronMppt.h" #include "Battery.h" #include "Huawei_can.h" @@ -39,12 +38,21 @@ #include #include #include +#include + +#include void setup() { // Move all dynamic allocations >512byte to psram (if available) heap_caps_malloc_extmem_enable(512); + // Initialize SpiManager + SpiManagerInst.register_bus(SPI2_HOST); +#if SOC_SPI_PERIPH_NUM > 2 + SpiManagerInst.register_bus(SPI3_HOST); +#endif + // Initialize serial output Serial.begin(SERIAL_BAUDRATE); #if ARDUINO_USB_CDC_ON_BOOT @@ -99,9 +107,7 @@ void setup() const auto& pin = PinMapping.get(); MessageOutput.println("done"); - // Initialize PortManagers SerialPortManager.init(); - SPIPortManager.init(); // Initialize WiFi MessageOutput.print("Initialize Network... "); @@ -167,7 +173,7 @@ void setup() if (config.Dtu.Serial == DTU_SERIAL) { MessageOutput.print("generate serial based on ESP chip id: "); const uint64_t dtuId = Utils::generateDtuSerial(); - MessageOutput.printf("%0x%08x... ", + MessageOutput.printf("%0" PRIx32 "%08" PRIx32 "... ", ((uint32_t)((dtuId >> 32) & 0xFFFFFFFF)), ((uint32_t)(dtuId & 0xFFFFFFFF))); config.Dtu.Serial = dtuId; diff --git a/webapp/eslint.config.js b/webapp/eslint.config.js index 9a2aaecb7..94f27454c 100644 --- a/webapp/eslint.config.js +++ b/webapp/eslint.config.js @@ -1,22 +1,12 @@ /* eslint-env node */ -import path from "node:path"; -import { fileURLToPath } from "node:url"; - -import { FlatCompat } from "@eslint/eslintrc"; import js from "@eslint/js"; import pluginVue from 'eslint-plugin-vue' - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, -}); +import vueTsEslintConfig from '@vue/eslint-config-typescript' export default [ js.configs.recommended, ...pluginVue.configs['flat/essential'], - ...compat.extends("@vue/eslint-config-typescript/recommended"), + ...vueTsEslintConfig(), { files: [ "**/*.vue", diff --git a/webapp/package.json b/webapp/package.json index ac5161ffd..dc6f8b895 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -19,7 +19,7 @@ "mitt": "^3.0.1", "sortablejs": "^1.15.3", "spark-md5": "^3.0.2", - "vue": "^3.5.9", + "vue": "^3.5.11", "vue-i18n": "9.13.1", "vue-router": "^4.4.5" }, @@ -27,24 +27,24 @@ "@intlify/unplugin-vue-i18n": "^4.0.0", "@tsconfig/node22": "^22.0.0", "@types/bootstrap": "^5.2.10", - "@types/node": "^22.7.2", + "@types/node": "^22.7.4", "@types/pulltorefreshjs": "^0.1.7", "@types/sortablejs": "^1.15.8", "@types/spark-md5": "^3.0.4", "@vitejs/plugin-vue": "^5.1.4", - "@vue/eslint-config-typescript": "^13.0.0", + "@vue/eslint-config-typescript": "^14.0.0", "@vue/tsconfig": "^0.5.1", - "eslint": "^9.11.1", + "eslint": "^9.12.0", "eslint-plugin-vue": "^9.28.0", "npm-run-all": "^4.1.5", "prettier": "^3.3.3", "pulltorefreshjs": "^0.1.22", "sass": "^1.77.6", - "terser": "^5.34.0", + "terser": "^5.34.1", "typescript": "^5.6.2", "vite": "^5.4.8", "vite-plugin-compression": "^0.5.1", - "vite-plugin-css-injected-by-js": "^3.5.1", + "vite-plugin-css-injected-by-js": "^3.5.2", "vue-tsc": "^2.1.6" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" diff --git a/webapp/src/components/BatteryView.vue b/webapp/src/components/BatteryView.vue index 1609346c0..0f1a6cbf7 100644 --- a/webapp/src/components/BatteryView.vue +++ b/webapp/src/components/BatteryView.vue @@ -234,7 +234,9 @@ export default defineComponent({ }, // Send heartbeat packets regularly * 59s Send a heartbeat heartCheck() { - this.heartInterval && clearTimeout(this.heartInterval); + if (this.heartInterval) { + clearTimeout(this.heartInterval); + } this.heartInterval = setInterval(() => { if (this.socket.readyState === 1) { // Connection status @@ -247,7 +249,9 @@ export default defineComponent({ /** To break off websocket Connect */ closeSocket() { this.socket.close(); - this.heartInterval && clearTimeout(this.heartInterval); + if (this.heartInterval) { + clearTimeout(this.heartInterval); + } this.isFirstFetchAfterConnect = true; }, }, diff --git a/webapp/src/components/FirmwareInfo.vue b/webapp/src/components/FirmwareInfo.vue index 926b48e74..e1f5d1ef6 100644 --- a/webapp/src/components/FirmwareInfo.vue +++ b/webapp/src/components/FirmwareInfo.vue @@ -126,9 +126,9 @@ export default defineComponent({ }, versionInfoUrl(): string { if (this.systemStatus.git_is_hash) { - return 'https://github.com/helgeerbe/OpenDTU-OnBattery/commits/' + this.systemStatus.git_hash; + return 'https://github.com/hoylabs/OpenDTU-OnBattery/commits/' + this.systemStatus.git_hash; } - return 'https://github.com/helgeerbe/OpenDTU-OnBattery/releases/tag/' + this.systemStatus.git_hash; + return 'https://github.com/hoylabs/OpenDTU-OnBattery/releases/tag/' + this.systemStatus.git_hash; }, }, }); diff --git a/webapp/src/components/HuaweiView.vue b/webapp/src/components/HuaweiView.vue index 34e968559..32b7c2b66 100644 --- a/webapp/src/components/HuaweiView.vue +++ b/webapp/src/components/HuaweiView.vue @@ -358,7 +358,9 @@ export default defineComponent({ }, // Send heartbeat packets regularly * 59s Send a heartbeat heartCheck() { - this.heartInterval && clearTimeout(this.heartInterval); + if (this.heartInterval) { + clearTimeout(this.heartInterval); + } this.heartInterval = setInterval(() => { if (this.socket.readyState === 1) { // Connection status @@ -371,7 +373,9 @@ export default defineComponent({ /** To break off websocket Connect */ closeSocket() { this.socket.close(); - this.heartInterval && clearTimeout(this.heartInterval); + if (this.heartInterval) { + clearTimeout(this.heartInterval); + } this.isFirstFetchAfterConnect = true; }, formatNumber(num: number) { diff --git a/webapp/src/components/NavBar.vue b/webapp/src/components/NavBar.vue index a3cde06d8..34536eea3 100644 --- a/webapp/src/components/NavBar.vue +++ b/webapp/src/components/NavBar.vue @@ -250,7 +250,9 @@ export default defineComponent({ this.$router.push('/'); }, onClick() { - this.$refs.navbarCollapse && (this.$refs.navbarCollapse as HTMLElement).classList.remove('show'); + if (this.$refs.navbarCollapse) { + (this.$refs.navbarCollapse as HTMLElement).classList.remove('show'); + } }, getEasterSunday(year: number): Date { const f = Math.floor; diff --git a/webapp/src/components/PinInfo.vue b/webapp/src/components/PinInfo.vue index b79bfc648..a24103254 100644 --- a/webapp/src/components/PinInfo.vue +++ b/webapp/src/components/PinInfo.vue @@ -60,6 +60,7 @@ export default defineComponent({ let selArray: Array = []; if (this.selectedPinAssignment) { selArray = Object.keys(this.selectedPinAssignment as Device); + selArray = selArray.filter((item) => curArray.includes(item)); } let total: Array = []; diff --git a/webapp/src/components/TaskDetails.vue b/webapp/src/components/TaskDetails.vue new file mode 100644 index 000000000..a9ab86ac3 --- /dev/null +++ b/webapp/src/components/TaskDetails.vue @@ -0,0 +1,40 @@ + + + diff --git a/webapp/src/components/VedirectView.vue b/webapp/src/components/VedirectView.vue index df925297f..95cb245b8 100644 --- a/webapp/src/components/VedirectView.vue +++ b/webapp/src/components/VedirectView.vue @@ -217,7 +217,9 @@ export default defineComponent({ }, // Send heartbeat packets regularly * 59s Send a heartbeat heartCheck() { - this.heartInterval && clearTimeout(this.heartInterval); + if (this.heartInterval) { + clearTimeout(this.heartInterval); + } this.heartInterval = setInterval(() => { if (this.socket.readyState === 1) { // Connection status @@ -230,7 +232,9 @@ export default defineComponent({ /** To break off websocket Connect */ closeSocket() { this.socket.close(); - this.heartInterval && clearTimeout(this.heartInterval); + if (this.heartInterval) { + clearTimeout(this.heartInterval); + } this.isFirstFetchAfterConnect = true; }, }, diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 0942139db..2cdb73341 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -159,7 +159,10 @@ "RxFailCorrupt": "Empfang Fehler: Beschädigt empfangen", "TxReRequest": "Gesendete Fragment Wiederanforderungen", "StatsReset": "Statistiken zurücksetzen", - "StatsResetting": "Zurücksetzen..." + "StatsResetting": "Zurücksetzen...", + "Rssi": "RSSI des zuletzt empfangenen Paketes", + "RssiHint": "HM-Wechselrichter unterstützen nur RSSI-Werte < -64 dBm und > -64 dBm. In diesem Fall wird -80 dBm und -30 dBm angezeigt.", + "dBm": "{dbm} dBm" }, "vedirecthome": { "SerialNumber": "Seriennummer", @@ -283,6 +286,24 @@ "MaxUsage": "Maximale Speichernutzung seit Start", "Fragmentation": "Grad der Fragmentierung" }, + "taskdetails": { + "TaskDetails": "Detailinformationen zu Tasks", + "Name": "Name", + "StackFree": "Stack Frei", + "Priority": "Priorität", + "Task_idle0": "Leerlauf (CPU-Kern 0)", + "Task_idle1": "Leerlauf (CPU-Kern 1)", + "Task_wifi": "Wi-Fi", + "Task_tit": "TCP/IP", + "Task_looptask": "Arduino Hauptschleife (loop)", + "Task_asynctcp": "Async TCP", + "Task_mqttclient": "MQTT Client", + "Task_huaweican0": "AC Ladegerät CAN", + "Task_pmsdm": "Stromzähler (SDM)", + "Task_pmhttpjson": "Stromzähler (HTTP+JSON)", + "Task_pmsml": "Stromzähler (Serial SML)", + "Task_pmhttpsml": "Stromzähler (HTTP+SML)" + }, "radioinfo": { "RadioInformation": "Funkmodulinformationen", "Status": "{module} Status", @@ -430,7 +451,7 @@ "Cancel": "@:base.Cancel", "RebootOpenDTU": "OpenDTU neu starten", "RebootQuestion": "Möchten Sie das Gerät wirklich neu starten?", - "RebootHint": "Hinweis: Ein manueller Neustart muss normalerweise nicht durchgeführt werden. OpenDTU führt jeden erforderlichen Neustart (z. B. nach einem Firmware-Update) automatisch durch. Einstellungen werden auch ohne Neustart übernommen. Wenn Sie aufgrund eines Fehlers einen Neustart durchführen müssen, denken Sie bitte daran, diesen unter Github zu melden." + "RebootHint": "Hinweis: Ein manueller Neustart muss normalerweise nicht durchgeführt werden. OpenDTU führt jeden erforderlichen Neustart (z. B. nach einem Firmware-Update) automatisch durch. Einstellungen werden auch ohne Neustart übernommen. Wenn Sie aufgrund eines Fehlers einen Neustart durchführen müssen, denken Sie bitte daran, diesen unter Github zu melden." }, "dtuadmin": { "DtuSettings": "DTU-Einstellungen", @@ -839,11 +860,11 @@ "ProjectOriginBody3": "Die Software wurde nach bestem Wissen und Gewissen entwickelt. Dennoch kann keine Haftung für eine Fehlfunktion oder einen Garantieverlust des Wechselrichters übernommen werden.", "ProjectOriginBody4": "OpenDTU ist frei verfügbar. Wenn Sie Geld für die Software bezahlt haben, wurden Sie wahrscheinlich abgezockt.", "NewsUpdates": "Neuigkeiten und Updates", - "NewsUpdatesBody": "Neue Updates sind auf Github zu finden.", + "NewsUpdatesBody": "Neue Updates sind auf Github zu finden.", "ErrorReporting": "Fehlerberichte", - "ErrorReportingBody": "Bitte melden Sie Probleme über die Ticketverwaltung von Github.", + "ErrorReportingBody": "Bitte melden Sie Probleme über die Ticketverwaltung von Github.", "Discussion": "Diskussion", - "DiscussionBody": "Diskutieren Sie mit uns auf Discord oder Github." + "DiscussionBody": "Diskutieren Sie mit uns auf Discord oder Github." }, "hints": { "RadioProblem": "Es konnte keine Verbindung zu einem der konfigurierten Funkmodule hergestellt werden. Bitte überprüfen Sie die Verdrahtung.", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 3b9f835d3..435e0d4f1 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -159,7 +159,10 @@ "RxFailCorrupt": "RX Fail: Receive Corrupt", "TxReRequest": "TX Re-Request Fragment", "StatsReset": "Reset Statistics", - "StatsResetting": "Resetting..." + "StatsResetting": "Resetting...", + "Rssi": "RSSI of last received packet", + "RssiHint": "HM inverters only support RSSI values < -64 dBm and > -64 dBm. In this case, -80 dbm and -30 dbm is shown.", + "dBm": "{dbm} dBm" }, "vedirecthome": { "SerialNumber": "Serial Number", @@ -284,6 +287,24 @@ "MaxUsage": "Maximum usage since start", "Fragmentation": "Level of fragmentation" }, + "taskdetails": { + "TaskDetails": "Task Details", + "Name": "Name", + "StackFree": "Stack Free", + "Priority": "Priority", + "Task_idle0": "Idle (CPU Core 0)", + "Task_idle1": "Idle (CPU Core 1)", + "Task_wifi": "Wi-Fi", + "Task_tit": "TCP/IP", + "Task_looptask": "Arduino Main Loop", + "Task_asynctcp": "Async TCP", + "Task_mqttclient": "MQTT Client", + "Task_huaweican0": "AC Charger CAN", + "Task_pmsdm": "PowerMeter (SDM)", + "Task_pmhttpjson": "PowerMeter (HTTP+JSON)", + "Task_pmsml": "PowerMeter (Serial SML)", + "Task_pmhttpsml": "PowerMeter (HTTP+SML)" + }, "radioinfo": { "RadioInformation": "Radio Information", "Status": "{module} Status", @@ -432,7 +453,7 @@ "Cancel": "@:base.Cancel", "RebootOpenDTU": "Reboot OpenDTU", "RebootQuestion": "Do you really want to reboot the device?", - "RebootHint": "Note: A manual reboot does not normally have to be performed. OpenDTU performs any required reboot (e.g. after a firmware update) automatically. Settings are also adopted without rebooting. If you need to reboot due to an error, please consider reporting it at Github." + "RebootHint": "Note: A manual reboot does not normally have to be performed. OpenDTU performs any required reboot (e.g. after a firmware update) automatically. Settings are also adopted without rebooting. If you need to reboot due to an error, please consider reporting it at Github." }, "dtuadmin": { "DtuSettings": "DTU Settings", @@ -843,11 +864,11 @@ "ProjectOriginBody3": "The software was developed to the best of our knowledge and belief. Nevertheless, no liability can be accepted for a malfunction or guarantee loss of the inverter.", "ProjectOriginBody4": "OpenDTU is freely available. If you paid money for the software, you probably got ripped off.", "NewsUpdates": "News & Updates", - "NewsUpdatesBody": "New updates can be found on Github.", + "NewsUpdatesBody": "New updates can be found on Github.", "ErrorReporting": "Error Reporting", - "ErrorReportingBody": "Please report issues using the feature provided by Github.", + "ErrorReportingBody": "Please report issues using the feature provided by Github.", "Discussion": "Discussion", - "DiscussionBody": "Discuss with us on Discord or Github." + "DiscussionBody": "Discuss with us on Discord or Github." }, "hints": { "RadioProblem": "Could not connect to a configured radio module. Please check the wiring.", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index df4c3a276..6a42081ae 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -159,7 +159,10 @@ "RxFailCorrupt": "RX Fail: Receive Corrupt", "TxReRequest": "TX Re-Request Fragment", "StatsReset": "Reset Statistics", - "StatsResetting": "Resetting..." + "StatsResetting": "Resetting...", + "Rssi": "RSSI of last received packet", + "RssiHint": "HM inverters only support RSSI values < -64 dBm and > -64 dBm. In this case, -80 dbm and -30 dbm is shown.", + "dBm": "{dbm} dBm" }, "vedirecthome": { "SerialNumber": "Numéro de série", @@ -465,7 +468,7 @@ "Cancel": "@:base.Cancel", "RebootOpenDTU": "Redémarrer OpenDTU", "RebootQuestion": "Voulez-vous vraiment redémarrer l'appareil ?", - "RebootHint": "Astuce : Normalement, il n'est pas nécessaire de procéder à un redémarrage manuel. OpenDTU effectue automatiquement tout redémarrage nécessaire (par exemple, après une mise à jour du firmware). Les paramètres sont également adoptés sans redémarrage. Si vous devez redémarrer en raison d'une erreur, veuillez envisager de la signaler à l'adresse suivante Github." + "RebootHint": "Astuce : Normalement, il n'est pas nécessaire de procéder à un redémarrage manuel. OpenDTU effectue automatiquement tout redémarrage nécessaire (par exemple, après une mise à jour du firmware). Les paramètres sont également adoptés sans redémarrage. Si vous devez redémarrer en raison d'une erreur, veuillez envisager de la signaler à l'adresse suivante Github." }, "dtuadmin": { "DtuSettings": "Paramètres du DTU", @@ -811,11 +814,11 @@ "ProjectOriginBody3": "Le logiciel a été développé au mieux de nos connaissances et de nos convictions. Néanmoins, aucune responsabilité ne peut être acceptée en cas de dysfonctionnement ou de perte de garantie de l'onduleur.", "ProjectOriginBody4": "OpenDTU est disponible gratuitement. Si vous avez payé pour le logiciel, vous avez probablement été arnaqué.", "NewsUpdates": "Actualités et mises à jour", - "NewsUpdatesBody": "Les nouvelles mises à jour peuvent être trouvées sur Github.", + "NewsUpdatesBody": "Les nouvelles mises à jour peuvent être trouvées sur Github.", "ErrorReporting": "Rapport d'erreurs", - "ErrorReportingBody": "Veuillez signaler les problèmes en utilisant la fonction fournie par Github.", + "ErrorReportingBody": "Veuillez signaler les problèmes en utilisant la fonction fournie par Github.", "Discussion": "Discussion", - "DiscussionBody": "Discutez avec nous sur Discord ou sur Github." + "DiscussionBody": "Discutez avec nous sur Discord ou sur Github." }, "hints": { "RadioProblem": "Impossible de se connecter à un module radio configuré.. Veuillez vérifier le câblage.", diff --git a/webapp/src/types/LiveDataStatus.ts b/webapp/src/types/LiveDataStatus.ts index 23cf6b955..591e01129 100644 --- a/webapp/src/types/LiveDataStatus.ts +++ b/webapp/src/types/LiveDataStatus.ts @@ -28,6 +28,7 @@ export interface RadioStatistics { rx_fail_nothing: number; rx_fail_partial: number; rx_fail_corrupt: number; + rssi: number; } export interface Inverter { diff --git a/webapp/src/types/PinMapping.ts b/webapp/src/types/PinMapping.ts index 4fcb61898..dd5423b09 100644 --- a/webapp/src/types/PinMapping.ts +++ b/webapp/src/types/PinMapping.ts @@ -60,4 +60,4 @@ export interface Device { battery: Battery; } -export interface PinMapping extends Array {} +export type PinMapping = Array; diff --git a/webapp/src/types/SystemStatus.ts b/webapp/src/types/SystemStatus.ts index 3937361d9..41fdd3cf5 100644 --- a/webapp/src/types/SystemStatus.ts +++ b/webapp/src/types/SystemStatus.ts @@ -1,3 +1,9 @@ +export interface TaskDetail { + name: string; + stack_watermark: number; + priority: number; +} + export interface SystemStatus { // HardwareInfo chipmodel: string; @@ -6,6 +12,8 @@ export interface SystemStatus { cpufreq: number; cputemp: number; flashsize: number; + // TaskDetails + task_details: TaskDetail[]; // FirmwareInfo hostname: string; sdkversion: string; diff --git a/webapp/src/views/ConsoleInfoView.vue b/webapp/src/views/ConsoleInfoView.vue index 745badd17..44feb07e4 100644 --- a/webapp/src/views/ConsoleInfoView.vue +++ b/webapp/src/views/ConsoleInfoView.vue @@ -84,7 +84,7 @@ export default defineComponent({ this.socket.onmessage = (event) => { console.log(event); - let outstr = new String(event.data); + let outstr = String(event.data); let removedNewline = false; if (outstr.endsWith('\n')) { outstr = outstr.substring(0, outstr.length - 1); @@ -108,7 +108,9 @@ export default defineComponent({ }, // Send heartbeat packets regularly * 59s Send a heartbeat heartCheck() { - this.heartInterval && clearTimeout(this.heartInterval); + if (this.heartInterval) { + clearTimeout(this.heartInterval); + } this.heartInterval = setInterval(() => { if (this.socket.readyState === 1) { // Connection status @@ -126,7 +128,9 @@ export default defineComponent({ // continue regardless of error } - this.heartInterval && clearTimeout(this.heartInterval); + if (this.heartInterval) { + clearTimeout(this.heartInterval); + } }, getOutDate(): string { const u = new Date(); diff --git a/webapp/src/views/DeviceAdminView.vue b/webapp/src/views/DeviceAdminView.vue index eafcf902f..258ec5b2c 100644 --- a/webapp/src/views/DeviceAdminView.vue +++ b/webapp/src/views/DeviceAdminView.vue @@ -385,7 +385,7 @@ export default defineComponent({ return; } const srcId = this.getNumberFromLedId((event.target as Element).id); - this.deviceConfigList.led.every((v) => (v.brightness = this.deviceConfigList.led[srcId].brightness)); + this.deviceConfigList.led.map((v) => (v.brightness = this.deviceConfigList.led[srcId].brightness)); }, }, }); diff --git a/webapp/src/views/HomeView.vue b/webapp/src/views/HomeView.vue index ac6674a4d..ba4eb0509 100644 --- a/webapp/src/views/HomeView.vue +++ b/webapp/src/views/HomeView.vue @@ -297,6 +297,16 @@ {{ $n(inverter.radio_stats.tx_re_request) }} + + + {{ $t('home.Rssi') }} + + + + {{ $t('home.dBm', { dbm: $n(inverter.radio_stats.rssi) }) }} + + +