├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── auto-merge.yml ├── dependabot.yml └── workflows │ ├── dependabot-auto-merge.yml │ └── test-and-release.yml ├── .gitignore ├── .mocharc.json ├── .releaseconfig.json ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG_OLD.md ├── LICENSE ├── README.md ├── admin ├── .gitignore ├── 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 ├── jsonCustom.json └── mqtt-client.png ├── eslint.config.mjs ├── img ├── dialog.png └── settings.png ├── io-package.json ├── lib └── adapter-config.d.ts ├── main.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 /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Something is not working as it should 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 1. Go to '...' 15 | 2. Click on '...' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots & Logfiles** 23 | If applicable, add screenshots and logfiles to help explain your problem. 24 | 25 | **Versions:** 26 | - Adapter version: 27 | - JS-Controller version: 28 | - Node version: 29 | - Operating system: 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | - "master" 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, 24.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 | # TODO: To enable automatic npm releases, create a token on npmjs.org 55 | # Enter this token as a GitHub secret (with name NPM_TOKEN) in the repository options 56 | # Then uncomment the following block: 57 | 58 | # Deploys the final package to NPM 59 | deploy: 60 | needs: [check-and-lint, adapter-tests] 61 | 62 | # Trigger this step only when a commit on any branch is tagged with a version number 63 | if: | 64 | contains(github.event.head_commit.message, '[skip ci]') == false && 65 | github.event_name == 'push' && 66 | startsWith(github.ref, 'refs/tags/v') 67 | 68 | runs-on: ubuntu-latest 69 | 70 | # Write permissions are required to create Github releases 71 | permissions: 72 | contents: write 73 | 74 | steps: 75 | - uses: ioBroker/testing-action-deploy@v1 76 | with: 77 | node-version: '20.x' 78 | # Uncomment the following line if your adapter cannot be installed using 'npm ci' 79 | # install-command: 'npm install' 80 | npm-token: ${{ secrets.NPM_TOKEN }} 81 | github-token: ${{ secrets.GITHUB_TOKEN }} 82 | 83 | # When using Sentry for error reporting, Sentry can be informed about new releases 84 | # To enable create a API-Token in Sentry (User settings, API keys) 85 | # Enter this token as a GitHub secret (with name SENTRY_AUTH_TOKEN) in the repository options 86 | # Then uncomment and customize the following block: 87 | # sentry: true 88 | # sentry-token: ${{ secrets.SENTRY_AUTH_TOKEN }} 89 | # sentry-project: "iobroker-pid" 90 | # sentry-version-prefix: "iobroker.pid" 91 | # # If your sentry project is linked to a GitHub repository, you can enable the following option 92 | # # sentry-github-integration: true 93 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": [ 3 | "./test/mocha.setup.js" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.releaseconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["iobroker", "license", "manual-review"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "esbenp.prettier-vscode" 5 | ] 6 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "eslint.enable": true, 4 | "editor.formatOnSave": false, 5 | "editor.defaultFormatter": "esbenp.prettier-vscode", 6 | "[typescript]": { 7 | "editor.codeActionsOnSave": { 8 | "source.organizeImports": "explicit" 9 | } 10 | }, 11 | "json.schemas": [ 12 | { 13 | "fileMatch": [ 14 | "io-package.json" 15 | ], 16 | "url": "https://raw.githubusercontent.com/ioBroker/ioBroker.js-controller/master/schemas/io-package.json" 17 | }, 18 | { 19 | "fileMatch": [ 20 | "admin/jsonConfig.json", 21 | "admin/jsonCustom.json", 22 | "admin/jsonTab.json" 23 | ], 24 | "url": "https://raw.githubusercontent.com/ioBroker/adapter-react-v5/main/schemas/jsonConfig.json" 25 | } 26 | ], 27 | "[json]": { 28 | "editor.defaultFormatter": "vscode.json-language-features" 29 | } 30 | } -------------------------------------------------------------------------------- /CHANGELOG_OLD.md: -------------------------------------------------------------------------------- 1 | # Older changes 2 | ## 1.7.0 (2023-10-30) 3 | 4 | * (mcm1957) Dependencies have been updated 5 | * (mcm1957) Adapter requires nodejs 16 now 6 | 7 | ## 1.6.5 (2023-09-28) 8 | * (foxriver76) prevent crash cases on invalid subscribe 9 | 10 | ## 1.6.4 (2023-07-26) 11 | * (DutchmanNL) Option to allow self-signed certificates in adapter settings added. 12 | 13 | ## 1.6.3 (2022-06-16) 14 | * (Apollon77) Prevent potential crash cases reported by Sentry 15 | 16 | ## 1.6.2 (2022-04-02) 17 | * (Apollon77) Prevent potential crash cases reported by Sentry 18 | 19 | ## 1.6.1 (2022-02-24) 20 | * (Pmant) fix subscriptions 21 | * (Pmant) fix unsubscribing 22 | * (Pmant) use prefix for LWT topic 23 | 24 | ## 1.6.0 (2022-02-19) 25 | * (Pmant) add option to select protocol version 26 | * (Pmant) add websocket support 27 | * (Pmant) publish values once on enabling publishing 28 | * (Pmant) Upgrade to MQTT version 4 (resolves many connection issues) 29 | * (Pmant) fix LWT documentation 30 | * (Pmant) optionally publish a message when disconnecting gracefully 31 | 32 | ## 1.5.0 (2022-01-26) 33 | * IMPORTANT: This adapter now required at least js-controller 3.3.x 34 | * (Apollon77) Fix crash cases 35 | 36 | ## 1.4.1 (2022-01-26) 37 | * (bluefox) js-controller 3.3 optimizations 38 | 39 | ## 1.4.0 (2021-07-16) 40 | * IMPORTANT: This adapter now required at least js-controller 2.0.0 41 | * (Apollon77) js-controller 3.3 optimizations 42 | * (AlCalzone) Unpublished expired states 43 | * (AlCalzone) Only handle stat values if state exists 44 | 45 | ## 1.3.2 (2021-04-19) 46 | * (bluefox) Added support of admin5 47 | 48 | ## 1.3.1 (2020-03-17) 49 | * (bluefox) mqtt package moved back to 2.x 50 | 51 | ## 1.3.0 (2020-03-11) 52 | * (bluefox) mqtt package was updated 53 | * (bluefox) Fixed the error with "custom" view 54 | 55 | ## 1.2.1 (2019-10-17) 56 | * (algar42) Fix adapter restarting 57 | * (algar42) Fix mqtt issues 58 | 59 | ## 1.2.0 (2019-10-14) 60 | * (bluefox) Support of js-controller 2.0 was added 61 | 62 | ## 1.1.1 (2018-01-30) 63 | * (bluefox) small fixes 64 | 65 | ## 1.1.0 (2017-12-30) 66 | * (bluefox) Translations 67 | * (bluefox) Update of MQTT module 68 | 69 | ## 1.0.1 (2017-11-16) 70 | 71 | ## 1.0.0 (2017-11-16) 72 | * (bluefox) Update io-package.json 73 | 74 | ## 0.3.2 (2016-11-18) 75 | * (Pmant) fix initial object parsing 76 | * (Pmant) fix objects view 77 | 78 | ## 0.3.1 (2016-11-16) 79 | * (Pmant) fix crash 80 | 81 | ## 0.3.0 (2016-09-08) 82 | * (Pmant) add optional publish and subscribe prefixes 83 | 84 | ## 0.2.5 (2016-09-08) 85 | * (Pmant) reduce logging -> debug 86 | 87 | ## 0.2.0 (2016-09-08) 88 | * (Pmant) use new custom settings 89 | 90 | ## 0.1.1 (2016-06-09) 91 | * (Pmant) fix possible loop 92 | 93 | ## 0.1.0 (2016-06-08) 94 | * (Pmant) initial commit 95 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2025 iobroker-community-adapters 4 | Copyright (c) 2016-2023 Pmant 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](admin/mqtt-client.png) 2 | 3 | # ioBroker.mqtt-client 4 | 5 | [![NPM version](https://img.shields.io/npm/v/iobroker.mqtt-client?style=flat-square)](https://www.npmjs.com/package/iobroker.mqtt-client) 6 | [![Downloads](https://img.shields.io/npm/dm/iobroker.mqtt-client?label=npm%20downloads&style=flat-square)](https://www.npmjs.com/package/iobroker.mqtt-client) 7 | ![node-lts](https://img.shields.io/node/v-lts/iobroker.mqtt-client?style=flat-square) 8 | ![Libraries.io dependency status for latest release](https://img.shields.io/librariesio/release/npm/iobroker.mqtt-client?label=npm%20dependencies&style=flat-square) 9 | 10 | ![GitHub](https://img.shields.io/github/license/iobroker-community-adapters/iobroker.mqtt-client?style=flat-square) 11 | ![GitHub repo size](https://img.shields.io/github/repo-size/iobroker-community-adapters/iobroker.mqtt-client?logo=github&style=flat-square) 12 | ![GitHub commit activity](https://img.shields.io/github/commit-activity/m/iobroker-community-adapters/iobroker.mqtt-client?logo=github&style=flat-square) 13 | ![GitHub last commit](https://img.shields.io/github/last-commit/iobroker-community-adapters/iobroker.mqtt-client?logo=github&style=flat-square) 14 | ![GitHub issues](https://img.shields.io/github/issues/iobroker-community-adapters/iobroker.mqtt-client?logo=github&style=flat-square) 15 | ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/iobroker-community-adapters/iobroker.mqtt-client/test-and-release.yml?branch=master&logo=github&style=flat-square) 16 | 17 | ## Versions 18 | 19 | ![Beta](https://img.shields.io/npm/v/iobroker.mqtt-client.svg?color=red&label=beta) 20 | ![Stable](http://iobroker.live/badges/mqtt-client-stable.svg) 21 | ![Installed](http://iobroker.live/badges/mqtt-client-installed.svg) 22 | 23 | Publish and subscribe ioBroker states to MQTT Brokers 24 | 25 | ## Sentry 26 | **This adapter uses Sentry libraries to automatically report exceptions and code errors to the developers.** 27 | For more details and for information how to disable the error reporting see [Sentry-Plugin Documentation](https://github.com/ioBroker/plugin-sentry#plugin-sentry)! Sentry reporting is used starting with js-controller 3.0. 28 | 29 | ## Adapter Settings 30 | ![Adapter](img/settings.png) 31 | 32 | ### on connect topic and message 33 | The ```on connect message``` is published to the ```on connect topic``` every time the client connects or reconnects to the server. 34 | 35 | ### on disconnect topic and message 36 | The ```on disconnect message``` is published to the ```on disconnect topic``` when the adapter stops gracefully. 37 | 38 | ### last will topic and message 39 | The ```last will message``` is published to the ```last will topic``` every time the client connects or reconnects to the server. 40 | The Server will store this message and send it to its subscribers when the client disconnects unexpectedly. 41 | 42 | ### subscriptions 43 | Comma separated list of topics that are not covered by existing states. 44 | Received messages are converted to states within the adapter's namespace (e.g. mqtt.0) and subscribed. 45 | You can remove topics after all states have been created. 46 | 47 | ### publish prefix 48 | When publishing this will be prepended to all topics. 49 | Default is empty (no prefix). 50 | 51 | ### subscribe prefix 52 | When subscribing this will be prepended to all topics. 53 | Default is empty (no prefix). 54 | 55 | ## State Settings 56 | ![State](img/dialog.png) 57 | 58 | ### enabled 59 | Enables or disables the mqtt-client functionality for this state. 60 | Disabling will delete any mqtt-client settings from this state. 61 | 62 | ### topic 63 | The topic this state is published to and subscribed from. 64 | default: state-ID converted to a mqtt topic. 65 | 66 | ### publish 67 | * ```enable``` state will be published 68 | * ```changes only``` state will only be published when its value changes 69 | * ```as object``` whole state will be published as object 70 | * ```qos``` see 71 | * ```retain``` see 72 | 73 | ### subscribe 74 | * ```enable``` topic will be subscribed and state will be updated accordingly 75 | * ```changes only``` state will only be written when the value changed 76 | * ```as object``` messages will be interpreted as objects 77 | * ```qos``` see 78 | * ```ack``` on state updates the ack flag will be set accordingly 79 | 80 | #### Note 81 | * when ack is set to true it will overwrite objects ack, see ```as object``` 82 | * to prevent message loops, if both publish and subscribe are enabled ```changes only``` is always on for subscribe 83 | 84 | 85 | 89 | ## Changelog 90 | ### 3.0.0 (2025-01-24) 91 | * (@klein0r) Breaking change: Underscores are not replaced by spaces in the corresponding topic anymore 92 | 93 | ### 2.1.0 (2024-11-12) 94 | * (mcm1957) Adapter requires node.js 20 now. 95 | * (mcm1957) Adapter requires js-controller 5.0.19 and admin 6.17.14 now. 96 | * (simatec) Adapter changed to meet Responsive Design rules. 97 | * (mcm1957) Dependencies have been updated. 98 | 99 | ### 2.0.1 (2024-09-23) 100 | * (@klein0r) Added missing information in configuration dialog 101 | * (@klein0r) Fixed type of port configuration to avoid conflicts 102 | 103 | ### 2.0.0 (2024-06-21) 104 | * (klein0r) Password is now encrypted - you have to re-renter your password in instance settings! 105 | * (klein0r) Use jsonConfig instead of materialize (for instance settings) 106 | 107 | ### 1.8.0 (2024-04-07) 108 | * (mcm1957) Adapter requires node.js 18 and js-controller >= 5 now 109 | * (mcm1957) Dependencies have been updated 110 | 111 | ## License 112 | The MIT License (MIT) 113 | 114 | Copyright (c) 2025 iobroker-community-adapters 115 | Copyright (c) 2016-2023 Pmant 116 | 117 | Permission is hereby granted, free of charge, to any person obtaining a copy 118 | of this software and associated documentation files (the "Software"), to deal 119 | in the Software without restriction, including without limitation the rights 120 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 121 | copies of the Software, and to permit persons to whom the Software is 122 | furnished to do so, subject to the following conditions: 123 | 124 | The above copyright notice and this permission notice shall be included in 125 | all copies or substantial portions of the Software. 126 | 127 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 128 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 129 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 130 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 131 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 132 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 133 | THE SOFTWARE. 134 | -------------------------------------------------------------------------------- /admin/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iobroker-community-adapters/ioBroker.mqtt-client/686b397a4370648ad2972e2e037d71b5f85a0af1/admin/.gitignore -------------------------------------------------------------------------------- /admin/i18n/de/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "(without prefix)": "(ohne Präfix)", 3 | "Client ID": "Client ID", 4 | "MQTT-client adapter settings": "MQTT-client Adapter Einstellungen", 5 | "Prefix for topics": "Prefix für alle Topics", 6 | "QoS": "QoS", 7 | "Server settings": "Server Einstellungen", 8 | "additional subscriptions": "Zusätzliche subscriptions", 9 | "as object": "als Objekt", 10 | "changes only": "nur Änderungen", 11 | "enabled": "aktiviert", 12 | "host": "MQTT Broker IP", 13 | "last will message": "last will message", 14 | "last will topic": "last will topic", 15 | "must be unique": "muss einmalig sein", 16 | "note": "MQTT-client Einstellungen müssen für jeden State einzeln gemacht werden", 17 | "on connect message": "Meldung bei Verbindung", 18 | "on connect topic": "Topic bei Verbindung", 19 | "on disconnect message": "Nachricht, die beim Trennen gesendet wird", 20 | "on disconnect topic": "Topic, welches beim Trennen verwendet wird", 21 | "password": "Kennwort", 22 | "port": "Port", 23 | "prefix for publishing topics": "Präfix für publish Topics", 24 | "prefix for subscribing topics": "Präfix für subscribe Topics", 25 | "publish": "publish", 26 | "retain": "retain", 27 | "ssl": "SSL", 28 | "username": "Benutzername", 29 | "without prefix": "ohne Präfix", 30 | "rejectUnauthorized": "Signierte Zertifikate erzwingen", 31 | "Custom options": "Benutzerdefinierte Optionen", 32 | "MQTT Version": "MQTT-Version", 33 | "Use websockets": "Verwende WebSockets", 34 | "Topic must not start with /": "Das Topics darf nicht mit / beginnen", 35 | "Topic must not end with /": "Das Topics darf nicht mit / enden" 36 | } 37 | -------------------------------------------------------------------------------- /admin/i18n/en/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "(without prefix)": "(without prefix)", 3 | "Client ID": "Client ID", 4 | "MQTT-client adapter settings": "MQTT-client adapter settings", 5 | "Prefix for topics": "Prefix for topics", 6 | "QoS": "QoS", 7 | "Server settings": "Server settings", 8 | "additional subscriptions": "Additional subscriptions", 9 | "as object": "as object", 10 | "changes only": "changes only", 11 | "enabled": "enabled", 12 | "host": "MQTT Broker IP", 13 | "last will message": "Last will message", 14 | "last will topic": "Last will topic", 15 | "must be unique": "must be unique", 16 | "note": "MQTT-client settings must be done for every state individually", 17 | "on connect message": "On connect message", 18 | "on connect topic": "On connect topic", 19 | "on disconnect message": "message sent when disconnecting", 20 | "on disconnect topic": "topic used when disconnecting", 21 | "password": "Password", 22 | "port": "Port", 23 | "prefix for publishing topics": "Prefix for publishing topics", 24 | "prefix for subscribing topics": "Prefix for subscribing topics", 25 | "publish": "publish", 26 | "retain": "retain", 27 | "ssl": "SSL", 28 | "username": "User name", 29 | "without prefix": "without prefix", 30 | "rejectUnauthorized": "Enforce signed certificates", 31 | "Custom options": "Custom options", 32 | "MQTT Version": "MQTT Version", 33 | "Use websockets": "Use websockets", 34 | "Topic must not start with /": "Topic must not start with /", 35 | "Topic must not end with /": "Topic must not end with /" 36 | } 37 | -------------------------------------------------------------------------------- /admin/i18n/es/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "(without prefix)": "(sin prefijo)", 3 | "Client ID": "Identificación del cliente", 4 | "MQTT-client adapter settings": "Configuraciones del adaptador MQTT-cliente", 5 | "Prefix for topics": "Prefijo para temas", 6 | "QoS": "QoS", 7 | "Server settings": "Configuración del servidor", 8 | "additional subscriptions": "suscripciones adicionales", 9 | "as object": "como objeto", 10 | "changes only": "solo cambios", 11 | "enabled": "habilitado", 12 | "host": "anfitrión", 13 | "last will message": "last will message", 14 | "last will topic": "last will topic", 15 | "must be unique": "debe ser único", 16 | "note": "La configuración del cliente MQTT se debe realizar para cada estado individualmente", 17 | "on connect message": "en conectar mensaje", 18 | "on connect topic": "sobre el tema de conexión", 19 | "on disconnect message": "mensaje enviado al desconectar", 20 | "on disconnect topic": "tema utilizado al desconectar", 21 | "password": "contraseña", 22 | "port": "Puerto", 23 | "prefix for publishing topics": "prefijo para publicar temas", 24 | "prefix for subscribing topics": "prefijo para suscribirse a los temas", 25 | "publish": "publicar", 26 | "retain": "conservar", 27 | "ssl": "SSL", 28 | "username": "usuario", 29 | "without prefix": "sin prefijo", 30 | "rejectUnauthorized": "Aplicar certificados firmados", 31 | "Custom options": "Opciones personalizadas", 32 | "MQTT Version": "Versión MQTT", 33 | "Use websockets": "Usar sockets web", 34 | "Topic must not start with /": "El tema no debe comenzar con /", 35 | "Topic must not end with /": "El tema no debe terminar con /" 36 | } 37 | -------------------------------------------------------------------------------- /admin/i18n/fr/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "(without prefix)": "(sans préfixe)", 3 | "Client ID": "identité du client", 4 | "MQTT-client adapter settings": "MQTT-paramètres de la carte client", 5 | "Prefix for topics": "Préfixe pour les sujets", 6 | "QoS": "QoS", 7 | "Server settings": "Paramètres du serveur", 8 | "additional subscriptions": "abonnements supplémentaires", 9 | "as object": "comme objet", 10 | "changes only": "changements seulement", 11 | "enabled": "activée", 12 | "host": "hôte", 13 | "last will message": "last will message", 14 | "last will topic": "last will topic", 15 | "must be unique": "doit être unique", 16 | "note": "Les paramètres du client MQTT doivent être définis pour chaque état individuellement", 17 | "on connect message": "sur le message de connexion", 18 | "on connect topic": "sur le sujet de connexion", 19 | "on disconnect message": "message envoyé lors de la déconnexion", 20 | "on disconnect topic": "sujet utilisé lors de la déconnexion", 21 | "password": "mot de passe", 22 | "port": "Port", 23 | "prefix for publishing topics": "préfixe pour les sujets de publication", 24 | "prefix for subscribing topics": "préfixe pour les sujets abonnés", 25 | "publish": "publish", 26 | "retain": "retain", 27 | "ssl": "SSL", 28 | "username": "Nom d'utilisateur", 29 | "without prefix": "sans préfixe", 30 | "rejectUnauthorized": "Appliquer les certificats signés", 31 | "Custom options": "Options personnalisées", 32 | "MQTT Version": "Version MQTT", 33 | "Use websockets": "Utiliser des websockets", 34 | "Topic must not start with /": "Le sujet ne doit pas commencer par /", 35 | "Topic must not end with /": "Le sujet ne doit pas se terminer par /" 36 | } 37 | -------------------------------------------------------------------------------- /admin/i18n/it/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "(without prefix)": "(senza prefisso)", 3 | "Client ID": "Identificativo cliente", 4 | "MQTT-client adapter settings": "Impostazioni dell'adattatore client MQTT", 5 | "Prefix for topics": "Prefisso per argomenti", 6 | "QoS": "QoS", 7 | "Server settings": "Impostazioni del server", 8 | "additional subscriptions": "iscrizioni aggiuntive", 9 | "as object": "come oggetto", 10 | "changes only": "solo modifiche", 11 | "enabled": "abilitato", 12 | "host": "ospite", 13 | "last will message": "last will message", 14 | "last will topic": "last will topic", 15 | "must be unique": "deve essere unico", 16 | "note": "Le impostazioni del client MQTT devono essere eseguite singolarmente per ogni stato", 17 | "on connect message": "sul messaggio di connessione", 18 | "on connect topic": "su argomento di connessione", 19 | "on disconnect message": "messaggio inviato durante la disconnessione", 20 | "on disconnect topic": "argomento utilizzato durante la disconnessione", 21 | "password": "parola d'ordine", 22 | "port": "Porta", 23 | "prefix for publishing topics": "prefisso per argomenti di pubblicazione", 24 | "prefix for subscribing topics": "prefisso per gli argomenti di iscrizione", 25 | "publish": "pubblicare", 26 | "retain": "conservare", 27 | "ssl": "SSL", 28 | "username": "nome utente", 29 | "without prefix": "senza prefisso", 30 | "rejectUnauthorized": "Applica i certificati firmati", 31 | "Custom options": "Opzioni personalizzate", 32 | "MQTT Version": "Versione MQTT", 33 | "Use websockets": "Usa i websocket", 34 | "Topic must not start with /": "L'argomento non deve iniziare con /", 35 | "Topic must not end with /": "L'argomento non deve terminare con /" 36 | } 37 | -------------------------------------------------------------------------------- /admin/i18n/nl/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "(without prefix)": "(zonder voorvoegsel)", 3 | "Client ID": "klant identificatie", 4 | "MQTT-client adapter settings": "MQTT-client adapterinstellingen", 5 | "Prefix for topics": "Voorvoegsel voor onderwerpen", 6 | "QoS": "QoS", 7 | "Server settings": "Server instellingen", 8 | "additional subscriptions": "extra abonnementen", 9 | "as object": "als object", 10 | "changes only": "alleen wijzigingen", 11 | "enabled": "ingeschakeld", 12 | "host": "gastheer", 13 | "last will message": "last will message", 14 | "last will topic": "last will topic", 15 | "must be unique": "moet uniek zijn", 16 | "note": "MQTT-clientinstellingen moeten voor elke status afzonderlijk worden uitgevoerd", 17 | "on connect message": "bij verbinden bericht", 18 | "on connect topic": "bij connect topic", 19 | "on disconnect message": "bericht verzonden bij loskoppelen", 20 | "on disconnect topic": "onderwerp gebruikt bij het verbreken van de verbinding", 21 | "password": "wachtwoord", 22 | "port": "haven", 23 | "prefix for publishing topics": "voorvoegsel voor het publiceren van onderwerpen", 24 | "prefix for subscribing topics": "voorvoegsel voor abonnementsonderwerpen", 25 | "publish": "publish", 26 | "retain": "retain", 27 | "ssl": "SSL", 28 | "username": "gebruikersnaam", 29 | "without prefix": "zonder voorvoegsel", 30 | "rejectUnauthorized": "Ondertekende certificaten afdwingen", 31 | "Custom options": "Aangepaste opties", 32 | "MQTT Version": "MQTT-versie", 33 | "Use websockets": "Gebruik websockets", 34 | "Topic must not start with /": "Onderwerp mag niet beginnen met /", 35 | "Topic must not end with /": "Onderwerp mag niet eindigen op /" 36 | } 37 | -------------------------------------------------------------------------------- /admin/i18n/pl/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "(without prefix)": "(bez prefiksu)", 3 | "Client ID": "Identyfikator klienta", 4 | "MQTT-client adapter settings": "Ustawienia adaptera klienta MQTT", 5 | "Prefix for topics": "Prefiks tematów", 6 | "QoS": "QoS", 7 | "Server settings": "Ustawienia serwera", 8 | "additional subscriptions": "Dodatkowe subskrypcje", 9 | "as object": "jako przedmiot", 10 | "changes only": "tylko zmiany", 11 | "enabled": "aktywowany", 12 | "host": "Adres IP brokera MQTT", 13 | "last will message": "Ostatnia wiadomość", 14 | "last will topic": "Ostatni temat", 15 | "must be unique": "musi być unikalny", 16 | "note": "Ustawienia klienta MQTT muszą być wykonane indywidualnie dla każdego stanu", 17 | "on connect message": "Po połączeniu wiadomość", 18 | "on connect topic": "Na temat połączenia", 19 | "on disconnect message": "wiadomość wysłana po rozłączeniu", 20 | "on disconnect topic": "temat używany podczas rozłączania", 21 | "password": "Hasło", 22 | "port": "Port", 23 | "prefix for publishing topics": "Prefiks do publikowania tematów", 24 | "prefix for subscribing topics": "Prefiks do subskrybowania tematów", 25 | "publish": "publikować", 26 | "retain": "retain", 27 | "ssl": "SSL", 28 | "username": "Nazwa Użytkownika", 29 | "without prefix": "bez prefiksu", 30 | "rejectUnauthorized": "Wymuszaj podpisane certyfikaty", 31 | "Custom options": "Opcje niestandardowe", 32 | "MQTT Version": "Wersja MQTT", 33 | "Use websockets": "Skorzystaj z websocketów", 34 | "Topic must not start with /": "Temat nie może zaczynać się od /", 35 | "Topic must not end with /": "Temat nie może kończyć się na /" 36 | } 37 | -------------------------------------------------------------------------------- /admin/i18n/pt/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "(without prefix)": "(sem prefixo)", 3 | "Client ID": "ID do Cliente", 4 | "MQTT-client adapter settings": "Configurações do adaptador do cliente MQTT", 5 | "Prefix for topics": "Prefixo para tópicos", 6 | "QoS": "QoS", 7 | "Server settings": "Configurações do servidor", 8 | "additional subscriptions": "assinaturas adicionais", 9 | "as object": "como objeto", 10 | "changes only": "muda apenas", 11 | "enabled": "ativado", 12 | "host": "hospedeiro", 13 | "last will message": "last will message", 14 | "last will topic": "last will topic", 15 | "must be unique": "deve ser único", 16 | "note": "As configurações do cliente MQTT devem ser feitas para cada estado individualmente", 17 | "on connect message": "na mensagem conectar", 18 | "on connect topic": "no tópico de conexão", 19 | "on disconnect message": "mensagem enviada ao desconectar", 20 | "on disconnect topic": "tópico usado ao desconectar", 21 | "password": "senha", 22 | "port": "Porta", 23 | "prefix for publishing topics": "prefixo para publicar tópicos", 24 | "prefix for subscribing topics": "prefixo para subscrever tópicos", 25 | "publish": "publish", 26 | "retain": "retain", 27 | "ssl": "SSL", 28 | "username": "nome de usuário", 29 | "without prefix": "sem prefixo", 30 | "rejectUnauthorized": "Aplicar certificados assinados", 31 | "Custom options": "Opções personalizadas", 32 | "MQTT Version": "Versão MQTT", 33 | "Use websockets": "Usar websockets", 34 | "Topic must not start with /": "O tópico não deve começar com /", 35 | "Topic must not end with /": "O tópico não deve terminar com /" 36 | } 37 | -------------------------------------------------------------------------------- /admin/i18n/ru/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "(without prefix)": "(без префикса)", 3 | "Client ID": "ID клиента", 4 | "MQTT-client adapter settings": "Настройки драйвера MQTT-клиента", 5 | "Prefix for topics": "Префикс для всех значений", 6 | "QoS": "QoS", 7 | "Server settings": "Настройки сервера", 8 | "additional subscriptions": "Дополнительные подписки", 9 | "as object": "как объект", 10 | "changes only": "только изменения", 11 | "enabled": "включен", 12 | "host": "MQTT Broker IP", 13 | "last will message": "last will message", 14 | "last will topic": "last will topic", 15 | "must be unique": "должно быть уникальным", 16 | "note": "Настройки MQTT-клиента должны выполняться для каждого состояния отдельно", 17 | "on connect message": "Сообщение при подключении", 18 | "on connect topic": "Топик при подключении", 19 | "on disconnect message": "сообщение отправляется при отключении", 20 | "on disconnect topic": "тема, используемая при отключении", 21 | "password": "Пароль", 22 | "port": "Порт", 23 | "prefix for publishing topics": "Префикс для публикации тем", 24 | "prefix for subscribing topics": "Префикс для подписки на темы", 25 | "publish": "publish", 26 | "retain": "retain", 27 | "ssl": "SSL", 28 | "username": "Имя пользователя", 29 | "without prefix": "без префикса", 30 | "rejectUnauthorized": "Применение подписанных сертификатов", 31 | "Custom options": "Пользовательские параметры", 32 | "MQTT Version": "MQTT-версия", 33 | "Use websockets": "Используйте веб-сокеты", 34 | "Topic must not start with /": "Тема не должна начинаться с /", 35 | "Topic must not end with /": "Тема не должна заканчиваться на /" 36 | } 37 | -------------------------------------------------------------------------------- /admin/i18n/uk/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "(without prefix)": "(без префікса)", 3 | "Client ID": "ID клієнта", 4 | "MQTT-client adapter settings": "Налаштування адаптера MQTT-клієнта", 5 | "Prefix for topics": "Префікс для тем", 6 | "QoS": "QoS", 7 | "Server settings": "Налаштування сервера", 8 | "additional subscriptions": "Додаткові підписки", 9 | "as object": "як об'єкт", 10 | "changes only": "тільки зміни", 11 | "enabled": "включено", 12 | "host": "IP брокера MQTT", 13 | "last will message": "Останнє повідомлення", 14 | "last will topic": "Остання буде тема", 15 | "must be unique": "має бути унікальним", 16 | "note": "Налаштування MQTT-клієнта необхідно робити для кожного стану окремо", 17 | "on connect message": "Повідомлення про підключення", 18 | "on connect topic": "По темі підключення", 19 | "on disconnect message": "повідомлення, надіслане під час відключення", 20 | "on disconnect topic": "тема, яка використовується під час відключення", 21 | "password": "Пароль", 22 | "port": "Порт", 23 | "prefix for publishing topics": "Префікс для публікації тем", 24 | "prefix for subscribing topics": "Префікс для підписки на теми", 25 | "publish": "публікувати", 26 | "retain": "зберегти", 27 | "ssl": "SSL", 28 | "username": "Ім'я користувача", 29 | "without prefix": "без префікса", 30 | "rejectUnauthorized": "Примусове виконання підписаних сертифікатів", 31 | "Custom options": "Спеціальні параметри", 32 | "MQTT Version": "Версія MQTT", 33 | "Use websockets": "Використовуйте веб-сокети", 34 | "Topic must not start with /": "Тема не повинна починатися з /", 35 | "Topic must not end with /": "Тема не повинна закінчуватися на /" 36 | } 37 | -------------------------------------------------------------------------------- /admin/i18n/zh-cn/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "(without prefix)": "(无前缀)", 3 | "Client ID": "客户编号", 4 | "MQTT-client adapter settings": "MQTT客户端适配器设置", 5 | "Prefix for topics": "主题前缀", 6 | "QoS": "QoS", 7 | "Server settings": "服务器设定", 8 | "additional subscriptions": "额外订阅", 9 | "as object": "作为对象", 10 | "changes only": "仅更改", 11 | "enabled": "活性", 12 | "host": "MQTT经纪人IP", 13 | "last will message": "最后的消息", 14 | "last will topic": "最后遗嘱主题", 15 | "must be unique": "必须是唯一的", 16 | "note": "必须为每个状态分别完成MQTT客户端设置", 17 | "on connect message": "在连接消息", 18 | "on connect topic": "关于连接主题", 19 | "on disconnect message": "断开连接时发送的消息", 20 | "on disconnect topic": "断开连接时使用的主题", 21 | "password": "密码", 22 | "port": "港口", 23 | "prefix for publishing topics": "发布主题的前缀", 24 | "prefix for subscribing topics": "订阅主题的前缀", 25 | "publish": "发布", 26 | "retain": "retain", 27 | "ssl": "SSL协议", 28 | "username": "用户名", 29 | "without prefix": "无前缀", 30 | "rejectUnauthorized": "强制执行签名证书", 31 | "Custom options": "自定义选项", 32 | "MQTT Version": "MQTT版本", 33 | "Use websockets": "使用网络套接字", 34 | "Topic must not start with /": "主题不得以 / 开头", 35 | "Topic must not end with /": "主题不得以 / 结尾" 36 | } 37 | -------------------------------------------------------------------------------- /admin/jsonConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "i18n": true, 3 | "type": "tabs", 4 | "tabsStyle": { 5 | "width": "calc(100% - 100px)" 6 | }, 7 | "iconPosition": "top", 8 | "items": { 9 | "_general": { 10 | "type": "panel", 11 | "label": "General settings", 12 | "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48IS0tISBGb250IEF3ZXNvbWUgUHJvIDYuNC4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlIChDb21tZXJjaWFsIExpY2Vuc2UpIENvcHlyaWdodCAyMDIzIEZvbnRpY29ucywgSW5jLiAtLT48cGF0aCBkPSJNNDk1LjkgMTY2LjZjMy4yIDguNyAuNSAxOC40LTYuNCAyNC42bC00My4zIDM5LjRjMS4xIDguMyAxLjcgMTYuOCAxLjcgMjUuNHMtLjYgMTcuMS0xLjcgMjUuNGw0My4zIDM5LjRjNi45IDYuMiA5LjYgMTUuOSA2LjQgMjQuNmMtNC40IDExLjktOS43IDIzLjMtMTUuOCAzNC4zbC00LjcgOC4xYy02LjYgMTEtMTQgMjEuNC0yMi4xIDMxLjJjLTUuOSA3LjItMTUuNyA5LjYtMjQuNSA2LjhsLTU1LjctMTcuN2MtMTMuNCAxMC4zLTI4LjIgMTguOS00NCAyNS40bC0xMi41IDU3LjFjLTIgOS4xLTkgMTYuMy0xOC4yIDE3LjhjLTEzLjggMi4zLTI4IDMuNS00Mi41IDMuNXMtMjguNy0xLjItNDIuNS0zLjVjLTkuMi0xLjUtMTYuMi04LjctMTguMi0xNy44bC0xMi41LTU3LjFjLTE1LjgtNi41LTMwLjYtMTUuMS00NC0yNS40TDgzLjEgNDI1LjljLTguOCAyLjgtMTguNiAuMy0yNC41LTYuOGMtOC4xLTkuOC0xNS41LTIwLjItMjIuMS0zMS4ybC00LjctOC4xYy02LjEtMTEtMTEuNC0yMi40LTE1LjgtMzQuM2MtMy4yLTguNy0uNS0xOC40IDYuNC0yNC42bDQzLjMtMzkuNEM2NC42IDI3My4xIDY0IDI2NC42IDY0IDI1NnMuNi0xNy4xIDEuNy0yNS40TDIyLjQgMTkxLjJjLTYuOS02LjItOS42LTE1LjktNi40LTI0LjZjNC40LTExLjkgOS43LTIzLjMgMTUuOC0zNC4zbDQuNy04LjFjNi42LTExIDE0LTIxLjQgMjIuMS0zMS4yYzUuOS03LjIgMTUuNy05LjYgMjQuNS02LjhsNTUuNyAxNy43YzEzLjQtMTAuMyAyOC4yLTE4LjkgNDQtMjUuNGwxMi41LTU3LjFjMi05LjEgOS0xNi4zIDE4LjItMTcuOEMyMjcuMyAxLjIgMjQxLjUgMCAyNTYgMHMyOC43IDEuMiA0Mi41IDMuNWM5LjIgMS41IDE2LjIgOC43IDE4LjIgMTcuOGwxMi41IDU3LjFjMTUuOCA2LjUgMzAuNiAxNS4xIDQ0IDI1LjRsNTUuNy0xNy43YzguOC0yLjggMTguNi0uMyAyNC41IDYuOGM4LjEgOS44IDE1LjUgMjAuMiAyMi4xIDMxLjJsNC43IDguMWM2LjEgMTEgMTEuNCAyMi40IDE1LjggMzQuM3pNMjU2IDMzNmE4MCA4MCAwIDEgMCAwLTE2MCA4MCA4MCAwIDEgMCAwIDE2MHoiLz48L3N2Zz4=", 13 | "items": { 14 | "host": { 15 | "newLine": true, 16 | "type": "text", 17 | "xs": 12, 18 | "sm": 12, 19 | "md": 6, 20 | "lg": 4, 21 | "xl": 4, 22 | "label": "host" 23 | }, 24 | "port": { 25 | "type": "number", 26 | "min": 1, 27 | "max": 65535, 28 | "step": 1, 29 | "xs": 12, 30 | "sm": 12, 31 | "md": 6, 32 | "lg": 4, 33 | "xl": 4, 34 | "label": "port" 35 | }, 36 | "mqttVersion": { 37 | "type": "select", 38 | "options": [ 39 | { 40 | "label": "3", 41 | "value": 3 42 | }, 43 | { 44 | "label": "4", 45 | "value": 4 46 | }, 47 | { 48 | "label": "5", 49 | "value": 5 50 | } 51 | ], 52 | "xs": 12, 53 | "sm": 12, 54 | "md": 6, 55 | "lg": 4, 56 | "xl": 4, 57 | "label": "MQTT Version" 58 | }, 59 | "websocket": { 60 | "newLine": true, 61 | "type": "checkbox", 62 | "xs": 12, 63 | "sm": 12, 64 | "md": 6, 65 | "lg": 4, 66 | "xl": 4, 67 | "label": "Use websockets" 68 | }, 69 | "ssl": { 70 | "type": "checkbox", 71 | "xs": 12, 72 | "sm": 12, 73 | "md": 6, 74 | "lg": 4, 75 | "xl": 4, 76 | "label": "ssl" 77 | }, 78 | "rejectUnauthorized": { 79 | "hidden": "!data.ssl", 80 | "type": "checkbox", 81 | "xs": 12, 82 | "sm": 12, 83 | "md": 6, 84 | "lg": 4, 85 | "xl": 4, 86 | "label": "rejectUnauthorized" 87 | }, 88 | "clientId": { 89 | "newLine": true, 90 | "type": "text", 91 | "xs": 12, 92 | "sm": 12, 93 | "md": 6, 94 | "lg": 4, 95 | "xl": 4, 96 | "label": "Client ID" 97 | }, 98 | "username": { 99 | "newLine": true, 100 | "type": "text", 101 | "xs": 12, 102 | "sm": 12, 103 | "md": 6, 104 | "lg": 4, 105 | "xl": 4, 106 | "label": "username" 107 | }, 108 | "password": { 109 | "type": "password", 110 | "repeat": false, 111 | "visible": true, 112 | "xs": 12, 113 | "sm": 12, 114 | "md": 6, 115 | "lg": 4, 116 | "xl": 4, 117 | "label": "password" 118 | }, 119 | "_headerCustomOptions": { 120 | "newLine": true, 121 | "type": "header", 122 | "size": 2, 123 | "xs": 12, 124 | "sm": 12, 125 | "md": 12, 126 | "lg": 12, 127 | "xl": 12, 128 | "text": "Custom options" 129 | }, 130 | "onConnectTopic": { 131 | "newLine": true, 132 | "type": "text", 133 | "xs": 12, 134 | "sm": 12, 135 | "md": 6, 136 | "lg": 4, 137 | "xl": 4, 138 | "label": "on connect topic", 139 | "validator": "!data.onConnectTopic.startsWith('/')", 140 | "validatorErrorText": "Topic must not start with /", 141 | "validatorNoSaveOnError": true 142 | }, 143 | "onConnectMessage": { 144 | "type": "text", 145 | "xs": 12, 146 | "sm": 12, 147 | "md": 6, 148 | "lg": 4, 149 | "xl": 4, 150 | "label": "on connect message" 151 | }, 152 | "onDisconnectTopic": { 153 | "newLine": true, 154 | "type": "text", 155 | "xs": 12, 156 | "sm": 12, 157 | "md": 6, 158 | "lg": 4, 159 | "xl": 4, 160 | "label": "on disconnect topic", 161 | "validator": "!data.onDisconnectTopic.startsWith('/')", 162 | "validatorErrorText": "Topic must not start with /", 163 | "validatorNoSaveOnError": true 164 | }, 165 | "onDisconnectMessage": { 166 | "type": "text", 167 | "xs": 12, 168 | "sm": 12, 169 | "md": 6, 170 | "lg": 4, 171 | "xl": 4, 172 | "label": "on disconnect message" 173 | }, 174 | "lastWillTopic": { 175 | "newLine": true, 176 | "type": "text", 177 | "xs": 12, 178 | "sm": 12, 179 | "md": 6, 180 | "lg": 4, 181 | "xl": 4, 182 | "label": "last will topic", 183 | "validator": "!data.lastWillTopic.startsWith('/')", 184 | "validatorErrorText": "Topic must not start with /", 185 | "validatorNoSaveOnError": true 186 | }, 187 | "lastWillMessage": { 188 | "type": "text", 189 | "xs": 12, 190 | "sm": 12, 191 | "md": 6, 192 | "lg": 4, 193 | "xl": 4, 194 | "label": "last will message" 195 | }, 196 | "inbox": { 197 | "newLine": true, 198 | "type": "text", 199 | "xs": 12, 200 | "sm": 12, 201 | "md": 6, 202 | "lg": 4, 203 | "xl": 4, 204 | "label": "prefix for subscribing topics", 205 | "validator": "!data.inbox.endsWith('/')", 206 | "validatorErrorText": "Topic must not end with /", 207 | "validatorNoSaveOnError": true 208 | }, 209 | "outbox": { 210 | "type": "text", 211 | "xs": 12, 212 | "sm": 12, 213 | "md": 6, 214 | "lg": 4, 215 | "xl": 4, 216 | "label": "prefix for publishing topics", 217 | "validator": "!data.outbox.endsWith('/')", 218 | "validatorErrorText": "Topic must not end with /", 219 | "validatorNoSaveOnError": true 220 | }, 221 | "subscriptions": { 222 | "type": "chips", 223 | "label": "additional subscriptions", 224 | "delimiter": ",", 225 | "xs": 12, 226 | "sm": 12, 227 | "md": 12, 228 | "lg": 12, 229 | "xl": 12 230 | } 231 | } 232 | } 233 | } 234 | } -------------------------------------------------------------------------------- /admin/jsonCustom.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": { 3 | "topic": { 4 | "type": "text", 5 | "label": "Topic", 6 | "help": "(without prefix)", 7 | "xs": 12, 8 | "sm": 12, 9 | "md": 6, 10 | "lg": 4, 11 | "xl": 4 12 | }, 13 | "_publish": { 14 | "newLine": true, 15 | "type": "header", 16 | "size": 2, 17 | "text": "publish", 18 | "xs": 12, 19 | "sm": 12, 20 | "md": 6, 21 | "lg": 4, 22 | "xl": 4 23 | }, 24 | "publish": { 25 | "newLine": true, 26 | "label": "enabled", 27 | "type": "checkbox", 28 | "default": false, 29 | "xs": 12, 30 | "sm": 12, 31 | "md": 6, 32 | "lg": 4, 33 | "xl": 4 34 | }, 35 | "pubChangesOnly": { 36 | "disabled": "!data.publish", 37 | "label": "changes only", 38 | "type": "checkbox", 39 | "default": false, 40 | "xs": 12, 41 | "sm": 12, 42 | "md": 6, 43 | "lg": 4, 44 | "xl": 4 45 | }, 46 | "pubAsObject": { 47 | "disabled": "!data.publish", 48 | "label": "as object", 49 | "type": "checkbox", 50 | "default": false, 51 | "xs": 12, 52 | "sm": 12, 53 | "md": 6, 54 | "lg": 4, 55 | "xl": 4 56 | }, 57 | "qos": { 58 | "disabled": "!data.publish", 59 | "label": "QoS", 60 | "type": "select", 61 | "default": false, 62 | "options": [ 63 | { 64 | "label": "At most once - 0", 65 | "value": 0 66 | }, 67 | { 68 | "label": "At least once - 1", 69 | "value": 1 70 | }, 71 | { 72 | "label": "Exactly once - 2", 73 | "value": 2 74 | } 75 | ], 76 | "xs": 12, 77 | "sm": 12, 78 | "md": 6, 79 | "lg": 4, 80 | "xl": 4 81 | }, 82 | "retain": { 83 | "disabled": "!data.publish", 84 | "label": "retain", 85 | "type": "checkbox", 86 | "default": false, 87 | "xs": 12, 88 | "sm": 12, 89 | "md": 6, 90 | "lg": 4, 91 | "xl": 4 92 | }, 93 | "_subscribe": { 94 | "newLine": true, 95 | "type": "header", 96 | "size": 2, 97 | "text": "subscribe", 98 | "xs": 12, 99 | "sm": 12, 100 | "md": 6, 101 | "lg": 4, 102 | "xl": 4 103 | }, 104 | "subscribe": { 105 | "newLine": true, 106 | "label": "enabled", 107 | "type": "checkbox", 108 | "default": false, 109 | "xs": 12, 110 | "sm": 12, 111 | "md": 6, 112 | "lg": 4, 113 | "xl": 4 114 | }, 115 | "subChangesOnly": { 116 | "disabled": "!data.subscribe", 117 | "label": "changes only", 118 | "type": "checkbox", 119 | "default": false, 120 | "xs": 12, 121 | "sm": 12, 122 | "md": 6, 123 | "lg": 4, 124 | "xl": 4 125 | }, 126 | "subAsObject": { 127 | "disabled": "!data.subscribe", 128 | "label": "as object", 129 | "type": "checkbox", 130 | "default": false, 131 | "xs": 12, 132 | "sm": 12, 133 | "md": 6, 134 | "lg": 4, 135 | "xl": 4 136 | }, 137 | "subQos": { 138 | "disabled": "!data.subscribe", 139 | "label": "QoS", 140 | "type": "select", 141 | "default": false, 142 | "options": [ 143 | { 144 | "label": "At most once - 0", 145 | "value": 0 146 | }, 147 | { 148 | "label": "At least once - 1", 149 | "value": 1 150 | }, 151 | { 152 | "label": "Exactly once - 2", 153 | "value": 2 154 | } 155 | ], 156 | "xs": 12, 157 | "sm": 12, 158 | "md": 6, 159 | "lg": 4, 160 | "xl": 4 161 | }, 162 | "setAck": { 163 | "disabled": "!data.subscribe", 164 | "label": "ack", 165 | "type": "checkbox", 166 | "default": false, 167 | "xs": 12, 168 | "sm": 12, 169 | "md": 6, 170 | "lg": 4, 171 | "xl": 4 172 | } 173 | } 174 | } -------------------------------------------------------------------------------- /admin/mqtt-client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iobroker-community-adapters/ioBroker.mqtt-client/686b397a4370648ad2972e2e037d71b5f85a0af1/admin/mqtt-client.png -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import config from '@iobroker/eslint-config'; 2 | 3 | export default [ 4 | ...config, 5 | { 6 | // specify files to exclude from linting here 7 | ignores: [ 8 | '.dev-server/', 9 | '.vscode/', 10 | '*.test.js', 11 | 'test/**/*.js', 12 | '*.config.mjs', 13 | 'build', 14 | 'admin/build', 15 | 'admin/words.js', 16 | 'admin/admin.d.ts', 17 | '**/adapter-config.d.ts' 18 | ] 19 | }, 20 | { 21 | rules: { 22 | 'jsdoc/require-jsdoc': 'off', 23 | 'jsdoc/require-param-description': 'off', 24 | }, 25 | }, 26 | ]; -------------------------------------------------------------------------------- /img/dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iobroker-community-adapters/ioBroker.mqtt-client/686b397a4370648ad2972e2e037d71b5f85a0af1/img/dialog.png -------------------------------------------------------------------------------- /img/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iobroker-community-adapters/ioBroker.mqtt-client/686b397a4370648ad2972e2e037d71b5f85a0af1/img/settings.png -------------------------------------------------------------------------------- /io-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "common": { 3 | "name": "mqtt-client", 4 | "version": "3.0.0", 5 | "news": { 6 | "3.0.0": { 7 | "en": "Breaking change: Underscores are not replaced by spaces in the corresponding topic anymore", 8 | "de": "Bruchänderung: Underscores werden nicht mehr durch Leerzeichen im entsprechenden Thema ersetzt", 9 | "ru": "Перерыв: Недооценки больше не заменяются пробелами в соответствующей теме", 10 | "pt": "Mudança de ruptura: Os sublinhados não são substituídos por espaços no tópico correspondente mais", 11 | "nl": "Verandering: Onderscores worden niet meer vervangen door spaties in het bijbehorende onderwerp", 12 | "fr": "Briser le changement : Les sous-scores ne sont plus remplacés par des espaces dans le sujet correspondant", 13 | "it": "Cambiamento di rottura: Gli underscore non sono più sostituiti da spazi nell'argomento corrispondente", 14 | "es": "Cambio de ruptura: Los subscores ya no son reemplazados por espacios en el tema correspondiente", 15 | "pl": "Łamanie zmian: Podkreślenia nie są już zastępowane spacjami w odpowiednim temacie", 16 | "uk": "Зміна розривів: Не замінюють пробіли в відповідній темі", 17 | "zh-cn": "中断更改 : 重点不再被相应主题的空格所取代" 18 | }, 19 | "2.1.0": { 20 | "en": "Adapter requires node.js 20 now.\nAdapter requires js-controller 5.0.19 and admin 6.17.14 now.\nAdapter changed to meet Responsive Design rules.\nDependencies have been updated.", 21 | "de": "Adapter benötigt node.js 20 jetzt.\nAdapter benötigt jetzt js-controller 5.0.19 und admin 6.17.14.\nAdapter geändert, um Responsive Design Regeln zu erfüllen.\nAbhängigkeiten wurden aktualisiert.", 22 | "ru": "Адаптер требует node.js 20.\nАдаптер требует js-контроллер 5.0.19 и admin 6.17.14.\nАдаптер изменился, чтобы соответствовать правилам Responsive Design.\nЗависимость была обновлена.", 23 | "pt": "Adaptador requer node.js 20 agora.\nAdaptador requer js-controller 5.0.19 e admin 6.17.14 agora.\nAdapter mudou para atender às regras de Design Responsável.\nAs dependências foram atualizadas.", 24 | "nl": "Adapter vereist node.js 20 nu.\nAdapter vereist js-controller 5.01.19 en admin 6.17.14 nu.\nAdapter veranderd om te voldoen aan Responsive Design regels.\nAfhankelijkheden zijn bijgewerkt.", 25 | "fr": "Adaptateur nécessite node.js 20 maintenant.\nAdaptateur nécessite js-controller 5.0.19 et admin 6.17.14 maintenant.\nAdaptateur modifié pour respecter les règles de conception réactive.\nLes dépendances ont été actualisées.", 26 | "it": "Adattatore richiede node.js 20 ora.\nAdattatore richiede js-controller 5.0.19 e admin 6.17.14 ora.\nAdattatore modificato per soddisfare le regole di Responsive Design.\nLe dipendenze sono state aggiornate.", 27 | "es": "Adaptador requiere node.js 20 ahora.\nAdaptador requiere js-controller 5.0.19 y admin 6.17.14 ahora.\nAdaptador cambió para cumplir con las reglas de diseño responsable.\nSe han actualizado las dependencias.", 28 | "pl": "Adapter wymaga Node.js 20 teraz.\nAdapter wymaga sterownika js- 5.0.19 i admin 6.17.14 teraz.\nAdapter zmienił się w celu spełnienia zasad Responsive Design.\nZaktualizowano zależność.", 29 | "uk": "Адаптер вимагає node.js 20 тепер.\nАдаптер вимагає js-controller 5.0.19 і admin 6.17.14 тепер.\nПерехідник змінився для дотримання правил адаптивного дизайну.\nОновлено залежність.", 30 | "zh-cn": "适配器现在需要20号节点.\n适配器现在需要js控制器5.0.19和管理员6.17.14.\n适配器更改以满足应变设计规则.\n附属关系已经更新." 31 | }, 32 | "2.0.1": { 33 | "en": "Added missing information in configuration dialog\nFixed type of port configuration to avoid conflicts", 34 | "de": "Fehlende Informationen im Konfigurationsdialog hinzugefügt\nFeste Art der Port-Konfiguration, um Konflikte zu vermeiden", 35 | "ru": "Добавить недостающую информацию в диалог конфигурации\nФиксированный тип конфигурации порта, чтобы избежать конфликтов", 36 | "pt": "Adicionada informação em falta na caixa de diálogo de configuração\nTipo fixo de configuração de porta para evitar conflitos", 37 | "nl": "Ontbrekende informatie toegevoegd in configuratiedialoog\nVast type poortconfiguratie om conflicten te voorkomen", 38 | "fr": "Ajout des informations manquantes dans la boîte de dialogue de configuration\nType de configuration de port fixe pour éviter les conflits", 39 | "it": "Aggiunto le informazioni mancanti nella finestra di dialogo di configurazione\nTipo fisso di configurazione della porta per evitare conflitti", 40 | "es": "Añadido información faltante en el diálogo de configuración\nTipo fijo de configuración portuaria para evitar conflictos", 41 | "pl": "Dodano brakujące informacje w oknie konfiguracji\nPoprawiony typ konfiguracji portu w celu uniknięcia konfliktów", 42 | "uk": "Додано відсутні дані в діалоговому вікні конфігурації\nВиправлено тип конфігурації порту, щоб уникнути конфліктів", 43 | "zh-cn": "在配置对话框中添加缺失的信息\n避免冲突的固定类型端口配置" 44 | }, 45 | "2.0.0": { 46 | "en": "(klein0r) Password is now encrypted - you have to re-renter your password in instance settings!\n(klein0r) Use jsonConfig instead of materialize (for instance settings)", 47 | "de": "(klein0r) Passwort ist jetzt verschlüsselt - Sie müssen Ihr Passwort in Instanzeinstellungen neugeben!\n(klein0r) Verwenden Sie jsonConfig anstelle von materialisieren (z.B. Einstellungen)", 48 | "ru": "(klein0r) Пароль теперь зашифрован - вы должны повторно набрать свой пароль в настройках экземпляра!\n(клеин0р) Используйте jsonConfig вместо материализации (например, настройки)", 49 | "pt": "(klein0r) A senha agora está criptografada - você tem que voltar a digitar sua senha em configurações de instância!\n(klein0r) Use jsonConfig em vez de materializar (por exemplo, configurações)", 50 | "nl": "(klein0r) Wachtwoord wordt nu versleuteld - u moet uw wachtwoord opnieuw verwisselen in instantie instellingen!\n(klein0r) jsonConfig gebruiken in plaats van materialiseren (bijvoorbeeld instellingen)", 51 | "fr": "(klein0r) Le mot de passe est maintenant chiffré - vous devez re-louer votre mot de passe dans les paramètres d'instance!\n(klein0r) Utiliser jsonConfig au lieu de se matérialiser (par exemple paramètres)", 52 | "it": "(klein0r) La password è ora crittografata - è necessario ripristinare la password nelle impostazioni delle istanze!\n(Klein0r) Utilizzare jsonConfig invece di materializzare (per esempio impostazioni)", 53 | "es": "(klein0r) La contraseña está ahora encriptada - tiene que volver a introducir su contraseña en los ajustes de instancia!\n(klein0r) Utilice jsonConfig en lugar de materializar (por ejemplo, ajustes)", 54 | "pl": "(klein0r) Hasło jest teraz zaszyfrowane - musisz ponownie wypożyczyć hasło na przykład w ustawieniach!\n(klein0r) Użyj jsonConfig zamiast materializować (na przykład ustawienia)", 55 | "uk": "(klein0r) Пароль тепер зашифрований - ви повинні повторно опрацювати пароль в налаштуваннях екземпляра!\n(klein0r) Використовуйте jsonConfig замість матеріалізації (наприклад, налаштування)", 56 | "zh-cn": "(klein0r) 密码现已加密 - 您必须在实例设置中重新租赁您的密码 !\n(鸣笛0r) 使用jsonConfig 代替实现( 例如设置)" 57 | }, 58 | "1.8.0": { 59 | "en": "Adapter requires node.js 18 and js-controller >= 5 now\nDependencies have been updated", 60 | "de": "Adapter benötigt node.js 18 und js-controller >= 5 jetzt\nAbhängigkeiten wurden aktualisiert", 61 | "ru": "Адаптер требует node.js 18 и js-controller >= 5 сейчас\nЗависимость обновлена", 62 | "pt": "Adapter requer node.js 18 e js-controller >= 5 agora\nAs dependências foram atualizadas", 63 | "nl": "Adapter vereist node.js 18 en js-controller Nu 5\nAfhankelijkheden zijn bijgewerkt", 64 | "fr": "Adaptateur nécessite node.js 18 et js-controller >= 5 maintenant\nLes dépendances ont été actualisées", 65 | "it": "Adattatore richiede node.js 18 e js-controller >= 5 ora\nLe dipendenze sono state aggiornate", 66 | "es": "Adaptador requiere node.js 18 y js-controller √= 5 ahora\nSe han actualizado las dependencias", 67 | "pl": "Adapter wymaga node.js 18 i sterownika js- > = 5 teraz\nZaktualizowano zależności", 68 | "uk": "Адаптер вимагає node.js 18 і js-controller >= 5 тепер\nЗалежність було оновлено", 69 | "zh-cn": "适配器需要节点.js 18和js控制器 QQ 现在5号\n依赖关系已更新" 70 | }, 71 | "1.7.0": { 72 | "en": "Dependencies have been updated\nAdapter requires nodejs 16 now", 73 | "de": "Abhängigkeiten wurden aktualisiert\nAdapter benötigt jetzt Nodejs 16", 74 | "ru": "В зависимости были обновлены\nАдаптер требует nodejs 16 сейчас", 75 | "pt": "As dependências foram atualizadas\nAdapter requer nodejs 16 agora", 76 | "nl": "Afhankelijkheid\nAdapter vereist nodej 16 nu", 77 | "fr": "Les dépendances ont été mises à jour\nAdaptateur nécessite nodejs 16 maintenant", 78 | "it": "Le dipendenze sono state aggiornate\nAdattatore richiede nodejs 16 ora", 79 | "es": "Se han actualizado las dependencias\nAdaptador requiere nodejs 16 ahora", 80 | "pl": "Zależności zostały zaktualizowane\nAdapter wymaga już 16 węzłów", 81 | "uk": "Оновлено залежність\nАдаптер вимагає nodejs 16 тепер", 82 | "zh-cn": "已更新了属地\n道歉现在需要16个不听众。" 83 | }, 84 | "1.6.5": { 85 | "en": "prevent crash cases on invalid subscribe", 86 | "de": "verhindern crash-fälle bei ungültiger anmeldung", 87 | "ru": "предотвратить случаи аварии на недействительной подписке", 88 | "pt": "prevenir casos de acidente em assinatura inválida", 89 | "nl": "verhinder crash gevallen op invalide subschrijving", 90 | "fr": "prévenir les cas de crash sur les abonnés invalides", 91 | "it": "prevenire casi di crash su abbonamento non valido", 92 | "es": "evitar los casos de fallo en invalidez", 93 | "pl": "zapobieganie wypadków w przypadku nielegalnego subskrypcji", 94 | "uk": "запобігання аварійних випадків на недійсній підписці", 95 | "zh-cn": "防止无效同意的事故" 96 | } 97 | }, 98 | "titleLang": { 99 | "en": "MQTT client", 100 | "de": "MQTT-Client", 101 | "ru": "MQTT Клиент", 102 | "pt": "Cliente MQTT", 103 | "nl": "MQTT-klant", 104 | "fr": "Client MQTT", 105 | "it": "Client MQTT", 106 | "es": "Cliente MQTT", 107 | "pl": "Klient MQTT", 108 | "uk": "Клієнт MQTT", 109 | "zh-cn": "MQTT客户端" 110 | }, 111 | "desc": { 112 | "en": "Syncing with MQTT Brokers", 113 | "de": "Synchronisierung mit MQTT-Brokern", 114 | "ru": "Синхронизация с брокерами MQTT", 115 | "pt": "Sincronização com corretores da MQTT", 116 | "nl": "Synchroniseren met MQTT-makelaars", 117 | "fr": "Synchronisation avec les courtiers MQTT", 118 | "it": "Sincronizzazione con i broker MQTT", 119 | "es": "Sincronización con intermediarios MQTT", 120 | "pl": "Synchronizacja z brokerami MQTT", 121 | "uk": "Синхронізація з MQTT Brokers", 122 | "zh-cn": "与MQTT经纪人同步" 123 | }, 124 | "authors": [ 125 | "mcm1957 ", 126 | "Pmant ", 127 | "algar42 ", 128 | "Matthias Kleine " 129 | ], 130 | "keywords": [ 131 | "mqtt", 132 | "syncing", 133 | "data", 134 | "subscribe", 135 | "publish" 136 | ], 137 | "licenseInformation": { 138 | "license": "MIT", 139 | "type": "free" 140 | }, 141 | "platform": "Javascript/Node.js", 142 | "icon": "mqtt-client.png", 143 | "enabled": true, 144 | "extIcon": "https://raw.githubusercontent.com/iobroker-community-adapters/ioBroker.mqtt-client/master/admin/mqtt-client.png", 145 | "readme": "https://github.com/iobroker-community-adapters/ioBroker.mqtt-client/blob/master/README.md", 146 | "loglevel": "info", 147 | "tier": 1, 148 | "mode": "daemon", 149 | "type": "protocols", 150 | "compact": true, 151 | "connectionType": "local", 152 | "dataSource": "push", 153 | "adminUI": { 154 | "custom": "json", 155 | "config": "json" 156 | }, 157 | "messagebox": true, 158 | "supportCustoms": true, 159 | "supportStopInstance": true, 160 | "preserveSettings": "custom", 161 | "dependencies": [ 162 | { 163 | "js-controller": ">=5.0.19" 164 | } 165 | ], 166 | "globalDependencies": [ 167 | { 168 | "admin": ">=6.17.14" 169 | } 170 | ], 171 | "plugins": { 172 | "sentry": { 173 | "dsn": "https://0bce9946ef3348dc9ef31ee6442370f2@sentry.iobroker.net/142" 174 | } 175 | }, 176 | "messages": [ 177 | { 178 | "condition": { 179 | "operand": "and", 180 | "rules": [ 181 | "oldVersion<2.0.0", 182 | "newVersion>=2.0.0" 183 | ] 184 | }, 185 | "title": { 186 | "en": "Passwords are now stored encrypted", 187 | "de": "Passwörter werden jetzt verschlüsselt gespeichert", 188 | "ru": "Пароли теперь хранятся зашифрованными", 189 | "pt": "As senhas são agora armazenadas criptografadas", 190 | "nl": "Wachtwoorden worden nu versleuteld opgeslagen", 191 | "fr": "Les mots de passe sont maintenant stockés cryptés", 192 | "it": "Le password sono ora memorizzate crittografate", 193 | "es": "Las contraseñas ahora se almacenan encriptadas", 194 | "pl": "Hasła są teraz przechowywane zaszyfrowane", 195 | "uk": "Паролі тепер зберігаються зашифровані", 196 | "zh-cn": "密码现已加密" 197 | }, 198 | "text": { 199 | "en": "You have to re-renter your MQTT password (if used) after the adapter upgrade. Open the instance configuration, enter your password and safe the configuration. The password is stored encrypted afterwards.", 200 | "de": "Sie müssen Ihr MQTT-Passwort (falls verwendet) nach dem Adapter-Upgrade neu eingeben. Öffnen Sie die Instanzkonfiguration, geben Sie Ihr Passwort ein und sichern Sie die Konfiguration. Das Passwort wird danach verschlüsselt gespeichert.", 201 | "ru": "Вы должны повторно нанять свой пароль MQTT (если используется) после обновления адаптера. Откройте настройку экземпляра, введите пароль и обезопасите конфигурацию. После этого пароль зашифрован.", 202 | "pt": "Você tem que reinserir sua senha MQTT (se usado) após a atualização do adaptador. Abra a configuração da instância, insira sua senha e proteja a configuração. A senha é armazenada criptografada depois.", 203 | "nl": "U moet uw MQTT-wachtwoord (indien gebruikt) na de upgrade van de adapter opnieuw verhuren. Open de instantieconfiguratie, voer uw wachtwoord in en beveilig de configuratie. Het wachtwoord wordt daarna versleuteld opgeslagen.", 204 | "fr": "Vous devez renouveler votre mot de passe MQTT (si utilisé) après la mise à niveau de l'adaptateur. Ouvrez la configuration de l'instance, entrez votre mot de passe et sécurisez la configuration. Le mot de passe est enregistré crypté par la suite.", 205 | "it": "È necessario ripristinare la password MQTT (se utilizzata) dopo l'aggiornamento dell'adattatore. Aprire la configurazione dell'istanza, inserire la password e proteggere la configurazione. La password viene archiviata successivamente.", 206 | "es": "Usted tiene que volver a introducir su contraseña MQTT (si se utiliza) después de la actualización del adaptador. Abra la configuración de instancia, introduzca su contraseña y asegúrese de la configuración. La contraseña se almacena encriptada después.", 207 | "pl": "Po aktualizacji adaptera należy ponownie wypożyczyć hasło MQTT (jeśli jest używane). Otwórz konfigurację instancji, wprowadź hasło i zabezpiecz konfigurację. Hasło jest później zapisywane.", 208 | "uk": "Після оновлення адаптера потрібно переоцінити пароль MQTT (якщо використовується) Відкрийте налаштування екземпляра, введіть пароль і встановіть налаштування. Зашифрований паролем після.", 209 | "zh-cn": "在适配器升级后,您必须重新租赁您的 MQTT 密码( 如果使用) 。 打开实例配置, 输入密码并安全配置 。 密码在之后被加密 ." 210 | }, 211 | "level": "warn", 212 | "buttons": [ 213 | "agree", 214 | "cancel" 215 | ] 216 | }, 217 | { 218 | "condition": { 219 | "operand": "and", 220 | "rules": [ 221 | "oldVersion<3.0.0", 222 | "newVersion>=3.0.0" 223 | ] 224 | }, 225 | "title": { 226 | "en": "Your topics may change", 227 | "de": "Deine Topics könnten sich ändern", 228 | "ru": "Ваши темы могут измениться", 229 | "pt": "Seus tópicos podem mudar", 230 | "nl": "Uw onderwerpen kunnen veranderen", 231 | "fr": "Vos sujets peuvent changer", 232 | "it": "I tuoi argomenti possono cambiare", 233 | "es": "Sus temas pueden cambiar", 234 | "pl": "Twoje tematy mogą się zmienić", 235 | "uk": "Ваша тема може змінитися", 236 | "zh-cn": "你的主题可能会改变" 237 | }, 238 | "text": { 239 | "en": "Underscores in object IDs have been replaced by spaces in the corresponding topic. This has been changed in version 3.0.0. Check if any of your object IDs contains an underscore which might lead to another topic.", 240 | "de": "Unterstriche in Objekt-IDs wurden bisher durch Leerzeichen im entsprechenden Topic ersetzt. Dies wurde in Version 3.0.0 geändert. Überprüfe, ob eine Deiner Objekt-IDs einen Unterstrich enthält, welcher zu einem anderen Topic führen könnte.", 241 | "ru": "Заполнители в ID объектов были заменены пробелами в соответствующей теме. Это было изменено в версии 3.0.0. Проверьте, содержит ли какой-либо из ваших идентификаторов объекта подчеркивание, которое может привести к другой теме.", 242 | "pt": "Os sublinhados em IDs de objeto foram substituídos por espaços no tópico correspondente. Isso foi alterado na versão 3.0.0. Verifique se qualquer um dos seus IDs de objeto contém um sublinhado que pode levar a outro tópico.", 243 | "nl": "Onderscores in object-ID's zijn vervangen door spaties in het overeenkomstige onderwerp. Dit is veranderd in versie 3.0.0. Controleer of een van uw object-ID's een underscore bevat die kan leiden tot een ander onderwerp.", 244 | "fr": "Les sous-scores dans les ID d'objet ont été remplacés par des espaces dans le sujet correspondant. Ceci a été modifié dans la version 3.0.0. Vérifiez si l'un de vos identifiants d'objet contient un souligné qui pourrait conduire à un autre sujet.", 245 | "it": "Gli underscore in ID oggetto sono stati sostituiti da spazi nell'argomento corrispondente. Questo è stato modificato nella versione 3.0.0. Controlla se uno dei tuoi ID oggetti contiene un underscore che potrebbe portare a un altro argomento.", 246 | "es": "Los subscores en identificaciones de objetos han sido reemplazados por espacios en el tema correspondiente. Esto ha sido cambiado en la versión 3.0.0. Comprueba si alguna de tus identificaciones de objetos contiene un subrayado que podría llevar a otro tema.", 247 | "pl": "Podkreślenia w identyfikatorach obiektów zostały zastąpione spacjami w odpowiednim temacie. Zostało to zmienione w wersji 3.0.0. Sprawdź, czy któraś z identyfikatorów obiektu zawiera podkreślenie, które może prowadzić do innego tematu.", 248 | "uk": "Підкреслення в ідентифікаторах об'єкта було замінено на пробіли в відповідній темі. Це було змінено в версії 3.0.0. Перевірити, якщо будь-який з ідентифікаторів об'єкта містить підряд, який може призвести до іншої теми.", 249 | "zh-cn": "对象ID中的强调已被相应主题的空格所取代. 在3.0.0版本中,这一说法有所改变。 检查您的对象ID是否包含一个可能导致另一个主题的下划线 ." 250 | }, 251 | "level": "warn", 252 | "buttons": [ 253 | "agree", 254 | "cancel" 255 | ] 256 | } 257 | ] 258 | }, 259 | "protectedNative": [ 260 | "username", 261 | "password" 262 | ], 263 | "encryptedNative": [ 264 | "password" 265 | ], 266 | "native": { 267 | "host": "", 268 | "port": 1883, 269 | "mqttVersion": 4, 270 | "websocket": false, 271 | "ssl": false, 272 | "rejectUnauthorized": true, 273 | "clientId": "iobroker-mqtt-client", 274 | "username": "", 275 | "password": "", 276 | "onConnectTopic": "", 277 | "onConnectMessage": "", 278 | "onDisconnectTopic": "", 279 | "onDisconnectMessage": "", 280 | "lastWillTopic": "", 281 | "lastWillMessage": "", 282 | "subscriptions": "", 283 | "reconnectPeriod": 30000, 284 | "outbox": "ioBroker", 285 | "inbox": "ioBroker", 286 | "enabled": false, 287 | "publish": false, 288 | "pubChangesOnly": false, 289 | "pubAsObject": false, 290 | "qos": 0, 291 | "retain": false, 292 | "subscribe": false, 293 | "subChangesOnly": false, 294 | "subAsObject": false, 295 | "subQos": 0, 296 | "setAck": true 297 | }, 298 | "instanceObjects": [ 299 | { 300 | "_id": "info", 301 | "type": "channel", 302 | "common": { 303 | "name": { 304 | "en": "Information", 305 | "de": "Informationen", 306 | "ru": "Информация", 307 | "pt": "Informação", 308 | "nl": "Informatie", 309 | "fr": "Informations", 310 | "it": "Informazioni", 311 | "es": "Información", 312 | "pl": "Informacje", 313 | "uk": "Інформація", 314 | "zh-cn": "资料" 315 | } 316 | }, 317 | "native": {} 318 | }, 319 | { 320 | "_id": "info.connection", 321 | "type": "state", 322 | "common": { 323 | "name": { 324 | "en": "Connected to MQTT broker", 325 | "de": "Mit MQTT-Broker verbunden", 326 | "ru": "Подключен к брокеру MQTT", 327 | "pt": "Conectado ao corretor MQTT", 328 | "nl": "Verbonden met MQTT-makelaar", 329 | "fr": "Connecté au courtier MQTT", 330 | "it": "Collegato al broker MQTT", 331 | "es": "Conectado al bróker de MQTT", 332 | "pl": "Połączony z pośrednikiem MQTT", 333 | "uk": "Підключення до брокера MQTT", 334 | "zh-cn": "已连接到 MQTT 经纪人" 335 | }, 336 | "type": "boolean", 337 | "role": "indicator.connected", 338 | "read": true, 339 | "write": false, 340 | "def": false 341 | }, 342 | "native": {} 343 | } 344 | ] 345 | } 346 | -------------------------------------------------------------------------------- /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 { }; 20 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const utils = require('@iobroker/adapter-core'); 4 | const mqtt = require('mqtt'); 5 | 6 | const _context = { 7 | custom: {}, //cache object's mqtt-client settings 8 | subTopics: {}, //subscribed mqtt topics 9 | topic2id: {}, //maps mqtt topics to ioBroker ids 10 | addTopics: {}, //additional mqtt topics to subscribe to 11 | addedTopics: {}, //received mqtt topics that created a new object (addTopics) 12 | }; 13 | 14 | class MqttClient extends utils.Adapter { 15 | constructor(options) { 16 | super({ 17 | ...options, 18 | name: 'mqtt-client', 19 | }); 20 | 21 | this._connected = false; 22 | this.client = null; 23 | this._subscribes = []; 24 | this.adapterFinished = false; 25 | 26 | this.on('ready', this.onReady.bind(this)); 27 | this.on('objectChange', this.onObjectChange.bind(this)); 28 | this.on('stateChange', this.onStateChange.bind(this)); 29 | this.on('message', this.onMessage.bind(this)); 30 | this.on('unload', this.onUnload.bind(this)); 31 | } 32 | 33 | connect() { 34 | this.log.info('connected to broker'); 35 | 36 | if (!this._connected) { 37 | this._connected = true; 38 | this.setState('info.connection', true, true); 39 | } 40 | 41 | if (this.config.onConnectTopic && this.config.onConnectMessage) { 42 | const topic = this.config.onConnectTopic; 43 | 44 | this.client.publish( 45 | this.topicAddPrefixOut(topic), 46 | this.config.onConnectMessage, 47 | { qos: 2, retain: true }, 48 | () => 49 | this.log.debug( 50 | `successfully published ${JSON.stringify({ 51 | topic: topic, 52 | message: this.config.onConnectMessage, 53 | })}`, 54 | ), 55 | ); 56 | } 57 | 58 | const subTopics = _context.subTopics; 59 | const addTopics = _context.addTopics; 60 | 61 | //initially subscribe to topics 62 | if (Object.keys(subTopics).length) { 63 | this.subscribe(subTopics, () => this.log.debug(`subscribed to: ${JSON.stringify(subTopics)}`)); 64 | } 65 | 66 | if (Object.keys(addTopics).length) { 67 | this.subscribe(addTopics, () => 68 | this.log.debug(`subscribed to additional topics: ${JSON.stringify(addTopics)}`), 69 | ); 70 | } 71 | } 72 | 73 | reconnect() { 74 | this.log.debug('trying to reconnect to broker'); 75 | } 76 | 77 | disconnect() { 78 | if (this._connected) { 79 | this._connected = false; 80 | this.setState('info.connection', false, true); 81 | } 82 | this.log.warn('disconnected from broker'); 83 | } 84 | 85 | offline() { 86 | if (this._connected) { 87 | this._connected = false; 88 | this.setState('info.connection', false, true); 89 | } 90 | this.log.warn('client offline'); 91 | } 92 | 93 | error(err) { 94 | this.log.warn(`client error: ${err}`); 95 | } 96 | 97 | message(topic, msg) { 98 | const custom = _context.custom; 99 | const topic2id = _context.topic2id; 100 | const addedTopics = _context.addedTopics; 101 | msg = msg.toString(); 102 | 103 | topic = this.topicRemovePrefixIn(topic); 104 | 105 | //if topic2id[topic] does not exist automatically convert topic to id with guiding adapter namespace 106 | const id = topic2id[topic] || this.convertTopic2ID(topic); 107 | 108 | this.log.debug(`received message ${msg} for id ${id}=>${JSON.stringify(custom[id])}`); 109 | 110 | if (topic2id[topic] && custom[id] && custom[id].subscribe) { 111 | if (custom[id].subAsObject) { 112 | this.setStateObj(id, msg); 113 | } else { 114 | this.setStateVal(id, msg); 115 | } 116 | } else if (!addedTopics[topic]) { 117 | //prevents object from being recreated while first creation has not finished 118 | addedTopics[topic] = null; 119 | const obj = { 120 | type: 'state', 121 | role: 'text', 122 | common: { 123 | name: id.split('.').pop(), 124 | type: 'mixed', 125 | read: true, 126 | write: true, 127 | desc: 'created from topic', 128 | custom: {}, 129 | }, 130 | native: { 131 | topic, 132 | }, 133 | }; 134 | obj.common.custom[this.namespace] = { 135 | enabled: true, 136 | topic, 137 | publish: false, 138 | pubChangesOnly: false, 139 | pubAsObject: false, 140 | qos: 0, 141 | retain: false, 142 | subscribe: true, 143 | subChangesOnly: false, 144 | subAsObject: false, 145 | subQos: 0, 146 | setAck: true, 147 | }; 148 | this.setObjectNotExists(id, obj, () => this.log.debug(`created and subscribed to new state: ${id}`)); 149 | //onObjectChange should now receive this object 150 | } else { 151 | this.log.debug('state already exists'); 152 | } 153 | } 154 | 155 | setStateObj(id, msg) { 156 | this.getForeignState(id, (err, state) => { 157 | try { 158 | const obj = JSON.parse(msg); 159 | this.log.debug(JSON.stringify(obj)); 160 | 161 | if (Object.prototype.hasOwnProperty.call(obj, 'val')) { 162 | const custom = _context.custom; 163 | if (Object.prototype.hasOwnProperty.call(obj, 'ts') && state && obj.ts <= state.ts) { 164 | this.log.debug(`object ts not newer than current state ts: ${msg}`); 165 | return false; 166 | } 167 | if (Object.prototype.hasOwnProperty.call(obj, 'lc') && state && obj.lc < state.lc) { 168 | this.log.debug(`object lc not newer than current state lc: ${msg}`); 169 | return false; 170 | } 171 | // todo: !== correct??? 172 | if ( 173 | this.config.inbox === this.config.outbox && 174 | custom[id].publish && 175 | !Object.prototype.hasOwnProperty.call(obj, 'ts') && 176 | !Object.prototype.hasOwnProperty.call(obj, 'lc') && 177 | obj.val !== state.val 178 | ) { 179 | this.log.debug(`object value did not change (loop protection): ${msg}`); 180 | return false; 181 | } 182 | // todo: !== correct??? 183 | if (custom[id].subChangesOnly && obj.val !== state.val) { 184 | this.log.debug(`object value did not change: ${msg}`); 185 | return false; 186 | } 187 | if (custom[id].setAck) { 188 | obj.ack = true; 189 | } 190 | delete obj.from; 191 | this.setForeignState(id, obj); 192 | this.log.debug(`object set (as object) to ${JSON.stringify(obj)}`); 193 | return true; 194 | } 195 | this.log.warn(`no value in object: ${msg}`); 196 | return false; 197 | } catch { 198 | this.log.warn(`could not parse message as object: ${msg}`); 199 | return false; 200 | } 201 | }); 202 | } 203 | 204 | setStateVal(id, msg) { 205 | const custom = _context.custom; 206 | //this.log.debug('state for id: '+ id); 207 | this.getForeignState(id, (err, state) => { 208 | if (state && this.val2String(state.val) === msg) { 209 | //this.log.debug('setVAL: ' + JSON.stringify(state) + '; value: ' + this.val2String(state.val) + '=> ' + msg); 210 | if (this.config.inbox === this.config.outbox && custom[id] && custom[id].publish) { 211 | this.log.debug('value did not change (loop protection)'); 212 | return false; 213 | } else if (custom[id] && custom[id].subChangesOnly) { 214 | this.log.debug('value did not change'); 215 | return false; 216 | } 217 | } 218 | const _state = { val: this.stringToVal(custom, id, msg), ack: custom[id] && custom[id].setAck }; 219 | this.setForeignState(id, _state); 220 | this.log.debug(`value of ${id} set to ${JSON.stringify(_state)}`); 221 | return true; 222 | }); 223 | } 224 | 225 | publishState(id, state) { 226 | if (this.client) { 227 | const custom = _context.custom; 228 | const settings = custom[id]; 229 | if (!settings || !state) { 230 | return false; 231 | } 232 | if (custom[id].pubState && settings.pubChangesOnly && state.ts !== state.lc) { 233 | return false; 234 | } 235 | 236 | custom[id].pubState = state; 237 | this.log.debug(`publishing ${id}`); 238 | 239 | const topic = settings.topic; 240 | 241 | const message = settings.pubAsObject ? JSON.stringify(state) : this.val2String(state.val); 242 | 243 | this.client.publish( 244 | this.topicAddPrefixOut(topic), 245 | message, 246 | { qos: settings.qos, retain: settings.retain }, 247 | () => 248 | this.log.debug( 249 | `successfully published ${id}: ${JSON.stringify({ topic: topic, message: message })}`, 250 | ), 251 | ); 252 | 253 | return true; 254 | } 255 | } 256 | 257 | topicAddPrefixOut(topic) { 258 | //add outgoing prefix 259 | return this.config.outbox ? `${this.config.outbox}/${topic}` : topic; 260 | } 261 | 262 | topicAddPrefixIn(topic) { 263 | //add outgoing prefix 264 | return this.config.inbox ? `${this.config.inbox}/${topic}` : topic; 265 | } 266 | 267 | topicRemovePrefixIn(topic) { 268 | if (this.config.inbox && topic.substring(0, this.config.inbox.length) === this.config.inbox) { 269 | topic = topic.substr(this.config.inbox.length + 1); 270 | } 271 | return topic; 272 | } 273 | 274 | unpublish(id) { 275 | if (this.client) { 276 | const custom = _context.custom; 277 | const settings = custom[id]; 278 | if (!settings) { 279 | return false; 280 | } 281 | 282 | custom[id].pubState = null; 283 | this.log.debug(`unpublishing ${id}`); 284 | 285 | const topic = settings.topic; 286 | 287 | this.client.publish(this.topicAddPrefixOut(topic), null, { qos: settings.qos, retain: false }, () => 288 | this.log.debug(`successfully unpublished ${id}`), 289 | ); 290 | 291 | return true; 292 | } 293 | } 294 | 295 | subscribe(topics, callback) { 296 | if (this.client) { 297 | const subTopics = {}; 298 | 299 | for (const key of Object.keys(topics)) { 300 | subTopics[this.topicAddPrefixIn(key)] = { qos: topics[key] }; 301 | } 302 | 303 | this.log.debug( 304 | `trying to subscribe to ${Object.keys(subTopics).length} topics: ${JSON.stringify(subTopics)}`, 305 | ); 306 | this.client.subscribe(subTopics, (err, granted) => { 307 | if (!err) { 308 | this.log.debug(`successfully subscribed to ${granted.length} topics`); 309 | } else { 310 | this.log.debug(`error subscribing to ${Object.keys(subTopics).length} topics`); 311 | } 312 | callback(); 313 | }); 314 | } 315 | } 316 | 317 | unsubscribe(topic, callback) { 318 | this.client && this.client.unsubscribe(this.topicAddPrefixIn(topic), callback); 319 | } 320 | 321 | async iobSubscribe(id, callback) { 322 | if (!this._subscribes.includes(id)) { 323 | this._subscribes.push(id); 324 | this._subscribes.sort(); 325 | try { 326 | await this.subscribeForeignStatesAsync(id); 327 | } catch (e) { 328 | this.log.error(`Cannot subscribe to "${id}": ${e.message}`); 329 | } 330 | if (typeof callback === 'function') { 331 | callback(); 332 | } 333 | } 334 | } 335 | 336 | iobUnsubscribe(id) { 337 | const pos = this._subscribes.indexOf(id); 338 | if (pos !== -1) { 339 | this._subscribes.splice(pos, 1); 340 | this.unsubscribeForeignStates(id); 341 | } 342 | } 343 | 344 | val2String(val) { 345 | return val === null ? 'null' : val === undefined ? 'undefined' : val.toString(); 346 | } 347 | 348 | stringToVal(custom, id, val) { 349 | if (val === 'undefined') { 350 | return undefined; 351 | } 352 | if (val === 'null') { 353 | return null; 354 | } 355 | if (!custom[id] || !custom[id].type || custom[id].type === 'string' || custom[id].type === 'mixed') { 356 | return val; 357 | } 358 | 359 | if (custom[id].type === 'number') { 360 | if (val === true || val === 'true') { 361 | val = 1; 362 | } 363 | if (val === false || val === 'false') { 364 | val = 0; 365 | } 366 | if (typeof val.toString === 'function') { 367 | val = val.toString().replace(',', '.'); 368 | } 369 | val = parseFloat(val) || 0; 370 | return val; 371 | } 372 | if (custom[id].type === 'boolean') { 373 | if (val === '1' || val === 'true') { 374 | val = true; 375 | } 376 | if (val === '0' || val === 'false') { 377 | val = false; 378 | } 379 | return !!val; 380 | } 381 | return val; 382 | } 383 | 384 | convertID2Topic(id, namespace) { 385 | let topic; 386 | 387 | //if necessary remove namespace before converting, e.g. "mqtt-client.0..." 388 | if (id.startsWith(namespace)) { 389 | topic = id.substring(namespace.length + 1); 390 | } else { 391 | topic = id; 392 | } 393 | 394 | //replace dots with slashes 395 | topic = topic.replace(/\./g, '/'); 396 | return topic; 397 | } 398 | 399 | convertTopic2ID(topic) { 400 | if (!topic) { 401 | return topic; 402 | } 403 | 404 | //replace slashes with dots and spaces with underscores 405 | topic = topic.replace(/\//g, '.').replace(/\s/g, '_'); 406 | 407 | //replace guiding and trailing dot 408 | if (topic[0] === '.') { 409 | topic = topic.substring(1); 410 | } 411 | if (topic[topic.length - 1] === '.') { 412 | topic = topic.substring(0, topic.length - 1); 413 | } 414 | 415 | return topic; 416 | } 417 | 418 | checkSettings(id, custom, aNamespace, qos, subQos) { 419 | custom.topic = custom.topic || this.convertID2Topic(id, aNamespace); 420 | custom.enabled = custom.enabled === true; 421 | custom.publish = custom.publish === true; 422 | custom.pubChangesOnly = custom.pubChangesOnly === true; 423 | custom.pubAsObject = custom.pubAsObject === true; 424 | custom.retain = custom.retain === true; 425 | custom.qos = parseInt(custom.qos || qos, 10) || 0; 426 | 427 | custom.subscribe = custom.subscribe === true; 428 | custom.subChangesOnly = custom.subChangesOnly === true; 429 | custom.subAsObject = custom.subAsObject === true; 430 | custom.setAck = custom.setAck !== false; 431 | custom.subQos = parseInt(custom.subQos || subQos, 10) || 0; 432 | } 433 | 434 | getObjects(adapter, ids, callback, _result) { 435 | _result = _result || {}; 436 | 437 | if (!ids || !ids.length) { 438 | callback(_result); 439 | } else { 440 | adapter.getForeignObject(ids.shift(), (err, obj) => { 441 | if (obj) { 442 | _result[obj._id] = obj; 443 | } 444 | setImmediate(adapter.getObjects, adapter, ids, callback, _result); 445 | }); 446 | } 447 | } 448 | 449 | /** 450 | * Is called when databases are this. and adapter received configuration. 451 | */ 452 | onReady() { 453 | this.getState('info.connection', (err, state) => { 454 | (!state || state.val) && this.setState('info.connection', false, true); 455 | 456 | this.config.inbox = this.config.inbox.trim(); 457 | this.config.outbox = this.config.outbox.trim(); 458 | 459 | if (this.config.host && this.config.host !== '') { 460 | const custom = _context.custom; 461 | const subTopics = _context.subTopics; 462 | const topic2id = _context.topic2id; 463 | const addTopics = _context.addTopics; 464 | 465 | const protocol = `${this.config.websocket ? 'ws' : 'mqtt'}${this.config.ssl ? 's' : ''}`; 466 | const _url = `${protocol}://${ 467 | this.config.username ? `${this.config.username}:${this.config.password}@` : '' 468 | }${this.config.host}${this.config.port ? `:${this.config.port}` : ''}?clientId=${this.config.clientId}`; 469 | const __url = `${protocol}://${ 470 | this.config.username ? `${this.config.username}:*******************@` : '' 471 | }${this.config.host}${this.config.port ? `:${this.config.port}` : ''}?clientId=${this.config.clientId}`; 472 | 473 | this.getObjectView('system', 'custom', {}, (err, doc) => { 474 | const ids = []; 475 | if (doc?.rows) { 476 | for (let i = 0, l = doc.rows.length; i < l; i++) { 477 | const cust = doc.rows[i].value; 478 | if (cust && cust[this.namespace] && cust[this.namespace].enabled) { 479 | ids.push(doc.rows[i].id); 480 | } 481 | } 482 | } 483 | 484 | // we need type of object 485 | this.getObjects(this, ids, objs => { 486 | for (const id of Object.keys(objs)) { 487 | custom[id] = objs[id].common.custom[this.namespace]; 488 | custom[id].type = objs[id].common.type; 489 | 490 | this.checkSettings(id, custom[id], this.namespace, this.config.qos, this.config.subQos); 491 | 492 | if (custom[id].subscribe) { 493 | subTopics[custom[id].topic] = custom[id].subQos; 494 | topic2id[custom[id].topic] = id; 495 | } 496 | 497 | // subscribe on changes 498 | if (custom[id].enabled) { 499 | this.iobSubscribe(id); 500 | } 501 | 502 | this.log.debug( 503 | `enabled syncing of ${id} (publish/subscribe:${custom[id].publish.toString()}/${custom[ 504 | id 505 | ].subscribe.toString()})`, 506 | ); 507 | } 508 | this.log.debug(`complete Custom: ${JSON.stringify(custom)}`); 509 | 510 | if (this.config.subscriptions) { 511 | for (const topic of this.config.subscriptions.split(',')) { 512 | if (topic && topic.trim()) { 513 | addTopics[topic.trim()] = 0; // QoS 514 | } 515 | } 516 | } 517 | this.log.debug(`found ${Object.keys(addTopics).length} additional topic to subscribe to`); 518 | 519 | let will = undefined; 520 | 521 | if (this.config.lastWillTopic && this.config.lastWillMessage) { 522 | this.log.info( 523 | `Try to connect to ${__url}, protocol version ${this.config.mqttVersion} with lwt "${this.config.lastWillTopic}"`, 524 | ); 525 | 526 | will = { 527 | topic: this.topicAddPrefixOut(this.config.lastWillTopic), 528 | payload: this.config.lastWillMessage, 529 | qos: 2, 530 | retain: true, 531 | }; 532 | } else { 533 | this.log.info(`Try to connect to ${__url}`); 534 | } 535 | const mqttVersion = Number.parseInt(this.config.mqttVersion || 4); 536 | try { 537 | this.client = mqtt.connect(_url, { 538 | host: this.config.host, 539 | port: this.config.port, 540 | protocolVersion: mqttVersion, 541 | ssl: this.config.ssl, 542 | rejectUnauthorized: this.config.rejectUnauthorized, 543 | reconnectPeriod: this.config.reconnectPeriod, 544 | username: this.config.username, 545 | password: this.config.password, 546 | clientId: this.config.clientId, 547 | clean: true, 548 | will, 549 | }); 550 | } catch (e) { 551 | this.log.error(e); 552 | this.finish(() => { 553 | setTimeout(() => (this.terminate ? this.terminate() : process.exit(0)), 200); 554 | }); 555 | return; 556 | } 557 | 558 | this.client.on('connect', this.connect.bind(this)); 559 | this.client.on('reconnect', this.reconnect.bind(this)); 560 | this.client.on('disconnect', this.disconnect.bind(this)); 561 | this.client.on('offline', this.offline.bind(this)); 562 | this.client.on('message', this.message.bind(this)); 563 | this.client.on('error', this.error.bind(this)); 564 | }); 565 | }); 566 | } 567 | 568 | this.subscribeForeignObjects('*'); 569 | }); 570 | } 571 | 572 | /** 573 | * Is called when adapter shuts down - callback has to be called under any circumstances! 574 | * 575 | * @param callback 576 | */ 577 | finish(callback) { 578 | if (this.adapterFinished) { 579 | return; 580 | } 581 | if (this.client && this.config.onDisconnectTopic && this.config.onDisconnectMessage) { 582 | const topic = this.config.onDisconnectTopic; 583 | 584 | this.log.info(`Disconnecting with message "${this.config.onDisconnectMessage}" on topic "${topic}"`); 585 | this.client.publish( 586 | this.topicAddPrefixOut(topic), 587 | this.config.onDisconnectMessage, 588 | { qos: 2, retain: true }, 589 | () => { 590 | this.log.debug( 591 | `successfully published ${JSON.stringify({ 592 | topic: topic, 593 | message: this.config.onDisconnectMessage, 594 | })}`, 595 | ); 596 | this.end(callback); 597 | }, 598 | ); 599 | } else { 600 | this.end(callback); 601 | } 602 | } 603 | 604 | /** 605 | * Is called when adapter shuts down - callback has to be called under any circumstances! 606 | * 607 | * @param callback 608 | */ 609 | end(callback) { 610 | this.adapterFinished = true; 611 | this.client && 612 | this.client.end(() => { 613 | this.log.debug(`closed client`); 614 | this.setState('info.connection', false, true); 615 | callback && callback(); 616 | }); 617 | } 618 | 619 | /** 620 | * Is called when adapter shuts down - callback has to be called under any circumstances! 621 | * 622 | * @param callback 623 | */ 624 | onUnload(callback) { 625 | try { 626 | this.finish(callback); 627 | } catch { 628 | if (callback) { 629 | callback(); 630 | } 631 | } 632 | } 633 | 634 | /** 635 | * Is called if a subscribed object changes 636 | * 637 | * @param id 638 | * @param obj 639 | */ 640 | onObjectChange(id, obj) { 641 | const custom = _context.custom; 642 | const subTopics = _context.subTopics; 643 | const topic2id = _context.topic2id; 644 | 645 | if (obj?.common?.custom?.[this.namespace]?.enabled) { 646 | custom[id] = obj.common.custom[this.namespace]; 647 | custom[id].type = obj.common.type; 648 | 649 | this.checkSettings(id, custom[id], this.namespace, this.config.qos, this.config.subQos); 650 | 651 | if (custom[id].subscribe) { 652 | subTopics[custom[id].topic] = custom[id].subQos; 653 | topic2id[custom[id].topic] = id; 654 | const sub = {}; 655 | sub[custom[id].topic] = custom[id].subQos; 656 | 657 | this.subscribe(sub, () => { 658 | this.log.debug(`subscribed to ${JSON.stringify(sub)}`); 659 | }); 660 | } else { 661 | delete subTopics[custom[id].topic]; 662 | delete topic2id[custom[id].topic]; 663 | this.iobUnsubscribe(id); 664 | 665 | this.unsubscribe( 666 | custom[id].topic, 667 | () => custom[id] && this.log.debug(`unsubscribed from ${custom[id].topic}`), 668 | ); 669 | } 670 | 671 | if (custom[id].enabled) { 672 | //subscribe to state changes 673 | this.iobSubscribe(id, err => { 674 | //publish state once 675 | if (err || !custom[id].publish) { 676 | return; 677 | } 678 | this.getForeignState(id, (err, state) => { 679 | if (err || !state) { 680 | return; 681 | } 682 | this.log.debug(`publish ${id} once: ${JSON.stringify(state)}`); 683 | this.onStateChange(id, state); 684 | }); 685 | }); 686 | } 687 | 688 | this.log.debug( 689 | `enabled syncing of ${id} (publish/subscribe:${custom[id].publish.toString()}/${custom[ 690 | id 691 | ].subscribe.toString()})`, 692 | ); 693 | } else if (custom[id]) { 694 | const topic = custom[id].topic; 695 | 696 | this.unsubscribe(topic, () => this.log.debug(`unsubscribed from ${topic}`)); 697 | 698 | delete subTopics[custom[id].topic]; 699 | delete topic2id[custom[id].topic]; 700 | 701 | if (custom[id].publish) { 702 | this.iobUnsubscribe(id); 703 | } 704 | 705 | delete custom[id]; 706 | 707 | this.log.debug(`disabled syncing of ${id}`); 708 | } 709 | } 710 | 711 | /** 712 | * Is called if a subscribed state changes 713 | * 714 | * @param id 715 | * @param state 716 | */ 717 | onStateChange(id, state) { 718 | const custom = _context.custom; 719 | 720 | if (custom[id]) { 721 | custom[id].state = state; 722 | 723 | if (custom[id].enabled && custom[id].publish) { 724 | if (!state) { 725 | // The state was deleted/expired, make sure it is no longer retained 726 | this.unpublish(id); 727 | } else if (state.from !== `system.adapter.${this.namespace}`) { 728 | // prevent republishing to same broker 729 | this.publishState(id, state); 730 | } 731 | } 732 | } 733 | } 734 | 735 | /** 736 | * Some message was sent to this instance over message box. Used by email, pushover, text2speech, ... 737 | * Using this method requires "common.message" property to be set to true in io-package.json 738 | * 739 | * @param obj 740 | */ 741 | onMessage(obj) { 742 | if (typeof obj === 'object' && obj.command) { 743 | if (obj.command === 'stopInstance') { 744 | // e.g. send email or pushover or whatever 745 | this.log.info('Stop Instance command received...'); 746 | 747 | this.finish(() => { 748 | this.sendTo(obj.from, obj.command, 'Message received', obj.callback); 749 | setTimeout(() => (this.terminate ? this.terminate() : process.exit(0)), 200); 750 | }); 751 | // Send response in callback if required 752 | // if (obj.callback) this.sendTo(obj.from, obj.command, 'Message received', obj.callback); 753 | } 754 | } 755 | } 756 | } 757 | 758 | // @ts-expect-error parent is a valid property on module 759 | if (module.parent) { 760 | // Export the constructor in compact mode 761 | /** 762 | * @param [options] 763 | */ 764 | module.exports = options => new MqttClient(options); 765 | } else { 766 | // otherwise start the instance directly 767 | new MqttClient(); 768 | } 769 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iobroker.mqtt-client", 3 | "version": "3.0.0", 4 | "description": "Client to connect states to MQTT Broker", 5 | "author": { 6 | "name": "mcm1957", 7 | "email": "mcm57@gmx.at" 8 | }, 9 | "contributors": [ 10 | { 11 | "name": "patrickmo", 12 | "email": "patrickmo@gmx.de" 13 | }, 14 | { 15 | "name": "algar42", 16 | "email": "igor.aleschenkov@gmail.com" 17 | }, 18 | { 19 | "name": "Matthias Kleine", 20 | "email": "info@haus-automatisierung.com" 21 | } 22 | ], 23 | "homepage": "https://github.com/iobroker-community-adapters/ioBroker.mqtt-client", 24 | "license": "MIT", 25 | "keywords": [ 26 | "ioBroker", 27 | "Smart Home", 28 | "home automation", 29 | "mqtt", 30 | "syncing", 31 | "data", 32 | "subscribe", 33 | "publish" 34 | ], 35 | "repository": { 36 | "type": "git", 37 | "url": "https://github.com/iobroker-community-adapters/ioBroker.mqtt-client" 38 | }, 39 | "engines": { 40 | "node": ">=20" 41 | }, 42 | "dependencies": { 43 | "@iobroker/adapter-core": "^3.2.3", 44 | "mqtt": "^5.10.4" 45 | }, 46 | "devDependencies": { 47 | "@alcalzone/release-script": "^3.8.0", 48 | "@alcalzone/release-script-plugin-iobroker": "^3.7.2", 49 | "@alcalzone/release-script-plugin-license": "^3.7.0", 50 | "@alcalzone/release-script-plugin-manual-review": "^3.7.0", 51 | "@eslint/eslintrc": "^3.3.1", 52 | "@eslint/js": "^9.28.0", 53 | "@iobroker/adapter-dev": "^1.4.0", 54 | "@iobroker/eslint-config": "^1.0.0", 55 | "@iobroker/testing": "^5.0.4", 56 | "@tsconfig/node14": "^14.1.3", 57 | "@types/chai": "^4.3.19", 58 | "@types/chai-as-promised": "^7.1.8", 59 | "@types/mocha": "^10.0.10", 60 | "@types/node": "^22.15.29", 61 | "@types/proxyquire": "^1.3.31", 62 | "@types/sinon": "^17.0.4", 63 | "@types/sinon-chai": "^3.2.12", 64 | "chai": "^4.5.0", 65 | "chai-as-promised": "^7.1.2", 66 | "globals": "^15.14.0", 67 | "mocha": "^11.5.0", 68 | "proxyquire": "^2.1.3", 69 | "sinon": "^19.0.2", 70 | "sinon-chai": "^3.7.0", 71 | "typescript": "~5.7.3" 72 | }, 73 | "main": "main.js", 74 | "files": [ 75 | "admin{,/!(src)/**}/!(tsconfig|tsconfig.*).json", 76 | "admin{,/!(src)/**}/*.{html,css,png,svg,jpg,js}", 77 | "lib/", 78 | "io-package.json", 79 | "LICENSE", 80 | "main.js" 81 | ], 82 | "scripts": { 83 | "test:js": "mocha --config test/mocharc.custom.json \"{!(node_modules|test)/**/*.test.js,*.test.js,test/**/test!(PackageFiles|Startup).js}\"", 84 | "test:package": "mocha test/package --exit", 85 | "test:integration": "mocha test/integration --exit", 86 | "test": "npm run test:js && npm run test:package", 87 | "check": "tsc --noEmit -p tsconfig.check.json", 88 | "lint": "eslint -c eslint.config.mjs .", 89 | "translate": "translate-adapter", 90 | "release": "release-script", 91 | "release-patch": "release-script patch --yes", 92 | "release-minor": "release-script minor --yes", 93 | "release-major": "release-script major --yes" 94 | }, 95 | "bugs": { 96 | "url": "https://github.com/iobroker-community-adapters/ioBroker.mqtt-client" 97 | }, 98 | "readmeFilename": "README.md" 99 | } 100 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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, '..')); 6 | -------------------------------------------------------------------------------- /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); 15 | -------------------------------------------------------------------------------- /test/mocharc.custom.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": ["test/mocha.setup.js"], 3 | "watch-files": ["!(node_modules|test)/**/*.test.js", "*.test.js", "test/**/test!(PackageFiles|Startup).js"] 4 | } 5 | -------------------------------------------------------------------------------- /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": ["./**/*.js"] 7 | } 8 | -------------------------------------------------------------------------------- /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 | "compileOnSave": true, 4 | "compilerOptions": { 5 | // do not compile anything, this file is just to configure type checking 6 | "noEmit": true, 7 | // check JS files 8 | "allowJs": true, 9 | "checkJs": true, 10 | "module": "commonjs", 11 | "moduleResolution": "node", 12 | "esModuleInterop": true, 13 | // this is necessary for the automatic typing of the adapter config 14 | "resolveJsonModule": true, 15 | // Set this to false if you want to disable the very strict rules (not recommended) 16 | "strict": true, 17 | // Or enable some of those features for more fine-grained control 18 | // "strictNullChecks": true, 19 | // "strictPropertyInitialization": true, 20 | // "strictBindCallApply": true, 21 | "noImplicitAny": false, 22 | // "noUnusedLocals": true, 23 | // "noUnusedParameters": true, 24 | "useUnknownInCatchVariables": false, 25 | "target": "es2022", 26 | }, 27 | "include": [ 28 | "**/*.js", 29 | "**/*.d.ts" 30 | ], 31 | "exclude": [ 32 | "node_modules/**" 33 | ] 34 | } --------------------------------------------------------------------------------