├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── dependabot.yml └── workflows │ ├── build.yml │ ├── config │ └── release-notes-config.json │ ├── cpplint.yml │ ├── repo-maintenance.yml │ ├── yarnlint.yml │ └── yarnprettier.yml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── COPYING ├── LICENSE ├── README.md ├── docs ├── DeviceProfiles.md ├── DeviceProfiles │ ├── AhoyDTU-ESP32.json │ ├── CASmo-DTU.json │ ├── blinkyparts_esp32.json │ ├── esp32_stick_poe_a.json │ ├── liligo_t-eth-lite_poe.json │ ├── lilygo_ttgo_t-internet_poe.json │ ├── nodemcu_esp32.json │ ├── olimex_esp32_evb.json │ ├── olimex_esp32_gateway.json │ ├── olimex_esp32_poe.json │ ├── opendtu_fusion.json │ ├── wemos-lolin32-oled.json │ └── wt32-eth01.json ├── Display.md ├── MQTT_Topics.md ├── README.md ├── UpgradePartition.md ├── Web-API.md ├── Wiring_ESP32.fzz ├── Wiring_ESP32_Schematic.png ├── Wiring_ESP32_Symbol.png ├── builds │ ├── 202654506-8a4ac4ef-c883-490e-8ee1-1e1f7fa34972.jpg │ ├── README.md │ ├── large_display_PXL_20220715_145622277.jpg │ ├── opendtu_breakoutboard.jpg │ ├── sol.webp │ └── thumbnail.jpg ├── esp32_flash_download_tool.png ├── nodemcu-esp32.png ├── nrf24l01plus.png └── screenshots │ ├── 01_LiveView.png │ ├── 02_NetworkAdmin.png │ ├── 03_NtpAdmin.png │ ├── 04_MqttAdmin.png │ ├── 05_InverterAdmin.png │ ├── 06_DtuAdmin.png │ ├── 07_FirmwareUpgrade.png │ ├── 08_NetworkInfo.png │ ├── 09_NtpInfo.png │ ├── 10_MqttInfo.png │ ├── 11_SystemInfo.png │ ├── 12_Eventlog.png │ ├── 13_InverterSettings.png │ ├── 14_ConfigManagement.png │ ├── 15_LimitSettings.png │ ├── 16_PowerSettings.png │ ├── 17_InverterInfo.png │ ├── 18_Console.png │ ├── 19_Reboot.png │ ├── 20_DeviceManager_Pin.png │ ├── 21_DeviceManager_Display.png │ ├── 22_Security.png │ ├── 23_Database.png │ ├── README.md │ └── Screenshot_2024-05-23_131208.png ├── include ├── Configuration.h ├── Datastore.h ├── Display_Graphic.h ├── Display_Graphic_Diagram.h ├── I18n.h ├── InverterSettings.h ├── Led_Single.h ├── MessageOutput.h ├── MqttHandleDtu.h ├── MqttHandleHass.h ├── MqttHandleInverter.h ├── MqttHandleInverterTotal.h ├── MqttSettings.h ├── NetworkSettings.h ├── NtpSettings.h ├── PinMapping.h ├── README ├── RestartHelper.h ├── Scheduler.h ├── SunPosition.h ├── Utils.h ├── W5500.h ├── WebApi.h ├── WebApi_database.h ├── WebApi_device.h ├── WebApi_devinfo.h ├── WebApi_dtu.h ├── WebApi_errors.h ├── WebApi_eventlog.h ├── WebApi_file.h ├── WebApi_firmware.h ├── WebApi_gridprofile.h ├── WebApi_i18n.h ├── WebApi_inverter.h ├── WebApi_limit.h ├── WebApi_maintenance.h ├── WebApi_mqtt.h ├── WebApi_network.h ├── WebApi_ntp.h ├── WebApi_power.h ├── WebApi_prometheus.h ├── WebApi_security.h ├── WebApi_sysstatus.h ├── WebApi_webapp.h ├── WebApi_ws_console.h ├── WebApi_ws_live.h ├── __compiled_constants.h ├── defaults.h └── helper.h ├── lang ├── README.md ├── es.lang.json └── it.lang.json ├── lib ├── CMT2300a │ ├── cmt2300a.c │ ├── cmt2300a.h │ ├── cmt2300a_defs.h │ ├── cmt2300a_hal.c │ ├── cmt2300a_hal.h │ ├── cmt2300a_params_860.h │ ├── cmt2300a_params_900.h │ ├── cmt2300wrapper.cpp │ ├── cmt2300wrapper.h │ ├── cmt_spi3.cpp │ └── cmt_spi3.h ├── CpuTemperature │ └── src │ │ ├── CpuTemperature.cpp │ │ └── CpuTemperature.h ├── Every │ └── Every.h ├── Frozen │ ├── AUTHORS │ ├── LICENSE │ ├── README.rst │ └── frozen │ │ ├── CMakeLists.txt │ │ ├── algorithm.h │ │ ├── bits │ │ ├── algorithms.h │ │ ├── basic_types.h │ │ ├── constexpr_assert.h │ │ ├── defines.h │ │ ├── elsa.h │ │ ├── elsa_std.h │ │ ├── exceptions.h │ │ ├── hash_string.h │ │ ├── mpl.h │ │ ├── pmh.h │ │ └── version.h │ │ ├── map.h │ │ ├── random.h │ │ ├── set.h │ │ ├── string.h │ │ ├── unordered_map.h │ │ └── unordered_set.h ├── Hoymiles │ ├── README.md │ ├── library.json │ └── src │ │ ├── Hoymiles.cpp │ │ ├── Hoymiles.h │ │ ├── HoymilesRadio.cpp │ │ ├── HoymilesRadio.h │ │ ├── HoymilesRadio_CMT.cpp │ │ ├── HoymilesRadio_CMT.h │ │ ├── HoymilesRadio_NRF.cpp │ │ ├── HoymilesRadio_NRF.h │ │ ├── Utils.cpp │ │ ├── Utils.h │ │ ├── commands │ │ ├── ActivePowerControlCommand.cpp │ │ ├── ActivePowerControlCommand.h │ │ ├── AlarmDataCommand.cpp │ │ ├── AlarmDataCommand.h │ │ ├── ChannelChangeCommand.cpp │ │ ├── ChannelChangeCommand.h │ │ ├── CommandAbstract.cpp │ │ ├── CommandAbstract.h │ │ ├── DevControlCommand.cpp │ │ ├── DevControlCommand.h │ │ ├── DevInfoAllCommand.cpp │ │ ├── DevInfoAllCommand.h │ │ ├── DevInfoSimpleCommand.cpp │ │ ├── DevInfoSimpleCommand.h │ │ ├── GridOnProFilePara.cpp │ │ ├── GridOnProFilePara.h │ │ ├── MultiDataCommand.cpp │ │ ├── MultiDataCommand.h │ │ ├── ParaSetCommand.cpp │ │ ├── ParaSetCommand.h │ │ ├── PowerControlCommand.cpp │ │ ├── PowerControlCommand.h │ │ ├── README.md │ │ ├── RealTimeRunDataCommand.cpp │ │ ├── RealTimeRunDataCommand.h │ │ ├── RequestFrameCommand.cpp │ │ ├── RequestFrameCommand.h │ │ ├── SingleDataCommand.cpp │ │ ├── SingleDataCommand.h │ │ ├── SystemConfigParaCommand.cpp │ │ └── SystemConfigParaCommand.h │ │ ├── crc.cpp │ │ ├── crc.h │ │ ├── inverters │ │ ├── HERF_1CH.cpp │ │ ├── HERF_1CH.h │ │ ├── HERF_2CH.cpp │ │ ├── HERF_2CH.h │ │ ├── HERF_4CH.cpp │ │ ├── HERF_4CH.h │ │ ├── HMS_1CH.cpp │ │ ├── HMS_1CH.h │ │ ├── HMS_1CHv2.cpp │ │ ├── HMS_1CHv2.h │ │ ├── HMS_2CH.cpp │ │ ├── HMS_2CH.h │ │ ├── HMS_4CH.cpp │ │ ├── HMS_4CH.h │ │ ├── HMS_Abstract.cpp │ │ ├── HMS_Abstract.h │ │ ├── HMT_4CH.cpp │ │ ├── HMT_4CH.h │ │ ├── HMT_6CH.cpp │ │ ├── HMT_6CH.h │ │ ├── HMT_Abstract.cpp │ │ ├── HMT_Abstract.h │ │ ├── HM_1CH.cpp │ │ ├── HM_1CH.h │ │ ├── HM_2CH.cpp │ │ ├── HM_2CH.h │ │ ├── HM_4CH.cpp │ │ ├── HM_4CH.h │ │ ├── HM_Abstract.cpp │ │ ├── HM_Abstract.h │ │ ├── InverterAbstract.cpp │ │ ├── InverterAbstract.h │ │ └── README.md │ │ ├── parser │ │ ├── AlarmLogParser.cpp │ │ ├── AlarmLogParser.h │ │ ├── DevInfoParser.cpp │ │ ├── DevInfoParser.h │ │ ├── GridProfileParser.cpp │ │ ├── GridProfileParser.h │ │ ├── Parser.cpp │ │ ├── Parser.h │ │ ├── PowerCommandParser.cpp │ │ ├── PowerCommandParser.h │ │ ├── StatisticsParser.cpp │ │ ├── StatisticsParser.h │ │ ├── SystemConfigParaParser.cpp │ │ └── SystemConfigParaParser.h │ │ └── types.h ├── MqttSubscribeParser │ ├── MqttSubscribeParser.cpp │ └── MqttSubscribeParser.h ├── README ├── ResetReason │ └── src │ │ ├── ResetReason.cpp │ │ └── ResetReason.h ├── SpiManager │ ├── library.json │ └── src │ │ ├── SpiBus.cpp │ │ ├── SpiBus.h │ │ ├── SpiBusConfig.cpp │ │ ├── SpiBusConfig.h │ │ ├── SpiCallback.cpp │ │ ├── SpiCallback.h │ │ ├── SpiManager.cpp │ │ └── SpiManager.h ├── ThreadSafeQueue │ ├── README.md │ ├── library.json │ └── src │ │ └── ThreadSafeQueue.h └── TimeoutHelper │ ├── README.md │ ├── library.json │ └── src │ ├── TimeoutHelper.cpp │ └── TimeoutHelper.h ├── partitions_custom_16mb.csv ├── partitions_custom_4mb.csv ├── pio-scripts ├── auto_firmware_version.py ├── create_factory_bin.py └── patch_apply.py ├── platformio.ini ├── platformio_override.ini ├── src ├── Configuration.cpp ├── Datastore.cpp ├── Display_Graphic.cpp ├── Display_Graphic_Diagram.cpp ├── I18n.cpp ├── InverterSettings.cpp ├── Led_Single.cpp ├── MessageOutput.cpp ├── MqttHandleDtu.cpp ├── MqttHandleHass.cpp ├── MqttHandleInverter.cpp ├── MqttHandleInverterTotal.cpp ├── MqttSettings.cpp ├── NetworkSettings.cpp ├── NtpSettings.cpp ├── PinMapping.cpp ├── RestartHelper.cpp ├── Scheduler.cpp ├── SunPosition.cpp ├── Utils.cpp ├── W5500.cpp ├── WebApi.cpp ├── WebApi_database.cpp ├── WebApi_device.cpp ├── WebApi_devinfo.cpp ├── WebApi_dtu.cpp ├── WebApi_eventlog.cpp ├── WebApi_file.cpp ├── WebApi_firmware.cpp ├── WebApi_gridprofile.cpp ├── WebApi_i18n.cpp ├── WebApi_inverter.cpp ├── WebApi_limit.cpp ├── WebApi_maintenance.cpp ├── WebApi_mqtt.cpp ├── WebApi_network.cpp ├── WebApi_ntp.cpp ├── WebApi_power.cpp ├── WebApi_prometheus.cpp ├── WebApi_security.cpp ├── WebApi_sysstatus.cpp ├── WebApi_webapp.cpp ├── WebApi_ws_console.cpp ├── WebApi_ws_live.cpp └── main.cpp ├── test └── README ├── webapp ├── .gitignore ├── .prettierrc.json ├── .vscode │ └── extensions.json ├── .yarn │ └── install-state.gz ├── .yarnrc.yml ├── README.md ├── env.d.ts ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── favicon.png │ ├── site.webmanifest │ └── zones.json ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ ├── BarChart.vue │ │ ├── BasePage.vue │ │ ├── BootstrapAlert.vue │ │ ├── CalendarChart.vue │ │ ├── CardElement.vue │ │ ├── DevInfo.vue │ │ ├── EventLog.vue │ │ ├── FirmwareInfo.vue │ │ ├── FormFooter.vue │ │ ├── FsInfo.vue │ │ ├── GridProfile.vue │ │ ├── HardwareInfo.vue │ │ ├── HeapDetails.vue │ │ ├── HintView.vue │ │ ├── InputElement.vue │ │ ├── InputSerial.vue │ │ ├── InterfaceApInfo.vue │ │ ├── InterfaceNetworkInfo.vue │ │ ├── InverterChannelInfo.vue │ │ ├── InverterTotalInfo.vue │ │ ├── LocaleSwitcher.vue │ │ ├── MemoryInfo.vue │ │ ├── ModalDialog.vue │ │ ├── NavBar.vue │ │ ├── PinInfo.vue │ │ ├── RadioInfo.vue │ │ ├── StatusBadge.vue │ │ ├── TaskDetails.vue │ │ ├── ThemeSwitcher.vue │ │ ├── WifiApInfo.vue │ │ └── WifiStationInfo.vue │ ├── emitter.d.ts │ ├── i18n.ts │ ├── locales │ │ ├── de.json │ │ ├── en.json │ │ └── fr.json │ ├── main.ts │ ├── plugins │ │ └── bootstrap.ts │ ├── router │ │ └── index.ts │ ├── scss │ │ └── styles.scss │ ├── types │ │ ├── AlertResponse.ts │ │ ├── DevInfoStatus.ts │ │ ├── DeviceConfig.ts │ │ ├── DtuConfig.ts │ │ ├── EventlogStatus.ts │ │ ├── File.ts │ │ ├── GridProfileRawdata.ts │ │ ├── GridProfileStatus.ts │ │ ├── InverterConfig.ts │ │ ├── LimitConfig.ts │ │ ├── LimitStatus.ts │ │ ├── LiveDataStatus.ts │ │ ├── MqttConfig.ts │ │ ├── MqttStatus.ts │ │ ├── NetworkConfig.ts │ │ ├── NetworkStatus.ts │ │ ├── NtpConfig.ts │ │ ├── NtpStatus.ts │ │ ├── PinMapping.ts │ │ ├── SecurityConfig.ts │ │ └── SystemStatus.ts │ ├── utils │ │ ├── authentication.ts │ │ ├── index.ts │ │ ├── structure.ts │ │ ├── time.ts │ │ └── waitRestart.ts │ └── views │ │ ├── AboutView.vue │ │ ├── ConfigAdminView.vue │ │ ├── ConsoleInfoView.vue │ │ ├── DeviceAdminView.vue │ │ ├── DtuAdminView.vue │ │ ├── ErrorView.vue │ │ ├── FirmwareUpgradeView.vue │ │ ├── HomeView.vue │ │ ├── InverterAdminView.vue │ │ ├── LoginView.vue │ │ ├── MaintenanceRebootView.vue │ │ ├── MqttAdminView.vue │ │ ├── MqttInfoView.vue │ │ ├── NetworkAdminView.vue │ │ ├── NetworkInfoView.vue │ │ ├── NtpAdminView.vue │ │ ├── NtpInfoView.vue │ │ ├── SecurityAdminView.vue │ │ ├── SystemInfoView.vue │ │ └── WaitRestartView.vue ├── tsconfig.config.json ├── tsconfig.json ├── vite.config.ts ├── yarn └── yarn.lock └── webapp_dist ├── favicon.ico ├── favicon.png ├── index.html.gz ├── js └── app.js.gz ├── site.webmanifest └── zones.json.gz /.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 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: tbnobody -------------------------------------------------------------------------------- /.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/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 | -------------------------------------------------------------------------------- /.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" -------------------------------------------------------------------------------- /.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": "## 🌎 Web Application", 23 | "labels": [ 24 | "webapp" 25 | ] 26 | }, 27 | { 28 | "title": "## 📚 Documentation", 29 | "labels": [ 30 | "doc" 31 | ] 32 | }, 33 | { 34 | "title": "## 🛠 Under the hood", 35 | "labels": [] 36 | } 37 | ], 38 | "template": "${{CHANGELOG}}", 39 | "pr_template": "- [${{TITLE}}](https://github.com/tbnobody/OpenDTU/commit/${{MERGE_SHA}})", 40 | "empty_template": "- no changes", 41 | "label_extractor": [ 42 | { 43 | "pattern": "(.): (.+)", 44 | "target": "$1", 45 | "on_property": "title" 46 | }, 47 | { 48 | "pattern": "(.) (.+)", 49 | "target": "$1", 50 | "on_property": "title" 51 | } 52 | ], 53 | "tag_resolver": { 54 | "method": "semver" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /.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@v4 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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | defaults: 10 | run: 11 | working-directory: webapp 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Enable Corepack 16 | run: corepack enable 17 | - name: Setup Node.js and yarn 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: "22" 21 | cache: "yarn" 22 | cache-dependency-path: "webapp/yarn.lock" 23 | 24 | - name: Install WebApp dependencies 25 | run: yarn install --frozen-lockfile 26 | 27 | - name: Linting 28 | run: yarn lint 29 | -------------------------------------------------------------------------------- /.github/workflows/yarnprettier.yml: -------------------------------------------------------------------------------- 1 | name: Yarn Prettier 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | defaults: 10 | run: 11 | working-directory: webapp 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Enable Corepack 16 | run: corepack enable 17 | - name: Setup Node.js and yarn 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: "22" 21 | cache: "yarn" 22 | cache-dependency-path: "webapp/yarn.lock" 23 | 24 | - name: Install WebApp dependencies 25 | run: yarn install --frozen-lockfile 26 | 27 | - name: Check Formatting 28 | run: yarn prettier --check src/ 29 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "C_Cpp.clang_format_style": "WebKit", 3 | "cmake.sourceDirectory": "C:/git/OpenDTU-Database/.pio/libdeps/generic_esp32/ArduinoJson/src" 4 | } 5 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /docs/DeviceProfiles.md: -------------------------------------------------------------------------------- 1 | # Device Profiles 2 | 3 | This documentation will has been moved and can be found here: 4 | -------------------------------------------------------------------------------- /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 | ] -------------------------------------------------------------------------------- /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 | ] -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | ] -------------------------------------------------------------------------------- /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 | ] -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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) -------------------------------------------------------------------------------- /docs/UpgradePartition.md: -------------------------------------------------------------------------------- 1 | # Upgrade Partition 2 | 3 | This documentation has been moved and can be found here: 4 | -------------------------------------------------------------------------------- /docs/Web-API.md: -------------------------------------------------------------------------------- 1 | # Web API 2 | 3 | This documentation will has been moved and can be found here: 4 | -------------------------------------------------------------------------------- /docs/Wiring_ESP32.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/Wiring_ESP32.fzz -------------------------------------------------------------------------------- /docs/Wiring_ESP32_Schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/Wiring_ESP32_Schematic.png -------------------------------------------------------------------------------- /docs/Wiring_ESP32_Symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/Wiring_ESP32_Symbol.png -------------------------------------------------------------------------------- /docs/builds/202654506-8a4ac4ef-c883-490e-8ee1-1e1f7fa34972.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/builds/202654506-8a4ac4ef-c883-490e-8ee1-1e1f7fa34972.jpg -------------------------------------------------------------------------------- /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) -------------------------------------------------------------------------------- /docs/builds/large_display_PXL_20220715_145622277.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/builds/large_display_PXL_20220715_145622277.jpg -------------------------------------------------------------------------------- /docs/builds/opendtu_breakoutboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/builds/opendtu_breakoutboard.jpg -------------------------------------------------------------------------------- /docs/builds/sol.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/builds/sol.webp -------------------------------------------------------------------------------- /docs/builds/thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/builds/thumbnail.jpg -------------------------------------------------------------------------------- /docs/esp32_flash_download_tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/esp32_flash_download_tool.png -------------------------------------------------------------------------------- /docs/nodemcu-esp32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/nodemcu-esp32.png -------------------------------------------------------------------------------- /docs/nrf24l01plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/nrf24l01plus.png -------------------------------------------------------------------------------- /docs/screenshots/01_LiveView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/01_LiveView.png -------------------------------------------------------------------------------- /docs/screenshots/02_NetworkAdmin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/02_NetworkAdmin.png -------------------------------------------------------------------------------- /docs/screenshots/03_NtpAdmin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/03_NtpAdmin.png -------------------------------------------------------------------------------- /docs/screenshots/04_MqttAdmin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/04_MqttAdmin.png -------------------------------------------------------------------------------- /docs/screenshots/05_InverterAdmin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/05_InverterAdmin.png -------------------------------------------------------------------------------- /docs/screenshots/06_DtuAdmin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/06_DtuAdmin.png -------------------------------------------------------------------------------- /docs/screenshots/07_FirmwareUpgrade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/07_FirmwareUpgrade.png -------------------------------------------------------------------------------- /docs/screenshots/08_NetworkInfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/08_NetworkInfo.png -------------------------------------------------------------------------------- /docs/screenshots/09_NtpInfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/09_NtpInfo.png -------------------------------------------------------------------------------- /docs/screenshots/10_MqttInfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/10_MqttInfo.png -------------------------------------------------------------------------------- /docs/screenshots/11_SystemInfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/11_SystemInfo.png -------------------------------------------------------------------------------- /docs/screenshots/12_Eventlog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/12_Eventlog.png -------------------------------------------------------------------------------- /docs/screenshots/13_InverterSettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/13_InverterSettings.png -------------------------------------------------------------------------------- /docs/screenshots/14_ConfigManagement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/14_ConfigManagement.png -------------------------------------------------------------------------------- /docs/screenshots/15_LimitSettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/15_LimitSettings.png -------------------------------------------------------------------------------- /docs/screenshots/16_PowerSettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/16_PowerSettings.png -------------------------------------------------------------------------------- /docs/screenshots/17_InverterInfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/17_InverterInfo.png -------------------------------------------------------------------------------- /docs/screenshots/18_Console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/18_Console.png -------------------------------------------------------------------------------- /docs/screenshots/19_Reboot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/19_Reboot.png -------------------------------------------------------------------------------- /docs/screenshots/20_DeviceManager_Pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/20_DeviceManager_Pin.png -------------------------------------------------------------------------------- /docs/screenshots/21_DeviceManager_Display.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/21_DeviceManager_Display.png -------------------------------------------------------------------------------- /docs/screenshots/22_Security.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/22_Security.png -------------------------------------------------------------------------------- /docs/screenshots/23_Database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/23_Database.png -------------------------------------------------------------------------------- /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 | 93 | *** 94 | 95 | ![Console](23_Database.png) 96 | -------------------------------------------------------------------------------- /docs/screenshots/Screenshot_2024-05-23_131208.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/docs/screenshots/Screenshot_2024-05-23_131208.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /include/I18n.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct LanguageInfo_t { 9 | String code; 10 | String name; 11 | String filename; 12 | }; 13 | 14 | class I18nClass { 15 | public: 16 | I18nClass(); 17 | void init(Scheduler& scheduler); 18 | std::list getAvailableLanguages(); 19 | String getFilenameByLocale(const String& locale) const; 20 | void readDisplayStrings( 21 | const String& locale, 22 | String& date_format, 23 | String& offline, 24 | String& power_w, String& power_kw, 25 | String& yield_today_wh, String& yield_today_kwh, 26 | String& yield_total_kwh, String& yield_total_mwh); 27 | 28 | private: 29 | void readLangPacks(); 30 | void readConfig(String file); 31 | 32 | std::list _availLanguages; 33 | }; 34 | 35 | extern I18nClass I18n; 36 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /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 | 16 | int8_t nrf24_miso; 17 | int8_t nrf24_mosi; 18 | int8_t nrf24_clk; 19 | int8_t nrf24_irq; 20 | int8_t nrf24_en; 21 | int8_t nrf24_cs; 22 | 23 | int8_t cmt_clk; 24 | int8_t cmt_cs; 25 | int8_t cmt_fcs; 26 | int8_t cmt_gpio2; 27 | int8_t cmt_gpio3; 28 | int8_t cmt_sdio; 29 | 30 | int8_t w5500_mosi; 31 | int8_t w5500_miso; 32 | int8_t w5500_sclk; 33 | int8_t w5500_cs; 34 | int8_t w5500_int; 35 | int8_t w5500_rst; 36 | 37 | #if CONFIG_ETH_USE_ESP32_EMAC 38 | int8_t eth_phy_addr; 39 | bool eth_enabled; 40 | int eth_power; 41 | int eth_mdc; 42 | int eth_mdio; 43 | eth_phy_type_t eth_type; 44 | eth_clock_mode_t eth_clk_mode; 45 | #endif 46 | 47 | uint8_t display_type; 48 | uint8_t display_data; 49 | uint8_t display_clk; 50 | uint8_t display_cs; 51 | uint8_t display_reset; 52 | 53 | int8_t led[PINMAPPING_LED_COUNT]; 54 | }; 55 | 56 | class PinMappingClass { 57 | public: 58 | PinMappingClass(); 59 | bool init(const String& deviceMapping); 60 | PinMapping_t& get(); 61 | 62 | bool isValidNrf24Config() const; 63 | bool isValidCmt2300Config() const; 64 | bool isValidW5500Config() const; 65 | #if CONFIG_ETH_USE_ESP32_EMAC 66 | bool isValidEthConfig() const; 67 | #endif 68 | 69 | private: 70 | PinMapping_t _pinMapping; 71 | }; 72 | 73 | extern PinMappingClass PinMapping; 74 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /include/RestartHelper.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | 6 | class RestartHelperClass { 7 | public: 8 | RestartHelperClass(); 9 | void init(Scheduler& scheduler); 10 | void triggerRestart(); 11 | 12 | private: 13 | void loop(); 14 | 15 | Task _rebootTask; 16 | }; 17 | 18 | extern RestartHelperClass RestartHelper; 19 | -------------------------------------------------------------------------------- /include/Scheduler.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | 6 | extern Scheduler scheduler; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /include/Utils.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class Utils { 9 | public: 10 | static uint32_t getChipId(); 11 | static uint64_t generateDtuSerial(); 12 | static int getTimezoneOffset(); 13 | static bool checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line); 14 | static void removeAllFiles(); 15 | static String generateMd5FromFile(String file); 16 | static void skipBom(File& f); 17 | }; 18 | -------------------------------------------------------------------------------- /include/W5500.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include // required for esp_eth_handle_t 7 | #include 8 | 9 | #include 10 | 11 | class W5500 { 12 | private: 13 | explicit W5500(spi_device_handle_t spi, gpio_num_t pin_int); 14 | 15 | public: 16 | W5500(const W5500&) = delete; 17 | W5500& operator=(const W5500&) = delete; 18 | ~W5500(); 19 | 20 | static std::unique_ptr setup(int8_t pin_mosi, int8_t pin_miso, int8_t pin_sclk, int8_t pin_cs, int8_t pin_int, int8_t pin_rst); 21 | String macAddress(); 22 | 23 | private: 24 | static bool connection_check_spi(spi_device_handle_t spi); 25 | static bool connection_check_interrupt(gpio_num_t pin_int); 26 | 27 | esp_eth_handle_t eth_handle; 28 | esp_netif_t* eth_netif; 29 | }; 30 | -------------------------------------------------------------------------------- /include/WebApi_database.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define DATABASE_FILENAME "/database.bin" 9 | 10 | class WebApiDatabaseClass { 11 | public: 12 | WebApiDatabaseClass(); 13 | void init(AsyncWebServer& server, Scheduler& scheduler); 14 | bool write(float energy); 15 | 16 | struct pvData { 17 | uint8_t tm_year; 18 | uint8_t tm_mon; 19 | uint8_t tm_mday; 20 | uint8_t tm_hour; 21 | float energy; 22 | }; 23 | 24 | private: 25 | void onDatabase(AsyncWebServerRequest* request); 26 | void onDatabaseHour(AsyncWebServerRequest* request); 27 | void onDatabaseDay(AsyncWebServerRequest* request); 28 | static size_t readchunk(uint8_t* buffer, size_t maxLen, size_t index); 29 | static size_t readchunk_log(uint8_t* buffer, size_t maxLen, size_t index); 30 | static size_t readchunkHour(uint8_t* buffer, size_t maxLen, size_t index); 31 | static size_t readchunkDay(uint8_t* buffer, size_t maxLen, size_t index); 32 | 33 | AsyncWebServer* _server; 34 | 35 | Task _sendDataTask; 36 | void sendDataTaskCb(); 37 | }; 38 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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_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_file.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class WebApiFileClass { 8 | public: 9 | void init(AsyncWebServer& server, Scheduler& scheduler); 10 | 11 | private: 12 | void onFileGet(AsyncWebServerRequest* request); 13 | void onFileDelete(AsyncWebServerRequest* request); 14 | void onFileDeleteAll(AsyncWebServerRequest* request); 15 | void onFileListGet(AsyncWebServerRequest* request); 16 | void onFileUploadFinish(AsyncWebServerRequest* request); 17 | void onFileUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final); 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /include/WebApi_i18n.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class WebApiI18nClass { 8 | public: 9 | void init(AsyncWebServer& server, Scheduler& scheduler); 10 | 11 | private: 12 | void onI18nLanguages(AsyncWebServerRequest* request); 13 | void onI18nLanguage(AsyncWebServerRequest* request); 14 | }; 15 | -------------------------------------------------------------------------------- /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 | void onInverterStatReset(AsyncWebServerRequest* request); 18 | }; 19 | -------------------------------------------------------------------------------- /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_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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | void reload(); 12 | 13 | private: 14 | AsyncWebSocket _ws; 15 | AuthenticationMiddleware _simpleDigestAuth; 16 | 17 | Task _wsCleanupTask; 18 | void wsCleanupTaskCb(); 19 | }; 20 | -------------------------------------------------------------------------------- /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 | void reload(); 15 | 16 | private: 17 | static void generateInverterCommonJsonResponse(JsonObject& root, std::shared_ptr inv); 18 | static void generateInverterChannelJsonResponse(JsonObject& root, std::shared_ptr inv); 19 | static void generateCommonJsonResponse(JsonVariant& root); 20 | 21 | static void addField(JsonObject& root, std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId, String topic = ""); 22 | static void addTotalField(JsonObject& root, const String& name, const float value, const String& unit, const uint8_t digits); 23 | 24 | void onLivedataStatus(AsyncWebServerRequest* request); 25 | void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len); 26 | 27 | AsyncWebSocket _ws; 28 | AuthenticationMiddleware _simpleDigestAuth; 29 | 30 | uint32_t _lastPublishStats[INV_MAX_COUNT] = { 0 }; 31 | 32 | std::mutex _mutex; 33 | 34 | Task _wsCleanupTask; 35 | void wsCleanupTaskCb(); 36 | 37 | Task _sendDataTask; 38 | void sendDataTaskCb(); 39 | }; 40 | -------------------------------------------------------------------------------- /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_GIT_BRANCH__; 9 | // extern const char *__COMPILED_DATE_TIME_UTC_STR__; 10 | -------------------------------------------------------------------------------- /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) -------------------------------------------------------------------------------- /lang/README.md: -------------------------------------------------------------------------------- 1 | # Language Packs 2 | 3 | This folder contains language packs for OpenDTU which can be uploaded to the 4 | device using the "Config Management" function. 5 | Select "Language Pack" in the restore section, select a `.json` file containing 6 | your language and press "Restore". Afterwards all language selection drop down 7 | menues contain the new language. 8 | 9 | Create a pull to request to share your own language pack (or corrections) with the community. 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/CMT2300a/cmt_spi3.h: -------------------------------------------------------------------------------- 1 | #ifndef __CMT_SPI3_H 2 | #define __CMT_SPI3_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | void cmt_spi3_init(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const int32_t spi_speed); 11 | 12 | void cmt_spi3_write(const uint8_t addr, const uint8_t dat); 13 | uint8_t cmt_spi3_read(const uint8_t addr); 14 | 15 | void cmt_spi3_write_fifo(const uint8_t* p_buf, const uint16_t len); 16 | void cmt_spi3_read_fifo(uint8_t* p_buf, const uint16_t len); 17 | 18 | #ifdef __cplusplus 19 | } 20 | #endif 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/Frozen/AUTHORS: -------------------------------------------------------------------------------- 1 | serge-sans-paille 2 | Jérôme Dumesnil 3 | Chris Beck 4 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/Hoymiles/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/lib/Hoymiles/README.md -------------------------------------------------------------------------------- /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/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/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 | 19 | void enqueCommand(std::shared_ptr cmd) 20 | { 21 | _commandQueue.push(cmd); 22 | } 23 | 24 | template 25 | std::shared_ptr prepareCommand(InverterAbstract* inv) 26 | { 27 | return std::make_shared(inv); 28 | } 29 | 30 | protected: 31 | static serial_u convertSerialToRadioId(const serial_u serial); 32 | static void dumpBuf(const uint8_t buf[], const uint8_t len, const bool appendNewline = true); 33 | 34 | bool checkFragmentCrc(const fragment_t& fragment) const; 35 | virtual void sendEsbPacket(CommandAbstract& cmd) = 0; 36 | void sendRetransmitPacket(const uint8_t fragment_id); 37 | void sendLastPacketAgain(); 38 | void handleReceivedPackage(); 39 | 40 | serial_u _dtuSerial; 41 | ThreadSafeQueue> _commandQueue; 42 | bool _isInitialized = false; 43 | bool _busyFlag = false; 44 | 45 | TimeoutHelper _rxTimeout; 46 | }; 47 | -------------------------------------------------------------------------------- /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/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/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 | }; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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] = static_cast(crc >> 8); 39 | _payload[10 + len + 1] = static_cast(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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /lib/Hoymiles/src/inverters/HERF_1CH.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "HM_Abstract.h" 5 | 6 | class HERF_1CH : public HM_Abstract { 7 | public: 8 | explicit HERF_1CH(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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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_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/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/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/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/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 | -------------------------------------------------------------------------------- /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_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 | }; -------------------------------------------------------------------------------- /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/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 | }; -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /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/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 | }; -------------------------------------------------------------------------------- /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, 1410 | 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_1CH | HERF 300 | 2841 | 15 | | HERF_2CH | HERF 800 | 2821 | 16 | | HERF_4CH | HERF 1800 | 2801 | 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | } -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /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/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/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 | }; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /lib/SpiManager/library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SpiManager", 3 | "keywords": "spi", 4 | "description": "Library for managing the allocation of dedicated or shared SPI buses on the ESP32.", 5 | "authors": { 6 | "name": "Lennart Ferlemann" 7 | }, 8 | "version": "0.0.1", 9 | "frameworks": "arduino", 10 | "platforms": [ 11 | "espressif32" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /lib/SpiManager/src/SpiBus.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #include "SpiBus.h" 3 | #include "SpiBusConfig.h" 4 | #include "SpiCallback.h" 5 | 6 | SpiBus::SpiBus(const std::string& _id, spi_host_device_t _host_device) 7 | : id(_id) 8 | , host_device(_host_device) 9 | , cur_config(nullptr) 10 | { 11 | spi_bus_config_t bus_config { 12 | .mosi_io_num = -1, 13 | .miso_io_num = -1, 14 | .sclk_io_num = -1, 15 | .quadwp_io_num = -1, 16 | .quadhd_io_num = -1, 17 | .data4_io_num = -1, 18 | .data5_io_num = -1, 19 | .data6_io_num = -1, 20 | .data7_io_num = -1, 21 | .max_transfer_sz = 0, // defaults to SPI_MAX_DMA_LEN (=4092) or SOC_SPI_MAXIMUM_BUFFER_SIZE (=64) 22 | .flags = 0, 23 | .intr_flags = 0 24 | }; 25 | 26 | #if !CONFIG_IDF_TARGET_ESP32S2 27 | spi_dma_chan_t dma_channel = SPI_DMA_CH_AUTO; 28 | #else 29 | // DMA for SPI3 on ESP32-S2 is shared with ADC/DAC, so we cannot use it here 30 | spi_dma_chan_t dma_channel = (host_device != SPI3_HOST ? SPI_DMA_CH_AUTO : SPI_DMA_DISABLED); 31 | #endif 32 | 33 | ESP_ERROR_CHECK(spi_bus_initialize(host_device, &bus_config, dma_channel)); 34 | } 35 | 36 | SpiBus::~SpiBus() 37 | { 38 | ESP_ERROR_CHECK(spi_bus_free(host_device)); 39 | } 40 | 41 | spi_device_handle_t SpiBus::add_device(const std::shared_ptr& bus_config, spi_device_interface_config_t& device_config) 42 | { 43 | if (!SpiCallback::patch(shared_from_this(), bus_config, device_config)) 44 | return nullptr; 45 | 46 | spi_device_handle_t device; 47 | ESP_ERROR_CHECK(spi_bus_add_device(host_device, &device_config, &device)); 48 | return device; 49 | } 50 | 51 | // TODO: add remove_device (with spi_device_acquire_bus) 52 | 53 | void SpiBus::apply_config(SpiBusConfig* config) 54 | { 55 | if (cur_config) 56 | cur_config->unpatch(host_device); 57 | cur_config = config; 58 | if (cur_config) 59 | cur_config->patch(host_device); 60 | } 61 | -------------------------------------------------------------------------------- /lib/SpiManager/src/SpiBus.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class SpiBusConfig; 9 | 10 | class SpiBus : public std::enable_shared_from_this { 11 | public: 12 | explicit SpiBus(const std::string& id, spi_host_device_t host_device); 13 | SpiBus(const SpiBus&) = delete; 14 | SpiBus& operator=(const SpiBus&) = delete; 15 | ~SpiBus(); 16 | 17 | inline __attribute__((always_inline)) void require_config(SpiBusConfig* config) 18 | { 19 | if (config == cur_config) 20 | return; 21 | apply_config(config); 22 | } 23 | 24 | inline __attribute__((always_inline)) void free_config(SpiBusConfig* config) 25 | { 26 | if (config != cur_config) 27 | return; 28 | apply_config(nullptr); 29 | } 30 | 31 | inline const std::string& get_id() const 32 | { 33 | return id; 34 | } 35 | 36 | inline spi_host_device_t get_host_device() const 37 | { 38 | return host_device; 39 | } 40 | 41 | spi_device_handle_t add_device(const std::shared_ptr& bus_config, spi_device_interface_config_t& device_config); 42 | 43 | private: 44 | void apply_config(SpiBusConfig* config); 45 | 46 | std::string id; 47 | spi_host_device_t host_device; 48 | SpiBusConfig* cur_config; 49 | }; 50 | -------------------------------------------------------------------------------- /lib/SpiManager/src/SpiBusConfig.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | class SpiBusConfig { 8 | public: 9 | explicit SpiBusConfig(gpio_num_t pin_mosi, gpio_num_t pin_miso, gpio_num_t pin_sclk); 10 | SpiBusConfig(const SpiBusConfig&) = delete; 11 | SpiBusConfig& operator=(const SpiBusConfig&) = delete; 12 | ~SpiBusConfig(); 13 | 14 | void patch(spi_host_device_t host_device); 15 | void unpatch(spi_host_device_t host_device); 16 | 17 | private: 18 | gpio_num_t pin_mosi; 19 | gpio_num_t pin_miso; 20 | gpio_num_t pin_sclk; 21 | }; 22 | -------------------------------------------------------------------------------- /lib/SpiManager/src/SpiCallback.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | // Pre and post callbacks for 2 buses with 3 devices each 8 | #define SPI_MANAGER_CALLBACK_COUNT 6 9 | 10 | class SpiBus; 11 | class SpiBusConfig; 12 | 13 | namespace SpiCallback { 14 | bool patch(const std::shared_ptr& bus, const std::shared_ptr& bus_config, spi_device_interface_config_t& device_config); 15 | } 16 | -------------------------------------------------------------------------------- /lib/SpiManager/src/SpiManager.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | #pragma once 3 | 4 | #include "SpiBus.h" 5 | #include "SpiBusConfig.h" 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define SPI_MANAGER_NUM_BUSES SOC_SPI_PERIPH_NUM 15 | 16 | class SpiManager { 17 | public: 18 | explicit SpiManager(); 19 | SpiManager(const SpiManager&) = delete; 20 | SpiManager& operator=(const SpiManager&) = delete; 21 | 22 | #ifdef ARDUINO 23 | static std::optional to_arduino(spi_host_device_t host_device); 24 | #endif 25 | 26 | bool register_bus(spi_host_device_t host_device); 27 | bool claim_bus(spi_host_device_t& host_device); 28 | #ifdef ARDUINO 29 | std::optional claim_bus_arduino(); 30 | #endif 31 | 32 | spi_device_handle_t alloc_device(const std::string& bus_id, const std::shared_ptr& bus_config, spi_device_interface_config_t& device_config); 33 | 34 | private: 35 | std::shared_ptr get_shared_bus(const std::string& bus_id); 36 | 37 | std::array, SPI_MANAGER_NUM_BUSES> available_buses; 38 | std::array, SPI_MANAGER_NUM_BUSES> shared_buses; 39 | }; 40 | 41 | extern SpiManager SpiManagerInst; 42 | -------------------------------------------------------------------------------- /lib/ThreadSafeQueue/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/lib/ThreadSafeQueue/README.md -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/TimeoutHelper/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/lib/TimeoutHelper/README.md -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /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, -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | #include 11 | 12 | MqttHandleDtuClass MqttHandleDtu; 13 | 14 | MqttHandleDtuClass::MqttHandleDtuClass() 15 | : _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&MqttHandleDtuClass::loop, this)) 16 | { 17 | } 18 | 19 | void MqttHandleDtuClass::init(Scheduler& scheduler) 20 | { 21 | scheduler.addTask(_loopTask); 22 | _loopTask.setInterval(Configuration.get().Mqtt.PublishInterval * TASK_SECOND); 23 | _loopTask.enable(); 24 | } 25 | 26 | void MqttHandleDtuClass::loop() 27 | { 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("dtu/uptime", String(millis() / 1000)); 36 | MqttSettings.publish("dtu/ip", NetworkSettings.localIP().toString()); 37 | MqttSettings.publish("dtu/hostname", NetworkSettings.getHostname()); 38 | MqttSettings.publish("dtu/heap/size", String(ESP.getHeapSize())); 39 | MqttSettings.publish("dtu/heap/free", String(ESP.getFreeHeap())); 40 | MqttSettings.publish("dtu/heap/minfree", String(ESP.getMinFreeHeap())); 41 | MqttSettings.publish("dtu/heap/maxalloc", String(ESP.getMaxAllocHeap())); 42 | if (NetworkSettings.NetworkMode() == network_mode::WiFi) { 43 | MqttSettings.publish("dtu/rssi", String(WiFi.RSSI())); 44 | MqttSettings.publish("dtu/bssid", WiFi.BSSIDstr()); 45 | } 46 | 47 | float temperature = CpuTemperature.read(); 48 | if (!std::isnan(temperature)) { 49 | MqttSettings.publish("dtu/temperature", String(temperature)); 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 | -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /src/RestartHelper.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Copyright (C) 2024 Thomas Basler and others 4 | */ 5 | #include "RestartHelper.h" 6 | #include "Display_Graphic.h" 7 | #include "Led_Single.h" 8 | #include 9 | 10 | RestartHelperClass RestartHelper; 11 | 12 | RestartHelperClass::RestartHelperClass() 13 | : _rebootTask(1 * TASK_SECOND, TASK_FOREVER, std::bind(&RestartHelperClass::loop, this)) 14 | { 15 | } 16 | 17 | void RestartHelperClass::init(Scheduler& scheduler) 18 | { 19 | scheduler.addTask(_rebootTask); 20 | } 21 | 22 | void RestartHelperClass::triggerRestart() 23 | { 24 | _rebootTask.enable(); 25 | _rebootTask.restart(); 26 | } 27 | 28 | void RestartHelperClass::loop() 29 | { 30 | if (_rebootTask.isFirstIteration()) { 31 | LedSingle.turnAllOff(); 32 | Display.setStatus(false); 33 | } else { 34 | ESP.restart(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 "RestartHelper.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["reboot"].is())) { 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 | RestartHelper.triggerRestart(); 47 | } else { 48 | retMsg["message"] = "Reboot cancled!"; 49 | retMsg["code"] = WebApiError::MaintenanceRebootCancled; 50 | 51 | WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /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 | _simpleDigestAuth.setUsername(AUTH_USERNAME); 26 | _simpleDigestAuth.setRealm("console websocket"); 27 | 28 | reload(); 29 | } 30 | 31 | void WebApiWsConsoleClass::reload() 32 | { 33 | _ws.removeMiddleware(&_simpleDigestAuth); 34 | 35 | auto const& config = Configuration.get(); 36 | 37 | if (config.Security.AllowReadonly) { return; } 38 | 39 | _ws.enable(false); 40 | _simpleDigestAuth.setPassword(config.Security.Password); 41 | _ws.addMiddleware(&_simpleDigestAuth); 42 | _ws.closeAll(); 43 | _ws.enable(true); 44 | } 45 | 46 | void WebApiWsConsoleClass::wsCleanupTaskCb() 47 | { 48 | // see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients 49 | _ws.cleanupClients(); 50 | } 51 | -------------------------------------------------------------------------------- /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/.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 | -------------------------------------------------------------------------------- /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/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /webapp/.yarn/install-state.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/webapp/.yarn/install-state.gz -------------------------------------------------------------------------------- /webapp/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /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/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { Router, Route } from 'vue-router' 4 | declare module 'vue' { 5 | interface ComponentCustomProperties { 6 | $router: Router 7 | $route: Route 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /webapp/eslint.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | import js from "@eslint/js"; 3 | import pluginVue from 'eslint-plugin-vue' 4 | import vueTsEslintConfig from '@vue/eslint-config-typescript' 5 | 6 | export default [ 7 | js.configs.recommended, 8 | ...pluginVue.configs['flat/essential'], 9 | ...vueTsEslintConfig(), 10 | { 11 | files: [ 12 | "**/*.vue", 13 | "**/*.js", 14 | "**/*.jsx", 15 | "**/*.cjs", 16 | "**/*.mjs", 17 | "**/*.ts", 18 | "**/*.tsx", 19 | "**/*.cts", 20 | "**/*.mts", 21 | ], 22 | languageOptions: { 23 | ecmaVersion: 2022 24 | }, 25 | } 26 | ] 27 | -------------------------------------------------------------------------------- /webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | OpenDTU 11 | 12 | 13 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /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 | "vue-google-charts": "^1.1.0", 17 | "vue3-calendar-heatmap": "^2.0.5", 18 | "tippy.js": "^6.3.7", 19 | "@popperjs/core": "^2.11.8", 20 | "bootstrap": "^5.3.3", 21 | "bootstrap-icons-vue": "^1.11.3", 22 | "mitt": "^3.0.1", 23 | "sortablejs": "^1.15.3", 24 | "spark-md5": "^3.0.2", 25 | "vue": "^3.5.12", 26 | "vue-i18n": "10.0.4", 27 | "vue-router": "^4.4.5" 28 | }, 29 | "devDependencies": { 30 | "@intlify/unplugin-vue-i18n": "^5.2.0", 31 | "@tsconfig/node22": "^22.0.0", 32 | "@types/bootstrap": "^5.2.10", 33 | "@types/node": "^22.9.0", 34 | "@types/pulltorefreshjs": "^0.1.7", 35 | "@types/sortablejs": "^1.15.8", 36 | "@types/spark-md5": "^3.0.5", 37 | "@vitejs/plugin-vue": "^5.1.4", 38 | "@vue/eslint-config-typescript": "^14.1.3", 39 | "@vue/tsconfig": "^0.5.1", 40 | "eslint": "^9.14.0", 41 | "eslint-plugin-vue": "^9.30.0", 42 | "npm-run-all": "^4.1.5", 43 | "prettier": "^3.3.3", 44 | "pulltorefreshjs": "^0.1.22", 45 | "sass": "=1.77.6", 46 | "terser": "^5.36.0", 47 | "typescript": "^5.6.3", 48 | "vite": "^5.4.10", 49 | "vite-plugin-compression": "^0.5.1", 50 | "vite-plugin-css-injected-by-js": "^3.5.2", 51 | "vue-tsc": "^2.1.10" 52 | }, 53 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" 54 | } 55 | -------------------------------------------------------------------------------- /webapp/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/webapp/public/favicon.ico -------------------------------------------------------------------------------- /webapp/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/webapp/public/favicon.png -------------------------------------------------------------------------------- /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/src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 17 | 18 | 25 | -------------------------------------------------------------------------------- /webapp/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/webapp/src/assets/logo.png -------------------------------------------------------------------------------- /webapp/src/components/CardElement.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 23 | -------------------------------------------------------------------------------- /webapp/src/components/EventLog.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 42 | -------------------------------------------------------------------------------- /webapp/src/components/FormFooter.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /webapp/src/components/FsInfo.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 42 | -------------------------------------------------------------------------------- /webapp/src/components/HintView.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 43 | -------------------------------------------------------------------------------- /webapp/src/components/InterfaceApInfo.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 34 | -------------------------------------------------------------------------------- /webapp/src/components/LocaleSwitcher.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 25 | -------------------------------------------------------------------------------- /webapp/src/components/ModalDialog.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 57 | -------------------------------------------------------------------------------- /webapp/src/components/StatusBadge.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 33 | -------------------------------------------------------------------------------- /webapp/src/components/TaskDetails.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 41 | -------------------------------------------------------------------------------- /webapp/src/components/WifiApInfo.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 46 | -------------------------------------------------------------------------------- /webapp/src/emitter.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vue' { 2 | interface ComponentCustomProperties { 3 | $emitter: Emitter; 4 | } 5 | } 6 | 7 | export {}; // Important! See note. 8 | -------------------------------------------------------------------------------- /webapp/src/main.ts: -------------------------------------------------------------------------------- 1 | import mitt from 'mitt'; 2 | import { createApp } from 'vue'; 3 | import App from './App.vue'; 4 | import { tooltip } from './plugins/bootstrap'; 5 | import router from './router'; 6 | import { i18n } from './i18n'; 7 | 8 | import 'bootstrap'; 9 | import './scss/styles.scss'; 10 | 11 | const app = createApp(App); 12 | 13 | const emitter = mitt(); 14 | app.config.globalProperties.$emitter = emitter; 15 | 16 | app.directive('tooltip', tooltip); 17 | 18 | app.use(router); 19 | app.use(i18n); 20 | 21 | app.mount('#app'); 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 8 | @include media-breakpoint-up(sm) { 9 | label.col-form-label, 10 | label.form-check-label { 11 | text-align: end; 12 | } 13 | } 14 | 15 | table.table tr:last-child th, 16 | table.table tr:last-child td { 17 | border-bottom: 0; 18 | } 19 | 20 | table.table { 21 | margin-bottom: 0; 22 | } 23 | 24 | div.card.card-table { 25 | // prevent tables in cards from 26 | // messing up the bottom corner radii 27 | overflow: hidden; 28 | } 29 | 30 | div.card.card-table tr > :first-child { 31 | padding-left: var(--bs-card-cap-padding-x); 32 | } 33 | 34 | div.card.card-table tr > :last-child { 35 | padding-right: var(--bs-card-cap-padding-x); 36 | } 37 | 38 | div.accordion-item.accordion-table tr > :first-child { 39 | padding-left: var(--bs-accordion-btn-padding-x); 40 | } 41 | 42 | div.accordion-item.accordion-table div.accordion-body { 43 | padding-top: 0; 44 | padding-left: 0; 45 | padding-right: 0; 46 | } 47 | 48 | div.card.card-table div.card-body { 49 | padding: 0; 50 | } 51 | 52 | div.card div.card-body > :last-child { 53 | margin-bottom: auto !important; 54 | } 55 | 56 | div.card.card-table table .value { 57 | text-align: right; 58 | padding-right: 0; 59 | } 60 | 61 | // blend cards into tabbed navigation (device admin view) 62 | div.card-tabbed { 63 | border-top: 0; 64 | border-top-left-radius: 0; 65 | border-top-right-radius: 0; 66 | } 67 | -------------------------------------------------------------------------------- /webapp/src/types/AlertResponse.ts: -------------------------------------------------------------------------------- 1 | export interface AlertResponse { 2 | message: string; 3 | type: string; 4 | code: number; 5 | show: boolean; 6 | } 7 | -------------------------------------------------------------------------------- /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/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 | locale: string; 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /webapp/src/types/File.ts: -------------------------------------------------------------------------------- 1 | export interface FileInfo { 2 | name: string; 3 | size: number; 4 | } 5 | -------------------------------------------------------------------------------- /webapp/src/types/GridProfileRawdata.ts: -------------------------------------------------------------------------------- 1 | export interface GridProfileRawdata { 2 | raw: Array; 3 | } 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/LimitConfig.ts: -------------------------------------------------------------------------------- 1 | export interface LimitConfig { 2 | serial: string; 3 | limit_value: number; 4 | limit_type: number; 5 | } 6 | -------------------------------------------------------------------------------- /webapp/src/types/LimitStatus.ts: -------------------------------------------------------------------------------- 1 | export interface LimitStatus { 2 | limit_relative: number; 3 | max_power: number; 4 | limit_set_status: string; 5 | } 6 | -------------------------------------------------------------------------------- /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 RadioStatistics { 25 | tx_request: number; 26 | tx_re_request: number; 27 | rx_success: number; 28 | rx_fail_nothing: number; 29 | rx_fail_partial: number; 30 | rx_fail_corrupt: number; 31 | rssi: number; 32 | } 33 | 34 | export interface Inverter { 35 | serial: string; 36 | name: string; 37 | order: number; 38 | data_age: number; 39 | poll_enabled: boolean; 40 | reachable: boolean; 41 | producing: boolean; 42 | limit_relative: number; 43 | limit_absolute: number; 44 | events: number; 45 | AC: InverterStatistics[]; 46 | DC: InverterStatistics[]; 47 | INV: InverterStatistics[]; 48 | radio_stats: RadioStatistics; 49 | } 50 | 51 | export interface Total { 52 | Power: ValueObject; 53 | YieldDay: ValueObject; 54 | YieldTotal: ValueObject; 55 | } 56 | 57 | export interface Hints { 58 | time_sync: boolean; 59 | default_password: boolean; 60 | radio_problem: boolean; 61 | } 62 | 63 | export interface LiveData { 64 | inverters: Inverter[]; 65 | total: Total; 66 | hints: Hints; 67 | } 68 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 type PinMapping = Array; 52 | -------------------------------------------------------------------------------- /webapp/src/types/SecurityConfig.ts: -------------------------------------------------------------------------------- 1 | export interface SecurityConfig { 2 | password: string; 3 | allow_readonly: boolean; 4 | } 5 | -------------------------------------------------------------------------------- /webapp/src/types/SystemStatus.ts: -------------------------------------------------------------------------------- 1 | export interface TaskDetail { 2 | name: string; 3 | stack_watermark: number; 4 | priority: number; 5 | } 6 | 7 | export interface SystemStatus { 8 | // HardwareInfo 9 | chipmodel: string; 10 | chiprevision: number; 11 | chipcores: number; 12 | cpufreq: number; 13 | cputemp: number; 14 | flashsize: number; 15 | // TaskDetails 16 | task_details: TaskDetail[]; 17 | // FirmwareInfo 18 | hostname: string; 19 | sdkversion: string; 20 | config_version: string; 21 | git_hash: string; 22 | git_is_hash: boolean; 23 | pioenv: string; 24 | resetreason_0: string; 25 | resetreason_1: string; 26 | cfgsavecount: number; 27 | uptime: number; 28 | update_text: string; 29 | update_url: string; 30 | update_status: string; 31 | // MemoryInfo 32 | heap_total: number; 33 | heap_used: number; 34 | heap_max_block: number; 35 | heap_min_free: number; 36 | littlefs_total: number; 37 | littlefs_used: number; 38 | psram_total: number; 39 | psram_used: number; 40 | sketch_total: number; 41 | sketch_used: number; 42 | // RadioInfo 43 | nrf_configured: boolean; 44 | nrf_connected: boolean; 45 | nrf_pvariant: boolean; 46 | cmt_configured: boolean; 47 | cmt_connected: boolean; 48 | } 49 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /webapp/src/utils/structure.ts: -------------------------------------------------------------------------------- 1 | export type Schema = { 2 | [key: string]: 'string' | 'number' | 'boolean' | 'object' | 'array' | Schema; 3 | }; 4 | 5 | /* eslint-disable @typescript-eslint/no-explicit-any */ 6 | export function hasStructure(obj: any, schema: Schema): boolean { 7 | if (typeof obj !== 'object' || obj === null) return false; 8 | 9 | for (const key in schema) { 10 | const expectedType = schema[key]; 11 | 12 | if (['string', 'number', 'boolean'].includes(expectedType as string)) { 13 | if (typeof obj[key] !== expectedType) return false; 14 | } else if (expectedType === 'object') { 15 | if (typeof obj[key] !== 'object' || obj[key] === null) return false; 16 | } else if (expectedType === 'array') { 17 | if (!Array.isArray(obj[key])) return false; 18 | } else if (typeof expectedType === 'object') { 19 | // Recursively check nested objects 20 | if (!hasStructure(obj[key], expectedType as Schema)) return false; 21 | } 22 | } 23 | 24 | return true; 25 | } 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 | -------------------------------------------------------------------------------- /webapp/src/utils/waitRestart.ts: -------------------------------------------------------------------------------- 1 | import type { Router } from 'vue-router'; 2 | 3 | export function waitRestart(router: Router) { 4 | setTimeout(() => { 5 | router.push('/wait'); 6 | }, 1000); 7 | } 8 | -------------------------------------------------------------------------------- /webapp/src/views/ErrorView.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | -------------------------------------------------------------------------------- /webapp/src/views/NetworkInfoView.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 59 | -------------------------------------------------------------------------------- /webapp/tsconfig.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@tsconfig/node22/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 | } 19 | -------------------------------------------------------------------------------- /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": ["ES2023", "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 | -------------------------------------------------------------------------------- /webapp/yarn: -------------------------------------------------------------------------------- 1 | /usr/share/nodejs/yarn/bin/yarn -------------------------------------------------------------------------------- /webapp_dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/webapp_dist/favicon.ico -------------------------------------------------------------------------------- /webapp_dist/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/webapp_dist/favicon.png -------------------------------------------------------------------------------- /webapp_dist/index.html.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/webapp_dist/index.html.gz -------------------------------------------------------------------------------- /webapp_dist/js/app.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/webapp_dist/js/app.js.gz -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /webapp_dist/zones.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaBa64/OpenDTU-Database/88557e49d45c56dcb4cd5d83e6bea100410067ec/webapp_dist/zones.json.gz --------------------------------------------------------------------------------