├── .eslintignore ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── auto-merge.yml ├── dependabot.yml └── workflows │ ├── dependabot-auto-merge.yml │ └── test-and-release.yml ├── .gitignore ├── .jshintrc ├── .npmignore ├── .releaseconfig.json ├── CHANGELOG_OLD.md ├── LICENSE ├── README.md ├── admin ├── broadlink.png ├── broadlink2.png ├── 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 └── words.js ├── broadlink2.js ├── broadlink_fj.js ├── doc ├── FloureonManual.pdf ├── New API for heating wifi thermostat.txt ├── OldCode │ ├── broadlink_fj copy.js │ ├── broadlink_fj_ori.js │ ├── fjadapter-core.js │ ├── myAdapter.js │ └── myadapter_old.js ├── README_DE.md ├── boadlink_patched.js ├── broadlink.py ├── broadlink2.2.1.0.js ├── broadlink2.2.1.2.js ├── broadlink_discovery ├── broadlink_fj.2.1.0.js ├── broadlink_fj.2.1.2.js ├── fjadapter.2.1.0.js ├── fjadapter.2.1.2.js ├── new API for fan coil wifi thermostat.txt ├── newBroadlink.py ├── otherProjectSources │ ├── 38_BEOK.pm │ ├── SmartHomeDIY.js │ ├── async.txt │ ├── broadlink │ │ └── __init__.py │ ├── broadlink2.old.js │ ├── broadlink_fj.old.js │ ├── broadlink_fj1.js │ ├── myAdapter.js │ ├── myAdapter.old.js │ ├── myAdapter_l.js │ ├── myAdaptero.js │ └── python-broadlink-master.zip ├── python-broadlink-master.zip ├── python-broadlink-master │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── broadlink │ │ ├── __init__.py │ │ └── exceptions.py │ ├── broadlink_cli │ ├── broadlink_discovery │ ├── broadlink_discovery copy │ ├── cli │ │ ├── README.md │ │ ├── broadlink_cli │ │ └── broadlink_discovery │ ├── get-pip.py │ ├── protocol.md │ ├── requirements.txt │ └── setup.py ├── structure_example.png └── test.py ├── fjadapter.js ├── io-package.json ├── lib ├── test.js └── tools.js ├── package-lock.json ├── package.json └── test ├── integration.js ├── mocha.setup.js ├── mocharc.custom.json ├── package.js └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | **/.eslintrc.js 2 | doc/** 3 | admin/words.js 4 | lib/test.js 5 | lib/tools.js 6 | broadlink2.js 7 | broadlink_fj.js 8 | fjadapter.js 9 | gulpfile.js 10 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "es6": true, 5 | "node": true, 6 | "mocha": true 7 | }, 8 | "extends": ["eslint:recommended"], 9 | "plugins": [], 10 | "rules": { 11 | "indent": [ 12 | "error", 13 | 4, 14 | { 15 | "SwitchCase": 1 16 | } 17 | ], 18 | "no-console": "off", 19 | "no-unused-vars": [ 20 | "error", 21 | { 22 | "ignoreRestSiblings": true, 23 | "argsIgnorePattern": "^_" 24 | } 25 | ], 26 | "no-var": "error", 27 | "no-trailing-spaces": "error", 28 | "prefer-const": "error", 29 | "quotes": [ 30 | "error", 31 | "single", 32 | { 33 | "avoidEscape": true, 34 | "allowTemplateLiterals": true 35 | } 36 | ], 37 | "semi": ["error", "always"] 38 | }, 39 | "parserOptions": { 40 | "ecmaVersion": 2020 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.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/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: [18.x, 20.x, 22.x] 44 | os: [ubuntu-latest, windows-latest, macos-latest] 45 | 46 | steps: 47 | - uses: ioBroker/testing-action-adapter@v1 48 | with: 49 | node-version: ${{ matrix.node-version }} 50 | os: ${{ matrix.os }} 51 | # Uncomment the following line if your adapter cannot be installed using 'npm ci' 52 | # install-command: 'npm install' 53 | 54 | # 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: '18.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 | /.idea 2 | /node_modules 3 | /tmp 4 | /.vscode -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // JSHint Default Configuration File (as on JSHint website) 3 | // See http://jshint.com/docs/ for more details 4 | 5 | "maxerr" : 100, // {int} Maximum error before stopping 6 | 7 | // Enforcing 8 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) 9 | "camelcase" : false, // true: Identifiers must be in camelCase 10 | "curly" : false, // true: Require {} for every new block or scope 11 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 12 | "forin" : false, // true: Require filtering for..in loops with obj.hasOwnProperty() 13 | "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc. 14 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 15 | "latedef" : false, // true: Require variables/functions to be defined before being used 16 | "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` 17 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 18 | "noempty" : true, // true: Prohibit use of empty blocks 19 | "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters. 20 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 21 | "plusplus" : false, // true: Prohibit use of `++` and `--` 22 | "quotmark" : false, // Quotation mark consistency: 23 | // false : do nothing (default) 24 | // true : ensure whatever is used is consistent 25 | // "single" : require single quotes 26 | // "double" : require double quotes 27 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 28 | "unused" : true, // Unused variables: 29 | // true : all variables, last function parameter 30 | // "vars" : all variables only 31 | // "strict" : all variables, all function parameters 32 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode 33 | "maxparams" : false, // {int} Max number of formal params allowed per function 34 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 35 | "maxstatements" : false, // {int} Max number statements per function 36 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 37 | "maxlen" : false, // {int} Max number of characters per line 38 | "varstmt" : false, // true: Disallow any var statements. Only `let` and `const` are allowed. 39 | 40 | // Relaxing 41 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 42 | "boss" : false, // true: Tolerate assignments where comparisons would be expected 43 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 44 | "eqnull" : false, // true: Tolerate use of `== null` 45 | "esversion" : 6, // {int} Specify the ECMAScript version to which the code must adhere. 46 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 47 | // (ex: `for each`, multiple try/catch, function expression…) 48 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 49 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs 50 | "funcscope" : false, // true: Tolerate defining variables inside control statements 51 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') 52 | "iterator" : false, // true: Tolerate using the `__iterator__` property 53 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 54 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 55 | "laxcomma" : false, // true: Tolerate comma-first style coding 56 | "loopfunc" : false, // true: Tolerate functions being defined in loops 57 | "multistr" : false, // true: Tolerate multi-line strings 58 | "noyield" : false, // true: Tolerate generator functions with no yield statement in them. 59 | "notypeof" : false, // true: Tolerate invalid typeof operator values 60 | "proto" : false, // true: Tolerate using the `__proto__` property 61 | "scripturl" : false, // true: Tolerate script-targeted URLs 62 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 63 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 64 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 65 | "validthis" : false, // true: Tolerate using this in a non-constructor function 66 | 67 | // Environments 68 | "browser" : true, // Web Browser (window, document, etc) 69 | "browserify" : false, // Browserify (node.js code in the browser) 70 | "couch" : false, // CouchDB 71 | "devel" : true, // Development/debugging (alert, confirm, etc) 72 | "dojo" : false, // Dojo Toolkit 73 | "jasmine" : false, // Jasmine 74 | "jquery" : false, // jQuery 75 | "mocha" : true, // Mocha 76 | "mootools" : false, // MooTools 77 | "node" : true, // Node.js 78 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 79 | "phantom" : false, // PhantomJS 80 | "prototypejs" : false, // Prototype and Scriptaculous 81 | "qunit" : false, // QUnit 82 | "rhino" : false, // Rhino 83 | "shelljs" : false, // ShellJS 84 | "typed" : false, // Globals for typed array constructions 85 | "worker" : false, // Web Workers 86 | "wsh" : false, // Windows Scripting Host 87 | "yui" : false, // Yahoo User Interface 88 | 89 | // Custom Globals 90 | "globals" : {} // additional predefined global variables 91 | } 92 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | Gruntfile.js 2 | gulpfile.js 3 | tasks 4 | node_modules 5 | .idea 6 | .git 7 | doc/ 8 | lib/otherProjectSources/ 9 | /node_modules 10 | test 11 | .travis.yml 12 | appveyor.yml 13 | .vscode 14 | -------------------------------------------------------------------------------- /.releaseconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["iobroker", "license", "manual-review"], 3 | "numNews": 15 4 | } 5 | -------------------------------------------------------------------------------- /CHANGELOG_OLD.md: -------------------------------------------------------------------------------- 1 | # Older Chnagelogs 2 | ## 2.1.0 3 | 4 | * Added RM4 protocol for newest RM4 and RM3-Minis 5 | * Added LB1 Wifi bulb device support 6 | * Added finding of devices if name or ip changes according to mac address 7 | * Added support of devices in other netword with IP address 8 | * Changed learning and device communication for all RM devices 9 | * Re-write of 70% nof the code for new js-controllers and nodejs versions. 10 | 11 | ## 2.0.3 12 | 13 | * changed to new myAdapter to support js-controller 2.0 and 3.0 14 | 15 | ## 2.0.1 16 | 17 | * Can handle Floureon/Beko thermostats (now with MQTT) 18 | * Can handle S1C security devices 19 | * Names device after their name or with their mac to reduce possibility of renaming 20 | * Can rename devices 21 | * Support compact mode 22 | * Can add device Id's/Types for new devices 23 | * New communication routines to find & re-find devices 24 | * New communication protocoll with devices which do not allow that devices can get commands from 2 sources intermixed 25 | 26 | ## 1.9.1 27 | 28 | * added anothe RM Mini code 29 | 30 | ## 1.8.1 31 | 32 | * Changed util.js and tests and added new devices 33 | 34 | ## 1.7.0 35 | 36 | * Changed and corrected states which are created by A1-devices -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024, iobroker-community-adapters 4 | Copyright (c) 2014-2020 Frank Joke 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/broadlink2.png) Controls BroadLink compatible devices 2 | 3 | [![NPM version](http://img.shields.io/npm/v/iobroker.broadlink2.svg)](https://www.npmjs.com/package/iobroker.broadlink2) 4 | [![installed](http://iobroker.live/badges/broadlink2-installed.svg)](http://iobroker.live/badges/broadlink2-installed.svg) 5 | [![Downloads](https://img.shields.io/npm/dm/iobroker.broadlink2.svg)](https://www.npmjs.com/package/iobroker.broadlink2) 6 | [![Travis-CI](http://img.shields.io/travis/frankjoke/ioBroker.broadlink2/master.svg)](https://travis-ci.org/frankjoke/ioBroker.broadlink2) 7 | 8 | [Deutsche Anleitung translated by google](https://translate.google.com/translate?sl=en&tl=de&u=https%3A%2F%2Fgithub.com%2Ffrankjoke%2FioBroker.broadlink2%2Fblob%2Fmaster%2FREADME.md) 9 | 10 | [Русские инструкции переведены с гуглом](https://translate.google.com/translate?sl=en&tl=ru&u=https%3A%2F%2Fgithub.com%2Ffrankjoke%2FioBroker.broadlink2%2Fblob%2Fmaster%2FREADME.md) 11 | 12 | 13 | ## Adapter for different Broadlink compatible WLan-devices (RM++,SP++,A1, Floureon, S1C, LB1) 14 | 15 | This is an ioBroker adapter for multiple Broadlink switch like RM2, RM3, RM Plus, SP1, SP2, SP3, Honeywell SP2, SPMini, SPMini2, SPMiniPlus and some OEM products from them. 16 | ALso remote controllers are supported like RM2, RM Mini, RM Pro Phicomm, RM2 Home Plus, RM2 Home Plus GDT, RM2 Pro Plus, RM2 Pro Plus2 and RM2 Pro Plus BL. Multiple controllers will generate their own entries and need to be trained separately. 17 | It scans the network to find compatible devices and installs them (currently only switches type SP?). 18 | 19 | If you learned states for RM* and then rename their name the state-ID will change to the new name as well! 20 | 21 | You can create also your own new commands in LearnedStates if you use 'code'+ your code as the value (with 'CODE_' preceeding the code or even better (because of it will remain if you rename the state) add a field 'code' to native with the admin.object pencil and put there the hex code (without 'CODE_'!). 22 | 23 | The adapter has fixed states to send codes from RM-devices or to learn them It can also send individual scenes (actions on multiple devices). 24 | 25 | If devices which are configured on a certain IP are not found again they will be flagged 'notReachable'! If they are connected again they will be useable normally. 26 | 27 | If a device is not answering for 5 minutes in a row it's set to unreachable. ***notReachable*** devices will give a log warning message every x scans. After some scans the adapter will try to find them again on the same mac address before. 28 | 29 | Please delete old devices from admin.objects in case you remove them permanentely or renamed them in your router! 30 | 31 | The adapter tries to find the device at first by it's name and then by it's mac addresses. If name changes due to a change of ip address for example and mac address stays the same then device will continue to use old name. If device changes to a new device with new mac you can use rename device in config to use an old device name instead. 32 | 33 | ### Note on polling 34 | 35 | * SP1 devices cannot be polled. 36 | * If you use only RM devices polling can be set to 2 minutes (120 seconds) but should not be set higher because otherwise they might not be re-authorized 37 | * If you use switches which can be switched manually then molling should be 30s-1 minute to reflect changes within a minute. 38 | 39 | ## Configuration 40 | 41 | * Enter prefix of network address in configuration which should be removed when generating device names 42 | * Enter the number of seconds between polls. On each poll all SP* devices expluding SP1 are asked what the switch status is. This feature can be disabled by setting the poll delay to 0. On some RM devices with temperature readout the temperature will be updated as well. 43 | * You can add now ip addresses of to be found/included devices which are also on another network than the network of the adapter. In this case you need to make sure that the computer on which the adapter is running kno0ws by internal or external routing tables how to connect to this other network. 44 | * The `use IP interface` option can be set to use a specified interface address, this may help if you have lan and wlan on the system running iobroker and you do not want to scan on first interface but on wlan only, it may help also if local interface is different from external one in some docker or VM environments. You need to enter the IPv4 address of the interface to be used as source address, otherwise adapter will use 0.0.0.0 and listen to all local interfaces only. 45 | 46 | ## How-To learn codes on RM's 47 | 48 | * In Objects of ioBroker you can find "broadlink2.[devicename].Learn or LearnRF for '+' type of devices". 49 | * For RM(x)+ (Plus) devices you get also a special RS-sweep learn button (_LearnRF) which can learn more devices than on normal 433MHz. 50 | * Set this object to true. (you can click on the button in object view) 51 | * Now press some button on your remote control within 30 seconds. in normal mode press them shortly with some time in between until learned. 52 | * In RF-sweep learn you need to long press the button first for ~10-20 seconds, then release it and wait 2-3 seconds before you press it aggain for very short time. 53 | * An new Object should now appear within the Object "broadlink.[n].[devicename].LearnedState" with the name ">>> Rename learned @ YYYYMMDDTHHmmSS" 54 | * You can click on the button in object view to send the code. 55 | * To rename the item click on the name (starting with `_Rename_learned_`) and change the name. It should not include `,`, `.` or `;` as well as some other characters, they will be replaced by '_'; 56 | 57 | It is also possible to use the codes from [RM-Bridge](http://rm-bridge.fun2code.de/). 58 | Just create an object (state, type button) with value where you prepend "CODE_" or with native entry `code` without any 'CODE_'. 59 | 60 | ## Note on new RM4/LB1 devices 61 | 62 | * Several new Broadlink-Devices support a new Broadlink-Cloud protocol which is automatically selected when you use the newer broadlink apps to bring in the device into your wifi network. THis new broadlink protocol is not compatible with the broadlink2-Adapter and you cannot use devices using this new protocol. 63 | * To avoid this problem bring the device into the network using older Broadlink apps like `e smart home` or `e-control` and make sure your phone is on the same 2.4GHz wifi network which you want to bring it in! 64 | * This newer devices need also re-authentication every 5-10 minutes which adapter does automatically. 65 | 66 | ## Use scenes 67 | 68 | * Scenes can contain ID's or names as well as numbers separated by `,`. Normally the ID's will be executed/sent with 100ms time difference but if you need a longer pause between then you can write in a number which reflects the milli seconds to wait. For example `SP:dose=1, 1000, RM:your.L.StereoEin, 1000, RM:your.L.TVEin` would switch on an wireless plug named 'SP:dose', then wait one second (actually 1.1 seconds), Switch on the stero and after another second the tv. You can also switch devices of other adapters, like `hm-rpc.0.MEQ1435726.1.STATE=true` would switch this Homematic device on! Boolsche states can be switched with '=1/=on/=true/=ein', if you leave it without `=` than it will use true. To switch off a device you end it with '=0/=false/=aus/=off' which is necessary to be switched off! 69 | 70 | ## Use states 71 | 72 | * You may create also states for your devices which combines an On and Off commands to a single state which can be switched like any other device.. 73 | * You need to list the commands for switching a state on and off in the separate columns, these can be multiple ones so state knows when your device is switched on/off by any of them 74 | * If you set the state the to on or off onlöy the first on/off command will be sent 75 | * If only on commands are present the switch will send the respective command on a numeric value-1, with means it will send the first command if it receives an `0`, the second if it receives a `1`. In this way you can simulate multiple states within one state. 76 | * If you use only '+' as off command then you need to provide 10 on commands separated by ',' which reflect the numbers `0-9` on the remote control. You can send the sstate then a number, like `123` (max is 9999) and it would send `1`, `2` and `3` with 1/3rd of a second delay between them! In this way you sen set for example the channel on TV to '33' by just write 'TVchannel=33' if the state name is TVchannel. 77 | * If you use `-number`as off command like `-17` then you can store a number to the state where 17 would be subtracted and the the (x-17)th item in the on state would be sent. This way you can setup different fixed temperatures for devices which have different codes for each temperature. 78 | 79 | ## Use send messages to adapter 80 | 81 | The adapter understands also 'sendTo' commands. 82 | 83 | * `debug`: `sendTo('broadlink2.0','debug','on')` (also 0,1,on,off,ein,aus,true,false) would switch debug mode on. 84 | * `get`: `sendTo('broadlink2.0','get', 'RM2:RMPROPLUS.Temperature'` could request data from device like `{ val: 29.9, ack: true, ts: 1505839335870, q: 0, from: 'system.adapter.broadlink2.0', lc: 1505839335870 }` zurück 85 | * `switch`: can switch a plug on or off: `sendTo('broadlink2.0','switch','SP:your device id=on')` 86 | * `switch_on`/`switch_off`: sendTo('broadlink2.0','switch_on','SP:your device id')` 87 | * `send`: `sendTo('broadlink2.0','send','RM:yourdev._Learn')` would start learn and `sendTo('broadlink2.0','send','RM:yourdev.L.yourid')` would send the code. 88 | * `send_scene`: `sendTo('broadlink2.0','send_scene','scene xxx ')` würde den als message angegebenen Text als Szene ausführen 89 | * `send_code`: `sendTo('broadlink2.0','send_code','RM:your remote.CODE_xxxxx')` würde den CODE_xxxx vom R:your name senden. 90 | 91 | ## Floureon or Beok313 Thermostats 92 | 93 | * Most data can be set, the time can be set by writing anything to `_setTime` in which case the time of the device will be set to ioBroker system time. This will be done automatically also on adpter start. 94 | 95 | ## Config additional dnew devices 96 | 97 | * You may add new devices which use same protocol by adding them with the device-ID (in hex or decimal) and the device class (löisted there (class = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1). So you can add a new remote which the adapter finds only as unknown device with hex ID of 0x1234 to the RM list by 0x01234=RMP. 98 | 99 | ## Rename devices 100 | 101 | * Devices receive normally their network host name, or a combination of the device type, ID and mac address as their name with the first 2 letters of the type with ':' in front. You can rename such a device with `T1:BroadLink-OEM-T1-fa-83-7c=Beok313` in which case the original name will not be used but the new name used will be `Beok313`. 102 | 103 | ## Debug mode 104 | 105 | * If you add an `!` at the end of the list of added new devices (even if it is empty) you can set the adapter to debug mode where it will log a lot of additional information even iof it is not set to 'info' mode in Admin. 106 | 107 | ## Known-Issues 108 | 109 | * If you learn the same signal multiple times the code can be different everytime. This can not be changed. 110 | * Sometimes it does not find devices if they do not respond to the search. Do a rescan or restart adapter to restart a new instance. 111 | 112 | ## Changelog 113 | 114 | 118 | ### **WORK IN PROGRESS** 119 | * (mattreim) Several issues reported by adapter checker have been fixed 120 | 121 | ### 2.3.0 (2024-05-21) 122 | * (mattreim) Adapter migrated to jsonConfig 123 | * (mcm1957) Adapter requires admin >= 6 now 124 | * (mcm1957) Dependencies have been updated 125 | 126 | ### 2.2.0 (2024-04-05) 127 | * (mcm1957) Adapter requires node.js 18 and js-controller >= 5 now 128 | * (mcm1957) Dependencies have been updated 129 | 130 | ### 2.1.5 131 | 132 | * beta to try to make 0x5f36 working 133 | 134 | ### 2.1.4 135 | 136 | * bug corrections for RM4 temperatures & Humidity 137 | 138 | ### 2.1.2 139 | 140 | * bug corrections for States and Scenes 141 | * Names are now taken from DNS end which mean you may rename devices in router and set their fixed IP address there 142 | 143 | ### Todo for later revisions 144 | 145 | * config of devices and codes in separate config tool 146 | 147 | ## Installation 148 | 149 | with ioBroker admin, npm install iobroker.broadlink2 or from 150 | 151 | ## License 152 | 153 | The MIT License (MIT) 154 | 155 | Copyright (c) 2024, iobroker-community-adapters 156 | Copyright (c) 2014-2020, frankjoke 157 | 158 | Permission is hereby granted, free of charge, to any person obtaining a copy 159 | of this software and associated documentation files (the "Software"), to deal 160 | in the Software without restriction, including without limitation the rights 161 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 162 | copies of the Software, and to permit persons to whom the Software is 163 | furnished to do so, subject to the following conditions: 164 | 165 | The above copyright notice and this permission notice shall be included in 166 | all copies or substantial portions of the Software. 167 | 168 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 169 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 170 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 171 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 172 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 173 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 174 | THE SOFTWARE. 175 | -------------------------------------------------------------------------------- /admin/broadlink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iobroker-community-adapters/ioBroker.broadlink2/fc261d5ae259e556f1eb7f7c4988cd8d0c4c5165/admin/broadlink.png -------------------------------------------------------------------------------- /admin/broadlink2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iobroker-community-adapters/ioBroker.broadlink2/fc261d5ae259e556f1eb7f7c4988cd8d0c4c5165/admin/broadlink2.png -------------------------------------------------------------------------------- /admin/i18n/de/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Broadlink2 adapter settings": "Broadlink2 Einstellungen", 3 | "lblIpAddress": "IP-Adresse", 4 | "lblPoll": "Aktualisierung nach x Sekunden", 5 | "lblNew": "Neue Geräte definieren", 6 | "hlpNew": "Gerätetyp = Klasse, … (Klasse = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1)", 7 | "lblRename": "Geräte zum Umbenennen", 8 | "hlpRename": "SP:0x7547_34:ea:34:bd:67:a1 = Heizungsstecker, …", 9 | "lblAdditional": "Zusätzliche IPs zum Scannen", 10 | "lblScenes": "Szenen", 11 | "ttScenes": "Eine Szene kann aus gelernten Zuständen, Szenen oder Sender.CODE_xxx oder einer Zeitverzögerung in ms bestehen!", 12 | "lblScenesName": "Szenenname", 13 | "lblScenesScene": "Szene, Befehle getrennt durch „,“", 14 | "lblSwitches": "Schalter", 15 | "ttSwitches": "Zustände, die mit gelernten Zuständen oder Sequenzen ein-/ausgeschaltet werden. Dabei kann es sich um Listen handeln, bei denen der erste gesendet wird, die anderen jedoch ebenfalls wechseln.", 16 | "lblSwitchesName": "Zustandsname", 17 | "lblOn": "EIN-Signale", 18 | "lblOff": "AUS-Signale" 19 | } 20 | -------------------------------------------------------------------------------- /admin/i18n/en/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Broadlink2 adapter settings": "Broadlink2 Settings", 3 | "lblIpAddress": "IP address", 4 | "lblPoll": "Refresh after x seconds", 5 | "lblNew": "Define new devices", 6 | "hlpNew": "devicetype = class, … (class = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1)", 7 | "lblRename": "Devices to rename", 8 | "hlpRename": "SP:0x7547_34:ea:34:bd:67:a1 = HeaterPlug, …", 9 | "lblAdditional": "Additional IP's to scan", 10 | "lblScenes": "Scenes", 11 | "ttScenes": "A scene can consist of learned states, scenes or Sender.CODE_xxx or a time delay in ms!", 12 | "lblScenesName": "Scene name", 13 | "lblScenesScene": "Scene, commands separated with ','", 14 | "lblSwitches": "Switches", 15 | "ttSwitches": "States which will be switched on/off with learned states or sequences. They can be lists where the first will be sent but the others will switch as well.", 16 | "lblSwitchesName": "State name", 17 | "lblOn": "ON-signals", 18 | "lblOff": "OFF-signals" 19 | } 20 | -------------------------------------------------------------------------------- /admin/i18n/es/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Broadlink2 adapter settings": "Configuración de Broadlink2", 3 | "lblIpAddress": "Dirección IP", 4 | "lblPoll": "Actualizar después de x segundos", 5 | "lblNew": "Definir nuevos dispositivos", 6 | "hlpNew": "tipo de dispositivo = clase, ... (clase = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1)", 7 | "lblRename": "Dispositivos para cambiar el nombre", 8 | "hlpRename": "SP:0x7547_34:ea:34:bd:67:a1=Enchufe del calentador, ...", 9 | "lblAdditional": "IP adicionales para escanear", 10 | "lblScenes": "Escenas", 11 | "ttScenes": "¡Una escena puede constar de estados aprendidos, escenas o Sender.CODE_xxx o un retraso de tiempo en ms!", 12 | "lblScenesName": "Nombre de la escena", 13 | "lblScenesScene": "Escena, comandos separados por ','", 14 | "lblSwitches": "interruptores", 15 | "ttSwitches": "Estados que se activarán/desactivarán con estados o secuencias aprendidas. Pueden ser listas donde se enviará el primero pero los demás también cambiarán.", 16 | "lblSwitchesName": "Nombre del Estado", 17 | "lblOn": "señales ON", 18 | "lblOff": "señales de APAGADO" 19 | } 20 | -------------------------------------------------------------------------------- /admin/i18n/fr/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Broadlink2 adapter settings": "Paramètres Broadlink2", 3 | "lblIpAddress": "Adresse IP", 4 | "lblPoll": "Actualiser après x secondes", 5 | "lblNew": "Définir de nouveaux appareils", 6 | "hlpNew": "type d'appareil=classe, ... (classe = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1)", 7 | "lblRename": "Appareils à renommer", 8 | "hlpRename": "SP:0x7547_34:ea:34:bd:67:a1=HeaterPlug, ...", 9 | "lblAdditional": "IP supplémentaires à analyser", 10 | "lblScenes": "Scènes", 11 | "ttScenes": "Une scène peut être constituée d'états appris, de scènes ou de Sender.CODE_xxx ou d'une temporisation en ms !", 12 | "lblScenesName": "Nom de la scène", 13 | "lblScenesScene": "Scène, commandes séparées par ','", 14 | "lblSwitches": "Commutateurs", 15 | "ttSwitches": "États qui seront activés/désactivés avec des états ou des séquences appris. Il peut s'agir de listes où le premier sera envoyé mais les autres basculeront également.", 16 | "lblSwitchesName": "Nom d'état", 17 | "lblOn": "Signaux ON", 18 | "lblOff": "Signaux OFF" 19 | } 20 | -------------------------------------------------------------------------------- /admin/i18n/it/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Broadlink2 adapter settings": "Impostazioni Broadlink2", 3 | "lblIpAddress": "Indirizzo IP", 4 | "lblPoll": "Aggiorna dopo x secondi", 5 | "lblNew": "Definire nuovi dispositivi", 6 | "hlpNew": "tipo dispositivo=classe, ... (classe = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1)", 7 | "lblRename": "Dispositivi da rinominare", 8 | "hlpRename": "SP:0x7547_34:ea:34:bd:67:a1=Spina riscaldatore, ...", 9 | "lblAdditional": "IP aggiuntivi da scansionare", 10 | "lblScenes": "Scene", 11 | "ttScenes": "Una scena può essere composta da stati appresi, scene o Sender.CODE_xxx o da un ritardo in ms!", 12 | "lblScenesName": "Nome della scena", 13 | "lblScenesScene": "Scena, comandi separati da ','", 14 | "lblSwitches": "Interruttori", 15 | "ttSwitches": "Stati che verranno accesi/spenti con stati o sequenze apprese. Possono essere liste dove verrà inviata la prima ma si scambieranno anche le altre.", 16 | "lblSwitchesName": "Nome dello stato", 17 | "lblOn": "Segnali ON", 18 | "lblOff": "Segnali OFF" 19 | } 20 | -------------------------------------------------------------------------------- /admin/i18n/nl/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Broadlink2 adapter settings": "Broadlink2-instellingen", 3 | "lblIpAddress": "IP adres", 4 | "lblPoll": "Vernieuwen na x seconden", 5 | "lblNew": "Definieer nieuwe apparaten", 6 | "hlpNew": "apparaattype=klasse, ... (klasse = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1)", 7 | "lblRename": "Apparaten om te hernoemen", 8 | "hlpRename": "SP:0x7547_34:ea:34:bd:67:a1=Verwarmingsplug, ...", 9 | "lblAdditional": "Extra IP's om te scannen", 10 | "lblScenes": "Scènes", 11 | "ttScenes": "Een scène kan bestaan ​​uit geleerde toestanden, scènes of Sender.CODE_xxx of een tijdsvertraging in ms!", 12 | "lblScenesName": "Naam van de scène", 13 | "lblScenesScene": "Scène, opdrachten gescheiden door ','", 14 | "lblSwitches": "Schakelaars", 15 | "ttSwitches": "Toestanden die worden in-/uitgeschakeld met aangeleerde toestanden of sequenties. Het kunnen lijsten zijn waar de eerste naartoe wordt gestuurd, maar de anderen wisselen ook.", 16 | "lblSwitchesName": "Staat naam", 17 | "lblOn": "AAN-signalen", 18 | "lblOff": "UIT-signalen" 19 | } 20 | -------------------------------------------------------------------------------- /admin/i18n/pl/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Broadlink2 adapter settings": "Ustawienia Broadlink2", 3 | "lblIpAddress": "Adres IP", 4 | "lblPoll": "Odśwież po x sekundach", 5 | "lblNew": "Zdefiniuj nowe urządzenia", 6 | "hlpNew": "typ urządzenia=klasa, ... (klasa = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1)", 7 | "lblRename": "Urządzenia do zmiany nazwy", 8 | "hlpRename": "SP:0x7547_34:ea:34:bd:67:a1=Wtyczka podgrzewacza, ...", 9 | "lblAdditional": "Dodatkowe adresy IP do skanowania", 10 | "lblScenes": "Sceny", 11 | "ttScenes": "Scena może składać się z wyuczonych stanów, scen lub Sender.CODE_xxx lub opóźnienia czasowego w ms!", 12 | "lblScenesName": "Nazwa sceny", 13 | "lblScenesScene": "Scena, polecenia oddzielone znakiem „,”", 14 | "lblSwitches": "Przełączniki", 15 | "ttSwitches": "Stany, które zostaną włączone/wyłączone za pomocą wyuczonych stanów lub sekwencji. Mogą to być listy, do których zostanie wysłana pierwsza lista, ale pozostałe również się zmienią.", 16 | "lblSwitchesName": "Nazwa stanu", 17 | "lblOn": "Sygnały ON", 18 | "lblOff": "Sygnały WYŁ" 19 | } 20 | -------------------------------------------------------------------------------- /admin/i18n/pt/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Broadlink2 adapter settings": "Configurações do Broadlink2", 3 | "lblIpAddress": "Endereço de IP", 4 | "lblPoll": "Atualizar após x segundos", 5 | "lblNew": "Definir novos dispositivos", 6 | "hlpNew": "tipodedispositivo=classe, ... (classe = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1)", 7 | "lblRename": "Dispositivos para renomear", 8 | "hlpRename": "SP:0x7547_34:ea:34:bd:67:a1=Plugue do aquecedor, ...", 9 | "lblAdditional": "IPs adicionais para verificar", 10 | "lblScenes": "Cenas", 11 | "ttScenes": "Uma cena pode consistir em estados aprendidos, cenas ou Sender.CODE_xxx ou um atraso de tempo em ms!", 12 | "lblScenesName": "Nome da cena", 13 | "lblScenesScene": "Cena, comandos separados por ','", 14 | "lblSwitches": "Comuta", 15 | "ttSwitches": "Estados que serão ligados/desligados com estados ou sequências aprendidas. Podem ser listas para onde o primeiro será enviado, mas os outros também mudarão.", 16 | "lblSwitchesName": "Nome do estado", 17 | "lblOn": "Sinais ON", 18 | "lblOff": "Sinais OFF" 19 | } 20 | -------------------------------------------------------------------------------- /admin/i18n/ru/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Broadlink2 adapter settings": "Настройки Broadlink2", 3 | "lblIpAddress": "Айпи адрес", 4 | "lblPoll": "Обновить через x секунд", 5 | "lblNew": "Определить новые устройства", 6 | "hlpNew": "тип устройства = класс, ... (класс = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1)", 7 | "lblRename": "Устройства для переименования", 8 | "hlpRename": "SP:0x7547_34:ea:34:bd:67:a1=Нагревательная вилка, ...", 9 | "lblAdditional": "Дополнительные IP-адреса для сканирования", 10 | "lblScenes": "Сцены", 11 | "ttScenes": "Сцена может состоять из изученных состояний, сцен или Sender.CODE_xxx или временной задержки в мс!", 12 | "lblScenesName": "Название сцены", 13 | "lblScenesScene": "Сцена, команды разделены знаком ','", 14 | "lblSwitches": "Переключатели", 15 | "ttSwitches": "Состояния, которые будут включаться/выключаться с помощью изученных состояний или последовательностей. Это могут быть списки, в которые будет отправлен первый, но остальные также будут переключаться.", 16 | "lblSwitchesName": "Название штата", 17 | "lblOn": "ON-сигналы", 18 | "lblOff": "ВЫКЛ-сигналы" 19 | } 20 | -------------------------------------------------------------------------------- /admin/i18n/uk/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Broadlink2 adapter settings": "Налаштування Broadlink2", 3 | "lblIpAddress": "IP-адреса", 4 | "lblPoll": "Оновити через x секунд", 5 | "lblNew": "Визначити нові пристрої", 6 | "hlpNew": "devicetype=клас, ... (клас = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1)", 7 | "lblRename": "Пристрої для перейменування", 8 | "hlpRename": "SP:0x7547_34:ea:34:bd:67:a1=Вилка обігрівача, ...", 9 | "lblAdditional": "Додаткові IP-адреси для сканування", 10 | "lblScenes": "Сцени", 11 | "ttScenes": "Сцена може складатися з вивчених станів, сцен або Sender.CODE_xxx або затримки часу в мс!", 12 | "lblScenesName": "Назва сцени", 13 | "lblScenesScene": "Сцена, команди розділені символом ','", 14 | "lblSwitches": "Перемикачі", 15 | "ttSwitches": "Стани, які вмикаються/вимикаються за допомогою вивчених станів або послідовностей. Це можуть бути списки, де буде надіслано перше, але інші також перемикатимуться.", 16 | "lblSwitchesName": "Назва штату", 17 | "lblOn": "ON-сигнали", 18 | "lblOff": "OFF-сигнали" 19 | } 20 | -------------------------------------------------------------------------------- /admin/i18n/zh-cn/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Broadlink2 adapter settings": "Broadlink2 设置", 3 | "lblIpAddress": "IP地址", 4 | "lblPoll": "x 秒后刷新", 5 | "lblNew": "定义新设备", 6 | "hlpNew": "设备类型 = 类,...(类 = A1、MP1、RM、RMP、S1C、SP1、SP2、SP3P、T1)", 7 | "lblRename": "要重命名的设备", 8 | "hlpRename": "SP:0x7547_34:ea:34:bd:67:a1=加热器插头,...", 9 | "lblAdditional": "要扫描的其他 IP", 10 | "lblScenes": "场景", 11 | "ttScenes": "场景可以由学习状态、场景或 Sender.CODE_xxx 或以毫秒为单位的时间延迟组成!", 12 | "lblScenesName": "场景名称", 13 | "lblScenesScene": "场景、命令用“,”分隔", 14 | "lblSwitches": "开关", 15 | "ttSwitches": "将通过学习的状态或序列打开/关闭的状态。它们可以是列表,其中第一个将被发送,但其他也将切换。", 16 | "lblSwitchesName": "州名", 17 | "lblOn": "ON信号", 18 | "lblOff": "关闭信号" 19 | } 20 | -------------------------------------------------------------------------------- /admin/jsonConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "i18n": true, 3 | "type": "panel", 4 | "items": { 5 | "interface": { 6 | "newLine": true, 7 | "label": "lblIpAddress", 8 | "type": "text", 9 | "xs": 12, 10 | "sm": 12, 11 | "md": 3, 12 | "lg": 3 13 | }, 14 | "poll": { 15 | "label": "lblPoll", 16 | "type": "number", 17 | "default": 300, 18 | "min": 0, 19 | "max": 65535, 20 | "xs": 12, 21 | "sm": 12, 22 | "md": 3, 23 | "lg": 3 24 | }, 25 | "new": { 26 | "label": "lblNew", 27 | "type": "text", 28 | "help": "hlpNew", 29 | "xs": 12, 30 | "sm": 12, 31 | "md": 6, 32 | "lg": 6 33 | }, 34 | "rename": { 35 | "newLine": true, 36 | "label": "lblRename", 37 | "type": "text", 38 | "help": "hlpRename", 39 | "xs": 12, 40 | "sm": 12, 41 | "md": 6, 42 | "lg": 6 43 | }, 44 | "additional": { 45 | "label": "lblAdditional", 46 | "type": "text", 47 | "xs": 12, 48 | "sm": 12, 49 | "md": 6, 50 | "lg": 6 51 | }, 52 | "scenes": { 53 | "newLine": true, 54 | "type": "table", 55 | "label": "lblScenes", 56 | "xs": 12, 57 | "sm": 12, 58 | "md": 12, 59 | "lg": 12, 60 | "tooltip": "ttScenes", 61 | "items": [ 62 | { 63 | "type": "text", 64 | "attr": "name", 65 | "width": "20%", 66 | "title": "lblScenesName", 67 | "filter": true, 68 | "sort": true, 69 | "default": "" 70 | }, 71 | { 72 | "type": "text", 73 | "attr": "scene", 74 | "width": "80%", 75 | "title": "lblScenesScene", 76 | "filter": false, 77 | "sort": false, 78 | "default": "" 79 | } 80 | ] 81 | }, 82 | "switches": { 83 | "newLine": true, 84 | "type": "table", 85 | "label": "lblSwitches", 86 | "xs": 12, 87 | "sm": 12, 88 | "md": 12, 89 | "lg": 12, 90 | "tooltip": "ttSwitches", 91 | "items": [ 92 | { 93 | "type": "text", 94 | "attr": "name", 95 | "width": "20%", 96 | "title": "lblSwitchesName", 97 | "filter": true, 98 | "sort": true, 99 | "default": "" 100 | }, 101 | { 102 | "type": "text", 103 | "attr": "on", 104 | "width": "40%", 105 | "title": "lblOn", 106 | "filter": false, 107 | "sort": false, 108 | "default": "" 109 | }, 110 | { 111 | "type": "text", 112 | "attr": "off", 113 | "width": "40%", 114 | "title": "lblOff", 115 | "filter": false, 116 | "sort": false, 117 | "default": "" 118 | } 119 | ] 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /admin/words.js: -------------------------------------------------------------------------------- 1 | /*global systemDictionary:true */ 2 | /* 3 | +===================== DO NOT MODIFY ======================+ 4 | | This file was generated by translate-adapter, please use | 5 | | `translate-adapter adminLanguages2words` to update it. | 6 | +===================== DO NOT MODIFY ======================+ 7 | */ 8 | 'use strict'; 9 | 10 | systemDictionary = { 11 | "Broadlink2 adapter settings": { "en": "Broadlink2 Settings", "de": "Broadlink2 Einstellungen", "ru": "Настройки Broadlink2", "pt": "Configurações do Broadlink2", "nl": "Broadlink2-instellingen", "fr": "Paramètres Broadlink2", "it": "Impostazioni Broadlink2", "es": "Configuración de Broadlink2", "pl": "Ustawienia Broadlink2", "uk": "Налаштування Broadlink2", "zh-cn": "Broadlink2 设置"}, 12 | "lblIpAddress": { "en": "IP address", "de": "IP-Adresse", "ru": "Айпи адрес", "pt": "Endereço de IP", "nl": "IP adres", "fr": "Adresse IP", "it": "Indirizzo IP", "es": "Dirección IP", "pl": "Adres IP", "uk": "IP-адреса", "zh-cn": "IP地址"}, 13 | "lblPoll": { "en": "Refresh after x seconds", "de": "Aktualisierung nach x Sekunden", "ru": "Обновить через x секунд", "pt": "Atualizar após x segundos", "nl": "Vernieuwen na x seconden", "fr": "Actualiser après x secondes", "it": "Aggiorna dopo x secondi", "es": "Actualizar después de x segundos", "pl": "Odśwież po x sekundach", "uk": "Оновити через x секунд", "zh-cn": "x 秒后刷新"}, 14 | "lblNew": { "en": "Define new devices", "de": "Neue Geräte definieren", "ru": "Определить новые устройства", "pt": "Definir novos dispositivos", "nl": "Definieer nieuwe apparaten", "fr": "Définir de nouveaux appareils", "it": "Definire nuovi dispositivi", "es": "Definir nuevos dispositivos", "pl": "Zdefiniuj nowe urządzenia", "uk": "Визначити нові пристрої", "zh-cn": "定义新设备"}, 15 | "hlpNew": { "en": "devicetype = class, … (class = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1)", "de": "Gerätetyp = Klasse, … (Klasse = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1)", "ru": "тип устройства = класс, ... (класс = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1)", "pt": "tipodedispositivo=classe, ... (classe = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1)", "nl": "apparaattype=klasse, ... (klasse = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1)", "fr": "type d'appareil=classe, ... (classe = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1)", "it": "tipo dispositivo=classe, ... (classe = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1)", "es": "tipo de dispositivo = clase, ... (clase = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1)", "pl": "typ urządzenia=klasa, ... (klasa = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1)", "uk": "devicetype=клас, ... (клас = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1)", "zh-cn": "设备类型 = 类,...(类 = A1、MP1、RM、RMP、S1C、SP1、SP2、SP3P、T1)"}, 16 | "lblRename": { "en": "Devices to rename", "de": "Geräte zum Umbenennen", "ru": "Устройства для переименования", "pt": "Dispositivos para renomear", "nl": "Apparaten om te hernoemen", "fr": "Appareils à renommer", "it": "Dispositivi da rinominare", "es": "Dispositivos para cambiar el nombre", "pl": "Urządzenia do zmiany nazwy", "uk": "Пристрої для перейменування", "zh-cn": "要重命名的设备"}, 17 | "hlpRename": { "en": "SP:0x7547_34:ea:34:bd:67:a1 = HeaterPlug, …", "de": "SP:0x7547_34:ea:34:bd:67:a1 = Heizungsstecker, …", "ru": "SP:0x7547_34:ea:34:bd:67:a1=Нагревательная вилка, ...", "pt": "SP:0x7547_34:ea:34:bd:67:a1=Plugue do aquecedor, ...", "nl": "SP:0x7547_34:ea:34:bd:67:a1=Verwarmingsplug, ...", "fr": "SP:0x7547_34:ea:34:bd:67:a1=HeaterPlug, ...", "it": "SP:0x7547_34:ea:34:bd:67:a1=Spina riscaldatore, ...", "es": "SP:0x7547_34:ea:34:bd:67:a1=Enchufe del calentador, ...", "pl": "SP:0x7547_34:ea:34:bd:67:a1=Wtyczka podgrzewacza, ...", "uk": "SP:0x7547_34:ea:34:bd:67:a1=Вилка обігрівача, ...", "zh-cn": "SP:0x7547_34:ea:34:bd:67:a1=加热器插头,..."}, 18 | "lblAdditional": { "en": "Additional IP's to scan", "de": "Zusätzliche IPs zum Scannen", "ru": "Дополнительные IP-адреса для сканирования", "pt": "IPs adicionais para verificar", "nl": "Extra IP's om te scannen", "fr": "IP supplémentaires à analyser", "it": "IP aggiuntivi da scansionare", "es": "IP adicionales para escanear", "pl": "Dodatkowe adresy IP do skanowania", "uk": "Додаткові IP-адреси для сканування", "zh-cn": "要扫描的其他 IP"}, 19 | "lblScenes": { "en": "Scenes", "de": "Szenen", "ru": "Сцены", "pt": "Cenas", "nl": "Scènes", "fr": "Scènes", "it": "Scene", "es": "Escenas", "pl": "Sceny", "uk": "Сцени", "zh-cn": "场景"}, 20 | "ttScenes": { "en": "A scene can consist of learned states, scenes or Sender.CODE_xxx or a time delay in ms!", "de": "Eine Szene kann aus gelernten Zuständen, Szenen oder Sender.CODE_xxx oder einer Zeitverzögerung in ms bestehen!", "ru": "Сцена может состоять из изученных состояний, сцен или Sender.CODE_xxx или временной задержки в мс!", "pt": "Uma cena pode consistir em estados aprendidos, cenas ou Sender.CODE_xxx ou um atraso de tempo em ms!", "nl": "Een scène kan bestaan ​​uit geleerde toestanden, scènes of Sender.CODE_xxx of een tijdsvertraging in ms!", "fr": "Une scène peut être constituée d'états appris, de scènes ou de Sender.CODE_xxx ou d'une temporisation en ms !", "it": "Una scena può essere composta da stati appresi, scene o Sender.CODE_xxx o da un ritardo in ms!", "es": "¡Una escena puede constar de estados aprendidos, escenas o Sender.CODE_xxx o un retraso de tiempo en ms!", "pl": "Scena może składać się z wyuczonych stanów, scen lub Sender.CODE_xxx lub opóźnienia czasowego w ms!", "uk": "Сцена може складатися з вивчених станів, сцен або Sender.CODE_xxx або затримки часу в мс!", "zh-cn": "场景可以由学习状态、场景或 Sender.CODE_xxx 或以毫秒为单位的时间延迟组成!"}, 21 | "lblScenesName": { "en": "Scene name", "de": "Szenenname", "ru": "Название сцены", "pt": "Nome da cena", "nl": "Naam van de scène", "fr": "Nom de la scène", "it": "Nome della scena", "es": "Nombre de la escena", "pl": "Nazwa sceny", "uk": "Назва сцени", "zh-cn": "场景名称"}, 22 | "lblScenesScene": { "en": "Scene, commands separated with ','", "de": "Szene, Befehle getrennt durch „,“", "ru": "Сцена, команды разделены знаком ','", "pt": "Cena, comandos separados por ','", "nl": "Scène, opdrachten gescheiden door ','", "fr": "Scène, commandes séparées par ','", "it": "Scena, comandi separati da ','", "es": "Escena, comandos separados por ','", "pl": "Scena, polecenia oddzielone znakiem „,”", "uk": "Сцена, команди розділені символом ','", "zh-cn": "场景、命令用“,”分隔"}, 23 | "lblSwitches": { "en": "Switches", "de": "Schalter", "ru": "Переключатели", "pt": "Comuta", "nl": "Schakelaars", "fr": "Commutateurs", "it": "Interruttori", "es": "interruptores", "pl": "Przełączniki", "uk": "Перемикачі", "zh-cn": "开关"}, 24 | "ttSwitches": { "en": "States which will be switched on/off with learned states or sequences. They can be lists where the first will be sent but the others will switch as well.", "de": "Zustände, die mit gelernten Zuständen oder Sequenzen ein-/ausgeschaltet werden. Dabei kann es sich um Listen handeln, bei denen der erste gesendet wird, die anderen jedoch ebenfalls wechseln.", "ru": "Состояния, которые будут включаться/выключаться с помощью изученных состояний или последовательностей. Это могут быть списки, в которые будет отправлен первый, но остальные также будут переключаться.", "pt": "Estados que serão ligados/desligados com estados ou sequências aprendidas. Podem ser listas para onde o primeiro será enviado, mas os outros também mudarão.", "nl": "Toestanden die worden in-/uitgeschakeld met aangeleerde toestanden of sequenties. Het kunnen lijsten zijn waar de eerste naartoe wordt gestuurd, maar de anderen wisselen ook.", "fr": "États qui seront activés/désactivés avec des états ou des séquences appris. Il peut s'agir de listes où le premier sera envoyé mais les autres basculeront également.", "it": "Stati che verranno accesi/spenti con stati o sequenze apprese. Possono essere liste dove verrà inviata la prima ma si scambieranno anche le altre.", "es": "Estados que se activarán/desactivarán con estados o secuencias aprendidas. Pueden ser listas donde se enviará el primero pero los demás también cambiarán.", "pl": "Stany, które zostaną włączone/wyłączone za pomocą wyuczonych stanów lub sekwencji. Mogą to być listy, do których zostanie wysłana pierwsza lista, ale pozostałe również się zmienią.", "uk": "Стани, які вмикаються/вимикаються за допомогою вивчених станів або послідовностей. Це можуть бути списки, де буде надіслано перше, але інші також перемикатимуться.", "zh-cn": "将通过学习的状态或序列打开/关闭的状态。它们可以是列表,其中第一个将被发送,但其他也将切换。"}, 25 | "lblSwitchesName": { "en": "State name", "de": "Zustandsname", "ru": "Название штата", "pt": "Nome do estado", "nl": "Staat naam", "fr": "Nom d'état", "it": "Nome dello stato", "es": "Nombre del Estado", "pl": "Nazwa stanu", "uk": "Назва штату", "zh-cn": "州名"}, 26 | "lblOn": { "en": "ON-signals", "de": "EIN-Signale", "ru": "ON-сигналы", "pt": "Sinais ON", "nl": "AAN-signalen", "fr": "Signaux ON", "it": "Segnali ON", "es": "señales ON", "pl": "Sygnały ON", "uk": "ON-сигнали", "zh-cn": "ON信号"}, 27 | "lblOff": { "en": "OFF-signals", "de": "AUS-Signale", "ru": "ВЫКЛ-сигналы", "pt": "Sinais OFF", "nl": "UIT-signalen", "fr": "Signaux OFF", "it": "Segnali OFF", "es": "señales de APAGADO", "pl": "Sygnały WYŁ", "uk": "OFF-сигнали", "zh-cn": "关闭信号"}, 28 | }; -------------------------------------------------------------------------------- /doc/FloureonManual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iobroker-community-adapters/ioBroker.broadlink2/fc261d5ae259e556f1eb7f7c4988cd8d0c4c5165/doc/FloureonManual.pdf -------------------------------------------------------------------------------- /doc/New API for heating wifi thermostat.txt: -------------------------------------------------------------------------------- 1 | As a developer, you need to know the ID number of thermostat firstly. 2 | 3 | For example, the ID number is 4664. 4 | 1. As the ID is encrypted, you need to calculate the effect ID_Effect. 5 | ID_Effect=(ID+1)*65535+65535=12 39 ED C6 (4 bytes). 6 | 2. When you know the number of ID_Effect, you need to communicate with the server of domain name bestbeca.cn. 7 | The engineer should use the server of domain name to analyse the real ip address. The port is 25565. 8 | The developer need to send 12 39 ED C6 for the registration then use the tcp protocol for communication . 9 | 3. After that, all the parameters of thermostat will be controlled according to the 8 byte datas in the following protocol detail . 10 | 4. When thermostat sends collected temperature data to upper computer, the value of collected temperature should be multiplied by 2 and sent completely by the format of HEX because the accuracy is 0.5. 11 | For example: When the collected temperature is 25.5, the value sent by thermostat to the upper computer will be 33H (the demical is 51 ); 12 | Similarly, When upper computer sends set temperature data to thermostat, the value of set temperature should be multiplied by 2 and sent completely by the format of HEX because the accuracy is 0.5. 13 | For example: When the set temperature is 25.5, the value sent by upper computer to the thermostat should be 33H (the demical is 51). 14 | E.G.: Read Temper= 25.5C 15 | The send(or receive) value is 25.5*2=51 16 | The data in HEX =33H 17 | 18 | Communication Protocol And DataFormat Describe 19 | 20 | Declare: Following " 0X " Mean's HEX Values 21 | 22 | ;;======================================================================================================================= 23 | 24 | 1Data Package protocol as below: 25 | 26 | COMMAND + ID0 + ID1 + Data0 + Data1 + Data2 + Data3 + CheckSum 27 | 28 | 8 Bytes in total 29 | 30 | ;;======================================================================================================================= 31 | 32 | 2: CheckSum definition: 33 | 34 | (size: 8 Bits) 35 | CheckSum = (COMMAND + ID0 + ID1 + Data0 + Data1 + Data2 + Data3) & 0xFF ^ 0xA5 36 | 37 | ;;======================================================================================================================== 38 | 39 | 3: COMMAND definition 40 | 41 | ReadPC read data from thermostat 42 | WritePC transmit data to thermostat 43 | 44 | COMMAND is command opcodesize: 8 Bit commands as below 45 | command 46 | PC ------->thermostat 47 | [0XA0] --> PC read all data from thermostat 48 | [0XA1] --> PC transmit all data to thermostat 49 | [0XA2] --> Turn-on/off power 50 | [0XA3] --> no use 51 | [0XA4] --> no use 52 | [0XA5] --> no use 53 | [0XA6] --> Temperature setting 54 | [0XA7] --> Temperature calibration 55 | [0XA8] --> Time setting 56 | [0XA9] --> Auto/Hand setting 57 | 58 | command 59 | Thermostat--------->PC 60 | [0X50] -->thermostat returns all data to PC 61 | 62 | 63 | ;;=============================================================================== 64 | 65 | 4: ID0 , ID1 definition 66 | Note: 67 | ID0,ID1 must be 01 01,it is fixed,can not be other data 68 | 69 | 70 | ;;=============================================================================== 71 | 72 | 5: Data Package definition 73 | 74 | Data0 ~ Data3 is command operand size is 32 Bit (4 Bytes) 75 | ;;------------------------------ 76 | 77 | COMMAND1: 0XA0 78 | Data Package0XA0 + ID0 + ID1 + Data0 + Data1 + Data2 + Data3 + CheckSum 79 | Data0 00 80 | Data1 00 81 | Data2 00 82 | Data3 00 83 | ;;------------------------------ 84 | 85 | Response: 0x50 data returns 86 | 87 | ;;------------------------------ 88 | 89 | COMMAND2: 0XA1 90 | Data package0XA1 + ID0 + ID1 + Data0 + Data1 + Data2 + Data3 + CheckSum 91 | 92 | Data0 =>no use 93 | 94 | Bit2 must be 0 95 | Bit3 Manual and weekly programmable 96 | 0 -> Weekly programmable 97 | 1 -> Manual 98 | 99 | Bit4 turn-on/off 100 | 0 -> turn-off 101 | 1 -> turn-on 102 | 103 | Bit6-Bit5 no use 104 | 105 | Bit7 no use 106 | 107 | Data1 => temperature calibration, -9-9 degree C(0XF7-0X09) 108 | 109 | Data2 => temperature setting, from 10 to 30 degree C (0X0A-0X1E) 110 | 111 | Data3 => ambient temperature 112 | ;;------------------------------ 113 | 114 | response: 0x50 returns OK 115 | ;;------------------------------ 116 | 117 | COMMAND3: 0XA2 118 | Data package0XA2 + ID0 + ID1 + Data0 + Data1 + Data2 + Data3 + CheckSum 119 | 120 | Data0 => Bit1-Bit3 Invalid 121 | Bit4 turn-on/off 122 | 0 -> turn-off 123 | 1 -> turn-on 124 | Bit5-Bit7 Invalid 125 | 126 | Data1 => Invalid 127 | 128 | Data2 => Invalid 129 | 130 | Data3 => Invalid 131 | ;;------------------------------ 132 | 133 | response: 0x50 returns OK 134 | 135 | ;;------------------------------ 136 | 137 | COMMAND4: 0XA3 138 | Data package0XA3 + ID0 + ID1 + Data0 + Data1 + Data2 + Data3 + CheckSum 139 | 140 | Data0 => no use 141 | Bit2-Bit7 Invalid 142 | 143 | Data1 => Invalid 144 | 145 | Data2 => Invalid 146 | 147 | Data3 => Invalid 148 | ;;------------------------------ 149 | 150 | response: 0x50 returns OK 151 | 152 | ;;------------------------------ 153 | 154 | COMMAND5: 0XA4 155 | Data package0XA4 + ID0 + ID1 + Data0 + Data1 + Data2 + Data3 + CheckSum 156 | 157 | Data0 => Bit0-Bit4 Invalid 158 | Bit6-Bit 5 no use 159 | 160 | Data1 => Invalid 161 | 162 | Data2 => Invalid 163 | 164 | Data3 => Invalid 165 | ;;------------------------------ 166 | 167 | response: 0x50 returns OK 168 | 169 | ;;------------------------------ 170 | 171 | 172 | COMMAND7: 0XA6 173 | Data package0XA6 + ID0 + ID1 + Data0 + Data1 + Data2 + Data3 + CheckSum 174 | 175 | Data0 => Invalid 176 | 177 | Data1 => Invalid 178 | 179 | Data2 => temperature setting 180 | 181 | Data3 => Invalid 182 | ;;------------------------------ 183 | 184 | response: 0x50 returns OK 185 | 186 | ;;------------------------------ 187 | 188 | COMMAND8: 0XA7 189 | Data package0XA7 + ID0 + ID1 + Data0 + Data1 + Data2 + Data3 + CheckSum 190 | 191 | Data0 => Invalid 192 | 193 | Data1 => temperature calibrationfrom-9 to 9 degree=> 0xF7...9) 194 | 195 | Data2 => Invalid 196 | 197 | Data3 => Invalid 198 | ;;------------------------------ 199 | 200 | response: 0x50 returns OK 201 | ;;------------------------------ 202 | 203 | COMMAND9: 0XA8 204 | Data package0XA8 + ID0 + ID1 + Data0 + Data1 + Data2 + Data3 + CheckSum 205 | 206 | Data0 => second(BCD code) 207 | 208 | Data1 => minute(BCD code) 209 | 210 | Data2 => hour(BCD code) 211 | 212 | Data3 => week(BCD code) 213 | ;;------------------------------ 214 | 215 | response: 0x50 returns OK 216 | ;;------------------------------ 217 | 218 | COMMAND10: 0XA9 219 | ;;------------------------------ 220 | Data package0XA4 + ID0 + ID1 + Data0 + Data1 + Data2 + Data3 + CheckSum 221 | 222 | Data0 => Bit0-Bit2 Invalid 223 | Bit3 Manual and weekly programmable 224 | 0 -> Weekly programmable 225 | 1 -> Manual 226 | Bit4-Bit 7 Invalid 227 | Data1 => Invalid 228 | 229 | Data2 => Invalid 230 | 231 | Data3 => Invalid 232 | response: 0x50 returns OK 233 | 234 | ;;=============================================================================== 235 | ;;=============================================================================== 236 | response: 0x50 OK 237 | Data package0x50 + ID0 + ID1 + Data0 + Data1 + Data2 + Data3 + CheckSum 238 | 239 | Data0 => Bit1-Bit0 no use 240 | 241 | Bit2 0 242 | Bit3 Manual and weekly programmable 243 | 0 -> Weekly programmable 244 | 1 -> Manual 245 | 246 | Bit4 turn-on/off 247 | 0 -> turn-off 248 | 1 -> turn-on 249 | 250 | Bit6-Bit5 no use 251 | 252 | 253 | Bit7 no use 254 | 255 | Data1 => temperature calibration, -9-9 degree C(0XF7-0X09) 256 | 257 | Data2 => temperature setting, from 10 to 30 degree C (0X0A-0X1E) 258 | 259 | Data3 => ambient temperature 260 | 261 | ;;=============================================================================== 262 | ;;=============================================================================== 263 | 264 | 6: Example of 2 commands 265 | 266 | PC is master thermosat is slave 267 | ---------------------------------------------------------- 268 | 269 | ---------------------------------------------------------- 270 | master read data from thermostat 271 | COMMAND == 0XA0 PC read data from thermostat 272 | ID0,ID1 => IP 273 | Data0 => 0xxx 274 | Data1 => 0xxx 275 | Data2 => 0xxx 276 | Data3 => 0xxx 277 | CheckSum 278 | 279 | slave respond 280 | COMMAND == 0X50 thermostat responds data to PC 281 | 282 | ID0,ID1 => 01 01 283 | 284 | Data0 => Bit1-Bit0 no use 285 | Bit2 0 286 | Bit3 standby 287 | 288 | Bit4 turn-on/off 289 | 0 -> turn-off 290 | 1 -> turn-on 291 | 292 | Bit6-Bit5 no use 293 | 294 | Bit7 valve 295 | 0->turn-off 296 | 1->turn-on 297 | 298 | Bit7 standby 299 | 300 | Data1 => temperature calibration 301 | 302 | Data2 => temperature setting from 10 to 30 degree 303 | 304 | Data3 => ambient temperature 305 | 306 | CheckSum 307 | 308 | ---------------------------------------------------------- 309 | 310 | ---------------------------------------------------------- 311 | PC transmit all data to thermostat 312 | COMMAND == 0XA1 PC transmit all data to thermostat 313 | 314 | ID0,ID1 => 01 01 315 | 316 | Data0 => Bit1-Bit0 no use 317 | Bit2 0 318 | Bit3 standby 319 | 320 | Bit4 turn-on/off 321 | 0 -> turn-off 322 | 1 -> turn-on 323 | 324 | Bit6-Bit5 no use 325 | 326 | Bit7 valve 327 | 0->turn-off 328 | 1->turn-on 329 | 330 | 331 | 332 | Data1 => temperature calibration 333 | 334 | Data2 => temperature setting from 10 to 30 degree 335 | 336 | Data3 => standby 337 | 338 | CheckSum 339 | 340 | slave response 341 | COMMAND == 0X50 PC transmit all data to thermostat OK 342 | 343 | ---------------------------------------------------------- 344 | -------------------------------------------------------------------------------- /doc/README_DE.md: -------------------------------------------------------------------------------- 1 | # ![Logo](./admin/broadlink2.png) Steuert BroadLink kompatible Geräte 2 | 3 | [![NPM version](http://img.shields.io/npm/v/iobroker.broadlink2.svg)](https://www.npmjs.com/package/iobroker.broadlink2) 4 | [![installed](http://iobroker.live/badges/broadlink2-installed.svg)](http://iobroker.live/badges/broadlink2-installed.svg) 5 | [![Downloads](https://img.shields.io/npm/dm/iobroker.broadlink2.svg)](https://www.npmjs.com/package/iobroker.broadlink2) 6 | [![Travis-CI](http://img.shields.io/travis/frankjoke/ioBroker.broadlink2/master.svg)](https://travis-ci.org/frankjoke/ioBroker.broadlink2) 7 | 8 | [English manual - Englische Anleitung](README.md) 9 | 10 | ## Adapter für verschiedene Broadlink WLan-Geräte (RM++,SP++,A1, Floureon, S1C) 11 | 12 | Dieser ioBroker adapter für die meisten Broadlink kompatiblen Geräte wie RM++, SP1, SP2, SP3, Honeywell SP2, SPMini, SPMini2, SPMiniPlus A1AIR und einige OEM Versionen. Die neueste Version kennt auch S1C Sicherheitstechnik und Floureon/Beok313 Thermostate. 13 | Natürlich werden Fernsteuerungen wie RM2, RM Mini, RM Pro Phicomm, RM2 Home Plus, RM2 Home Plus GDT, RM2 Pro Plus, RM2 Pro Plus2 und RM2 Pro Plus BL unterstützt. Die Fernsteuerungen können Befehle lernen welche dann mit ioBroker gesendet werden können. 14 | 15 | Neue angelerne Befehle können umbenannt werden, Dabei wird auch der Befehls-ID umbenannt. 16 | 17 | Man kann auch eigene Einträge erzeugen indem man sie entweder 'CODE_'+code nennt oder unter AdminObjekte mittels dem Bleistift im 'nativ'-Bereich einen Eintrag `code` mit dem code (ohne _CODE voran) generierst, Allerdings müssen all diese im `.L.` Verzeichnis sein. 18 | 19 | Man kann Szenen bilden weche dann Mehrere Befehle hintereinander auszuführen. 20 | 21 | This is an ioBroker adapter for multiple Broadlink switch like RM2, RM3, RM Plus, SP1, SP2, SP3, Honeywell SP2, SPMini, SPMini2, SPMiniPlus and some OEM products from them. 22 | ALso remote controllers are supported like RM2, RM Mini, RM Pro Phicomm, RM2 Home Plus, RM2 Home Plus GDT, RM2 Pro Plus, RM2 Pro Plus2 and RM2 Pro Plus BL. Multiple controllers will generate their own entries and need to be trained separately. 23 | It scans the network to find compatible devices and installs them (currently only switches type SP?). 24 | 25 | If you learned states for RM* and then rename their name the state-ID will change to the new name as well! 26 | 27 | You can create also your own new commands in LearnedStates if you use 'code'+ your code as the value (with 'CODE_' preceeding the code or even better (because of it will remain if you rename the state) add a field 'code' to native with the admin.object pencil and put there the hex code (without 'CODE_'!). 28 | 29 | The adapter has fixed states to send codes from RM-devices or to learn them It can also send individual scenes (actions on multiple devices). 30 | 31 | If devices which are configured on a certain IP are not found again they will be flagged 'notReachable'! If they are connected again they will be useable normally. 32 | 33 | If a device is not answering 2 times in a row it's set to unreachable. ***notReachable*** devices will give a log warning message every 50 scans. After 10 scans the adapter will try to find them again on the same IP like before. If you changed IP please do a rescan. 34 | 35 | Please delete devices from admin.objects in case you remove them permanentely or renamed them in your router! 36 | 37 | ### Note 38 | 39 | SP1-Geräte können nicht abgefragt werden! 40 | 41 | ## Configuration 42 | 43 | * Enter prefix of network address in configuration which should be removed when generating device names 44 | * Enter the number of seconds between polls. On each poll all SP* devices expluding SP1 are asked what the switch status is. This feature can be disabled by setting the poll delay to 0. On some RM devices with temperature readout the temperature will be updated as well. 45 | 46 | ## How-To learn codes 47 | 48 | * In Objects of ioBroker you can find "broadlink2.[devicename].Learn or LearnRF for '+' type of devices". 49 | * For RM(x)+ (Plus) devices you get also a special RS-sweep-lear button which can learn more devices than on normal 433MHz. 50 | * Set this object to true. (you can click on the button in object view) 51 | * Now press some button on your remote control within 30 seconds. 52 | * An new Object should now appear within the Object "broadlink.[n].[devicename].LearnedState" with the name ">>> Rename learned @ YYYYMMDDTHHmmSS" 53 | * You can click on the button in object view to send the code. 54 | * To rename the item click on the name (starting with `>>>`) and change the name. It should not include `,`, `.` or `;` 55 | 56 | It is also possible to use the codes from [RM-Bridge](http://rm-bridge.fun2code.de/). 57 | Just create an object (state, type button) with value where you prepend "CODE_" or with native entry `code` without any 'CODE_'. 58 | 59 | ## Use scenes 60 | 61 | * Szenen bestehen aus ID's oder Zahlen mit `,` aneinandergereiht. Normal werden sie einfach im Abstand von 100ms hintereinander ausgelöst. Wird eine Zahl gefunden wird dort so viele ms gewartet bis zum nächsten Auslösen. Also `,SP:dose1, RM:your.L.StereoEin, 1000, RM:your.L.TVEin` würde die Steckdose einschalten, dann den Fernseher 1100ms nachher die Stereoanlage. Man kann auch Werte bei anderen (auch fremde) States durch Angabe des kompletten id's schalten: `hm-rpc.0.MEQ1435726.1.STATE` würde diesen einschalten! Übrigens, Bei boolschen Stateskann kann beim Einschalten das '=1/=on/=true/=ein' weggelassen werden da true der default-Wert ist. Beim Ausschalten wäre ein '=0/=false/=aus/=off' undbedingt notwendig! 62 | 63 | ## Use states 64 | 65 | * Sie können states anlegen welche mittels gelernten Signale ein- oder ausgeschaltet werden. 66 | * Damit geben sie den State-Namen an und die Signale (listem mit ',' getrennt) die das Gerät einschalten und auch solche die es ausschalten. 67 | * Bei boolschen States wird nur der erste Wert gesendet aber beim Senden von allen Werten wird der State gesetzt. Das ist von Vorteil wenn mehrere Tasten ein Gerät einschalten (oder Ausschalten) 68 | * Es kännen zum Ausschalten auch keine Signale gelistet werden dann werden die zum Einschalten verwendeten Werte in einer Liste 69 | * wird als Aus-Signal nur '+' angegeben werden die Werte im Ein-Bereich (hoffentlich 10 Signale) als Zehnertastatur verwendet die Wete bis zu 9999 senden kann. Wenn dann der State mit Wert 123 beschrieben wird wird dann '1' , '2' und dann '3' mit jeweils nach 1/3 Sekunde Verzögerung gesendet! 70 | 71 | Die Liste muss mit dem 0-Befehl beginnen und mit dem 9-Befehl enden! 72 | 73 | ## Use send messages to adapter 74 | 75 | Der Adapter versteht jetzt auch 'sendTo' Kommandos. 76 | 77 | * `debug`: `sendTo('broadlink2.0','debug','on')` (es geht auch 0,1,on,off,ein,aus,true,false) würde debug ein- oder ausschalten. 78 | * `get`: `sendTo('broadlink2.0','get', 'RM2:RMPROPLUS.Temperature'` kann der state von Werten abgefragt werden, man bekommt z.B. `{ val: 29.9, ack: true, ts: 1505839335870, q: 0, from: 'system.adapter.broadlink2.0', lc: 1505839335870 }` zurück 79 | * `switch`: schaltet Steckdose ein/aus je nach Text: `sendTo('broadlink2.0','switch','SP:your device id=on')` 80 | * `switch_on`/`switch_off`: sendTo('broadlink2.0','switch_on','SP:your device id')` 81 | * `send`: `sendTo('broadlink2.0','send','RM:yourdev.Learn')` würde lernen starten und `sendTo('broadlink2.0','send','RM:yourdev.L.yourid')` würde den code (oder eine Scene) senden. 82 | * `send_scene`: `sendTo('broadlink2.0','send_scene','scene xxx ')` würde den als message angegebenen Text als Szene ausführen 83 | * `send_code`: `sendTo('broadlink2.0','send_code','RM:your remote.CODE_xxxxx')` würde den CODE_xxxx vom R:your name senden. 84 | 85 | ## Floureon or Beok313 Thermostats 86 | 87 | * Sie meisten States können beschrieben werden. Die Uhrzeit kann mit einem Schreiben auf `_setTime` auf die ioBroker Systemzeit gesetzt werden. Das passiert auch automatisch bei Adapter-Start. 88 | 89 | ## Config additional dnew devices 90 | 91 | * Falls sie ein neues Gerät einbinden welches der Adapter noch nicht kennt wobei sie aber wissen dass es mit einem der Gerätetypen kompatible ist können sie es in der Konfig eintragen. Sie brauchen dazu den device-id (in hex oder dezimal) und die Klasse des Geätes (wie = A1,MP1,RM,RMP,S1C,SP1,SP2,SP3P,T1). Mit dem device ID von 0x1234 kann ein RM Plus mit `0x01234=RMP` hinzugefügt werden. 92 | 93 | ## Rename devices 94 | 95 | * Die Geräte werden normalerweise nach ihrem Host-Namen benannt (den sie selbst bei der Anmeldung angeben), falls das nicht der Fall ist wird eine Kombination aus Geräte-Type, ID und Mac Addresse Verwendet. Es werden die ersten 2 Buchstaben der Type mit ':' vorangestellt. Sie können das auf andere Namen umbenennen wenn sie z.B. `T1:BroadLink-OEM-T1-fa-83-7c=Beok313` ins Umbenenne-Feld der Konfiguration schreiben, der Name 'T1:BroadLink-OEM-T1-fa-83-7c' würde dann nicht verwendet und durch `Beok313` ersetzt werden. 96 | 97 | ## Debug Modus 98 | 99 | * Wenn sie ans Ende der hinzuzufüghenden Geräte ein `!` schreiben (auch bei leerer Liste) dann wird der Adapter Debug-Meldungen loggen auch wenn er im Admin auf 'info' gesetzt ist. 100 | 101 | ## Known-Issues 102 | 103 | * Die Codes bei mehrmaligen Lernen von den selben Tasten können unterschiedlich sein! 104 | * Manchmal können Geräte nicht gefunden werden da sie die Wlan-Verbindung verlogen wurde. Sie werden vom Adapter selbständig wieder gesucht aber können während der Abwesenheit nicht bediehnt werden. 105 | 106 | ## Important/Wichtig 107 | 108 | * Der Adapter braucht Nodejs Version >=V6 109 | 110 | ## Changelog 111 | 112 | ### 2.0.9 113 | 114 | * Beta für V2.1, RM4-Protokoll hinzugefügt und auch automatische Erkennung von Geräten mittels mac-Adresse hinzugefügt 115 | 116 | ### 2.0.1 117 | 118 | * Can handle Floureon/Beko thermostats (now with MQTT) 119 | * Can handle S1C security devices 120 | * Names device after their name or with their mac to reduce possibility of renaming 121 | * Can rename devices 122 | * Support compact mode 123 | * Can add device Id's/Types for new devices 124 | * New communication routines to find & re-find devices 125 | * New communication protocoll with devices which do not allow that devices can get commands from 2 sources intermixed 126 | 127 | 128 | ### 1.9.1 129 | * added anothe RM Mini code 130 | 131 | ### 1.8.1 132 | * Changed util.js and tests and added new devices 133 | 134 | ### Todo for later revisions 135 | * config of devices and codes 136 | 137 | ## Installation 138 | 139 | Mit ioBroker admin, npm install iobroker.broadlink2 oder von 140 | 141 | ## License 142 | 143 | The MIT License (MIT) 144 | 145 | Copyright (c) 2014-2019 Frank Joke 146 | 147 | Permission is hereby granted, free of charge, to any person obtaining a copy 148 | of this software and associated documentation files (the "Software"), to deal 149 | in the Software without restriction, including without limitation the rights 150 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 151 | copies of the Software, and to permit persons to whom the Software is 152 | furnished to do so, subject to the following conditions: 153 | 154 | The above copyright notice and this permission notice shall be included in 155 | all copies or substantial portions of the Software. 156 | 157 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 158 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 159 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 160 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 161 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 162 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 163 | THE SOFTWARE. 164 | -------------------------------------------------------------------------------- /doc/broadlink_discovery: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | 5 | import broadlink 6 | 7 | parser = argparse.ArgumentParser(fromfile_prefix_chars='@') 8 | parser.add_argument("--timeout", type=int, default=5, help="timeout to wait for receiving discovery responses") 9 | parser.add_argument("--ip", default=None, help="ip address to use in the discovery") 10 | parser.add_argument("--dst-ip", default="255.255.255.255", help="destination ip address to use in the discovery") 11 | args = parser.parse_args() 12 | 13 | print("Discovering...") 14 | devices = broadlink.discover(timeout=args.timeout, local_ip_address=args.ip, discover_ip_address=args.dst_ip) 15 | for device in devices: 16 | if device.auth(): 17 | print("###########################################") 18 | print(device.type) 19 | print("# broadlink_cli --type {} --host {} --mac {}".format(hex(device.devtype), device.host[0], 20 | ''.join(format(x, '02x') for x in device.mac))) 21 | print("Device file data (to be used with --device @filename in broadlink_cli) : ") 22 | print("{} {} {}".format(hex(device.devtype), device.host[0], ''.join(format(x, '02x') for x in device.mac))) 23 | if hasattr(device, 'check_temperature'): 24 | print("temperature = {}".format(device.check_temperature())) 25 | print("") 26 | else: 27 | print("Error authenticating with device : {}".format(device.host)) 28 | -------------------------------------------------------------------------------- /doc/new API for fan coil wifi thermostat.txt: -------------------------------------------------------------------------------- 1 | As a developer, you need to know the ID number of thermostat firstly. 2 | 3 | For example, the ID number is 4664. 4 | 1. As the ID is encrypted, you need to calculate the effect ID_Effect. 5 | ID_Effect=(ID+1)*65535+65535=12 39 ED C6 (4 bytes). 6 | 2. When you know the number of ID_Effect, you need to communicate with the server of domain name bestbeca.cn. 7 | The engineer should use the server of domain name to analyse the real ip address. The port is 25565. 8 | The developer need to send 12 39 ED C6 for the registration then use the tcp protocol for communication . 9 | 3. After that, all the parameters of thermostat will be controlled according to the 8 byte datas in the following protocol detail . 10 | 4. When thermostat sends collected temperature data to upper computer, the value of collected temperature should be multiplied by 2 and sent completely by the format of HEX because the accuracy is 0.5. 11 | For example: When the collected temperature is 25.5, the value sent by thermostat to the upper computer will be 33H (the demical is 51 ); 12 | Similarly, When upper computer sends set temperature data to thermostat, the value of set temperature should be multiplied by 2 and sent completely by the format of HEX because the accuracy is 0.5. 13 | For example: When the set temperature is 25.5, the value sent by upper computer to the thermostat should be 33H (the demical is 51). 14 | E.G.: Read Temper= 25.5C 15 | The send(or receive) value is 25.5*2=51 16 | The data in HEX =33H 17 | 18 | Communication Protocol And DataFormat Describe 19 | 20 | Declare: Following " 0X " Mean's HEX Values 21 | 22 | ;;======================================================================================================================= 23 | 24 | 1Data Package protocol as below: 25 | 26 | COMMAND + ID0 + ID1 + Data0 + Data1 + Data2 + Data3 + CheckSum 27 | 28 | 8 Bytes in total 29 | 30 | ;;======================================================================================================================= 31 | 32 | 2: CheckSum definition: 33 | 34 | (size: 8 Bits) 35 | CheckSum = (COMMAND + ID0 + ID1 + Data0 + Data1 + Data2 + Data3) & 0xFF ^ 0xA5 36 | 37 | ;;======================================================================================================================== 38 | 39 | 3: COMMAND definition 40 | 41 | ReadPC read data from thermostat 42 | WritePC transmit data to thermostat 43 | 44 | COMMAND is command opcodesize: 8 Bit commands as below 45 | command 46 | PC ------->thermostat 47 | [0XA0] --> PC read all data from thermostat 48 | [0XA1] --> PC transmit all data to thermostat 49 | [0XA8] --> Time setting 50 | 51 | 52 | command 53 | Thermostat--------->PC 54 | [0X50] -->thermostat returns all data to PC 55 | 56 | 57 | ;;=============================================================================== 58 | 59 | 4: ID0 , ID1 definition 60 | Note: 61 | ID0,ID1 must be 01 01,it is fixed,can not be other data 62 | 63 | 64 | ;;=============================================================================== 65 | 66 | 5: Data Package definition 67 | 68 | Data0 ~ Data3 is command operand size is 32 Bit (4 Bytes) 69 | ;;------------------------------ 70 | 71 | COMMAND1: 0XA0 72 | Data Package0XA0 + ID0 + ID1 + Data0 + Data1 + Data2 + Data3 + CheckSum 73 | Data0 00 74 | Data1 00 75 | Data2 00 76 | Data3 00 77 | ;;------------------------------ 78 | 79 | Response: 0x50 data returns 80 | 81 | ;;------------------------------ 82 | 83 | COMMAND2: 0XA1 84 | Data package0XA1 + ID0 + ID1 + Data0 + Data1 + Data2 + Data3 + CheckSum 85 | 86 | Data0 =>Bit1-Bit0 fan speed 87 | 00 -> auto 88 | 01 -> high 89 | 10 -> medium 90 | 11 -> low 91 | 92 | Bit2 must be 0 93 | Bit3 Manual and weekly programmable 94 | 0 -> Weekly programmable 95 | 1 -> Manual 96 | 97 | Bit4 turn-on/off 98 | 0 -> turn-off 99 | 1 -> turn-on 100 | 101 | Bit6-Bit5 heating and cooling 102 | 00->cooling 103 | 01->heating 104 | 10->ventilation 105 | 106 | Bit7 A flag for valve open 107 | 108 | Data1 => temperature calibration, -9-9 degree C(0XF7-0X09) 109 | 110 | Data2 => temperature setting, from 10 to 30 degree C (0X0A-0X1E) 111 | 112 | Data3 => ambient temperature 113 | ;;------------------------------ 114 | 115 | response: 0x50 returns OK 116 | ;;------------------------------ 117 | 118 | 119 | COMMAND9: 0XA8 120 | Data package0XA8 + ID0 + ID1 + Data0 + Data1 + Data2 + Data3 + CheckSum 121 | 122 | Data0 => second(BCD code) 123 | 124 | Data1 => minute(BCD code) 125 | 126 | Data2 => hour(BCD code) 127 | 128 | Data3 => week(BCD code) 129 | ;;------------------------------ 130 | 131 | response: 0x50 returns OK 132 | ;;------------------------------ 133 | 134 | 135 | ;;=============================================================================== 136 | ;;=============================================================================== 137 | response: 0x50 OK 138 | Data package0x50 + ID0 + ID1 + Data0 + Data1 + Data2 + Data3 + CheckSum 139 | 140 | Data0 => Bit1-Bit0 fan speed 141 | 00 -> auto 142 | 01 -> high 143 | 10 -> medium 144 | 11 -> low 145 | 146 | Bit2 0 147 | Bit3 Manual and weekly programmable 148 | 0 -> Weekly programmable 149 | 1 -> Manual 150 | 151 | Bit4 turn-on/off 152 | 0 -> turn-off 153 | 1 -> turn-on 154 | 155 | Bit6-Bit5 heating and cooling 156 | 00->cooling 157 | 01->heating 158 | 10->ventilation 159 | 160 | 161 | Bit7 A flag for valve open 162 | 163 | Data1 => temperature calibration, -9-9 degree C(0XF7-0X09) 164 | 165 | Data2 => temperature setting, from 10 to 30 degree C (0X0A-0X1E) 166 | 167 | Data3 => ambient temperature 168 | 169 | ;;=============================================================================== 170 | ;;=============================================================================== 171 | 172 | 6: Example of 2 commands 173 | 174 | PC is master thermosat is slave 175 | ---------------------------------------------------------- 176 | 177 | ---------------------------------------------------------- 178 | master read data from thermostat 179 | COMMAND == 0XA0 PC read data from thermostat 180 | ID0,ID1 => IP 181 | Data0 => 0xxx 182 | Data1 => 0xxx 183 | Data2 => 0xxx 184 | Data3 => 0xxx 185 | CheckSum 186 | 187 | slave respond 188 | COMMAND == 0X50 thermostat responds data to PC 189 | 190 | ID0,ID1 => 01 01 191 | 192 | Data0 => Bit1-Bit0 fan speed 193 | 00 -> auto 194 | 01 -> high 195 | 10 -> medium 196 | 11 -> low 197 | Bit2 0 198 | Bit3 standby 199 | 200 | Bit4 turn-on/off 201 | 0 -> turn-off 202 | 1 -> turn-on 203 | 204 | Bit6-Bit5 cooling and heating 205 | 00->cooling 206 | 01->heating 207 | 10->ventilation 208 | 209 | Bit7 valve 210 | 0->turn-off 211 | 1->turn-on 212 | 213 | Bit7 standby 214 | 215 | Data1 => temperature calibration 216 | 217 | Data2 => temperature setting from 10 to 30 degree 218 | 219 | Data3 => ambient temperature 220 | 221 | CheckSum 222 | 223 | ---------------------------------------------------------- 224 | 225 | ---------------------------------------------------------- 226 | PC transmit all data to thermostat 227 | COMMAND == 0XA1 PC transmit all data to thermostat 228 | 229 | ID0,ID1 => 01 01 230 | 231 | Data0 => Bit1-Bit0 fan speed 232 | 00 -> auto 233 | 01 -> high 234 | 10 -> medium 235 | 11 -> low 236 | Bit2 0 237 | Bit3 standby 238 | 239 | Bit4 turn-on/off 240 | 0 -> turn-off 241 | 1 -> turn-on 242 | 243 | Bit6-Bit5 cooling and heating 244 | 00->cooling 245 | 01->heating 246 | 10->ventilation 247 | 248 | Bit7 valve 249 | 0->turn-off 250 | 1->turn-on 251 | 252 | 253 | 254 | Data1 => temperature calibration 255 | 256 | Data2 => temperature setting from 10 to 30 degree 257 | 258 | Data3 => standby 259 | 260 | CheckSum 261 | 262 | slave response 263 | COMMAND == 0X50 PC transmit all data to thermostat OK 264 | 265 | ---------------------------------------------------------- 266 | -------------------------------------------------------------------------------- /doc/otherProjectSources/SmartHomeDIY.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const _blEvnt = 'brl:'; 3 | const crypto = require('crypto'); 4 | var BLK = {}; 5 | BLK.Commands = { 6 | Hello: [0x6, 0x7], 7 | Discover: [0x1a, 0x1b], 8 | Join: [0x14, 0x15], 9 | Auth: [0x65, 0x3e9], 10 | TogglePower: [0x6a, 0x3ee] 11 | }; 12 | BLK.Hello = { 13 | Request: (ip, port) => { 14 | var res = { 15 | command: BLK.Commands.Hello, 16 | }; 17 | 18 | if (ip && port) { 19 | var buffer = Buffer.alloc(40); 20 | var d = new Date(); 21 | var os = d.getTimezoneOffset() / 60 * -1; 22 | buffer.writeUInt32LE(os, 0); 23 | buffer.writeUInt16LE(d.getFullYear(), 4); 24 | buffer.writeUInt8(d.getSeconds(), 6); 25 | buffer.writeUInt8(d.getMinutes(), 7); 26 | buffer.writeUInt8(d.getHours(), 8); 27 | buffer.writeUInt8(d.getDay(), 9); 28 | buffer.writeUInt8(d.getDate(), 10); 29 | buffer.writeUInt8(d.getMonth(), 11); 30 | buffer.writeInt32LE(_parseIp(ip), 16); 31 | buffer.writeUInt16LE(port, 20); 32 | res.payload = buffer; 33 | } 34 | return res; 35 | }, 36 | Response: (buffer) => { 37 | var res = {}; 38 | res.ip = _readIp(buffer, 54); 39 | res.mac = _readMac(buffer, 58); 40 | res.type = _readType(buffer, 64); 41 | 42 | return res; 43 | } 44 | }; 45 | BLK.Discover = { 46 | Request: (target) => { 47 | var res = { 48 | command: BLK.Commands.Discover, 49 | target: target 50 | }; 51 | return res; 52 | }, 53 | Response: (buffer) => { 54 | var res = {}; 55 | 56 | res.networks = []; 57 | var offset = 48; 58 | var c = buffer.readUInt8(offset); 59 | offset += 4; 60 | for (; offset <= c * 64; offset += 64) { 61 | var l = buffer.readUInt8(offset + 32 + 4); 62 | var n = { 63 | ssid: buffer.toString('ascii', offset, offset + l), 64 | x1: buffer.readUInt8(offset + 32 + 4 + 12), 65 | encryption: buffer.readUInt8(offset + 32 + 4 + 24) 66 | }; 67 | res.networks.push(n); 68 | } 69 | 70 | return res; 71 | } 72 | }; 73 | BLK.Join = { 74 | Request: (target, ssid, pwd, security) => { 75 | var res = { 76 | isPublic: true, 77 | command: BLK.Commands.Join, 78 | target: target 79 | }; 80 | if (ssid && pwd) { 81 | var buffer = Buffer.alloc(128); 82 | buffer.writeUInt8(ssid.length, 124); 83 | buffer.writeUInt8(pwd.length, 125); 84 | buffer.write(ssid, 60, 'ascii'); 85 | buffer.write(pwd, 92, 'ascii'); 86 | var s = security || ((!pwd || pwd == '') ? 0 : 4); //(0 = none, 1 = WEP, 2 = WPA1, 3 = WPA2, 4 = WPA1/2) 87 | buffer.writeUInt8(s, 126); 88 | res.payload = buffer; 89 | } 90 | 91 | return res; 92 | }, 93 | Response: () => { 94 | var res = {}; 95 | //nothing here - just ACK 96 | return res; 97 | } 98 | }; 99 | BLK.Auth = { 100 | Request: (target) => { 101 | var res = { 102 | command: BLK.Commands.Auth, 103 | target: target, 104 | isEncrypted: true 105 | }; 106 | if (!target) return res; 107 | var buffer = Buffer.alloc(80); 108 | var key = crypto.randomBytes(16); //target.key || BLK.key; 109 | key.copy(buffer, 4); // 0x4 : Shared key (16 bytes) 110 | 111 | //buffer.writeUInt8(0x1,30); // 0x1e : 0x1 112 | buffer.writeUInt8(0x1, 45); // 0x2d : 0x1 113 | buffer.write('Khone alone', 48, 'ascii'); // 0x30 : Device name 114 | res.payload = buffer; 115 | 116 | return res; 117 | }, 118 | Response: (buffer, target) => { 119 | var res = {}; 120 | var data = _decryptPayload(buffer, BLK.key); 121 | var key = Buffer.alloc(16); 122 | data.copy(key, 0, 4, 20); // 0x4 : key in payload 123 | var id = data.readInt32LE(0); // 0x0 : device id in payload 124 | res.key = key; 125 | res.id = id; 126 | res.target = target; //TODO do not need to return res, return target itself 127 | console.log('INFO | %s (#%s) key is %s', target.kind, res.id, res.key.toString('hex')); 128 | return res; 129 | } 130 | }; 131 | BLK.TogglePower = { 132 | Request: (target, state) => { 133 | var res = { 134 | command: BLK.Commands.TogglePower, 135 | target: target, 136 | isEncrypted: true 137 | }; 138 | if (target && target.id && target.key) { 139 | var buffer = Buffer.alloc(16); 140 | buffer.writeUInt8((state !== null) ? 2 : 1, 0); // 0x0 : toggle->value=2, check->value=1 141 | buffer.writeUInt8(state ? 1 : 0, 4); // 0x4 : 1: on, 2: off 142 | res.payload = buffer; 143 | } 144 | return res; 145 | }, 146 | Response: (buffer, target) => { 147 | var res = { 148 | target: target 149 | }; 150 | var err = buffer.readUInt16LE(34); // 0x22 : Error 151 | if (err === 0) { 152 | var data = _decryptPayload(buffer, target.key); 153 | res.state = data.readUInt8(4) ? 'ON' : 'OFF'; // 0x4 : State 154 | if (data.length > 16) { 155 | //this is info message 156 | //TODO: parse and learn 157 | console.log('==>', data.toString('hex')); 158 | } 159 | } else { 160 | console.log('ERR | Error %s getting device %s power state', err, target.id); 161 | } 162 | return res; 163 | } 164 | }; 165 | BLK.getPacket = (message, deviceId = 0x7D00, currentCID = [0xa5, 0xaa, 0x55, 0x5a, 0xa5, 0xaa, 0x55, 0x0]) => { 166 | var packet,cid,cs; 167 | if (!message.payload) message.payload = Buffer.alloc(0); 168 | var isBroadcast = !message.target || !message.target.ip || message.target.ip == '255.255.255.255' || message.target.ip == '224.0.0.251'; 169 | //QUIC header 170 | if (isBroadcast || message.isPublic) { 171 | packet = Buffer.alloc(8); //0x8 padding, Public flag = 0x0 172 | //multicast - PKN:0 173 | //merge payload right away 174 | if (message.payload.length < 40) { // minimum payload length 175 | var filler = Buffer.alloc(40 - message.payload.length); 176 | message.payload = Buffer.concat([message.payload, filler]); 177 | } 178 | packet = Buffer.concat([packet, message.payload]); 179 | } else { 180 | packet = Buffer.alloc(56); //0x38 total custom header length 181 | packet.writeUInt8(0x5a, 0); //Version:0, Reset:Yes, CID:0x2,Packet #1, Multipath:Yes 182 | cid = Buffer.from(message.target.CID || currentCID); 183 | cid.copy(packet, 1); 184 | //tag:0x0, tag #:0x0, padding: 0 185 | } 186 | packet.writeUInt16LE(deviceId, 36); // 0x24 : Device ID 187 | packet.writeUInt8(message.command[0], 38); // 0x26 : Command 188 | 189 | if (!isBroadcast && !message.isPublic) { 190 | BLK.mgs = BLK.mgs || 0; 191 | packet.writeUInt16LE(BLK.mgs++ & 0xFFFF, 40); // 0x28 : Send Counter 192 | 193 | if (message.target.mac) { 194 | var m = message.target.mac.split(':').reverse(); 195 | var offset = 42; // 0x2a : MAC 196 | for (var i = 0; i < m.length; i++) { 197 | packet.writeUInt8(parseInt(m[i], 16), offset++); 198 | } 199 | } 200 | if (message.target.id) { 201 | packet.writeUInt32LE(48); // 0x30 : Device ID 202 | } 203 | if (message.payload.length > 0) { 204 | cs = _cs(message.payload); 205 | packet.writeUInt16LE(cs, 52); // 0x34 : Header Checksum 206 | } 207 | if (message.isEncrypted) { 208 | 209 | BLK.key = Buffer.from([0x09, 0x76, 0x28, 0x34, 0x3f, 0xe9, 0x9e, 0x23, 0x76, 0x5c, 0x15, 0x13, 0xac, 0xcf, 0x8b, 0x02]); 210 | BLK.iv = Buffer.from([0x56, 0x2e, 0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58]); 211 | var key = message.target.key || BLK.key; 212 | var cipher = crypto.createCipheriv('aes-128-cbc', key, BLK.iv); 213 | cipher.setAutoPadding(false); 214 | message.payload = Buffer.concat([cipher.update(message.payload), cipher.final()]); 215 | } 216 | 217 | packet = Buffer.concat([packet, message.payload]); 218 | } 219 | cs = _cs(packet); 220 | packet.writeUInt16LE(cs, 32); // 0x20 : Full checksum 221 | return packet; 222 | }; 223 | var _decryptPayload = (buffer, key) => { 224 | var data = Buffer.alloc(buffer.length - 56); // 0x38 : Encrypted payload 225 | buffer.copy(data, 0, 56, buffer.length); 226 | 227 | var decipher = crypto.createDecipheriv('aes-128-cbc', key, BLK.iv); 228 | decipher.setAutoPadding(false); 229 | return Buffer.concat([decipher.update(data), decipher.final()]); 230 | }; 231 | var _parseIp = (ip) => { 232 | var o = ip.split('.'); 233 | var res = 0; 234 | for (var i = 0; i < o.length; ++i) { 235 | var p = parseInt(o[i]); 236 | res |= p << ((o.length - i) * 8); 237 | } 238 | return res; 239 | }; 240 | var _readIp = (buffer, start) => { 241 | var ip = buffer.readInt32LE(start); 242 | var p = []; 243 | for (var s = 0; s <= 24; s += 8) { 244 | p.push(((ip >> s) & 0xFF).toString()); 245 | } 246 | return p.join('.'); 247 | }; 248 | var _readMac = (buffer, start) => { 249 | var mac = []; 250 | for (var i = start; i < start + 6; i++) { 251 | mac.push(buffer.readUInt8(i).toString(16)); 252 | } 253 | return mac.reverse().join(':'); 254 | }; 255 | var _cs = (buffer) => (0xbeaf + Array.prototype.slice.call(buffer, 0).reduce((p, c) => (p + c))) & 0xffff; 256 | var _readType = (buffer, start) => { //TODO: Maybe i will call it translate? :) 257 | var type = buffer.toString('utf8', start, buffer.length); 258 | if (type.match('智能插座').length > 0) return 'SMART SOCKET'; 259 | else return 'UNDEFINED'; 260 | }; 261 | // eslint-disable-next-line complexity 262 | var _readDeviceType = (buffer) => { 263 | var type = buffer.readUInt16LE(36); 264 | switch (type) { 265 | case 0: 266 | return 'SP1'; 267 | case 0x2711: 268 | return 'SP2'; 269 | case 0x2719: 270 | case 0x7919: 271 | case 0x271a: 272 | case 0x791a: 273 | return 'Honeywell SP2'; 274 | case 0x2720: 275 | return 'SPMini'; 276 | case 0x753e: 277 | return 'SP3'; 278 | case 0x2728: 279 | return 'SPMini2'; 280 | case 0x2733: 281 | case 0x273e: 282 | return 'SPMini OEM'; 283 | case 0x2736: 284 | return 'SPMiniPlus'; 285 | case 0x2712: 286 | return 'RM2'; 287 | case 0x2737: 288 | return 'RM Mini'; 289 | case 0x273d: 290 | return 'RM Pro Phicomm'; 291 | case 0x2783: 292 | return 'RM2 Home Plus'; 293 | case 0x277c: 294 | return 'RM2 Home Plus GDT'; 295 | case 0x272a: 296 | return 'RM2 Pro Plus'; 297 | case 0x2787: 298 | return 'RM2 Pro Plus2'; 299 | case 0x278b: 300 | return 'RM2 Pro Plus BL'; 301 | case 0x278f: 302 | return 'RM Mini Shate'; 303 | case 0x2714: 304 | return 'A1'; 305 | case 0x4EB5: 306 | return 'MP1'; 307 | default: 308 | if (type >= 0x7530 & type <= 0x7918) return 'SPMini2 OEM'; 309 | else return 'Unknown'; 310 | } 311 | }; 312 | BLK.getName = function (value) { 313 | return Object.keys(BLK.Commands).find(key => Array.isArray(value) ? BLK.Commands[key] === value : BLK.Commands[key].includes(value)); 314 | }; 315 | BLK.get = function (value) { 316 | var m = BLK.getName(value); 317 | if (!m) return null; 318 | return this[m]; 319 | }; 320 | BLK.getTrigger = function (msg) { 321 | var m = BLK.get(msg.command); 322 | if (m) { 323 | var n = BLK.getName(msg.command); 324 | return _blEvnt + n; 325 | } 326 | return null; 327 | }; 328 | BLK.parse = function (buffer, targets) { 329 | if (buffer.length < 48) { 330 | console.log('ERR | Response message is too short (%d bytes)', buffer.length); 331 | return null; 332 | } 333 | var cs = buffer.readUInt16LE(32); 334 | buffer.writeUInt16LE(0x0, 32); 335 | if (_cs(buffer) != cs) { 336 | console.log('ERR | Wrong incoming message format : ', JSON.stringify(buffer)); 337 | return null; 338 | } 339 | //header 340 | /*if(buffer.readUInt8(0) & 2){ //this is public reset 341 | //hack to workout JS bug! 342 | var cid = []; 343 | for(var i=1;i<=8;i++) { 344 | cid.push(buffer[i]); 345 | } 346 | 347 | //attach it to device 348 | }*/ 349 | var command = buffer.readUInt16LE(38); 350 | var device = _readDeviceType(buffer); 351 | var srs = _readMac(buffer, 42); 352 | var msg = BLK.get(command); 353 | if (!msg) { 354 | console.log('TODO | Unknown incoming message 0x%s', command.toString(16)); 355 | return null; 356 | } 357 | var evt = BLK.getTrigger(msg.Request()); 358 | var target = targets.find(t => t.id === evt); 359 | var res = msg.Response(buffer, target ? target.target : null); 360 | res.event = evt; 361 | res.name = BLK.getName(command); 362 | res.srs = srs; 363 | res.kind = device; 364 | return res; 365 | }; 366 | module.exports = BLK; -------------------------------------------------------------------------------- /doc/otherProjectSources/async.txt: -------------------------------------------------------------------------------- 1 | found asynf cunction addChannelToEnumAsync in adapter: 2 | found asynf cunction addStateToEnumAsync in adapter: 3 | found asynf cunction calculatePermissionsAsync in adapter: 4 | found asynf cunction checkGroupAsync in adapter: 5 | found asynf cunction checkPasswordAsync in adapter: 6 | found asynf cunction chmodFileAsync in adapter: 7 | found asynf cunction createChannelAsync in adapter: 8 | found asynf cunction createDeviceAsync in adapter: 9 | found asynf cunction createStateAsync in adapter: 10 | found asynf cunction deleteChannelAsync in adapter: 11 | found asynf cunction deleteChannelFromEnumAsync in adapter: 12 | found asynf cunction deleteDeviceAsync in adapter: 13 | found asynf cunction deleteStateAsync in adapter: 14 | found asynf cunction deleteStateFromEnumAsync in adapter: 15 | found asynf cunction delFileAsync in adapter: 16 | found asynf cunction delForeignObjectAsync in adapter: 17 | found asynf cunction delForeignStateAsync in adapter: 18 | found asynf cunction delObjectAsync in adapter: 19 | found asynf cunction delStateAsync in adapter: 20 | found asynf cunction extendForeignObjectAsync in adapter: 21 | found asynf cunction extendObjectAsync in adapter: 22 | found asynf cunction findForeignObjectAsync in adapter: 23 | found asynf cunction getAdapterObjectsAsync in adapter: 24 | found asynf cunction getBinaryStateAsync in adapter: 25 | found asynf cunction getCertificatesAsync in adapter: 26 | found asynf cunction getChannelsOfAsync in adapter: 27 | found asynf cunction getDevicesAsync in adapter: 28 | found asynf cunction getEnumAsync in adapter: 29 | found asynf cunction getEnumsAsync in adapter: 30 | found asynf cunction getForeignObjectAsync in adapter: 31 | found asynf cunction getForeignObjectsAsync in adapter: 32 | found asynf cunction getForeignStateAsync in adapter: 33 | found asynf cunction getForeignStatesAsync in adapter: 34 | found asynf cunction getHistoryAsync in adapter: 35 | found asynf cunction getObjectAsync in adapter: 36 | found asynf cunction getPortAsync in adapter: 37 | found asynf cunction getStateAsync in adapter: 38 | found asynf cunction getStatesAsync in adapter: 39 | found asynf cunction getStatesOfAsync in adapter: 40 | found asynf cunction mkdirAsync in adapter: 41 | found asynf cunction readDirAsync in adapter: 42 | found asynf cunction readFileAsync in adapter: 43 | found asynf cunction renameAsync in adapter: 44 | found asynf cunction sendToAsync in adapter: 45 | found asynf cunction sendToHostAsync in adapter: 46 | found asynf cunction setBinaryStateAsync in adapter: 47 | found asynf cunction setForeignObjectAsync in adapter: 48 | found asynf cunction setForeignObjectNotExistsAsync in adapter: 49 | found asynf cunction setForeignStateAsync in adapter: 50 | found asynf cunction setForeignStateChangedAsync in adapter: 51 | found asynf cunction setObjectAsync in adapter: 52 | found asynf cunction setObjectNotExistsAsync in adapter: 53 | found asynf cunction setPasswordAsync in adapter: 54 | found asynf cunction setStateAsync in adapter: 55 | found asynf cunction setStateChangedAsync in adapter: 56 | found asynf cunction subscribeForeignStatesAsync in adapter: 57 | found asynf cunction subscribeStatesAsync in adapter: 58 | found asynf cunction unlinkAsync in adapter: 59 | found asynf cunction unsubscribeForeignStatesAsync in adapter: 60 | found asynf cunction unsubscribeStatesAsync in adapter: 61 | -------------------------------------------------------------------------------- /doc/otherProjectSources/myAdapter.old.js: -------------------------------------------------------------------------------- 1 | /** 2 | * iobroker MyAdapter class V1.1.0 from broadlink2 3 | * (c) 2016- 4 | * MIT License 5 | */ 6 | /*jshint -W089, -W030 */ 7 | // jshint node: true, esversion: 6, strict: true, undef: true, unused: true 8 | "use strict"; 9 | const util = require('util'), 10 | http = require('http'), 11 | https = require('https'), 12 | url = require('url'), 13 | fs = require('fs'), 14 | exec = require('child_process').exec, 15 | assert = require('assert'); 16 | 17 | var adapter, that, main, messages, timer, stateChange, objChange, unload, name, stopping = false, 18 | inDebug = false, 19 | objects = {}, 20 | states = {}; 21 | 22 | const slog = (adapter, log, text) => adapter && adapter.log && typeof adapter.log[log] === 'function' ? 23 | adapter.log[log](text) : 24 | console.log(log + ': ' + text); 25 | 26 | class MyAdapter { 27 | constructor(adapter, main) { 28 | if (adapter && main) 29 | MyAdapter.init(adapter, main); 30 | return MyAdapter; 31 | } 32 | 33 | static processMessage(obj) { 34 | return (obj.command === 'debug' ? Promise.resolve(`debug set to '${inDebug = this.parseLogic(obj.message)}'`) : messages(obj)) 35 | .then(res => this.D(`Message from '${obj.from}', command '${obj.command}', message '${this.S(obj.message)}' executed with result:"${this.S(res)}"`, res), 36 | err => this.W(`invalid Message ${this.O(obj)} caused error ${this.O(err)}`, err)) 37 | .then(res => obj.callback ? adapter.sendTo(obj.from, obj.command, res, obj.callback) : undefined) 38 | .then(() => this.c2p(adapter.getMessage)().then(obj => obj ? this.processMessage(obj) : true)); 39 | } 40 | 41 | 42 | static initAdapter() { 43 | states = {}; 44 | this.D(`Adapter ${this.ains} starting.`); 45 | this.getObjectList = this.c2p(adapter.objects.getObjectList); 46 | this.getForeignState = this.c2p(adapter.getForeignState); 47 | this.setForeignState = this.c2p(adapter.setForeignState); 48 | this.getState = this.c2p(adapter.getState); 49 | this.setState = this.c2p(adapter.setState); 50 | 51 | return (!adapter.config.forceinit ? 52 | Promise.resolve({ 53 | rows: [] 54 | }) : 55 | this.getObjectList({ 56 | startkey: this.ain, 57 | endkey: this.ain + '\u9999' 58 | })) 59 | .then(res => res.rows.length > 0 ? this.D(`will remove ${res.rows.length} old states!`, res) : res) 60 | .then(res => this.seriesOf(res.rows, (i) => this.removeState(i.doc.common.name), 2)) 61 | .then(res => res, err => this.E('err from MyAdapter.series: ' + err)) 62 | .then(() => this.getObjectList({ 63 | include_docs: true 64 | })) 65 | .then(res => { 66 | res = res && res.rows ? res.rows : []; 67 | objects = {}; 68 | for (let i of res) 69 | objects[i.doc._id] = i.doc; 70 | if (objects['system.config'] && objects['system.config'].common.language) 71 | adapter.config.lang = objects['system.config'].common.language; 72 | if (objects['system.config'] && objects['system.config'].common.latitude) { 73 | adapter.config.latitude = parseFloat(objects['system.config'].common.latitude); 74 | adapter.config.longitude = parseFloat(objects['system.config'].common.longitude); 75 | } 76 | return res.length; 77 | }, err => this.E('err from getObjectList: ' + err, 'no')) 78 | .then(len => this.D(`${adapter.name} received ${len} objects with config ${Object.keys(adapter.config)}`)) 79 | .catch(err => this.W(`Error in adapter.ready: ${err}`)) 80 | .then(() => { 81 | if (stateChange) adapter.subscribeStates('*'); 82 | if (objChange) adapter.subscribeObjects('*'); 83 | }); 84 | } 85 | 86 | static init(ori_adapter, ori_main) { 87 | assert(!adapter, `myAdapter:(${ori_adapter.name}) defined already!`); 88 | adapter = ori_adapter; 89 | assert(adapter && adapter.name, 'myAdapter:(adapter) no adapter here!'); 90 | name = adapter.name; 91 | main = typeof ori_main === 'function' ? ori_main : () => this.W(`No 'main() defined for ${adapter.name}!`); 92 | messages = (mes) => Promise.resolve(this.W(`Message ${this.O(mes)} received and no handler defined!`)); 93 | 94 | this.writeFile = this.c2p(fs.writeFile); 95 | this.readFile = this.c2p(fs.readFile); 96 | this.getForeignObject = this.c2p(adapter.getForeignObject); 97 | this.setForeignObject = this.c2p(adapter.setForeignObject); 98 | this.getForeignObjects = this.c2p(adapter.getForeignObjects); 99 | this.getObject = this.c2p(adapter.getObject); 100 | this.deleteState = (id) => this.c1pe(adapter.deleteState)(id).catch(res => res === 'Not exists' ? Promise.resolve() : Promise.reject(res)); 101 | this.delObject = (id, opt) => this.c1pe(adapter.delObject)(id, opt).catch(res => res === 'Not exists' ? Promise.resolve() : Promise.reject(res)); 102 | this.delState = (id, opt) => this.c1pe(adapter.delState)(id, opt).catch(res => res === 'Not exists' ? Promise.resolve() : Promise.reject(res)); 103 | this.removeState = (id, opt) => this.delState(id, opt).then(() => this.delObject((delete this.states[id], id), opt)); 104 | this.setObject = this.c2p(adapter.setObject); 105 | this.createState = this.c2p(adapter.createState); 106 | this.extendObject = this.c2p(adapter.extendObject); 107 | 108 | adapter.on('message', (obj) => !!obj ? this.processMessage( 109 | this.D(`received Message ${this.O(obj)}`, obj)) : true) 110 | .on('unload', (callback) => this.stop(false, callback)) 111 | .on('ready', () => this.initAdapter().then(main)) 112 | .on('objectChange', (id, obj) => obj && obj._id && objChange ? objChange(id, obj) : null) 113 | .on('stateChange', (id, state) => state && state.from !== 'system.adapter.' + this.ains && stateChange ? 114 | stateChange(this.D(`stateChange called for ${id} = ${this.O(state)}`, id), state).then(() => true, 115 | err => this.W(`Error in StateChange for ${id} = ${this.O(err)}`) 116 | ) : null); 117 | 118 | return that; 119 | } 120 | 121 | static J( /** string */ str, /** function */ reviewer) { 122 | let res; 123 | if (!str) 124 | return null; 125 | if (this.T(str) !== 'string') 126 | str = str.toString(); 127 | try { 128 | res = JSON.parse(str, reviewer); 129 | } catch (e) { 130 | res = { 131 | error: e, 132 | error_description: `${e} on string ${str}` 133 | }; 134 | } 135 | return res; 136 | } 137 | static nop(obj) { 138 | return obj; 139 | } 140 | static split(x, s) { 141 | return this.trim((typeof x === 'string' ? x : `${x}`).split(s)); 142 | } 143 | static trim(x) { 144 | return Array.isArray(x) ? x.map(this.trim) : typeof x === 'string' ? x.trim() : `${x}`.trim(); 145 | } 146 | static D(str, val) { 147 | return (inDebug ? 148 | slog(adapter, 'info', `debug: ${str}`) : 149 | slog(adapter, 'debug', str), val !== undefined ? val : str); 150 | } 151 | static I(l, v) { 152 | return (slog(adapter, 'info', l), v === undefined ? l : v); 153 | } 154 | static W(l, v) { 155 | return (slog(adapter, 'warn', l), v === undefined ? l : v); 156 | } 157 | static E(l, v) { 158 | return (slog(adapter, 'error', l), v === undefined ? l : v); 159 | } 160 | 161 | static get debug() { 162 | return inDebug; 163 | } 164 | static set debug(y) { 165 | inDebug = y; 166 | } 167 | static get timer() { 168 | return timer; 169 | } 170 | static set timer(y) { 171 | timer = y; 172 | } 173 | static get messages() { 174 | return messages; 175 | } 176 | static set messages(y) { 177 | messages = (assert(typeof y === 'function', 'Error: messages handler not a function!'), y); 178 | } 179 | static get stateChange() { 180 | return stateChange; 181 | } 182 | static set stateChange(y) { 183 | stateChange = (assert(typeof y === 'function', 'Error: StateChange handler not a function!'), y); 184 | } 185 | static get objChange() { 186 | return objChange; 187 | } 188 | static set objChange(y) { 189 | objChange = (assert(typeof y === 'function', 'Error: ObjectChange handler not a function!'), y); 190 | } 191 | static get unload() { 192 | return unload; 193 | } 194 | static set unload(y) { 195 | unload = (assert(typeof y === 'function', 'Error: unload handler not a function!'), y); 196 | } 197 | static get name() { 198 | return name; 199 | } 200 | static get states() { 201 | return states; 202 | } 203 | static get aObjects() { 204 | return adapter.objects; 205 | } 206 | static get objects() { 207 | return objects; 208 | } 209 | static set objects(y) { 210 | objects = y; 211 | } 212 | static get ains() { 213 | return name + '.' + adapter.instance; 214 | } 215 | static get ain() { 216 | return this.ains + '.'; 217 | } 218 | static get C() { 219 | return adapter.config; 220 | } 221 | 222 | static parseLogic(obj) { 223 | return this.includes(['0', 'off', 'aus', 'false', 'inactive'], obj.toString().trim().toLowerCase()) ? 224 | false : this.includes(['1', '-1', 'on', 'ein', 'true', 'active'], obj.toString().trim().toLowerCase()); 225 | } 226 | static clone(obj) { 227 | return JSON.parse(JSON.stringify(obj)); 228 | } 229 | static wait(time, arg) { 230 | return new Promise(res => setTimeout(res, time, arg)); 231 | } 232 | 233 | static P(pv, res, rej) { 234 | if (pv instanceof Promise) 235 | return pv; 236 | if (pv && typeof pv.then === 'function') 237 | return new Promise((rs, rj) => pv.then(rs, rj)); 238 | if (pv) 239 | return Promise.resolve(res || pv); 240 | return Promise.reject(rej || pv); 241 | } 242 | 243 | static pTimeout(pr, time, callback) { 244 | let t = parseInt(time); 245 | assert(typeof t === 'number' && t > 0, `pTimeout requires a positive number as second argument for the ms`); 246 | let st = null; 247 | assert(callback && typeof callback === 'function', `pTimeout requires optionally a function for callback as third argument`); 248 | return new Promise((resolve, reject) => { 249 | let rs = res => { 250 | if (st) clearTimeout(st); 251 | st = null; 252 | return resolve(res); 253 | }, 254 | rj = err => { 255 | if (st) clearTimeout(st); 256 | st = null; 257 | return reject(err); 258 | }; 259 | st = setTimeout(() => { 260 | st = null; 261 | reject(`timer ${t} run out`); 262 | }, t); 263 | if (callback) callback(rs, rj); 264 | this.P(pr).then(rs, rj); 265 | }); 266 | } 267 | 268 | static O(obj, level) { 269 | return util.inspect(obj, false, level || 2, false).replace(/\n/g, ' '); 270 | } 271 | static S(obj, level) { 272 | return typeof obj === 'string' ? obj : this.O(obj, level); 273 | } 274 | static N(fun) { 275 | return setTimeout.apply(null, [fun, 0].concat(Array.prototype.slice.call(arguments, 1))); 276 | } // move fun to next schedule keeping arguments 277 | static T(i, j) { 278 | let t = typeof i; 279 | if (t === 'object') { 280 | if (Array.isArray(i)) t = 'array'; 281 | else if (i instanceof RegExp) t = 'regexp'; 282 | else if (i === null) t = 'null'; 283 | } else if (t === 'number' && isNaN(i)) t = 'NaN'; 284 | return j === undefined ? t : this.T(j) === t; 285 | } 286 | static locDate(date) { 287 | return date instanceof Date ? 288 | new Date(date.getTime() - date.getTimezoneOffset() * 60000) : 289 | typeof date === 'string' ? 290 | new Date(Date.parse(date) - (new Date().getTimezoneOffset()) * 60000) : 291 | !isNaN(+date) ? 292 | new Date(+date - (new Date().getTimezoneOffset()) * 60000) : 293 | new Date(Date.now() - (new Date().getTimezoneOffset()) * 60000); 294 | } 295 | static dateTime(date) { 296 | return this.locDate(date).toISOString().slice(0, -5).replace('T', '@'); 297 | } 298 | static obToArray(obj) { 299 | return (Object.keys(obj).map(i => obj[i])); 300 | } 301 | static includes(obj, value) { 302 | return this.T(obj, {}) ? obj[value] !== undefined : 303 | this.T(obj, []) ? obj.find(x => x === value) !== undefined : obj === value; 304 | } 305 | 306 | static stop(dostop, callback) { 307 | if (stopping) return; 308 | stopping = true; 309 | if (timer) 310 | clearInterval(timer); 311 | timer = null; 312 | this.D(`Adapter disconnected and stopped with dostop(${dostop}) and callback(${!!callback})`); 313 | Promise.resolve(unload ? unload(dostop) : null) 314 | .then(() => callback && callback()) 315 | .catch(this.W) 316 | .then(() => dostop ? this.E("Adapter will exit in lates 2 sec!", setTimeout(process.exit, 2000, 55)) : null); 317 | } 318 | 319 | static seriesOf(obj, promfn, delay) { // fun gets(item) and returns a promise 320 | assert(typeof promfn === 'function', 'series(obj,promfn,delay) error: promfn is not a function!'); 321 | delay = parseInt(delay); 322 | let p = Promise.resolve(); 323 | const nv = [], 324 | f = delay > 0 ? (k) => p = p.then(() => promfn(k).then(res => this.wait(delay, nv.push(res)))) : 325 | (k) => p = p.then(() => promfn(k)); 326 | for (let item of obj) 327 | f(item); 328 | return p.then(() => nv); 329 | } 330 | 331 | static seriesIn(obj, promfn, delay) { // fun gets(item,object) and returns a promise 332 | assert(typeof promfn === 'function', 'series(obj,promfn,delay) error: promfn is not a function!'); 333 | delay = parseInt(delay); 334 | let p = Promise.resolve(); 335 | const nv = [], 336 | f = delay > 0 ? (k) => p = p.then(() => promfn(k).then(res => this.wait(delay, nv.push(res)))) : 337 | (k) => p = p.then(() => promfn(k)); 338 | for (let item in obj) 339 | f(item, obj); 340 | return p.then(() => nv); 341 | } 342 | 343 | static c2p(f) { 344 | assert(typeof f === 'function', 'c2p (f) error: f is not a function!'); 345 | return function () { 346 | const args = Array.prototype.slice.call(arguments); 347 | return new Promise((res, rej) => (args.push((err, result) => (err && rej(err)) || res(result)), f.apply(this, args))); 348 | }; 349 | } 350 | 351 | static c1p(f) { 352 | assert(typeof f === 'function', 'c1p (f) error: f is not a function!'); 353 | return function () { 354 | const args = Array.prototype.slice.call(arguments); 355 | return new Promise(res => (args.push((result) => res(result)), f.apply(this, args))); 356 | }; 357 | } 358 | 359 | static c1pe(f) { // one parameter != null = error 360 | assert(typeof f === 'function', 'c1pe (f) error: f is not a function!'); 361 | return function () { 362 | const args = Array.prototype.slice.call(arguments); 363 | return new Promise((res, rej) => (args.push((result) => !result ? res(result) : rej(result)), f.apply(this, args))); 364 | }; 365 | } 366 | 367 | static retry(nretry, fn, arg) { 368 | assert(typeof fn === 'function', 'retry (,fn,) error: fn is not a function!'); 369 | nretry = parseInt(nretry); 370 | return fn(arg).catch(err => nretry <= 0 ? Promise.reject(err) : this.retry(nretry - 1, fn, arg)); 371 | } 372 | 373 | static 374 | while ( /** function */ fw, /** function */ fn, /** number */ time) { 375 | assert(typeof fw === 'function' && typeof fn === 'function', 'retry (fw,fn,) error: fw or fn is not a function!'); 376 | time = parseInt(time) || 1; 377 | return !fw() ? Promise.resolve(true) : 378 | fn().then(() => true, () => true) 379 | .then(() => this.wait(time)) 380 | .then(() => this.while(fw, fn, time)); 381 | } 382 | 383 | static repeat( /** number */ nretry, /** function */ fn, arg) { 384 | assert(typeof fn === 'function', 'repeat (,fn,) error: fn is not a function!'); 385 | nretry = parseInt(nretry); 386 | return fn(arg) 387 | .then(res => Promise.reject(res)) 388 | .catch(res => nretry <= 0 ? Promise.resolve(res) : this.repeat(nretry - 1, fn, arg)); 389 | } 390 | 391 | static exec(command) { 392 | assert(typeof fn === 'string', 'exec (fn) error: fn is not a string!'); 393 | const istest = command.startsWith('!'); 394 | return new Promise((resolve, reject) => { 395 | exec(istest ? command.slice(1) : command, (error, stdout, stderr) => { 396 | if (istest && error) { 397 | error[stderr] = stderr; 398 | return reject(error); 399 | } 400 | resolve(stdout); 401 | }); 402 | }); 403 | } 404 | 405 | static url(turl, opt) { 406 | // this.D(`mup start: ${this.O(turl)}: ${this.O(opt)}`); 407 | if (this.T(turl) === 'string') 408 | turl = url.parse(turl.trim(), true); 409 | if (this.T(opt) === 'object') 410 | for (var i of Object.keys(opt)) 411 | if (i !== 'url') turl[i] = opt[i]; 412 | // this.D(`mup ret: ${this.O(turl)}`); 413 | return turl; 414 | } 415 | 416 | static request(opt, value, transform) { 417 | if (this.T(opt) === 'string') 418 | opt = this.url(opt.trim()); 419 | if (this.T(opt) !== 'object' && !(opt instanceof url.Url)) 420 | return Promise.reject(this.W(`Invalid opt or Url for request: ${this.O(opt)}`)); 421 | if (opt.url > '') 422 | opt = this.url(opt.url, opt); 423 | if (opt.json) 424 | if (opt.headers) opt.headers.Accept = 'application/json'; 425 | else opt.headers = { 426 | Accept: 'application/json' 427 | }; 428 | if (!opt.protocol) 429 | opt.protocol = 'http:'; 430 | let fun = opt.protocol.startsWith('https') ? https.request : http.request; 431 | // this.D(`opt: ${this.O(opt)}`); 432 | return new Promise((resolve, reject) => { 433 | let data = new Buffer(''), 434 | res; 435 | const req = fun(opt, function (result) { 436 | res = result; 437 | // MyAdapter.D(`status: ${MyAdapter.O(res.statusCode)}/${http.STATUS_CODES[res.statusCode]}`); 438 | res.setEncoding(opt.encoding ? opt.encoding : 'utf8'); 439 | if (MyAdapter.T(opt.status) === 'array' && opt.status.indexOf(res.statusCode) < 0) 440 | return reject(MyAdapter.D(`request for ${url.format(opt)} had status ${res.statusCode}/${http.STATUS_CODES[res.statusCode]} other than supported ${opt.status}`)); 441 | res.on('data', chunk => data += chunk) 442 | .on('end', () => { 443 | res.removeAllListeners(); 444 | req.removeAllListeners(); 445 | if (MyAdapter.T(transform) === 'function') 446 | data = transform(data); 447 | if (opt.json) { 448 | try { 449 | return resolve(JSON.parse(data)); 450 | } catch (e) { 451 | return err(`request JSON error ${MyAdapter.O(e)}`); 452 | } 453 | } 454 | return resolve(data); 455 | }) 456 | .on('close', () => err(`Connection closed before data was received!`)); 457 | }); 458 | 459 | function err(e, msg) { 460 | if (!msg) 461 | msg = e; 462 | res && res.removeAllListeners(); 463 | // req && req.removeAllListeners(); 464 | req && !req.aborted && req.abort(); 465 | // res && res.destroy(); 466 | MyAdapter.D('err in response:' + msg); 467 | return reject(msg); 468 | } 469 | 470 | if (opt.timeout) 471 | req.setTimeout(opt.timeout, () => err('request timeout Error: ' + opt.timeout + 'ms')); 472 | req.on('error', (e) => err('request Error: ' + MyAdapter.O(e))) 473 | .on('aborted', (e) => err('request aborted: ' + MyAdapter.O(e))); 474 | // write data to request body 475 | return req.end(value, opt.encoding ? opt.encoding : 'utf8'); 476 | }); 477 | } 478 | 479 | static get(url, retry) { // get a web page either with http or https and return a promise for the data, could be done also with request but request is now an external package and http/https are part of nodejs. 480 | const fun = typeof url === 'string' && url.trim().toLowerCase().startsWith('https') || 481 | url.protocol === 'https' ? https.get : http.get; 482 | return (new Promise((resolve, reject) => { 483 | fun(url, (res) => { 484 | if (res.statusCode !== 200) { 485 | const error = new Error(`Request Failed. Status Code: ${res.statusCode}`); 486 | res.resume(); // consume response data to free up memory 487 | return reject(error); 488 | } 489 | res.setEncoding('utf8'); 490 | let rawData = ''; 491 | res.on('data', (chunk) => rawData += chunk); 492 | res.on('end', () => resolve(rawData)); 493 | }).on('error', (e) => reject(e)); 494 | })).catch(err => !retry ? Promise.reject(err) : this.wait(100, retry - 1).then(a => this.get(url, a))); 495 | } 496 | 497 | static equal(a, b) { 498 | if (a === b) 499 | return true; 500 | let ta = this.T(a), 501 | tb = this.T(b); 502 | if (ta === tb) { 503 | if (ta === 'array' || ta === 'function' || ta === 'object') 504 | return JSON.stringify(a) === JSON.stringify(b); 505 | } else if (ta === 'string' && (tb === 'array' || tb === 'function' || tb === 'object') && a === this.O(b)) 506 | return true; 507 | return false; 508 | } 509 | 510 | static changeState(id, value, ack, always) { 511 | if (value === undefined) return Promise.resolve(); 512 | assert(typeof id === 'string', 'changeState (id,,,) error: id is not a string!'); 513 | always = always === undefined ? false : !!always; 514 | ack = ack === undefined ? true : !!ack; 515 | return this.getState(id) 516 | .then(st => st && !always && this.equal(st.val, value) && st.ack === ack ? Promise.resolve() : 517 | this.setState(this.D(`Change ${id} to ${this.O(value)} with ack: ${ack}`, id), value, ack)) 518 | .catch(err => this.W(`Error in MyAdapter.setState(${id},${value},${ack}): ${err}`, this.setState(id, value, ack))); 519 | } 520 | 521 | static makeState(ido, value, ack, always) { 522 | // this.D(`Make State ${this.O(ido)} and set value to:${this.O(value)} ack:${ack}`); ///TC 523 | ack = ack === undefined || !!ack; 524 | let id = ido; 525 | if (typeof id === 'string') 526 | ido = id.endsWith('Percent') ? { 527 | unit: "%" 528 | } : {}; 529 | else if (typeof id.id === 'string') { 530 | id = id.id; 531 | } else return Promise.reject(this.W(`Invalid makeState id: ${this.O(id)}`)); 532 | if (this.states[id]) 533 | return this.changeState(id, value, ack, always); 534 | // this.D(`Make State ${id} and set value to:${this.O(value)} ack:${ack}`); ///TC 535 | const st = { 536 | common: { 537 | name: id, // You can add here some description 538 | read: true, 539 | write: false, 540 | state: 'state', 541 | role: 'value', 542 | type: typeof value 543 | }, 544 | type: 'state', 545 | _id: id 546 | }; 547 | for (let i in ido) { 548 | if (i === 'native') { 549 | st.native = st.native || {}; 550 | for (let j in ido[i]) 551 | st.native[j] = ido[i][j]; 552 | } else if (i !== 'id' && i !== 'val') st.common[i] = ido[i]; 553 | } 554 | if (st.common.write) 555 | st.common.role = st.common.role.replace(/^value/, 'level'); 556 | // this.I(`will create state:${id} with ${this.O(st)}`); 557 | return this.extendObject(id, st, null) 558 | .then(x => this.states[id] = x) 559 | .then(() => st.common.state === 'state' ? this.changeState(id, value, ack, always) : true) 560 | .catch(err => this.D(`MS ${this.O(err)}`, id)); 561 | } 562 | } 563 | 564 | module.exports = MyAdapter; -------------------------------------------------------------------------------- /doc/otherProjectSources/python-broadlink-master.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iobroker-community-adapters/ioBroker.broadlink2/fc261d5ae259e556f1eb7f7c4988cd8d0c4c5165/doc/otherProjectSources/python-broadlink-master.zip -------------------------------------------------------------------------------- /doc/python-broadlink-master.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iobroker-community-adapters/ioBroker.broadlink2/fc261d5ae259e556f1eb7f7c4988cd8d0c4c5165/doc/python-broadlink-master.zip -------------------------------------------------------------------------------- /doc/python-broadlink-master/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /doc/python-broadlink-master/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Mike Ryan 4 | Copyright (c) 2016 Matthew Garrett 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 | -------------------------------------------------------------------------------- /doc/python-broadlink-master/README.md: -------------------------------------------------------------------------------- 1 | Python control for Broadlink RM2, RM3 and RM4 series controllers 2 | =============================================== 3 | 4 | A simple Python API for controlling IR/RF controllers from [Broadlink](http://www.ibroadlink.com/rm/). At present, the following devices are currently supported: 5 | 6 | * RM Pro (referred to as RM2 in the codebase) 7 | * A1 sensor platform devices are supported 8 | * RM3 mini IR blaster 9 | * RM4 and RM4C mini blasters 10 | 11 | There is currently no support for the cloud API. 12 | 13 | Example use 14 | ----------- 15 | 16 | Setup a new device on your local wireless network: 17 | 18 | 1. Put the device into AP Mode 19 | 1. Long press the reset button until the blue LED is blinking quickly. 20 | 2. Long press again until blue LED is blinking slowly. 21 | 3. Manually connect to the WiFi SSID named BroadlinkProv. 22 | 2. Run setup() and provide your ssid, network password (if secured), and set the security mode 23 | 1. Security mode options are (0 = none, 1 = WEP, 2 = WPA1, 3 = WPA2, 4 = WPA1/2) 24 | ``` 25 | import broadlink 26 | 27 | broadlink.setup('myssid', 'mynetworkpass', 3) 28 | ``` 29 | 30 | Discover available devices on the local network: 31 | ``` 32 | import broadlink 33 | 34 | devices = broadlink.discover(timeout=5) 35 | ``` 36 | 37 | Obtain the authentication key required for further communication: 38 | ``` 39 | devices[0].auth() 40 | ``` 41 | 42 | Enter learning mode: 43 | ``` 44 | devices[0].enter_learning() 45 | ``` 46 | 47 | Sweep RF frequencies: 48 | ``` 49 | devices[0].sweep_frequency() 50 | ``` 51 | 52 | Cancel sweep RF frequencies: 53 | ``` 54 | devices[0].cancel_sweep_frequency() 55 | ``` 56 | Check whether a frequency has been found: 57 | ``` 58 | found = devices[0].check_frequency() 59 | ``` 60 | (This will return True if the RM has locked onto a frequency, False otherwise) 61 | 62 | Attempt to learn an RF packet: 63 | ``` 64 | found = devices[0].find_rf_packet() 65 | ``` 66 | (This will return True if a packet has been found, False otherwise) 67 | 68 | Obtain an IR or RF packet while in learning mode: 69 | ``` 70 | ir_packet = devices[0].check_data() 71 | ``` 72 | (This will return None if the device does not have a packet to return) 73 | 74 | Send an IR or RF packet: 75 | ``` 76 | devices[0].send_data(ir_packet) 77 | ``` 78 | 79 | Obtain temperature data from an RM2: 80 | ``` 81 | devices[0].check_temperature() 82 | ``` 83 | 84 | Obtain sensor data from an A1: 85 | ``` 86 | data = devices[0].check_sensors() 87 | ``` 88 | 89 | Set power state on a SmartPlug SP2/SP3: 90 | ``` 91 | devices[0].set_power(True) 92 | ``` 93 | 94 | Check power state on a SmartPlug: 95 | ``` 96 | state = devices[0].check_power() 97 | ``` 98 | 99 | Check energy consumption on a SmartPlug: 100 | ``` 101 | state = devices[0].get_energy() 102 | ``` 103 | 104 | Set power state for S1 on a SmartPowerStrip MP1: 105 | ``` 106 | devices[0].set_power(1, True) 107 | ``` 108 | 109 | Check power state on a SmartPowerStrip: 110 | ``` 111 | state = devices[0].check_power() 112 | ``` 113 | -------------------------------------------------------------------------------- /doc/python-broadlink-master/broadlink/exceptions.py: -------------------------------------------------------------------------------- 1 | """Exceptions for Broadlink devices.""" 2 | 3 | 4 | class BroadlinkException(Exception): 5 | """Common base class for all Broadlink exceptions.""" 6 | pass 7 | 8 | 9 | class AuthenticationError(BroadlinkException): 10 | """Authentication error.""" 11 | pass 12 | 13 | 14 | class AuthorizationError(BroadlinkException): 15 | """Authorization error.""" 16 | pass 17 | 18 | 19 | class CommandNotSupportedError(BroadlinkException): 20 | """Command not supported error.""" 21 | pass 22 | 23 | 24 | class ConnectionClosedError(BroadlinkException): 25 | """Connection closed error.""" 26 | pass 27 | 28 | 29 | class DataValidationError(BroadlinkException): 30 | """Data validation error.""" 31 | pass 32 | 33 | 34 | class DeviceOfflineError(BroadlinkException): 35 | """Device offline error.""" 36 | pass 37 | 38 | 39 | class ReadError(BroadlinkException): 40 | """Read error.""" 41 | pass 42 | 43 | 44 | class SendError(BroadlinkException): 45 | """Send error.""" 46 | pass 47 | 48 | 49 | class SSIDNotFoundError(BroadlinkException): 50 | """SSID not found error.""" 51 | pass 52 | 53 | 54 | class StorageError(BroadlinkException): 55 | """Storage error.""" 56 | pass 57 | 58 | 59 | class UnknownError(BroadlinkException): 60 | """Unknown error.""" 61 | pass 62 | 63 | 64 | class WriteError(BroadlinkException): 65 | """Write error.""" 66 | pass 67 | 68 | 69 | FIRMWARE_ERRORS = { 70 | 0xffff: (AuthenticationError, "Authentication failed"), 71 | 0xfffe: (ConnectionClosedError, "You have been logged out"), 72 | 0xfffd: (DeviceOfflineError, "The device is offline"), 73 | 0xfffc: (CommandNotSupportedError, "Command not supported"), 74 | 0xfffb: (StorageError, "The device storage is full"), 75 | 0xfffa: (DataValidationError, "Structure is abnormal"), 76 | 0xfff9: (AuthorizationError, "Control key is expired"), 77 | 0xfff8: (SendError, "Send error"), 78 | 0xfff7: (WriteError, "Write error"), 79 | 0xfff6: (ReadError, "Read error"), 80 | 0xfff5: (SSIDNotFoundError, "SSID could not be found in AP configuration"), 81 | } 82 | 83 | 84 | def exception(error_code): 85 | """Return exception corresponding to an error code.""" 86 | try: 87 | exc, msg = FIRMWARE_ERRORS[error_code] 88 | return exc(msg) 89 | except KeyError: 90 | return UnknownError("Unknown error: " + hex(error_code)) 91 | 92 | 93 | def check_error(error): 94 | """Raise exception if an error occurred.""" 95 | error_code = error[0] | (error[1] << 8) 96 | if error_code: 97 | raise exception(error_code) 98 | -------------------------------------------------------------------------------- /doc/python-broadlink-master/broadlink_cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import base64 5 | import codecs 6 | import time 7 | 8 | import broadlink 9 | from broadlink.exceptions import ReadError, StorageError 10 | 11 | TICK = 32.84 12 | TIMEOUT = 30 13 | IR_TOKEN = 0x26 14 | 15 | 16 | def auto_int(x): 17 | return int(x, 0) 18 | 19 | 20 | def to_microseconds(bytes): 21 | result = [] 22 | # print bytes[0] # 0x26 = 38for IR 23 | index = 4 24 | while index < len(bytes): 25 | chunk = bytes[index] 26 | index += 1 27 | if chunk == 0: 28 | chunk = bytes[index] 29 | chunk = 256 * chunk + bytes[index + 1] 30 | index += 2 31 | result.append(int(round(chunk * TICK))) 32 | if chunk == 0x0d05: 33 | break 34 | return result 35 | 36 | 37 | def durations_to_broadlink(durations): 38 | result = bytearray() 39 | result.append(IR_TOKEN) 40 | result.append(0) 41 | result.append(len(durations) % 256) 42 | result.append(len(durations) / 256) 43 | for dur in durations: 44 | num = int(round(dur / TICK)) 45 | if num > 255: 46 | result.append(0) 47 | result.append(num / 256) 48 | result.append(num % 256) 49 | return result 50 | 51 | 52 | def format_durations(data): 53 | result = '' 54 | for i in range(0, len(data)): 55 | if len(result) > 0: 56 | result += ' ' 57 | result += ('+' if i % 2 == 0 else '-') + str(data[i]) 58 | return result 59 | 60 | 61 | def parse_durations(str): 62 | result = [] 63 | for s in str.split(): 64 | result.append(abs(int(s))) 65 | return result 66 | 67 | 68 | parser = argparse.ArgumentParser(fromfile_prefix_chars='@') 69 | parser.add_argument("--device", help="device definition as 'type host mac'") 70 | parser.add_argument("--type", type=auto_int, default=0x2712, help="type of device") 71 | parser.add_argument("--host", help="host address") 72 | parser.add_argument("--mac", help="mac address (hex reverse), as used by python-broadlink library") 73 | parser.add_argument("--temperature", action="store_true", help="request temperature from device") 74 | parser.add_argument("--energy", action="store_true", help="request energy consumption from device") 75 | parser.add_argument("--check", action="store_true", help="check current power state") 76 | parser.add_argument("--checknl", action="store_true", help="check current nightlight state") 77 | parser.add_argument("--turnon", action="store_true", help="turn on device") 78 | parser.add_argument("--turnoff", action="store_true", help="turn off device") 79 | parser.add_argument("--turnnlon", action="store_true", help="turn on nightlight on the device") 80 | parser.add_argument("--turnnloff", action="store_true", help="turn off nightlight on the device") 81 | parser.add_argument("--switch", action="store_true", help="switch state from on to off and off to on") 82 | parser.add_argument("--send", action="store_true", help="send command") 83 | parser.add_argument("--sensors", action="store_true", help="check all sensors") 84 | parser.add_argument("--learn", action="store_true", help="learn command") 85 | parser.add_argument("--rfscanlearn", action="store_true", help="rf scan learning") 86 | parser.add_argument("--learnfile", help="save learned command to a specified file") 87 | parser.add_argument("--durations", action="store_true", 88 | help="use durations in micro seconds instead of the Broadlink format") 89 | parser.add_argument("--convert", action="store_true", help="convert input data to durations") 90 | parser.add_argument("--joinwifi", nargs=2, help="Args are SSID PASSPHRASE to configure Broadlink device with") 91 | parser.add_argument("data", nargs='*', help="Data to send or convert") 92 | args = parser.parse_args() 93 | 94 | if args.device: 95 | values = args.device.split() 96 | type = int(values[0], 0) 97 | host = values[1] 98 | mac = bytearray.fromhex(values[2]) 99 | elif args.mac: 100 | type = args.type 101 | host = args.host 102 | mac = bytearray.fromhex(args.mac) 103 | 104 | if args.host or args.device: 105 | dev = broadlink.gendevice(type, (host, 80), mac) 106 | dev.auth() 107 | 108 | if args.joinwifi: 109 | broadlink.setup(args.joinwifi[0], args.joinwifi[1], 4) 110 | 111 | if args.convert: 112 | data = bytearray.fromhex(''.join(args.data)) 113 | durations = to_microseconds(data) 114 | print(format_durations(durations)) 115 | if args.temperature: 116 | print(dev.check_temperature()) 117 | if args.energy: 118 | print(dev.get_energy()) 119 | if args.sensors: 120 | try: 121 | data = dev.check_sensors() 122 | except: 123 | data = {} 124 | data['temperature'] = dev.check_temperature() 125 | for key in data: 126 | print("{} {}".format(key, data[key])) 127 | if args.send: 128 | data = durations_to_broadlink(parse_durations(' '.join(args.data))) \ 129 | if args.durations else bytearray.fromhex(''.join(args.data)) 130 | dev.send_data(data) 131 | if args.learn or (args.learnfile and not args.rfscanlearn): 132 | dev.enter_learning() 133 | print("Learning...") 134 | start = time.time() 135 | while time.time() - start < TIMEOUT: 136 | time.sleep(1) 137 | try: 138 | data = dev.check_data() 139 | except (ReadError, StorageError): 140 | continue 141 | else: 142 | break 143 | else: 144 | print("No data received...") 145 | exit(1) 146 | 147 | learned = format_durations(to_microseconds(bytearray(data))) \ 148 | if args.durations \ 149 | else ''.join(format(x, '02x') for x in bytearray(data)) 150 | if args.learn: 151 | print(learned) 152 | decode_hex = codecs.getdecoder("hex_codec") 153 | print("Base64: " + str(base64.b64encode(decode_hex(learned)[0]))) 154 | if args.learnfile: 155 | print("Saving to {}".format(args.learnfile)) 156 | with open(args.learnfile, "w") as text_file: 157 | text_file.write(learned) 158 | if args.check: 159 | if dev.check_power(): 160 | print('* ON *') 161 | else: 162 | print('* OFF *') 163 | if args.checknl: 164 | if dev.check_nightlight(): 165 | print('* ON *') 166 | else: 167 | print('* OFF *') 168 | if args.turnon: 169 | dev.set_power(True) 170 | if dev.check_power(): 171 | print('== Turned * ON * ==') 172 | else: 173 | print('!! Still OFF !!') 174 | if args.turnoff: 175 | dev.set_power(False) 176 | if dev.check_power(): 177 | print('!! Still ON !!') 178 | else: 179 | print('== Turned * OFF * ==') 180 | if args.turnnlon: 181 | dev.set_nightlight(True) 182 | if dev.check_nightlight(): 183 | print('== Turned * ON * ==') 184 | else: 185 | print('!! Still OFF !!') 186 | if args.turnnloff: 187 | dev.set_nightlight(False) 188 | if dev.check_nightlight(): 189 | print('!! Still ON !!') 190 | else: 191 | print('== Turned * OFF * ==') 192 | if args.switch: 193 | if dev.check_power(): 194 | dev.set_power(False) 195 | print('* Switch to OFF *') 196 | else: 197 | dev.set_power(True) 198 | print('* Switch to ON *') 199 | if args.rfscanlearn: 200 | dev.sweep_frequency() 201 | print("Learning RF Frequency, press and hold the button to learn...") 202 | 203 | start = time.time() 204 | while time.time() - start < TIMEOUT: 205 | time.sleep(1) 206 | if dev.check_frequency(): 207 | break 208 | else: 209 | print("RF Frequency not found") 210 | dev.cancel_sweep_frequency() 211 | exit(1) 212 | 213 | print("Found RF Frequency - 1 of 2!") 214 | print("You can now let go of the button") 215 | 216 | input("Press enter to continue...") 217 | 218 | print("To complete learning, single press the button you want to learn") 219 | 220 | dev.find_rf_packet() 221 | 222 | start = time.time() 223 | while time.time() - start < TIMEOUT: 224 | time.sleep(1) 225 | try: 226 | data = dev.check_data() 227 | except (ReadError, StorageError): 228 | continue 229 | else: 230 | break 231 | else: 232 | print("No data received...") 233 | exit(1) 234 | 235 | print("Found RF Frequency - 2 of 2!") 236 | learned = format_durations(to_microseconds(bytearray(data))) \ 237 | if args.durations \ 238 | else ''.join(format(x, '02x') for x in bytearray(data)) 239 | if args.learnfile is None: 240 | print(learned) 241 | decode_hex = codecs.getdecoder("hex_codec") 242 | print("Base64: {}".format(str(base64.b64encode(decode_hex(learned)[0])))) 243 | if args.learnfile is not None: 244 | print("Saving to {}".format(args.learnfile)) 245 | with open(args.learnfile, "w") as text_file: 246 | text_file.write(learned) 247 | -------------------------------------------------------------------------------- /doc/python-broadlink-master/broadlink_discovery: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | 5 | import broadlink 6 | 7 | parser = argparse.ArgumentParser(fromfile_prefix_chars='@') 8 | parser.add_argument("--timeout", type=int, default=5, help="timeout to wait for receiving discovery responses") 9 | parser.add_argument("--ip", default=None, help="ip address to use in the discovery") 10 | parser.add_argument("--dst-ip", default="255.255.255.255", help="destination ip address to use in the discovery") 11 | args = parser.parse_args() 12 | 13 | print("Discovering...") 14 | devices = broadlink.discover(timeout=args.timeout, local_ip_address=args.ip, discover_ip_address=args.dst_ip) 15 | for device in devices: 16 | try: 17 | print("###########################################") 18 | print(device, device.type) 19 | print("# broadlink_cli --type {} --host {} --mac {}".format(hex(device.devtype), device.host[0],':'.join(format(x, '02x') for x in device.mac))) 20 | if device.auth(): 21 | #print(device.type) 22 | #print("# broadlink_cli --type {} --host {} --mac {}".format(hex(device.devtype), device.host[0], 23 | # ''.join(format(x, '02x') for x in device.mac))) 24 | print("Device file data (to be used with --device @filename in broadlink_cli) : ") 25 | print("{} {} {}".format(hex(device.devtype), device.host[0], ''.join(format(x, '02x') for x in device.mac))) 26 | if hasattr(device, 'check_temperature'): 27 | print("temperature = {}".format(device.check_temperature())) 28 | print("") 29 | else: 30 | print("Error authenticating with device : {}".format(device.host)) 31 | except Exception as e: 32 | print("Oops!", e.__class__, "occurred.") -------------------------------------------------------------------------------- /doc/python-broadlink-master/broadlink_discovery copy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | 5 | import broadlink 6 | 7 | parser = argparse.ArgumentParser(fromfile_prefix_chars='@') 8 | parser.add_argument("--timeout", type=int, default=5, help="timeout to wait for receiving discovery responses") 9 | parser.add_argument("--ip", default=None, help="ip address to use in the discovery") 10 | parser.add_argument("--dst-ip", default="255.255.255.255", help="destination ip address to use in the discovery") 11 | args = parser.parse_args() 12 | 13 | print("Discovering...") 14 | devices = broadlink.discover(timeout=args.timeout, local_ip_address=args.ip, discover_ip_address=args.dst_ip) 15 | for device in devices: 16 | if device.auth(): 17 | print("###########################################") 18 | print(device.type) 19 | print("# broadlink_cli --type {} --host {} --mac {}".format(hex(device.devtype), device.host[0], 20 | ''.join(format(x, '02x') for x in device.mac))) 21 | print("Device file data (to be used with --device @filename in broadlink_cli) : ") 22 | print("{} {} {}".format(hex(device.devtype), device.host[0], ''.join(format(x, '02x') for x in device.mac))) 23 | if hasattr(device, 'check_temperature'): 24 | print("temperature = {}".format(device.check_temperature())) 25 | print("") 26 | else: 27 | print("Error authenticating with device : {}".format(device.host)) 28 | -------------------------------------------------------------------------------- /doc/python-broadlink-master/cli/README.md: -------------------------------------------------------------------------------- 1 | Command line interface for python-broadlink 2 | =========================================== 3 | 4 | This is a command line interface for broadlink python library 5 | 6 | Tested with BroadLink RMPRO / RM2 7 | 8 | 9 | Requirements 10 | ------------ 11 | You should have the broadlink python installed, this can be made in many linux distributions using : 12 | ``` 13 | sudo pip install broadlink 14 | ``` 15 | 16 | Installation 17 | ----------- 18 | Just copy this files 19 | 20 | 21 | Programs 22 | -------- 23 | 24 | 25 | * broadlink_discovery 26 | used to run the discovery in the network 27 | this program withh show the command line parameters to be used with 28 | broadlink_cli to select broadlink device 29 | 30 | * broadlink_cli 31 | used to send commands and query the broadlink device 32 | 33 | 34 | device specification formats 35 | ---------------------------- 36 | 37 | Using separate parameters for each information: 38 | ``` 39 | broadlink_cli --type 0x2712 --host 1.1.1.1 --mac aaaaaaaaaa --temp 40 | ``` 41 | 42 | Using all parameters as a single argument: 43 | ``` 44 | broadlink_cli --device "0x2712 1.1.1.1 aaaaaaaaaa" --temp 45 | ``` 46 | 47 | Using file with parameters: 48 | ``` 49 | broadlink_cli --device @BEDROOM.device --temp 50 | ``` 51 | This is prefered as the configuration is stored in file and you can change 52 | just a file to point to a different hardware 53 | 54 | Sample usage 55 | ------------ 56 | 57 | Learn commands : 58 | ``` 59 | # Learn and save to file 60 | broadlink_cli --device @BEDROOM.device --learnfile LG-TV.power 61 | # LEard and show at console 62 | broadlink_cli --device @BEDROOM.device --learn 63 | ``` 64 | 65 | 66 | Send command : 67 | ``` 68 | broadlink_cli --device @BEDROOM.device --send @LG-TV.power 69 | broadlink_cli --device @BEDROOM.device --send ....datafromlearncommand... 70 | ``` 71 | 72 | Get Temperature : 73 | ``` 74 | broadlink_cli --device @BEDROOM.device --temperature 75 | ``` 76 | 77 | Get Energy Consumption (For a SmartPlug) : 78 | ``` 79 | broadlink_cli --device @BEDROOM.device --energy 80 | ``` 81 | 82 | Once joined to the Broadlink provisioning Wi-Fi, configure it with your Wi-Fi details: 83 | ``` 84 | broadlink_cli --joinwifi MySSID MyWifiPassword 85 | ``` 86 | -------------------------------------------------------------------------------- /doc/python-broadlink-master/cli/broadlink_cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import base64 5 | import codecs 6 | import time 7 | 8 | import broadlink 9 | from broadlink.exceptions import ReadError, StorageError 10 | 11 | TICK = 32.84 12 | TIMEOUT = 30 13 | IR_TOKEN = 0x26 14 | 15 | 16 | def auto_int(x): 17 | return int(x, 0) 18 | 19 | 20 | def to_microseconds(bytes): 21 | result = [] 22 | # print bytes[0] # 0x26 = 38for IR 23 | index = 4 24 | while index < len(bytes): 25 | chunk = bytes[index] 26 | index += 1 27 | if chunk == 0: 28 | chunk = bytes[index] 29 | chunk = 256 * chunk + bytes[index + 1] 30 | index += 2 31 | result.append(int(round(chunk * TICK))) 32 | if chunk == 0x0d05: 33 | break 34 | return result 35 | 36 | 37 | def durations_to_broadlink(durations): 38 | result = bytearray() 39 | result.append(IR_TOKEN) 40 | result.append(0) 41 | result.append(len(durations) % 256) 42 | result.append(len(durations) / 256) 43 | for dur in durations: 44 | num = int(round(dur / TICK)) 45 | if num > 255: 46 | result.append(0) 47 | result.append(num / 256) 48 | result.append(num % 256) 49 | return result 50 | 51 | 52 | def format_durations(data): 53 | result = '' 54 | for i in range(0, len(data)): 55 | if len(result) > 0: 56 | result += ' ' 57 | result += ('+' if i % 2 == 0 else '-') + str(data[i]) 58 | return result 59 | 60 | 61 | def parse_durations(str): 62 | result = [] 63 | for s in str.split(): 64 | result.append(abs(int(s))) 65 | return result 66 | 67 | 68 | parser = argparse.ArgumentParser(fromfile_prefix_chars='@') 69 | parser.add_argument("--device", help="device definition as 'type host mac'") 70 | parser.add_argument("--type", type=auto_int, default=0x2712, help="type of device") 71 | parser.add_argument("--host", help="host address") 72 | parser.add_argument("--mac", help="mac address (hex reverse), as used by python-broadlink library") 73 | parser.add_argument("--temperature", action="store_true", help="request temperature from device") 74 | parser.add_argument("--energy", action="store_true", help="request energy consumption from device") 75 | parser.add_argument("--check", action="store_true", help="check current power state") 76 | parser.add_argument("--checknl", action="store_true", help="check current nightlight state") 77 | parser.add_argument("--turnon", action="store_true", help="turn on device") 78 | parser.add_argument("--turnoff", action="store_true", help="turn off device") 79 | parser.add_argument("--turnnlon", action="store_true", help="turn on nightlight on the device") 80 | parser.add_argument("--turnnloff", action="store_true", help="turn off nightlight on the device") 81 | parser.add_argument("--switch", action="store_true", help="switch state from on to off and off to on") 82 | parser.add_argument("--send", action="store_true", help="send command") 83 | parser.add_argument("--sensors", action="store_true", help="check all sensors") 84 | parser.add_argument("--learn", action="store_true", help="learn command") 85 | parser.add_argument("--rfscanlearn", action="store_true", help="rf scan learning") 86 | parser.add_argument("--learnfile", help="save learned command to a specified file") 87 | parser.add_argument("--durations", action="store_true", 88 | help="use durations in micro seconds instead of the Broadlink format") 89 | parser.add_argument("--convert", action="store_true", help="convert input data to durations") 90 | parser.add_argument("--joinwifi", nargs=2, help="Args are SSID PASSPHRASE to configure Broadlink device with") 91 | parser.add_argument("data", nargs='*', help="Data to send or convert") 92 | args = parser.parse_args() 93 | 94 | if args.device: 95 | values = args.device.split() 96 | type = int(values[0], 0) 97 | host = values[1] 98 | mac = bytearray.fromhex(values[2]) 99 | elif args.mac: 100 | type = args.type 101 | host = args.host 102 | mac = bytearray.fromhex(args.mac) 103 | 104 | if args.host or args.device: 105 | dev = broadlink.gendevice(type, (host, 80), mac) 106 | dev.auth() 107 | 108 | if args.joinwifi: 109 | broadlink.setup(args.joinwifi[0], args.joinwifi[1], 4) 110 | 111 | if args.convert: 112 | data = bytearray.fromhex(''.join(args.data)) 113 | durations = to_microseconds(data) 114 | print(format_durations(durations)) 115 | if args.temperature: 116 | print(dev.check_temperature()) 117 | if args.energy: 118 | print(dev.get_energy()) 119 | if args.sensors: 120 | try: 121 | data = dev.check_sensors() 122 | except: 123 | data = {} 124 | data['temperature'] = dev.check_temperature() 125 | for key in data: 126 | print("{} {}".format(key, data[key])) 127 | if args.send: 128 | data = durations_to_broadlink(parse_durations(' '.join(args.data))) \ 129 | if args.durations else bytearray.fromhex(''.join(args.data)) 130 | dev.send_data(data) 131 | if args.learn or (args.learnfile and not args.rfscanlearn): 132 | dev.enter_learning() 133 | print("Learning...") 134 | start = time.time() 135 | while time.time() - start < TIMEOUT: 136 | time.sleep(1) 137 | try: 138 | data = dev.check_data() 139 | except (ReadError, StorageError): 140 | continue 141 | else: 142 | break 143 | else: 144 | print("No data received...") 145 | exit(1) 146 | 147 | learned = format_durations(to_microseconds(bytearray(data))) \ 148 | if args.durations \ 149 | else ''.join(format(x, '02x') for x in bytearray(data)) 150 | if args.learn: 151 | print(learned) 152 | decode_hex = codecs.getdecoder("hex_codec") 153 | print("Base64: " + str(base64.b64encode(decode_hex(learned)[0]))) 154 | if args.learnfile: 155 | print("Saving to {}".format(args.learnfile)) 156 | with open(args.learnfile, "w") as text_file: 157 | text_file.write(learned) 158 | if args.check: 159 | if dev.check_power(): 160 | print('* ON *') 161 | else: 162 | print('* OFF *') 163 | if args.checknl: 164 | if dev.check_nightlight(): 165 | print('* ON *') 166 | else: 167 | print('* OFF *') 168 | if args.turnon: 169 | dev.set_power(True) 170 | if dev.check_power(): 171 | print('== Turned * ON * ==') 172 | else: 173 | print('!! Still OFF !!') 174 | if args.turnoff: 175 | dev.set_power(False) 176 | if dev.check_power(): 177 | print('!! Still ON !!') 178 | else: 179 | print('== Turned * OFF * ==') 180 | if args.turnnlon: 181 | dev.set_nightlight(True) 182 | if dev.check_nightlight(): 183 | print('== Turned * ON * ==') 184 | else: 185 | print('!! Still OFF !!') 186 | if args.turnnloff: 187 | dev.set_nightlight(False) 188 | if dev.check_nightlight(): 189 | print('!! Still ON !!') 190 | else: 191 | print('== Turned * OFF * ==') 192 | if args.switch: 193 | if dev.check_power(): 194 | dev.set_power(False) 195 | print('* Switch to OFF *') 196 | else: 197 | dev.set_power(True) 198 | print('* Switch to ON *') 199 | if args.rfscanlearn: 200 | dev.sweep_frequency() 201 | print("Learning RF Frequency, press and hold the button to learn...") 202 | 203 | start = time.time() 204 | while time.time() - start < TIMEOUT: 205 | time.sleep(1) 206 | if dev.check_frequency(): 207 | break 208 | else: 209 | print("RF Frequency not found") 210 | dev.cancel_sweep_frequency() 211 | exit(1) 212 | 213 | print("Found RF Frequency - 1 of 2!") 214 | print("You can now let go of the button") 215 | 216 | input("Press enter to continue...") 217 | 218 | print("To complete learning, single press the button you want to learn") 219 | 220 | dev.find_rf_packet() 221 | 222 | start = time.time() 223 | while time.time() - start < TIMEOUT: 224 | time.sleep(1) 225 | try: 226 | data = dev.check_data() 227 | except (ReadError, StorageError): 228 | continue 229 | else: 230 | break 231 | else: 232 | print("No data received...") 233 | exit(1) 234 | 235 | print("Found RF Frequency - 2 of 2!") 236 | learned = format_durations(to_microseconds(bytearray(data))) \ 237 | if args.durations \ 238 | else ''.join(format(x, '02x') for x in bytearray(data)) 239 | if args.learnfile is None: 240 | print(learned) 241 | decode_hex = codecs.getdecoder("hex_codec") 242 | print("Base64: {}".format(str(base64.b64encode(decode_hex(learned)[0])))) 243 | if args.learnfile is not None: 244 | print("Saving to {}".format(args.learnfile)) 245 | with open(args.learnfile, "w") as text_file: 246 | text_file.write(learned) 247 | -------------------------------------------------------------------------------- /doc/python-broadlink-master/cli/broadlink_discovery: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | 5 | import broadlink 6 | 7 | parser = argparse.ArgumentParser(fromfile_prefix_chars='@') 8 | parser.add_argument("--timeout", type=int, default=5, help="timeout to wait for receiving discovery responses") 9 | parser.add_argument("--ip", default=None, help="ip address to use in the discovery") 10 | parser.add_argument("--dst-ip", default="255.255.255.255", help="destination ip address to use in the discovery") 11 | args = parser.parse_args() 12 | 13 | print("Discovering...") 14 | devices = broadlink.discover(timeout=args.timeout, local_ip_address=args.ip, discover_ip_address=args.dst_ip) 15 | for device in devices: 16 | if device.auth(): 17 | print("###########################################") 18 | print(device.type) 19 | print("# broadlink_cli --type {} --host {} --mac {}".format(hex(device.devtype), device.host[0], 20 | ''.join(format(x, '02x') for x in device.mac))) 21 | print("Device file data (to be used with --device @filename in broadlink_cli) : ") 22 | print("{} {} {}".format(hex(device.devtype), device.host[0], ''.join(format(x, '02x') for x in device.mac))) 23 | if hasattr(device, 'check_temperature'): 24 | print("temperature = {}".format(device.check_temperature())) 25 | print("") 26 | else: 27 | print("Error authenticating with device : {}".format(device.host)) 28 | -------------------------------------------------------------------------------- /doc/python-broadlink-master/protocol.md: -------------------------------------------------------------------------------- 1 | Broadlink RM2 network protocol 2 | ============================== 3 | 4 | Encryption 5 | ---------- 6 | 7 | Packets include AES-based encryption in CBC mode. The initial key is 0x09, 0x76, 0x28, 0x34, 0x3f, 0xe9, 0x9e, 0x23, 0x76, 0x5c, 0x15, 0x13, 0xac, 0xcf, 0x8b, 0x02. The IV is 0x56, 0x2e, 0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58. 8 | 9 | Checksum 10 | -------- 11 | 12 | Construct the packet and set checksum bytes to zero. Add each byte to the starting value of 0xbeaf, wrapping after 0xffff. 13 | 14 | New device setup 15 | ---------------- 16 | 17 | To setup a new Broadlink device while in AP Mode a 136 byte packet needs to be sent to the device as follows: 18 | 19 | | Offset | Contents | 20 | |---------|----------| 21 | |0x00-0x19|00| 22 | |0x20-0x21|Checksum as a little-endian 16 bit integer| 23 | |0x26|14 (Always 14)| 24 | |0x44-0x63|SSID Name (zero padding is appended)| 25 | |0x64-0x83|Password (zero padding is appended)| 26 | |0x84|Character length of SSID| 27 | |0x85|Character length of password| 28 | |0x86|Wireless security mode (00 - none, 01 = WEP, 02 = WPA1, 03 = WPA2, 04 = WPA1/2)| 29 | |0x87-88|00| 30 | 31 | Send this packet as a UDP broadcast to 255.255.255.255 on port 80. 32 | 33 | Network discovery 34 | ----------------- 35 | 36 | To discover Broadlink devices on the local network, send a 48 byte packet with the following contents: 37 | 38 | | Offset | Contents | 39 | |---------|----------| 40 | |0x00-0x07|00| 41 | |0x08-0x0b|Current offset from GMT as a little-endian 32 bit integer| 42 | |0x0c-0x0d|Current year as a little-endian 16 bit integer| 43 | |0x0e|Current number of seconds past the minute| 44 | |0x0f|Current number of minutes past the hour| 45 | |0x10|Current number of hours past midnight| 46 | |0x11|Current day of the week (Monday = 1, Tuesday = 2, etc)| 47 | |0x12|Current day in month| 48 | |0x13|Current month| 49 | |0x14-0x17|00| 50 | |0x18-0x1b|Local IP address| 51 | |0x1c-0x1d|Source port as a little-endian 16 bit integer| 52 | |0x1e-0x1f|00| 53 | |0x20-0x21|Checksum as a little-endian 16 bit integer| 54 | |0x22-0x25|00| 55 | |0x26|06| 56 | |0x27-0x2f|00| 57 | 58 | Send this packet as a UDP broadcast to 255.255.255.255 on port 80. 59 | 60 | Response (any unicast response): 61 | 62 | | Offset | Contents | 63 | |---------|----------| 64 | |0x34-0x35|Device type as a little-endian 16 bit integer (see device type mapping)| 65 | |0x3a-0x3f|MAC address of the target device| 66 | 67 | Device type mapping: 68 | 69 | | Device type in response packet | Device type | Treat as | 70 | |---------|----------|----------| 71 | |0|SP1|SP1| 72 | |0x2711|SP2|SP2| 73 | |0x2719 or 0x7919 or 0x271a or 0x791a|Honeywell SP2|SP2| 74 | |0x2720|SPMini|SP2| 75 | |0x753e|SP3|SP2| 76 | |0x2728|SPMini2|SP2 77 | |0x2733 or 0x273e|OEM branded SPMini|SP2| 78 | |>= 0x7530 and <= 0x7918|OEM branded SPMini2|SP2| 79 | |0x2736|SPMiniPlus|SP2| 80 | |0x2712|RM2|RM| 81 | |0x2737|RM Mini / RM3 Mini Blackbean|RM| 82 | |0x273d|RM Pro Phicomm|RM| 83 | |0x2783|RM2 Home Plus|RM| 84 | |0x277c|RM2 Home Plus GDT|RM| 85 | |0x272a|RM2 Pro Plus|RM| 86 | |0x2787|RM2 Pro Plus2|RM| 87 | |0x278b|RM2 Pro Plus BL|RM| 88 | |0x278f|RM Mini Shate|RM| 89 | |0x2714|A1|A1| 90 | |0x4EB5|MP1|MP1| 91 | 92 | 93 | Command packet format 94 | --------------------- 95 | 96 | The command packet header is 56 bytes long with the following format: 97 | 98 | |Offset|Contents| 99 | |------|--------| 100 | |0x00|0x5a| 101 | |0x01|0xa5| 102 | |0x02|0xaa| 103 | |0x03|0x55| 104 | |0x04|0x5a| 105 | |0x05|0xa5| 106 | |0x06|0xaa| 107 | |0x07|0x55| 108 | |0x08-0x1f|00| 109 | |0x20-0x21|Checksum of full packet as a little-endian 16 bit integer| 110 | |0x22-0x23|00| 111 | |0x24-0x25|Device type as a little-endian 16 bit integer| 112 | |0x26-0x27|Command code as a little-endian 16 bit integer| 113 | |0x28-0x29|Packet count as a little-endian 16 bit integer| 114 | |0x2a-0x2f|Local MAC address| 115 | |0x30-0x33|Local device ID (obtained during authentication, 00 before authentication)| 116 | |0x34-0x35|Checksum of unencrypted payload as a little-endian 16 bit integer 117 | |0x36-0x37|00| 118 | 119 | The payload is appended immediately after this. The checksum at 0x20 is calculated *after* the payload is appended, and covers the entire packet (including the checksum at 0x34). Therefore: 120 | 121 | 1. Generate packet header with checksum values set to 0 122 | 2. Set the checksum initialisation value to 0xbeaf and calculate the checksum of the unencrypted payload. Set 0x34-0x35 to this value. 123 | 3. Encrypt and append the payload 124 | 4. Set the checksum initialisation value to 0xbeaf and calculate the checksum of the entire packet. Set 0x20-0x21 to this value. 125 | 126 | Authorisation 127 | ------------- 128 | 129 | You must obtain an authorisation key from the device before you can communicate. To do so, generate an 80 byte packet with the following contents: 130 | 131 | |Offset|Contents| 132 | |------|--------| 133 | |0x00-0x03|00| 134 | |0x04-0x12|A 15-digit value that represents this device. Broadlink's implementation uses the IMEI.| 135 | |0x13|01| 136 | |0x14-0x2c|00| 137 | |0x2d|0x01| 138 | |0x30-0x7f|NULL-terminated ASCII string containing the device name| 139 | 140 | Send this payload with a command value of 0x0065. The response packet will contain an encrypted payload from byte 0x38 onwards. Decrypt this using the default key and IV. The format of the decrypted payload is: 141 | 142 | |Offset|Contents| 143 | |------|--------| 144 | |0x00-0x03|Device ID| 145 | |0x04-0x13|Device encryption key| 146 | 147 | All further command packets must use this encryption key and device ID. 148 | 149 | Entering learning mode 150 | ---------------------- 151 | 152 | Send the following 16 byte payload with a command value of 0x006a: 153 | 154 | |Offset|Contents| 155 | |------|--------| 156 | |0x00|0x03| 157 | |0x01-0x0f|0x00| 158 | 159 | Reading back data from learning mode 160 | ------------------------------------ 161 | 162 | Send the following 16 byte payload with a command value of 0x006a: 163 | 164 | |Offset|Contents| 165 | |------|--------| 166 | |0x00|0x04| 167 | |0x01-0x0f|0x00| 168 | 169 | Byte 0x22 of the response contains a little-endian 16 bit error code. If this is 0, a code has been obtained. Bytes 0x38 and onward of the response are encrypted. Decrypt them. Bytes 0x04 and onward of the decrypted payload contain the captured data. 170 | 171 | Sending data 172 | ------------ 173 | 174 | Send the following payload with a command byte of 0x006a 175 | 176 | |Offset|Contents| 177 | |------|--------| 178 | |0x00|0x02| 179 | |0x01-0x03|0x00| 180 | |0x04|0x26 = IR, 0xb2 for RF 433Mhz, 0xd7 for RF 315Mhz| 181 | |0x05|repeat count, (0 = no repeat, 1 send twice, .....)| 182 | |0x06-0x07|Length of the following data in little endian| 183 | |0x08 ....|Pulse lengths in 2^-15 s units (µs * 269 / 8192 works very well)| 184 | |....|0x0d 0x05 at the end for IR only| 185 | 186 | Each value is represented by one byte. If the length exceeds one byte 187 | then it is stored big endian with a leading 0. 188 | 189 | Example: The header for my Optoma projector is 8920 4450 190 | 8920 * 269 / 8192 = 0x124 191 | 4450 * 269 / 8192 = 0x92 192 | 193 | So the data starts with `0x00 0x1 0x24 0x92 ....` 194 | 195 | 196 | Todo 197 | ---- 198 | 199 | * Support for other devices using the Broadlink protocol (various smart home devices) 200 | * Figure out what the format of the data packets actually is. 201 | * Deal with the response after AP Mode WiFi network setup. 202 | 203 | -------------------------------------------------------------------------------- /doc/python-broadlink-master/requirements.txt: -------------------------------------------------------------------------------- 1 | cryptography==41.0.6 2 | -------------------------------------------------------------------------------- /doc/python-broadlink-master/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | from setuptools import setup, find_packages 6 | 7 | 8 | version = '0.14.0' 9 | 10 | setup( 11 | name='broadlink', 12 | version=version, 13 | author='Matthew Garrett', 14 | author_email='mjg59@srcf.ucam.org', 15 | url='http://github.com/mjg59/python-broadlink', 16 | packages=find_packages(), 17 | scripts=[], 18 | install_requires=['cryptography>=2.1.1'], 19 | description='Python API for controlling Broadlink IR controllers', 20 | classifiers=[ 21 | 'Development Status :: 4 - Beta', 22 | 'Intended Audience :: Developers', 23 | 'License :: OSI Approved :: MIT License', 24 | 'Operating System :: OS Independent', 25 | 'Programming Language :: Python', 26 | ], 27 | include_package_data=True, 28 | zip_safe=False, 29 | ) 30 | -------------------------------------------------------------------------------- /doc/structure_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iobroker-community-adapters/ioBroker.broadlink2/fc261d5ae259e556f1eb7f7c4988cd8d0c4c5165/doc/structure_example.png -------------------------------------------------------------------------------- /doc/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import broadlink 4 | 5 | strHost="192.168.0.187" 6 | strMac="34:ea:34:9b:92:46" 7 | strType = '0x4ead' 8 | macbytes = bytearray.fromhex(strMac.replace(':','')) 9 | device = broadlink.hysen((strHost,80),macbytes,strType) 10 | print device.auth() 11 | data = device.get_full_status() 12 | print data 13 | -------------------------------------------------------------------------------- /io-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "common": { 3 | "name": "broadlink2", 4 | "version": "2.3.0", 5 | "news": { 6 | "2.3.0": { 7 | "en": "Adapter migrated to jsonConfig\nAdapter requires admin >= 6 now\nDependencies have been updated", 8 | "de": "Adapter migriert zu jsonConfig\nAdapter erfordert Admin >= 6 jetzt\nAbhängigkeiten wurden aktualisiert", 9 | "ru": "Адаптер мигрировал в jsonConfig\nАдаптер требует admin >= 6 сейчас\nЗависимость обновлена", 10 | "pt": "Adaptador migrado para jsonConfig\nAdapter requer admin >= 6 agora\nAs dependências foram atualizadas", 11 | "nl": "Adapter gemigreerd naar jsonConfig\nAdapter vereist admin >= 6 nu\nAfhankelijkheden zijn bijgewerkt", 12 | "fr": "Adaptateur migré vers jsonConfig\nAdaptateur nécessite admin >= 6 maintenant\nLes dépendances ont été actualisées", 13 | "it": "Adattatore migrato a jsonConfig\nAdattatore richiede admin >= 6 ora\nLe dipendenze sono state aggiornate", 14 | "es": "Adaptador migrado a jsonConfig\nAdaptador requiere administración >= 6 ahora\nSe han actualizado las dependencias", 15 | "pl": "Adapter migrował do jsonConfig\nAdapter wymaga admin > = 6 teraz\nZaktualizowano zależności", 16 | "uk": "Адаптер мігрований до jsonConfig\nАдаптер вимагає адміністратора >= 6 тепер\nЗалежність було оновлено", 17 | "zh-cn": "将适配器迁移到 jsonConfig\n适配器需要管理员 现在6个\n依赖关系已更新" 18 | }, 19 | "2.2.0": { 20 | "en": "Adapter requires node.js 18 and js-controller >= 5 now\nDependencies have been updated", 21 | "de": "Adapter benötigt node.js 18 und js-controller >= 5 jetzt\nAbhängigkeiten wurden aktualisiert", 22 | "ru": "Адаптер требует node.js 18 и js-controller >= 5 сейчас\nЗависимость обновлена", 23 | "pt": "Adapter requer node.js 18 e js-controller >= 5 agora\nAs dependências foram atualizadas", 24 | "nl": "Adapter vereist node.js 18 en js-controller Nu 5\nAfhankelijkheden zijn bijgewerkt", 25 | "fr": "Adaptateur nécessite node.js 18 et js-controller >= 5 maintenant\nLes dépendances ont été actualisées", 26 | "it": "Adattatore richiede node.js 18 e js-controller >= 5 ora\nLe dipendenze sono state aggiornate", 27 | "es": "Adaptador requiere node.js 18 y js-controller √= 5 ahora\nSe han actualizado las dependencias", 28 | "pl": "Adapter wymaga node.js 18 i sterownika js- > = 5 teraz\nZaktualizowano zależności", 29 | "uk": "Адаптер вимагає node.js 18 і js-controller >= 5 тепер\nЗалежність було оновлено", 30 | "zh-cn": "适配器需要节点.js 18和js控制器 QQ 现在5号\n依赖关系已更新" 31 | }, 32 | "2.1.5": { 33 | "en": "Bug fix for RM3 Mini and some other text/debug changes", 34 | "de": "Fehlerbehebung für RM3 Mini und einige andere Text-/Debug-Änderungen", 35 | "ru": "Исправлена ​​ошибка для RM3 Mini и некоторые другие изменения в тексте и отладке.", 36 | "pt": "Correção de bug para RM3 Mini e algumas outras alterações de texto/depuração", 37 | "nl": "Bugfix voor RM3 Mini en enkele andere tekst-/foutopsporingswijzigingen", 38 | "fr": "Correction d'un bug pour RM3 Mini et quelques autres modifications de texte/débogage", 39 | "it": "Correzione di bug per RM3 Mini e alcune altre modifiche di testo/debug", 40 | "es": "Corrección de errores para RM3 Mini y algunos otros cambios de texto/depuración", 41 | "pl": "Poprawka błędu dla RM3 Mini i kilka innych zmian w tekście/debugowaniu", 42 | "uk": "Виправлено помилку для RM3 Mini та деякі інші зміни тексту/налагодження", 43 | "zh-cn": "RM3 Mini 的错误修复和一些其他文本/调试更改" 44 | }, 45 | "2.1.4": { 46 | "en": "Bug fix of RM4 temperature and Scenes/States, also new naming convention for found devices and LB1/RM devices update their data in device", 47 | "de": "Fehlerbehebung für RM4-Temperatur und Szenen/Zustände, außerdem neue Namenskonvention für gefundene Geräte und LB1/RM-Geräte aktualisieren ihre Daten im Gerät", 48 | "ru": "Исправлена ​​ошибка температуры RM4 и сцен/состояний, а также новое соглашение об именах для найденных устройств, а устройства LB1/RM обновляют свои данные в устройстве.", 49 | "pt": "Correção de bug de temperatura e cenas/estados do RM4, também nova convenção de nomenclatura para dispositivos encontrados e dispositivos LB1/RM atualizam seus dados no dispositivo", 50 | "nl": "Bugfix van RM4-temperatuur en scènes/statussen, ook nieuwe naamgevingsconventie voor gevonden apparaten en LB1/RM-apparaten updaten hun gegevens op het apparaat", 51 | "fr": "Correction d'un bug de la température RM4 et des scènes/états, ainsi qu'une nouvelle convention de dénomination pour les appareils trouvés et les appareils LB1/RM mettent à jour leurs données dans l'appareil", 52 | "it": "Correzione del bug della temperatura RM4 e delle scene/stati, inoltre la nuova convenzione di denominazione per i dispositivi trovati e i dispositivi LB1/RM aggiornano i loro dati nel dispositivo", 53 | "es": "Corrección de errores de temperatura RM4 y escenas/estados, también nueva convención de nomenclatura para dispositivos encontrados y dispositivos LB1/RM actualizan sus datos en el dispositivo", 54 | "pl": "Poprawka błędu temperatury i scen/stanów RM4, a także nowa konwencja nazewnictwa dla znalezionych urządzeń i urządzeń LB1/RM aktualizują swoje dane w urządzeniu", 55 | "uk": "Виправлено помилку температури RM4 і сцен/станів, а також нові правила іменування знайдених пристроїв, а пристрої LB1/RM оновлюють свої дані на пристрої", 56 | "zh-cn": "RM4 温度和场景/状态的错误修复,以及找到的设备和 LB1/RM 设备的新命名约定更新设备中的数据" 57 | }, 58 | "2.1.0": { 59 | "en": "Added RM4+LB1 protocol and also check now device names according mac address or find additional ip addresses", 60 | "de": "RM4+LB1-Protokolle hinzugefügt und auch automatische Erkennung von Geräten mittels mac-Adresse, oder in Konfig IP-Adresse hinzugefügt", 61 | "ru": "Добавлен протокол RM4+LB1, а также теперь можно проверять имена устройств по MAC-адресу или находить дополнительные IP-адреса.", 62 | "pt": "Adicionado protocolo RM4 + LB1 e também verifique agora os nomes dos dispositivos de acordo com o endereço MAC ou encontre endereços IP adicionais", 63 | "nl": "RM4+LB1-protocol toegevoegd en controleer nu ook apparaatnamen op basis van het Mac-adres of vind extra IP-adressen", 64 | "fr": "Ajout du protocole RM4 + LB1 et vérification également maintenant des noms d'appareils en fonction de l'adresse Mac ou recherche d'adresses IP supplémentaires", 65 | "it": "Aggiunto il protocollo RM4+LB1 e ora controlla anche i nomi dei dispositivi in ​​base all'indirizzo Mac o trova indirizzi IP aggiuntivi", 66 | "es": "Se agregó el protocolo RM4+LB1 y ahora también verifique los nombres de los dispositivos según la dirección mac o busque direcciones IP adicionales.", 67 | "pl": "Dodano protokół RM4+LB1, a także sprawdź teraz nazwy urządzeń według adresu MAC lub znajdź dodatkowe adresy IP", 68 | "uk": "Додано протокол RM4+LB1, а також тепер перевіряйте назви пристроїв відповідно до mac-адреси або знайдіть додаткові ip-адреси", 69 | "zh-cn": "添加了 RM4+LB1 协议,现在还可以根据 mac 地址检查设备名称或查找其他 ip 地址" 70 | }, 71 | "2.0.3": { 72 | "en": "Changed myAdapter to support js-controller v2.x & v3.x!", 73 | "de": "myAdapter geändert um js-controller v2.x & v3.x zu unterstützen.", 74 | "ru": "Изменен myAdapter для поддержки js-контроллера v2.x и v3.x!", 75 | "pt": "MyAdapter alterado para suportar js-controller v2.x e v3.x!", 76 | "nl": "MyAdapter gewijzigd om js-controller v2.x & v3.x te ondersteunen!", 77 | "fr": "Modification de myAdapter pour prendre en charge js-controller v2.x et v3.x !", 78 | "it": "Modificato myAdapter per supportare js-controller v2.x e v3.x!", 79 | "es": "¡Se cambió myAdapter para admitir js-controller v2.x y v3.x!", 80 | "pl": "Zmieniono myAdapter, aby obsługiwał kontroler js v2.x i v3.x!", 81 | "uk": "Змінено myAdapter для підтримки js-controller v2.x & v3.x!", 82 | "zh-cn": "更改了 myAdapter 以支持 js-controller v2.x 和 v3.x!" 83 | }, 84 | "2.0.0": { 85 | "en": "Complete rewrite of adaper with multiple options, please read readme.md!", 86 | "de": "Komplett neu geschriebenen Adapter mit neuen Funktionen und Konfiguration, bitte README lesen!", 87 | "ru": "Полная переработка адаптера с множеством опций, читайте readme.md!", 88 | "pt": "Reescrita completa do adaptador com múltiplas opções, leia readme.md!", 89 | "nl": "Volledige herschrijving van de adapter met meerdere opties, lees readme.md!", 90 | "fr": "Réécriture complète de l'adaptateur avec plusieurs options, veuillez lire readme.md !", 91 | "it": "Riscrittura completa di adaper con più opzioni, leggi readme.md!", 92 | "es": "Reescritura completa del adaptador con múltiples opciones, lea readme.md.", 93 | "pl": "Całkowite przepisanie adaptera z wieloma opcjami, przeczytaj readme.md!", 94 | "uk": "Повний перепис адаптера з кількома параметрами, будь ласка, прочитайте readme.md!", 95 | "zh-cn": "完整重写了 adaper,具有多个选项,请阅读 readme.md!" 96 | } 97 | }, 98 | "titleLang": { 99 | "en": "Broadlink2", 100 | "de": "Broadlink2", 101 | "ru": "Broadlink2", 102 | "pt": "Broadlink2", 103 | "nl": "Broadlink2", 104 | "fr": "Broadlink2", 105 | "it": "Broadlink2", 106 | "es": "Broadlink2", 107 | "pl": "Broadlink2", 108 | "uk": "Broadlink2", 109 | "zh-cn": "连接2" 110 | }, 111 | "desc": { 112 | "en": "Integrates wireless devices (RM++, SP++, A1, T1, S1C, Beok313, Floureon, LB+)", 113 | "de": "Integriert drahtlose Geräte (RM++, SP++, A1, T1, S1C, Beok313, Floureon, LB+)", 114 | "ru": "Интеграция беспроводных устройств (RM++, SP++, A1, T1, S1C, Beok313, Floureon, LB+)", 115 | "pt": "Integra dispositivos sem fio (RM++, SP++, A1, T1, S1C, Beok313, Floureon, LB+)", 116 | "nl": "Integreert draadloze apparaten (RM++, SP++, A1, T1, S1C, Beok313, Floureon, LB+)", 117 | "fr": "Intégration des appareils sans fil (RM++, SP++, A1, T1, S1C, Beok313, Floureon, LB+)", 118 | "it": "Integra i dispositivi wireless (RM++, SP++, A1, T1, S1C, Beok313, Floureon, LB+)", 119 | "es": "Integra dispositivos inalámbricos (RM++, SP++, A1, T1, S1C, Beok313, Floureon, LB+)", 120 | "pl": "Integracja urządzeń bezprzewodowych (RM++, SP++, A1, T1, S1C, Beok313, Floureon, LB+)", 121 | "uk": "Інтеграція бездротових пристроїв (RM++, SP++, A1, T1, S1C, Beok313, Floureon, LB+)", 122 | "zh-cn": "无线装置的一体化 (RM++, SP++, A1, T1, S1C, Beok313, Floureon, LB+)" 123 | }, 124 | "authors": [ 125 | "Frank Joke ", 126 | "mcm1957 " 127 | ], 128 | "keywords": [ 129 | "Broadlink", 130 | "W-Lan", 131 | "IR/RF", 132 | "Remote", 133 | "Switch", 134 | "Plug", 135 | "A1", 136 | "RM2", 137 | "RM3", 138 | "SP1", 139 | "SP2", 140 | "RM4", 141 | "LB1" 142 | ], 143 | "licenseInformation": { 144 | "license": "MIT", 145 | "type": "free" 146 | }, 147 | "platform": "Javascript/Node.js", 148 | "icon": "broadlink2.png", 149 | "enabled": true, 150 | "extIcon": "https://raw.githubusercontent.com/iobroker-community-adapters/ioBroker.broadlink2/master/admin/broadlink2.png", 151 | "readme": "https://github.com/iobroker-community-adapters/ioBroker.broadlink2/blob/master/README.md", 152 | "loglevel": "info", 153 | "tier": 2, 154 | "mode": "daemon", 155 | "type": "iot-systems", 156 | "compact": false, 157 | "connectionType": "local", 158 | "dataSource": "poll", 159 | "messagebox": true, 160 | "adminUI": { 161 | "config": "json" 162 | }, 163 | "dependencies": [ 164 | { 165 | "js-controller": ">=5.0.19" 166 | } 167 | ], 168 | "globalDependencies": [ 169 | { 170 | "admin": ">=6.13.16" 171 | } 172 | ] 173 | }, 174 | "native": { 175 | "interface": "", 176 | "scenes": [ 177 | { 178 | "name": "Fernsehen", 179 | "scene": "FernseherAn, FernsehLichtAn, 500, TVWZKanal=12" 180 | } 181 | ], 182 | "switches": [ 183 | { 184 | "name": "Steckdose_A", 185 | "on": "Steckdose_A_Ein", 186 | "off:": "Steckdose_A_Aus" 187 | }, 188 | { 189 | "name": "Lüfter_A", 190 | "on": "Lüfter_A_Aus, Lüfter_A_Mittel, Lüfter_A_Stark" 191 | }, 192 | { 193 | "name": "TVWZKanal", 194 | "on": "TVWZK0, TVWZK1, TVWZK2, TVWZK3, TVWZK4, TVWZK5, TVWZK6, TVWZK7, TVWZK8, TVWZK9", 195 | "off": "+" 196 | } 197 | ], 198 | "poll": "30", 199 | "new": "0xabcd=RM4", 200 | "rename": "LB:hostName-aa-bb-cc=LB:SmartBulb", 201 | "additional": "192.168.1.199" 202 | }, 203 | "objects": [], 204 | "config": { 205 | "minWidth": 800, 206 | "width ": 1024, 207 | "minHeight": 800, 208 | "height": 900 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /lib/test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // const net = require('net'); 4 | // const dns = require('dns'); 5 | 6 | const A = require('@frankjoke/myadapter').MyAdapter, 7 | Broadlink = require('../broadlink_fj'); 8 | 9 | const readline = require('readline'); 10 | readline.emitKeypressEvents(process.stdin); 11 | process.stdin.setRawMode(true); 12 | process.stdin.on('keypress', (str, key) => { 13 | if (key.ctrl && key.name === 'c') { 14 | process.exit(); 15 | } else { 16 | A.If(`You pressed the "${str}" key:%O`, key); 17 | if (key.name === 'q') 18 | bl.close(); 19 | process.exit(); 20 | 21 | } 22 | }); 23 | 24 | 25 | const bl = new Broadlink( /* [[0x27145, 'A1']] */ ); 26 | 27 | A.debug = true; 28 | 29 | let devs = []; 30 | //let testdev = null; 31 | 32 | let pDnsReverse = A.c2p(A._dns.reverse); 33 | 34 | bl.on("deviceReady", function (device) { 35 | if (device && device.host.name.endsWith('.fritz.box')) 36 | device.host.name = device.host.name.slice(0, -10); 37 | let h = device.host; 38 | // A.If('found device #%d:%s %s/%s =%s,%s, %O, %O', devs.length, h.name, h.address, h.mac, h.devhex, h.type, device.id, device.key); 39 | // if (device.host.mac === '7a:94:c0:a8:b2:79') 40 | // testdev = device; 41 | devs.push(device); 42 | pDnsReverse(h.address).then(x => A.If('found device #%d:%s %s/%s =%s,%s, %O, %O', devs.length, h.name, h.address, h.mac, h.devhex, h.type, device.id, x)); 43 | }); 44 | 45 | async function wait(x, arg) { 46 | await A.wait(x ? x : 2000); 47 | return arg; 48 | } 49 | 50 | bl.on('15001', (m, r) => A.If('Got 15001 with m:%O and r:%O', m, r)); 51 | 52 | const t = new A.Hrtime(); 53 | 54 | /* 55 | function pE(x,y) { 56 | y = y ? y : pE; 57 | function get() { 58 | var oldLimit = Error.stackTraceLimit; 59 | Error.stackTraceLimit = Infinity; 60 | var orig = Error.prepareStackTrace; 61 | Error.prepareStackTrace = function (_, stack) { 62 | return stack; 63 | }; 64 | var err = new Error('Test'); 65 | Error.captureStackTrace(err, y); 66 | var stack = err.stack; 67 | Error.prepareStackTrace = orig; 68 | Error.stackTraceLimit = oldLimit; 69 | return stack.map(site => site.getFileName() ? (site.getFunctionName() || 'anonymous') + ' in ' + site.getFileName() +' @'+site.getLineNumber()+':'+site.getColumnNumber() : ''); 70 | } 71 | 72 | A.If('Promise failed @ %O error: %o', get().join('; '), x); 73 | return x; 74 | } 75 | */ 76 | //A.init(null,main); 77 | main().catch(A.pE).catch(e => A.Wf('main error was %O', e, bl.close())); 78 | /* 79 | let x=A.f(bl); 80 | A.If("test If %%O:%O",bl); 81 | A.If("test If %%s:%s",x); 82 | A.If("test If true:%s, NaN:%s, undefined:%s, null:%s",true,NaN,undefined,null); 83 | */ 84 | async function main() { 85 | await bl.start15001(); 86 | A.If('staring main after start 15001: %s', t.text); 87 | await wait(100); 88 | // A.If('this = %O',test); 89 | await Promise.resolve(A.A(true, "assert false!")).catch(A.pE).catch(A.nop); 90 | await Promise.reject().catch(A.pE).catch(A.nop); 91 | A.If('Start Discover:%s', t.text); 92 | // await bl.discover('192.168.0.187'); 93 | // await bl.discover('192.168.0.255'); 94 | await bl.discover(); 95 | A.If('after Discover, get all values, %s', t.text); 96 | await A.seriesOf(devs, dev => dev.getAll ? dev.getAll().then(res => A.Ir(res, '%s returned %O', dev.host.name, res), A.nop) : A.resolve(), 10).catch(e => A.Wf('catch error getAll %O', e)); 97 | let { 98 | temperature 99 | } = bl.getDev('A1:EAIR1') ? await bl.getDev('A1:EAIR1').getAll() : { 100 | temperature: 'N/A' 101 | }; 102 | A.If('after GetAll temperature from A1: %f', temperature); 103 | let addr = devs[0].host.address.split('.'); 104 | let addrs = []; 105 | for (let j = 1; j < 255; j++) { 106 | addr[3] = j; 107 | addrs.push(addr.join('.')); 108 | } 109 | let addrn = 0; 110 | // A.If('Adresses are: %O',addrs); 111 | await A.seriesOf(addrs, addr => pDnsReverse(addr).then(x => A.If('dns for #%d:%s was %O', ++addrn, addr, x), () => null), 0); 112 | A.If('Totally %d addresses found!',addrs.length); 113 | await pDnsReverse('169.254.100.100').then(x => A.If(' dns for 169.254.100.100 was %O', x), () => A.I('169.254.100.100 not found')); 114 | await wait(100); 115 | let sw = bl.list['2a:27:66:b2:a8:c0']; 116 | if (sw) { 117 | A.If('before learn: %s', t.text); 118 | sw.learn(true).then(result => A.If('Learned: %O', result), e => A.Wf('catch error learn %O', e)); 119 | } 120 | sw = bl.list['47:75:c0:a8:0:1e']; 121 | sw = bl.getDev('SP:Gibssi1'); 122 | if (sw) { 123 | A.I('Found ' + sw.host.name); 124 | let state = false; 125 | setInterval(() => { 126 | sw.setVal(state = !state).then(() => A.wait(2000)).then(() => sw.setVal(state = !state), e => A.Wf('catch error setval %O', e)); 127 | }, 10000); 128 | } 129 | await wait(100); 130 | // bl.close(); 131 | } -------------------------------------------------------------------------------- /lib/tools.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | 3 | /** 4 | * Tests whether the given variable is a real object and not an Array 5 | * @param {any} it The variable to test 6 | * @returns {it is Record} 7 | */ 8 | function isObject(it) { 9 | // This is necessary because: 10 | // typeof null === 'object' 11 | // typeof [] === 'object' 12 | // [] instanceof Object === true 13 | return Object.prototype.toString.call(it) === "[object Object]"; 14 | } 15 | 16 | /** 17 | * Tests whether the given variable is really an Array 18 | * @param {any} it The variable to test 19 | * @returns {it is any[]} 20 | */ 21 | function isArray(it) { 22 | if (Array.isArray != null) 23 | return Array.isArray(it); 24 | return Object.prototype.toString.call(it) === "[object Array]"; 25 | } 26 | 27 | /** 28 | * Choose the right tranalation API 29 | * @param {string} text The text to translate 30 | * @param {string} targetLang The target languate 31 | * @param {string} yandex api key 32 | * @returns {Promise} 33 | */ 34 | async function translateText(text, targetLang, yandex) { 35 | if (targetLang === "en") { 36 | return text; 37 | } 38 | if (yandex) { 39 | return await translateYandex(text, targetLang, yandex); 40 | } else { 41 | return await translateGoogle(text, targetLang); 42 | } 43 | } 44 | 45 | /** 46 | * Translates text with Yandex API 47 | * @param {string} text The text to translate 48 | * @param {string} targetLang The target languate 49 | * @param {string} yandex api key 50 | * @returns {Promise} 51 | */ 52 | async function translateYandex(text, targetLang, yandex) { 53 | if (targetLang === "zh-cn") { 54 | targetLang = "zh"; 55 | } 56 | try { 57 | const url = `https://translate.yandex.net/api/v1.5/tr.json/translate?key=${yandex}&text=${encodeURIComponent(text)}&lang=en-${targetLang}`; 58 | const response = await axios({url, timeout: 15000}); 59 | if (response.data && response.data['text']) { 60 | return response.data['text'][0]; 61 | } 62 | throw new Error("Invalid response for translate request"); 63 | } catch (e) { 64 | throw new Error(`Could not translate to "${targetLang}": ${e}`); 65 | } 66 | } 67 | 68 | /** 69 | * Translates text with Google API 70 | * @param {string} text The text to translate 71 | * @param {string} targetLang The target languate 72 | * @returns {Promise} 73 | */ 74 | async function translateGoogle(text, targetLang) { 75 | try { 76 | const url = `http://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodeURIComponent(text)}&ie=UTF-8&oe=UTF-8`; 77 | const response = await axios({url, timeout: 15000}); 78 | if (isArray(response.data)) { 79 | // we got a valid response 80 | return response.data[0][0][0]; 81 | } 82 | throw new Error("Invalid response for translate request"); 83 | } catch (e) { 84 | throw new Error(`Could not translate to "${targetLang}": ${e}`); 85 | } 86 | } 87 | 88 | module.exports = { 89 | isArray, 90 | isObject, 91 | translateText 92 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iobroker.broadlink2", 3 | "version": "2.3.0", 4 | "description": "ioBroker Broadlink2 Adapter", 5 | "author": "Frank Joke ", 6 | "contributors": [ 7 | "Reinhard Hiebl ", 8 | "mcm1957 " 9 | ], 10 | "homepage": "https://github.com/iobroker-community-adapters/ioBroker.broadlink2", 11 | "license": "MIT", 12 | "keywords": [ 13 | "ioBroker", 14 | "broadlink", 15 | "RM3", 16 | "A1", 17 | "SP1", 18 | "SP2", 19 | "SP3", 20 | "Smart Home", 21 | "home automation" 22 | ], 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/iobroker-community-adapters/ioBroker.broadlink2.git" 26 | }, 27 | "engines": { 28 | "node": ">= 18" 29 | }, 30 | "dependencies": { 31 | "@iobroker/adapter-core": "^3.1.6", 32 | "axios": "^1.6.8" 33 | }, 34 | "devDependencies": { 35 | "@alcalzone/release-script": "^3.8.0", 36 | "@alcalzone/release-script-plugin-iobroker": "^3.7.2", 37 | "@alcalzone/release-script-plugin-license": "^3.7.0", 38 | "@alcalzone/release-script-plugin-manual-review": "^3.7.0", 39 | "@iobroker/adapter-dev": "^1.3.0", 40 | "@iobroker/testing": "^4.1.3", 41 | "@tsconfig/node14": "^14.1.2", 42 | "@types/chai": "^4.3.11", 43 | "@types/chai-as-promised": "^7.1.8", 44 | "@types/mocha": "^10.0.6", 45 | "@types/node": "^20.14.9", 46 | "@types/proxyquire": "^1.3.31", 47 | "@types/sinon": "^17.0.3", 48 | "@types/sinon-chai": "^3.2.12", 49 | "chai": "^4.5.0", 50 | "chai-as-promised": "^7.1.2", 51 | "eslint": "^8.57.0", 52 | "eslint-config-prettier": "^9.1.0", 53 | "eslint-plugin-prettier": "^5.2.3", 54 | "gulp": "^4.0.2", 55 | "mocha": "^10.4.0", 56 | "prettier": "^3.4.2", 57 | "proxyquire": "^2.1.3", 58 | "sinon": "^18.0.0", 59 | "sinon-chai": "^3.7.0", 60 | "typescript": "~5.7.3" 61 | }, 62 | "main": "broadlink2.js", 63 | "scripts": { 64 | "test:js": "mocha --config test/mocharc.custom.json \"{!(node_modules|test)/**/*.test.js,*.test.js,test/**/test!(PackageFiles|Startup).js}\"", 65 | "test:package": "mocha test/package --exit", 66 | "test:integration": "mocha test/integration --exit", 67 | "test": "npm run test:js && npm run test:package", 68 | "check": "tsc --noEmit -p tsconfig.check.json", 69 | "lint": "eslint .", 70 | "translate": "translate-adapter", 71 | "release": "release-script" 72 | }, 73 | "bugs": { 74 | "url": "https://github.com/frankjoke/ioBroker.broadlink2/issues" 75 | }, 76 | "directories": { 77 | "doc": "doc", 78 | "lib": "lib", 79 | "test": "test" 80 | }, 81 | "eslintConfig": { 82 | "env": { 83 | "node": true, 84 | "mocha": true, 85 | "es6": true 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------