├── .create-adapter.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml ├── auto-merge.yml ├── dependabot.yml └── workflows │ ├── codeql.yml │ ├── dependabot-auto-merge.yml │ └── test-and-release.yml ├── .gitignore ├── .releaseconfig.json ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CHANGELOG_OLD.md ├── LICENSE ├── README.md ├── admin ├── i18n │ ├── de │ │ └── translations.json │ ├── en │ │ └── translations.json │ ├── es │ │ └── translations.json │ ├── fr │ │ └── translations.json │ ├── it │ │ └── translations.json │ ├── nl │ │ └── translations.json │ ├── pl │ │ └── translations.json │ ├── pt │ │ └── translations.json │ ├── ru │ │ └── translations.json │ ├── uk │ │ └── translations.json │ └── zh-cn │ │ └── translations.json ├── jsonConfig.json └── opendtu.png ├── eslint.config.mjs ├── io-package.json ├── lib ├── adapter-config.d.ts ├── dataController.js ├── stateDefinition.js ├── tools.js └── websocketController.js ├── main.js ├── main.test.js ├── package-lock.json ├── package.json ├── prettier.config.mjs ├── test ├── integration.js ├── mocha.setup.js ├── mocharc.custom.json ├── package.js └── tsconfig.json ├── tsconfig.check.json └── tsconfig.json /.create-adapter.json: -------------------------------------------------------------------------------- 1 | { 2 | "expert": "yes", 3 | "cli": false, 4 | "adapterName": "opendtu", 5 | "title": "OpenDTU", 6 | "description": "Adapter for the OpenDTU project", 7 | "keywords": [ 8 | "OpenDTU", 9 | "Solar" 10 | ], 11 | "authorName": "Dennis Rathjen", 12 | "authorGithub": "o0shojo0o", 13 | "authorEmail": "dennis.rathjen@outlook.de", 14 | "features": [ 15 | "adapter" 16 | ], 17 | "adminFeatures": [ 18 | "tab" 19 | ], 20 | "type": "energy", 21 | "startMode": "daemon", 22 | "connectionType": "local", 23 | "dataSource": "push", 24 | "connectionIndicator": "yes", 25 | "adapterSettings": [ 26 | { 27 | "key": "option1", 28 | "defaultValue": true, 29 | "inputType": "checkbox" 30 | }, 31 | { 32 | "key": "option2", 33 | "defaultValue": "42", 34 | "inputType": "text" 35 | } 36 | ], 37 | "language": "JavaScript", 38 | "nodeVersion": "14", 39 | "adminUi": "json", 40 | "tabReact": "no", 41 | "releaseScript": "yes", 42 | "indentation": "Tab", 43 | "quotes": "single", 44 | "es6class": "yes", 45 | "tools": [ 46 | "ESLint", 47 | "type checking" 48 | ], 49 | "gitRemoteProtocol": "HTTPS", 50 | "license": "MIT License", 51 | "dependabot": "yes", 52 | "target": "github", 53 | "creatorVersion": "2.3.0" 54 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: "Report a Bug" 3 | labels: [bug] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | **IMPORTANT:** Before submitting: 9 | - Is your OpenDTU up to date? [Releases](https://github.com/tbnobody/OpenDTU/releases) 10 | - type: textarea 11 | id: what_happend 12 | attributes: 13 | label: What happened? 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: expect_to_happen 18 | attributes: 19 | label: What did you expect to happen? 20 | placeholder: I expected that ... 21 | validations: 22 | required: false 23 | - type: textarea 24 | id: reproduce 25 | attributes: 26 | label: How to reproduce it (minimal and precise) 27 | placeholder: First do this, than this.. 28 | validations: 29 | required: false 30 | - type: input 31 | id: opendtu_version 32 | attributes: 33 | label: OpenDTU version 34 | placeholder: 0.0.0 35 | validations: 36 | required: true 37 | - type: input 38 | id: adapter_version 39 | attributes: 40 | label: Adapter version 41 | placeholder: 0.0.0 42 | validations: 43 | required: true 44 | - type: textarea 45 | id: log 46 | attributes: 47 | label: Log 48 | validations: 49 | required: false 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | -------------------------------------------------------------------------------- /.github/auto-merge.yml: -------------------------------------------------------------------------------- 1 | # Configure here which dependency updates should be merged automatically. 2 | # The recommended configuration is the following: 3 | - match: 4 | # Only merge patches for production dependencies 5 | dependency_type: production 6 | update_type: "semver:patch" 7 | - match: 8 | # Except for security fixes, here we allow minor patches 9 | dependency_type: production 10 | update_type: "security:minor" 11 | - match: 12 | # and development dependencies can have a minor update, too 13 | dependency_type: development 14 | update_type: "semver:minor" 15 | 16 | # The syntax is based on the legacy dependabot v1 automerged_updates syntax, see: 17 | # https://dependabot.com/docs/config-file/#automerged_updates 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | time: "04:00" 8 | timezone: Europe/Berlin 9 | open-pull-requests-limit: 15 10 | versioning-strategy: increase 11 | 12 | - package-ecosystem: github-actions 13 | directory: "/" 14 | schedule: 15 | interval: monthly 16 | time: "04:00" 17 | timezone: Europe/Berlin 18 | open-pull-requests-limit: 15 19 | 20 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: ["main"] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: ["main"] 20 | schedule: 21 | - cron: "21 8 * * 2" 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: ["javascript"] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v4 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v3 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 56 | # If this step fails, then you should remove it and run the build manually (see below) 57 | - name: Autobuild 58 | uses: github/codeql-action/autobuild@v3 59 | 60 | # ℹ️ Command-line programs to run using the OS shell. 61 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 62 | 63 | # If the Autobuild fails above, remove it and uncomment the following three lines. 64 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 65 | 66 | # - run: | 67 | # echo "Run, Build Application using script" 68 | # ./location_of_script_within_repo/buildscript.sh 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v3 72 | with: 73 | category: "/language:${{matrix.language}}" 74 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-auto-merge.yml: -------------------------------------------------------------------------------- 1 | # Automatically merge Dependabot PRs when version comparison is within the range 2 | # that is configured in .github/auto-merge.yml 3 | 4 | name: Auto-Merge Dependabot PRs 5 | 6 | on: 7 | # WARNING: This needs to be run in the PR base, DO NOT build untrusted code in this action 8 | # details under https://github.blog/changelog/2021-02-19-github-actions-workflows-triggered-by-dependabot-prs-will-run-with-read-only-permissions/ 9 | pull_request_target: 10 | 11 | jobs: 12 | auto-merge: 13 | if: github.actor == 'dependabot[bot]' 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v4 18 | 19 | - name: Check if PR should be auto-merged 20 | uses: ahmadnassri/action-dependabot-auto-merge@v2 21 | with: 22 | # In order to use this, you need to go to https://github.com/settings/tokens and 23 | # create a Personal Access Token with the permission "public_repo". 24 | # Enter this token in your repository settings under "Secrets" and name it AUTO_MERGE_TOKEN 25 | github-token: ${{ secrets.AUTO_MERGE_TOKEN }} 26 | # By default, squash and merge, so Github chooses nice commit messages 27 | command: squash and merge 28 | -------------------------------------------------------------------------------- /.github/workflows/test-and-release.yml: -------------------------------------------------------------------------------- 1 | name: Test and Release 2 | 3 | # Run this job on all pushes and pull requests 4 | # as well as tags with a semantic version 5 | on: 6 | push: 7 | branches: 8 | - "main" 9 | tags: 10 | # normal versions 11 | - "v[0-9]+.[0-9]+.[0-9]+" 12 | # pre-releases 13 | - "v[0-9]+.[0-9]+.[0-9]+-**" 14 | pull_request: {} 15 | 16 | # Cancel previous PR/branch runs when a new commit is pushed 17 | concurrency: 18 | group: ${{ github.ref }} 19 | cancel-in-progress: true 20 | 21 | jobs: 22 | # Performs quick checks before the expensive test runs 23 | check-and-lint: 24 | if: contains(github.event.head_commit.message, '[skip ci]') == false 25 | 26 | runs-on: ubuntu-latest 27 | 28 | steps: 29 | - uses: ioBroker/testing-action-check@v1 30 | with: 31 | node-version: "20.x" 32 | # Uncomment the following line if your adapter cannot be installed using 'npm ci' 33 | # install-command: 'npm install' 34 | lint: true 35 | 36 | # Runs adapter tests on all supported node versions and OSes 37 | adapter-tests: 38 | if: contains(github.event.head_commit.message, '[skip ci]') == false 39 | 40 | runs-on: ${{ matrix.os }} 41 | strategy: 42 | matrix: 43 | node-version: [20.x, 22.x] 44 | os: [ubuntu-latest, windows-latest, macos-latest] 45 | 46 | steps: 47 | - uses: ioBroker/testing-action-adapter@v1 48 | with: 49 | node-version: ${{ matrix.node-version }} 50 | os: ${{ matrix.os }} 51 | # Uncomment the following line if your adapter cannot be installed using 'npm ci' 52 | # install-command: 'npm install' 53 | 54 | # Deploys the final package to NPM 55 | deploy: 56 | needs: [check-and-lint, adapter-tests] 57 | 58 | # Trigger this step only when a commit on any branch is tagged with a version number 59 | if: | 60 | contains(github.event.head_commit.message, '[skip ci]') == false && 61 | github.event_name == 'push' && 62 | startsWith(github.ref, 'refs/tags/v') 63 | 64 | runs-on: ubuntu-latest 65 | 66 | # Write permissions are required to create Github releases 67 | permissions: 68 | contents: write 69 | 70 | steps: 71 | - uses: ioBroker/testing-action-deploy@v1 72 | with: 73 | node-version: "20.x" 74 | # Uncomment the following line if your adapter cannot be installed using 'npm ci' 75 | # install-command: 'npm install' 76 | npm-token: ${{ secrets.NPM_TOKEN }} 77 | github-token: ${{ secrets.ACCESS_TOKEN }} 78 | 79 | # # When using Sentry for error reporting, Sentry can be informed about new releases 80 | # # To enable create a API-Token in Sentry (User settings, API keys) 81 | # # Enter this token as a GitHub secret (with name SENTRY_AUTH_TOKEN) in the repository options 82 | # # Then uncomment and customize the following block: 83 | # sentry: false 84 | # sentry-token: ${{ secrets.SENTRY_AUTH_TOKEN }} 85 | # sentry-project: "iobroker-opendtu" 86 | # sentry-version-prefix: "iobroker.opendtu" 87 | # # If your sentry project is linked to a GitHub repository, you can enable the following option 88 | # # sentry-github-integration: true 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # No dot-directories except github/vscode 2 | .*/ 3 | !.vscode/ 4 | !.github/ 5 | 6 | *.code-workspace 7 | node_modules 8 | nbproject 9 | 10 | # npm package files 11 | iobroker.*.tgz 12 | 13 | Thumbs.db 14 | 15 | # i18n intermediate files 16 | admin/i18n/flat.txt 17 | admin/i18n/*/flat.txt -------------------------------------------------------------------------------- /.releaseconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "iobroker", 4 | "license", 5 | "manual-review" 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint" 4 | ] 5 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Launch ioBroker Adapter", 8 | "skipFiles": [ 9 | "/**" 10 | ], 11 | "args": [ 12 | "--debug", 13 | "0" 14 | ], 15 | "program": "node_modules/iobroker.opendtu/main.js", 16 | "cwd": "${workspaceFolder}/.dev-server/default" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true, 3 | "json.schemas": [ 4 | { 5 | "fileMatch": [ 6 | "io-package.json" 7 | ], 8 | "url": "https://raw.githubusercontent.com/ioBroker/ioBroker.js-controller/master/schemas/io-package.json" 9 | }, 10 | { 11 | "fileMatch": [ 12 | "admin/jsonConfig.json", 13 | "admin/jsonCustom.json", 14 | "admin/jsonTab.json" 15 | ], 16 | "url": "https://raw.githubusercontent.com/ioBroker/adapter-react-v5/main/schemas/jsonConfig.json" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /CHANGELOG_OLD.md: -------------------------------------------------------------------------------- 1 | # Older changes 2 | ## 1.0.1 (2023-10-29) 3 | 4 | - (o0shojo0o) fixed `power_control.current_limit_absolute" has value "-1" less than min "0"` 5 | 6 | ## 1.0.0 (2023-10-01) 7 | 8 | - (o0shojo0o) Increase to the first major release, as it has now reached a stable level. 9 | - (o0shojo0o) added yieldtotal Protection against incorrect zeroing when the OpenDTU restarts if the inverter is not accessible 10 | - (o0shojo0o) added option `Set the states to 0 if the inverter is not accessible.` ([#97](https://github.com/o0shojo0o/ioBroker.opendtu/issues/97)) 11 | 12 | ## 0.1.8 (2023-09-22) 13 | 14 | - (o0shojo0o) added option `Protect self-set names from being overwritten by the adapter` ([#76](https://github.com/o0shojo0o/ioBroker.opendtu/issues/76)) 15 | - (o0shojo0o) allow multiple AdminTabs for multiple instances ([#88](https://github.com/o0shojo0o/ioBroker.opendtu/issues/88)) 16 | - (o0shojo0o) fixed password with special characters ([#35](https://github.com/o0shojo0o/ioBroker.opendtu/issues/35)) 17 | - (o0shojo0o) fixed incorrect handling of zeroing of `yield*` data points by OpenDTU ([#96](https://github.com/o0shojo0o/ioBroker.opendtu/issues/96)) 18 | - (o0shojo0o) remove zeroing of `yield*` data points by this adapter ([#96](https://github.com/o0shojo0o/ioBroker.opendtu/issues/96)) 19 | 20 | ## 0.1.7 (2023-06-30) 21 | 22 | - (o0shojo0o) workaround for incorrectly used button data point 23 | 24 | ## 0.1.6 (2023-06-30) 25 | 26 | - (o0shojo0o) fixed power control (power_off) 27 | 28 | ## 0.1.5 (2023-05-15) 29 | 30 | - (o0shojo0o) code optimizations 31 | 32 | ## 0.1.4 (2023-03-23) 33 | 34 | - (o0shojo0o) fixed power control `on`, `off`, `restart` 35 | - (o0shojo0o) support for password protected liveview 36 | - (o0shojo0o) other small fixes 37 | 38 | ## 0.1.2 (2023-03-03) 39 | 40 | - (o0shojo0o) fixed yield* values 41 | 42 | ## 0.1.1 (2023-02-24) 43 | 44 | - (o0shojo0o) state rolls corrected 45 | - (o0shojo0o) add DTU datapoint `rssi` and `ip` 46 | - (o0shojo0o) repeated writing of the yieldtotal set to 00:01:00. (is necessary for e.g. sourceanalytix) 47 | 48 | ## 0.1.0 (2023-02-17) 49 | 50 | - (o0shojo0o) initial release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Dennis Rathjen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](admin/opendtu.png) 2 | # ioBroker.opendtu 3 | 4 | [![NPM version](https://img.shields.io/npm/v/iobroker.opendtu.svg)](https://www.npmjs.com/package/iobroker.opendtu) 5 | [![Downloads](https://img.shields.io/npm/dm/iobroker.opendtu.svg)](https://www.npmjs.com/package/iobroker.opendtu) 6 | ![Number of Installations](https://iobroker.live/badges/opendtu-installed.svg) 7 | ![Current version in stable repository](https://iobroker.live/badges/opendtu-stable.svg) 8 | 9 | [![NPM](https://nodei.co/npm/iobroker.opendtu.png?downloads=true)](https://nodei.co/npm/iobroker.opendtu/) 10 | 11 | **Tests:** ![Test and Release](https://github.com/o0shojo0o/ioBroker.opendtu/workflows/Test%20and%20Release/badge.svg) [![CodeQL](https://github.com/o0shojo0o/ioBroker.opendtu/actions/workflows/codeql.yml/badge.svg)](https://github.com/o0shojo0o/ioBroker.opendtu/actions/workflows/codeql.yml) 12 | 13 | ## opendtu adapter for ioBroker 14 | 15 | This adapter get the data points from the project [OpenDTU](https://github.com/tbnobody/OpenDTU) available in real time. 16 | In addition, the following data points can be used via the adapter to the power limitation of the OpenDTU can be controlled. 17 | 18 | ``` 19 | - opendtu.0.xxxxxx.power_control.limit_nonpersistent_absolute 20 | - opendtu.0.xxxxxx.power_control.limit_nonpersistent_relative 21 | - opendtu.0.xxxxxx.power_control.limit_persistent_absolute 22 | - opendtu.0.xxxxxx.power_control.limit_persistent_relative 23 | ``` 24 | For more information on the data points, see their description or click [here](https://github.com/tbnobody/OpenDTU/blob/master/docs/MQTT_Topics.md#inverter-limit-specific-topics). 25 | 26 | ## Credits 27 | 28 | This adapter would not have been possible without the great work of @o0Shojo0o (https://github.com/o0Shojo0o), who developed former releases of this adapter. 29 | 30 | ## How to report issues and feature requests 31 | 32 | Ideally, please use GitHub issues for this, with the best method achieved by setting the adapter to Debug log mode (Instances -> Expert mode -> Column Log level). Then retrieve the logfile from disk via the 'log' ioBroker subdirectory, **not** from Admin, which will cut lines. 33 | 34 | ## Configuration 35 | 36 | 1. Create a new instance of the adapter 37 | 2. Fill in Security *(default http)*, IP-Address and port *(default 80)* of the [OpenDTU](https://github.com/tbnobody/OpenDTU) hardware 38 | 3. Set the WebUI-Password **(this is mandatory, if it is incorrect no limit can be set!)** 39 | 4. Save the settings 40 | 41 | ## Changelog 42 | 46 | ### 3.1.0 (2024-12-02) 47 | - (mattreim) Variable polling interval has been removed and polling intervals hev been increased. 48 | - (mattreim) Description has been translated into supported languages. 49 | - (mattreim) Admin-UI has been adapted for some display sizes. 50 | - (mcm1957) Dependencies have been updated. 51 | 52 | ### 3.0.1 (2024-10-26) 53 | - (simatec) Admin-UI has been adapted for small displays. 54 | - (mcm1957) Dependencies have been updated. 55 | 56 | ### 3.0.0 (2024-10-19) 57 | - (mcm1957) Adapter has been moved to iobroker-community-adapter organisation. 58 | - (mcm1957) Adapter requires js-controller 5, admin 6 and node.js 20 now. 59 | - (mcm1957) Dependencies have been updated. 60 | 61 | ### 2.1.0 (2024-10-11) 62 | 63 | - (o0shojo0o) update dependencies 64 | - (mattreim) support small screens 65 | - (mattreim) update translations 66 | - (mattreim) update object names 67 | - (mattreim) add variable polling intervall [1-59s] 68 | 69 | ### 2.0.0 (2024-08-13) 70 | 71 | - (o0shojo0o) changes for new websocket structure ([#129](https://github.com/o0shojo0o/ioBroker.opendtu/issues/129)) 72 | - (o0shojo0o) `Efficiency`, `YieldTotal`, `YieldDay` and `DC Power` moved from the AC section to the INV (old data points must be removed manually) 73 | - (mattreim) update to current OpenDTU logo ([#156](https://github.com/o0shojo0o/ioBroker.opendtu/issues/156)) 74 | - (mattreim) update dependencies ([#162](https://github.com/o0shojo0o/ioBroker.opendtu/issues/162)), ([#179](https://github.com/o0shojo0o/ioBroker.opendtu/issues/179)) 75 | - (mattreim) fix GUI translation ([#163](https://github.com/o0shojo0o/ioBroker.opendtu/issues/163)) 76 | 77 | ## License 78 | MIT License 79 | 80 | Copyright (c) 2024 ioBroker Community Developers 81 | Copyright (c) 2024 Dennis Rathjen 82 | 83 | Permission is hereby granted, free of charge, to any person obtaining a copy 84 | of this software and associated documentation files (the "Software"), to deal 85 | in the Software without restriction, including without limitation the rights 86 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 87 | copies of the Software, and to permit persons to whom the Software is 88 | furnished to do so, subject to the following conditions: 89 | 90 | The above copyright notice and this permission notice shall be included in all 91 | copies or substantial portions of the Software. 92 | 93 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 94 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 95 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 96 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 97 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 98 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 99 | SOFTWARE. 100 | -------------------------------------------------------------------------------- /admin/i18n/de/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Config": "Konfigurieren der OpenDTU-WebUI-Verbindung", 3 | "Scheme": "Sicherheit", 4 | "WebAddress": "IP-Adresse", 5 | "WebPort": "Port", 6 | "WebPoll": "Aktualisierung nach x Sekunden", 7 | "WebPassword": "WebUI-Passwort", 8 | "OtherConfig": "Andere Konfigurationen", 9 | "ProtNames": "Schützen von selbst festgelegten Namen vor dem Überschreiben durch den Adapter", 10 | "StatesToZero": "Setzen der Zustände auf 0, wenn der Wechselrichter nicht verfügbar ist" 11 | } -------------------------------------------------------------------------------- /admin/i18n/en/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Config": "Configuring the OpenDTU WebUI connection", 3 | "Scheme": "Security", 4 | "WebAddress": "IP Address", 5 | "WebPort": "Port", 6 | "WebPoll": "refresh after x seconds", 7 | "WebPassword": "WebUI Password", 8 | "OtherConfig": "Other configurations", 9 | "ProtNames": "Protect self-defined names from being overwritten by the adapter", 10 | "StatesToZero": "Setting the states to 0 if the inverter is not available" 11 | } -------------------------------------------------------------------------------- /admin/i18n/es/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Config": "Configuración de la conexión OpenDTU WebUI", 3 | "Scheme": "Seguridad", 4 | "WebAddress": "Dirección IP", 5 | "WebPort": "Puerto", 6 | "WebPoll": "actualizar después de x segundos", 7 | "WebPassword": "Contraseña de la interfaz de usuario web", 8 | "OtherConfig": "Otras configuraciones", 9 | "ProtNames": "Proteger los nombres autodefinidos para que el adaptador no los sobrescriba", 10 | "StatesToZero": "Establecer los estados en 0 si el inversor no está disponible" 11 | } -------------------------------------------------------------------------------- /admin/i18n/fr/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Config": "Configuration de la connexion OpenDTU WebUI", 3 | "Scheme": "Sécurité", 4 | "WebAddress": "Adresse IP", 5 | "WebPort": "Port", 6 | "WebPoll": "actualiser après x secondes", 7 | "WebPassword": "Mot de passe de l'interface utilisateur Web", 8 | "OtherConfig": "Autres configurations", 9 | "ProtNames": "Protéger les noms auto-définis contre l'écrasement par l'adaptateur", 10 | "StatesToZero": "Mise des états à 0 si l'onduleur n'est pas disponible" 11 | } -------------------------------------------------------------------------------- /admin/i18n/it/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Config": "Configurazione della connessione OpenDTU WebUI", 3 | "Scheme": "Sicurezza", 4 | "WebAddress": "Indirizzo IP", 5 | "WebPort": "Porta", 6 | "WebPoll": "aggiorna dopo x secondi", 7 | "WebPassword": "Password dell'interfaccia utente Web", 8 | "OtherConfig": "Altre configurazioni", 9 | "ProtNames": "Proteggi i nomi autodefiniti dalla sovrascrittura da parte dell'adattatore", 10 | "StatesToZero": "Impostazione degli stati a 0 se l'inverter non è disponibile" 11 | } -------------------------------------------------------------------------------- /admin/i18n/nl/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Config": "De OpenDTU WebUI-verbinding configureren", 3 | "Scheme": "Beveiliging", 4 | "WebAddress": "IP-adres", 5 | "WebPort": "Port", 6 | "WebPoll": "vernieuwen na x seconden", 7 | "WebPassword": "WebUI-wachtwoord", 8 | "OtherConfig": "Andere configuraties", 9 | "ProtNames": "Bescherm zelfgedefinieerde namen tegen overschrijving door de adapter", 10 | "StatesToZero": "De status op 0 zetten als de omvormer niet beschikbaar is" 11 | } -------------------------------------------------------------------------------- /admin/i18n/pl/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Config": "Konfigurowanie połączenia OpenDTU WebUI", 3 | "Scheme": "Bezpieczeństwo", 4 | "WebAddress": "Adres IP", 5 | "WebPort": "Port", 6 | "WebPoll": "odśwież po x sekundach", 7 | "WebPassword": "Hasło WebUI", 8 | "OtherConfig": "Inne konfiguracje", 9 | "ProtNames": "Chroń samodzielnie zdefiniowane nazwy przed nadpisaniem przez adapter", 10 | "StatesToZero": "Ustawienie stanów na 0 w przypadku braku falownika" 11 | } -------------------------------------------------------------------------------- /admin/i18n/pt/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Config": "Configurando a conexão OpenDTU WebUI", 3 | "Scheme": "Segurança", 4 | "WebAddress": "Endereço IP", 5 | "WebPort": "Porta", 6 | "WebPoll": "atualizar após x segundos", 7 | "WebPassword": "Senha da WebUI", 8 | "OtherConfig": "Outras configurações", 9 | "ProtNames": "Proteja nomes autodefinidos contra substituição pelo adaptador", 10 | "StatesToZero": "Definir os estados para 0 se o inversor não estiver disponível" 11 | } -------------------------------------------------------------------------------- /admin/i18n/ru/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Config": "Настройка подключения OpenDTU WebUI", 3 | "Scheme": "Безопасность", 4 | "WebAddress": "IP-адрес", 5 | "WebPort": "Порт", 6 | "WebPoll": "обновить через x секунд", 7 | "WebPassword": "Пароль веб-интерфейса", 8 | "OtherConfig": "Другие конфигурации", 9 | "ProtNames": "Защита самоопределяемых имен от перезаписи адаптером", 10 | "StatesToZero": "Установка состояний на 0, если инвертор недоступен" 11 | } -------------------------------------------------------------------------------- /admin/i18n/uk/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Config": "Налаштування підключення OpenDTU WebUI", 3 | "Scheme": "Безпека", 4 | "WebAddress": "IP-адреса", 5 | "WebPort": "Порт", 6 | "WebPoll": "оновити через x секунд", 7 | "WebPassword": "Пароль WebUI", 8 | "OtherConfig": "Інші конфігурації", 9 | "ProtNames": "Захистіть самовизначені імена від перезапису адаптером", 10 | "StatesToZero": "Встановлення станів на 0, якщо інвертор недоступний" 11 | } -------------------------------------------------------------------------------- /admin/i18n/zh-cn/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Config": "配置 OpenDTU WebUI 連接", 3 | "Scheme": "安全", 4 | "WebAddress": "IP地址", 5 | "WebPort": "港口", 6 | "WebPoll": "x 秒后刷新", 7 | "WebPassword": "网页密码", 8 | "OtherConfig": "其他配置", 9 | "ProtNames": "保護自訂名稱不被適配器覆蓋", 10 | "StatesToZero": "如果逆變器不可用,則將狀態設為 0" 11 | } -------------------------------------------------------------------------------- /admin/jsonConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "i18n": true, 3 | "type": "panel", 4 | "items": { 5 | "webUIHeader": { 6 | "type": "header", 7 | "text": "Config", 8 | "size": 2, 9 | "xs": 12, 10 | "sm": 12, 11 | "md": 12, 12 | "lg": 12, 13 | "xl": 12 14 | }, 15 | "webUIScheme": { 16 | "newLine": true, 17 | "type": "select", 18 | "label": "Scheme", 19 | "options": [ 20 | { 21 | "label": "HTTP", 22 | "value": "http" 23 | }, 24 | { 25 | "label": "HTTPS", 26 | "value": "https" 27 | } 28 | ], 29 | "xs": 12, 30 | "sm": 12, 31 | "md": 2, 32 | "lg": 2, 33 | "xl": 1 34 | }, 35 | "webUIServer": { 36 | "type": "text", 37 | "label": "WebAddress", 38 | "xs": 12, 39 | "sm": 12, 40 | "md": 6, 41 | "lg": 3, 42 | "xl": 3 43 | }, 44 | "webUIPort": { 45 | "type": "number", 46 | "label": "WebPort", 47 | "min": 1, 48 | "max": 65535, 49 | "xs": 12, 50 | "sm": 12, 51 | "md": 2, 52 | "lg": 2, 53 | "xl": 1 54 | }, 55 | "password": { 56 | "newLine": true, 57 | "type": "password", 58 | "label": "WebPassword", 59 | "visible": true, 60 | "xs": 12, 61 | "sm": 12, 62 | "md": 6, 63 | "lg": 3, 64 | "xl": 3 65 | }, 66 | "otherHeader": { 67 | "newLine": true, 68 | "type": "header", 69 | "text": "OtherConfig", 70 | "size": 2, 71 | "xs": 12, 72 | "sm": 12, 73 | "md": 12, 74 | "lg": 12, 75 | "xl": 12 76 | }, 77 | "protectNames": { 78 | "newLine": true, 79 | "type": "checkbox", 80 | "label": "ProtNames", 81 | "xs": 12, 82 | "sm": 12, 83 | "md": 12, 84 | "lg": 12, 85 | "xl": 12 86 | }, 87 | "useInvOfflineStatesToZero": { 88 | "newLine": true, 89 | "type": "checkbox", 90 | "label": "StatesToZero", 91 | "xs": 12, 92 | "sm": 12, 93 | "md": 12, 94 | "lg": 12, 95 | "xl": 12 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /admin/opendtu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iobroker-community-adapters/ioBroker.opendtu/8f85a6c7de7954ad9175ff60956232ff5c064f53/admin/opendtu.png -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // ioBroker eslint template configuration file for js and ts files 2 | // Please note that esm or react based modules need additional modules loaded. 3 | import config from '@iobroker/eslint-config'; 4 | 5 | export default [ 6 | ...config, 7 | 8 | { 9 | // specify files to exclude from linting here 10 | ignores: [ 11 | '.dev-server/', 12 | '.vscode/', 13 | '*.test.js', 14 | 'test/**/*.js', 15 | '*.config.mjs', 16 | 'build', 17 | 'admin/build', 18 | 'admin/words.js', 19 | 'admin/admin.d.ts', 20 | '**/adapter-config.d.ts' 21 | ] 22 | }, 23 | 24 | { 25 | // you may disable some 'jsdoc' warnings - but using jsdoc is highly recommended 26 | // as this improves maintainability. jsdoc warnings will not block buiuld process. 27 | rules: { 28 | // 'jsdoc/require-jsdoc': 'off', 29 | }, 30 | }, 31 | 32 | ]; -------------------------------------------------------------------------------- /io-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "common": { 3 | "name": "opendtu", 4 | "version": "3.1.0", 5 | "news": { 6 | "3.1.0": { 7 | "en": "Variable polling interval has been removed and polling intervals hev been increased.\nDescription has been translated into supported languages.\nAdmin-UI has been adapted for some display sizes.\nDependencies have been updated.", 8 | "de": "Variables Abfrageintervall wurde entfernt und die Abfrageintervalle wurden erhöht.\nBeschreibung wurde in unterstützte Sprachen übersetzt.\nAdmin-UI wurde für einige Displaygrößen angepasst.\nAbhängigkeiten wurden aktualisiert.", 9 | "ru": "Был удален изменяемый интервал голосования и увеличены интервалы между голосованием.\nОписание переведено на поддерживаемые языки.\nAdmin-UI был адаптирован для некоторых размеров дисплея.\nЗависимость была обновлена.", 10 | "pt": "O intervalo de votação variável foi removido e os intervalos de votação foram aumentados.\nA descrição foi traduzida em idiomas suportados.\nAdmin-UI foi adaptado para alguns tamanhos de exibição.\nAs dependências foram atualizadas.", 11 | "nl": "Het variabele peilingsinterval is verwijderd en de peilingsintervallen zijn verhoogd.\nBeschrijving is vertaald in ondersteunde talen.\nBeheerder-UI is aangepast voor sommige weergavegroottes.\nAfhankelijkheden zijn bijgewerkt.", 12 | "fr": "L'intervalle de scrutin variable a été supprimé et les intervalles de scrutin ont été augmentés.\nLa description a été traduite dans des langues prises en charge.\nAdmin-UI a été adapté pour certaines tailles d'affichage.\nLes dépendances ont été actualisées.", 13 | "it": "Intervallo variabile è stato rimosso e gli intervalli di polling sono stati aumentati.\nLa descrizione è stata tradotta in lingue supportate.\nAdmin-UI è stato adattato per alcune dimensioni del display.\nLe dipendenze sono state aggiornate.", 14 | "es": "Se ha eliminado el intervalo de votación variable y se han incrementado los intervalos de votación.\nLa descripción se ha traducido a idiomas compatibles.\nAdmin-UI ha sido adaptado para algunos tamaños de pantalla.\nSe han actualizado las dependencias.", 15 | "pl": "Usunięto zmienny odstęp między sondażami oraz zwiększono odstęp między sondażami.\nOpis został przetłumaczony na obsługiwane języki.\nAdmin- UI został dostosowany do niektórych rozmiarów wyświetlacza.\nZaktualizowano zależność.", 16 | "uk": "Збільшився термін запилення.\nОпис перекладено на підтримувані мови.\nАдмін-УІ адаптований для деяких розмірів відображення.\nОновлено залежність.", 17 | "zh-cn": "可变投票间隔被取消,投票间隔延长.\n描述已翻译成辅助语言.\nAdmin-UI已经适应一些显示大小.\n附属关系已经更新." 18 | }, 19 | "3.0.1": { 20 | "en": "Admin-UI has been adapted for small displays.\nDependencies have been updated.", 21 | "de": "Admin-UI wurde für kleine Displays angepasst.\nAbhängigkeiten wurden aktualisiert.", 22 | "ru": "Admin-UI был адаптирован для небольших дисплеев.\nЗависимость была обновлена.", 23 | "pt": "Admin-UI foi adaptado para pequenos monitores.\nAs dependências foram atualizadas.", 24 | "nl": "Beheerder-UI is aangepast voor kleine displays.\nAfhankelijkheden zijn bijgewerkt.", 25 | "fr": "Admin-UI a été adapté pour les petits affichages.\nLes dépendances ont été actualisées.", 26 | "it": "Admin-UI è stato adattato per piccoli display.\nLe dipendenze sono state aggiornate.", 27 | "es": "Admin-UI ha sido adaptado para pequeñas pantallas.\nSe han actualizado las dependencias.", 28 | "pl": "Admin- UI został dostosowany do małych wyświetlaczy.\nZaktualizowano zależność.", 29 | "uk": "Адмін-УІ адаптований для малих дисплеїв.\nОновлено залежність.", 30 | "zh-cn": "Admin-UI已经适应了小型显示功能.\n附属关系已经更新." 31 | }, 32 | "3.0.0": { 33 | "en": "Adapter has been moved to iobroker-community-adapter organisation.\nAdapter requires js-controller 5, admin 6 and node.js 20 now.\nDependencies have been updated.", 34 | "de": "Adapter wurde in die iobroker-community-Adapter-Organisation verschoben.\nAdapter benötigt jetzt js-controller 5, admin 6 und node.js 20.\nAbhängigkeiten wurden aktualisiert.", 35 | "ru": "Адаптер был перемещен в организацию iobroker-community-adapter.\nAdapter requires js-controller 5, admin 6 and node.js 20 now.\nЗависимость была обновлена.", 36 | "pt": "Adapter foi transferido para a organização iobroker-community-adapter.\nAdaptador requer js-controller 5, admin 6 e node.js 20 agora.\nAs dependências foram atualizadas.", 37 | "nl": "Adapter is verplaatst naar iobroker-community-adapter organisatie.\nAdapter vereist js-controller 5, admin 6 en node.js 20 nu.\nAfhankelijkheden zijn bijgewerkt.", 38 | "fr": "L'adaptateur a été transféré à l'organisation iobroker-community-adaptateur.\nAdaptateur nécessite js-controller 5, admin 6 et node.js 20 maintenant.\nLes dépendances ont été actualisées.", 39 | "it": "L'adattatore è stato spostato nell'organizzazione di iobroker-community-adapter.\nAdattatore richiede js-controller 5, admin 6 e node.js 20 ora.\nLe dipendenze sono state aggiornate.", 40 | "es": "El adaptador ha sido trasladado a la organización yobroker-community-adapter.\nAdaptador requiere js-controller 5, admin 6 y node.js 20 ahora.\nSe han actualizado las dependencias.", 41 | "pl": "Adapter został przeniesiony do organizacji iobroker- community- adapter.\nAdapter wymaga sterownika js- controller 5, admin 6 i node.js 20.\nZaktualizowano zależność.", 42 | "uk": "Переміщено перехід на iobroker-community-adapter.\nАдаптер вимагає js-controller 5, admin 6 і node.js 20 тепер.\nОновлено залежність.", 43 | "zh-cn": "适应者已经转移到了职业经纪人-社区适应者组织.\n适配器现在需要js控制器5,admin 6和节点20.js.\n附属关系已经更新." 44 | }, 45 | "2.1.0": { 46 | "en": "update dependencies\nsupport small screens\nupdate translations\nupdate object names\nadd variable polling intervall [1-59s]", 47 | "de": "aktualisierung der abhängigkeiten\nkleine bildschirme unterstützen\nübersetzungen aktualisieren\nänderung der objektnamen\nvariable abfrageintervall hinzufügen [1-59s]", 48 | "ru": "обновление зависимостей\nподдержка небольших экранов\nобновление переводов\nобновления имен объектов\nдобавить переменный интервал опроса [1-59]", 49 | "pt": "dependências\nsuporte telas pequenas\ntraduções de atualização\natualizar nomes de objetos\nadicionar intervalo de votação variável [1-59s]", 50 | "nl": "afhankelijkheden bijwerken\nkleine schermen ondersteunen\nvertalingen bijwerken\nobjectnamen bijwerken\nvariabele polling intervall [1-59s] toevoegen", 51 | "fr": "mettre à jour les dépendances\nsoutenir les petits écrans\nmettre à jour les traductions\nmettre à jour les noms des objets\najouter intervalle de scrutin variablel [1-59s]", 52 | "it": "aggiornamento dipendenze\nsupporto piccoli schermi\naggiornare le traduzioni\naggiornare i nomi degli oggetti\naggiungere intervallo di polling variabile [1-59s]", 53 | "es": "dependencias de actualización\nsoporte pantallas pequeñas\ntraducción actualizada\nactualizar nombres de objetos\nañadir intervalo de votación variable [1-59s]", 54 | "pl": "aktualizacji zależności\nwsparcie dla małych ekranów\naktualizacja tłumaczeń\naktualizacja nazw obiektów\ndodać zmienną polling intervall [1-59s]", 55 | "uk": "оновлення залежності\nпідтримка невеликих екранів\nоновлення перекладів\nоновлення назв об'єктів\nдодати змінний інтервал опитування [1-59s]", 56 | "zh-cn": "更新依赖关系\n支持小屏幕\n更新翻译\n更新对象名称\n添加可变投票间隔l [1-59s]" 57 | }, 58 | "2.0.0": { 59 | "en": "changes for new websocket structure ([#129](https://github.com/iobroker-community-adapters/ioBroker.opendtu/issues/129))\n`Efficiency`, `YieldTotal`, `YieldDay` and `DC Power` moved from the AC section to the INV (old data points must be removed manually)", 60 | "de": "änderungen für neue Websocket-Struktur ([#129](https://github.com/iobroker-community-adapters/ioBroker.opendtu/issues/129))\n`Efficiency`, `YieldTotal`, `YieldDay` und `DC Power` von der AC-Sektion zum INV bewegt (alte Datenpunkte müssen manuell entfernt werden)", 61 | "ru": "изменения для новой структуры веб-сокета ([#129] (https://github.com/iobroker-community-adapters/ioBroker.opendtu/issues/129)\n`Efficiency \" , `YieldTotal \" , `YieldDay \" и `DC Power \" перенесены из раздела AC в ИНВ (старые точки данных должны быть удалены вручную)", 62 | "pt": "alterações para nova estrutura websocket ([#129](https://github.com/iobroker-community-adapters/ioBroker.opendtu/issues/129))\n`Eficiência`, `YieldTotal`, `YieldDay` e `DC Power` movido da seção AC para a INV (velhos pontos de dados devem ser removidos manualmente)", 63 | "nl": "wijzigingen voor nieuwe websocketstructuur ([#129](https://github.com/iobroker-community-adapters/ioBroker.opendtu/issues/129))\n`Efficiency`, `YieldTotal`, `YieldDay` and `DC Power` moved from the AC section to the INV (old data points must be removed manually)", 64 | "fr": "changements pour la nouvelle structure de la poche web ([#129](https://github.com/iobroker-community-adapters/ioBroker.opendtu/issues/129))\n`Efficacité`, `YieldTotal`, `YieldDay` et `DC Power` déplacés de la section AC à l'INV (les anciens points de données doivent être supprimés manuellement)", 65 | "it": "modifiche per la nuova struttura websocket ([#129](https://github.com/iobroker-community-adapters/ioBroker.opendtu/issues/129))\n`Efficienza`, `YieldTotal`, `YieldDay` e `DC Power` spostati dalla sezione AC al INV (i vecchi punti di dati devono essere rimossi manualmente)", 66 | "es": "(https://github.com/iobroker-community-adapters/ioBroker.opendtu/issues/129))\n\" Eficiencia \" , `YieldTotal ' , `YieldDay ' y `DC Power ' se trasladaron de la sección AC al INV (los antiguos puntos de datos deben eliminarse manualmente)", 67 | "pl": "zmiany w strukturze nowego gniazda sieciowego ([# 129] (https: / / github.com / iobroker-community-adapters / ioBroker.opendtu / issues / 129)\n\"Efektywność\", \"YieldTotal\", \"YieldDay\" i \"DC Power\" przeniesione z sekcji AC do INV (stare punkty danych muszą być usunięte ręcznie)", 68 | "uk": "[#129](https://github.com/iobroker-community-adapters/ioBroker.opendtu/languages/129))\n`Efficiency`, `YieldTotal`, `YieldDay` і `DC Power` перемістили з розділу змінного струму до INV (старі дані повинні бути видалені вручну)", 69 | "zh-cn": "新的websocket结构的变化([#129](https://github.com/iobroker-community-adapters/ioBroker.opendtu/issues/129))\n\" 效率 \" 、 \" YieldTotal \" 、 \" YieldDay \" 和 \" DC Power \" 从AC区移至INV区(旧数据点必须手工删除)" 70 | }, 71 | "1.0.1": { 72 | "en": "fixed `power_control.current_limit_absolute\" has value \"-1\" less than min \"0\"`", 73 | "de": "`power_control.current_limit_absolute\" hat wert -1 kleiner als min 0", 74 | "ru": "фиксированный `power_control.current_limit_absolute\" имеет значение -1 меньше, чем мин 0`", 75 | "pt": "fixo `power_control.current_limit_absolute\" tem valor -1 menos do que min 0", 76 | "nl": "_ _", 77 | "fr": "fixed `power_control.current_limit_absolute\" has value -1 less than min 0", 78 | "it": "fisso `power_control.current_limit_absolute' ha valore -1 meno di min #", 79 | "es": "fijo `power_control.current_limit_absolute' tiene valor -1 menos que min 0", 80 | "pl": "stacjonarna kontrola (ang. fixed_absolute) ma wartość -1 mniej niż min. 0)", 81 | "uk": "фіксована `power_control.current_limit_absolute\" має значення -1 менше, ніж хв 0 товар(ов)", 82 | "zh-cn": "固定的`权力':控制. 平行_限性_absolute”数值-1低于分钟 0%" 83 | }, 84 | "1.0.0": { 85 | "en": "Increase to the first major release, as it has now reached a stable level. \nadded yieldtotal Protection against incorrect zeroing when the OpenDTU restarts if the inverter is not accessible\nadded option `Set the states to 0 if the inverter is not accessible.` ([#97](https://github.com/iobroker-community-adapters/ioBroker.opendtu/issues/97))", 86 | "de": "Erhöhen Sie die erste große Freigabe, da sie jetzt ein stabiles Niveau erreicht hat.\nzusatzrendite Schutz vor falschem Nullen beim Neustart der OpenDTU, wenn der Wechselrichter nicht zugänglich ist\nzusatzoption Setzen Sie die Zustände auf 0, wenn der Wechselrichter nicht erreichbar ist.` #[97](https://github.com/iobroker-community-adapters/ioBroker.opendtu/issues/97)", 87 | "ru": "Повысьте первый крупный выпуск, так как он теперь достиг стабильного уровня.\nдобавлена доходность Защита от неправильного нуля при возобновлении OpenDTU, если инвертор недоступен\nдобавлена опция `Смотри состояния на 0, если инвертор недоступен.` #[97](https://github.com/iobroker-community-adapters/ioBroker.opendtu/issues/97)", 88 | "pt": "Aumento ao primeiro grande lançamento, já que agora atingiu um nível estável.\nadicionados de rendimento Proteção contra zeros incorretos quando o OpenDTU reinicia se o inversor não é acessível\nopção adicionada `Configurar os estados para 0 se o inversor não estiver acessível.`#[97](https://github.com/iobroker-community-adapters/ioBroker.opendtu/issues/97)", 89 | "nl": "Verhoog de eerste grote vrijlating, zoals het nu een stabiel niveau bereikt heeft.\nvertaling: Bescherming tegen onjuiste nulling als de OpenDTU herstart als de inverter niet toegankelijk is\noptie toegevoegd Zet de staten tot 0 als de inverter niet toegankelijk is", 90 | "fr": "Accroître à la première version majeure, car elle a maintenant atteint un niveau stable.\nrendement ajouté Protection contre le zéro incorrect lorsque l'OpenDTU redémarre si l'onduleur n'est pas accessible\noption ajoutée `Set the states to 0 if the inverter is not accessible.` #[97](https://github.com/iobroker-community-adapters/ioBroker.opendtu/issues/97)", 91 | "it": "Aumento del primo rilascio importante, come ora ha raggiunto un livello stabile.\nrendimento aggiunto Protezione contro lo zero non corretto quando l'OpenDTU riavvia se l'inverter non è accessibile\nopzione aggiunta «Settare gli stati a 0 se l'inverter non è accessibile»", 92 | "es": "Incremento a la primera liberación importante, ya que ahora ha alcanzado un nivel estable.\nrendimiento añadido Protección contra cero incorrecto cuando el OpenDTU reinicia si el inversor no es accesible\nopción `Contar los estados a 0 si el inversor no es accesible.` #[97](https://github.com/iobroker-community-adapters/ioBroker.opendtu/issues/97)", 93 | "pl": "Wstęp do pierwszego większego wydania, jak się teraz osiągnął stabilny poziom.\ndodać Ochrona przed nieprawidłowym zerwaniem, gdy restartuje OpenDTU, jeśli inwerter nie jest dostępny\ndodać `Set the State to 0 if the inverter is not available.' #97(https:/github.com/iobroker-community-adapters/ioBroker.opendtu/issues/97) (ang.)", 94 | "uk": "Підвищити перший великий реліз, оскільки він досяг стабільного рівня.\nдоданий врожай Захист від неправильного нулювання при перезавантаженні OpenDTU, якщо інвертор не доступний\nдодано параметр `Set стани до 0, якщо інвертор не доступний.` #[97](https://github.com/iobroker-community-adapters/ioBroker.opendtu/issues/97)", 95 | "zh-cn": "增加第一批主要释放,因为现在已达到稳定水平。.\n增产 如果不易懂的话,保护不纠正零碎的现象。\n备选案文 “国家如果不能获得多样性,即为0.。”" 96 | } 97 | }, 98 | "titleLang": { 99 | "en": "OpenDTU", 100 | "de": "OpenDTU", 101 | "ru": "OpenDTU", 102 | "pt": "OpenDTU", 103 | "nl": "OpenDTU", 104 | "fr": "OpenDTU", 105 | "it": "OpenDTU", 106 | "es": "OpenDTU", 107 | "pl": "OpenDTU", 108 | "uk": "OpenDTU", 109 | "zh-cn": "OpenDTU" 110 | }, 111 | "desc": { 112 | "en": "Adapter for the OpenDTU project", 113 | "de": "Adapter für das OpenDTU-Projekt", 114 | "ru": "Адаптер для проекта OpenDTU", 115 | "pt": "Adaptador para o projeto OpenDTU", 116 | "nl": "Adapter voor het OpenDTU project", 117 | "fr": "Adaptateur pour le projet OpenDTU", 118 | "it": "Adattatore per il progetto OpenDTU", 119 | "es": "Adaptador para el proyecto OpenDTU", 120 | "pl": "Adapter dla projektu OpenDTU", 121 | "uk": "Адаптер для проекту OpenDTU", 122 | "zh-cn": "OpenDTU 项目的适配器" 123 | }, 124 | "authors": [ 125 | "Dennis Rathjen ", 126 | "Iobroker Community" 127 | ], 128 | "keywords": [ 129 | "OpenDTU", 130 | "Solar" 131 | ], 132 | "licenseInformation": { 133 | "license": "MIT", 134 | "type": "free" 135 | }, 136 | "platform": "Javascript/Node.js", 137 | "icon": "opendtu.png", 138 | "enabled": true, 139 | "extIcon": "https://raw.githubusercontent.com/iobroker-community-adapters/ioBroker.opendtu/main/admin/opendtu.png", 140 | "readme": "https://github.com/iobroker-community-adapters/ioBroker.opendtu/blob/main/README.md", 141 | "loglevel": "info", 142 | "mode": "daemon", 143 | "tier": 2, 144 | "type": "energy", 145 | "compact": true, 146 | "connectionType": "local", 147 | "dataSource": "push", 148 | "adminUI": { 149 | "config": "json", 150 | "tab": "materialize" 151 | }, 152 | "adminTab": { 153 | "singleton": false, 154 | "name": { 155 | "en": "OpenDTU", 156 | "de": "OpenDTU", 157 | "ru": "OpenDTU", 158 | "pt": "OpenDTU", 159 | "nl": "OpenDTU", 160 | "fr": "OpenDTU", 161 | "it": "OpenDTU", 162 | "es": "OpenDTU", 163 | "pl": "OpenDTU", 164 | "uk": "OpenDTU", 165 | "zh-cn": "OpenDTU" 166 | }, 167 | "link": "%webUIScheme%://%webUIServer%:%webUIPort%", 168 | "fa-icon": "info" 169 | }, 170 | "dependencies": [ 171 | { 172 | "js-controller": ">=5.0.19" 173 | } 174 | ], 175 | "globalDependencies": [ 176 | { 177 | "admin": ">=6.17.14" 178 | } 179 | ] 180 | }, 181 | "native": { 182 | "webUIScheme": "http", 183 | "webUIServer": "", 184 | "webUIPort": 80, 185 | "userName": "admin", 186 | "password": "", 187 | "protectNames": false, 188 | "useInvOfflineStatesToZero": true 189 | }, 190 | "protectedNative": [ 191 | "password" 192 | ], 193 | "encryptedNative": [ 194 | "password" 195 | ], 196 | "objects": [], 197 | "instanceObjects": [] 198 | } 199 | -------------------------------------------------------------------------------- /lib/adapter-config.d.ts: -------------------------------------------------------------------------------- 1 | // This file extends the AdapterConfig type from "@types/iobroker" 2 | // using the actual properties present in io-package.json 3 | // in order to provide typings for adapter.config properties 4 | 5 | import { native } from '../io-package.json'; 6 | 7 | type _AdapterConfig = typeof native; 8 | 9 | // Augment the globally declared type ioBroker.AdapterConfig 10 | declare global { 11 | namespace ioBroker { 12 | interface AdapterConfig extends _AdapterConfig { 13 | // Do not enter anything here! 14 | } 15 | } 16 | } 17 | 18 | // this is required so the above AdapterConfig is found by TypeScript / type checking 19 | export { }; -------------------------------------------------------------------------------- /lib/dataController.js: -------------------------------------------------------------------------------- 1 | const stateDefinition = require('./stateDefinition').stateDefinition; 2 | const inverterOffline = []; 3 | const createCache = []; 4 | const statesToSetZero = [ 5 | 'current', 6 | 'irradiation', 7 | 'power', 8 | 'voltage', 9 | 'frequency', 10 | 'power_dc', 11 | 'reactivepower', 12 | 'temperature', 13 | ]; 14 | 15 | class DataController { 16 | constructor(adapter) { 17 | this.adapter = adapter; 18 | } 19 | 20 | async processInverterData(inverters) { 21 | for (const inverter of inverters) { 22 | if (!createCache.includes(inverter.serial)) { 23 | const deviceObj = { 24 | type: 'device', 25 | common: { 26 | name: inverter.name, 27 | desc: 'Inverter', 28 | statusStates: { 29 | onlineId: `${this.adapter.name}.${this.adapter.instance}.${inverter.serial}.available`, 30 | }, 31 | }, 32 | native: {}, 33 | }; 34 | await this.adapter.extendObjectAsync(inverter.serial, deviceObj); 35 | createCache.push(inverter.serial); 36 | } 37 | 38 | // Power control 39 | await this.createPowerControls(inverter.serial); 40 | 41 | // States 42 | for (const [stateName, val] of Object.entries(inverter)) { 43 | switch (stateName) { 44 | case 'AC': 45 | await this.processACData(inverter.serial, val); 46 | break; 47 | case 'DC': 48 | await this.processDCData(inverter.serial, val); 49 | break; 50 | case 'INV': 51 | // Create and/or set values 52 | for (const [invStateName, invVal] of Object.entries(val['0'])) { 53 | this.setObjectAndState(inverter.serial, `inv_${invStateName.toLowerCase()}`, invVal); 54 | } 55 | break; 56 | default: 57 | // Must states be set to zero? 58 | if (stateName.toLowerCase() == 'reachable') { 59 | if (val == true) { 60 | const serialIdx = inverterOffline.indexOf(inverter.serial); 61 | inverterOffline.splice(serialIdx, 1); 62 | } else { 63 | if (this.adapter.config.useInvOfflineStatesToZero == true) { 64 | this.invOfflineStatesToZero(inverter.serial); 65 | } 66 | } 67 | } 68 | // Create and/or set values 69 | this.setObjectAndState(inverter.serial, stateName.toLowerCase(), val); 70 | } 71 | } 72 | } 73 | } 74 | 75 | async processDCData(serial, val) { 76 | // Create channel 77 | if (!createCache.includes(`${serial}.dc`)) { 78 | const deviceObj = { 79 | type: 'channel', 80 | common: { 81 | name: 'DC', 82 | }, 83 | native: {}, 84 | }; 85 | 86 | await this.adapter.extendObjectAsync(`${serial}.dc`, deviceObj); 87 | createCache.push(`${serial}.dc`); 88 | } 89 | 90 | // DC Input x 91 | for (const [string, stringObj] of Object.entries(val)) { 92 | // Create channel 93 | const stringNumber = Number(string) + 1; 94 | if (!createCache.includes(`${serial}.dc.input_${stringNumber}`)) { 95 | const deviceObj = { 96 | type: 'channel', 97 | common: { 98 | name: `DC Input ${stringNumber}`, 99 | }, 100 | native: {}, 101 | }; 102 | 103 | await this.adapter.extendObjectAsync(`${serial}.dc.input_${stringNumber}`, deviceObj); 104 | createCache.push(`${serial}.dc.input_${stringNumber}`); 105 | } 106 | 107 | // Create and/or set values 108 | for (const [channelStateName, channelVal] of Object.entries(stringObj)) { 109 | this.setObjectAndState(serial, `dc_${channelStateName.toLowerCase()}`, channelVal, stringNumber); 110 | } 111 | } 112 | } 113 | 114 | async processACData(serial, val) { 115 | // Create channel 116 | if (!createCache.includes(`${serial}.ac`)) { 117 | const deviceObj = { 118 | type: 'channel', 119 | common: { 120 | name: 'AC', 121 | }, 122 | native: {}, 123 | }; 124 | 125 | await this.adapter.extendObjectAsync(`${serial}.ac`, deviceObj); 126 | createCache.push(`${serial}.ac`); 127 | } 128 | 129 | const channelObj = { 130 | type: 'channel', 131 | native: {}, 132 | }; 133 | 134 | // AC Phase x 135 | for (const [phase, phaseObj] of Object.entries(val)) { 136 | // Create channel 137 | const phaseNumber = Number(phase) + 1; 138 | if (!createCache.includes(`${serial}.ac.phase_${phaseNumber}`)) { 139 | channelObj.common = { 140 | name: `Phase ${phaseNumber}`, 141 | }; 142 | 143 | await this.adapter.extendObjectAsync(`${serial}.ac.phase_${phaseNumber}`, channelObj); 144 | createCache.push(`${serial}.ac.phase_${phaseNumber}`); 145 | } 146 | 147 | // Create and/or set values 148 | for (const [phaseStateName, phaseVal] of Object.entries(phaseObj)) { 149 | this.setObjectAndState(serial, `ac_${phaseStateName.toLowerCase()}`, phaseVal, phaseNumber); 150 | } 151 | } 152 | } 153 | 154 | async createPowerControls(serial) { 155 | const forceStatesNameList = [ 156 | 'limit_persistent_relative', 157 | 'limit_persistent_absolute', 158 | 'limit_nonpersistent_relative', 159 | 'limit_nonpersistent_absolute', 160 | 'power_on', 161 | 'power_off', 162 | 'restart', 163 | ]; 164 | // Create channel 165 | const powerControlChannelId = `${serial}.power_control`; 166 | if (!createCache.includes(powerControlChannelId)) { 167 | const deviceObj = { 168 | type: 'channel', 169 | common: { 170 | name: 'Power control', 171 | }, 172 | native: {}, 173 | }; 174 | await this.adapter.extendObjectAsync(powerControlChannelId, deviceObj); 175 | createCache.push(powerControlChannelId); 176 | } 177 | 178 | // Create values 179 | for (const stateName of forceStatesNameList) { 180 | this.setObjectAndState(serial, stateName.toLowerCase()); 181 | } 182 | } 183 | 184 | async processTotalData(data) { 185 | // Create channel 186 | if (!createCache.includes('total')) { 187 | const deviceObj = { 188 | type: 'channel', 189 | common: { 190 | name: 'Total', 191 | desc: 'Sum over all inverters', 192 | }, 193 | native: {}, 194 | }; 195 | 196 | await this.adapter.extendObjectAsync('total', deviceObj); 197 | createCache.push('total'); 198 | } 199 | 200 | // Create and/or set values 201 | for (const [stateName, val] of Object.entries(data)) { 202 | this.setObjectAndState('total', `total_${stateName.toLowerCase()}`, val); 203 | } 204 | } 205 | 206 | async processDTUData(data) { 207 | // Create device 208 | if (!createCache.includes('dtu')) { 209 | const deviceObj = { 210 | type: 'device', 211 | common: { 212 | name: 'OpenDTU Device', 213 | statusStates: { 214 | onlineId: `${this.adapter.name}.${this.adapter.instance}.dtu.available`, 215 | }, 216 | }, 217 | native: {}, 218 | }; 219 | 220 | await this.adapter.extendObjectAsync('dtu', deviceObj); 221 | createCache.push('dtu'); 222 | } 223 | 224 | // Create and/or set values 225 | for (const [stateName, val] of Object.entries(data)) { 226 | this.setObjectAndState('dtu', `dtu_${stateName.toLowerCase()}`, val); 227 | } 228 | } 229 | 230 | async setObjectAndState(stateID, stateName, val, count) { 231 | const state = stateDefinition[stateName]; 232 | if (!state) { 233 | return; 234 | } 235 | 236 | const fullStateID = `${stateID}.${state.id}`.replace('%count%', count); 237 | 238 | let options = undefined; 239 | 240 | if (this.adapter.config.protectNames) { 241 | options = { 242 | preserve: { 243 | common: ['name'], 244 | }, 245 | }; 246 | } 247 | 248 | if (!createCache.includes(fullStateID)) { 249 | await this.adapter.extendObjectAsync( 250 | fullStateID, 251 | { 252 | type: 'state', 253 | common: this.copyAndCleanStateObj(state), 254 | native: {}, 255 | }, 256 | options, 257 | ); 258 | 259 | // Subscribe to writable states 260 | if (state.write == true) { 261 | await this.adapter.subscribeStatesAsync(fullStateID); 262 | } 263 | 264 | createCache.push(fullStateID); 265 | } 266 | 267 | if (val !== undefined) { 268 | let value = val; 269 | 270 | if (state.getter) { 271 | value = state.getter(val); 272 | } 273 | 274 | // yieldtotal Protection against incorrect zeroing when the OpenDTU restarts if the inverter is not accessible. 275 | if (fullStateID.endsWith('yieldtotal')) { 276 | if (Number(value) <= 0) { 277 | return; 278 | } 279 | } 280 | 281 | // Are the states allowed to be set or is the inverter offline? 282 | for (const serial of inverterOffline) { 283 | if (fullStateID.includes(serial)) { 284 | if (statesToSetZero.includes(fullStateID.split('.').at(-1))) { 285 | return; 286 | } 287 | } 288 | } 289 | 290 | await this.adapter.setStateChangedAsync(fullStateID, value, true); 291 | } 292 | } 293 | async invOfflineStatesToZero(serial) { 294 | if (inverterOffline.includes(serial)) { 295 | return; 296 | } 297 | 298 | // Get all StateIDs 299 | const allStateIDs = Object.keys(await this.adapter.getAdapterObjectsAsync()); 300 | 301 | // Get all yieldday StateIDs to set zero 302 | const idsSetToZero = []; 303 | for (const id of allStateIDs.filter(x => x.includes(serial))) { 304 | if (statesToSetZero.includes(id.split('.').at(-1))) { 305 | idsSetToZero.push(id); 306 | } 307 | } 308 | 309 | // Set IDs to Zero 310 | for (const id of idsSetToZero) { 311 | this.adapter.setStateChangedAsync(id, 0, true); 312 | } 313 | 314 | inverterOffline.push(serial); 315 | } 316 | 317 | copyAndCleanStateObj(state) { 318 | const iobState = { ...state }; 319 | const blacklistedKeys = ['id', 'setter', 'getter', 'setattr']; 320 | for (const blacklistedKey of blacklistedKeys) { 321 | delete iobState[blacklistedKey]; 322 | } 323 | return iobState; 324 | } 325 | } 326 | 327 | module.exports = { 328 | DataController, 329 | }; 330 | -------------------------------------------------------------------------------- /lib/stateDefinition.js: -------------------------------------------------------------------------------- 1 | const pad2 = require('./tools').pad2; 2 | const stateDefinition = { 3 | reachable: { 4 | id: 'available', 5 | name: 'Available', 6 | role: 'indicator.connected', 7 | write: false, 8 | read: true, 9 | type: 'boolean', 10 | def: false, 11 | }, 12 | name: { 13 | id: 'name', 14 | name: 'Name as configured (WebGUI)', 15 | role: 'info.name', 16 | write: false, 17 | read: true, 18 | type: 'string', 19 | def: '', 20 | }, 21 | serial: { 22 | id: 'serial', 23 | name: 'Serial number', 24 | role: 'info.serial', 25 | write: false, 26 | read: true, 27 | type: 'string', 28 | def: '', 29 | }, 30 | bootloaderversion: { 31 | id: 'bootloaderversion', 32 | name: 'Bootloader version', 33 | role: 'state', 34 | write: false, 35 | read: true, 36 | type: 'string', 37 | def: '', 38 | }, 39 | fwbuildversion: { 40 | id: 'fwbuildversion', 41 | name: 'Firmware version', 42 | role: 'state', 43 | write: false, 44 | read: true, 45 | type: 'string', 46 | def: '', 47 | }, 48 | fwbuilddatetime: { 49 | id: 'fwbuilddatetime', 50 | name: 'Build date / time', 51 | role: 'state', 52 | write: false, 53 | read: true, 54 | type: 'string', 55 | def: '', 56 | }, 57 | hwpartnumber: { 58 | id: 'hwpartnumber', 59 | name: 'Hardware part number', 60 | role: 'state', 61 | write: false, 62 | read: true, 63 | type: 'number', 64 | def: 0, 65 | getter: x => Number(x), 66 | }, 67 | hwversion: { 68 | id: 'hwversion', 69 | name: 'Hardware version', 70 | role: 'state', 71 | write: false, 72 | read: true, 73 | type: 'string', 74 | def: '', 75 | }, 76 | producing: { 77 | id: 'producing', 78 | name: 'Indicates whether is producing AC power', 79 | role: 'state', 80 | write: false, 81 | read: true, 82 | type: 'boolean', 83 | def: false, 84 | }, 85 | data_age: { 86 | id: 'last_update', 87 | name: 'Unix timestamp of last statistics udpate', 88 | role: 'state', 89 | write: false, 90 | read: true, 91 | type: 'string', 92 | def: '', 93 | getter: x => { 94 | const date = new Date(Number(new Date()) - x * 1000); 95 | date.setMinutes(date.getMinutes() - date.getTimezoneOffset()); 96 | return date.toISOString().replace('T', ' ').split('.')[0]; 97 | }, 98 | }, 99 | // PowerControl 100 | limit_relative: { 101 | id: 'power_control.current_limit_relative', 102 | name: 'Current applied production limit', 103 | role: 'state', 104 | write: false, 105 | read: true, 106 | type: 'number', 107 | unit: '%', 108 | def: 0, 109 | getter: x => Number(x), 110 | }, 111 | limit_absolute: { 112 | id: 'power_control.current_limit_absolute', 113 | name: 'Current applied production limit', 114 | role: 'state', 115 | write: false, 116 | read: true, 117 | type: 'number', 118 | unit: 'W', 119 | min: -1, 120 | def: -1, 121 | getter: x => Number(x), 122 | }, 123 | limit_persistent_relative: { 124 | id: 'power_control.limit_persistent_relative', 125 | name: 'Set limit as a percentage of total production capability', 126 | role: 'state', 127 | write: true, 128 | read: true, 129 | type: 'number', 130 | unit: '%', 131 | min: 0, 132 | max: 100, 133 | def: 0, 134 | getter: x => Number(x), 135 | }, 136 | limit_persistent_absolute: { 137 | id: 'power_control.limit_persistent_absolute', 138 | name: 'Set limit as a absolute value', 139 | role: 'state', 140 | write: true, 141 | read: true, 142 | type: 'number', 143 | unit: 'W', 144 | min: 0, 145 | def: 0, 146 | getter: x => Number(x), 147 | }, 148 | limit_nonpersistent_relative: { 149 | id: 'power_control.limit_nonpersistent_relative', 150 | name: 'Set limit as a percentage of total production capability', 151 | role: 'state', 152 | write: true, 153 | read: true, 154 | type: 'number', 155 | unit: '%', 156 | min: 0, 157 | max: 100, 158 | def: 0, 159 | getter: x => Number(x), 160 | }, 161 | limit_nonpersistent_absolute: { 162 | id: 'power_control.limit_nonpersistent_absolute', 163 | name: 'Set limit as a absolute value', 164 | role: 'state', 165 | write: true, 166 | read: true, 167 | type: 'number', 168 | unit: 'W', 169 | min: 0, 170 | def: 0, 171 | getter: x => Number(x), 172 | }, 173 | // 'power_switch': { 174 | // id: 'power_control.power_switch', 175 | // name: 'Turn the inverter on or off', 176 | // role: 'state', 177 | // write: true, 178 | // read: true, 179 | // type: 'boolean', 180 | // def: true, 181 | // getter: x => x == 1, 182 | // setter: x => x ? '1' : '0', 183 | // }, 184 | power_on: { 185 | id: 'power_control.power_on', 186 | name: 'Start', 187 | role: 'button', 188 | write: true, 189 | read: true, 190 | type: 'boolean', 191 | def: true, 192 | }, 193 | power_off: { 194 | id: 'power_control.power_off', 195 | name: 'Shutdown', 196 | role: 'button', 197 | write: true, 198 | read: true, 199 | type: 'boolean', 200 | def: true, 201 | }, 202 | restart: { 203 | id: 'power_control.restart', 204 | name: 'Restart (also resets YieldDay)', 205 | role: 'button', 206 | write: true, 207 | read: true, 208 | type: 'boolean', 209 | def: true, 210 | setter: x => (x ? '1' : '0'), 211 | }, 212 | // AC 213 | ac_temperature: { 214 | id: 'temperature', 215 | name: 'Device temperature', 216 | role: 'value.temperature', 217 | write: false, 218 | read: true, 219 | type: 'number', 220 | unit: '°C', 221 | def: 0, 222 | getter: x => Number(x.v.toFixed(x.d)), 223 | }, 224 | ac_power: { 225 | id: 'ac.phase_%count%.power', 226 | name: 'AC active power', 227 | role: 'value.power', 228 | write: false, 229 | read: true, 230 | type: 'number', 231 | unit: 'W', 232 | def: 0, 233 | getter: x => Number(x.v.toFixed(x.d)), 234 | }, 235 | ac_voltage: { 236 | id: 'ac.phase_%count%.voltage', 237 | name: 'AC voltage', 238 | role: 'value.voltage', 239 | write: false, 240 | read: true, 241 | type: 'number', 242 | unit: 'V', 243 | def: 0, 244 | getter: x => Number(x.v.toFixed(x.d)), 245 | }, 246 | ac_current: { 247 | id: 'ac.phase_%count%.current', 248 | name: 'AC current', 249 | role: 'value.current', 250 | write: false, 251 | read: true, 252 | type: 'number', 253 | unit: 'A', 254 | def: 0, 255 | getter: x => Number(x.v.toFixed(x.d)), 256 | }, 257 | 'ac_power dc': { 258 | id: 'ac.phase_%count%.power_dc', 259 | name: 'DC power', 260 | role: 'value.power', 261 | write: false, 262 | read: true, 263 | type: 'number', 264 | unit: 'W', 265 | def: 0, 266 | getter: x => Number(x.v.toFixed(x.d)), 267 | }, 268 | // 'ac_yieldday': { 269 | // id: 'ac.phase_%count%.yieldday', 270 | // name: 'Energy converted to AC per day in watt hours', 271 | // role: 'value.power.production', 272 | // write: false, 273 | // read: true, 274 | // type: 'number', 275 | // unit: 'Wh', 276 | // def: 0, 277 | // getter: x => Number(x.v.toFixed(x.d)), 278 | // }, 279 | // 'ac_yieldtotal': { 280 | // id: 'ac.phase_%count%.yieldtotal', 281 | // name: 'Energy converted to AC since reset watt hours', 282 | // role: 'value.power.production', 283 | // write: false, 284 | // read: true, 285 | // type: 'number', 286 | // unit: 'kWh', 287 | // def: 0, 288 | // getter: x => Number(x.v.toFixed(x.d)), 289 | // }, 290 | // 'ac_efficiency': { 291 | // id: 'ac.phase_%count%.efficiency', 292 | // name: 'Ratio AC Power over DC Power in percent', 293 | // role: 'state', 294 | // write: false, 295 | // read: true, 296 | // type: 'number', 297 | // unit: '', 298 | // def: 0, 299 | // getter: x => Number(x.v.toFixed(x.d)), 300 | // }, 301 | ac_frequency: { 302 | id: 'ac.phase_%count%.frequency', 303 | name: 'AC frequency', 304 | role: 'state', 305 | write: false, 306 | read: true, 307 | type: 'number', 308 | unit: 'Hz', 309 | def: 0, 310 | getter: x => Number(x.v.toFixed(x.d)), 311 | }, 312 | ac_powerfactor: { 313 | id: 'ac.phase_%count%.powerfactor', 314 | name: 'Power factor', 315 | role: 'state', 316 | write: false, 317 | read: true, 318 | type: 'number', 319 | unit: '', 320 | def: 0, 321 | getter: x => Number(x.v.toFixed(x.d)), 322 | }, 323 | ac_reactivepower: { 324 | id: 'ac.phase_%count%.reactivepower', 325 | name: 'AC reactive power', 326 | role: 'value.power.reactive', 327 | write: false, 328 | read: true, 329 | type: 'number', 330 | unit: 'var', 331 | def: 0, 332 | getter: x => Number(x.v.toFixed(x.d)), 333 | }, 334 | // DC 335 | dc_name: { 336 | id: 'dc.input_%count%.name', 337 | name: 'Name of the input (WebGUI)', 338 | role: 'info.name', 339 | write: false, 340 | read: true, 341 | type: 'string', 342 | def: '', 343 | getter: x => x.u, 344 | }, 345 | dc_power: { 346 | id: 'dc.input_%count%.power', 347 | name: 'DC power', 348 | role: 'value.power', 349 | write: false, 350 | read: true, 351 | type: 'number', 352 | unit: 'W', 353 | def: 0, 354 | getter: x => Number(x.v.toFixed(x.d)), 355 | }, 356 | dc_voltage: { 357 | id: 'dc.input_%count%.voltage', 358 | name: 'DC voltage', 359 | role: 'value.voltage', 360 | write: false, 361 | read: true, 362 | type: 'number', 363 | unit: 'V', 364 | def: 0, 365 | getter: x => Number(x.v.toFixed(x.d)), 366 | }, 367 | dc_current: { 368 | id: 'dc.input_%count%.current', 369 | name: 'DC current', 370 | role: 'value.current', 371 | write: false, 372 | read: true, 373 | type: 'number', 374 | unit: 'A', 375 | def: 0, 376 | getter: x => Number(x.v.toFixed(x.d)), 377 | }, 378 | dc_yieldday: { 379 | id: 'dc.input_%count%.yieldday', 380 | name: 'Energy converted to AC per day', 381 | role: 'value.power.production', 382 | write: false, 383 | read: true, 384 | type: 'number', 385 | unit: 'Wh', 386 | def: 0, 387 | getter: x => Number(x.v.toFixed(x.d)), 388 | }, 389 | dc_yieldtotal: { 390 | id: 'dc.input_%count%.yieldtotal', 391 | name: 'Energy converted to AC since commissioning', 392 | role: 'value.power.production', 393 | write: false, 394 | read: true, 395 | type: 'number', 396 | unit: 'kWh', 397 | def: 0, 398 | getter: x => Number(x.v.toFixed(x.d)), 399 | }, 400 | dc_irradiation: { 401 | id: 'dc.input_%count%.irradiation', 402 | name: 'Ratio DC power over set maximum power (WebGUI)', 403 | role: 'state', 404 | write: false, 405 | read: true, 406 | type: 'number', 407 | unit: '%', 408 | def: 0, 409 | getter: x => Number(x.v.toFixed(x.d)), 410 | }, 411 | // INV 412 | inv_temperature: { 413 | id: 'temperature', 414 | name: 'Device temperature', 415 | role: 'value.temperature', 416 | write: false, 417 | read: true, 418 | type: 'number', 419 | unit: '°C', 420 | def: 0, 421 | getter: x => Number(x.v.toFixed(x.d)), 422 | }, 423 | inv_yieldday: { 424 | id: 'yieldday', 425 | name: 'Energy converted to AC per day', 426 | role: 'value.power.production', 427 | write: false, 428 | read: true, 429 | type: 'number', 430 | unit: 'Wh', 431 | def: 0, 432 | getter: x => Number(x.v.toFixed(x.d)), 433 | }, 434 | inv_yieldtotal: { 435 | id: 'yieldtotal', 436 | name: 'Energy converted to AC since commissioning', 437 | role: 'value.power.production', 438 | write: false, 439 | read: true, 440 | type: 'number', 441 | unit: 'kWh', 442 | def: 0, 443 | getter: x => Number(x.v.toFixed(x.d)), 444 | }, 445 | inv_efficiency: { 446 | id: 'efficiency', 447 | name: 'Ratio AC power over DC power', 448 | role: 'state', 449 | write: false, 450 | read: true, 451 | type: 'number', 452 | unit: '%', 453 | def: 0, 454 | getter: x => Number(x.v.toFixed(x.d)), 455 | }, 456 | 'inv_power dc': { 457 | id: 'power_dc', 458 | name: 'DC power', 459 | role: 'value.power', 460 | write: false, 461 | read: true, 462 | type: 'number', 463 | unit: 'W', 464 | def: 0, 465 | getter: x => Number(x.v.toFixed(x.d)), 466 | }, 467 | // Total 468 | total_power: { 469 | id: 'power', 470 | name: 'Total power over all inverters', 471 | role: 'value.power', 472 | write: false, 473 | read: true, 474 | type: 'number', 475 | unit: 'W', 476 | def: 0, 477 | getter: x => Number(x.v.toFixed(x.d)), 478 | }, 479 | total_yieldday: { 480 | id: 'yieldday', 481 | name: 'Total energy converted to AC per day', 482 | role: 'value.power.production', 483 | write: false, 484 | read: true, 485 | type: 'number', 486 | unit: 'Wh', 487 | def: 0, 488 | getter: x => Number(x.v.toFixed(x.d)), 489 | }, 490 | total_yieldtotal: { 491 | id: 'yieldtotal', 492 | name: 'Total energy converted to AC since commissioning', 493 | role: 'value.power.production', 494 | write: false, 495 | read: true, 496 | type: 'number', 497 | unit: 'kWh', 498 | def: 0, 499 | getter: x => Number(x.v.toFixed(x.d)), 500 | }, 501 | // DTU 502 | dtu_reachable: { 503 | id: 'available', 504 | name: 'Available', 505 | role: 'indicator.reachable', 506 | write: false, 507 | read: true, 508 | type: 'boolean', 509 | def: false, 510 | }, 511 | dtu_hostname: { 512 | id: 'hostname', 513 | name: 'Current hostname of the DTU (WebGUI)', 514 | role: 'info.name ', 515 | write: false, 516 | read: true, 517 | type: 'string', 518 | def: '', 519 | }, 520 | dtu_uptime: { 521 | id: 'uptime', 522 | name: 'Time since startup', 523 | role: 'state', 524 | write: false, 525 | read: true, 526 | type: 'string', 527 | def: '0', 528 | getter: x => { 529 | const seconds = pad2(Math.trunc(x % 60)); 530 | const miutes = pad2(Math.trunc((x / 60) % 60)); 531 | const houres = pad2(Math.trunc((x / 60 / 60) % 24)); 532 | const days = Math.trunc(x / 60 / 60 / 24); 533 | return `${days} Days ${houres}:${miutes}:${seconds}`; 534 | }, 535 | }, 536 | dtu_sta_rssi: { 537 | id: 'rssi', 538 | name: 'WiFi network quality', 539 | role: 'state', 540 | write: false, 541 | read: true, 542 | unit: 'db', 543 | type: 'number', 544 | def: 0, 545 | getter: x => Number(x), 546 | }, 547 | dtu_network_ip: { 548 | id: 'ip', 549 | name: 'IP address of OpenDTU', 550 | role: 'info.ip', 551 | write: false, 552 | read: true, 553 | type: 'string', 554 | def: '0.0.0.0', 555 | }, 556 | }; 557 | 558 | module.exports = { 559 | stateDefinition: stateDefinition, 560 | }; 561 | -------------------------------------------------------------------------------- /lib/tools.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param num 4 | */ 5 | function pad2(num) { 6 | const s = `0${num}`; 7 | return s.substring(s.length - 2); 8 | } 9 | 10 | module.exports = { 11 | pad2: pad2, 12 | }; 13 | -------------------------------------------------------------------------------- /lib/websocketController.js: -------------------------------------------------------------------------------- 1 | const WebSocket = require('ws'); 2 | let wsClient; 3 | const wsHeartbeatIntervall = 30000; 4 | const restartTimeout = 2000; 5 | let ping; 6 | let pingTimeout; 7 | let autoRestartTimeout; 8 | 9 | class WebsocketController { 10 | constructor(adapter) { 11 | this.adapter = adapter; 12 | } 13 | 14 | initWsClient() { 15 | try { 16 | const encodedUsername = encodeURIComponent(this.adapter.config.userName); 17 | const encodedPassword = encodeURIComponent(this.adapter.config.password); 18 | 19 | const wsURL = `${this.adapter.config.webUIScheme == 'http' ? 'ws' : 'wss'}://${encodedUsername}:${encodedPassword}@${this.adapter.config.webUIServer}:${this.adapter.config.webUIPort}/livedata`; 20 | //this.adapter.log.debug(`Websocket connect over URL: ${wsURL}`); 21 | 22 | wsClient = new WebSocket(wsURL); 23 | 24 | wsClient.on('open', () => { 25 | // Send ping to server 26 | this.sendPingToServer(); 27 | // Start Heartbeat 28 | this.wsHeartbeat(); 29 | }); 30 | 31 | wsClient.on('pong', () => { 32 | this.wsHeartbeat(); 33 | }); 34 | 35 | wsClient.on('close', () => { 36 | clearTimeout(pingTimeout); 37 | clearTimeout(ping); 38 | 39 | if (wsClient.readyState === WebSocket.CLOSED) { 40 | this.autoRestart(); 41 | } 42 | }); 43 | 44 | wsClient.on('message', () => {}); 45 | 46 | wsClient.on('error', err => { 47 | this.adapter.log.debug(`Websocket error: ${err}`); 48 | }); 49 | 50 | return wsClient; 51 | } catch (err) { 52 | this.adapter.log.error(err); 53 | } 54 | } 55 | 56 | sendPingToServer() { 57 | //this.logDebug('Send ping to server'); 58 | wsClient.ping(); 59 | ping = setTimeout(() => { 60 | this.sendPingToServer(); 61 | }, wsHeartbeatIntervall); 62 | } 63 | 64 | wsHeartbeat() { 65 | clearTimeout(pingTimeout); 66 | pingTimeout = setTimeout(() => { 67 | this.adapter.log.debug('Websocked connection timed out'); 68 | wsClient.terminate(); 69 | }, wsHeartbeatIntervall + 3000); 70 | } 71 | 72 | autoRestart() { 73 | this.adapter.log.debug(`Start try again in ${restartTimeout / 1000} seconds...`); 74 | clearTimeout(autoRestartTimeout); 75 | autoRestartTimeout = setTimeout(() => { 76 | this.adapter.startWebsocket(); 77 | }, restartTimeout); 78 | } 79 | 80 | closeConnection() { 81 | if (wsClient && wsClient.readyState !== WebSocket.CLOSED) { 82 | wsClient.close(); 83 | } 84 | } 85 | 86 | async allTimerClear() { 87 | clearTimeout(pingTimeout); 88 | clearTimeout(ping); 89 | clearTimeout(autoRestartTimeout); 90 | } 91 | } 92 | 93 | module.exports = { 94 | WebsocketController, 95 | }; 96 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const utils = require('@iobroker/adapter-core'); 3 | const { default: axios } = require('axios'); 4 | const schedule = require('node-schedule'); 5 | const WebsocketController = require('./lib/websocketController').WebsocketController; 6 | const DataController = require('./lib/dataController').DataController; 7 | 8 | let dtuApiURL; 9 | let dtuNetworkApiURL; 10 | let limitApiURL; 11 | let powerApiURL; 12 | let websocketController; 13 | let dataController; 14 | 15 | class Opendtu extends utils.Adapter { 16 | constructor(options) { 17 | super({ 18 | ...options, 19 | name: 'opendtu', 20 | }); 21 | this.on('ready', this.onReady.bind(this)); 22 | this.on('stateChange', this.onStateChange.bind(this)); 23 | this.on('unload', this.onUnload.bind(this)); 24 | } 25 | 26 | async onReady() { 27 | // Check if webUIServer is configured, log warning message and return if not 28 | if (!this.config.webUIServer) { 29 | this.log.warn('Please configure the Websoket connection!'); 30 | return; 31 | } 32 | 33 | // Construct the base URL for API calls using configuration variables 34 | const baseURL = `${this.config.webUIScheme}://${this.config.webUIServer}:${this.config.webUIPort}/api`; 35 | 36 | // Construct URLs for various API endpoints using the base URL 37 | dtuApiURL = `${baseURL}/system/status`; 38 | dtuNetworkApiURL = `${baseURL}/network/status`; 39 | limitApiURL = `${baseURL}/limit/config`; 40 | powerApiURL = `${baseURL}/power/config`; 41 | 42 | // Set default authentication credentials for Axios requests 43 | axios.defaults.auth = { username: this.config.userName, password: this.config.password }; 44 | 45 | // Instantiate a new DataController object with necessary arguments 46 | dataController = new DataController(this); 47 | 48 | // Start the websocket connection and initiate data retrieval from DTU 49 | this.startWebsocket(); 50 | this.getDTUData(); 51 | 52 | // Schedule jobs to run at specified intervals using Cron-style syntax 53 | schedule.scheduleJob('rewriteYildTotal', '0 1 0 * * *', () => this.rewriteYildTotal()); 54 | schedule.scheduleJob('getDTUData', '*/1 * * * *', () => this.getDTUData()); 55 | } 56 | 57 | // This function gets called whenever there's a state change in the system. 58 | async onStateChange(id, state) { 59 | // Check that the new state is not an acknowledgement of a previous command. 60 | if (state && state.ack == false) { 61 | // Split the ID into parts to get the serial number and state identifier. 62 | const idParts = id.split('.'); 63 | const serial = idParts[2]; 64 | const stateID = idParts[4]; 65 | 66 | // Use a switch statement to handle different types of state changes. 67 | switch (stateID) { 68 | case 'limit_persistent_relative': 69 | // Set the inverter limit based on the new value and a fixed parameter. 70 | this.setInverterLimit(serial, state.val, 257); 71 | break; 72 | case 'limit_persistent_absolute': 73 | this.setInverterLimit(serial, state.val, 256); 74 | break; 75 | case 'limit_nonpersistent_relative': 76 | this.setInverterLimit(serial, state.val, 1); 77 | break; 78 | case 'limit_nonpersistent_absolute': 79 | this.setInverterLimit(serial, state.val, 0); 80 | break; 81 | case 'power_on': 82 | // Switch the inverter power status based on the new value. 83 | if (state.val == true) { 84 | this.setInverterPower(serial, true); 85 | } 86 | break; 87 | case 'power_off': 88 | // Switch the inverter power status based on the new value. 89 | if (state.val == true) { 90 | this.setInverterPower(serial, false); 91 | } 92 | break; 93 | case 'restart': 94 | // Restart the inverter based on the new value. 95 | if (state.val == true) { 96 | this.setInverterRestart(serial, true); 97 | } 98 | break; 99 | default: 100 | // If the state change isn't recognized, do nothing. 101 | return; 102 | } 103 | 104 | // Update the state with the new values and acknowledge the change. 105 | this.setStateAsync(id, state, true); 106 | } 107 | } 108 | 109 | async onUnload(callback) { 110 | try { 111 | this.allAvailableToFalse(); 112 | } catch (e) { 113 | this.log.error(e); 114 | } 115 | // Websocket 116 | try { 117 | if (websocketController) { 118 | websocketController.closeConnection(); 119 | } 120 | } catch (e) { 121 | this.log.error(e); 122 | } 123 | // Clear all websocket timers 124 | try { 125 | if (websocketController) { 126 | await websocketController.allTimerClear(); 127 | } 128 | } catch (e) { 129 | this.log.error(e); 130 | } 131 | 132 | try { 133 | schedule.cancelJob('dayEndJob'); 134 | } catch (e) { 135 | this.log.error(e); 136 | } 137 | 138 | try { 139 | schedule.cancelJob('rewriteYildTotal'); 140 | } catch (e) { 141 | this.log.error(e); 142 | } 143 | 144 | try { 145 | schedule.cancelJob('getDTUData'); 146 | } catch (e) { 147 | this.log.error(e); 148 | } 149 | callback(); 150 | } 151 | 152 | startWebsocket() { 153 | websocketController = new WebsocketController(this); 154 | const wsClient = websocketController.initWsClient(); 155 | 156 | wsClient.on('open', () => { 157 | this.log.info('Connect to OpenDTU over websocket connection.'); 158 | }); 159 | 160 | wsClient.on('message', message => { 161 | this.processMessage(message); 162 | }); 163 | 164 | wsClient.on('close', async () => { 165 | this.allAvailableToFalse(); 166 | }); 167 | } 168 | 169 | async processMessage(message, isObject) { 170 | try { 171 | if (!isObject) { 172 | message = JSON.parse(message); 173 | } 174 | } catch (err) { 175 | this.log.error(err); 176 | this.log.debug(message); 177 | } 178 | 179 | // Create inverter rootfolder 180 | if (message.inverters) { 181 | dataController.processInverterData(message.inverters); 182 | } 183 | 184 | // Total 185 | if (message.total) { 186 | dataController.processTotalData(message.total); 187 | } 188 | 189 | // DTU 190 | if (message.dtu) { 191 | dataController.processDTUData(message.dtu); 192 | } 193 | } 194 | 195 | async getDTUData() { 196 | try { 197 | const res = await axios.all([axios.get(dtuNetworkApiURL), axios.get(dtuApiURL)]); 198 | 199 | const dtuData = res[0].data; 200 | dtuData.uptime = res[1].data.uptime; 201 | dtuData.reachable = true; 202 | 203 | this.processMessage({ dtu: dtuData }, true); 204 | } catch (err) { 205 | this.log.debug(`getDTUData axios error: ${err}`); 206 | this.processMessage({ dtu: { reachable: false } }, true); 207 | } 208 | } 209 | 210 | async setInverterLimit(serial, limit_value, limit_type) { 211 | try { 212 | const payload = `data=${JSON.stringify({ serial, limit_type, limit_value })}`; 213 | await axios.post(limitApiURL, payload); 214 | } catch (err) { 215 | this.log.warn(`setInverterLimit axios error: ${err}`); 216 | } 217 | } 218 | 219 | async setInverterPower(serial, power) { 220 | try { 221 | const payload = `data=${JSON.stringify({ serial, power })}`; 222 | await axios.post(powerApiURL, payload); 223 | } catch (err) { 224 | this.log.warn(`setInverterPower axios error: ${err}`); 225 | } 226 | } 227 | 228 | async setInverterRestart(serial, restart) { 229 | try { 230 | const payload = `data=${JSON.stringify({ serial, restart })}`; 231 | await axios.post(powerApiURL, payload); 232 | } catch (err) { 233 | this.log.warn(`setInverterRestart axios error: ${err}`); 234 | } 235 | } 236 | 237 | async rewriteYildTotal() { 238 | // Get all StateIDs 239 | const allStateIDs = Object.keys(await this.getAdapterObjectsAsync()); 240 | 241 | // Get all yieldtotal StateIDs to reset for eg. sourceanalytix 242 | const idsSetToReset = allStateIDs.filter(x => x.endsWith('yieldtotal')); 243 | for (const id of idsSetToReset) { 244 | const currentState = await this.getStateAsync(id); 245 | if (currentState) { 246 | this.setStateAsync(id, currentState.val, true); 247 | } 248 | } 249 | } 250 | 251 | async allAvailableToFalse() { 252 | // Get all available StateIDs 253 | const allAvailableStates = Object.keys(await this.getAdapterObjectsAsync()).filter(x => 254 | x.endsWith('.available'), 255 | ); 256 | 257 | // Set all available StateIDs to false 258 | for (const id of allAvailableStates) { 259 | this.setStateChangedAsync(id, false, true); 260 | } 261 | } 262 | } 263 | 264 | if (require.main !== module) { 265 | // Export the constructor in compact mode 266 | /** 267 | * @param [options] 268 | */ 269 | module.exports = options => new Opendtu(options); 270 | } else { 271 | // otherwise start the instance directly 272 | new Opendtu(); 273 | } 274 | -------------------------------------------------------------------------------- /main.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This is a dummy TypeScript test file using chai and mocha 5 | * 6 | * It's automatically excluded from npm and its build output is excluded from both git and npm. 7 | * It is advised to test all your modules with accompanying *.test.js-files 8 | */ 9 | 10 | // tslint:disable:no-unused-expression 11 | 12 | const { expect } = require('chai'); 13 | // import { functionToTest } from "./moduleToTest"; 14 | 15 | describe('module to test => function to test', () => { 16 | // initializing logic 17 | const expected = 5; 18 | 19 | it(`should return ${expected}`, () => { 20 | const result = 5; 21 | // assign result a value from functionToTest 22 | expect(result).to.equal(expected); 23 | // or using the should() syntax 24 | result.should.equal(expected); 25 | }); 26 | // ... more tests => it 27 | 28 | }); 29 | 30 | // ... more test suites => describe 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iobroker.opendtu", 3 | "version": "3.1.0", 4 | "description": "Adapter for the OpenDTU project", 5 | "author": { 6 | "name": "Dennis Rathjen", 7 | "email": "dennis.rathjen@outlook.de" 8 | }, 9 | "contributors": [ 10 | { 11 | "name": "iobroker-community-adapters", 12 | "email": "iobroker-community-adapters@gmx.de" 13 | } 14 | ], 15 | "homepage": "https://github.com/iobroker-community-adapters/ioBroker.opendtu", 16 | "license": "MIT", 17 | "keywords": [ 18 | "ioBroker", 19 | "OpenDTU", 20 | "Solar" 21 | ], 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/iobroker-community-adapters/ioBroker.opendtu.git" 25 | }, 26 | "engines": { 27 | "node": ">= 20" 28 | }, 29 | "dependencies": { 30 | "@iobroker/adapter-core": "^3.2.3", 31 | "axios": "^1.7.9", 32 | "node-schedule": "^2.1.1", 33 | "ws": "^8.18.2" 34 | }, 35 | "devDependencies": { 36 | "@alcalzone/release-script": "^3.8.0", 37 | "@alcalzone/release-script-plugin-iobroker": "^3.7.2", 38 | "@alcalzone/release-script-plugin-license": "^3.7.0", 39 | "@alcalzone/release-script-plugin-manual-review": "^3.7.0", 40 | "@iobroker/adapter-dev": "^1.4.0", 41 | "@iobroker/eslint-config": "^1.0.0", 42 | "@iobroker/testing": "^5.0.4", 43 | "@tsconfig/node14": "^14.1.3", 44 | "@types/chai": "^4.3.9", 45 | "@types/chai-as-promised": "^8.0.2", 46 | "@types/mocha": "^10.0.10", 47 | "@types/node": "^22.15.29", 48 | "@types/proxyquire": "^1.3.31", 49 | "@types/sinon": "^17.0.4", 50 | "@types/sinon-chai": "^3.2.12", 51 | "chai": "^4.5.0", 52 | "chai-as-promised": "^8.0.1", 53 | "mocha": "^10.8.2", 54 | "proxyquire": "^2.1.3", 55 | "sinon": "^19.0.2", 56 | "sinon-chai": "^3.7.0", 57 | "typescript": "~5.8.3" 58 | }, 59 | "main": "main.js", 60 | "files": [ 61 | "admin{,/!(src)/**}/!(tsconfig|tsconfig.*|.eslintrc).json", 62 | "admin{,/!(src)/**}/*.{html,css,png,svg,jpg,js}", 63 | "lib/", 64 | "www/", 65 | "io-package.json", 66 | "LICENSE", 67 | "main.js" 68 | ], 69 | "scripts": { 70 | "test:js": "mocha --config test/mocharc.custom.json \"{!(node_modules|test)/**/*.test.js,*.test.js,test/**/test!(PackageFiles|Startup).js}\"", 71 | "test:package": "mocha test/package --exit", 72 | "test:integration": "mocha test/integration --exit", 73 | "test": "npm run test:js && npm run test:package", 74 | "check": "tsc --noEmit -p tsconfig.check.json", 75 | "lint": "eslint -c eslint.config.mjs .", 76 | "translate": "translate-adapter", 77 | "release": "release-script" 78 | }, 79 | "bugs": { 80 | "url": "https://github.com/o0shojo0o/ioBroker.opendtu/issues" 81 | }, 82 | "readmeFilename": "README.md" 83 | } 84 | -------------------------------------------------------------------------------- /prettier.config.mjs: -------------------------------------------------------------------------------- 1 | // iobroker prettier configuration file 2 | import prettierConfig from '@iobroker/eslint-config/prettier.config.mjs'; 3 | 4 | export default { 5 | ...prettierConfig, 6 | // uncomment next line if you prefer double quotes 7 | // singleQuote: false, 8 | } -------------------------------------------------------------------------------- /test/integration.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { tests } = require('@iobroker/testing'); 3 | 4 | // Run integration tests - See https://github.com/ioBroker/testing for a detailed explanation and further options 5 | tests.integration(path.join(__dirname, '..')); -------------------------------------------------------------------------------- /test/mocha.setup.js: -------------------------------------------------------------------------------- 1 | // Don't silently swallow unhandled rejections 2 | process.on('unhandledRejection', (e) => { 3 | throw e; 4 | }); 5 | 6 | // enable the should interface with sinon 7 | // and load chai-as-promised and sinon-chai by default 8 | const sinonChai = require('sinon-chai'); 9 | const chaiAsPromised = require('chai-as-promised'); 10 | const { should, use } = require('chai'); 11 | 12 | should(); 13 | use(sinonChai); 14 | use(chaiAsPromised); -------------------------------------------------------------------------------- /test/mocharc.custom.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": [ 3 | "test/mocha.setup.js" 4 | ], 5 | "watch-files": [ 6 | "!(node_modules|test)/**/*.test.js", 7 | "*.test.js", 8 | "test/**/test!(PackageFiles|Startup).js" 9 | ] 10 | } -------------------------------------------------------------------------------- /test/package.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { tests } = require('@iobroker/testing'); 3 | 4 | // Validate the package files 5 | tests.packageFiles(path.join(__dirname, '..')); 6 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "noImplicitAny": false 5 | }, 6 | "include": [ 7 | "./**/*.js" 8 | ] 9 | } -------------------------------------------------------------------------------- /tsconfig.check.json: -------------------------------------------------------------------------------- 1 | // Specialized tsconfig for type-checking js files 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": {}, 5 | "include": [ 6 | "**/*.js", 7 | "**/*.d.ts" 8 | ], 9 | "exclude": [ 10 | "**/build", 11 | "node_modules/", 12 | "widgets/" 13 | ] 14 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // Root tsconfig to set the settings and power editor support for all TS files 2 | { 3 | // To update the compilation target, install a different version of @tsconfig/node... and reference it here 4 | // https://github.com/tsconfig/bases#node-14-tsconfigjson 5 | "extends": "@tsconfig/node14/tsconfig.json", 6 | "compilerOptions": { 7 | // do not compile anything, this file is just to configure type checking 8 | "noEmit": true, 9 | // check JS files 10 | "allowJs": true, 11 | "checkJs": true, 12 | // This is necessary for the automatic typing of the adapter config 13 | "resolveJsonModule": true, 14 | // If you want to disable the stricter type checks (not recommended), uncomment the following line 15 | // "strict": false, 16 | // And enable some of those features for more fine-grained control 17 | // "strictNullChecks": true, 18 | // "strictPropertyInitialization": true, 19 | // "strictBindCallApply": true, 20 | "noImplicitAny": false, 21 | // "noUnusedLocals": true, 22 | // "noUnusedParameters": true, 23 | "useUnknownInCatchVariables": false, 24 | }, 25 | "include": [ 26 | "**/*.js", 27 | "**/*.d.ts" 28 | ], 29 | "exclude": [ 30 | "node_modules/**", 31 | "widgets/**" 32 | ] 33 | } --------------------------------------------------------------------------------