├── lib ├── Hoymiles │ ├── README.md │ ├── src │ │ ├── Utils.h │ │ ├── commands │ │ │ ├── ParaSetCommand.h │ │ │ ├── SingleDataCommand.h │ │ │ ├── ParaSetCommand.cpp │ │ │ ├── DevControlCommand.h │ │ │ ├── README.md │ │ │ ├── DevInfoAllCommand.h │ │ │ ├── GridOnProFilePara.h │ │ │ ├── DevInfoSimpleCommand.h │ │ │ ├── AlarmDataCommand.h │ │ │ ├── RealTimeRunDataCommand.h │ │ │ ├── SystemConfigParaCommand.h │ │ │ ├── PowerControlCommand.h │ │ │ ├── RequestFrameCommand.h │ │ │ ├── ChannelChangeCommand.h │ │ │ ├── MultiDataCommand.h │ │ │ ├── ActivePowerControlCommand.h │ │ │ ├── SingleDataCommand.cpp │ │ │ ├── DevControlCommand.cpp │ │ │ ├── RequestFrameCommand.cpp │ │ │ ├── CommandAbstract.h │ │ │ ├── DevInfoAllCommand.cpp │ │ │ ├── DevInfoSimpleCommand.cpp │ │ │ └── GridOnProFilePara.cpp │ │ ├── inverters │ │ │ ├── HMS_Abstract.h │ │ │ ├── HMT_Abstract.h │ │ │ ├── HERF_4CH.h │ │ │ ├── HM_2CH.h │ │ │ ├── HM_4CH.h │ │ │ ├── HMS_4CH.h │ │ │ ├── HMT_4CH.h │ │ │ ├── HMT_6CH.h │ │ │ ├── HERF_2CH.h │ │ │ ├── HM_1CH.h │ │ │ ├── HMS_1CH.h │ │ │ ├── HMS_2CH.h │ │ │ ├── HMS_1CHv2.h │ │ │ ├── HERF_4CH.cpp │ │ │ ├── README.md │ │ │ ├── HMS_Abstract.cpp │ │ │ ├── HM_Abstract.h │ │ │ ├── HMT_Abstract.cpp │ │ │ ├── HMS_1CH.cpp │ │ │ └── HMS_1CHv2.cpp │ │ ├── Utils.cpp │ │ ├── types.h │ │ ├── crc.h │ │ ├── parser │ │ │ ├── PowerCommandParser.h │ │ │ ├── Parser.cpp │ │ │ ├── PowerCommandParser.cpp │ │ │ ├── Parser.h │ │ │ ├── SystemConfigParaParser.h │ │ │ ├── DevInfoParser.h │ │ │ ├── GridProfileParser.h │ │ │ └── AlarmLogParser.h │ │ ├── HoymilesRadio_NRF.h │ │ ├── crc.cpp │ │ ├── HoymilesRadio.h │ │ └── Hoymiles.h │ └── library.json ├── TimeoutHelper │ ├── README.md │ ├── library.json │ └── src │ │ ├── TimeoutHelper.h │ │ └── TimeoutHelper.cpp ├── ThreadSafeQueue │ ├── README.md │ ├── library.json │ └── src │ │ └── ThreadSafeQueue.h ├── Frozen │ ├── AUTHORS │ └── frozen │ │ ├── CMakeLists.txt │ │ └── bits │ │ ├── hash_string.h │ │ ├── version.h │ │ ├── elsa_std.h │ │ ├── constexpr_assert.h │ │ ├── exceptions.h │ │ ├── mpl.h │ │ └── elsa.h ├── CpuTemperature │ └── src │ │ ├── CpuTemperature.h │ │ └── CpuTemperature.cpp ├── ResetReason │ └── src │ │ └── ResetReason.h ├── CMT2300a │ ├── cmt_spi3.h │ └── cmt2300a_hal.h ├── MqttSubscribeParser │ └── MqttSubscribeParser.h └── README ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ └── feature_request.yml ├── dependabot.yml └── workflows │ ├── yarnlint.yml │ ├── cpplint.yml │ ├── config │ └── release-notes-config.json │ └── repo-maintenance.yml ├── .vscode ├── settings.json └── extensions.json ├── docs ├── builds │ ├── sol.webp │ ├── thumbnail.jpg │ ├── opendtu_breakoutboard.jpg │ ├── large_display_PXL_20220715_145622277.jpg │ ├── 202654506-8a4ac4ef-c883-490e-8ee1-1e1f7fa34972.jpg │ └── README.md ├── Wiring_ESP32.fzz ├── nodemcu-esp32.png ├── nrf24l01plus.png ├── Wiring_ESP32_Symbol.png ├── screenshots │ ├── 19_Reboot.png │ ├── 01_LiveView.png │ ├── 03_NtpAdmin.png │ ├── 04_MqttAdmin.png │ ├── 06_DtuAdmin.png │ ├── 09_NtpInfo.png │ ├── 10_MqttInfo.png │ ├── 12_Eventlog.png │ ├── 18_Console.png │ ├── 22_Security.png │ ├── 08_NetworkInfo.png │ ├── 11_SystemInfo.png │ ├── 02_NetworkAdmin.png │ ├── 05_InverterAdmin.png │ ├── 15_LimitSettings.png │ ├── 16_PowerSettings.png │ ├── 17_InverterInfo.png │ ├── 07_FirmwareUpgrade.png │ ├── 13_InverterSettings.png │ ├── 14_ConfigManagement.png │ ├── 20_DeviceManager_Pin.png │ ├── 21_DeviceManager_Display.png │ └── README.md ├── Wiring_ESP32_Schematic.png ├── esp32_flash_download_tool.png ├── Web-API.md ├── Display.md ├── MQTT_Topics.md ├── DeviceProfiles.md ├── UpgradePartition.md ├── README.md └── DeviceProfiles │ ├── wemos-lolin32-oled.json │ ├── CASmo-DTU.json │ ├── esp32_stick_poe_a.json │ ├── olimex_esp32_evb.json │ ├── olimex_esp32_gateway.json │ ├── AhoyDTU-ESP32.json │ ├── liligo_t-eth-lite_poe.json │ └── lilygo_ttgo_t-internet_poe.json ├── webapp_dist ├── favicon.ico ├── favicon.png ├── index.html.gz ├── js │ └── app.js.gz ├── zones.json.gz └── site.webmanifest ├── webapp ├── public │ ├── favicon.ico │ ├── favicon.png │ └── site.webmanifest ├── src │ ├── assets │ │ └── logo.png │ ├── types │ │ ├── GridProfileRawdata.ts │ │ ├── SecurityConfig.ts │ │ ├── LimitConfig.ts │ │ ├── LimitStatus.ts │ │ ├── Config.ts │ │ ├── NtpConfig.ts │ │ ├── EventlogStatus.ts │ │ ├── DevInfoStatus.ts │ │ ├── NetworkConfig.ts │ │ ├── NtpStatus.ts │ │ ├── GridProfileStatus.ts │ │ ├── DeviceConfig.ts │ │ ├── DtuConfig.ts │ │ ├── InverterConfig.ts │ │ ├── NetworkStatus.ts │ │ ├── MqttStatus.ts │ │ ├── MqttConfig.ts │ │ ├── PinMapping.ts │ │ ├── SystemStatus.ts │ │ └── LiveDataStatus.ts │ ├── scss │ │ └── styles.scss │ ├── plugins │ │ └── bootstrap.ts │ ├── emitter.d.ts │ ├── utils │ │ ├── index.ts │ │ └── time.ts │ ├── components │ │ ├── FormFooter.vue │ │ ├── CardElement.vue │ │ ├── StatusBadge.vue │ │ ├── LocaleSwitcher.vue │ │ ├── InterfaceApInfo.vue │ │ ├── FsInfo.vue │ │ ├── EventLog.vue │ │ ├── HintView.vue │ │ ├── WifiApInfo.vue │ │ ├── ModalDialog.vue │ │ ├── HardwareInfo.vue │ │ ├── MemoryInfo.vue │ │ └── InterfaceNetworkInfo.vue │ ├── App.vue │ ├── views │ │ ├── ErrorView.vue │ │ └── NetworkInfoView.vue │ ├── main.ts │ └── locales │ │ └── index.ts ├── .vscode │ └── extensions.json ├── .prettierrc.json ├── env.d.ts ├── tsconfig.config.json ├── .gitignore ├── tsconfig.json ├── index.html ├── README.md ├── eslint.config.js ├── package.json └── vite.config.ts ├── include ├── helper.h ├── Scheduler.h ├── __compiled_constants.h ├── NtpSettings.h ├── WebApi_devinfo.h ├── WebApi_eventlog.h ├── WebApi_maintenance.h ├── WebApi_sysstatus.h ├── MqttHandleDtu.h ├── WebApi_limit.h ├── WebApi_power.h ├── MqttHandleInverterTotal.h ├── WebApi_device.h ├── WebApi_ws_console.h ├── WebApi_gridprofile.h ├── Utils.h ├── WebApi_webapp.h ├── WebApi_network.h ├── WebApi_security.h ├── WebApi_dtu.h ├── WebApi_firmware.h ├── WebApi_mqtt.h ├── InverterSettings.h ├── WebApi_ntp.h ├── WebApi_inverter.h ├── WebApi_config.h ├── MessageOutput.h ├── Led_Single.h ├── Display_Graphic_Diagram.h ├── SunPosition.h ├── MqttHandleInverter.h ├── PinMapping.h ├── WebApi_ws_live.h ├── MqttSettings.h ├── README ├── WebApi_prometheus.h ├── Display_Graphic.h └── WebApi_errors.h ├── src ├── Scheduler.cpp ├── NtpSettings.cpp ├── WebApi_ws_console.cpp ├── MqttHandleDtu.cpp ├── WebApi_devinfo.cpp ├── WebApi_maintenance.cpp ├── MessageOutput.cpp ├── MqttHandleInverterTotal.cpp └── WebApi_eventlog.cpp ├── .editorconfig ├── .gitignore ├── partitions_custom_16mb.csv ├── partitions_custom_4mb.csv ├── COPYING ├── test └── README └── platformio_override.ini /lib/Hoymiles/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/TimeoutHelper/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/ThreadSafeQueue/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: tbnobody -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "C_Cpp.clang_format_style": "WebKit" 3 | } -------------------------------------------------------------------------------- /docs/builds/sol.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/builds/sol.webp -------------------------------------------------------------------------------- /docs/Wiring_ESP32.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/Wiring_ESP32.fzz -------------------------------------------------------------------------------- /docs/nodemcu-esp32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/nodemcu-esp32.png -------------------------------------------------------------------------------- /docs/nrf24l01plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/nrf24l01plus.png -------------------------------------------------------------------------------- /webapp_dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/webapp_dist/favicon.ico -------------------------------------------------------------------------------- /webapp_dist/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/webapp_dist/favicon.png -------------------------------------------------------------------------------- /docs/builds/thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/builds/thumbnail.jpg -------------------------------------------------------------------------------- /webapp/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/webapp/public/favicon.ico -------------------------------------------------------------------------------- /webapp/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/webapp/public/favicon.png -------------------------------------------------------------------------------- /webapp_dist/index.html.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/webapp_dist/index.html.gz -------------------------------------------------------------------------------- /webapp_dist/js/app.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/webapp_dist/js/app.js.gz -------------------------------------------------------------------------------- /webapp_dist/zones.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/webapp_dist/zones.json.gz -------------------------------------------------------------------------------- /docs/Wiring_ESP32_Symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/Wiring_ESP32_Symbol.png -------------------------------------------------------------------------------- /webapp/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/webapp/src/assets/logo.png -------------------------------------------------------------------------------- /docs/screenshots/19_Reboot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/19_Reboot.png -------------------------------------------------------------------------------- /docs/Wiring_ESP32_Schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/Wiring_ESP32_Schematic.png -------------------------------------------------------------------------------- /docs/screenshots/01_LiveView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/01_LiveView.png -------------------------------------------------------------------------------- /docs/screenshots/03_NtpAdmin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/03_NtpAdmin.png -------------------------------------------------------------------------------- /docs/screenshots/04_MqttAdmin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/04_MqttAdmin.png -------------------------------------------------------------------------------- /docs/screenshots/06_DtuAdmin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/06_DtuAdmin.png -------------------------------------------------------------------------------- /docs/screenshots/09_NtpInfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/09_NtpInfo.png -------------------------------------------------------------------------------- /docs/screenshots/10_MqttInfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/10_MqttInfo.png -------------------------------------------------------------------------------- /docs/screenshots/12_Eventlog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/12_Eventlog.png -------------------------------------------------------------------------------- /docs/screenshots/18_Console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/18_Console.png -------------------------------------------------------------------------------- /docs/screenshots/22_Security.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/22_Security.png -------------------------------------------------------------------------------- /webapp/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /webapp/src/types/GridProfileRawdata.ts: -------------------------------------------------------------------------------- 1 | export interface GridProfileRawdata { 2 | raw: Array; 3 | } 4 | -------------------------------------------------------------------------------- /docs/esp32_flash_download_tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/esp32_flash_download_tool.png -------------------------------------------------------------------------------- /docs/screenshots/08_NetworkInfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/08_NetworkInfo.png -------------------------------------------------------------------------------- /docs/screenshots/11_SystemInfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/11_SystemInfo.png -------------------------------------------------------------------------------- /docs/builds/opendtu_breakoutboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/builds/opendtu_breakoutboard.jpg -------------------------------------------------------------------------------- /docs/screenshots/02_NetworkAdmin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/02_NetworkAdmin.png -------------------------------------------------------------------------------- /docs/screenshots/05_InverterAdmin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/05_InverterAdmin.png -------------------------------------------------------------------------------- /docs/screenshots/15_LimitSettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/15_LimitSettings.png -------------------------------------------------------------------------------- /docs/screenshots/16_PowerSettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/16_PowerSettings.png -------------------------------------------------------------------------------- /docs/screenshots/17_InverterInfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/17_InverterInfo.png -------------------------------------------------------------------------------- /docs/screenshots/07_FirmwareUpgrade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/07_FirmwareUpgrade.png -------------------------------------------------------------------------------- /docs/screenshots/13_InverterSettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/13_InverterSettings.png -------------------------------------------------------------------------------- /docs/screenshots/14_ConfigManagement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/14_ConfigManagement.png -------------------------------------------------------------------------------- /docs/screenshots/20_DeviceManager_Pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/20_DeviceManager_Pin.png -------------------------------------------------------------------------------- /webapp/src/types/SecurityConfig.ts: -------------------------------------------------------------------------------- 1 | export interface SecurityConfig { 2 | password: string; 3 | allow_readonly: boolean; 4 | } 5 | -------------------------------------------------------------------------------- /docs/screenshots/21_DeviceManager_Display.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/screenshots/21_DeviceManager_Display.png -------------------------------------------------------------------------------- /include/helper.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #define STR_HELPER(x) #x 5 | #define STR(x) STR_HELPER(x) -------------------------------------------------------------------------------- /lib/Frozen/AUTHORS: -------------------------------------------------------------------------------- 1 | serge-sans-paille 2 | Jérôme Dumesnil 3 | Chris Beck 4 | -------------------------------------------------------------------------------- /webapp/src/types/LimitConfig.ts: -------------------------------------------------------------------------------- 1 | export interface LimitConfig { 2 | serial: string; 3 | limit_value: number; 4 | limit_type: number; 5 | } 6 | -------------------------------------------------------------------------------- /docs/builds/large_display_PXL_20220715_145622277.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/builds/large_display_PXL_20220715_145622277.jpg -------------------------------------------------------------------------------- /docs/Web-API.md: -------------------------------------------------------------------------------- 1 | # Web API 2 | 3 | This documentation will has been moved and can be found here: 4 | -------------------------------------------------------------------------------- /include/Scheduler.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | 6 | extern Scheduler scheduler; -------------------------------------------------------------------------------- /webapp/src/types/LimitStatus.ts: -------------------------------------------------------------------------------- 1 | export interface LimitStatus { 2 | limit_relative: number; 3 | max_power: number; 4 | limit_set_status: string; 5 | } 6 | -------------------------------------------------------------------------------- /docs/Display.md: -------------------------------------------------------------------------------- 1 | # Display integration 2 | 3 | This documentation will has been moved and can be found here: 4 | -------------------------------------------------------------------------------- /docs/MQTT_Topics.md: -------------------------------------------------------------------------------- 1 | # MQTT Topics 2 | 3 | This documentation will has been moved and can be found here: 4 | -------------------------------------------------------------------------------- /docs/builds/202654506-8a4ac4ef-c883-490e-8ee1-1e1f7fa34972.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciLor/OpenDTU/master/docs/builds/202654506-8a4ac4ef-c883-490e-8ee1-1e1f7fa34972.jpg -------------------------------------------------------------------------------- /docs/DeviceProfiles.md: -------------------------------------------------------------------------------- 1 | # Device Profiles 2 | 3 | This documentation will has been moved and can be found here: 4 | -------------------------------------------------------------------------------- /webapp/src/scss/styles.scss: -------------------------------------------------------------------------------- 1 | // Import all of Bootstrap's CSS 2 | @import '~bootstrap/scss/bootstrap'; 3 | 4 | .container-fluid .row { 5 | font-feature-settings: 'tnum'; 6 | } 7 | -------------------------------------------------------------------------------- /webapp/src/types/Config.ts: -------------------------------------------------------------------------------- 1 | export interface ConfigFileInfo { 2 | name: string; 3 | } 4 | 5 | export interface ConfigFileList { 6 | configs: Array; 7 | } 8 | -------------------------------------------------------------------------------- /src/Scheduler.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2023 Thomas Basler and others 4 | */ 5 | #include "Scheduler.h" 6 | 7 | Scheduler scheduler; -------------------------------------------------------------------------------- /webapp/src/plugins/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import { Tooltip } from 'bootstrap'; 2 | 3 | export const tooltip = { 4 | mounted(el: HTMLElement) { 5 | new Tooltip(el); 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/Utils.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | 6 | class Utils { 7 | public: 8 | static uint8_t getWeekDay(); 9 | }; -------------------------------------------------------------------------------- /docs/UpgradePartition.md: -------------------------------------------------------------------------------- 1 | # Upgrade Partition 2 | 3 | This documentation will has been moved and can be found here: 4 | -------------------------------------------------------------------------------- /webapp/src/emitter.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@vue/runtime-core' { 2 | interface ComponentCustomProperties { 3 | $emitter: Emitter; 4 | } 5 | } 6 | 7 | export {}; // Important! See note. 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /webapp/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "semi": true, 4 | "tabWidth": 4, 5 | "singleQuote": true, 6 | "printWidth": 120, 7 | "trailingComma": "es5" 8 | } 9 | -------------------------------------------------------------------------------- /webapp/src/types/NtpConfig.ts: -------------------------------------------------------------------------------- 1 | export interface NtpConfig { 2 | ntp_server: string; 3 | ntp_timezone: string; 4 | ntp_timezone_descr: string; 5 | latitude: number; 6 | longitude: number; 7 | sunsettype: number; 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | .vscode/settings.json 7 | platformio-device-monitor*.log 8 | logs/device-monitor*.log 9 | platformio_override.ini 10 | .DS_Store 11 | -------------------------------------------------------------------------------- /webapp/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { Router, Route } from 'vue-router' 4 | declare module '@vue/runtime-core' { 5 | interface ComponentCustomProperties { 6 | $router: Router 7 | $route: Route 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /include/__compiled_constants.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | // The referenced values are generated by pio-scripts/auto_firmware_version.py 5 | 6 | 7 | extern const char *__COMPILED_GIT_HASH__; 8 | // extern const char *__COMPILED_DATE_TIME_UTC_STR__; 9 | -------------------------------------------------------------------------------- /include/NtpSettings.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | class NtpSettingsClass { 5 | public: 6 | NtpSettingsClass(); 7 | void init(); 8 | 9 | void setServer(); 10 | void setTimezone(); 11 | }; 12 | 13 | extern NtpSettingsClass NtpSettings; -------------------------------------------------------------------------------- /webapp/src/types/EventlogStatus.ts: -------------------------------------------------------------------------------- 1 | export interface EventlogItem { 2 | message_id: number; 3 | message: string; 4 | start_time: number; 5 | end_time: number; 6 | } 7 | 8 | export interface EventlogItems { 9 | count: number; 10 | events: Array; 11 | } 12 | -------------------------------------------------------------------------------- /partitions_custom_16mb.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | nvs, data, nvs, 0x9000, 0x5000 3 | otadata, data, ota, 0xE000, 0x2000 4 | app0, app, ota_0, 0x10000, 0x7E0000 5 | app1, app, ota_1, 0x7F0000, 0x7E0000 6 | spiffs, data, spiffs, 0xFD0000, 0x30000 7 | -------------------------------------------------------------------------------- /partitions_custom_4mb.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | nvs, data, nvs, 0x9000, 0x5000, 3 | otadata, data, ota, 0xe000, 0x2000, 4 | app0, app, ota_0, 0x10000, 0x1E0000, 5 | app1, app, ota_1, 0x1F0000, 0x1E0000, 6 | spiffs, data, spiffs, 0x3D0000, 0x30000, -------------------------------------------------------------------------------- /lib/CpuTemperature/src/CpuTemperature.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | 6 | class CpuTemperatureClass { 7 | public: 8 | float read(); 9 | 10 | private: 11 | std::mutex _mutex; 12 | }; 13 | 14 | extern CpuTemperatureClass CpuTemperature; 15 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/ParaSetCommand.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "CommandAbstract.h" 5 | 6 | class ParaSetCommand : public CommandAbstract { 7 | public: 8 | explicit ParaSetCommand(InverterAbstract* inv, const uint64_t router_address = 0); 9 | }; 10 | -------------------------------------------------------------------------------- /lib/ResetReason/src/ResetReason.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | 6 | class ResetReason { 7 | public: 8 | static String get_reset_reason_verbose(const uint8_t cpu_id); 9 | static String get_reset_reason_short(const uint8_t cpu_id); 10 | }; -------------------------------------------------------------------------------- /webapp/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { isLoggedIn, login, logout } from './authentication'; 2 | import { timestampToString } from './time'; 3 | 4 | export { timestampToString, login, logout, isLoggedIn }; 5 | 6 | export default { 7 | timestampToString, 8 | login, 9 | logout, 10 | isLoggedIn, 11 | }; 12 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/SingleDataCommand.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "CommandAbstract.h" 5 | 6 | class SingleDataCommand : public CommandAbstract { 7 | public: 8 | explicit SingleDataCommand(InverterAbstract* inv, const uint64_t router_address = 0); 9 | }; 10 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/HMS_Abstract.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "HM_Abstract.h" 5 | 6 | class HMS_Abstract : public HM_Abstract { 7 | public: 8 | explicit HMS_Abstract(HoymilesRadio* radio, const uint64_t serial); 9 | 10 | virtual bool sendChangeChannelRequest(); 11 | }; -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/HMT_Abstract.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "HM_Abstract.h" 5 | 6 | class HMT_Abstract : public HM_Abstract { 7 | public: 8 | explicit HMT_Abstract(HoymilesRadio* radio, const uint64_t serial); 9 | 10 | virtual bool sendChangeChannelRequest(); 11 | }; -------------------------------------------------------------------------------- /webapp/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "OpenDTU", 3 | "short_name": "OpenDTU", 4 | "display": "standalone", 5 | "orientation": "portrait", 6 | "icons": [ 7 | { 8 | "src": "/favicon.png", 9 | "sizes": "144x144", 10 | "type": "image/png" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /webapp_dist/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "OpenDTU", 3 | "short_name": "OpenDTU", 4 | "display": "standalone", 5 | "orientation": "portrait", 6 | "icons": [ 7 | { 8 | "src": "/favicon.png", 9 | "sizes": "144x144", 10 | "type": "image/png" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | OpenDTU - ESP32 firmware to control HoyMiles Inverter 2 | 3 | Copyright (C) 2022 Thomas Basler and others 4 | 5 | OpenDTU is provided under: 6 | 7 | SPDX-License-Identifier: GPL-2.0-or-later 8 | 9 | Being under the terms of the GNU General Public License version 2 10 | or any later version, according with: 11 | 12 | LICENSE -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/HERF_4CH.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "HM_4CH.h" 5 | 6 | class HERF_4CH : public HM_4CH { 7 | public: 8 | explicit HERF_4CH(HoymilesRadio* radio, const uint64_t serial); 9 | static bool isValidSerial(const uint64_t serial); 10 | String typeName() const; 11 | }; 12 | -------------------------------------------------------------------------------- /webapp/src/types/DevInfoStatus.ts: -------------------------------------------------------------------------------- 1 | export interface DevInfoStatus { 2 | serial: string; 3 | valid_data: boolean; 4 | fw_bootloader_version: number; 5 | fw_build_version: number; 6 | fw_build_datetime: Date; 7 | hw_part_number: number; 8 | hw_version: number; 9 | hw_model_name: string; 10 | max_power: number; 11 | } 12 | -------------------------------------------------------------------------------- /webapp/src/types/NetworkConfig.ts: -------------------------------------------------------------------------------- 1 | export interface NetworkConfig { 2 | ssid: string; 3 | password: string; 4 | hostname: string; 5 | dhcp: boolean; 6 | ipaddress: string; 7 | netmask: string; 8 | gateway: string; 9 | dns1: string; 10 | dns2: string; 11 | aptimeout: number; 12 | mdnsenabled: boolean; 13 | } 14 | -------------------------------------------------------------------------------- /webapp/src/types/NtpStatus.ts: -------------------------------------------------------------------------------- 1 | export interface NtpStatus { 2 | ntp_server: string; 3 | ntp_timezone: string; 4 | ntp_timezone_descr: string; 5 | ntp_status: boolean; 6 | ntp_localtime: string; 7 | sun_risetime: string; 8 | sun_settime: string; 9 | sun_isDayPeriod: boolean; 10 | sun_isSunsetAvailable: boolean; 11 | } 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 🤔 Have questions or need support? 4 | url: https://discord.gg/WzhxEY62mB 5 | about: Discuss with us on Discord 6 | - name: 🤔 Have questions or need support? 7 | url: https://github.com/tbnobody/OpenDTU/discussions 8 | about: Use the GitHub Discussions feature -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for npm 4 | - package-ecosystem: "npm" 5 | directory: "/webapp" 6 | schedule: 7 | interval: "weekly" 8 | 9 | # Maintain dependencies for GitHub Actions 10 | - package-ecosystem: "github-actions" 11 | directory: "/" 12 | schedule: 13 | interval: "daily" -------------------------------------------------------------------------------- /lib/Hoymiles/src/Utils.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2023 Thomas Basler and others 4 | */ 5 | #include "Utils.h" 6 | #include 7 | 8 | uint8_t Utils::getWeekDay() 9 | { 10 | time_t raw; 11 | struct tm info; 12 | time(&raw); 13 | localtime_r(&raw, &info); 14 | return info.tm_mday; 15 | } -------------------------------------------------------------------------------- /lib/TimeoutHelper/library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TimeoutHelper", 3 | "keywords": "timeout", 4 | "description": "An Arduino for ESP32 timeout helper", 5 | "authors": { 6 | "name": "Thomas Basler" 7 | }, 8 | "version": "0.0.1", 9 | "frameworks": "arduino", 10 | "platforms": [ 11 | "espressif32" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Documents - Table of content 2 | 3 | More detailed descriptions for some topics can be found here. 4 | 5 | ## [Display Documentation](Display.md) 6 | 7 | ## [MQTT Topic Documentation](MQTT_Topics.md) 8 | 9 | ## [Web API Documentation](Web-API.md) 10 | 11 | ## [Device Profile Documentation](DeviceProfiles.md) 12 | 13 | ## [Builds](builds/README.md) -------------------------------------------------------------------------------- /include/WebApi_devinfo.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class WebApiDevInfoClass { 8 | public: 9 | void init(AsyncWebServer& server, Scheduler& scheduler); 10 | 11 | private: 12 | void onDevInfoStatus(AsyncWebServerRequest* request); 13 | }; 14 | -------------------------------------------------------------------------------- /include/WebApi_eventlog.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class WebApiEventlogClass { 8 | public: 9 | void init(AsyncWebServer& server, Scheduler& scheduler); 10 | 11 | private: 12 | void onEventlogStatus(AsyncWebServerRequest* request); 13 | }; 14 | -------------------------------------------------------------------------------- /include/WebApi_maintenance.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class WebApiMaintenanceClass { 8 | public: 9 | void init(AsyncWebServer& server, Scheduler& scheduler); 10 | 11 | private: 12 | void onRebootPost(AsyncWebServerRequest* request); 13 | }; 14 | -------------------------------------------------------------------------------- /include/WebApi_sysstatus.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class WebApiSysstatusClass { 8 | public: 9 | void init(AsyncWebServer& server, Scheduler& scheduler); 10 | 11 | private: 12 | void onSystemStatus(AsyncWebServerRequest* request); 13 | }; 14 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/ParaSetCommand.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2022-2024 Thomas Basler and others 4 | */ 5 | #include "ParaSetCommand.h" 6 | 7 | ParaSetCommand::ParaSetCommand(InverterAbstract* inv, const uint64_t router_address) 8 | : CommandAbstract(inv, router_address) 9 | { 10 | _payload[0] = 0x52; 11 | } 12 | -------------------------------------------------------------------------------- /lib/ThreadSafeQueue/library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ThreadSafeQueue", 3 | "keywords": "queue, threadsafe", 4 | "description": "An Arduino for ESP32 thread safe queue implementation", 5 | "authors": { 6 | "name": "Thomas Basler" 7 | }, 8 | "version": "0.0.1", 9 | "frameworks": "arduino", 10 | "platforms": [ 11 | "espressif32" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /webapp/src/types/GridProfileStatus.ts: -------------------------------------------------------------------------------- 1 | export interface GridProfileValue { 2 | n: string; 3 | u: string; 4 | v: number; 5 | } 6 | 7 | export interface GridProfileSection { 8 | name: string; 9 | items: Array; 10 | } 11 | 12 | export interface GridProfileStatus { 13 | name: string; 14 | version: string; 15 | sections: Array; 16 | } 17 | -------------------------------------------------------------------------------- /include/MqttHandleDtu.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class MqttHandleDtuClass { 8 | public: 9 | MqttHandleDtuClass(); 10 | void init(Scheduler& scheduler); 11 | 12 | private: 13 | void loop(); 14 | 15 | Task _loopTask; 16 | }; 17 | 18 | extern MqttHandleDtuClass MqttHandleDtu; 19 | -------------------------------------------------------------------------------- /lib/TimeoutHelper/src/TimeoutHelper.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | 6 | class TimeoutHelper { 7 | public: 8 | TimeoutHelper(); 9 | void set(const uint32_t ms); 10 | void extend(const uint32_t ms); 11 | void reset(); 12 | bool occured() const; 13 | 14 | private: 15 | uint32_t startMillis; 16 | uint32_t timeout; 17 | }; -------------------------------------------------------------------------------- /docs/DeviceProfiles/wemos-lolin32-oled.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Wemos Lolin32 OLED", 4 | "nrf24": { 5 | "miso": 2, 6 | "mosi": 14, 7 | "clk": 12, 8 | "irq": 0, 9 | "en": 15, 10 | "cs": 13 11 | }, 12 | "eth": { 13 | "enabled": false 14 | }, 15 | "display": { 16 | "type": 2, 17 | "data": 5, 18 | "clk": 4 19 | } 20 | } 21 | ] 22 | -------------------------------------------------------------------------------- /include/WebApi_limit.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class WebApiLimitClass { 8 | public: 9 | void init(AsyncWebServer& server, Scheduler& scheduler); 10 | 11 | private: 12 | void onLimitStatus(AsyncWebServerRequest* request); 13 | void onLimitPost(AsyncWebServerRequest* request); 14 | }; 15 | -------------------------------------------------------------------------------- /include/WebApi_power.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class WebApiPowerClass { 8 | public: 9 | void init(AsyncWebServer& server, Scheduler& scheduler); 10 | 11 | private: 12 | void onPowerStatus(AsyncWebServerRequest* request); 13 | void onPowerPost(AsyncWebServerRequest* request); 14 | }; 15 | -------------------------------------------------------------------------------- /webapp/src/components/FormFooter.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /include/MqttHandleInverterTotal.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | 6 | class MqttHandleInverterTotalClass { 7 | public: 8 | MqttHandleInverterTotalClass(); 9 | void init(Scheduler& scheduler); 10 | 11 | private: 12 | void loop(); 13 | 14 | Task _loopTask; 15 | }; 16 | 17 | extern MqttHandleInverterTotalClass MqttHandleInverterTotal; 18 | -------------------------------------------------------------------------------- /include/WebApi_device.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class WebApiDeviceClass { 8 | public: 9 | void init(AsyncWebServer& server, Scheduler& scheduler); 10 | 11 | private: 12 | void onDeviceAdminGet(AsyncWebServerRequest* request); 13 | void onDeviceAdminPost(AsyncWebServerRequest* request); 14 | }; 15 | -------------------------------------------------------------------------------- /webapp/tsconfig.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@tsconfig/node18/tsconfig.json", 4 | "@vue/tsconfig/tsconfig.json" 5 | ], 6 | "include": [ 7 | "vite.config.*", 8 | "vitest.config.*", 9 | "cypress.config.*" 10 | ], 11 | "compilerOptions": { 12 | "noEmit": false, 13 | "composite": true, 14 | "types": [ 15 | "node" 16 | ] 17 | } 18 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "DavidAnson.vscode-markdownlint", 6 | "EditorConfig.EditorConfig", 7 | "Vue.volar", 8 | "platformio.platformio-ide" 9 | ], 10 | "unwantedRecommendations": [ 11 | "ms-vscode.cpptools-extension-pack" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /include/WebApi_ws_console.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class WebApiWsConsoleClass { 8 | public: 9 | WebApiWsConsoleClass(); 10 | void init(AsyncWebServer& server, Scheduler& scheduler); 11 | 12 | private: 13 | AsyncWebSocket _ws; 14 | 15 | Task _wsCleanupTask; 16 | void wsCleanupTaskCb(); 17 | }; 18 | -------------------------------------------------------------------------------- /include/WebApi_gridprofile.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class WebApiGridProfileClass { 8 | public: 9 | void init(AsyncWebServer& server, Scheduler& scheduler); 10 | 11 | private: 12 | void onGridProfileStatus(AsyncWebServerRequest* request); 13 | void onGridProfileRawdata(AsyncWebServerRequest* request); 14 | }; 15 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/HM_2CH.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "HM_Abstract.h" 5 | 6 | class HM_2CH : public HM_Abstract { 7 | public: 8 | explicit HM_2CH(HoymilesRadio* radio, const uint64_t serial); 9 | static bool isValidSerial(const uint64_t serial); 10 | String typeName() const; 11 | const byteAssign_t* getByteAssignment() const; 12 | uint8_t getByteAssignmentSize() const; 13 | }; -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/HM_4CH.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "HM_Abstract.h" 5 | 6 | class HM_4CH : public HM_Abstract { 7 | public: 8 | explicit HM_4CH(HoymilesRadio* radio, const uint64_t serial); 9 | static bool isValidSerial(const uint64_t serial); 10 | String typeName() const; 11 | const byteAssign_t* getByteAssignment() const; 12 | uint8_t getByteAssignmentSize() const; 13 | }; -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/HMS_4CH.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "HMS_Abstract.h" 5 | 6 | class HMS_4CH : public HMS_Abstract { 7 | public: 8 | explicit HMS_4CH(HoymilesRadio* radio, const uint64_t serial); 9 | static bool isValidSerial(const uint64_t serial); 10 | String typeName() const; 11 | const byteAssign_t* getByteAssignment() const; 12 | uint8_t getByteAssignmentSize() const; 13 | }; -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/HMT_4CH.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "HMT_Abstract.h" 5 | 6 | class HMT_4CH : public HMT_Abstract { 7 | public: 8 | explicit HMT_4CH(HoymilesRadio* radio, const uint64_t serial); 9 | static bool isValidSerial(const uint64_t serial); 10 | String typeName() const; 11 | const byteAssign_t* getByteAssignment() const; 12 | uint8_t getByteAssignmentSize() const; 13 | }; -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/HMT_6CH.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "HMT_Abstract.h" 5 | 6 | class HMT_6CH : public HMT_Abstract { 7 | public: 8 | explicit HMT_6CH(HoymilesRadio* radio, const uint64_t serial); 9 | static bool isValidSerial(const uint64_t serial); 10 | String typeName() const; 11 | const byteAssign_t* getByteAssignment() const; 12 | uint8_t getByteAssignmentSize() const; 13 | }; -------------------------------------------------------------------------------- /webapp/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | vite.user.ts 17 | 18 | /cypress/videos/ 19 | /cypress/screenshots/ 20 | 21 | # Editor directories and files 22 | .vscode/* 23 | !.vscode/extensions.json 24 | .idea 25 | *.suo 26 | *.ntvs* 27 | *.njsproj 28 | *.sln 29 | *.sw? 30 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/HERF_2CH.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "HM_Abstract.h" 5 | 6 | class HERF_2CH : public HM_Abstract { 7 | public: 8 | explicit HERF_2CH(HoymilesRadio* radio, const uint64_t serial); 9 | static bool isValidSerial(const uint64_t serial); 10 | String typeName() const; 11 | const byteAssign_t* getByteAssignment() const; 12 | uint8_t getByteAssignmentSize() const; 13 | }; 14 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/HM_1CH.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "HM_Abstract.h" 5 | #include 6 | 7 | class HM_1CH : public HM_Abstract { 8 | public: 9 | explicit HM_1CH(HoymilesRadio* radio, const uint64_t serial); 10 | static bool isValidSerial(const uint64_t serial); 11 | String typeName() const; 12 | const byteAssign_t* getByteAssignment() const; 13 | uint8_t getByteAssignmentSize() const; 14 | }; -------------------------------------------------------------------------------- /include/Utils.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class Utils { 8 | public: 9 | static uint32_t getChipId(); 10 | static uint64_t generateDtuSerial(); 11 | static int getTimezoneOffset(); 12 | static void restartDtu(); 13 | static bool checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line); 14 | static void removeAllFiles(); 15 | }; 16 | -------------------------------------------------------------------------------- /include/WebApi_webapp.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class WebApiWebappClass { 8 | public: 9 | void init(AsyncWebServer& server, Scheduler& scheduler); 10 | 11 | private: 12 | void responseBinaryDataWithETagCache(AsyncWebServerRequest* request, const String &contentType, const String &contentEncoding, const uint8_t *content, size_t len); 13 | }; 14 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/HMS_1CH.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "HMS_Abstract.h" 5 | #include 6 | 7 | class HMS_1CH : public HMS_Abstract { 8 | public: 9 | explicit HMS_1CH(HoymilesRadio* radio, const uint64_t serial); 10 | static bool isValidSerial(const uint64_t serial); 11 | String typeName() const; 12 | const byteAssign_t* getByteAssignment() const; 13 | uint8_t getByteAssignmentSize() const; 14 | }; -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/HMS_2CH.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "HMS_Abstract.h" 5 | #include 6 | 7 | class HMS_2CH : public HMS_Abstract { 8 | public: 9 | explicit HMS_2CH(HoymilesRadio* radio, const uint64_t serial); 10 | static bool isValidSerial(const uint64_t serial); 11 | String typeName() const; 12 | const byteAssign_t* getByteAssignment() const; 13 | uint8_t getByteAssignmentSize() const; 14 | }; -------------------------------------------------------------------------------- /lib/Frozen/frozen/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(frozen-headers INTERFACE 2 | "${prefix}/frozen/algorithm.h" 3 | "${prefix}/frozen/map.h" 4 | "${prefix}/frozen/random.h" 5 | "${prefix}/frozen/set.h" 6 | "${prefix}/frozen/string.h" 7 | "${prefix}/frozen/unordered_map.h" 8 | "${prefix}/frozen/unordered_set.h" 9 | "${prefix}/frozen/bits/algorithms.h" 10 | "${prefix}/frozen/bits/basic_types.h" 11 | "${prefix}/frozen/bits/elsa.h" 12 | "${prefix}/frozen/bits/pmh.h") 13 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/DevControlCommand.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "CommandAbstract.h" 5 | 6 | class DevControlCommand : public CommandAbstract { 7 | public: 8 | explicit DevControlCommand(InverterAbstract* inv, const uint64_t router_address = 0); 9 | 10 | virtual bool handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id); 11 | 12 | protected: 13 | void udpateCRC(const uint8_t len); 14 | }; 15 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/HMS_1CHv2.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "HMS_Abstract.h" 5 | #include 6 | 7 | class HMS_1CHv2 : public HMS_Abstract { 8 | public: 9 | explicit HMS_1CHv2(HoymilesRadio* radio, const uint64_t serial); 10 | static bool isValidSerial(const uint64_t serial); 11 | String typeName() const; 12 | const byteAssign_t* getByteAssignment() const; 13 | uint8_t getByteAssignmentSize() const; 14 | }; -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/README.md: -------------------------------------------------------------------------------- 1 | # Class hierarchy 2 | 3 | * CommandAbstract 4 | * DevControlCommand 5 | * ActivePowerControlCommand 6 | * PowerControlCommand 7 | * MultiDataCommand 8 | * AlarmDataCommand 9 | * DevInfoAllCommand 10 | * DevInfoSimpleCommand 11 | * GridOnProFilePara 12 | * RealTimeRunDataCommand 13 | * SystemConfigParaCommand 14 | * ParaSetCommand 15 | * SingleDataCommand 16 | * RequestFrameCommand 17 | * ChannelChangeCommand 18 | -------------------------------------------------------------------------------- /include/WebApi_network.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class WebApiNetworkClass { 8 | public: 9 | void init(AsyncWebServer& server, Scheduler& scheduler); 10 | 11 | private: 12 | void onNetworkStatus(AsyncWebServerRequest* request); 13 | void onNetworkAdminGet(AsyncWebServerRequest* request); 14 | void onNetworkAdminPost(AsyncWebServerRequest* request); 15 | }; 16 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/DevInfoAllCommand.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "MultiDataCommand.h" 5 | 6 | class DevInfoAllCommand : public MultiDataCommand { 7 | public: 8 | explicit DevInfoAllCommand(InverterAbstract* inv, const uint64_t router_address = 0, const time_t time = 0); 9 | 10 | virtual String getCommandName() const; 11 | 12 | virtual bool handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id); 13 | }; 14 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/GridOnProFilePara.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "MultiDataCommand.h" 5 | 6 | class GridOnProFilePara : public MultiDataCommand { 7 | public: 8 | explicit GridOnProFilePara(InverterAbstract* inv, const uint64_t router_address = 0, const time_t time = 0); 9 | 10 | virtual String getCommandName() const; 11 | 12 | virtual bool handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id); 13 | }; 14 | -------------------------------------------------------------------------------- /webapp/src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 17 | 18 | 25 | -------------------------------------------------------------------------------- /include/WebApi_security.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class WebApiSecurityClass { 8 | public: 9 | void init(AsyncWebServer& server, Scheduler& scheduler); 10 | 11 | private: 12 | void onSecurityGet(AsyncWebServerRequest* request); 13 | void onSecurityPost(AsyncWebServerRequest* request); 14 | 15 | void onAuthenticateGet(AsyncWebServerRequest* request); 16 | }; 17 | -------------------------------------------------------------------------------- /webapp/src/views/ErrorView.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/DevInfoSimpleCommand.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "MultiDataCommand.h" 5 | 6 | class DevInfoSimpleCommand : public MultiDataCommand { 7 | public: 8 | explicit DevInfoSimpleCommand(InverterAbstract* inv, const uint64_t router_address = 0, const time_t time = 0); 9 | 10 | virtual String getCommandName() const; 11 | 12 | virtual bool handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id); 13 | }; 14 | -------------------------------------------------------------------------------- /webapp/src/types/DeviceConfig.ts: -------------------------------------------------------------------------------- 1 | import type { Device } from './PinMapping'; 2 | 3 | export interface Display { 4 | rotation: number; 5 | power_safe: boolean; 6 | screensaver: boolean; 7 | contrast: number; 8 | language: number; 9 | diagramduration: number; 10 | diagrammode: number; 11 | } 12 | 13 | export interface Led { 14 | brightness: number; 15 | } 16 | 17 | export interface DeviceConfig { 18 | curPin: Device; 19 | display: Display; 20 | led: Array; 21 | } 22 | -------------------------------------------------------------------------------- /docs/DeviceProfiles/CASmo-DTU.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "CASmo-DTU", 4 | "links": [ 5 | {"name": "Information", "url": "https://casmo.info/product-details/?product=2"} 6 | ], 7 | "nrf24": { 8 | "miso": 19, 9 | "mosi": 23, 10 | "clk": 18, 11 | "irq": 16, 12 | "en": 4, 13 | "cs": 5 14 | }, 15 | "led": { 16 | "led0": 25, 17 | "led1": 26 18 | } 19 | } 20 | ] -------------------------------------------------------------------------------- /include/WebApi_dtu.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class WebApiDtuClass { 8 | public: 9 | WebApiDtuClass(); 10 | void init(AsyncWebServer& server, Scheduler& scheduler); 11 | 12 | private: 13 | void onDtuAdminGet(AsyncWebServerRequest* request); 14 | void onDtuAdminPost(AsyncWebServerRequest* request); 15 | 16 | Task _applyDataTask; 17 | void applyDataTaskCb(); 18 | }; 19 | -------------------------------------------------------------------------------- /include/WebApi_firmware.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class WebApiFirmwareClass { 8 | public: 9 | void init(AsyncWebServer& server, Scheduler& scheduler); 10 | 11 | private: 12 | void onFirmwareUpdateFinish(AsyncWebServerRequest* request); 13 | void onFirmwareUpdateUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final); 14 | }; 15 | -------------------------------------------------------------------------------- /lib/CMT2300a/cmt_spi3.h: -------------------------------------------------------------------------------- 1 | #ifndef __CMT_SPI3_H 2 | #define __CMT_SPI3_H 3 | 4 | #include 5 | 6 | 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 uint32_t spi_speed); 7 | 8 | void cmt_spi3_write(const uint8_t addr, const uint8_t dat); 9 | uint8_t cmt_spi3_read(const uint8_t addr); 10 | 11 | void cmt_spi3_write_fifo(const uint8_t* p_buf, const uint16_t len); 12 | void cmt_spi3_read_fifo(uint8_t* p_buf, const uint16_t len); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/types.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | 6 | union serial_u { 7 | uint64_t u64; 8 | uint8_t b[8]; 9 | }; 10 | 11 | // maximum buffer length of packet received / sent to RF24 module 12 | #define MAX_RF_PAYLOAD_SIZE 32 13 | 14 | typedef struct { 15 | uint8_t mainCmd; 16 | uint8_t fragment[MAX_RF_PAYLOAD_SIZE]; 17 | uint8_t len; 18 | uint8_t channel; 19 | int8_t rssi; 20 | bool wasReceived; 21 | } fragment_t; 22 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/AlarmDataCommand.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "MultiDataCommand.h" 5 | 6 | class AlarmDataCommand : public MultiDataCommand { 7 | public: 8 | explicit AlarmDataCommand(InverterAbstract* inv, const uint64_t router_address = 0, const time_t time = 0); 9 | 10 | virtual String getCommandName() const; 11 | 12 | virtual bool handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id); 13 | virtual void gotTimeout(); 14 | }; 15 | -------------------------------------------------------------------------------- /include/WebApi_mqtt.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class WebApiMqttClass { 8 | public: 9 | void init(AsyncWebServer& server, Scheduler& scheduler); 10 | 11 | private: 12 | void onMqttStatus(AsyncWebServerRequest* request); 13 | void onMqttAdminGet(AsyncWebServerRequest* request); 14 | void onMqttAdminPost(AsyncWebServerRequest* request); 15 | String getTlsCertInfo(const char* cert); 16 | }; 17 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/crc.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | 6 | #define CRC8_INIT 0x00 7 | #define CRC8_POLY 0x01 8 | 9 | #define CRC16_MODBUS_POLYNOM 0xA001 10 | #define CRC16_NRF24_POLYNOM 0x1021 11 | 12 | uint8_t crc8(const uint8_t buf[], const uint8_t len); 13 | uint16_t crc16(const uint8_t buf[], const uint8_t len, const uint16_t start = 0xffff); 14 | uint16_t crc16nrf24(const uint8_t buf[], const uint16_t lenBits, const uint16_t startBit = 0, const uint16_t crcIn = 0xffff); 15 | -------------------------------------------------------------------------------- /include/InverterSettings.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | #define INVERTER_UPDATE_SETTINGS_INTERVAL 60000l 8 | 9 | class InverterSettingsClass { 10 | public: 11 | InverterSettingsClass(); 12 | void init(Scheduler& scheduler); 13 | 14 | private: 15 | void settingsLoop(); 16 | void hoyLoop(); 17 | 18 | Task _settingsTask; 19 | Task _hoyTask; 20 | }; 21 | 22 | extern InverterSettingsClass InverterSettings; 23 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/RealTimeRunDataCommand.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "MultiDataCommand.h" 5 | 6 | class RealTimeRunDataCommand : public MultiDataCommand { 7 | public: 8 | explicit RealTimeRunDataCommand(InverterAbstract* inv, const uint64_t router_address = 0, const time_t time = 0); 9 | 10 | virtual String getCommandName() const; 11 | 12 | virtual bool handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id); 13 | virtual void gotTimeout(); 14 | }; 15 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/SystemConfigParaCommand.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "MultiDataCommand.h" 5 | 6 | class SystemConfigParaCommand : public MultiDataCommand { 7 | public: 8 | explicit SystemConfigParaCommand(InverterAbstract* inv, const uint64_t router_address = 0, const time_t time = 0); 9 | 10 | virtual String getCommandName() const; 11 | 12 | virtual bool handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id); 13 | virtual void gotTimeout(); 14 | }; 15 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /webapp/src/types/DtuConfig.ts: -------------------------------------------------------------------------------- 1 | export interface CountryDef { 2 | freq_default: number; 3 | freq_min: number; 4 | freq_max: number; 5 | freq_legal_min: number; 6 | freq_legal_max: number; 7 | } 8 | 9 | export interface DtuConfig { 10 | serial: number; 11 | pollinterval: number; 12 | nrf_enabled: boolean; 13 | nrf_palevel: number; 14 | cmt_enabled: boolean; 15 | cmt_palevel: number; 16 | cmt_frequency: number; 17 | cmt_country: number; 18 | country_def: Array; 19 | cmt_chan_width: number; 20 | } 21 | -------------------------------------------------------------------------------- /lib/Hoymiles/library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Hoymiles", 3 | "keywords": "solar, inverter", 4 | "description": "An Arduino for ESP32 solar inverter implementation", 5 | "authors": { 6 | "name": "Thomas Basler" 7 | }, 8 | "version": "0.0.1", 9 | "frameworks": "arduino", 10 | "platforms": [ 11 | "espressif32" 12 | ], 13 | "dependencies": [ 14 | { 15 | "owner": "nrf24", 16 | "name": "RF24", 17 | "version": ">=1.4.2", 18 | "platforms": "espressif32" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/PowerControlCommand.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "DevControlCommand.h" 5 | 6 | class PowerControlCommand : public DevControlCommand { 7 | public: 8 | explicit PowerControlCommand(InverterAbstract* inv, const uint64_t router_address = 0); 9 | 10 | virtual String getCommandName() const; 11 | 12 | virtual bool handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id); 13 | virtual void gotTimeout(); 14 | 15 | void setPowerOn(const bool state); 16 | void setRestart(); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/RequestFrameCommand.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "SingleDataCommand.h" 5 | 6 | class RequestFrameCommand : public SingleDataCommand { 7 | public: 8 | explicit RequestFrameCommand(InverterAbstract* inv, const uint64_t router_address = 0, uint8_t frame_no = 0); 9 | 10 | virtual String getCommandName() const; 11 | 12 | void setFrameNo(const uint8_t frame_no); 13 | uint8_t getFrameNo() const; 14 | 15 | virtual bool handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id); 16 | }; 17 | -------------------------------------------------------------------------------- /webapp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "paths": { 7 | "@/*": ["./src/*"] 8 | }, 9 | "lib": ["ES2021", "DOM"], 10 | "moduleResolution": "Node", 11 | 12 | /* Linting */ 13 | "strict": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noFallthroughCasesInSwitch": true 17 | }, 18 | 19 | "references": [ 20 | { 21 | "path": "./tsconfig.config.json" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/yarnlint.yml: -------------------------------------------------------------------------------- 1 | name: Yarn Linting 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v3 11 | - name: Setup Node.js and yarn 12 | uses: actions/setup-node@v3 13 | with: 14 | node-version: "18" 15 | cache: "yarn" 16 | cache-dependency-path: "webapp/yarn.lock" 17 | 18 | - name: Install WebApp dependencies 19 | run: yarn --cwd webapp install --frozen-lockfile 20 | 21 | - name: Linting 22 | run: yarn --cwd webapp lint -------------------------------------------------------------------------------- /include/WebApi_ntp.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class WebApiNtpClass { 8 | public: 9 | void init(AsyncWebServer& server, Scheduler& scheduler); 10 | 11 | private: 12 | void onNtpStatus(AsyncWebServerRequest* request); 13 | void onNtpAdminGet(AsyncWebServerRequest* request); 14 | void onNtpAdminPost(AsyncWebServerRequest* request); 15 | void onNtpTimeGet(AsyncWebServerRequest* request); 16 | void onNtpTimePost(AsyncWebServerRequest* request); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/HERF_4CH.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2022-2024 Thomas Basler and others 4 | */ 5 | #include "HERF_4CH.h" 6 | 7 | HERF_4CH::HERF_4CH(HoymilesRadio* radio, const uint64_t serial) 8 | : HM_4CH(radio, serial) {}; 9 | 10 | bool HERF_4CH::isValidSerial(const uint64_t serial) 11 | { 12 | // serial >= 0x280100000000 && serial <= 0x2801ffffffff 13 | uint16_t preSerial = (serial >> 32) & 0xffff; 14 | return preSerial == 0x2801; 15 | } 16 | 17 | String HERF_4CH::typeName() const 18 | { 19 | return "HERF-1600/1800-4T"; 20 | } 21 | -------------------------------------------------------------------------------- /include/WebApi_inverter.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class WebApiInverterClass { 8 | public: 9 | void init(AsyncWebServer& server, Scheduler& scheduler); 10 | 11 | private: 12 | void onInverterList(AsyncWebServerRequest* request); 13 | void onInverterAdd(AsyncWebServerRequest* request); 14 | void onInverterEdit(AsyncWebServerRequest* request); 15 | void onInverterDelete(AsyncWebServerRequest* request); 16 | void onInverterOrder(AsyncWebServerRequest* request); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/parser/PowerCommandParser.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | #include "Parser.h" 4 | 5 | class PowerCommandParser : public Parser { 6 | public: 7 | void setLastPowerCommandSuccess(const LastCommandSuccess status); 8 | LastCommandSuccess getLastPowerCommandSuccess() const; 9 | uint32_t getLastUpdateCommand() const; 10 | void setLastUpdateCommand(const uint32_t lastUpdate); 11 | 12 | private: 13 | LastCommandSuccess _lastLimitCommandSuccess = CMD_OK; // Set to OK because we have to assume nothing is done at startup 14 | 15 | uint32_t _lastUpdateCommand = 0; 16 | }; -------------------------------------------------------------------------------- /webapp/src/components/CardElement.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 22 | -------------------------------------------------------------------------------- /webapp/src/types/InverterConfig.ts: -------------------------------------------------------------------------------- 1 | export interface InverterChannel { 2 | name: string; 3 | max_power: number; 4 | yield_total_offset: number; 5 | } 6 | 7 | export interface Inverter { 8 | id: string; 9 | serial: string; 10 | name: string; 11 | type: string; 12 | order: number; 13 | poll_enable: boolean; 14 | poll_enable_night: boolean; 15 | command_enable: boolean; 16 | command_enable_night: boolean; 17 | reachable_threshold: number; 18 | zero_runtime: boolean; 19 | zero_day: boolean; 20 | clear_eventlog: boolean; 21 | yieldday_correction: boolean; 22 | channel: Array; 23 | } 24 | -------------------------------------------------------------------------------- /webapp/src/types/NetworkStatus.ts: -------------------------------------------------------------------------------- 1 | export interface NetworkStatus { 2 | // WifiStationInfo 3 | sta_status: boolean; 4 | sta_ssid: string; 5 | sta_bssid: string; 6 | sta_rssi: number; 7 | // WifiApInfo 8 | ap_status: boolean; 9 | ap_ssid: string; 10 | ap_stationnum: number; 11 | // InterfaceNetworkInfo 12 | network_hostname: string; 13 | network_ip: string; 14 | network_netmask: string; 15 | network_gateway: string; 16 | network_dns1: string; 17 | network_dns2: string; 18 | network_mac: string; 19 | network_mode: string; 20 | // InterfaceApInfo 21 | ap_ip: string; 22 | ap_mac: string; 23 | } 24 | -------------------------------------------------------------------------------- /include/WebApi_config.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class WebApiConfigClass { 8 | public: 9 | void init(AsyncWebServer& server, Scheduler& scheduler); 10 | 11 | private: 12 | void onConfigGet(AsyncWebServerRequest* request); 13 | void onConfigDelete(AsyncWebServerRequest* request); 14 | void onConfigListGet(AsyncWebServerRequest* request); 15 | void onConfigUploadFinish(AsyncWebServerRequest* request); 16 | void onConfigUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final); 17 | }; 18 | -------------------------------------------------------------------------------- /docs/DeviceProfiles/esp32_stick_poe_a.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Esp32-Stick-PoE-A", 4 | "links": [ 5 | {"name": "Information", "url": "https://github.com/allexoK/Esp32-Stick-Boards-Docs"} 6 | ], 7 | "nrf24": { 8 | "miso": 2, 9 | "mosi": 15, 10 | "clk": 14, 11 | "irq": 34, 12 | "en": 12, 13 | "cs": 4 14 | }, 15 | "eth": { 16 | "enabled": true, 17 | "phy_addr": 1, 18 | "power": -1, 19 | "mdc": 23, 20 | "mdio": 18, 21 | "type": 0, 22 | "clk_mode": 3 23 | } 24 | } 25 | ] 26 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/parser/Parser.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2022 - 2023 Thomas Basler and others 4 | */ 5 | #include "Parser.h" 6 | 7 | Parser::Parser() 8 | { 9 | _xSemaphore = xSemaphoreCreateMutex(); 10 | HOY_SEMAPHORE_GIVE(); // release before first use 11 | } 12 | 13 | uint32_t Parser::getLastUpdate() const 14 | { 15 | return _lastUpdate; 16 | } 17 | 18 | void Parser::setLastUpdate(const uint32_t lastUpdate) 19 | { 20 | _lastUpdate = lastUpdate; 21 | } 22 | 23 | void Parser::beginAppendFragment() 24 | { 25 | HOY_SEMAPHORE_TAKE(); 26 | } 27 | 28 | void Parser::endAppendFragment() 29 | { 30 | HOY_SEMAPHORE_GIVE(); 31 | } -------------------------------------------------------------------------------- /src/NtpSettings.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2022 - 2023 Thomas Basler and others 4 | */ 5 | #include "NtpSettings.h" 6 | #include "Configuration.h" 7 | #include 8 | #include 9 | 10 | NtpSettingsClass::NtpSettingsClass() 11 | { 12 | } 13 | 14 | void NtpSettingsClass::init() 15 | { 16 | setServer(); 17 | setTimezone(); 18 | } 19 | 20 | void NtpSettingsClass::setServer() 21 | { 22 | configTime(0, 0, Configuration.get().Ntp.Server); 23 | } 24 | 25 | void NtpSettingsClass::setTimezone() 26 | { 27 | setenv("TZ", Configuration.get().Ntp.Timezone, 1); 28 | tzset(); 29 | } 30 | 31 | NtpSettingsClass NtpSettings; -------------------------------------------------------------------------------- /docs/DeviceProfiles/olimex_esp32_evb.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Olimex ESP32-EVB", 4 | "links": [ 5 | { "name": "Datasheet", "url": "https://www.olimex.com/Products/IoT/ESP32/ESP32-EVB/open-source-hardware" } 6 | ], 7 | "nrf24": { 8 | "miso": 15, 9 | "mosi": 2, 10 | "clk": 14, 11 | "irq": 13, 12 | "en": 16, 13 | "cs": 17 14 | }, 15 | "eth": { 16 | "enabled": true, 17 | "phy_addr": 0, 18 | "power": 12, 19 | "mdc": 23, 20 | "mdio": 18, 21 | "type": 0, 22 | "clk_mode": 0 23 | } 24 | } 25 | ] -------------------------------------------------------------------------------- /lib/TimeoutHelper/src/TimeoutHelper.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2022 Thomas Basler and others 4 | */ 5 | #include "TimeoutHelper.h" 6 | #include 7 | 8 | TimeoutHelper::TimeoutHelper() 9 | { 10 | timeout = 0; 11 | startMillis = 0; 12 | } 13 | 14 | void TimeoutHelper::set(const uint32_t ms) 15 | { 16 | timeout = ms; 17 | startMillis = millis(); 18 | } 19 | 20 | void TimeoutHelper::extend(const uint32_t ms) 21 | { 22 | timeout += ms; 23 | } 24 | 25 | void TimeoutHelper::reset() 26 | { 27 | startMillis = millis(); 28 | } 29 | 30 | bool TimeoutHelper::occured() const 31 | { 32 | return millis() > (startMillis + timeout); 33 | } -------------------------------------------------------------------------------- /webapp/src/types/MqttStatus.ts: -------------------------------------------------------------------------------- 1 | export interface MqttStatus { 2 | mqtt_enabled: boolean; 3 | mqtt_hostname: string; 4 | mqtt_port: number; 5 | mqtt_clientid: string; 6 | mqtt_username: string; 7 | mqtt_topic: string; 8 | mqtt_publish_interval: number; 9 | mqtt_clean_session: boolean; 10 | mqtt_retain: boolean; 11 | mqtt_tls: boolean; 12 | mqtt_root_ca_cert_info: string; 13 | mqtt_tls_cert_login: boolean; 14 | mqtt_client_cert_info: string; 15 | mqtt_connected: boolean; 16 | mqtt_hass_enabled: boolean; 17 | mqtt_hass_expire: boolean; 18 | mqtt_hass_retain: boolean; 19 | mqtt_hass_topic: string; 20 | mqtt_hass_individualpanels: boolean; 21 | } 22 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/ChannelChangeCommand.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "CommandAbstract.h" 5 | #include "../HoymilesRadio_CMT.h" 6 | 7 | class ChannelChangeCommand : public CommandAbstract { 8 | public: 9 | explicit ChannelChangeCommand(InverterAbstract* inv, const uint64_t router_address = 0, const uint8_t channel = 0); 10 | 11 | virtual String getCommandName() const; 12 | 13 | void setChannel(const uint8_t channel); 14 | uint8_t getChannel() const; 15 | 16 | void setCountryMode(const CountryModeId_t mode); 17 | 18 | virtual bool handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id); 19 | 20 | virtual uint8_t getMaxResendCount(); 21 | }; 22 | -------------------------------------------------------------------------------- /.github/workflows/cpplint.yml: -------------------------------------------------------------------------------- 1 | name: cpplint 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v3 11 | - name: Set up Python 12 | uses: actions/setup-python@v5 13 | with: 14 | python-version: "3.x" 15 | - name: Install dependencies 16 | run: | 17 | python -m pip install --upgrade pip 18 | pip install cpplint 19 | - name: Linting 20 | run: | 21 | 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 22 | -------------------------------------------------------------------------------- /webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | OpenDTU 11 | 12 | 13 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/parser/PowerCommandParser.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2022 - 2023 Thomas Basler and others 4 | */ 5 | #include "PowerCommandParser.h" 6 | 7 | void PowerCommandParser::setLastPowerCommandSuccess(const LastCommandSuccess status) 8 | { 9 | _lastLimitCommandSuccess = status; 10 | } 11 | 12 | LastCommandSuccess PowerCommandParser::getLastPowerCommandSuccess() const 13 | { 14 | return _lastLimitCommandSuccess; 15 | } 16 | 17 | uint32_t PowerCommandParser::getLastUpdateCommand() const 18 | { 19 | return _lastUpdateCommand; 20 | } 21 | 22 | void PowerCommandParser::setLastUpdateCommand(const uint32_t lastUpdate) 23 | { 24 | _lastUpdateCommand = lastUpdate; 25 | setLastUpdate(lastUpdate); 26 | } -------------------------------------------------------------------------------- /webapp/src/utils/time.ts: -------------------------------------------------------------------------------- 1 | export function timestampToString(locale: string, timestampSeconds: number, includeDays: true): [number, string]; 2 | export function timestampToString(locale: string, timestampSeconds: number, includeDays?: false): [string]; 3 | export function timestampToString( 4 | locale: string, 5 | timestampSeconds: number, 6 | includeDays = false 7 | ): [number, string] | [string] { 8 | const timeString = new Date(timestampSeconds * 1000).toLocaleTimeString(locale, { 9 | timeZone: 'UTC', 10 | hour12: false, 11 | }); 12 | if (!includeDays) return [timeString]; 13 | 14 | const secondsPerDay = 60 * 60 * 24; 15 | const days = Math.floor(timestampSeconds / secondsPerDay); 16 | return [days, timeString]; 17 | } 18 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/parser/Parser.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | #include 4 | #include 5 | 6 | #define HOY_SEMAPHORE_TAKE() \ 7 | do { \ 8 | } while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS) 9 | #define HOY_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore) 10 | 11 | typedef enum { 12 | CMD_OK, 13 | CMD_NOK, 14 | CMD_PENDING 15 | } LastCommandSuccess; 16 | 17 | class Parser { 18 | public: 19 | Parser(); 20 | uint32_t getLastUpdate() const; 21 | void setLastUpdate(const uint32_t lastUpdate); 22 | 23 | void beginAppendFragment(); 24 | void endAppendFragment(); 25 | 26 | protected: 27 | SemaphoreHandle_t _xSemaphore; 28 | 29 | private: 30 | uint32_t _lastUpdate = 0; 31 | }; -------------------------------------------------------------------------------- /webapp/README.md: -------------------------------------------------------------------------------- 1 | # OpenDTU web frontend 2 | 3 | You can run the webapp locally with `yarn dev`. If you enter the IP of your ESP in the `vite.user.ts` beforehand (template can be found in `vite.config.ts`), all api requests will even be proxied to the real ESP. Then you can develop the webapp as if it were running directly on the ESP. The `yarn dev` also supports hot reload, i.e. as soon as you save a vue file, it is automatically reloaded in the browser. 4 | 5 | ## Project Setup 6 | 7 | ```sh 8 | yarn install 9 | ``` 10 | 11 | ### Compile and Hot-Reload for Development 12 | 13 | ```sh 14 | yarn dev 15 | ``` 16 | 17 | ### Type-Check, Compile and Minify for Production 18 | 19 | ```sh 20 | yarn build 21 | ``` 22 | 23 | ### Lint with [ESLint](https://eslint.org/) 24 | 25 | ```sh 26 | yarn lint 27 | ``` 28 | -------------------------------------------------------------------------------- /webapp/src/types/MqttConfig.ts: -------------------------------------------------------------------------------- 1 | export interface MqttConfig { 2 | mqtt_enabled: boolean; 3 | mqtt_hostname: string; 4 | mqtt_port: number; 5 | mqtt_clientid: string; 6 | mqtt_username: string; 7 | mqtt_password: string; 8 | mqtt_topic: string; 9 | mqtt_publish_interval: number; 10 | mqtt_clean_session: boolean; 11 | mqtt_retain: boolean; 12 | mqtt_tls: boolean; 13 | mqtt_root_ca_cert: string; 14 | mqtt_tls_cert_login: boolean; 15 | mqtt_client_cert: string; 16 | mqtt_client_key: string; 17 | mqtt_lwt_topic: string; 18 | mqtt_lwt_online: string; 19 | mqtt_lwt_offline: string; 20 | mqtt_lwt_qos: number; 21 | mqtt_hass_enabled: boolean; 22 | mqtt_hass_expire: boolean; 23 | mqtt_hass_retain: boolean; 24 | mqtt_hass_topic: string; 25 | mqtt_hass_individualpanels: boolean; 26 | } 27 | -------------------------------------------------------------------------------- /webapp/src/components/StatusBadge.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 33 | -------------------------------------------------------------------------------- /include/MessageOutput.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define BUFFER_SIZE 500 11 | 12 | class MessageOutputClass : public Print { 13 | public: 14 | MessageOutputClass(); 15 | void init(Scheduler& scheduler); 16 | size_t write(uint8_t c) override; 17 | size_t write(const uint8_t* buffer, size_t size) override; 18 | void register_ws_output(AsyncWebSocket* output); 19 | 20 | private: 21 | void loop(); 22 | 23 | Task _loopTask; 24 | 25 | AsyncWebSocket* _ws = nullptr; 26 | char _buffer[BUFFER_SIZE]; 27 | uint16_t _buff_pos = 0; 28 | uint32_t _lastSend = 0; 29 | bool _forceSend = false; 30 | 31 | std::mutex _msgLock; 32 | }; 33 | 34 | extern MessageOutputClass MessageOutput; 35 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/README.md: -------------------------------------------------------------------------------- 1 | # Class overview 2 | 3 | | Class | Models | Serial range | 4 | | --------------| --------------------------- | ------------ | 5 | | HM_1CH | HM-300/350/400-1T | 1121 | 6 | | HM_2CH | HM-600/700/800-2T | 1141 | 7 | | HM_4CH | HM-1000/1200/1500-4T | 1161 | 8 | | HMS_1CH | HMS-300/350/400/450/500-1T | 1124 | 9 | | HMS_1CHv2 | HMS-500-1T v2 | 1125 | 10 | | HMS_2CH | HMS-600/700/800/900/1000-2T | 1143, 1144 | 11 | | HMS_4CH | HMS-1600/1800/2000-4T | 1164 | 12 | | HMT_4CH | HMT-1600/1800/2000-4T | 1361 | 13 | | HMT_6CH | HMT-1800/2250-6T | 1382 | 14 | | HERF_2CH | HERF 800 | 2821 | 15 | | HERF_4CH | HERF 1800 | 2801 | 16 | -------------------------------------------------------------------------------- /include/Led_Single.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "PinMapping.h" 5 | #include 6 | #include 7 | 8 | #define LEDSINGLE_UPDATE_INTERVAL 2000 9 | 10 | class LedSingleClass { 11 | public: 12 | LedSingleClass(); 13 | void init(Scheduler& scheduler); 14 | 15 | void turnAllOff(); 16 | void turnAllOn(); 17 | 18 | private: 19 | void setLoop(); 20 | void outputLoop(); 21 | 22 | void setLed(const uint8_t ledNo, const bool ledState); 23 | 24 | Task _setTask; 25 | Task _outputTask; 26 | 27 | enum class LedState_t { 28 | On, 29 | Off, 30 | Blink, 31 | }; 32 | 33 | LedState_t _ledMode[PINMAPPING_LED_COUNT]; 34 | LedState_t _allMode; 35 | bool _ledStateCurrent[PINMAPPING_LED_COUNT]; 36 | TimeoutHelper _blinkTimeout; 37 | }; 38 | 39 | extern LedSingleClass LedSingle; -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/HMS_Abstract.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2023-2024 Thomas Basler and others 4 | */ 5 | #include "HMS_Abstract.h" 6 | #include "Hoymiles.h" 7 | #include "HoymilesRadio_CMT.h" 8 | #include "commands/ChannelChangeCommand.h" 9 | 10 | HMS_Abstract::HMS_Abstract(HoymilesRadio* radio, const uint64_t serial) 11 | : HM_Abstract(radio, serial) 12 | { 13 | } 14 | 15 | bool HMS_Abstract::sendChangeChannelRequest() 16 | { 17 | if (!(getEnableCommands() || getEnablePolling())) { 18 | return false; 19 | } 20 | 21 | auto cmdChannel = _radio->prepareCommand(this); 22 | cmdChannel->setCountryMode(Hoymiles.getRadioCmt()->getCountryMode()); 23 | cmdChannel->setChannel(Hoymiles.getRadioCmt()->getChannelFromFrequency(Hoymiles.getRadioCmt()->getInverterTargetFrequency())); 24 | _radio->enqueCommand(cmdChannel); 25 | 26 | return true; 27 | }; 28 | -------------------------------------------------------------------------------- /webapp/src/main.ts: -------------------------------------------------------------------------------- 1 | import messages from '@intlify/unplugin-vue-i18n/messages'; 2 | import mitt from 'mitt'; 3 | import { createApp } from 'vue'; 4 | import { createI18n } from 'vue-i18n'; 5 | import App from './App.vue'; 6 | import { dateTimeFormats, defaultLocale, numberFormats } from './locales'; 7 | import { tooltip } from './plugins/bootstrap'; 8 | import router from './router'; 9 | 10 | import 'bootstrap'; 11 | import './scss/styles.scss'; 12 | 13 | const app = createApp(App); 14 | 15 | const emitter = mitt(); 16 | app.config.globalProperties.$emitter = emitter; 17 | 18 | app.directive('tooltip', tooltip); 19 | 20 | const i18n = createI18n({ 21 | legacy: false, 22 | globalInjection: true, 23 | locale: navigator.language.split('-')[0], 24 | fallbackLocale: defaultLocale, 25 | messages, 26 | datetimeFormats: dateTimeFormats, 27 | numberFormats: numberFormats, 28 | }); 29 | 30 | app.use(router); 31 | app.use(i18n); 32 | 33 | app.mount('#app'); 34 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/MultiDataCommand.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "CommandAbstract.h" 5 | #include "RequestFrameCommand.h" 6 | #include 7 | 8 | class MultiDataCommand : public CommandAbstract { 9 | public: 10 | explicit MultiDataCommand(InverterAbstract* inv, const uint64_t router_address = 0, const uint8_t data_type = 0, const time_t time = 0); 11 | 12 | void setTime(const time_t time); 13 | time_t getTime() const; 14 | 15 | CommandAbstract* getRequestFrameCommand(const uint8_t frame_no); 16 | 17 | virtual bool handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id); 18 | 19 | protected: 20 | void setDataType(const uint8_t data_type); 21 | uint8_t getDataType() const; 22 | void udpateCRC(); 23 | static uint8_t getTotalFragmentSize(const fragment_t fragment[], const uint8_t max_fragment_id); 24 | 25 | RequestFrameCommand _cmdRequestFrame; 26 | }; 27 | -------------------------------------------------------------------------------- /lib/Frozen/frozen/bits/hash_string.h: -------------------------------------------------------------------------------- 1 | #ifndef FROZEN_LETITGO_BITS_HASH_STRING_H 2 | #define FROZEN_LETITGO_BITS_HASH_STRING_H 3 | 4 | #include 5 | 6 | namespace frozen { 7 | 8 | template 9 | constexpr std::size_t hash_string(const String& value) { 10 | std::size_t d = 5381; 11 | for (const auto& c : value) 12 | d = d * 33 + static_cast(c); 13 | return d; 14 | } 15 | 16 | // https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function 17 | // With the lowest bits removed, based on experimental setup. 18 | template 19 | constexpr std::size_t hash_string(const String& value, std::size_t seed) { 20 | std::size_t d = (0x811c9dc5 ^ seed) * static_cast(0x01000193); 21 | for (const auto& c : value) 22 | d = (d ^ static_cast(c)) * static_cast(0x01000193); 23 | return d >> 8 ; 24 | } 25 | 26 | } // namespace frozen 27 | 28 | #endif // FROZEN_LETITGO_BITS_HASH_STRING_H -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/ActivePowerControlCommand.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "DevControlCommand.h" 5 | 6 | typedef enum { // ToDo: to be verified by field tests 7 | AbsolutNonPersistent = 0x0000, // 0 8 | RelativNonPersistent = 0x0001, // 1 9 | AbsolutPersistent = 0x0100, // 256 10 | RelativPersistent = 0x0101 // 257 11 | } PowerLimitControlType; 12 | 13 | class ActivePowerControlCommand : public DevControlCommand { 14 | public: 15 | explicit ActivePowerControlCommand(InverterAbstract* inv, const uint64_t router_address = 0); 16 | 17 | virtual String getCommandName() const; 18 | 19 | virtual bool handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id); 20 | virtual void gotTimeout(); 21 | 22 | void setActivePowerLimit(const float limit, const PowerLimitControlType type = RelativNonPersistent); 23 | float getLimit() const; 24 | PowerLimitControlType getType(); 25 | }; 26 | -------------------------------------------------------------------------------- /include/Display_Graphic_Diagram.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define MAX_DATAPOINTS 128 9 | 10 | class DisplayGraphicDiagramClass { 11 | public: 12 | DisplayGraphicDiagramClass(); 13 | 14 | void init(Scheduler& scheduler, U8G2* display); 15 | void redraw(uint8_t screenSaverOffsetX, uint8_t xPos, uint8_t yPos, uint8_t width, uint8_t height, bool isFullscreen); 16 | 17 | void updatePeriod(); 18 | 19 | private: 20 | void averageLoop(); 21 | void dataPointLoop(); 22 | 23 | uint32_t getSecondsPerDot(); 24 | 25 | Task _averageTask; 26 | Task _dataPointTask; 27 | 28 | U8G2* _display = nullptr; 29 | std::array _graphValues = {}; 30 | uint8_t _graphValuesCount = 0; 31 | 32 | uint8_t _chartWidth = MAX_DATAPOINTS; 33 | 34 | float _iRunningAverage = 0; 35 | uint16_t _iRunningAverageCnt = 0; 36 | }; 37 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/HM_Abstract.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "InverterAbstract.h" 5 | 6 | class HM_Abstract : public InverterAbstract { 7 | public: 8 | explicit HM_Abstract(HoymilesRadio* radio, const uint64_t serial); 9 | bool sendStatsRequest(); 10 | bool sendAlarmLogRequest(const bool force = false); 11 | bool sendDevInfoRequest(); 12 | bool sendSystemConfigParaRequest(); 13 | bool sendActivePowerControlRequest(float limit, const PowerLimitControlType type); 14 | bool resendActivePowerControlRequest(); 15 | bool sendPowerControlRequest(const bool turnOn); 16 | bool sendRestartControlRequest(); 17 | bool resendPowerControlRequest(); 18 | bool sendGridOnProFileParaRequest(); 19 | 20 | private: 21 | uint8_t _lastAlarmLogCnt = 0; 22 | float _activePowerControlLimit = 0; 23 | PowerLimitControlType _activePowerControlType = PowerLimitControlType::AbsolutNonPersistent; 24 | 25 | uint8_t _powerState = 1; 26 | }; -------------------------------------------------------------------------------- /webapp/eslint.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | import path from "node:path"; 3 | import { fileURLToPath } from "node:url"; 4 | 5 | import { FlatCompat } from "@eslint/eslintrc"; 6 | import js from "@eslint/js"; 7 | import pluginVue from 'eslint-plugin-vue' 8 | 9 | const __filename = fileURLToPath(import.meta.url); 10 | const __dirname = path.dirname(__filename); 11 | const compat = new FlatCompat({ 12 | baseDirectory: __dirname, 13 | recommendedConfig: js.configs.recommended, 14 | }); 15 | 16 | export default [ 17 | js.configs.recommended, 18 | ...pluginVue.configs['flat/essential'], 19 | ...compat.extends("@vue/eslint-config-typescript/recommended"), 20 | { 21 | files: [ 22 | "**/*.vue", 23 | "**/*.js", 24 | "**/*.jsx", 25 | "**/*.cjs", 26 | "**/*.mjs", 27 | "**/*.ts", 28 | "**/*.tsx", 29 | "**/*.cts", 30 | "**/*.mts", 31 | ], 32 | languageOptions: { 33 | ecmaVersion: 2022 34 | }, 35 | } 36 | ] 37 | -------------------------------------------------------------------------------- /include/SunPosition.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class SunPositionClass { 9 | public: 10 | SunPositionClass(); 11 | void init(Scheduler& scheduler); 12 | 13 | bool isDayPeriod() const; 14 | bool isSunsetAvailable() const; 15 | bool sunsetTime(struct tm* info) const; 16 | bool sunriseTime(struct tm* info) const; 17 | void setDoRecalc(const bool doRecalc); 18 | 19 | private: 20 | void loop(); 21 | void updateSunData(); 22 | bool checkRecalcDayChanged() const; 23 | bool getSunTime(struct tm* info, const uint32_t offset) const; 24 | 25 | Task _loopTask; 26 | 27 | bool _isSunsetAvailable = true; 28 | uint32_t _sunriseMinutes = 0; 29 | uint32_t _sunsetMinutes = 0; 30 | 31 | bool _isValidInfo = false; 32 | std::atomic_bool _doRecalc = true; 33 | uint32_t _lastSunPositionCalculatedYMD = 0; 34 | }; 35 | 36 | extern SunPositionClass SunPosition; 37 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/HMT_Abstract.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2023-2024 Thomas Basler and others 4 | */ 5 | #include "HMT_Abstract.h" 6 | #include "Hoymiles.h" 7 | #include "HoymilesRadio_CMT.h" 8 | #include "commands/ChannelChangeCommand.h" 9 | #include "parser/AlarmLogParser.h" 10 | 11 | HMT_Abstract::HMT_Abstract(HoymilesRadio* radio, const uint64_t serial) 12 | : HM_Abstract(radio, serial) 13 | { 14 | EventLog()->setMessageType(AlarmMessageType_t::HMT); 15 | }; 16 | 17 | bool HMT_Abstract::sendChangeChannelRequest() 18 | { 19 | if (!(getEnableCommands() || getEnablePolling())) { 20 | return false; 21 | } 22 | 23 | auto cmdChannel = _radio->prepareCommand(this); 24 | cmdChannel->setCountryMode(Hoymiles.getRadioCmt()->getCountryMode()); 25 | cmdChannel->setChannel(Hoymiles.getRadioCmt()->getChannelFromFrequency(Hoymiles.getRadioCmt()->getInverterTargetFrequency())); 26 | _radio->enqueCommand(cmdChannel); 27 | 28 | return true; 29 | }; 30 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/SingleDataCommand.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2022-2024 Thomas Basler and others 4 | */ 5 | 6 | /* 7 | This command is used to send simple commands, containing only one payload, to the inverter. 8 | 9 | Derives from CommandAbstract. 10 | 11 | Command structure: 12 | * ID: fixed identifier and everytime 0x15 13 | 14 | 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 15 | --------------------------------------------------------------------------------------------------------- 16 | 15 71 60 35 46 80 12 23 04 00 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 17 | ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ 18 | ID Target Addr Source Addr CRC8 19 | */ 20 | #include "SingleDataCommand.h" 21 | 22 | SingleDataCommand::SingleDataCommand(InverterAbstract* inv, const uint64_t router_address) 23 | : CommandAbstract(inv, router_address) 24 | { 25 | _payload[0] = 0x15; 26 | setTimeout(100); 27 | } 28 | -------------------------------------------------------------------------------- /lib/MqttSubscribeParser/MqttSubscribeParser.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct cb_filter_t { 10 | std::string topic; 11 | uint8_t qos; 12 | espMqttClientTypes::OnMessageCallback cb; 13 | }; 14 | 15 | class MqttSubscribeParser { 16 | public: 17 | void register_callback(const std::string& topic, uint8_t qos, const espMqttClientTypes::OnMessageCallback& cb); 18 | void unregister_callback(const std::string& topic); 19 | void handle_message(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total); 20 | std::vector get_callbacks(); 21 | 22 | private: 23 | int mosquitto_topic_matches_sub(const char* sub, const char* topic, bool* result); 24 | 25 | std::vector _callbacks; 26 | 27 | enum mosq_err_t { 28 | MOSQ_ERR_SUCCESS = 0, 29 | MOSQ_ERR_INVAL = 3, 30 | }; 31 | }; -------------------------------------------------------------------------------- /webapp/src/types/PinMapping.ts: -------------------------------------------------------------------------------- 1 | export interface Nrf24 { 2 | miso: number; 3 | mosi: number; 4 | clk: number; 5 | irq: number; 6 | en: number; 7 | cs: number; 8 | } 9 | 10 | export interface Cmt2300 { 11 | clk: number; 12 | cs: number; 13 | fcs: number; 14 | sdio: number; 15 | gpio2: number; 16 | gpio3: number; 17 | } 18 | 19 | export interface Ethernet { 20 | enabled: boolean; 21 | phy_addr: number; 22 | power: number; 23 | mdc: number; 24 | mdio: number; 25 | type: number; 26 | clk_mode: number; 27 | } 28 | 29 | export interface Display { 30 | type: number; 31 | data: number; 32 | clk: number; 33 | cs: number; 34 | reset: number; 35 | } 36 | 37 | export interface Links { 38 | name: string; 39 | url: string; 40 | } 41 | 42 | export interface Device { 43 | name: string; 44 | links: Array; 45 | nrf24: Nrf24; 46 | cmt: Cmt2300; 47 | eth: Ethernet; 48 | display: Display; 49 | } 50 | 51 | export interface PinMapping extends Array {} 52 | -------------------------------------------------------------------------------- /webapp/src/components/LocaleSwitcher.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 32 | -------------------------------------------------------------------------------- /webapp/src/types/SystemStatus.ts: -------------------------------------------------------------------------------- 1 | export interface SystemStatus { 2 | // HardwareInfo 3 | chipmodel: string; 4 | chiprevision: number; 5 | chipcores: number; 6 | cpufreq: number; 7 | cputemp: number; 8 | flashsize: number; 9 | // FirmwareInfo 10 | hostname: string; 11 | sdkversion: string; 12 | config_version: string; 13 | git_hash: string; 14 | git_is_hash: boolean; 15 | pioenv: string; 16 | resetreason_0: string; 17 | resetreason_1: string; 18 | cfgsavecount: number; 19 | uptime: number; 20 | update_text: string; 21 | update_url: string; 22 | update_status: string; 23 | // MemoryInfo 24 | heap_total: number; 25 | heap_used: number; 26 | heap_max_block: number; 27 | heap_min_free: number; 28 | littlefs_total: number; 29 | littlefs_used: number; 30 | psram_total: number; 31 | psram_used: number; 32 | sketch_total: number; 33 | sketch_used: number; 34 | // RadioInfo 35 | nrf_configured: boolean; 36 | nrf_connected: boolean; 37 | nrf_pvariant: boolean; 38 | cmt_configured: boolean; 39 | cmt_connected: boolean; 40 | } 41 | -------------------------------------------------------------------------------- /lib/Frozen/frozen/bits/version.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Frozen 3 | * Copyright 2016 QuarksLab 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | */ 22 | 23 | #ifndef FROZEN_LETITGO_VERSION_H 24 | #define FROZEN_LETITGO_VERSION_H 25 | 26 | #define FROZEN_MAJOR_VERSION 1 27 | #define FROZEN_MINOR_VERSION 1 28 | #define FROZEN_PATCH_VERSION 1 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /docs/builds/README.md: -------------------------------------------------------------------------------- 1 | # Builds using different boards 2 | 3 | ## ESP32 Dev Board 4 | 5 | ### Build by @tbnobody, jan and @marove2000 6 | * Used build environment: generic 7 | * Case: https://www.printables.com/de/model/441037-opendtu-breakoutboard-case 8 | * Soldering Kit: https://shop.blinkyparts.com/en/OpenDTU-Breakoutboard-Your-evaluation-for-your-balcony-solar-system/blink237542 9 | * Breakout board: https://github.com/marove2000/openDTU_BreakoutBoard 10 | ![](opendtu_breakoutboard.jpg) 11 | ![](thumbnail.jpg) 12 | 13 | ### Build by @Marc-- 14 | * Used build environment: generic 15 | * Case: https://www.thingiverse.com/thing:5435911 16 | ![](large_display_PXL_20220715_145622277.jpg) 17 | 18 | ### Build by @cepresso 19 | * Used build environment: generic 20 | * Case: https://www.printables.com/de/model/293003-sol-opendtu-esp32-nrf24l01-case 21 | ![](sol.webp) 22 | 23 | ## LILYGO® TTGO T-Internet-POE 24 | ### Build by @fromCologne 25 | * Used build environment: LilyGO_T_ETH_POE 26 | * Board info: http://www.lilygo.cn/claprod_view.aspx?TypeId=21&Id=1344&FId=t28:21:28 27 | * Case: https://www.thingiverse.com/thing:5244895 28 | ![](202654506-8a4ac4ef-c883-490e-8ee1-1e1f7fa34972.jpg) -------------------------------------------------------------------------------- /src/WebApi_ws_console.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2022-2024 Thomas Basler and others 4 | */ 5 | #include "WebApi_ws_console.h" 6 | #include "Configuration.h" 7 | #include "MessageOutput.h" 8 | #include "WebApi.h" 9 | #include "defaults.h" 10 | 11 | WebApiWsConsoleClass::WebApiWsConsoleClass() 12 | : _ws("/console") 13 | , _wsCleanupTask(1 * TASK_SECOND, TASK_FOREVER, std::bind(&WebApiWsConsoleClass::wsCleanupTaskCb, this)) 14 | { 15 | } 16 | 17 | void WebApiWsConsoleClass::init(AsyncWebServer& server, Scheduler& scheduler) 18 | { 19 | server.addHandler(&_ws); 20 | MessageOutput.register_ws_output(&_ws); 21 | 22 | scheduler.addTask(_wsCleanupTask); 23 | _wsCleanupTask.enable(); 24 | } 25 | 26 | void WebApiWsConsoleClass::wsCleanupTaskCb() 27 | { 28 | // see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients 29 | _ws.cleanupClients(); 30 | 31 | if (Configuration.get().Security.AllowReadonly) { 32 | _ws.setAuthentication("", ""); 33 | } else { 34 | _ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security.Password); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docs/DeviceProfiles/olimex_esp32_gateway.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Olimex ESP32-Gateway", 4 | "nrf24": { 5 | "miso": 14, 6 | "mosi": 13, 7 | "clk": 12, 8 | "irq": 15, 9 | "en": 2, 10 | "cs": 4 11 | }, 12 | "eth": { 13 | "enabled": true, 14 | "phy_addr": 0, 15 | "power": 12, 16 | "mdc": 23, 17 | "mdio": 18, 18 | "type": 0, 19 | "clk_mode": 3 20 | } 21 | }, 22 | { 23 | "name": "Olimex ESP32-Gateway with SSH1106", 24 | "nrf24": { 25 | "miso": 14, 26 | "mosi": 13, 27 | "clk": 12, 28 | "irq": 15, 29 | "en": 2, 30 | "cs": 4 31 | }, 32 | "eth": { 33 | "enabled": true, 34 | "phy_addr": 0, 35 | "power": 12, 36 | "mdc": 23, 37 | "mdio": 18, 38 | "type": 0, 39 | "clk_mode": 3 40 | }, 41 | "display": { 42 | "type": 3, 43 | "data": 32, 44 | "clk": 16 45 | } 46 | } 47 | ] 48 | -------------------------------------------------------------------------------- /webapp/src/components/InterfaceApInfo.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 34 | -------------------------------------------------------------------------------- /lib/Frozen/frozen/bits/elsa_std.h: -------------------------------------------------------------------------------- 1 | #ifndef FROZEN_LETITGO_BITS_ELSA_STD_H 2 | #define FROZEN_LETITGO_BITS_ELSA_STD_H 3 | 4 | #include "defines.h" 5 | #include "elsa.h" 6 | #include "hash_string.h" 7 | 8 | #ifdef FROZEN_LETITGO_HAS_STRING_VIEW 9 | #include 10 | #endif 11 | #include 12 | 13 | namespace frozen { 14 | 15 | #ifdef FROZEN_LETITGO_HAS_STRING_VIEW 16 | 17 | template struct elsa> 18 | { 19 | constexpr std::size_t operator()(const std::basic_string_view& value) const { 20 | return hash_string(value); 21 | } 22 | constexpr std::size_t operator()(const std::basic_string_view& value, std::size_t seed) const { 23 | return hash_string(value, seed); 24 | } 25 | }; 26 | 27 | #endif 28 | 29 | template struct elsa> 30 | { 31 | constexpr std::size_t operator()(const std::basic_string& value) const { 32 | return hash_string(value); 33 | } 34 | constexpr std::size_t operator()(const std::basic_string& value, std::size_t seed) const { 35 | return hash_string(value, seed); 36 | } 37 | }; 38 | 39 | } // namespace frozen 40 | 41 | #endif // FROZEN_LETITGO_BITS_ELSA_STD_H 42 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /platformio_override.ini: -------------------------------------------------------------------------------- 1 | ; *** PlatformIO Project Configuration Override File *** 2 | ; *** Changes done here override settings in platformio.ini *** 3 | ; 4 | ; Place your personal settings like monitor_port and upload_port here 5 | ; instead of editing platformio.ini 6 | ; to avoid annoying merge conflicts when you pull updates into your 7 | ; personal clone of the repository 8 | ; 9 | ; Please visit documentation for the options and examples 10 | ; http://docs.platformio.org/en/stable/projectconf.html 11 | 12 | [env] 13 | ; Specify port here. Comment out (add ; in front of line) to use auto detection. 14 | ; Under Linux, the ports are in the format /dev/tty***, typically /dev/ttyUSB0 15 | ; To look up the port, execute ls /dev/tty*, then connect the device 16 | ; then execute ls /dev/tty* again and check which device was added 17 | ;monitor_port = COM4 18 | ;upload_port = COM4 19 | 20 | 21 | ; you can define your personal board and/or settings here 22 | ; non functional example: 23 | 24 | ;[env:my_very_special_board] 25 | ;board = esp32dev 26 | ;build_flags = ${env.build_flags} 27 | ; -DHOYMILES_PIN_MISO=1 28 | ; -DHOYMILES_PIN_MOSI=2 29 | ; -DHOYMILES_PIN_SCLK=3 30 | ; -DHOYMILES_PIN_IRQ=4 31 | ; -DHOYMILES_PIN_CE=5 32 | ; -DHOYMILES_PIN_CS=6 33 | ;monitor_port = /dev/ttyACM0 34 | ;upload_port = /dev/ttyACM0 35 | -------------------------------------------------------------------------------- /lib/Frozen/frozen/bits/constexpr_assert.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Frozen 3 | * Copyright 2016 QuarksLab 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | */ 22 | 23 | #ifndef FROZEN_LETITGO_CONSTEXPR_ASSERT_H 24 | #define FROZEN_LETITGO_CONSTEXPR_ASSERT_H 25 | 26 | #include 27 | 28 | #ifdef _MSC_VER 29 | 30 | // FIXME: find a way to implement that correctly for msvc 31 | #define constexpr_assert(cond, msg) 32 | 33 | #else 34 | 35 | #define constexpr_assert(cond, msg)\ 36 | assert(cond && msg); 37 | #endif 38 | 39 | #endif 40 | 41 | -------------------------------------------------------------------------------- /.github/workflows/config/release-notes-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "categories": [ 3 | { 4 | "title": "## ⚡ Breaking Changes", 5 | "labels": [ 6 | "breaking change" 7 | ] 8 | }, 9 | { 10 | "title": "## 🚀 Features", 11 | "labels": [ 12 | "feature" 13 | ] 14 | }, 15 | { 16 | "title": "## 🐛 Fixes", 17 | "labels": [ 18 | "fix" 19 | ] 20 | }, 21 | { 22 | "title": "## 📚 Documentation", 23 | "labels": [ 24 | "doc" 25 | ] 26 | }, 27 | { 28 | "title": "## 🛠 Under the hood", 29 | "labels": [] 30 | } 31 | ], 32 | "template": "${{CHANGELOG}}", 33 | "pr_template": "- [${{TITLE}}](https://github.com/tbnobody/OpenDTU/commit/${{MERGE_SHA}})", 34 | "empty_template": "- no changes", 35 | "label_extractor": [ 36 | { 37 | "pattern": "(.): (.+)", 38 | "target": "$1", 39 | "on_property": "title" 40 | }, 41 | { 42 | "pattern": "(.) (.+)", 43 | "target": "$1", 44 | "on_property": "title" 45 | } 46 | ], 47 | "tag_resolver": { 48 | "method": "semver" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: ✨ Request a feature 2 | description: Suggest an improvement idea for OpenDTU! 3 | title: "[Request]" 4 | labels: ["enhancement"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: > 9 | **Thank you for wanting to request a feature in OpenDTU!** 10 | 11 | Before you go ahead with your request, please first consider if it wouldn't be 12 | better suited in a external home automation software like OpenHAB, ioBroker, Home Assistant etc. 13 | 14 | - type: textarea 15 | attributes: 16 | label: Is your feature request related to a problem? Please describe. 17 | description: A clear and concise description of what the problem is. Eg, "I'm always frustrated when [...]". 18 | - type: textarea 19 | attributes: 20 | label: Describe the solution you'd like 21 | description: A clear and concise description of what you want to happen. 22 | validations: 23 | required: true 24 | - type: textarea 25 | attributes: 26 | label: Describe alternatives you've considered 27 | description: A clear and concise description of any alternative solutions or features you've considered. 28 | - type: textarea 29 | attributes: 30 | label: Additional context 31 | description: Add any other context or screenshots about the feature request here. 32 | 33 | 34 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/HoymilesRadio_NRF.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "HoymilesRadio.h" 5 | #include "commands/CommandAbstract.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // number of fragments hold in buffer 12 | #define FRAGMENT_BUFFER_SIZE 30 13 | 14 | class HoymilesRadio_NRF : public HoymilesRadio { 15 | public: 16 | void init(SPIClass* initialisedSpiBus, const uint8_t pinCE, const uint8_t pinIRQ); 17 | void loop(); 18 | void setPALevel(const rf24_pa_dbm_e paLevel); 19 | 20 | virtual void setDtuSerial(const uint64_t serial); 21 | 22 | bool isConnected() const; 23 | bool isPVariant() const; 24 | 25 | private: 26 | void ARDUINO_ISR_ATTR handleIntr(); 27 | uint8_t getRxNxtChannel(); 28 | uint8_t getTxNxtChannel(); 29 | void switchRxCh(); 30 | void openReadingPipe(); 31 | void openWritingPipe(const serial_u serial); 32 | 33 | void sendEsbPacket(CommandAbstract& cmd); 34 | 35 | std::unique_ptr _spiPtr; 36 | std::unique_ptr _radio; 37 | uint8_t _rxChLst[5] = { 3, 23, 40, 61, 75 }; 38 | uint8_t _rxChIdx = 0; 39 | 40 | uint8_t _txChLst[5] = { 3, 23, 40, 61, 75 }; 41 | uint8_t _txChIdx = 0; 42 | 43 | volatile bool _packetReceived = false; 44 | 45 | std::queue _rxBuffer; 46 | }; -------------------------------------------------------------------------------- /lib/Frozen/frozen/bits/exceptions.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Frozen 3 | * Copyright 2016 QuarksLab 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | */ 22 | 23 | #ifndef FROZEN_LETITGO_EXCEPTIONS_H 24 | #define FROZEN_LETITGO_EXCEPTIONS_H 25 | 26 | #if defined(FROZEN_NO_EXCEPTIONS) || (defined(_MSC_VER) && !defined(_CPPUNWIND)) || (!defined(_MSC_VER) && !defined(__cpp_exceptions)) 27 | 28 | #include 29 | #define FROZEN_THROW_OR_ABORT(_) std::abort() 30 | 31 | #else 32 | 33 | #include 34 | #define FROZEN_THROW_OR_ABORT(err) throw err 35 | 36 | 37 | #endif 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /webapp/src/components/FsInfo.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 42 | -------------------------------------------------------------------------------- /docs/screenshots/README.md: -------------------------------------------------------------------------------- 1 | # OpenDTU Screenshots 2 | 3 | here are some screenshots of OpenDTU's web interface. 4 | 5 | *** 6 | 7 | ![Live View](01_LiveView.png) 8 | 9 | *** 10 | 11 | ![Limit Settings](15_LimitSettings.png) 12 | 13 | *** 14 | 15 | ![Power Settings](16_PowerSettings.png) 16 | 17 | *** 18 | 19 | ![Inverter Info](17_InverterInfo.png) 20 | 21 | *** 22 | 23 | ![Eventlog](12_Eventlog.png) 24 | 25 | *** 26 | 27 | ![Network Admin](02_NetworkAdmin.png) 28 | 29 | *** 30 | 31 | ![NTP Admin](03_NtpAdmin.png) 32 | 33 | *** 34 | 35 | ![MQTT Admin](04_MqttAdmin.png) 36 | 37 | *** 38 | 39 | ![Inverter Admin](05_InverterAdmin.png) 40 | 41 | *** 42 | 43 | ![Inverter Settings](13_InverterSettings.png) 44 | 45 | *** 46 | 47 | ![Security](22_Security.png) 48 | 49 | *** 50 | 51 | ![DTU Admin](06_DtuAdmin.png) 52 | 53 | *** 54 | 55 | ![Device Manager Pin](20_DeviceManager_Pin.png) 56 | 57 | *** 58 | 59 | ![Device Manager Display](21_DeviceManager_Display.png) 60 | 61 | *** 62 | 63 | ![Config Management](14_ConfigManagement.png) 64 | 65 | *** 66 | 67 | ![Firmware Upgrade](07_FirmwareUpgrade.png) 68 | 69 | *** 70 | 71 | ![Reboot](19_Reboot.png) 72 | 73 | *** 74 | 75 | ![System Info](11_SystemInfo.png) 76 | 77 | *** 78 | 79 | ![Network Info](08_NetworkInfo.png) 80 | 81 | *** 82 | 83 | ![NTP Info](09_NtpInfo.png) 84 | 85 | *** 86 | 87 | ![MQTT Info](10_MqttInfo.png) 88 | 89 | *** 90 | 91 | ![Console](18_Console.png) 92 | -------------------------------------------------------------------------------- /src/MqttHandleDtu.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2022-2024 Thomas Basler and others 4 | */ 5 | #include "MqttHandleDtu.h" 6 | #include "Configuration.h" 7 | #include "MqttSettings.h" 8 | #include "NetworkSettings.h" 9 | #include 10 | 11 | MqttHandleDtuClass MqttHandleDtu; 12 | 13 | MqttHandleDtuClass::MqttHandleDtuClass() 14 | : _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&MqttHandleDtuClass::loop, this)) 15 | { 16 | } 17 | 18 | void MqttHandleDtuClass::init(Scheduler& scheduler) 19 | { 20 | scheduler.addTask(_loopTask); 21 | _loopTask.setInterval(Configuration.get().Mqtt.PublishInterval * TASK_SECOND); 22 | _loopTask.enable(); 23 | } 24 | 25 | void MqttHandleDtuClass::loop() 26 | { 27 | _loopTask.setInterval(Configuration.get().Mqtt.PublishInterval * TASK_SECOND); 28 | 29 | if (!MqttSettings.getConnected() || !Hoymiles.isAllRadioIdle()) { 30 | _loopTask.forceNextIteration(); 31 | return; 32 | } 33 | 34 | MqttSettings.publish("dtu/uptime", String(millis() / 1000)); 35 | MqttSettings.publish("dtu/ip", NetworkSettings.localIP().toString()); 36 | MqttSettings.publish("dtu/hostname", NetworkSettings.getHostname()); 37 | if (NetworkSettings.NetworkMode() == network_mode::WiFi) { 38 | MqttSettings.publish("dtu/rssi", String(WiFi.RSSI())); 39 | MqttSettings.publish("dtu/bssid", WiFi.BSSIDstr()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /include/MqttHandleInverter.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "Configuration.h" 5 | #include 6 | #include 7 | #include 8 | 9 | class MqttHandleInverterClass { 10 | public: 11 | MqttHandleInverterClass(); 12 | void init(Scheduler& scheduler); 13 | 14 | static String getTopic(std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId); 15 | 16 | void subscribeTopics(); 17 | void unsubscribeTopics(); 18 | 19 | private: 20 | void loop(); 21 | void publishField(std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId); 22 | void onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, const size_t len, const size_t index, const size_t total); 23 | 24 | Task _loopTask; 25 | 26 | uint32_t _lastPublishStats[INV_MAX_COUNT] = { 0 }; 27 | 28 | FieldId_t _publishFields[14] = { 29 | FLD_UDC, 30 | FLD_IDC, 31 | FLD_PDC, 32 | FLD_YD, 33 | FLD_YT, 34 | FLD_UAC, 35 | FLD_IAC, 36 | FLD_PAC, 37 | FLD_F, 38 | FLD_T, 39 | FLD_PF, 40 | FLD_EFF, 41 | FLD_IRR, 42 | FLD_Q 43 | }; 44 | }; 45 | 46 | extern MqttHandleInverterClass MqttHandleInverter; 47 | -------------------------------------------------------------------------------- /include/PinMapping.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define PINMAPPING_FILENAME "/pin_mapping.json" 9 | #define PINMAPPING_LED_COUNT 2 10 | 11 | #define MAPPING_NAME_STRLEN 31 12 | 13 | struct PinMapping_t { 14 | char name[MAPPING_NAME_STRLEN + 1]; 15 | int8_t nrf24_miso; 16 | int8_t nrf24_mosi; 17 | int8_t nrf24_clk; 18 | int8_t nrf24_irq; 19 | int8_t nrf24_en; 20 | int8_t nrf24_cs; 21 | 22 | int8_t cmt_clk; 23 | int8_t cmt_cs; 24 | int8_t cmt_fcs; 25 | int8_t cmt_gpio2; 26 | int8_t cmt_gpio3; 27 | int8_t cmt_sdio; 28 | 29 | int8_t eth_phy_addr; 30 | bool eth_enabled; 31 | int eth_power; 32 | int eth_mdc; 33 | int eth_mdio; 34 | eth_phy_type_t eth_type; 35 | eth_clock_mode_t eth_clk_mode; 36 | uint8_t display_type; 37 | uint8_t display_data; 38 | uint8_t display_clk; 39 | uint8_t display_cs; 40 | uint8_t display_reset; 41 | int8_t led[PINMAPPING_LED_COUNT]; 42 | }; 43 | 44 | class PinMappingClass { 45 | public: 46 | PinMappingClass(); 47 | bool init(const String& deviceMapping); 48 | PinMapping_t& get(); 49 | 50 | bool isValidNrf24Config() const; 51 | bool isValidCmt2300Config() const; 52 | bool isValidEthConfig() const; 53 | 54 | private: 55 | PinMapping_t _pinMapping; 56 | }; 57 | 58 | extern PinMappingClass PinMapping; -------------------------------------------------------------------------------- /webapp/src/types/LiveDataStatus.ts: -------------------------------------------------------------------------------- 1 | export interface ValueObject { 2 | v: number; // value 3 | u: string; // unit 4 | d: number; // digits 5 | max: number; 6 | } 7 | 8 | export interface InverterStatistics { 9 | name: ValueObject; 10 | Power?: ValueObject; 11 | Voltage?: ValueObject; 12 | Current?: ValueObject; 13 | 'Power DC'?: ValueObject; 14 | YieldDay?: ValueObject; 15 | YieldTotal?: ValueObject; 16 | Frequency?: ValueObject; 17 | Temperature?: ValueObject; 18 | PowerFactor?: ValueObject; 19 | ReactivePower?: ValueObject; 20 | Efficiency?: ValueObject; 21 | Irradiation?: ValueObject; 22 | } 23 | 24 | export interface Inverter { 25 | serial: string; 26 | name: string; 27 | order: number; 28 | data_age: number; 29 | poll_enabled: boolean; 30 | reachable: boolean; 31 | producing: boolean; 32 | limit_relative: number; 33 | limit_absolute: number; 34 | events: number; 35 | AC: InverterStatistics[]; 36 | DC: InverterStatistics[]; 37 | INV: InverterStatistics[]; 38 | } 39 | 40 | export interface Total { 41 | Power: ValueObject; 42 | YieldDay: ValueObject; 43 | YieldTotal: ValueObject; 44 | } 45 | 46 | export interface Hints { 47 | time_sync: boolean; 48 | default_password: boolean; 49 | radio_problem: boolean; 50 | } 51 | 52 | export interface LiveData { 53 | inverters: Inverter[]; 54 | total: Total; 55 | hints: Hints; 56 | } 57 | -------------------------------------------------------------------------------- /include/WebApi_ws_live.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "Configuration.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class WebApiWsLiveClass { 11 | public: 12 | WebApiWsLiveClass(); 13 | void init(AsyncWebServer& server, Scheduler& scheduler); 14 | 15 | private: 16 | static void generateInverterCommonJsonResponse(JsonObject& root, std::shared_ptr inv); 17 | static void generateInverterChannelJsonResponse(JsonObject& root, std::shared_ptr inv); 18 | static void generateCommonJsonResponse(JsonVariant& root); 19 | 20 | static void addField(JsonObject& root, std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId, String topic = ""); 21 | static void addTotalField(JsonObject& root, const String& name, const float value, const String& unit, const uint8_t digits); 22 | 23 | void onLivedataStatus(AsyncWebServerRequest* request); 24 | void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len); 25 | 26 | AsyncWebSocket _ws; 27 | 28 | uint32_t _lastPublishStats[INV_MAX_COUNT] = { 0 }; 29 | 30 | std::mutex _mutex; 31 | 32 | Task _wsCleanupTask; 33 | void wsCleanupTaskCb(); 34 | 35 | Task _sendDataTask; 36 | void sendDataTaskCb(); 37 | }; 38 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/crc.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2022 Thomas Basler and others 4 | */ 5 | #include "crc.h" 6 | 7 | uint8_t crc8(const uint8_t buf[], const uint8_t len) 8 | { 9 | uint8_t crc = CRC8_INIT; 10 | for (uint8_t i = 0; i < len; i++) { 11 | crc ^= buf[i]; 12 | for (uint8_t b = 0; b < 8; b++) { 13 | crc = (crc << 1) ^ ((crc & 0x80) ? CRC8_POLY : 0x00); 14 | } 15 | } 16 | return crc; 17 | } 18 | 19 | uint16_t crc16(const uint8_t buf[], const uint8_t len, const uint16_t start) 20 | { 21 | uint16_t crc = start; 22 | uint8_t shift = 0; 23 | 24 | for (uint8_t i = 0; i < len; i++) { 25 | crc = crc ^ buf[i]; 26 | for (uint8_t bit = 0; bit < 8; bit++) { 27 | shift = (crc & 0x0001); 28 | crc = crc >> 1; 29 | if (shift != 0) 30 | crc = crc ^ 0xA001; 31 | } 32 | } 33 | return crc; 34 | } 35 | 36 | uint16_t crc16nrf24(const uint8_t buf[], const uint16_t lenBits, const uint16_t startBit, const uint16_t crcIn) 37 | { 38 | uint16_t crc = crcIn; 39 | uint8_t idx, val = buf[(startBit >> 3)]; 40 | 41 | for (uint16_t bit = startBit; bit < lenBits; bit++) { 42 | idx = bit & 0x07; 43 | if (0 == idx) 44 | val = buf[(bit >> 3)]; 45 | crc ^= 0x8000 & (val << (8 + idx)); 46 | crc = (crc & 0x8000) ? ((crc << 1) ^ CRC16_NRF24_POLYNOM) : (crc << 1); 47 | } 48 | 49 | return crc; 50 | } -------------------------------------------------------------------------------- /lib/Hoymiles/src/parser/SystemConfigParaParser.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | #include "Parser.h" 4 | 5 | #define SYSTEM_CONFIG_PARA_SIZE 16 6 | 7 | class SystemConfigParaParser : public Parser { 8 | public: 9 | SystemConfigParaParser(); 10 | void clearBuffer(); 11 | void appendFragment(const uint8_t offset, const uint8_t* payload, const uint8_t len); 12 | 13 | float getLimitPercent() const; 14 | void setLimitPercent(const float value); 15 | 16 | void setLastLimitCommandSuccess(const LastCommandSuccess status); 17 | LastCommandSuccess getLastLimitCommandSuccess() const; 18 | uint32_t getLastUpdateCommand() const; 19 | void setLastUpdateCommand(const uint32_t lastUpdate); 20 | 21 | void setLastLimitRequestSuccess(const LastCommandSuccess status); 22 | LastCommandSuccess getLastLimitRequestSuccess() const; 23 | uint32_t getLastUpdateRequest() const; 24 | void setLastUpdateRequest(const uint32_t lastUpdate); 25 | 26 | // Returns 1 based amount of expected bytes of data 27 | uint8_t getExpectedByteCount() const; 28 | 29 | private: 30 | uint8_t _payload[SYSTEM_CONFIG_PARA_SIZE]; 31 | uint8_t _payloadLength; 32 | 33 | LastCommandSuccess _lastLimitCommandSuccess = CMD_OK; // Set to OK because we have to assume nothing is done at startup 34 | LastCommandSuccess _lastLimitRequestSuccess = CMD_NOK; // Set to NOK to fetch at startup 35 | 36 | uint32_t _lastUpdateCommand = 0; 37 | uint32_t _lastUpdateRequest = 0; 38 | }; -------------------------------------------------------------------------------- /lib/Hoymiles/src/parser/DevInfoParser.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | #include "Parser.h" 4 | 5 | #define DEV_INFO_SIZE 20 6 | 7 | class DevInfoParser : public Parser { 8 | public: 9 | DevInfoParser(); 10 | void clearBufferAll(); 11 | void appendFragmentAll(const uint8_t offset, const uint8_t* payload, const uint8_t len); 12 | 13 | void clearBufferSimple(); 14 | void appendFragmentSimple(const uint8_t offset, const uint8_t* payload, const uint8_t len); 15 | 16 | uint32_t getLastUpdateAll() const; 17 | void setLastUpdateAll(const uint32_t lastUpdate); 18 | 19 | uint32_t getLastUpdateSimple() const; 20 | void setLastUpdateSimple(const uint32_t lastUpdate); 21 | 22 | uint16_t getFwBuildVersion() const; 23 | time_t getFwBuildDateTime() const; 24 | String getFwBuildDateTimeStr() const; 25 | uint16_t getFwBootloaderVersion() const; 26 | 27 | uint32_t getHwPartNumber() const; 28 | String getHwVersion() const; 29 | 30 | uint16_t getMaxPower() const; 31 | String getHwModelName() const; 32 | 33 | bool containsValidData() const; 34 | 35 | private: 36 | static time_t timegm(const struct tm* tm); 37 | uint8_t getDevIdx() const; 38 | 39 | uint32_t _lastUpdateAll = 0; 40 | uint32_t _lastUpdateSimple = 0; 41 | 42 | uint8_t _payloadDevInfoAll[DEV_INFO_SIZE] = {}; 43 | uint8_t _devInfoAllLength = 0; 44 | 45 | uint8_t _payloadDevInfoSimple[DEV_INFO_SIZE] = {}; 46 | uint8_t _devInfoSimpleLength = 0; 47 | }; 48 | -------------------------------------------------------------------------------- /include/MqttSettings.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "NetworkSettings.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class MqttSettingsClass { 11 | public: 12 | MqttSettingsClass(); 13 | void init(); 14 | void performReconnect(); 15 | bool getConnected(); 16 | void publish(const String& subtopic, const String& payload); 17 | void publishGeneric(const String& topic, const String& payload, const bool retain, const uint8_t qos = 0); 18 | 19 | void subscribe(const String& topic, const uint8_t qos, const espMqttClientTypes::OnMessageCallback& cb); 20 | void unsubscribe(const String& topic); 21 | 22 | String getPrefix() const; 23 | String getClientId(); 24 | 25 | private: 26 | void NetworkEvent(network_event event); 27 | 28 | void onMqttDisconnect(espMqttClientTypes::DisconnectReason reason); 29 | void onMqttConnect(const bool sessionPresent); 30 | void onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, const size_t len, const size_t index, const size_t total); 31 | 32 | void performConnect(); 33 | void performDisconnect(); 34 | 35 | void createMqttClientObject(); 36 | 37 | MqttClient* _mqttClient = nullptr; 38 | Ticker _mqttReconnectTimer; 39 | MqttSubscribeParser _mqttSubscribeParser; 40 | std::mutex _clientLock; 41 | }; 42 | 43 | extern MqttSettingsClass MqttSettings; 44 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/HoymilesRadio.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "commands/CommandAbstract.h" 5 | #include "types.h" 6 | #include 7 | #include 8 | #include 9 | 10 | class HoymilesRadio { 11 | public: 12 | serial_u DtuSerial() const; 13 | virtual void setDtuSerial(const uint64_t serial); 14 | 15 | bool isIdle() const; 16 | bool isQueueEmpty() const; 17 | bool isInitialized() const; 18 | int8_t getLastRssi(); 19 | 20 | void enqueCommand(std::shared_ptr cmd) 21 | { 22 | _commandQueue.push(cmd); 23 | } 24 | 25 | template 26 | std::shared_ptr prepareCommand(InverterAbstract* inv) 27 | { 28 | return std::make_shared(inv); 29 | } 30 | 31 | protected: 32 | static serial_u convertSerialToRadioId(const serial_u serial); 33 | static void dumpBuf(const uint8_t buf[], const uint8_t len, const bool appendNewline = true); 34 | 35 | bool checkFragmentCrc(const fragment_t& fragment) const; 36 | virtual void sendEsbPacket(CommandAbstract& cmd) = 0; 37 | void sendRetransmitPacket(const uint8_t fragment_id); 38 | void sendLastPacketAgain(); 39 | void handleReceivedPackage(); 40 | 41 | serial_u _dtuSerial; 42 | ThreadSafeQueue> _commandQueue; 43 | bool _isInitialized = false; 44 | bool _busyFlag = false; 45 | 46 | TimeoutHelper _rxTimeout; 47 | int8_t _rxLastRssi = -128; 48 | }; 49 | -------------------------------------------------------------------------------- /webapp/src/components/EventLog.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 40 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /webapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opendtu", 3 | "version": "0.1.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "run-p type-check build-only", 9 | "preview": "vite preview --port 4173", 10 | "build-only": "vite build", 11 | "type-check": "vue-tsc --noEmit", 12 | "lint": "eslint .", 13 | "format": "prettier --write src/" 14 | }, 15 | "dependencies": { 16 | "@popperjs/core": "^2.11.8", 17 | "bootstrap": "^5.3.3", 18 | "bootstrap-icons-vue": "^1.11.3", 19 | "mitt": "^3.0.1", 20 | "sortablejs": "^1.15.2", 21 | "spark-md5": "^3.0.2", 22 | "vue": "^3.4.35", 23 | "vue-i18n": "^9.13.1", 24 | "vue-router": "^4.4.2" 25 | }, 26 | "devDependencies": { 27 | "@intlify/unplugin-vue-i18n": "^4.0.0", 28 | "@tsconfig/node18": "^18.2.4", 29 | "@types/bootstrap": "^5.2.10", 30 | "@types/node": "^22.1.0", 31 | "@types/pulltorefreshjs": "^0.1.7", 32 | "@types/sortablejs": "^1.15.8", 33 | "@types/spark-md5": "^3.0.4", 34 | "@vitejs/plugin-vue": "^5.1.2", 35 | "@vue/eslint-config-typescript": "^13.0.0", 36 | "@vue/tsconfig": "^0.5.1", 37 | "eslint": "^9.8.0", 38 | "eslint-plugin-vue": "^9.27.0", 39 | "npm-run-all": "^4.1.5", 40 | "prettier": "^3.3.3", 41 | "pulltorefreshjs": "^0.1.22", 42 | "sass": "^1.77.6", 43 | "terser": "^5.31.3", 44 | "typescript": "^5.5.4", 45 | "vite": "^5.3.5", 46 | "vite-plugin-compression": "^0.5.1", 47 | "vite-plugin-css-injected-by-js": "^3.5.1", 48 | "vue-tsc": "^2.0.29" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/ThreadSafeQueue/src/ThreadSafeQueue.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | template 9 | class ThreadSafeQueue { 10 | public: 11 | ThreadSafeQueue() = default; 12 | ThreadSafeQueue(const ThreadSafeQueue&) = delete; 13 | ThreadSafeQueue& operator=(const ThreadSafeQueue&) = delete; 14 | 15 | ThreadSafeQueue(ThreadSafeQueue&& other) 16 | { 17 | std::lock_guard lock(_mutex); 18 | _queue = std::move(other._queue); 19 | } 20 | 21 | virtual ~ThreadSafeQueue() { } 22 | 23 | unsigned long size() const 24 | { 25 | std::lock_guard lock(_mutex); 26 | return _queue.size(); 27 | } 28 | 29 | std::optional pop() 30 | { 31 | std::lock_guard lock(_mutex); 32 | if (_queue.empty()) { 33 | return {}; 34 | } 35 | T tmp = _queue.front(); 36 | _queue.pop(); 37 | return tmp; 38 | } 39 | 40 | void push(const T& item) 41 | { 42 | std::lock_guard lock(_mutex); 43 | _queue.push(item); 44 | } 45 | 46 | T front() 47 | { 48 | std::lock_guard lock(_mutex); 49 | return _queue.front(); 50 | } 51 | 52 | private: 53 | // Moved out of public interface to prevent races between this 54 | // and pop(). 55 | bool empty() const 56 | { 57 | return _queue.empty(); 58 | } 59 | 60 | std::queue _queue; 61 | mutable std::mutex _mutex; 62 | }; 63 | -------------------------------------------------------------------------------- /src/WebApi_devinfo.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2022-2024 Thomas Basler and others 4 | */ 5 | #include "WebApi_devinfo.h" 6 | #include "WebApi.h" 7 | #include 8 | #include 9 | #include 10 | 11 | void WebApiDevInfoClass::init(AsyncWebServer& server, Scheduler& scheduler) 12 | { 13 | using std::placeholders::_1; 14 | 15 | server.on("/api/devinfo/status", HTTP_GET, std::bind(&WebApiDevInfoClass::onDevInfoStatus, this, _1)); 16 | } 17 | 18 | void WebApiDevInfoClass::onDevInfoStatus(AsyncWebServerRequest* request) 19 | { 20 | if (!WebApi.checkCredentialsReadonly(request)) { 21 | return; 22 | } 23 | 24 | AsyncJsonResponse* response = new AsyncJsonResponse(); 25 | auto& root = response->getRoot(); 26 | auto serial = WebApi.parseSerialFromRequest(request); 27 | auto inv = Hoymiles.getInverterBySerial(serial); 28 | 29 | if (inv != nullptr) { 30 | root["valid_data"] = inv->DevInfo()->getLastUpdate() > 0; 31 | root["fw_bootloader_version"] = inv->DevInfo()->getFwBootloaderVersion(); 32 | root["fw_build_version"] = inv->DevInfo()->getFwBuildVersion(); 33 | root["hw_part_number"] = inv->DevInfo()->getHwPartNumber(); 34 | root["hw_version"] = inv->DevInfo()->getHwVersion(); 35 | root["hw_model_name"] = inv->DevInfo()->getHwModelName(); 36 | root["max_power"] = inv->DevInfo()->getMaxPower(); 37 | root["fw_build_datetime"] = inv->DevInfo()->getFwBuildDateTimeStr(); 38 | } 39 | 40 | WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); 41 | } 42 | -------------------------------------------------------------------------------- /webapp/src/components/HintView.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 43 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/parser/GridProfileParser.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | #include "Parser.h" 4 | #include 5 | 6 | #define GRID_PROFILE_SIZE 141 7 | #define PROFILE_TYPE_COUNT 10 8 | #define SECTION_VALUE_COUNT 158 9 | 10 | typedef struct { 11 | uint8_t lIdx; 12 | uint8_t hIdx; 13 | const char* Name; 14 | } ProfileType_t; 15 | 16 | struct GridProfileValue_t { 17 | uint8_t Section; 18 | uint8_t Version; 19 | uint8_t ItemDefinition; 20 | }; 21 | 22 | struct GridProfileItem_t { 23 | String Name; 24 | String Unit; 25 | float Value; 26 | }; 27 | 28 | struct GridProfileSection_t { 29 | String SectionName; 30 | std::list items; 31 | }; 32 | 33 | class GridProfileParser : public Parser { 34 | public: 35 | GridProfileParser(); 36 | void clearBuffer(); 37 | void appendFragment(const uint8_t offset, const uint8_t* payload, const uint8_t len); 38 | 39 | String getProfileName() const; 40 | String getProfileVersion() const; 41 | 42 | std::vector getRawData() const; 43 | 44 | std::list getProfile() const; 45 | 46 | bool containsValidData() const; 47 | 48 | private: 49 | static uint8_t getSectionSize(const uint8_t section_id, const uint8_t section_version); 50 | static int16_t getSectionStart(const uint8_t section_id, const uint8_t section_version); 51 | 52 | uint8_t _payloadGridProfile[GRID_PROFILE_SIZE] = {}; 53 | uint8_t _gridProfileLength = 0; 54 | 55 | static const std::array _profileTypes; 56 | static const std::array _profileValues; 57 | }; 58 | -------------------------------------------------------------------------------- /lib/Frozen/frozen/bits/mpl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Frozen 3 | * Copyright 2022 Giel van Schijndel 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | */ 22 | 23 | #ifndef FROZEN_LETITGO_BITS_MPL_H 24 | #define FROZEN_LETITGO_BITS_MPL_H 25 | 26 | #include 27 | 28 | namespace frozen { 29 | 30 | namespace bits { 31 | 32 | // Forward declarations 33 | template 34 | class carray; 35 | 36 | template 37 | struct remove_cv : std::remove_cv {}; 38 | 39 | template 40 | struct remove_cv> { 41 | using type = std::pair::type...>; 42 | }; 43 | 44 | template 45 | struct remove_cv> { 46 | using type = carray::type, N>; 47 | }; 48 | 49 | template 50 | using remove_cv_t = typename remove_cv::type; 51 | 52 | } // namespace bits 53 | 54 | } // namespace frozen 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /webapp/src/components/WifiApInfo.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 46 | -------------------------------------------------------------------------------- /src/WebApi_maintenance.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2022-2024 Thomas Basler and others 4 | */ 5 | 6 | #include "WebApi_maintenance.h" 7 | #include "Utils.h" 8 | #include "WebApi.h" 9 | #include "WebApi_errors.h" 10 | #include 11 | 12 | void WebApiMaintenanceClass::init(AsyncWebServer& server, Scheduler& scheduler) 13 | { 14 | using std::placeholders::_1; 15 | 16 | server.on("/api/maintenance/reboot", HTTP_POST, std::bind(&WebApiMaintenanceClass::onRebootPost, this, _1)); 17 | } 18 | 19 | void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request) 20 | { 21 | if (!WebApi.checkCredentials(request)) { 22 | return; 23 | } 24 | 25 | AsyncJsonResponse* response = new AsyncJsonResponse(); 26 | JsonDocument root; 27 | if (!WebApi.parseRequestData(request, response, root)) { 28 | return; 29 | } 30 | 31 | auto& retMsg = response->getRoot(); 32 | 33 | if (!(root.containsKey("reboot"))) { 34 | retMsg["message"] = "Values are missing!"; 35 | retMsg["code"] = WebApiError::GenericValueMissing; 36 | WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); 37 | return; 38 | } 39 | 40 | if (root["reboot"].as()) { 41 | retMsg["type"] = "success"; 42 | retMsg["message"] = "Reboot triggered!"; 43 | retMsg["code"] = WebApiError::MaintenanceRebootTriggered; 44 | 45 | WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); 46 | Utils::restartDtu(); 47 | } else { 48 | retMsg["message"] = "Reboot cancled!"; 49 | retMsg["code"] = WebApiError::MaintenanceRebootCancled; 50 | 51 | WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/CMT2300a/cmt2300a_hal.h: -------------------------------------------------------------------------------- 1 | /* 2 | * THE FOLLOWING FIRMWARE IS PROVIDED: (1) "AS IS" WITH NO WARRANTY; AND 3 | * (2)TO ENABLE ACCESS TO CODING INFORMATION TO GUIDE AND FACILITATE CUSTOMER. 4 | * CONSEQUENTLY, CMOSTEK SHALL NOT BE HELD LIABLE FOR ANY DIRECT, INDIRECT OR 5 | * CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE CONTENT 6 | * OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING INFORMATION 7 | * CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. 8 | * 9 | * Copyright (C) CMOSTEK SZ. 10 | */ 11 | 12 | /*! 13 | * @file cmt2300a_hal.h 14 | * @brief CMT2300A hardware abstraction layer 15 | * 16 | * @version 1.2 17 | * @date Jul 17 2017 18 | * @author CMOSTEK R@D 19 | */ 20 | 21 | #ifndef __CMT2300A_HAL_H 22 | #define __CMT2300A_HAL_H 23 | 24 | #include 25 | #include 26 | 27 | #ifdef __cplusplus 28 | extern "C" { 29 | #endif 30 | 31 | /* ************************************************************************ 32 | * The following need to be modified by user 33 | * ************************************************************************ */ 34 | #define CMT2300A_DelayMs(ms) delay(ms) 35 | #define CMT2300A_DelayUs(us) delayMicroseconds(us) 36 | #define CMT2300A_GetTickCount() millis() 37 | /* ************************************************************************ */ 38 | 39 | 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); 40 | 41 | uint8_t CMT2300A_ReadReg(const uint8_t addr); 42 | void CMT2300A_WriteReg(const uint8_t addr, const uint8_t dat); 43 | 44 | void CMT2300A_ReadFifo(uint8_t buf[], const uint16_t len); 45 | void CMT2300A_WriteFifo(const uint8_t buf[], const uint16_t len); 46 | 47 | #ifdef __cplusplus 48 | } 49 | #endif 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /src/MessageOutput.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2022-2024 Thomas Basler and others 4 | */ 5 | #include "MessageOutput.h" 6 | 7 | #include 8 | 9 | MessageOutputClass MessageOutput; 10 | 11 | MessageOutputClass::MessageOutputClass() 12 | : _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&MessageOutputClass::loop, this)) 13 | { 14 | } 15 | 16 | void MessageOutputClass::init(Scheduler& scheduler) 17 | { 18 | scheduler.addTask(_loopTask); 19 | _loopTask.enable(); 20 | } 21 | 22 | void MessageOutputClass::register_ws_output(AsyncWebSocket* output) 23 | { 24 | _ws = output; 25 | } 26 | 27 | size_t MessageOutputClass::write(uint8_t c) 28 | { 29 | if (_buff_pos < BUFFER_SIZE) { 30 | std::lock_guard lock(_msgLock); 31 | _buffer[_buff_pos] = c; 32 | _buff_pos++; 33 | } else { 34 | _forceSend = true; 35 | } 36 | 37 | return Serial.write(c); 38 | } 39 | 40 | size_t MessageOutputClass::write(const uint8_t* buffer, size_t size) 41 | { 42 | std::lock_guard lock(_msgLock); 43 | if (_buff_pos + size < BUFFER_SIZE) { 44 | memcpy(&_buffer[_buff_pos], buffer, size); 45 | _buff_pos += size; 46 | } 47 | _forceSend = true; 48 | 49 | return Serial.write(buffer, size); 50 | } 51 | 52 | void MessageOutputClass::loop() 53 | { 54 | // Send data via websocket if either time is over or buffer is full 55 | if (_forceSend || (millis() - _lastSend > 1000)) { 56 | std::lock_guard lock(_msgLock); 57 | if (_ws && _buff_pos > 0) { 58 | _ws->textAll(_buffer, _buff_pos); 59 | _buff_pos = 0; 60 | } 61 | if (_forceSend) { 62 | _buff_pos = 0; 63 | } 64 | _forceSend = false; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/DevControlCommand.cpp: -------------------------------------------------------------------------------- 1 | 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | /* 4 | * Copyright (C) 2022-2024 Thomas Basler and others 5 | */ 6 | 7 | /* 8 | Derives from CommandAbstract. Has a variable length. 9 | 10 | Command structure: 11 | * ID: fixed identifier and everytime 0x51 12 | * Cmd: Fixed at 0x81 for these types of commands 13 | * Payload: dynamic amount of bytes 14 | * CRC16: calcuclated over the highlighted amount of bytes 15 | 16 | 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 17 | ------------------------------------------------------------------------------------------------------------- 18 | |<->| CRC16 19 | 51 71 60 35 46 80 12 23 04 81 00 00 00 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20 | ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^^^^ ^^ ^^ 21 | ID Target Addr Source Addr Cmd Payload CRC16 CRC8 22 | */ 23 | #include "DevControlCommand.h" 24 | #include "crc.h" 25 | 26 | DevControlCommand::DevControlCommand(InverterAbstract* inv, const uint64_t router_address) 27 | : CommandAbstract(inv, router_address) 28 | { 29 | _payload[0] = 0x51; 30 | _payload[9] = 0x81; 31 | 32 | setTimeout(1000); 33 | } 34 | 35 | void DevControlCommand::udpateCRC(const uint8_t len) 36 | { 37 | const uint16_t crc = crc16(&_payload[10], len); 38 | _payload[10 + len] = (uint8_t)(crc >> 8); 39 | _payload[10 + len + 1] = (uint8_t)(crc); 40 | } 41 | 42 | bool DevControlCommand::handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id) 43 | { 44 | for (uint8_t i = 0; i < max_fragment_id; i++) { 45 | if (fragment[i].mainCmd != (_payload[0] | 0x80)) { 46 | return false; 47 | } 48 | } 49 | 50 | return true; 51 | } 52 | -------------------------------------------------------------------------------- /include/WebApi_prometheus.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class WebApiPrometheusClass { 10 | public: 11 | void init(AsyncWebServer& server, Scheduler& scheduler); 12 | 13 | private: 14 | void onPrometheusMetricsGet(AsyncWebServerRequest* request); 15 | 16 | void addField(AsyncResponseStream* stream, const String& serial, const uint8_t idx, std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId, const char* metricName, const char* channelName = nullptr); 17 | 18 | void addPanelInfo(AsyncResponseStream* stream, const String& serial, const uint8_t idx, std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel); 19 | 20 | enum MetricType_t { 21 | NONE = 0, 22 | GAUGE, 23 | COUNTER, 24 | }; 25 | const char* _metricTypes[3] = { 0, "gauge", "counter" }; 26 | 27 | struct publish_type_t { 28 | FieldId_t field; 29 | MetricType_t type; 30 | }; 31 | 32 | const publish_type_t _publishFields[14] = { 33 | { FLD_PAC, MetricType_t::GAUGE }, 34 | { FLD_UAC, MetricType_t::GAUGE }, 35 | { FLD_IAC, MetricType_t::GAUGE }, 36 | { FLD_PDC, MetricType_t::GAUGE }, 37 | { FLD_UDC, MetricType_t::GAUGE }, 38 | { FLD_IDC, MetricType_t::GAUGE }, 39 | { FLD_YD, MetricType_t::COUNTER }, 40 | { FLD_YT, MetricType_t::COUNTER }, 41 | { FLD_F, MetricType_t::GAUGE }, 42 | { FLD_T, MetricType_t::GAUGE }, 43 | { FLD_PF, MetricType_t::GAUGE }, 44 | { FLD_Q, MetricType_t::GAUGE }, 45 | { FLD_EFF, MetricType_t::GAUGE }, 46 | { FLD_IRR, MetricType_t::GAUGE }, 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /lib/CpuTemperature/src/CpuTemperature.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2024 Thomas Basler and others 4 | */ 5 | 6 | #include "CpuTemperature.h" 7 | #include 8 | 9 | #if defined(CONFIG_IDF_TARGET_ESP32) 10 | // there is no official API available on the original ESP32 11 | extern "C" { 12 | uint8_t temprature_sens_read(); 13 | } 14 | #elif defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) 15 | #include "driver/temp_sensor.h" 16 | #endif 17 | 18 | CpuTemperatureClass CpuTemperature; 19 | 20 | float CpuTemperatureClass::read() 21 | { 22 | std::lock_guard lock(_mutex); 23 | 24 | float temperature = NAN; 25 | bool success = false; 26 | 27 | #if defined(CONFIG_IDF_TARGET_ESP32) 28 | uint8_t raw = temprature_sens_read(); 29 | ESP_LOGV(TAG, "Raw temperature value: %d", raw); 30 | temperature = (raw - 32) / 1.8f; 31 | success = (raw != 128); 32 | #elif defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) 33 | temp_sensor_config_t tsens = TSENS_CONFIG_DEFAULT(); 34 | temp_sensor_set_config(tsens); 35 | temp_sensor_start(); 36 | #if defined(CONFIG_IDF_TARGET_ESP32S3) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 3)) 37 | #error \ 38 | "ESP32-S3 internal temperature sensor requires ESP IDF V4.4.3 or higher. See https://github.com/esphome/issues/issues/4271" 39 | #endif 40 | esp_err_t result = temp_sensor_read_celsius(&temperature); 41 | temp_sensor_stop(); 42 | success = (result == ESP_OK); 43 | #endif 44 | 45 | if (success && std::isfinite(temperature)) { 46 | return temperature; 47 | } else { 48 | ESP_LOGD(TAG, "Ignoring invalid temperature (success=%d, value=%.1f)", success, temperature); 49 | return NAN; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/MqttHandleInverterTotal.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2023-2024 Thomas Basler and others 4 | */ 5 | #include "MqttHandleInverterTotal.h" 6 | #include "Configuration.h" 7 | #include "Datastore.h" 8 | #include "MqttSettings.h" 9 | #include 10 | 11 | MqttHandleInverterTotalClass MqttHandleInverterTotal; 12 | 13 | MqttHandleInverterTotalClass::MqttHandleInverterTotalClass() 14 | : _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&MqttHandleInverterTotalClass::loop, this)) 15 | { 16 | } 17 | 18 | void MqttHandleInverterTotalClass::init(Scheduler& scheduler) 19 | { 20 | scheduler.addTask(_loopTask); 21 | _loopTask.setInterval(Configuration.get().Mqtt.PublishInterval * TASK_SECOND); 22 | _loopTask.enable(); 23 | } 24 | 25 | void MqttHandleInverterTotalClass::loop() 26 | { 27 | // Update interval from config 28 | _loopTask.setInterval(Configuration.get().Mqtt.PublishInterval * TASK_SECOND); 29 | 30 | if (!MqttSettings.getConnected() || !Hoymiles.isAllRadioIdle()) { 31 | _loopTask.forceNextIteration(); 32 | return; 33 | } 34 | 35 | MqttSettings.publish("ac/power", String(Datastore.getTotalAcPowerEnabled(), Datastore.getTotalAcPowerDigits())); 36 | MqttSettings.publish("ac/yieldtotal", String(Datastore.getTotalAcYieldTotalEnabled(), Datastore.getTotalAcYieldTotalDigits())); 37 | MqttSettings.publish("ac/yieldday", String(Datastore.getTotalAcYieldDayEnabled(), Datastore.getTotalAcYieldDayDigits())); 38 | MqttSettings.publish("ac/is_valid", String(Datastore.getIsAllEnabledReachable())); 39 | MqttSettings.publish("dc/power", String(Datastore.getTotalDcPowerEnabled(), Datastore.getTotalDcPowerDigits())); 40 | MqttSettings.publish("dc/irradiation", String(Datastore.getTotalDcIrradiation(), 3)); 41 | MqttSettings.publish("dc/is_valid", String(Datastore.getIsAllEnabledReachable())); 42 | } 43 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/RequestFrameCommand.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2022-2024 Thomas Basler and others 4 | */ 5 | 6 | /* 7 | This command is used to re-request a specific fragment returned by a MultiDataCommand from the inverter. 8 | 9 | Derives from SingleDataCommand. Has a fixed length of 10 bytes. 10 | 11 | Command structure: 12 | * ID: fixed identifier and everytime 0x15 13 | * Idx: the counter of sequencial packages to send. Currently it's only 0x80 14 | because all request requests only consist of one package. 15 | * Frm: is set to the fragment id to re-request. "Or" operation with 0x80 is applied to the frame. 16 | 17 | 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 18 | --------------------------------------------------------------------------------------------------------- 19 | 15 71 60 35 46 80 12 23 04 85 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20 | ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ 21 | ID Target Addr Source Addr Frm CRC8 22 | */ 23 | #include "RequestFrameCommand.h" 24 | 25 | RequestFrameCommand::RequestFrameCommand(InverterAbstract* inv, const uint64_t router_address, uint8_t frame_no) 26 | : SingleDataCommand(inv, router_address) 27 | { 28 | if (frame_no > 127) { 29 | frame_no = 0; 30 | } 31 | setFrameNo(frame_no); 32 | _payload_size = 10; 33 | } 34 | 35 | String RequestFrameCommand::getCommandName() const 36 | { 37 | return "RequestFrame"; 38 | } 39 | 40 | void RequestFrameCommand::setFrameNo(const uint8_t frame_no) 41 | { 42 | _payload[9] = frame_no | 0x80; 43 | } 44 | 45 | uint8_t RequestFrameCommand::getFrameNo() const 46 | { 47 | return _payload[9] & (~0x80); 48 | } 49 | 50 | bool RequestFrameCommand::handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id) 51 | { 52 | return true; 53 | } 54 | -------------------------------------------------------------------------------- /docs/DeviceProfiles/AhoyDTU-ESP32.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "AhoyDTU ESP32 Display LED", 4 | "links": [ 5 | {"name": "Information", "url": "https://ahoydtu.de/getting_started/"} 6 | ], 7 | "nrf24": { 8 | "miso": 19, 9 | "mosi": 23, 10 | "clk": 18, 11 | "irq": 16, 12 | "en": 4, 13 | "cs": 5 14 | }, 15 | "led": { 16 | "led0": 25, 17 | "led1": 26 18 | }, 19 | "display": { 20 | "type": 2, 21 | "data": 21, 22 | "clk": 22 23 | } 24 | }, 25 | { 26 | "name": "AhoyDTU ESP32 Display", 27 | "links": [ 28 | {"name": "Information", "url": "https://ahoydtu.de/getting_started/"} 29 | ], 30 | "nrf24": { 31 | "miso": 19, 32 | "mosi": 23, 33 | "clk": 18, 34 | "irq": 16, 35 | "en": 4, 36 | "cs": 5 37 | }, 38 | "display": { 39 | "type": 2, 40 | "data": 21, 41 | "clk": 22 42 | } 43 | }, 44 | { 45 | "name": "AhoyDTU ESP32 LED", 46 | "links": [ 47 | {"name": "Information", "url": "https://ahoydtu.de/getting_started/"} 48 | ], 49 | "nrf24": { 50 | "miso": 19, 51 | "mosi": 23, 52 | "clk": 18, 53 | "irq": 16, 54 | "en": 4, 55 | "cs": 5 56 | }, 57 | "led": { 58 | "led0": 25, 59 | "led1": 26 60 | } 61 | }, 62 | { 63 | "name": "AhoyDTU ESP32", 64 | "links": [ 65 | {"name": "Information", "url": "https://ahoydtu.de/getting_started/"} 66 | ], 67 | "nrf24": { 68 | "miso": 19, 69 | "mosi": 23, 70 | "clk": 18, 71 | "irq": 16, 72 | "en": 4, 73 | "cs": 5 74 | } 75 | } 76 | ] -------------------------------------------------------------------------------- /lib/Hoymiles/src/parser/AlarmLogParser.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | #include "Parser.h" 4 | #include 5 | #include 6 | 7 | #define ALARM_LOG_ENTRY_COUNT 15 8 | #define ALARM_LOG_ENTRY_SIZE 12 9 | #define ALARM_LOG_PAYLOAD_SIZE (ALARM_LOG_ENTRY_COUNT * ALARM_LOG_ENTRY_SIZE + 4) 10 | 11 | #define ALARM_MSG_COUNT 131 12 | 13 | struct AlarmLogEntry_t { 14 | uint16_t MessageId; 15 | String Message; 16 | time_t StartTime; 17 | time_t EndTime; 18 | }; 19 | 20 | enum class AlarmMessageType_t { 21 | ALL = 0, 22 | HMT 23 | }; 24 | 25 | enum class AlarmMessageLocale_t { 26 | EN, 27 | DE, 28 | FR 29 | }; 30 | 31 | typedef struct { 32 | AlarmMessageType_t InverterType; 33 | uint16_t MessageId; 34 | const char* Message_en; 35 | const char* Message_de; 36 | const char* Message_fr; 37 | } AlarmMessage_t; 38 | 39 | class AlarmLogParser : public Parser { 40 | public: 41 | AlarmLogParser(); 42 | void clearBuffer(); 43 | void appendFragment(const uint8_t offset, const uint8_t* payload, const uint8_t len); 44 | 45 | uint8_t getEntryCount() const; 46 | void getLogEntry(const uint8_t entryId, AlarmLogEntry_t& entry, const AlarmMessageLocale_t locale = AlarmMessageLocale_t::EN); 47 | 48 | void setLastAlarmRequestSuccess(const LastCommandSuccess status); 49 | LastCommandSuccess getLastAlarmRequestSuccess() const; 50 | 51 | void setMessageType(const AlarmMessageType_t type); 52 | 53 | private: 54 | static int getTimezoneOffset(); 55 | String getLocaleMessage(const AlarmMessage_t* msg, const AlarmMessageLocale_t locale) const; 56 | 57 | uint8_t _payloadAlarmLog[ALARM_LOG_PAYLOAD_SIZE]; 58 | uint8_t _alarmLogLength = 0; 59 | 60 | LastCommandSuccess _lastAlarmRequestSuccess = CMD_NOK; // Set to NOK to fetch at startup 61 | 62 | AlarmMessageType_t _messageType = AlarmMessageType_t::ALL; 63 | 64 | static const std::array _alarmMessages; 65 | }; 66 | -------------------------------------------------------------------------------- /docs/DeviceProfiles/liligo_t-eth-lite_poe.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "LILYGO T-ETH-Lite-POE CMT", 4 | "links": [ 5 | {"name": "Datasheet", "url": "https://www.lilygo.cc/products/t-eth-lite"} 6 | ], 7 | "eth": { 8 | "enabled": true, 9 | "phy_addr": 0, 10 | "power": 12, 11 | "mdc": 23, 12 | "mdio": 18, 13 | "type": 2, 14 | "clk_mode": 0 15 | }, 16 | "cmt": { 17 | "clk": 15, 18 | "cs": 32, 19 | "fcs": 33, 20 | "sdio": 4 21 | } 22 | }, 23 | { 24 | "name": "LILYGO T-ETH-Lite-POE NRF24", 25 | "links": [ 26 | {"name": "Datasheet", "url": "https://www.lilygo.cc/products/t-eth-lite"} 27 | ], 28 | "eth": { 29 | "enabled": true, 30 | "phy_addr": 0, 31 | "power": 12, 32 | "mdc": 23, 33 | "mdio": 18, 34 | "type": 2, 35 | "clk_mode": 0 36 | }, 37 | "nrf24": { 38 | "miso": 34, 39 | "mosi": 13, 40 | "clk": 14, 41 | "irq": 35, 42 | "en": 4, 43 | "cs": 2 44 | } 45 | }, 46 | { 47 | "name": "LILYGO T-ETH-Lite-POE NRF24 + Display", 48 | "links": [ 49 | {"name": "Datasheet", "url": "https://www.lilygo.cc/products/t-eth-lite"} 50 | ], 51 | "eth": { 52 | "enabled": true, 53 | "phy_addr": 0, 54 | "power": 12, 55 | "mdc": 23, 56 | "mdio": 18, 57 | "type": 2, 58 | "clk_mode": 0 59 | }, 60 | "nrf24": { 61 | "miso": 34, 62 | "mosi": 13, 63 | "clk": 14, 64 | "irq": 35, 65 | "en": 4, 66 | "cs": 2 67 | }, 68 | "display": { 69 | "type": 3, 70 | "data": 32, 71 | "clk": 33 72 | } 73 | } 74 | ] 75 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/Hoymiles.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "HoymilesRadio_CMT.h" 5 | #include "HoymilesRadio_NRF.h" 6 | #include "inverters/InverterAbstract.h" 7 | #include "types.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define HOY_SYSTEM_CONFIG_PARA_POLL_INTERVAL (2 * 60 * 1000) // 2 minutes 14 | #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 15 | 16 | class HoymilesClass { 17 | public: 18 | void init(); 19 | void initNRF(SPIClass* initialisedSpiBus, const uint8_t pinCE, const uint8_t pinIRQ); 20 | 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); 21 | void loop(); 22 | 23 | void setMessageOutput(Print* output); 24 | Print* getMessageOutput(); 25 | 26 | std::shared_ptr addInverter(const char* name, const uint64_t serial); 27 | std::shared_ptr getInverterByPos(const uint8_t pos); 28 | std::shared_ptr getInverterBySerial(const uint64_t serial); 29 | std::shared_ptr getInverterByFragment(const fragment_t& fragment); 30 | void removeInverterBySerial(const uint64_t serial); 31 | size_t getNumInverters() const; 32 | 33 | HoymilesRadio_NRF* getRadioNrf(); 34 | HoymilesRadio_CMT* getRadioCmt(); 35 | 36 | uint32_t PollInterval() const; 37 | void setPollInterval(const uint32_t interval); 38 | 39 | bool isAllRadioIdle() const; 40 | 41 | private: 42 | std::vector> _inverters; 43 | std::unique_ptr _radioNrf; 44 | std::unique_ptr _radioCmt; 45 | 46 | std::mutex _mutex; 47 | 48 | uint32_t _pollInterval = 0; 49 | uint32_t _lastPoll = 0; 50 | 51 | Print* _messageOutput = &Serial; 52 | }; 53 | 54 | extern HoymilesClass Hoymiles; -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/CommandAbstract.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "types.h" 5 | #include 6 | #include 7 | 8 | #define RF_LEN 32 9 | #define MAX_RESEND_COUNT 4 // Used if all packages are missing 10 | #define MAX_RETRANSMIT_COUNT 5 // Used to send the retransmit package 11 | 12 | class InverterAbstract; 13 | 14 | class CommandAbstract { 15 | public: 16 | explicit CommandAbstract(InverterAbstract* inv, const uint64_t router_address = 0); 17 | virtual ~CommandAbstract() {}; 18 | 19 | const uint8_t* getDataPayload(); 20 | void dumpDataPayload(Print* stream); 21 | 22 | uint8_t getDataSize() const; 23 | 24 | uint64_t getTargetAddress() const; 25 | 26 | void setRouterAddress(const uint64_t address); 27 | uint64_t getRouterAddress() const; 28 | 29 | void setTimeout(const uint32_t timeout); 30 | uint32_t getTimeout() const; 31 | 32 | virtual String getCommandName() const = 0; 33 | 34 | void setSendCount(const uint8_t count); 35 | uint8_t getSendCount() const; 36 | uint8_t incrementSendCount(); 37 | 38 | virtual CommandAbstract* getRequestFrameCommand(const uint8_t frame_no); 39 | 40 | virtual bool handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id) = 0; 41 | virtual void gotTimeout(); 42 | 43 | // Sets the amount how often the specific command is resent if all fragments where missing 44 | virtual uint8_t getMaxResendCount() const; 45 | 46 | // Sets the amount how often a missing fragment is re-requested if it was not available 47 | virtual uint8_t getMaxRetransmitCount() const; 48 | 49 | protected: 50 | uint8_t _payload[RF_LEN]; 51 | uint8_t _payload_size; 52 | uint32_t _timeout; 53 | uint8_t _sendCount; 54 | 55 | uint64_t _targetAddress; 56 | uint64_t _routerAddress; 57 | 58 | InverterAbstract* _inv; 59 | 60 | private: 61 | void setTargetAddress(const uint64_t address); 62 | static void convertSerialToPacketId(uint8_t buffer[], const uint64_t serial); 63 | }; 64 | -------------------------------------------------------------------------------- /lib/Frozen/frozen/bits/elsa.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Frozen 3 | * Copyright 2016 QuarksLab 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | */ 22 | 23 | #ifndef FROZEN_LETITGO_ELSA_H 24 | #define FROZEN_LETITGO_ELSA_H 25 | 26 | #include 27 | 28 | namespace frozen { 29 | 30 | template struct elsa { 31 | static_assert(std::is_integral::value || std::is_enum::value, 32 | "only supports integral types, specialize for other types"); 33 | 34 | constexpr std::size_t operator()(T const &value, std::size_t seed) const { 35 | std::size_t key = seed ^ static_cast(value); 36 | key = (~key) + (key << 21); // key = (key << 21) - key - 1; 37 | key = key ^ (key >> 24); 38 | key = (key + (key << 3)) + (key << 8); // key * 265 39 | key = key ^ (key >> 14); 40 | key = (key + (key << 2)) + (key << 4); // key * 21 41 | key = key ^ (key >> 28); 42 | key = key + (key << 31); 43 | return key; 44 | } 45 | }; 46 | 47 | template <> struct elsa { 48 | template 49 | constexpr std::size_t operator()(T const &value, std::size_t seed) const { 50 | return elsa{}(value, seed); 51 | } 52 | }; 53 | 54 | template using anna = elsa; 55 | } // namespace frozen 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /webapp/src/views/NetworkInfoView.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 59 | -------------------------------------------------------------------------------- /webapp/src/components/ModalDialog.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 57 | -------------------------------------------------------------------------------- /docs/DeviceProfiles/lilygo_ttgo_t-internet_poe.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "LILYGO TTGO T-Internet-POE", 4 | "links": [ 5 | {"name": "Datasheet", "url": "https://www.lilygo.cc/products/t-internet-poe"} 6 | ], 7 | "nrf24": { 8 | "miso": 2, 9 | "mosi": 15, 10 | "clk": 14, 11 | "irq": 34, 12 | "en": 12, 13 | "cs": 4 14 | }, 15 | "eth": { 16 | "enabled": true, 17 | "phy_addr": 0, 18 | "power": -1, 19 | "mdc": 23, 20 | "mdio": 18, 21 | "type": 0, 22 | "clk_mode": 3 23 | } 24 | }, 25 | { 26 | "name": "LILYGO TTGO T-Internet-POE, nrf24 direct solder", 27 | "links": [ 28 | {"name": "Datasheet", "url": "https://www.lilygo.cc/products/t-internet-poe"} 29 | ], 30 | "nrf24": { 31 | "miso": 12, 32 | "mosi": 4, 33 | "clk": 15, 34 | "irq": 33, 35 | "en": 14, 36 | "cs": 2 37 | }, 38 | "eth": { 39 | "enabled": true, 40 | "phy_addr": 0, 41 | "power": -1, 42 | "mdc": 23, 43 | "mdio": 18, 44 | "type": 0, 45 | "clk_mode": 3 46 | } 47 | }, 48 | { 49 | "name": "LILYGO TTGO T-Internet-POE, nrf24 direct solder, SSD1306", 50 | "links": [ 51 | {"name": "Datasheet", "url": "https://www.lilygo.cc/products/t-internet-poe"} 52 | ], 53 | "nrf24": { 54 | "miso": 12, 55 | "mosi": 4, 56 | "clk": 15, 57 | "irq": 33, 58 | "en": 14, 59 | "cs": 2 60 | }, 61 | "eth": { 62 | "enabled": true, 63 | "phy_addr": 0, 64 | "power": -1, 65 | "mdc": 23, 66 | "mdio": 18, 67 | "type": 0, 68 | "clk_mode": 3 69 | }, 70 | "display": { 71 | "type": 2, 72 | "data": 16, 73 | "clk": 32 74 | } 75 | } 76 | ] -------------------------------------------------------------------------------- /src/WebApi_eventlog.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2022-2024 Thomas Basler and others 4 | */ 5 | #include "WebApi_eventlog.h" 6 | #include "WebApi.h" 7 | #include 8 | #include 9 | 10 | void WebApiEventlogClass::init(AsyncWebServer& server, Scheduler& scheduler) 11 | { 12 | using std::placeholders::_1; 13 | 14 | server.on("/api/eventlog/status", HTTP_GET, std::bind(&WebApiEventlogClass::onEventlogStatus, this, _1)); 15 | } 16 | 17 | void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request) 18 | { 19 | if (!WebApi.checkCredentialsReadonly(request)) { 20 | return; 21 | } 22 | 23 | AsyncJsonResponse* response = new AsyncJsonResponse(); 24 | auto& root = response->getRoot(); 25 | auto serial = WebApi.parseSerialFromRequest(request); 26 | 27 | AlarmMessageLocale_t locale = AlarmMessageLocale_t::EN; 28 | if (request->hasParam("locale")) { 29 | String s = request->getParam("locale")->value(); 30 | s.toLowerCase(); 31 | if (s == "de") { 32 | locale = AlarmMessageLocale_t::DE; 33 | } 34 | if (s == "fr") { 35 | locale = AlarmMessageLocale_t::FR; 36 | } 37 | } 38 | 39 | auto inv = Hoymiles.getInverterBySerial(serial); 40 | 41 | if (inv != nullptr) { 42 | uint8_t logEntryCount = inv->EventLog()->getEntryCount(); 43 | 44 | root["count"] = logEntryCount; 45 | JsonArray eventsArray = root["events"].to(); 46 | 47 | for (uint8_t logEntry = 0; logEntry < logEntryCount; logEntry++) { 48 | JsonObject eventsObject = eventsArray.add(); 49 | 50 | AlarmLogEntry_t entry; 51 | inv->EventLog()->getLogEntry(logEntry, entry, locale); 52 | 53 | eventsObject["message_id"] = entry.MessageId; 54 | eventsObject["message"] = entry.Message; 55 | eventsObject["start_time"] = entry.StartTime; 56 | eventsObject["end_time"] = entry.EndTime; 57 | } 58 | } 59 | 60 | WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); 61 | } 62 | -------------------------------------------------------------------------------- /.github/workflows/repo-maintenance.yml: -------------------------------------------------------------------------------- 1 | name: 'Repository Maintenance' 2 | 3 | on: 4 | schedule: 5 | - cron: '0 4 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | discussions: write 12 | 13 | concurrency: 14 | group: lock 15 | 16 | jobs: 17 | stale: 18 | name: 'Stale' 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/stale@v9 22 | with: 23 | days-before-stale: 14 24 | days-before-close: 60 25 | any-of-labels: 'cant-reproduce,not a bug' 26 | stale-issue-label: stale 27 | stale-pr-label: stale 28 | stale-issue-message: > 29 | This issue has been automatically marked as stale because it has not had 30 | recent activity. It will be closed if no further activity occurs. Thank you 31 | for your contributions. 32 | 33 | lock-threads: 34 | name: 'Lock Old Threads' 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: dessant/lock-threads@v5 38 | with: 39 | issue-inactive-days: '30' 40 | pr-inactive-days: '30' 41 | discussion-inactive-days: '30' 42 | log-output: true 43 | issue-comment: > 44 | This issue has been automatically locked since there 45 | has not been any recent activity after it was closed. 46 | Please open a new discussion or issue for related concerns. 47 | pr-comment: > 48 | This pull request has been automatically locked since there 49 | has not been any recent activity after it was closed. 50 | Please open a new discussion or issue for related concerns. 51 | discussion-comment: > 52 | This discussion has been automatically locked since there 53 | has not been any recent activity after it was closed. 54 | Please open a new discussion for related concerns. 55 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/HMS_1CH.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2023-2024 Thomas Basler and others 4 | */ 5 | #include "HMS_1CH.h" 6 | 7 | static const byteAssign_t byteAssignment[] = { 8 | { TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 }, 9 | { TYPE_DC, CH0, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 }, 10 | { TYPE_DC, CH0, FLD_PDC, UNIT_W, 6, 2, 10, false, 1 }, 11 | { TYPE_DC, CH0, FLD_YD, UNIT_WH, 12, 2, 1, false, 0 }, 12 | { TYPE_DC, CH0, FLD_YT, UNIT_KWH, 8, 4, 1000, false, 3 }, 13 | { TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_CH_IRR, CH0, CMD_CALC, false, 3 }, 14 | 15 | { TYPE_AC, CH0, FLD_UAC, UNIT_V, 14, 2, 10, false, 1 }, 16 | { TYPE_AC, CH0, FLD_IAC, UNIT_A, 22, 2, 100, false, 2 }, 17 | { TYPE_AC, CH0, FLD_PAC, UNIT_W, 18, 2, 10, false, 1 }, 18 | { TYPE_AC, CH0, FLD_Q, UNIT_VAR, 20, 2, 10, false, 1 }, 19 | { TYPE_AC, CH0, FLD_F, UNIT_HZ, 16, 2, 100, false, 2 }, 20 | { TYPE_AC, CH0, FLD_PF, UNIT_NONE, 24, 2, 1000, false, 3 }, 21 | 22 | { TYPE_INV, CH0, FLD_T, UNIT_C, 26, 2, 10, true, 1 }, 23 | { TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 28, 2, 1, false, 0 }, 24 | 25 | { TYPE_INV, CH0, FLD_YD, UNIT_WH, CALC_TOTAL_YD, 0, CMD_CALC, false, 0 }, 26 | { TYPE_INV, CH0, FLD_YT, UNIT_KWH, CALC_TOTAL_YT, 0, CMD_CALC, false, 3 }, 27 | { TYPE_INV, CH0, FLD_PDC, UNIT_W, CALC_TOTAL_PDC, 0, CMD_CALC, false, 1 }, 28 | { TYPE_INV, CH0, FLD_EFF, UNIT_PCT, CALC_TOTAL_EFF, 0, CMD_CALC, false, 3 } 29 | }; 30 | 31 | HMS_1CH::HMS_1CH(HoymilesRadio* radio, const uint64_t serial) 32 | : HMS_Abstract(radio, serial) {}; 33 | 34 | bool HMS_1CH::isValidSerial(const uint64_t serial) 35 | { 36 | // serial >= 0x112400000000 && serial <= 0x1124ffffffff 37 | uint16_t preSerial = (serial >> 32) & 0xffff; 38 | return preSerial == 0x1124; 39 | } 40 | 41 | String HMS_1CH::typeName() const 42 | { 43 | return "HMS-300/350/400/450/500-1T"; 44 | } 45 | 46 | const byteAssign_t* HMS_1CH::getByteAssignment() const 47 | { 48 | return byteAssignment; 49 | } 50 | 51 | uint8_t HMS_1CH::getByteAssignmentSize() const 52 | { 53 | return sizeof(byteAssignment) / sizeof(byteAssignment[0]); 54 | } 55 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/HMS_1CHv2.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2023-2024 Thomas Basler and others 4 | */ 5 | #include "HMS_1CHv2.h" 6 | 7 | static const byteAssign_t byteAssignment[] = { 8 | { TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 }, 9 | { TYPE_DC, CH0, FLD_IDC, UNIT_A, 6, 2, 100, false, 2 }, 10 | { TYPE_DC, CH0, FLD_PDC, UNIT_W, 10, 2, 10, false, 1 }, 11 | { TYPE_DC, CH0, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 }, 12 | { TYPE_DC, CH0, FLD_YT, UNIT_KWH, 14, 4, 1000, false, 3 }, 13 | { TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_CH_IRR, CH0, CMD_CALC, false, 3 }, 14 | 15 | { TYPE_AC, CH0, FLD_UAC, UNIT_V, 26, 2, 10, false, 1 }, 16 | { TYPE_AC, CH0, FLD_IAC, UNIT_A, 34, 2, 100, false, 2 }, 17 | { TYPE_AC, CH0, FLD_PAC, UNIT_W, 30, 2, 10, false, 1 }, 18 | { TYPE_AC, CH0, FLD_Q, UNIT_VAR, 20, 2, 10, false, 1 }, 19 | { TYPE_AC, CH0, FLD_F, UNIT_HZ, 28, 2, 100, false, 2 }, 20 | { TYPE_AC, CH0, FLD_PF, UNIT_NONE, 36, 2, 1000, false, 3 }, 21 | 22 | { TYPE_INV, CH0, FLD_T, UNIT_C, 38, 2, 10, true, 1 }, 23 | { TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 18, 2, 1, false, 0 }, 24 | 25 | { TYPE_INV, CH0, FLD_YD, UNIT_WH, CALC_TOTAL_YD, 0, CMD_CALC, false, 0 }, 26 | { TYPE_INV, CH0, FLD_YT, UNIT_KWH, CALC_TOTAL_YT, 0, CMD_CALC, false, 3 }, 27 | { TYPE_INV, CH0, FLD_PDC, UNIT_W, CALC_TOTAL_PDC, 0, CMD_CALC, false, 1 }, 28 | { TYPE_INV, CH0, FLD_EFF, UNIT_PCT, CALC_TOTAL_EFF, 0, CMD_CALC, false, 3 } 29 | }; 30 | 31 | HMS_1CHv2::HMS_1CHv2(HoymilesRadio* radio, const uint64_t serial) 32 | : HMS_Abstract(radio, serial) {}; 33 | 34 | bool HMS_1CHv2::isValidSerial(const uint64_t serial) 35 | { 36 | // serial >= 0x112500000000 && serial <= 0x1125ffffffff 37 | uint16_t preSerial = (serial >> 32) & 0xffff; 38 | return preSerial == 0x1125; 39 | } 40 | 41 | String HMS_1CHv2::typeName() const 42 | { 43 | return "HMS-500-1T v2"; 44 | } 45 | 46 | const byteAssign_t* HMS_1CHv2::getByteAssignment() const 47 | { 48 | return byteAssignment; 49 | } 50 | 51 | uint8_t HMS_1CHv2::getByteAssignmentSize() const 52 | { 53 | return sizeof(byteAssignment) / sizeof(byteAssignment[0]); 54 | } 55 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/DevInfoAllCommand.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2022-2024 Thomas Basler and others 4 | */ 5 | 6 | /* 7 | This command is used to fetch firmware information from the inverter. 8 | 9 | Derives from MultiDataCommand 10 | 11 | Command structure: 12 | * DT: this specific command uses 0x01 13 | 14 | 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 15 | ----------------------------------------------------------------------------------------------------------------------- 16 | |<------------------- CRC16 --------------------->| 17 | 15 71 60 35 46 80 12 23 04 80 01 00 65 72 06 B8 00 00 00 00 00 00 00 00 00 00 00 -- -- -- -- -- 18 | ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ ^^ ^^^^^^^^^^^ ^^^^^ ^^^^^^^^^^^ ^^^^^ ^^ 19 | ID Target Addr Source Addr Idx DT ? Time Gap Password CRC16 CRC8 20 | */ 21 | #include "DevInfoAllCommand.h" 22 | #include "inverters/InverterAbstract.h" 23 | 24 | DevInfoAllCommand::DevInfoAllCommand(InverterAbstract* inv, const uint64_t router_address, const time_t time) 25 | : MultiDataCommand(inv, router_address) 26 | { 27 | setTime(time); 28 | setDataType(0x01); 29 | setTimeout(200); 30 | } 31 | 32 | String DevInfoAllCommand::getCommandName() const 33 | { 34 | return "DevInfoAll"; 35 | } 36 | 37 | bool DevInfoAllCommand::handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id) 38 | { 39 | // Check CRC of whole payload 40 | if (!MultiDataCommand::handleResponse(fragment, max_fragment_id)) { 41 | return false; 42 | } 43 | 44 | // Move all fragments into target buffer 45 | uint8_t offs = 0; 46 | _inv->DevInfo()->beginAppendFragment(); 47 | _inv->DevInfo()->clearBufferAll(); 48 | for (uint8_t i = 0; i < max_fragment_id; i++) { 49 | _inv->DevInfo()->appendFragmentAll(offs, fragment[i].fragment, fragment[i].len); 50 | offs += (fragment[i].len); 51 | } 52 | _inv->DevInfo()->endAppendFragment(); 53 | _inv->DevInfo()->setLastUpdateAll(millis()); 54 | return true; 55 | } 56 | -------------------------------------------------------------------------------- /webapp/src/locales/index.ts: -------------------------------------------------------------------------------- 1 | import type { I18nOptions } from 'vue-i18n'; 2 | 3 | export enum Locales { 4 | EN = 'en', 5 | DE = 'de', 6 | FR = 'fr', 7 | } 8 | 9 | export const LOCALES = [ 10 | { value: Locales.EN, caption: 'English' }, 11 | { value: Locales.DE, caption: 'Deutsch' }, 12 | { value: Locales.FR, caption: 'Français' }, 13 | ]; 14 | 15 | export const dateTimeFormats: I18nOptions['datetimeFormats'] = {}; 16 | export const numberFormats: I18nOptions['numberFormats'] = {}; 17 | 18 | LOCALES.forEach((locale) => { 19 | dateTimeFormats[locale.value] = { 20 | datetime: { 21 | hour: 'numeric', 22 | minute: 'numeric', 23 | second: 'numeric', 24 | year: 'numeric', 25 | month: 'numeric', 26 | day: 'numeric', 27 | hour12: false, 28 | }, 29 | }; 30 | 31 | numberFormats[locale.value] = { 32 | decimal: { 33 | style: 'decimal', 34 | }, 35 | decimalNoDigits: { 36 | style: 'decimal', 37 | minimumFractionDigits: 0, 38 | maximumFractionDigits: 0, 39 | }, 40 | decimalOneDigit: { 41 | style: 'decimal', 42 | minimumFractionDigits: 1, 43 | maximumFractionDigits: 1, 44 | }, 45 | decimalTwoDigits: { 46 | style: 'decimal', 47 | minimumFractionDigits: 2, 48 | maximumFractionDigits: 2, 49 | }, 50 | percent: { 51 | style: 'percent', 52 | }, 53 | percentOneDigit: { 54 | style: 'percent', 55 | minimumFractionDigits: 1, 56 | maximumFractionDigits: 1, 57 | }, 58 | byte: { 59 | style: 'unit', 60 | unit: 'byte', 61 | }, 62 | kilobyte: { 63 | style: 'unit', 64 | unit: 'kilobyte', 65 | }, 66 | megabyte: { 67 | style: 'unit', 68 | unit: 'megabyte', 69 | }, 70 | celsius: { 71 | style: 'unit', 72 | unit: 'celsius', 73 | maximumFractionDigits: 1, 74 | }, 75 | }; 76 | }); 77 | 78 | export const defaultLocale = Locales.EN; 79 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/DevInfoSimpleCommand.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2022-2024 Thomas Basler and others 4 | */ 5 | 6 | /* 7 | This command is used to fetch hardware information from the inverter. 8 | 9 | Derives from MultiDataCommand 10 | 11 | Command structure: 12 | * DT: this specific command uses 0x00 13 | 14 | 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 15 | ----------------------------------------------------------------------------------------------------------------------- 16 | |<------------------- CRC16 --------------------->| 17 | 15 71 60 35 46 80 12 23 04 80 00 00 65 72 06 B8 00 00 00 00 00 00 00 00 00 00 00 -- -- -- -- -- 18 | ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ ^^ ^^^^^^^^^^^ ^^^^^ ^^^^^^^^^^^ ^^^^^ ^^ 19 | ID Target Addr Source Addr Idx DT ? Time Gap Password CRC16 CRC8 20 | */ 21 | #include "DevInfoSimpleCommand.h" 22 | #include "inverters/InverterAbstract.h" 23 | 24 | DevInfoSimpleCommand::DevInfoSimpleCommand(InverterAbstract* inv, const uint64_t router_address, const time_t time) 25 | : MultiDataCommand(inv, router_address) 26 | { 27 | setTime(time); 28 | setDataType(0x00); 29 | setTimeout(200); 30 | } 31 | 32 | String DevInfoSimpleCommand::getCommandName() const 33 | { 34 | return "DevInfoSimple"; 35 | } 36 | 37 | bool DevInfoSimpleCommand::handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id) 38 | { 39 | // Check CRC of whole payload 40 | if (!MultiDataCommand::handleResponse(fragment, max_fragment_id)) { 41 | return false; 42 | } 43 | 44 | // Move all fragments into target buffer 45 | uint8_t offs = 0; 46 | _inv->DevInfo()->beginAppendFragment(); 47 | _inv->DevInfo()->clearBufferSimple(); 48 | for (uint8_t i = 0; i < max_fragment_id; i++) { 49 | _inv->DevInfo()->appendFragmentSimple(offs, fragment[i].fragment, fragment[i].len); 50 | offs += (fragment[i].len); 51 | } 52 | _inv->DevInfo()->endAppendFragment(); 53 | _inv->DevInfo()->setLastUpdateSimple(millis()); 54 | return true; 55 | } 56 | -------------------------------------------------------------------------------- /webapp/src/components/HardwareInfo.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 53 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/commands/GridOnProFilePara.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2022-2024 Thomas Basler and others 4 | */ 5 | 6 | /* 7 | This command is used to fetch the grid profile from the inverter. 8 | 9 | Derives from MultiDataCommand 10 | 11 | Command structure: 12 | * DT: this specific command uses 0x02 13 | 14 | 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 15 | ----------------------------------------------------------------------------------------------------------------------- 16 | |<------------------- CRC16 --------------------->| 17 | 15 71 60 35 46 80 12 23 04 80 02 00 65 72 06 B8 00 00 00 00 00 00 00 00 00 00 00 -- -- -- -- -- 18 | ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ ^^ ^^^^^^^^^^^ ^^^^^ ^^^^^^^^^^^ ^^^^^ ^^ 19 | ID Target Addr Source Addr Idx DT ? Time Gap Password CRC16 CRC8 20 | */ 21 | #include "GridOnProFilePara.h" 22 | #include "Hoymiles.h" 23 | #include "inverters/InverterAbstract.h" 24 | 25 | GridOnProFilePara::GridOnProFilePara(InverterAbstract* inv, const uint64_t router_address, const time_t time) 26 | : MultiDataCommand(inv, router_address) 27 | { 28 | setTime(time); 29 | setDataType(0x02); 30 | setTimeout(500); 31 | } 32 | 33 | String GridOnProFilePara::getCommandName() const 34 | { 35 | return "GridOnProFilePara"; 36 | } 37 | 38 | bool GridOnProFilePara::handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id) 39 | { 40 | // Check CRC of whole payload 41 | if (!MultiDataCommand::handleResponse(fragment, max_fragment_id)) { 42 | return false; 43 | } 44 | 45 | // Move all fragments into target buffer 46 | uint8_t offs = 0; 47 | _inv->GridProfile()->beginAppendFragment(); 48 | _inv->GridProfile()->clearBuffer(); 49 | for (uint8_t i = 0; i < max_fragment_id; i++) { 50 | _inv->GridProfile()->appendFragment(offs, fragment[i].fragment, fragment[i].len); 51 | offs += (fragment[i].len); 52 | } 53 | _inv->GridProfile()->endAppendFragment(); 54 | _inv->GridProfile()->setLastUpdate(millis()); 55 | return true; 56 | } 57 | -------------------------------------------------------------------------------- /include/Display_Graphic.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "Display_Graphic_Diagram.h" 5 | #include "defaults.h" 6 | #include 7 | #include 8 | 9 | #define CHART_HEIGHT 20 // chart area hight in pixels 10 | #define CHART_WIDTH 47 // chart area width in pixels 11 | 12 | // Left-Upper position of diagram is drawn 13 | // (text of Y-axis is display left of that pos) 14 | #define CHART_POSX 80 15 | #define CHART_POSY 0 16 | 17 | enum DisplayType_t { 18 | None, 19 | PCD8544, 20 | SSD1306, 21 | SH1106, 22 | SSD1309, 23 | ST7567_GM12864I_59N, 24 | DisplayType_Max, 25 | }; 26 | 27 | enum DiagramMode_t { 28 | Off, 29 | Small, 30 | Fullscreen, 31 | DisplayMode_Max, 32 | }; 33 | 34 | class DisplayGraphicClass { 35 | public: 36 | DisplayGraphicClass(); 37 | ~DisplayGraphicClass(); 38 | 39 | void init(Scheduler& scheduler, const DisplayType_t type, const uint8_t data, const uint8_t clk, const uint8_t cs, const uint8_t reset); 40 | void setContrast(const uint8_t contrast); 41 | void setStatus(const bool turnOn); 42 | void setOrientation(const uint8_t rotation = DISPLAY_ROTATION); 43 | void setLanguage(const uint8_t language); 44 | void setDiagramMode(DiagramMode_t mode); 45 | void setStartupDisplay(); 46 | 47 | DisplayGraphicDiagramClass& Diagram(); 48 | 49 | bool enablePowerSafe = true; 50 | bool enableScreensaver = true; 51 | 52 | private: 53 | void loop(); 54 | void printText(const char* text, const uint8_t line); 55 | void calcLineHeights(); 56 | void setFont(const uint8_t line); 57 | bool isValidDisplay(); 58 | 59 | Task _loopTask; 60 | 61 | U8G2* _display; 62 | DisplayGraphicDiagramClass _diagram; 63 | 64 | bool _displayTurnedOn; 65 | 66 | DisplayType_t _display_type = DisplayType_t::None; 67 | DiagramMode_t _diagram_mode = DiagramMode_t::Off; 68 | uint8_t _display_language = DISPLAY_LANGUAGE; 69 | uint8_t _mExtra; 70 | const uint16_t _period = 1000; 71 | const uint16_t _interval = 60000; // interval at which to power save (milliseconds) 72 | uint32_t _previousMillis = 0; 73 | char _fmtText[32]; 74 | bool _isLarge = false; 75 | uint8_t _lineOffsets[5]; 76 | }; 77 | 78 | extern DisplayGraphicClass Display; 79 | -------------------------------------------------------------------------------- /webapp/src/components/MemoryInfo.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 57 | -------------------------------------------------------------------------------- /webapp/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | 6 | import viteCompression from 'vite-plugin-compression'; 7 | import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js' 8 | import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite' 9 | 10 | import path from 'path' 11 | 12 | // example 'vite.user.ts': export const proxy_target = '192.168.16.107' 13 | let proxy_target; 14 | try { 15 | // eslint-disable-next-line 16 | proxy_target = require('./vite.user.ts').proxy_target; 17 | } catch (error) { 18 | proxy_target = '192.168.20.110'; 19 | } 20 | 21 | // https://vitejs.dev/config/ 22 | export default defineConfig({ 23 | plugins: [ 24 | vue(), 25 | viteCompression({ deleteOriginFile: true, threshold: 0 }), 26 | cssInjectedByJsPlugin(), 27 | VueI18nPlugin({ 28 | /* options */ 29 | include: path.resolve(path.dirname(fileURLToPath(import.meta.url)), './src/locales/**.json'), 30 | fullInstall: false, 31 | forceStringify: true, 32 | strictMessage: false, 33 | jitCompilation: false, 34 | }), 35 | ], 36 | resolve: { 37 | alias: { 38 | '@': fileURLToPath(new URL('./src', import.meta.url)), 39 | '~bootstrap': path.resolve(__dirname, 'node_modules/bootstrap'), 40 | } 41 | }, 42 | build: { 43 | // Prevent vendor.css being created 44 | cssCodeSplit: false, 45 | outDir: '../webapp_dist', 46 | emptyOutDir: true, 47 | minify: 'terser', 48 | rollupOptions: { 49 | output: { 50 | // Only create one js file 51 | inlineDynamicImports: true, 52 | // Get rid of hash on js file 53 | entryFileNames: 'js/app.js', 54 | // Get rid of hash on css file 55 | assetFileNames: "assets/[name].[ext]", 56 | }, 57 | }, 58 | }, 59 | esbuild: { 60 | drop: ['console', 'debugger'], 61 | }, 62 | server: { 63 | proxy: { 64 | '^/api': { 65 | target: 'http://' + proxy_target 66 | }, 67 | '^/livedata': { 68 | target: 'ws://' + proxy_target, 69 | ws: true, 70 | changeOrigin: true 71 | }, 72 | '^/console': { 73 | target: 'ws://' + proxy_target, 74 | ws: true, 75 | changeOrigin: true 76 | } 77 | } 78 | } 79 | }) 80 | -------------------------------------------------------------------------------- /webapp/src/components/InterfaceNetworkInfo.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 57 | -------------------------------------------------------------------------------- /include/WebApi_errors.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | enum WebApiError { 5 | GenericBase = 1000, 6 | GenericSuccess, 7 | GenericNoValueFound, 8 | GenericDataTooLarge, // not used anymore 9 | GenericParseError, 10 | GenericValueMissing, 11 | GenericWriteFailed, 12 | GenericInternalServerError, 13 | 14 | DtuBase = 2000, 15 | DtuSerialZero, 16 | DtuPollZero, 17 | DtuInvalidPowerLevel, 18 | DtuInvalidCmtFrequency, 19 | DtuInvalidCmtCountry, 20 | 21 | ConfigBase = 3000, 22 | ConfigNotDeleted, 23 | ConfigSuccess, 24 | 25 | InverterBase = 4000, 26 | InverterSerialZero, 27 | InverterNameLength, 28 | InverterCount, 29 | InverterAdded, 30 | InverterInvalidId, 31 | InverterInvalidMaxChannel, 32 | InverterChanged, 33 | InverterDeleted, 34 | InverterOrdered, 35 | 36 | LimitBase = 5000, 37 | LimitSerialZero, 38 | LimitInvalidLimit, 39 | LimitInvalidType, 40 | LimitInvalidInverter, 41 | 42 | MaintenanceBase = 6000, 43 | MaintenanceRebootTriggered, 44 | MaintenanceRebootCancled, 45 | 46 | MqttBase = 7000, 47 | MqttHostnameLength, 48 | MqttUsernameLength, 49 | MqttPasswordLength, 50 | MqttTopicLength, 51 | MqttTopicCharacter, 52 | MqttTopicTrailingSlash, 53 | MqttPort, 54 | MqttCertificateLength, 55 | MqttLwtTopicLength, 56 | MqttLwtTopicCharacter, 57 | MqttLwtOnlineLength, 58 | MqttLwtOfflineLength, 59 | MqttPublishInterval, 60 | MqttHassTopicLength, 61 | MqttHassTopicCharacter, 62 | MqttLwtQos, 63 | MqttClientIdLength, 64 | 65 | NetworkBase = 8000, 66 | NetworkIpInvalid, 67 | NetworkNetmaskInvalid, 68 | NetworkGatewayInvalid, 69 | NetworkDns1Invalid, 70 | NetworkDns2Invalid, 71 | NetworkApTimeoutInvalid, 72 | 73 | NtpBase = 9000, 74 | NtpServerLength, 75 | NtpTimezoneLength, 76 | NtpTimezoneDescriptionLength, 77 | NtpYearInvalid, 78 | NtpMonthInvalid, 79 | NtpDayInvalid, 80 | NtpHourInvalid, 81 | NtpMinuteInvalid, 82 | NtpSecondInvalid, 83 | NtpTimeUpdated, 84 | 85 | SecurityBase = 10000, 86 | SecurityPasswordLength, 87 | SecurityAuthSuccess, 88 | 89 | PowerBase = 11000, 90 | PowerSerialZero, 91 | PowerInvalidInverter, 92 | 93 | HardwareBase = 12000, 94 | HardwarePinMappingLength, 95 | }; 96 | --------------------------------------------------------------------------------