├── .vscode ├── extensions.json └── settings.json ├── license.md ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature-request 2.md │ ├── feature-request.md │ ├── support-request.md │ ├── support-request 2.md │ ├── bug-report 2.md │ └── bug-report.md └── workflows │ ├── build.yml │ └── tag-release.yml ├── nodemon.json ├── src ├── settings.ts ├── index.ts ├── deviceDatabase.json ├── InsteonLocalPlatform.ts └── InsteonLocalAccessory.ts ├── tsconfig.json ├── config-example.json ├── .eslintrc ├── package.json ├── .gitignore ├── .npmignore ├── config.schema.json ├── CHANGELOG.md └── README.md /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint" 4 | ] 5 | } -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | ## License 2 | Free for non-commercial use. You may not exercise any of the rights granted to you in any manner that is primarily intended for or directed toward commercial advantage or private monetary compensation. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # blank_issues_enabled: false 2 | # contact_links: 3 | # - name: Homebridge Discord Community 4 | # url: https://discord.gg/kqNCe2D 5 | # about: Ask your questions in the #YOUR_CHANNEL_HERE channel -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": [ 3 | "src" 4 | ], 5 | "ext": "ts", 6 | "ignore": [], 7 | "exec": "tsc && homebridge -I -D", 8 | "signal": "SIGTERM", 9 | "delay": 2000, 10 | "env": { 11 | "NODE_OPTIONS": "--trace-warnings" 12 | } 13 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.eol": "\n", 3 | "editor.formatOnSave": false, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": true 6 | }, 7 | "eslint.validate": [ 8 | "typescript" 9 | ], 10 | "editor.rulers": [ 11 | 140 12 | ], 13 | "eslint.enable": true 14 | } -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the name of the platform that users will use to register the plugin in the Homebridge config.json 3 | */ 4 | export const PLATFORM_NAME = 'InsteonLocal'; 5 | 6 | /** 7 | * This must match the name of your plugin as defined the package.json 8 | */ 9 | export const PLUGIN_NAME = 'homebridge-platform-insteonlocal'; -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { API } from 'homebridge'; 2 | 3 | import { PLATFORM_NAME } from './settings'; 4 | import { InsteonLocalPlatform } from './InsteonLocalPlatform'; 5 | 6 | /** 7 | * This method registers the platform with Homebridge 8 | */ 9 | export = (api: API) => { 10 | api.registerPlatform(PLATFORM_NAME, InsteonLocalPlatform); 11 | }; 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", // ~node10 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2015", 7 | "es2016", 8 | "es2017", 9 | "es2018" 10 | ], 11 | "allowJs": true, 12 | "declaration": true, 13 | "declarationMap": true, 14 | "sourceMap": true, 15 | "outDir": "./dist", 16 | "rootDir": "./src", 17 | "strict": true, 18 | "resolveJsonModule": true, 19 | "esModuleInterop": true, 20 | "noImplicitAny": false, 21 | "strictPropertyInitialization": false 22 | }, 23 | "include": [ 24 | "src/" 25 | ], 26 | "exclude": [ 27 | "**/*.spec.ts" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and Lint 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | # the Node.js versions to build on 12 | node-version: [10.x, 12.x, 13.x, 14.x, 15.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Use Node.js ${{ matrix.node-version }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | 22 | - name: Install dependencies 23 | run: npm install 24 | 25 | - name: Lint the project 26 | run: npm run lint 27 | 28 | - name: Build the project 29 | run: npm run build 30 | env: 31 | CI: true 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request 2.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe:** 11 | 12 | 13 | **Describe the solution you'd like:** 14 | 15 | 16 | **Describe alternatives you've considered:** 17 | 18 | 19 | **Additional context:** 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe:** 11 | 12 | 13 | **Describe the solution you'd like:** 14 | 15 | 16 | **Describe alternatives you've considered:** 17 | 18 | 19 | **Additional context:** 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support Request 3 | about: Need help? 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | **Describe Your Problem:** 13 | 14 | 15 | **Logs:** 16 | 17 | ``` 18 | Show the Homebridge logs here, remove any sensitive information. 19 | ``` 20 | 21 | **Plugin Config:** 22 | 23 | ```json 24 | Show your Homebridge config.json here, remove any sensitive information. 25 | ``` 26 | 27 | **Screenshots:** 28 | 29 | 30 | **Environment:** 31 | 32 | * **Plugin Version**: 33 | * **Homebridge Version**: 34 | * **Node.js Version**: 35 | * **NPM Version**: 36 | * **Operating System**: 37 | 38 | 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support-request 2.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support Request 3 | about: Need help? 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | **Describe Your Problem:** 13 | 14 | 15 | **Logs:** 16 | 17 | ``` 18 | Show the Homebridge logs here, remove any sensitive information. 19 | ``` 20 | 21 | **Plugin Config:** 22 | 23 | ```json 24 | Show your Homebridge config.json here, remove any sensitive information. 25 | ``` 26 | 27 | **Screenshots:** 28 | 29 | 30 | **Environment:** 31 | 32 | * **Plugin Version**: 33 | * **Homebridge Version**: 34 | * **Node.js Version**: 35 | * **NPM Version**: 36 | * **Operating System**: 37 | 38 | 39 | -------------------------------------------------------------------------------- /config-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "bridge": { 3 | "name": "Homebridge", 4 | "username": "CC:22:3D:E3:CE:22", 5 | "pin": "123-45-678" 6 | }, 7 | 8 | "platforms": [ 9 | { 10 | "platform": "InsteonLocal", 11 | "name": "Insteon Local Platform", 12 | "user": "", 13 | "pass": "", 14 | "host": "", 15 | "port": "", 16 | "model": "2245", 17 | "refresh":"0", 18 | "server_port": "3000", 19 | "keepAlive": "3600", 20 | "devices": [ 21 | { 22 | "name": "Great Room Lamp", 23 | "deviceID" : "", 24 | "dimmable" : "yes", 25 | "deviceType" : "lightbulb" }, 26 | { 27 | "name": "FR Lamp", 28 | "deviceID": "", 29 | "dimmable": "no", 30 | "groupID": "30", 31 | "keypadbtn": "F", 32 | "groupMembers": "AABBCC,XXYYZZ,DDEEFF", 33 | "deviceType": "scene" 34 | } 35 | ] 36 | } 37 | ], 38 | 39 | "accessories": [ 40 | 41 | ] 42 | 43 | } 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report 2.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | **Describe The Bug:** 13 | 14 | 15 | **To Reproduce:** 16 | 17 | 18 | **Expected behavior:** 19 | 20 | 21 | **Logs:** 22 | 23 | ``` 24 | Show the Homebridge logs here, remove any sensitive information. 25 | ``` 26 | 27 | **Plugin Config:** 28 | 29 | ```json 30 | Show your Homebridge config.json here, remove any sensitive information. 31 | ``` 32 | 33 | **Screenshots:** 34 | 35 | 36 | **Environment:** 37 | 38 | * **Plugin Version**: 39 | * **Homebridge Version**: 40 | * **Node.js Version**: 41 | * **NPM Version**: 42 | * **Operating System**: 43 | 44 | 45 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | **Describe The Bug:** 13 | 14 | 15 | **To Reproduce:** 16 | 17 | 18 | **Expected behavior:** 19 | 20 | 21 | **Logs:** 22 | 23 | ``` 24 | Show the Homebridge logs here, remove any sensitive information. 25 | ``` 26 | 27 | **Plugin Config:** 28 | 29 | ```json 30 | Show your Homebridge config.json here, remove any sensitive information. 31 | ``` 32 | 33 | **Screenshots:** 34 | 35 | 36 | **Environment:** 37 | 38 | * **Plugin Version**: 39 | * **Homebridge Version**: 40 | * **Node.js Version**: 41 | * **NPM Version**: 42 | * **Operating System**: 43 | 44 | 45 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:@typescript-eslint/eslint-recommended", 6 | "plugin:@typescript-eslint/recommended" // uses the recommended rules from the @typescript-eslint/eslint-plugin 7 | ], 8 | "parserOptions": { 9 | "ecmaVersion": 2018, 10 | "sourceType": "module" 11 | }, 12 | "ignorePatterns": [ 13 | "dist" 14 | ], 15 | "rules": { 16 | "quotes": ["warn", "single"], 17 | "indent": ["warn", 2, { "SwitchCase": 1 }], 18 | "semi": ["off"], 19 | "comma-dangle": ["warn", "always-multiline"], 20 | "dot-notation": "off", 21 | "eqeqeq": "warn", 22 | "curly": ["warn", "all"], 23 | "brace-style": ["warn"], 24 | "prefer-arrow-callback": ["warn"], 25 | "max-len": ["warn", 140], 26 | "no-console": ["warn"], // use the provided Homebridge log method instead 27 | "no-non-null-assertion": ["off"], 28 | "comma-spacing": ["error"], 29 | "no-multi-spaces": ["warn", { "ignoreEOLComments": true }], 30 | "no-trailing-spaces": ["warn"], 31 | "lines-between-class-members": ["warn", "always", {"exceptAfterSingleLine": true}], 32 | "@typescript-eslint/explicit-function-return-type": "off", 33 | "@typescript-eslint/no-non-null-assertion": "off", 34 | "@typescript-eslint/explicit-module-boundary-types": "off", 35 | "@typescript-eslint/semi": ["warn"], 36 | "@typescript-eslint/member-delimiter-style": ["warn"] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": false, 3 | "displayName": "InsteonLocal", 4 | "name": "homebridge-platform-insteonlocal", 5 | "version": "0.5.11", 6 | "description": "Insteon platform plugin with local control for homebridge: https://github.com/nfarina/homebridge", 7 | "license": "ISC", 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/kuestess/homebridge-platform-insteonlocal.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/kuestess/homebridge-platform-insteonlocal/issues" 14 | }, 15 | "engines": { 16 | "node": ">=12.13.0", 17 | "homebridge": ">=1.3.0" 18 | }, 19 | "main": "dist/index.js", 20 | "scripts": { 21 | "lint": "eslint src/**.ts", 22 | "watch": "npm run build && npm link && nodemon", 23 | "build": "rimraf ./dist && tsc", 24 | "prepublishOnly": "npm run lint && npm run build" 25 | }, 26 | "keywords": [ 27 | "homebridge-plugin", 28 | "insteon", 29 | "homebridge" 30 | ], 31 | "dependencies": { 32 | "chalk": "^4.1.2", 33 | "express": "^4.17.1", 34 | "home-controller": "^0.9.2", 35 | "moment": "^2.29.1", 36 | "underscore": "^1.13.1", 37 | "util": "^0.12.4" 38 | }, 39 | "devDependencies": { 40 | "@types/node": "^14.14.31", 41 | "@typescript-eslint/eslint-plugin": "^4.16.1", 42 | "@typescript-eslint/parser": "^4.16.1", 43 | "eslint": "^7.21.0", 44 | "homebridge": "^1.5.0", 45 | "nodemon": "^2.0.7", 46 | "rimraf": "^3.0.2", 47 | "ts-node": "^9.1.1", 48 | "typescript": "^4.2.2" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.github/workflows/tag-release.yml: -------------------------------------------------------------------------------- 1 | name: Tag and Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | create-tag: 10 | runs-on: ubuntu-latest 11 | outputs: 12 | output1: ${{ steps.create-tag.outputs.version }} 13 | steps: 14 | - name: checkout 15 | id: checkout 16 | uses: actions/checkout@v2 17 | - name: create-tag 18 | id: create-tag 19 | uses: Klemensas/action-autotag@stable 20 | with: 21 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 22 | tag_prefix: "v" 23 | draft-release: 24 | needs: create-tag 25 | runs-on: ubuntu-latest 26 | permissions: 27 | contents: write 28 | steps: 29 | - name: Get version from tag 30 | id: tag_name 31 | run: | 32 | echo ::set-output name=current_version::${GITHUB_REF#refs/tags/v} 33 | shell: bash 34 | - uses: actions/checkout@v2 35 | - name: Get Changelog Entry 36 | id: changelog_reader 37 | uses: mindsers/changelog-reader-action@v2 38 | with: 39 | validation_level: warn 40 | version: ${{needs.create-tag.outputs.output1}} 41 | # version: ${{ steps.tag_name.outputs.current_version }} 42 | path: ./CHANGELOG.md 43 | - uses: ncipollo/release-action@v1 44 | with: 45 | tag: v${{needs.create-tag.outputs.output1}} 46 | name: Release ${{ steps.changelog_reader.outputs.version }} 47 | body: ${{ steps.changelog_reader.outputs.changes }} 48 | # prerelease: ${{ steps.changelog_reader.outputs.status == 'prereleased' }} 49 | draft: true 50 | allowUpdates: true 51 | artifacts: "*,src/*" 52 | token: ${{ secrets.GITHUB_TOKEN }} 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore compiled code 2 | dist 3 | 4 | # ------------- Defaults ------------- # 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # Snowpack dependency directory (https://snowpack.dev/) 50 | web_modules/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variables file 77 | .env 78 | .env.test 79 | 80 | # parcel-bundler cache (https://parceljs.org/) 81 | .cache 82 | .parcel-cache 83 | 84 | # Next.js build output 85 | .next 86 | 87 | # Nuxt.js build / generate output 88 | .nuxt 89 | dist 90 | 91 | # Gatsby files 92 | .cache/ 93 | # Comment in the public line in if your project uses Gatsby and not Next.js 94 | # https://nextjs.org/blog/next-9-1#public-directory-support 95 | # public 96 | 97 | # vuepress build output 98 | .vuepress/dist 99 | 100 | # Serverless directories 101 | .serverless/ 102 | 103 | # FuseBox cache 104 | .fusebox/ 105 | 106 | # DynamoDB Local files 107 | .dynamodb/ 108 | 109 | # TernJS port file 110 | .tern-port 111 | 112 | # Stores VSCode versions used for testing VSCode extensions 113 | .vscode-test 114 | 115 | # yarn v2 116 | 117 | .yarn/cache 118 | .yarn/unplugged 119 | .yarn/build-state.yml 120 | .pnp.* 121 | .DS_Store 122 | .gitignore 123 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Ignore source code 2 | src 3 | 4 | # ------------- Defaults ------------- # 5 | 6 | # gitHub actions 7 | .github 8 | 9 | # eslint 10 | .eslintrc 11 | 12 | # typescript 13 | tsconfig.json 14 | 15 | # vscode 16 | .vscode 17 | 18 | # nodemon 19 | nodemon.json 20 | 21 | # Logs 22 | logs 23 | *.log 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | lerna-debug.log* 28 | 29 | # Diagnostic reports (https://nodejs.org/api/report.html) 30 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 31 | 32 | # Runtime data 33 | pids 34 | *.pid 35 | *.seed 36 | *.pid.lock 37 | 38 | # Directory for instrumented libs generated by jscoverage/JSCover 39 | lib-cov 40 | 41 | # Coverage directory used by tools like istanbul 42 | coverage 43 | *.lcov 44 | 45 | # nyc test coverage 46 | .nyc_output 47 | 48 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 49 | .grunt 50 | 51 | # Bower dependency directory (https://bower.io/) 52 | bower_components 53 | 54 | # node-waf configuration 55 | .lock-wscript 56 | 57 | # Compiled binary addons (https://nodejs.org/api/addons.html) 58 | build/Release 59 | 60 | # Dependency directories 61 | node_modules/ 62 | jspm_packages/ 63 | 64 | # Snowpack dependency directory (https://snowpack.dev/) 65 | web_modules/ 66 | 67 | # TypeScript cache 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | .npm 72 | 73 | # Optional eslint cache 74 | .eslintcache 75 | 76 | # Microbundle cache 77 | .rpt2_cache/ 78 | .rts2_cache_cjs/ 79 | .rts2_cache_es/ 80 | .rts2_cache_umd/ 81 | 82 | # Optional REPL history 83 | .node_repl_history 84 | 85 | # Output of 'npm pack' 86 | *.tgz 87 | 88 | # Yarn Integrity file 89 | .yarn-integrity 90 | 91 | # dotenv environment variables file 92 | .env 93 | .env.test 94 | 95 | # parcel-bundler cache (https://parceljs.org/) 96 | .cache 97 | .parcel-cache 98 | 99 | # Next.js build output 100 | .next 101 | 102 | # Nuxt.js build / generate output 103 | .nuxt 104 | dist 105 | 106 | # Gatsby files 107 | .cache/ 108 | # Comment in the public line in if your project uses Gatsby and not Next.js 109 | # https://nextjs.org/blog/next-9-1#public-directory-support 110 | # public 111 | 112 | # vuepress build output 113 | .vuepress/dist 114 | 115 | # Serverless directories 116 | .serverless/ 117 | 118 | # FuseBox cache 119 | .fusebox/ 120 | 121 | # DynamoDB Local files 122 | .dynamodb/ 123 | 124 | # TernJS port file 125 | .tern-port 126 | 127 | # Stores VSCode versions used for testing VSCode extensions 128 | .vscode-test 129 | 130 | # yarn v2 131 | 132 | .yarn/cache 133 | .yarn/unplugged 134 | .yarn/build-state.yml 135 | .pnp.* -------------------------------------------------------------------------------- /config.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginAlias": "InsteonLocal", 3 | "pluginType": "platform", 4 | "singular": true, 5 | "headerDisplay": "Homebridge platform plugin for local Insteon control.", 6 | "footerDisplay": "See [CHANGELOG](https://github.com/kuestess/homebridge-platform-insteonlocal/blob/master/CHANGELOG.md).", 7 | "schema": { 8 | "type": "object", 9 | "properties": { 10 | "name": { 11 | "title": "Name", 12 | "type": "string", 13 | "default": "InsteonLocal", 14 | "required": true 15 | }, 16 | "user": { 17 | "title": "User", 18 | "type": "string", 19 | "default": true 20 | }, 21 | "pass": { 22 | "$title": "Pass", 23 | "type": "string", 24 | "default": true 25 | }, 26 | "host": { 27 | "title": "Host", 28 | "type": "string", 29 | "default": true 30 | }, 31 | "port": { 32 | "title": "Port", 33 | "type": "string", 34 | "default": "25105" 35 | }, 36 | "model": { 37 | "title": "Model", 38 | "type": "string", 39 | "default": "2245", 40 | "oneOf": [ 41 | { "title": "2245", "enum": ["2245"] }, 42 | { "title": "2243", "enum": ["2243"] }, 43 | { "title": "2242", "enum": ["2242"] }, 44 | { "title": "PLM", "enum": ["PLM"] } 45 | ] 46 | }, 47 | "refresh": { 48 | "title": "Refresh", 49 | "type": "string", 50 | "default": false 51 | }, 52 | "server_port": { 53 | "title": "Server Port", 54 | "type": "string", 55 | "default": "3000" 56 | }, 57 | "keepAlive": { 58 | "title": "Keep Alive", 59 | "type": "string", 60 | "default": false 61 | }, 62 | "devices": { 63 | "title": "Devices", 64 | "type": "array", 65 | "required": false, 66 | "items": { 67 | "title": "Device", 68 | "type": "object", 69 | "properties": { 70 | "name": { 71 | "title": "Name", 72 | "type": "string", 73 | "required": true 74 | }, 75 | "deviceID": { 76 | "title": "Device ID", 77 | "type": "string", 78 | "required": true 79 | }, 80 | "dimmable": { 81 | "title": "Dimmable", 82 | "type": "string", 83 | "default": true 84 | }, 85 | "deviceType": { 86 | "title": "Device Type", 87 | "type": "string", 88 | "default": "dimmer", 89 | "oneOf": [ 90 | { "title": "lightbulb", "enum": ["lightbulb"] }, 91 | { "title": "dimmer", "enum": ["dimmer"] }, 92 | { "title": "switch", "enum": ["switch"] }, 93 | { "title": "scene", "enum": ["scene"] }, 94 | { "title": "remote", "enum": ["remote"] }, 95 | { "title": "iolinc", "enum": ["iolinc"] }, 96 | { "title": "motionsensor", "enum": ["motionsensor"] }, 97 | { "title": "leaksensor", "enum": ["leaksensor"] }, 98 | { "title": "doorsensor", "enum": ["doorsensor"] }, 99 | { "title": "outlet", "enum": ["outlet"] }, 100 | { "title": "keypad", "enum": ["keypad"] }, 101 | { "title": "shades", "enum": ["shades"] }, 102 | { "title": "blinds", "enum": ["blinds"] }, 103 | { "title": "smoke", "enum": ["smoke"] }, 104 | { "title": "fan", "enum": ["fan"] }, 105 | { "title": "thermostat", "enum": ["thermostat"] } 106 | ] 107 | }, 108 | "groupID": { 109 | "title": "Group ID", 110 | "type": "string" 111 | }, 112 | "keypadbtn": { 113 | "title": "Keypad Button", 114 | "type": "string" 115 | }, 116 | "groupMembers": { 117 | "title": "Group Members", 118 | "type": "string" 119 | } 120 | } 121 | } 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | ## [0.5.11] - 2023-09-19 5 | ### Fixed 6 | - Fix for target keypads feature (#300) 7 | 8 | ## [0.5.10] - 2023-07-29 9 | ### Enhanced 10 | - Initial support for new i3 keypad 11 | 12 | ## [0.5.9] - 2023-07-07 13 | ### Enhanced 14 | - Update device database to include new i3 devices 15 | 16 | ## [0.5.8] - 2023-02-15 17 | ### Fixed 18 | - Fix error with deleting links (#291) 19 | - Fix error when devices array is not present in config (#289) 20 | ## [0.5.7] - 2023-01-03 21 | ### Fixed 22 | - Restore 'keepAlive' and 'connectionWatcher' functionality (#286) 23 | 24 | ## 0.5.6 25 | - [FIXED] Fixes for thermostat support 26 | ## 0.5.5 27 | - [FIXED] Fixes for group members defined by name (#260) 28 | - [FIXED] Fixes for removing or renaming accessories 29 | - [ENHANCED] Optimize getting hub id on startup 30 | ## 0.5.4-3 31 | - [DEBUG] Additional debugging for setting thermostat temp/mode 32 | ## 0.5.4-2 33 | - [FIXED] Fix for setting thermostat mode 34 | - [ENHANCED] Additional debugging for temp setting 35 | ## 0.5.4-1 36 | - [FIXED] Fix for temperature units 37 | ## 0.5.4 38 | - [NEW] Initial support for thermostat (#267) 39 | ## 0.5.3 40 | - [FIXED] Fix for fanlinc issue (#278) 41 | ## 0.5.1 42 | - [FIXED] Fix crash from specifying refresh interval (#272) 43 | - [FIXED] Restore `setTargetKeypadBtn` (#273) 44 | ## 0.5.0 45 | - [NEW] Complete refactor to more modern, dynamic platform 46 | - [NEW] Merge PR from @microbmen to add fan control/status to the express server (#223) 47 | - [FIXED] Address issue with groupMember logic (#260) 48 | ## 0.4.28 49 | - [NEW] Automated device discovery/addition to config 50 | - [REMOVE] Remove config.json editing gui from main page in favor of homebridge/hoobs ui and above auto-discovery/addition 51 | - [FIXED] Fix crash when groupMembers contain a period in the device id (#239) 52 | ## 0.4.27 53 | - [FIXED] Fix stupid error in logging :( 54 | ## 0.4.26 55 | - [FIXED] Fix restart issues on HOOBS 56 | - [FIXED] Better error handling for `getHubInfo` 57 | - [ENHANCED] Address 'max listeners' message 58 | ## 0.4.25 59 | - [FIXED] Add outlet position to schema. 60 | - [NEW] Hub info automatically discovered in ui (no longer need to get hub info). 61 | - [FIXED] Fix ui when no devices are defined in config. 62 | ## 0.4.24 63 | - [FIXED] Quick fixes to prevent empty model/devices array and improve configuration. 64 | ## 0.4.23 65 | - [FIXED] Fix status for keypads with attached load (#185) 66 | ## 0.4.22 67 | - [FIXED] Fix debounce logic when using Siri. 68 | ## 0.4.21 69 | - [FIXED] Fixes for configuration via homebrudge ui. 70 | ## 0.4.20 71 | - [FIXED] Enable connection to Hub and load of Insteon UI even if no devices are defined in config. 72 | - [NEW] Merge PR from @donavanbecker: Allow easy homebridge config UI setup (#152) 73 | - [ENHANCED] Readme updates courtesy of @calorian 74 | ## 0.4.19 75 | - [FIXED] Remove npm-shrinkwrap; no longer required with updates to `home-controller` dependencies 76 | - [NEW] Merge PRs from @mikeypdev: fix for groupMembers (#147), expand groupMembers to switches/dimmers (#149), add 'momentary' property for scene configurations (#148) 77 | ## 0.4.18 78 | - [FIXED] Fix warning from 'set' handlers (#136) 79 | - [NEW] Add invert sensor option for door/window/contact sensors (#134) 80 | ## 0.4.17 81 | 82 | - [FIXED] Fix crash when devices array not in config (#77) 83 | - [NEW] Initial support for X10 devices (#119) 84 | - [NEW] Added ability to disable battery monitoring (#123) 85 | ## 0.4.16 86 | 87 | - [FIXED] Fix crash when no devices defined (#77) 88 | - [FIXED] Fix dimmer/switch/fan status check on every hub reconnect (#127) 89 | ## 0.4.15 90 | 91 | - [FIXED] Fix fan light status in eventListener (#102) 92 | ## 0.4.14 93 | 94 | - [ENHANCED] Update eventListener to better capture off status events (#110) 95 | 96 | ## 0.4.13 97 | 98 | - [FIXED] Fix for 'no response' from garage door (thanks to @THX723) (#97) 99 | - [ENHANCED] Add ability to create a simple scene in the config (thanks to @THX723) - see example here: https://github.com/kuestess/homebridge-platform-insteonlocal/pull/103 (#101) 100 | 101 | ## 0.4.12 102 | 103 | - [NEW] Added 'disabled' feature (#89) 104 | - [ENHANCED] Dimmer/lightbulb devices are automatically set to dimmable, no need to specify in the config (#76) 105 | 106 | ## 0.4.11 107 | 108 | - [NEW] Initial support for four-button remotes 109 | - [NEW] Added changelog 110 | - [ENHANCED] Improved quick start for InsteonUI 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Support via PayPal][paypal-button]][paypal-kuestess] 2 | 3 | # homebridge-platform-insteonlocal 4 | Homebridge platform plugin for local Insteon control 5 | 6 | See [CHANGELOG][]. 7 | 8 | [changelog]: CHANGELOG.md 9 | 10 | ** Note: When upgrading from any prior version of the plugin to version 0.5.0 or greater, you will need to re-add your devices and rebuild existing automations. This is a one-time thing and is not a bug. ** 11 | 12 | Overview 13 | -------- 14 | Implements local control of Insteon devices including switches, dimmers, outlets, fans, blinds, scenes, iolincs (configured as a garage door), motion sensors, door/window sensors, and leak sensors via Homebridge. Leverages [home-controller](https://github.com/automategreen/home-controller) to enable control and status of multiple Insteon devices. Supports both Insteon Hub 2242 and 2245 and now has beta support for running directly on a Hub Pro (thanks to @rasod). 15 | 16 | Devices can be auto-discovered via the Insteon UI (see below) and automatically added to the homebridge config.json with the most approproate device type. On the 'Devices' tab, select the 'Devices Action' menu. If you have not already, click on 'Get Devices' to pull devices from the Hub/PLM. Once that completes, you can click 'Add All to Config' which will add all devices to your config. Alternately, you can add a single device to the config by clicking the 'Add to Config' button on the device page after selecting an individual device. 17 | 18 | Supported devices include: dimmers (including i3), lightbulbs, on/off switch, keypads (6 or 8 button config and i3), on/off and dimmer outlets, remotes, iolinc, motion sensor, leak sensor, door sensor, smoke detector, fan controller, micro module, and thermostat. 19 | 20 | Supported controllers include: Hub1 (2242), Hub2 (2245), Hub Pro, PLM 21 | 22 | ## Install 23 | 24 | To install from npm: 25 | 26 | `npm -g install homebridge-platform-insteonlocal` 27 | 28 | Alternately, clone the repository, cd into the cloned directory and install using npm: 29 | 30 | `npm -g install` 31 | 32 | Configuration 33 | ------------- 34 | Edit the config.json (see example) to fit your installation - configuration parameters are: 35 | 36 | - `user`: Hub username from Insteon app (Not your Insteon login username. Go to Settings->House in the Insteon app and use the 'Hub Username' from there.) 37 | - `pass`: Hub password from Insteon app (Not your Insteon login password. Go to Settings->House in the Insteon app and use the 'Hub Password' from there.) 38 | - `host`: local IP of your Insteon hub or device path of your Insteon PLM 39 | - `port`: port from Insteon app 40 | - `model`: model number of your hub. Valid values are 2242, 2245, 2243, or PLM 41 | - `refresh`: device status refresh interval in seconds (disabled by default, set to 0 to disable polling) 42 | - `use_express`: true or false to enable/disable Express server 43 | - `server_port`: port for local Express server 44 | 45 | 46 | 47 | Devices are also defined in the config.json as follows: 48 | 49 | - `name`: Device name as you want it to appear in Homebridge 50 | - `deviceID`: Insteon ID 51 | - `groupID`: Insteon group ID for a scene 52 | - `keypadbtn`: Keypad button to check for status of a scene, in caps. For a six-button configuration, use 'ON' if the ON/OFF buttons are your scene controller. 53 | - `dimmable`: dimmable or non-dimming device - valid values are "yes" or "no". This is automatically set to 'yes' for dimmer/lightbulb device types. 54 | - `deviceType`: valid values include 'lightbulb', 'dimmer', 'switch', 'scene', 'remote', 'iolinc', 'motionsensor', 'leaksensor', 'doorsensor', 'outlet', 'keypad', 'shades', 'blinds', 'smoke', 'fan', and 'thermostat'. 55 | - `refresh`: device-level refresh interval in seconds. This will override the platform-level refresh value and will still refresh individual devices even if platform-level polling is turned off. 56 | - `controllers`: this is an array `["",""]` of other Insteon devices that this device is controlled by. ie if you have a plug in dimmer that is controlled by a wall switch you would add the wall switch ID as a controller for the plug in dimmer. The controller device does not need to be a device listed in the config.json 57 | - `targetKeypadID`: this is an array `["",""]` of Insteon keypad(s), whose scene button LED you would like to set accoridngly to the state of the device. See additional notes below on Target Keypad LED. 58 | - `targetKeypadBtn`: this is an array `["button_letter","button_letter"]` of Insteon keypad button(s), 'A' - 'H', that corresponds to the array from `targetKeypadID`. See additional notes below on Target Keypad LED. 59 | - `targetKeypadSixBtn`: this is an array `[true/false, true/false]` of Insteon keypad button layout, that corresponds to the array from `targetKeypadID`. `true` denote a 6-button keypad, while `false` denotes an 8-button keypad. See additional notes below on Target Keypad LED. 60 | - `targetKeypadFourBtn`: similar to `targetKeypadSixBtn` above but for a 4-button (i3) keypad. `true` denotes a 4-button keypad, while `false` denotes an 8-button keypad. See additional notes below on Target Keypad LED. 61 | - `disabled`: set to true to disable Insteon communication for a device (defaults to false). Device will still appear in Home (or other apps), but can't be controlled. Good to use for 'seasonal' devices. 62 | - `groupMembers`: comma-delimited list of Insteon IDs that are linked to this device (optional, for devices with on/off states like dimmers and switches); member device status will be automatically updated after the device is turned on or off 63 | 64 | Battery operated devices: Battery status is monitored for battery operated devices (leak, motion, door/window sensors) and will alert when the device sends a low battery signal. The heartbeat for those devices is also monitored (sent from device every 24 hours). You will also receive a low battery alert if no heartbeat is received within 24 hours. 65 | 66 | Scenes: 67 | Scenes remain on/off only and support status when controlled via a Keypadlinc. Scenes are configured using additional the parameters below: 68 | 69 | - `deviceID`: Insteon ID of a keypad that controls the scene 70 | - `keypadbtn`: Keypadlinc button that indicates the status of the scene - valid values are 'A' - 'H' 71 | - `six_btn`: set to `true` if using a Keypadlinc configured as a 6-button; default is `false` 72 | - `groupID`: the group number in the Insteon app (Scenes->Edit Scene->Group Number) 73 | - `groupMembers`: comma-delimited list of Insteon IDs that are part of the group/scene (optional); member device status will be automatically updated after a scene is triggered 74 | - `momentary`: since hub-based scenes do not support status, you can set this to `true` to make a scene 'stateless'. This will allow you to re-trigger the scene or run a different scene on the same devices without having to turn the scene `off` first. 75 | 76 | Target Keypad LED: 77 | By Insteon's design, keypad (scene button) LED follows the state of a linked scene only; it does not act according to the device state itself. eg. Turn on `scene 1` then the corresponding keypad LED lights up, but turning on `Light 1` directly will not light up the keypad LED. 78 | This enhancement allows you to specify which keypad LED(s) to set according to the device state; effetively turning keypad buttons into true device status indicators that many had wished for. 79 | For the following example, when `Light 1` "XXYYZZ" is turned on (or at any dim level), button "A" of the 6-button keypad "AABBCC" is lit up, as do button "D" of the 8-button keypad "BBCCDD". 80 | ``` 81 | "name" : "Light 1", 82 | "deviceID" : "XXYYZZ", 83 | "targetKeypadID" : [ "AABBCC", "BBCCDD" ], 84 | "targetKeypadBtn" : [ "A", "D" ], 85 | "targetKeypadSixBtn" : [ true, false ] 86 | ``` 87 | 88 | Fanlinc support: 89 | To configure fanlinc support, use the 'fan' device type. This will create a fan device only - you can add a separate entry in your config (using the same `deviceID`) to add the light as a device. 90 | 91 | In addition to scenes as described above, keypads are supported as on/off switches intended to be used in Homekit automation and/or scenes. 92 | 93 | - `keypadbtn`: keypad button to use as the trigger 94 | - `six_btn`: set to `true` if using a Keypadlinc configured as a 6-button; default is `false` 95 | 96 | For iolinc devices, there is an additional parameter that can be defined: 97 | 98 | - `gdo_delay`: number of seconds before status of the garage door is updated. This delay should be configured to closely approximate the time it takes the garage door to fully close (if `invert_sensor` = false) or fully open (if `invert_sensor` = true). [default = 15] 99 | - `invert_sensor`: set to true if your iolinc sensor is inverted, ie off when closed and on when open. [default = false] 100 | 101 | Remote support: 102 | Remotes are supported as on/off switches or stateless switches intended to be used in Homekit automation and/or scenes. Both 8-button and 4-button remotes are supported. Additional parameters that should be used when defining a Remote device are: 103 | 104 | - `remotebtn`: Remote button that triggers the switch - valid values are 'A' - 'H' 105 | - `stateless`: Define as a stateless switch - valid values are true or false [default = false] 106 | - `four_btn`: set to `true` for 4-button remotes [default = false] 107 | 108 | Outlet support: 109 | On/off outlets are supported with independent control over each outlet (each is defined as an individual device). Additional parameters that should be used when defining an outlet are: 110 | 111 | - `position`: Specify the position of the outlet - valid values are 'top' or 'bottom' [default = top] 112 | 113 | Battery operated devices: 114 | Low battery levels are reported periodically by the device and by default are shown in the Home app UI. To disable low battery reporting, use the optional configuration parameter below. This should be set at the individual device level. Even with low battery status disabled, you will still get a low battery alert in the Home app if two heartbeat messages from the device are missed (takes ~2 days), likely indicating a dead device. 115 | 116 | - `disableBatteryStatus`: default false; set to true to disable low battery reporting. 117 | 118 | Contact sensors: 119 | - `invert_sensor`: set to true to invert the sensor status, ie off when closed and on when open. [default = false] 120 | 121 | InsteonUI 122 | --------- 123 | 124 | Introducing InsteonUI, a new way to manage your Insteon devices and InsteonLocal configuration. Think of it as 'Houselinc' in a browser. 125 | 126 | ### InsteonUI Quick Start 127 | Direct your browser to the host that you have Insteonlocal running on and the port configured in your config.json (ie, 127.0.0.1:3000 if running on the local machine). Before using any of the pages described below, you will need to complete the following steps: 128 | 129 | 1. Click on the 'Hub' link. In the top section of the page, click 'Get Hub Info'. This pulls information from the Hub, most notably the Insteon ID. 130 | 2. Still on the 'Hub' page, click 'Get Devices/Links' under the action menu. 131 | 132 | You should now have devices populated on the 'Devices' page, and be able to link/unlink devices from the Hub, as well as create scenes. 133 | 134 | * Config:
135 | Manage your Insteonlocal configuration. Hub connection parameter settings are managed in the top section, device settings in the bottom. No changes are made to your config.json until you click save. To add a device, click the 'Add' button at the bottom of the page. Configuration is limited to basics until I can figure out a new UI. 136 | 137 | * Hub
138 | Information about your Hub. To start, click 'Get Hub Info'. Under the 'Action' menu, click 'Get Devices/Links'. This will discover devices and links from the Hub and populate scenes controlled by the hub. This must be done before any devices are displayed on the 'Devices' tab.

In the 'Links' tab on the Hub page, you can delete a link by clicking the trashcan icon (it wil confirm before deleteing). Note that this only deletes the link from the Hub and not the corresponding device. This is useful for deleting half-links from the Hub. 139 | 140 | * Devices
141 | If you have already discovered devices from the Hub, you should see a list of devices in the left-hand column. If not, click 'Get Devices' and the device list should populate after discovery is complete.

Once devices are discovered, click on a device in the list. In the right-hand pane, you can give the device a friendly name (be sure to click 'Save'). Devices that were in your config should already be named (you can change the name here without overwriting your config). Under the 'Action' menu, you can get links information and links from the device by clicking 'Get Dev Info/Links'. Depending upon the number of links in the device, this may take some time and is best to do when there is no other Insteon traffic. If you want to do this for all devices at once, click 'Get All Dev Links' in the top right. Again, this may take time. 142 | 143 | You can also identify the device by clicking 'Beep' under the 'Action' menu.

Three tabs in the bottom part of the page show details for the selected device: 144 | * Operating Flags: Lists device config parameters (not editable, for now). The database delta will change anytime a link is modified on a device. The UI will check this before retrieving links from the device. 145 | * Links: Lists all links stored in the device database. You can delete a link by clicking the wastebasket button (there is a confirmation). Again, this only deletes the link from the selected device and not from any linked devices. Good for cleaning up half-links. 146 | * Scenes: Lists all scenes (complete with other responders) that the device participates in. Level and ramp rate information is only available for devices that you have retrieved information and links for. 147 | 148 | * Link
149 | Link/unlink devices from the hub and create scenes. This is fairly sel-explanatory, but to link/unlink a device, just enter the device id that you wish to link/unlink in the relevant field and click the 'Link' or 'Unlink' button. 150 | 151 | To create a scene, select the desired device from the dropdown list and fill out the level, ramp rate, and controller/responder fields. The group number defaults to 1 for most devices. For a keypad, the group number corresponds to the button number (ie, A=1, B=2, C=3, etc). If the Hub is a controller, select an unoccupied group number (one that does not currently have a scene defined) or you will overwrite an existing scene. 152 | 153 | All information for your Hub and devices is stored in `insteon.json` saved in your homebridge config directory. It is fully readable json, and can be viewed in any editor. 154 | 155 | ## Express Server 156 | 157 | This plugin will set up a local [Express](https://expressjs.com) server at the port specified in your config.json (see below) that can also be accessed via a browser to get or manipulate Insteon device status and view hub or device information. Endpoints for the Express sever include: 158 | 159 | - `/light/[id]/on`: turn on the light with Insteon [id] 160 | - `/light/[id]/off`: turn off the light with Insteon [id] 161 | - `/light/[id]/status`: get status of the light with Insteon [id] 162 | - `/light/[id]/level/[targetlevel]`: set brightness of the light with Insteon [id] to [targetlevel] 163 | - `/scene/[group]/on`: turn on the scene with Insteon [group] number 164 | - `/scene/[group]/off`: turn off the scene with Insteon [group] number 165 | - `/iolinc/[id]/relay_on`: turn on the relay for iolinc with Insteon [id] 166 | - `/iolinc/[id]/relay_off`: turn off the relay for iolinc with Insteon [id] 167 | - `/iolinc/[id]/relay_status`: get status of the relay for iolinc with Insteon [id] 168 | - `/iolinc/[id]/sensor_status`: get status of the sensor for iolinc with Insteon [id] 169 | - `/fan/[id]/level/[targetlevel]`: set speed of the fan (fanlinc) with Insteon [id] to [targetlevel] 170 | - `/fan/[id]/status`: get status of the fan (fanlinc) with Insteon [id] 171 | - `/links`: get all links from your Insteon Hub 172 | - `/links/[id]`: get links for device with Insteon [id] 173 | - `/info/[id]`: get info for device with Insteon [id] 174 | 175 | The Express server is now optional and can be disabled if desired. 176 | 177 | 178 | Connection Watcher 179 | ------------------ 180 | The Insteon Hub seems to give up on connections after a certain period of time, resulting in no response or incorrect status in Homekit. Starting with v0.3.4, a `connectionWatcher` will periodically reset the connection to the Hub. This is a temporary workaround, but seems to address the issue and create a better experience without having to restart `homebridge`. 181 | The default connection reset duration is 1 hour and can be customized or disabled in you config as follows: 182 | 183 | - `keepAlive`: Hub connection reset duration in seconds (default is "3600" [1 hour]). Set to "0" to disable. 184 | 185 | For model 2242 hubs, the Connection Watcher wil determine if a request is in progress and, if not, close the connection. This model of hub seems particularly sensitive to connection duration/number of connections, so this will effectively spare connections as much as possible an only create them on-demand. The downside is that the eventListener will not function (as it requires a persistent connection), however polling will still update device status. 186 | 187 | Using The HubPro Model 2243 (Beta) 188 | ----------------------------------- 189 | It is possible to use the official Insteon HubPro as a complete homebridge server and Insteon Hub. This requires flashing the HubPro and installing homebridge as normal. Inside the HubPro is a BeagleBoard Black Computer and a Power Line Modem connected via a serial connection. 190 | 191 | 1. Follow the intructions here http://beagleboard.org/getting-started to create an microSD card with latest board software 192 | 2. Open the HubPro removing the 6 screws on the bottom. 193 | 3. Insert the SD Card. 194 | 4. While holding down the Boot Button "S2" connect the power. Don't electrocute yourself. Wait until the LED starts flashing. 195 | 5. You should be able to connect via SSH now (username is 'debian' and the password is 'temppwd'). 196 | 6. Change the password! 197 | 7. Enable the serial port by editing /boot/uEnv.txt 198 | 199 | Add: 200 | ``` 201 | cape_disable=bone_capemgr.disable_partno=BB-BONELT-HDMI,BB-BONELT-HDMIN 202 | cape_enable=bone_capemgr.enable_partno=BB-UART1,BB-UART2,BB-UART4,BB-UART5 203 | ``` 204 | 205 | Enable: 206 | ``` 207 | ###Overide capes with eeprom 208 | uboot_overlay_addr0=/lib/firmware/BB-UART1-00A0.dtbo 209 | uboot_overlay_addr1=/lib/firmware/BB-UART2-00A0.dtbo 210 | uboot_overlay_addr2=/lib/firmware/BB-UART4-00A0.dtbo 211 | uboot_overlay_addr3=/lib/firmware/BB-UART5-00A0.dtbo 212 | ``` 213 | 214 | 8. Reboot and log back in (same as step 4 & 5) 215 | 9. Install homebridge and this plug as usual seting the model in config.json to 2243 216 | 217 | Donate 218 | ----------------------------------- 219 | If you find this plugin useful you may make a donation using the button below. Donations are not expected, but appreciated! 220 | 221 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=AP4SUF96E39GU) 222 | 223 | [paypal-button]: https://img.shields.io/badge/Donate-PayPal-green.svg 224 | [paypal-kuestess]: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=AP4SUF96E39GU 225 | -------------------------------------------------------------------------------- /src/deviceDatabase.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "category": 0, 4 | "subcategory": 4, 5 | "sku": 2430, 6 | "name": "ControLinc" 7 | }, 8 | { 9 | "category": 0, 10 | "subcategory": 5, 11 | "sku": 2440, 12 | "name": "RemoteLinc" 13 | }, 14 | { 15 | "category": 0, 16 | "subcategory": 6, 17 | "sku": 2830, 18 | "name": "ICON Tabletop Controller" 19 | }, 20 | { 21 | "category": 0, 22 | "subcategory": 9, 23 | "sku": 2442, 24 | "name": "SignaLinc RF Signal Enhancer" 25 | }, 26 | { 27 | "category": 0, 28 | "subcategory": 11, 29 | "sku": 2443, 30 | "name": "Access Point (Wireless Phase Coupler)" 31 | }, 32 | { 33 | "category": 0, 34 | "subcategory": 12, 35 | "sku": 12005, 36 | "name": "IES Color Touchscreen" 37 | }, 38 | { 39 | "category": 0, 40 | "subcategory": 14, 41 | "sku": "2440EZ ", 42 | "name": "RemoteLinc EZ" 43 | }, 44 | { 45 | "category": 0, 46 | "subcategory": 16, 47 | "sku": "2444A2xx4 ", 48 | "name": "RemoteLinc 2 Keypad, 4 Scene" 49 | }, 50 | { 51 | "category": 0, 52 | "subcategory": 17, 53 | "sku": "2444A3xx ", 54 | "name": "RemoteLinc 2 Switch" 55 | }, 56 | { 57 | "category": 0, 58 | "subcategory": 18, 59 | "sku": "2444A2xx8 ", 60 | "name": "RemoteLinc 2 Keypad, 8 Scene" 61 | }, 62 | { 63 | "category": 0, 64 | "subcategory": 19, 65 | "sku": "2993-222 ", 66 | "name": "Insteon Diagnostics Keypad" 67 | }, 68 | { 69 | "category": 0, 70 | "subcategory": 20, 71 | "sku": "2342-432 ", 72 | "name": "Insteon Mini Remote - 4 Scene (869 MHz)" 73 | }, 74 | { 75 | "category": 0, 76 | "subcategory": 21, 77 | "sku": "2342-442 ", 78 | "name": "Insteon Mini Remote - Switch (869 MHz)" 79 | }, 80 | { 81 | "category": 0, 82 | "subcategory": 22, 83 | "sku": "2342-422 ", 84 | "name": "Insteon Mini Remote - 8 Scene (869 MHz)" 85 | }, 86 | { 87 | "category": 0, 88 | "subcategory": 23, 89 | "sku": "2342-532 ", 90 | "name": "Insteon Mini Remote - 4 Scene (921 MHz)" 91 | }, 92 | { 93 | "category": 0, 94 | "subcategory": 24, 95 | "sku": "2342-522 ", 96 | "name": "Insteon Mini Remote - 8 Scene (921 MHz)" 97 | }, 98 | { 99 | "category": 0, 100 | "subcategory": 25, 101 | "sku": "2342-542 ", 102 | "name": "Insteon Mini Remote - Switch (921 MHz)" 103 | }, 104 | { 105 | "category": 0, 106 | "subcategory": 26, 107 | "sku": "2342-222 ", 108 | "name": "Insteon Mini Remote - 8 Scene (915 MHz)" 109 | }, 110 | { 111 | "category": 0, 112 | "subcategory": 27, 113 | "sku": "2342-232 ", 114 | "name": "Insteon Mini Remote - 4 Scene (915 MHz)" 115 | }, 116 | { 117 | "category": 0, 118 | "subcategory": 28, 119 | "sku": "2342-242 ", 120 | "name": "Insteon Mini Remote - Switch (915 MHz)" 121 | }, 122 | { 123 | "category": 0, 124 | "subcategory": 29, 125 | "sku": "2992-222 ", 126 | "name": "Range Extender" 127 | }, 128 | { 129 | "category": 1, 130 | "subcategory": 0, 131 | "sku": "2456D3 ", 132 | "name": "LampLinc 3-Pin" 133 | }, 134 | { 135 | "category": 1, 136 | "subcategory": 1, 137 | "sku": "2476D ", 138 | "name": "SwitchLinc Dimmer" 139 | }, 140 | { 141 | "category": 1, 142 | "subcategory": 2, 143 | "sku": "2475D ", 144 | "name": "In-LineLinc Dimmer" 145 | }, 146 | { 147 | "category": 1, 148 | "subcategory": 3, 149 | "sku": "2876DB ", 150 | "name": "ICON Dimmer Switch" 151 | }, 152 | { 153 | "category": 1, 154 | "subcategory": 4, 155 | "sku": "2476DH ", 156 | "name": "SwitchLinc Dimmer (High Wattage)" 157 | }, 158 | { 159 | "category": 1, 160 | "subcategory": 5, 161 | "sku": "2484DWH8 ", 162 | "name": "Keypad Countdown Timer w/ Dimmer" 163 | }, 164 | { 165 | "category": 1, 166 | "subcategory": 6, 167 | "sku": "2456D2 ", 168 | "name": "LampLinc Dimmer (2-Pin)" 169 | }, 170 | { 171 | "category": 1, 172 | "subcategory": 7, 173 | "sku": "2856D2B ", 174 | "name": "ICON LampLinc" 175 | }, 176 | { 177 | "category": 1, 178 | "subcategory": 8, 179 | "sku": "2476DT ", 180 | "name": "SwitchLinc Dimmer Count-down Timer" 181 | }, 182 | { 183 | "category": 1, 184 | "subcategory": 9, 185 | "sku": "2486D ", 186 | "name": "KeypadLinc Dimmer" 187 | }, 188 | { 189 | "category": 1, 190 | "subcategory": 10, 191 | "sku": "2886D ", 192 | "name": "Icon In-Wall Controller" 193 | }, 194 | { 195 | "category": 1, 196 | "subcategory": 11, 197 | "sku": "2632-422 ", 198 | "name": "Insteon Dimmer Module, France (869 MHz)" 199 | }, 200 | { 201 | "category": 1, 202 | "subcategory": 12, 203 | "sku": "2486DWH8 ", 204 | "name": "KeypadLinc Dimmer" 205 | }, 206 | { 207 | "category": 1, 208 | "subcategory": 13, 209 | "sku": "2454D ", 210 | "name": "SocketLinc" 211 | }, 212 | { 213 | "category": 1, 214 | "subcategory": 14, 215 | "sku": "2457D2 ", 216 | "name": "LampLinc (Dual-Band)" 217 | }, 218 | { 219 | "category": 1, 220 | "subcategory": 15, 221 | "sku": "2632-432 ", 222 | "name": "Insteon Dimmer Module, Germany (869 MHz)" 223 | }, 224 | { 225 | "category": 1, 226 | "subcategory": 17, 227 | "sku": "2632-442 ", 228 | "name": "Insteon Dimmer Module, UK (869 MHz)" 229 | }, 230 | { 231 | "category": 1, 232 | "subcategory": 18, 233 | "sku": "2632-522 ", 234 | "name": "Insteon Dimmer Module, Aus/NZ (921 MHz)" 235 | }, 236 | { 237 | "category": 1, 238 | "subcategory": 19, 239 | "sku": "2676D-B ", 240 | "name": "ICON SwitchLinc Dimmer Lixar/Bell Canada" 241 | }, 242 | { 243 | "category": 1, 244 | "subcategory": 23, 245 | "sku": "2466D ", 246 | "name": "ToggleLinc Dimmer" 247 | }, 248 | { 249 | "category": 1, 250 | "subcategory": 24, 251 | "sku": "2474D ", 252 | "name": "Icon SwitchLinc Dimmer Inline Companion" 253 | }, 254 | { 255 | "category": 1, 256 | "subcategory": 25, 257 | "sku": "2476D ", 258 | "name": "SwitchLinc Dimmer [with beeper]" 259 | }, 260 | { 261 | "category": 1, 262 | "subcategory": 26, 263 | "sku": "2475D ", 264 | "name": "In-LineLinc Dimmer [with beeper]" 265 | }, 266 | { 267 | "category": 1, 268 | "subcategory": 27, 269 | "sku": "2486DWH6 ", 270 | "name": "KeypadLinc Dimmer" 271 | }, 272 | { 273 | "category": 1, 274 | "subcategory": 28, 275 | "sku": "2486DWH8 ", 276 | "name": "KeypadLinc Dimmer" 277 | }, 278 | { 279 | "category": 1, 280 | "subcategory": 29, 281 | "sku": "2476DH ", 282 | "name": "SwitchLinc Dimmer (High Wattage)[beeper]" 283 | }, 284 | { 285 | "category": 1, 286 | "subcategory": 30, 287 | "sku": "2876DB ", 288 | "name": "ICON Switch Dimmer" 289 | }, 290 | { 291 | "category": 1, 292 | "subcategory": 31, 293 | "sku": "2466Dx ", 294 | "name": "ToggleLinc Dimmer [with beeper]" 295 | }, 296 | { 297 | "category": 1, 298 | "subcategory": 32, 299 | "sku": "2477D ", 300 | "name": "SwitchLinc Dimmer (Dual-Band)" 301 | }, 302 | { 303 | "category": 1, 304 | "subcategory": 33, 305 | "sku": "2472D ", 306 | "name": "OutletLinc Dimmer (Dual-Band)" 307 | }, 308 | { 309 | "category": 1, 310 | "subcategory": 34, 311 | "sku": "2457D2X ", 312 | "name": "LampLinc" 313 | }, 314 | { 315 | "category": 1, 316 | "subcategory": 35, 317 | "sku": "2457D2EZ ", 318 | "name": "LampLinc Dual-Band EZ" 319 | }, 320 | { 321 | "category": 1, 322 | "subcategory": 36, 323 | "sku": "2474DWH ", 324 | "name": "SwitchLinc 2-Wire Dimmer (RF)" 325 | }, 326 | { 327 | "category": 1, 328 | "subcategory": 37, 329 | "sku": "2475DA2 ", 330 | "name": "In-LineLinc 0-10VDC Dimmer/Dual-SwitchDB" 331 | }, 332 | { 333 | "category": 1, 334 | "subcategory": 45, 335 | "sku": "2477DH ", 336 | "name": "SwitchLinc-Dimmer Dual-Band 1000W" 337 | }, 338 | { 339 | "category": 1, 340 | "subcategory": 46, 341 | "sku": "2475F ", 342 | "name": "FanLinc" 343 | }, 344 | { 345 | "category": 1, 346 | "subcategory": 47, 347 | "sku": "2484DST6 ", 348 | "name": "KeypadLinc Schedule Timer with Dimmer" 349 | }, 350 | { 351 | "category": 1, 352 | "subcategory": 48, 353 | "sku": "2476D ", 354 | "name": "SwitchLinc Dimmer" 355 | }, 356 | { 357 | "category": 1, 358 | "subcategory": 49, 359 | "sku": "2478D ", 360 | "name": "SwitchLinc Dimmer 240V-50/60Hz Dual-Band" 361 | }, 362 | { 363 | "category": 1, 364 | "subcategory": 50, 365 | "sku": "2475DA1 ", 366 | "name": "In-LineLinc Dimmer (Dual Band)" 367 | }, 368 | { 369 | "category": 1, 370 | "subcategory": 52, 371 | "sku": "2452-222 ", 372 | "name": "Insteon DIN Rail Dimmer (915 MHz)" 373 | }, 374 | { 375 | "category": 1, 376 | "subcategory": 53, 377 | "sku": "2442-222 ", 378 | "name": "Insteon Micro Dimmer (915 MHz)" 379 | }, 380 | { 381 | "category": 1, 382 | "subcategory": 54, 383 | "sku": "2452-422 ", 384 | "name": "Insteon DIN Rail Dimmer (869 MHz)" 385 | }, 386 | { 387 | "category": 1, 388 | "subcategory": 55, 389 | "sku": "2452-522 ", 390 | "name": "Insteon DIN Rail Dimmer (921 MHz)" 391 | }, 392 | { 393 | "category": 1, 394 | "subcategory": 56, 395 | "sku": "2442-422 ", 396 | "name": "Insteon Micro Dimmer (869 MHz)" 397 | }, 398 | { 399 | "category": 1, 400 | "subcategory": 57, 401 | "sku": "2442-522 ", 402 | "name": "Insteon Micro Dimmer (921 MHz)" 403 | }, 404 | { 405 | "category": 1, 406 | "subcategory": 58, 407 | "sku": "2672-222 ", 408 | "name": "LED Bulb 240V (915 MHz) - Screw-in Base" 409 | }, 410 | { 411 | "category": 1, 412 | "subcategory": 59, 413 | "sku": "2672-422 ", 414 | "name": "LED Bulb 240V Europe - Screw-in Base" 415 | }, 416 | { 417 | "category": 1, 418 | "subcategory": 60, 419 | "sku": "2672-522 ", 420 | "name": "LED Bulb 240V Aus/NZ - Screw-in Base" 421 | }, 422 | { 423 | "category": 1, 424 | "subcategory": 61, 425 | "sku": "2446-422 ", 426 | "name": "Insteon Ballast Dimmer (869 MHz)" 427 | }, 428 | { 429 | "category": 1, 430 | "subcategory": 62, 431 | "sku": "2446-522 ", 432 | "name": "Insteon Ballast Dimmer (921 MHz)" 433 | }, 434 | { 435 | "category": 1, 436 | "subcategory": 63, 437 | "sku": "2447-422 ", 438 | "name": "Insteon Fixture Dimmer (869 MHz)" 439 | }, 440 | { 441 | "category": 1, 442 | "subcategory": 64, 443 | "sku": "2447-522 ", 444 | "name": "Insteon Fixture Dimmer (921 MHz)" 445 | }, 446 | { 447 | "category": 1, 448 | "subcategory": 65, 449 | "sku": "2334-222 ", 450 | "name": "Keypad Dimmer Dual-Band, 8 Button" 451 | }, 452 | { 453 | "category": 1, 454 | "subcategory": 66, 455 | "sku": "2334-232 ", 456 | "name": "Keypad Dimmer Dual-Band, 6 Button" 457 | }, 458 | { 459 | "category": 1, 460 | "subcategory": 73, 461 | "sku": "2674-222 ", 462 | "name": "LED Bulb PAR38 US/Can - Screw-in Base" 463 | }, 464 | { 465 | "category": 1, 466 | "subcategory": 74, 467 | "sku": "2674-422 ", 468 | "name": "LED Bulb PAR38 Europe - Screw-in Base" 469 | }, 470 | { 471 | "category": 1, 472 | "subcategory": 75, 473 | "sku": "2674-522 ", 474 | "name": "LED Bulb PAR38 Aus/NZ - Screw-in Base" 475 | }, 476 | { 477 | "category": 1, 478 | "subcategory": 76, 479 | "sku": "2672-432 ", 480 | "name": "LED Bulb 240V Europe - Bayonet Base" 481 | }, 482 | { 483 | "category": 1, 484 | "subcategory": 77, 485 | "sku": "2672-532 ", 486 | "name": "LED Bulb 240V Aus/NZ - Bayonet Base" 487 | }, 488 | { 489 | "category": 1, 490 | "subcategory": 78, 491 | "sku": "2674-432 ", 492 | "name": "LED Bulb PAR38 Europe - Bayonet Base" 493 | }, 494 | { 495 | "category": 1, 496 | "subcategory": 79, 497 | "sku": "2674-532 ", 498 | "name": "LED Bulb PAR38 Aus/NZ - Bayonet Base" 499 | }, 500 | { 501 | "category": 1, 502 | "subcategory": 80, 503 | "sku": "2632-452 ", 504 | "name": "Insteon Dimmer Module, Chile (915 MHz)" 505 | }, 506 | { 507 | "category": 1, 508 | "subcategory": 81, 509 | "sku": "2672-452 ", 510 | "name": "LED Bulb 240V (915 MHz) - Screw-in Base" 511 | }, 512 | { 513 | "category": 1, 514 | "subcategory": 87, 515 | "sku": "PS01", 516 | "name": "i3 Paddle" 517 | }, 518 | { 519 | "category": 1, 520 | "subcategory": 88, 521 | "sku": "DS01", 522 | "name": "i3 Dial" 523 | }, 524 | { 525 | "category": 1, 526 | "subcategory": 89, 527 | "sku": "KP014 ", 528 | "name": "i3 Keypad" 529 | }, 530 | { 531 | "category": 2, 532 | "subcategory": 5, 533 | "sku": "2486SWH8 ", 534 | "name": "KeypadLinc 8-button On/Off Switch" 535 | }, 536 | { 537 | "category": 2, 538 | "subcategory": 6, 539 | "sku": "2456S3E ", 540 | "name": "Outdoor ApplianceLinc" 541 | }, 542 | { 543 | "category": 2, 544 | "subcategory": 7, 545 | "sku": "2456S3T ", 546 | "name": "TimerLinc" 547 | }, 548 | { 549 | "category": 2, 550 | "subcategory": 8, 551 | "sku": "2473S ", 552 | "name": "OutletLinc" 553 | }, 554 | { 555 | "category": 2, 556 | "subcategory": 9, 557 | "sku": "2456S3 ", 558 | "name": "ApplianceLinc (3-Pin)" 559 | }, 560 | { 561 | "category": 2, 562 | "subcategory": 10, 563 | "sku": "2476S ", 564 | "name": "SwitchLinc Relay" 565 | }, 566 | { 567 | "category": 2, 568 | "subcategory": 11, 569 | "sku": "2876S ", 570 | "name": "ICON On/Off Switch" 571 | }, 572 | { 573 | "category": 2, 574 | "subcategory": 12, 575 | "sku": "2856S3 ", 576 | "name": "Icon Appliance Module" 577 | }, 578 | { 579 | "category": 2, 580 | "subcategory": 13, 581 | "sku": "2466S ", 582 | "name": "ToggleLinc Relay" 583 | }, 584 | { 585 | "category": 2, 586 | "subcategory": 14, 587 | "sku": "2476ST ", 588 | "name": "SwitchLinc Relay Countdown Timer" 589 | }, 590 | { 591 | "category": 2, 592 | "subcategory": 15, 593 | "sku": "2486SWH6 ", 594 | "name": "KeypadLinc On/Off" 595 | }, 596 | { 597 | "category": 2, 598 | "subcategory": 16, 599 | "sku": "2475S ", 600 | "name": "In-LineLinc Relay" 601 | }, 602 | { 603 | "category": 2, 604 | "subcategory": 18, 605 | "sku": "2474 S/D ", 606 | "name": "ICON In-lineLinc Relay Companion" 607 | }, 608 | { 609 | "category": 2, 610 | "subcategory": 19, 611 | "sku": "2676R-B ", 612 | "name": "ICON SwitchLinc Relay Lixar/Bell Canada" 613 | }, 614 | { 615 | "category": 2, 616 | "subcategory": 20, 617 | "sku": "2475S2 ", 618 | "name": "In-LineLinc Relay with Sense" 619 | }, 620 | { 621 | "category": 2, 622 | "subcategory": 21, 623 | "sku": "2476SS ", 624 | "name": "SwitchLinc Relay with Sense" 625 | }, 626 | { 627 | "category": 2, 628 | "subcategory": 22, 629 | "sku": "2876S ", 630 | "name": "ICON On/Off Switch (25 max links)" 631 | }, 632 | { 633 | "category": 2, 634 | "subcategory": 23, 635 | "sku": "2856S3B ", 636 | "name": "ICON Appliance Module" 637 | }, 638 | { 639 | "category": 2, 640 | "subcategory": 24, 641 | "sku": "2494S220 ", 642 | "name": "SwitchLinc 220V Relay" 643 | }, 644 | { 645 | "category": 2, 646 | "subcategory": 25, 647 | "sku": "2494S220 ", 648 | "name": "SwitchLinc 220V Relay [with beeper]" 649 | }, 650 | { 651 | "category": 2, 652 | "subcategory": 26, 653 | "sku": "2466Sx ", 654 | "name": "ToggleLinc Relay [with Beeper]" 655 | }, 656 | { 657 | "category": 2, 658 | "subcategory": 28, 659 | "sku": "2476S ", 660 | "name": "SwitchLinc Relay" 661 | }, 662 | { 663 | "category": 2, 664 | "subcategory": 29, 665 | "sku": 4101, 666 | "name": "Commercial Switch with relay" 667 | }, 668 | { 669 | "category": 2, 670 | "subcategory": 30, 671 | "sku": "2487S ", 672 | "name": "KeypadLinc On/Off (Dual-Band)" 673 | }, 674 | { 675 | "category": 2, 676 | "subcategory": 31, 677 | "sku": "2475SDB ", 678 | "name": "In-LineLinc On/Off (Dual-Band)" 679 | }, 680 | { 681 | "category": 2, 682 | "subcategory": 37, 683 | "sku": "2484SWH8 ", 684 | "name": "KeypadLinc 8-Button Countdown On/Off Switch Timer" 685 | }, 686 | { 687 | "category": 2, 688 | "subcategory": 38, 689 | "sku": "2485SWH6 ", 690 | "name": "Keypad Schedule Timer with On/Off Switch" 691 | }, 692 | { 693 | "category": 2, 694 | "subcategory": 41, 695 | "sku": "2476ST ", 696 | "name": "SwitchLinc Relay Countdown Timer" 697 | }, 698 | { 699 | "category": 2, 700 | "subcategory": 42, 701 | "sku": "2477S ", 702 | "name": "SwitchLinc Relay (Dual-Band)" 703 | }, 704 | { 705 | "category": 2, 706 | "subcategory": 43, 707 | "sku": "2475SDB-50 ", 708 | "name": "In-LineLinc On/Off (Dual Band, 50/60 Hz)" 709 | }, 710 | { 711 | "category": 2, 712 | "subcategory": 44, 713 | "sku": "2487S ", 714 | "name": "KeypadLinc On/Off (Dual-Band,50/60 Hz)" 715 | }, 716 | { 717 | "category": 2, 718 | "subcategory": 45, 719 | "sku": "2633-422 ", 720 | "name": "Insteon On/Off Module, France (869 MHz)" 721 | }, 722 | { 723 | "category": 2, 724 | "subcategory": 46, 725 | "sku": "2453-222 ", 726 | "name": "Insteon DIN Rail On/Off (915 MHz)" 727 | }, 728 | { 729 | "category": 2, 730 | "subcategory": 47, 731 | "sku": "2443-222 ", 732 | "name": "Insteon Micro On/Off (915 MHz)" 733 | }, 734 | { 735 | "category": 2, 736 | "subcategory": 48, 737 | "sku": "2633-432 ", 738 | "name": "Insteon On/Off Module, Germany (869 MHz)" 739 | }, 740 | { 741 | "category": 2, 742 | "subcategory": 49, 743 | "sku": "2443-422 ", 744 | "name": "Insteon Micro On/Off (869 MHz)" 745 | }, 746 | { 747 | "category": 2, 748 | "subcategory": 50, 749 | "sku": "2443-522 ", 750 | "name": "Insteon Micro On/Off (921 MHz)" 751 | }, 752 | { 753 | "category": 2, 754 | "subcategory": 51, 755 | "sku": "2453-422 ", 756 | "name": "Insteon DIN Rail On/Off (869 MHz)" 757 | }, 758 | { 759 | "category": 2, 760 | "subcategory": 52, 761 | "sku": "2453-522 ", 762 | "name": "Insteon DIN Rail On/Off (921 MHz)" 763 | }, 764 | { 765 | "category": 2, 766 | "subcategory": 53, 767 | "sku": "2633-442 ", 768 | "name": "Insteon On/Off Module, UK (869 MHz)" 769 | }, 770 | { 771 | "category": 2, 772 | "subcategory": 54, 773 | "sku": "2633-522 ", 774 | "name": "Insteon On/Off Module, Aus/NZ (921 MHz)" 775 | }, 776 | { 777 | "category": 2, 778 | "subcategory": 55, 779 | "sku": "2635-222 ", 780 | "name": "Insteon On/Off Module, US (915 MHz)" 781 | }, 782 | { 783 | "category": 2, 784 | "subcategory": 56, 785 | "sku": "2634-222 ", 786 | "name": "On/Off Outdoor Module (Dual-Band)" 787 | }, 788 | { 789 | "category": 2, 790 | "subcategory": 57, 791 | "sku": "2663-222 ", 792 | "name": "On/Off Outlet" 793 | }, 794 | { 795 | "category": 2, 796 | "subcategory": 58, 797 | "sku": "2633-452 ", 798 | "name": "Insteon On/Off Module, Chile (915 MHz)" 799 | }, 800 | { 801 | "category": 2, 802 | "subcategory": 63, 803 | "sku": "WR01 ", 804 | "name": "i3 Outlet" 805 | }, 806 | { 807 | "category": 3, 808 | "subcategory": 1, 809 | "sku": "2414S ", 810 | "name": "PowerLinc Serial Controller" 811 | }, 812 | { 813 | "category": 3, 814 | "subcategory": 2, 815 | "sku": "2414U ", 816 | "name": "PowerLinc USB Controller" 817 | }, 818 | { 819 | "category": 3, 820 | "subcategory": 3, 821 | "sku": "2814S ", 822 | "name": "ICON PowerLinc Serial" 823 | }, 824 | { 825 | "category": 3, 826 | "subcategory": 4, 827 | "sku": "2814U ", 828 | "name": "ICON PowerLinc USB" 829 | }, 830 | { 831 | "category": 3, 832 | "subcategory": 5, 833 | "sku": "2412S ", 834 | "name": "PowerLinc Serial Modem" 835 | }, 836 | { 837 | "category": 3, 838 | "subcategory": 6, 839 | "sku": "2411R ", 840 | "name": "IRLinc Receiver" 841 | }, 842 | { 843 | "category": 3, 844 | "subcategory": 7, 845 | "sku": "2411T ", 846 | "name": "IRLinc Transmitter" 847 | }, 848 | { 849 | "category": 3, 850 | "subcategory": 9, 851 | "sku": "2600RF ", 852 | "name": "SmartLabs RF Developer’s Board" 853 | }, 854 | { 855 | "category": 3, 856 | "subcategory": 10, 857 | "sku": "2410S ", 858 | "name": "SeriaLinc - Insteon to RS232" 859 | }, 860 | { 861 | "category": 3, 862 | "subcategory": 11, 863 | "sku": "2412U ", 864 | "name": "PowerLinc USB Modem" 865 | }, 866 | { 867 | "category": 3, 868 | "subcategory": 15, 869 | "sku": "EZX10IR ", 870 | "name": "EZX10IR X10 IR Receiver" 871 | }, 872 | { 873 | "category": 3, 874 | "subcategory": 16, 875 | "sku": "2412N ", 876 | "name": "SmartLinc" 877 | }, 878 | { 879 | "category": 3, 880 | "subcategory": 17, 881 | "sku": "2413S ", 882 | "name": "PowerLinc Serial Modem (Dual Band)" 883 | }, 884 | { 885 | "category": 3, 886 | "subcategory": 19, 887 | "sku": "2412UH ", 888 | "name": "PowerLinc USB Modem for HouseLinc" 889 | }, 890 | { 891 | "category": 3, 892 | "subcategory": 20, 893 | "sku": "2412SH ", 894 | "name": "PowerLinc Serial Modem for HouseLinc" 895 | }, 896 | { 897 | "category": 3, 898 | "subcategory": 21, 899 | "sku": "2413U ", 900 | "name": "PowerLinc USB Modem (Dual Band)" 901 | }, 902 | { 903 | "category": 3, 904 | "subcategory": 24, 905 | "sku": "2243-222 ", 906 | "name": "Insteon Central Controller (915 MHz)" 907 | }, 908 | { 909 | "category": 3, 910 | "subcategory": 25, 911 | "sku": "2413SH ", 912 | "name": "PowerLinc Serial Modem for HL(Dual Band)" 913 | }, 914 | { 915 | "category": 3, 916 | "subcategory": 26, 917 | "sku": "2413UH ", 918 | "name": "PowerLinc USB Modem for HL (Dual Band)" 919 | }, 920 | { 921 | "category": 3, 922 | "subcategory": 27, 923 | "sku": "2423A4 ", 924 | "name": "iGateway" 925 | }, 926 | { 927 | "category": 3, 928 | "subcategory": 28, 929 | "sku": "2423A7 ", 930 | "name": "iGateway 2.0" 931 | }, 932 | { 933 | "category": 3, 934 | "subcategory": 30, 935 | "sku": "2412S ", 936 | "name": "PowerLincModemSerial w/o EEPROM(w/o RF)" 937 | }, 938 | { 939 | "category": 3, 940 | "subcategory": 31, 941 | "sku": "2448A7 ", 942 | "name": "USB Adapter - Domestically made" 943 | }, 944 | { 945 | "category": 3, 946 | "subcategory": 32, 947 | "sku": "2448A7 ", 948 | "name": "USB Adapter" 949 | }, 950 | { 951 | "category": 3, 952 | "subcategory": 33, 953 | "sku": "2448A7H ", 954 | "name": "Portable USB Adapter for HouseLinc" 955 | }, 956 | { 957 | "category": 3, 958 | "subcategory": 35, 959 | "sku": "2448A7H ", 960 | "name": "Portable USB Adapter for HouseLinc" 961 | }, 962 | { 963 | "category": 3, 964 | "subcategory": 36, 965 | "sku": "2448A7T ", 966 | "name": "TouchLinc" 967 | }, 968 | { 969 | "category": 3, 970 | "subcategory": 39, 971 | "sku": "2448A7T ", 972 | "name": "TouchLinc" 973 | }, 974 | { 975 | "category": 3, 976 | "subcategory": 40, 977 | "sku": "2413Gxx ", 978 | "name": "Global PLM, Dual Band (915 MHz)" 979 | }, 980 | { 981 | "category": 3, 982 | "subcategory": 41, 983 | "sku": "2413SAD ", 984 | "name": "PowerLinc Serial Modem (Dual Band) RF OFF, Auto Detect 128K" 985 | }, 986 | { 987 | "category": 3, 988 | "subcategory": 43, 989 | "sku": "2242-222 ", 990 | "name": "Insteon Hub (915 MHz) - no RF" 991 | }, 992 | { 993 | "category": 3, 994 | "subcategory": 46, 995 | "sku": "2242-422 ", 996 | "name": "Insteon Hub (EU - 869 MHz)" 997 | }, 998 | { 999 | "category": 3, 1000 | "subcategory": 47, 1001 | "sku": "2242-522 ", 1002 | "name": "Insteon Hub (921 MHz)" 1003 | }, 1004 | { 1005 | "category": 3, 1006 | "subcategory": 48, 1007 | "sku": "2242-442 ", 1008 | "name": "Insteon Hub (UK - 869 MHz)" 1009 | }, 1010 | { 1011 | "category": 3, 1012 | "subcategory": 49, 1013 | "sku": "2242-232 ", 1014 | "name": "Insteon Hub (Plug-In Version)" 1015 | }, 1016 | { 1017 | "category": 3, 1018 | "subcategory": 51, 1019 | "sku": "2245-222 ", 1020 | "name": "Insteon Hub II (915 MHz)" 1021 | }, 1022 | { 1023 | "category": 3, 1024 | "subcategory": 55, 1025 | "sku": "2242-222 ", 1026 | "name": "Insteon Hub (915 MHz) - RF" 1027 | }, 1028 | { 1029 | "category": 4, 1030 | "subcategory": 0, 1031 | "sku": 31270, 1032 | "name": "Compacta EZRain Sprinkler Controller" 1033 | }, 1034 | { 1035 | "category": 5, 1036 | "subcategory": 0, 1037 | "sku": "2670IAQ-80 ", 1038 | "name": "Broan SMSC080 Exhaust Fan (no beeper)" 1039 | }, 1040 | { 1041 | "category": 5, 1042 | "subcategory": 2, 1043 | "sku": "2670IAQ-110 ", 1044 | "name": "Broan SMSC110 Exhaust Fan (no beeper)" 1045 | }, 1046 | { 1047 | "category": 5, 1048 | "subcategory": 3, 1049 | "sku": "2441V ", 1050 | "name": "Thermostat Adapter" 1051 | }, 1052 | { 1053 | "category": 5, 1054 | "subcategory": 7, 1055 | "sku": "2441ZT ", 1056 | "name": "Insteon Wireless Thermostat" 1057 | }, 1058 | { 1059 | "category": 5, 1060 | "subcategory": 10, 1061 | "sku": "2441ZTH ", 1062 | "name": "Insteon Wireless Thermostat (915 MHz)" 1063 | }, 1064 | { 1065 | "category": 5, 1066 | "subcategory": 11, 1067 | "sku": "2441TH ", 1068 | "name": "Insteon Thermostat (915 MHz)" 1069 | }, 1070 | { 1071 | "category": 5, 1072 | "subcategory": 12, 1073 | "sku": "2670IAQ-80 ", 1074 | "name": "Broan SMSC080 Switch for 80CFM Fans" 1075 | }, 1076 | { 1077 | "category": 5, 1078 | "subcategory": 13, 1079 | "sku": "2670IAQ-110 ", 1080 | "name": "Broan SMSC110 Switch for 110CFM Fans" 1081 | }, 1082 | { 1083 | "category": 5, 1084 | "subcategory": 14, 1085 | "sku": "2491TxE ", 1086 | "name": "Integrated Remote Control Thermostat" 1087 | }, 1088 | { 1089 | "category": 5, 1090 | "subcategory": 15, 1091 | "sku": "2732-422 ", 1092 | "name": "Insteon Thermostat (869 MHz)" 1093 | }, 1094 | { 1095 | "category": 5, 1096 | "subcategory": 16, 1097 | "sku": "2732-522 ", 1098 | "name": "Insteon Thermostat (921 MHz)" 1099 | }, 1100 | { 1101 | "category": 5, 1102 | "subcategory": 17, 1103 | "sku": "2732-432 ", 1104 | "name": "Insteon Zone Thermostat (869 MHz)" 1105 | }, 1106 | { 1107 | "category": 5, 1108 | "subcategory": 18, 1109 | "sku": "2732-532 ", 1110 | "name": "Insteon Zone Thermostat (921 MHz)" 1111 | }, 1112 | { 1113 | "category": 5, 1114 | "subcategory": 19, 1115 | "sku": "2732-242", 1116 | "name": "Heat Pump Thermostat - US/Can (915MHz" 1117 | }, 1118 | { 1119 | "category": 5, 1120 | "subcategory": 20, 1121 | "sku": "2732-242", 1122 | "name": "Heat Pump Thermostat - Europe (869.85MHz" 1123 | }, 1124 | { 1125 | "category": 5, 1126 | "subcategory": 21, 1127 | "sku": "2732-242", 1128 | "name": "Heat Pump Thermostat - Aus/NZ (921MHz" 1129 | }, 1130 | { 1131 | "category": 7, 1132 | "subcategory": 0, 1133 | "sku": 2450, 1134 | "name": "I/OLinc" 1135 | }, 1136 | { 1137 | "category": 7, 1138 | "subcategory": 3, 1139 | "sku": 31274, 1140 | "name": "Compacta EZIO2X4 #5010D" 1141 | }, 1142 | { 1143 | "category": 7, 1144 | "subcategory": 5, 1145 | "sku": 31275, 1146 | "name": "Compacta EZSnsRF RcvrIntrfc Dakota Alert" 1147 | }, 1148 | { 1149 | "category": 7, 1150 | "subcategory": 7, 1151 | "sku": 31280, 1152 | "name": "EZIO6I (6 inputs)" 1153 | }, 1154 | { 1155 | "category": 7, 1156 | "subcategory": 8, 1157 | "sku": 31283, 1158 | "name": "EZIO4O (4 relay outputs)" 1159 | }, 1160 | { 1161 | "category": 7, 1162 | "subcategory": 9, 1163 | "sku": "2423A5 ", 1164 | "name": "SynchroLinc" 1165 | }, 1166 | { 1167 | "category": 7, 1168 | "subcategory": 12, 1169 | "sku": "2448A5 ", 1170 | "name": "Lumistat" 1171 | }, 1172 | { 1173 | "category": 7, 1174 | "subcategory": 13, 1175 | "sku": 2450, 1176 | "name": "I/OLinc 50/60Hz Auto Detect" 1177 | }, 1178 | { 1179 | "category": 7, 1180 | "subcategory": 14, 1181 | "sku": "2248-222 ", 1182 | "name": "I/O Module - US (915 MHz)" 1183 | }, 1184 | { 1185 | "category": 7, 1186 | "subcategory": 15, 1187 | "sku": "2248-422 ", 1188 | "name": "I/O Module - EU (869.85 MHz)" 1189 | }, 1190 | { 1191 | "category": 7, 1192 | "subcategory": 16, 1193 | "sku": "2248-442 ", 1194 | "name": "I/O Module - UK (869.85 MHz)" 1195 | }, 1196 | { 1197 | "category": 7, 1198 | "subcategory": 17, 1199 | "sku": "2248-522 ", 1200 | "name": "I/O Module - AUS (921 MHz)" 1201 | }, 1202 | { 1203 | "category": 7, 1204 | "subcategory": 18, 1205 | "sku": "2822-222 ", 1206 | "name": "IOLinc Dual-Band - US" 1207 | }, 1208 | { 1209 | "category": 7, 1210 | "subcategory": 19, 1211 | "sku": "2822-422 ", 1212 | "name": "IOLinc Dual-Band - EU" 1213 | }, 1214 | { 1215 | "category": 7, 1216 | "subcategory": 20, 1217 | "sku": "2822-442 ", 1218 | "name": "IOLinc Dual-Band - UK" 1219 | }, 1220 | { 1221 | "category": 7, 1222 | "subcategory": 21, 1223 | "sku": "2822-522 ", 1224 | "name": "IOLinc Dual-Band - AUS/NZ" 1225 | }, 1226 | { 1227 | "category": 7, 1228 | "subcategory": 22, 1229 | "sku": "2822-222 ", 1230 | "name": "Low Voltage/Contact Closure Interface (Dual Band) - US" 1231 | }, 1232 | { 1233 | "category": 7, 1234 | "subcategory": 23, 1235 | "sku": "2822-422 ", 1236 | "name": "Low Voltage/Contact Closure Interface (Dual Band) - EU" 1237 | }, 1238 | { 1239 | "category": 7, 1240 | "subcategory": 24, 1241 | "sku": "2822-442 ", 1242 | "name": "Low Voltage/Contact Closure Interface (Dual Band) - UK" 1243 | }, 1244 | { 1245 | "category": 7, 1246 | "subcategory": 25, 1247 | "sku": "2822-522 ", 1248 | "name": "Low Voltage/Contact Closure Interface (Dual Band) - AUS/NZ" 1249 | }, 1250 | { 1251 | "category": 9, 1252 | "subcategory": 7, 1253 | "sku": "2423A1 ", 1254 | "name": "iMeter Solo" 1255 | }, 1256 | { 1257 | "category": 9, 1258 | "subcategory": 8, 1259 | "sku": "2423A2 ", 1260 | "name": "iMeter Home (Breaker Panel)" 1261 | }, 1262 | { 1263 | "category": 9, 1264 | "subcategory": 9, 1265 | "sku": "2423A3 ", 1266 | "name": "iMeter Home (Meter)" 1267 | }, 1268 | { 1269 | "category": 9, 1270 | "subcategory": 10, 1271 | "sku": "2477SA1 ", 1272 | "name": "220/240V 30A Load Controller NO (DB)" 1273 | }, 1274 | { 1275 | "category": 9, 1276 | "subcategory": 11, 1277 | "sku": "2477SA2 ", 1278 | "name": "220/240V 30A Load Controller NC (DB)" 1279 | }, 1280 | { 1281 | "category": 9, 1282 | "subcategory": 12, 1283 | "sku": "2630A1 ", 1284 | "name": "GE Water Heater U-SNAP module" 1285 | }, 1286 | { 1287 | "category": 9, 1288 | "subcategory": 13, 1289 | "sku": "2448A2 ", 1290 | "name": "Energy Display" 1291 | }, 1292 | { 1293 | "category": 9, 1294 | "subcategory": 14, 1295 | "sku": "2423A6 ", 1296 | "name": "Power Strip with iMeter and SynchroLinc" 1297 | }, 1298 | { 1299 | "category": 9, 1300 | "subcategory": 17, 1301 | "sku": "2423A8 ", 1302 | "name": "Insteon Digital Meter Reader" 1303 | }, 1304 | { 1305 | "category": 14, 1306 | "subcategory": 0, 1307 | "sku": "318276I ", 1308 | "name": "Somfy Drape Controller RF Bridge" 1309 | }, 1310 | { 1311 | "category": 14, 1312 | "subcategory": 1, 1313 | "sku": "2444-222 ", 1314 | "name": "Insteon Micro Open/Close (915 MHz)" 1315 | }, 1316 | { 1317 | "category": 14, 1318 | "subcategory": 2, 1319 | "sku": "2444-422 ", 1320 | "name": "Insteon Micro Open/Close (869 MHz)" 1321 | }, 1322 | { 1323 | "category": 14, 1324 | "subcategory": 3, 1325 | "sku": "2444-522 ", 1326 | "name": "Insteon Micro Open/Close (921 MHz)" 1327 | }, 1328 | { 1329 | "category": 14, 1330 | "subcategory": 4, 1331 | "sku": "2772-222 ", 1332 | "name": "Window Shade Kit - US" 1333 | }, 1334 | { 1335 | "category": 14, 1336 | "subcategory": 5, 1337 | "sku": "2772-422 ", 1338 | "name": "Window Shade Kit - EU" 1339 | }, 1340 | { 1341 | "category": 14, 1342 | "subcategory": 6, 1343 | "sku": "2772-522 ", 1344 | "name": "Window Shade Kit - AUS/NZ" 1345 | }, 1346 | { 1347 | "category": 15, 1348 | "subcategory": 6, 1349 | "sku": "2458A1 ", 1350 | "name": "MorningLinc" 1351 | }, 1352 | { 1353 | "category": 16, 1354 | "subcategory": 1, 1355 | "sku": "2842-222 ", 1356 | "name": "Motion Sensor - US (915 MHz)" 1357 | }, 1358 | { 1359 | "category": 16, 1360 | "subcategory": 2, 1361 | "sku": "2843-222 ", 1362 | "name": "Insteon Open/Close Sensor (915 MHz)" 1363 | }, 1364 | { 1365 | "category": 16, 1366 | "subcategory": 4, 1367 | "sku": "2842-422 ", 1368 | "name": "Insteon Motion Sensor (869 MHz)" 1369 | }, 1370 | { 1371 | "category": 16, 1372 | "subcategory": 5, 1373 | "sku": "2842-522 ", 1374 | "name": "Insteon Motion Sensor (921 MHz)" 1375 | }, 1376 | { 1377 | "category": 16, 1378 | "subcategory": 6, 1379 | "sku": "2843-422 ", 1380 | "name": "Insteon Open/Close Sensor (869 MHz)" 1381 | }, 1382 | { 1383 | "category": 16, 1384 | "subcategory": 7, 1385 | "sku": "2843-522 ", 1386 | "name": "Insteon Open/Close Sensor (921 MHz)" 1387 | }, 1388 | { 1389 | "category": 16, 1390 | "subcategory": 8, 1391 | "sku": "2852-222 ", 1392 | "name": "Leak Sensor - US (915 MHz)" 1393 | }, 1394 | { 1395 | "category": 16, 1396 | "subcategory": 9, 1397 | "sku": "2843-232 ", 1398 | "name": "Insteon Door Senso" 1399 | }, 1400 | { 1401 | "category": 16, 1402 | "subcategory": 10, 1403 | "sku": "2982-222 ", 1404 | "name": "Smoke Bridge" 1405 | }, 1406 | { 1407 | "category": 16, 1408 | "subcategory": 13, 1409 | "sku": "2852-422 ", 1410 | "name": "Leak Sensor - EU (869 MHz)" 1411 | }, 1412 | { 1413 | "category": 16, 1414 | "subcategory": 14, 1415 | "sku": "2852-522 ", 1416 | "name": "Leak Sensor - AUS/NZ (921 MHz)" 1417 | }, 1418 | { 1419 | "category": 16, 1420 | "subcategory": 17, 1421 | "sku": "2845-222 ", 1422 | "name": "Door Sensor II (915 MHz)" 1423 | }, 1424 | { 1425 | "category": 16, 1426 | "subcategory": 20, 1427 | "sku": "2845-422 ", 1428 | "name": "Door Sensor II (869 MHz)" 1429 | }, 1430 | { 1431 | "category": 16, 1432 | "subcategory": 21, 1433 | "sku": "2845-522", 1434 | "name": "Door Sensor II (921 MHz" 1435 | }, 1436 | { 1437 | "category": 16, 1438 | "subcategory": 22, 1439 | "sku": "2844-222", 1440 | "name": "Motion Sensor  - US (915 MHz)" 1441 | } 1442 | ] -------------------------------------------------------------------------------- /src/InsteonLocalPlatform.ts: -------------------------------------------------------------------------------- 1 | import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service, Characteristic, ColorUtils } from 'homebridge'; 2 | 3 | import { PLATFORM_NAME, PLUGIN_NAME } from './settings'; 4 | import { InsteonLocalAccessory } from './InsteonLocalAccessory'; 5 | 6 | import { InsteonUI } from './insteon-ui'; 7 | 8 | import hc from 'home-controller'; 9 | import express from 'express'; 10 | const app = express(); 11 | import _ from 'underscore'; 12 | import moment, { Moment } from 'moment'; 13 | import events from 'events'; 14 | import util from 'util'; 15 | 16 | const Insteon = hc.Insteon; 17 | const hub = new Insteon(); 18 | 19 | let connectedToHub = false; 20 | let connectingToHub = false; 21 | let inUse = true; 22 | let express_init = false; 23 | let eventListener_init = false; 24 | export class InsteonLocalPlatform implements DynamicPlatformPlugin { 25 | public readonly Service: typeof Service = this.api.hap.Service; 26 | public readonly Characteristic: typeof Characteristic = this.api.hap.Characteristic; 27 | 28 | // this is used to track restored cached accessories 29 | public accessories: PlatformAccessory[] = []; 30 | 31 | public hub: any; 32 | private host: string; 33 | private port: string; 34 | private user: string; 35 | private pass: string; 36 | private model: string; 37 | public devices: Array; 38 | public deviceIDs: Array; 39 | private server_port: string; 40 | private use_express: boolean; 41 | private keepAlive: number; 42 | private checkInterval: number; 43 | public refreshInterval: number; 44 | private hubConfig: {host: string; port: string; user: string; password: string}; 45 | hubID: any; 46 | insteonAccessories: Array; 47 | platform: any; 48 | configPath: string; 49 | app: any; 50 | ui: InsteonUI; 51 | 52 | constructor( 53 | public readonly log: Logger, 54 | public readonly config: PlatformConfig, 55 | public readonly api: API, 56 | ) { 57 | 58 | this.hub = hub; 59 | this.host = config['host']; 60 | this.host = config['host']; 61 | this.port = config['port']; 62 | this.user = config['user']; 63 | this.pass = config['pass']; 64 | this.model = config['model']; 65 | this.devices = config['devices'] || []; 66 | this.server_port = config['server_port'] || 3000; 67 | this.use_express = config['use_express'] || false; 68 | this.keepAlive = config['keepAlive'] || 3600; 69 | this.checkInterval = config['checkInterval'] || 20; 70 | this.deviceIDs = []; 71 | this.platform = this; 72 | this.insteonAccessories = []; 73 | this.configPath = api.user.configPath(); 74 | this.app = app; 75 | 76 | events.EventEmitter.defaultMaxListeners = Math.max(10, this.devices.length); 77 | 78 | if (this.model == '2242') { 79 | this.refreshInterval = config['refresh'] || 300; 80 | } else { 81 | this.refreshInterval = config['refresh'] || 0; 82 | } 83 | 84 | this.hubConfig = { 85 | host: this.host, 86 | port: this.port, 87 | user: this.user, 88 | password: this.pass, 89 | }; 90 | 91 | this.connectToHub(); 92 | 93 | if (this.keepAlive > 0){ 94 | this.connectionWatcher(); 95 | } 96 | 97 | if(this.use_express && express_init == false){ 98 | this.initAPI(); 99 | express_init = true; 100 | this.log.info('Started Insteon Express server...'); 101 | } 102 | 103 | //InsteonUI 104 | const ui = new InsteonUI(this.configPath, hub); 105 | app.use('/', ui.handleRequest.bind(ui)); 106 | app.listen(this.server_port); 107 | 108 | this.log.debug('Finished initializing platform:', this.config.name); 109 | 110 | // When this event is fired it means Homebridge has restored all cached accessories from disk. 111 | // Dynamic Platform plugins should only register new accessories after this event was fired, 112 | // in order to ensure they weren't added to homebridge already. This event can also be used 113 | // to start discovery of new accessories. 114 | this.api.on('didFinishLaunching', () => { 115 | log.debug('Executed didFinishLaunching callback'); 116 | this.discoverDevices(); 117 | }); 118 | } 119 | 120 | connectionWatcher() { //resets connection to hub every keepAlive mS 121 | this.log.info('Started connection watcher...'); 122 | 123 | if (this.model == '2245') { 124 | if(this.keepAlive > 0){ 125 | setInterval(()=> { 126 | this.log.info('Closing connection to Hub...'); 127 | hub.close(); 128 | connectedToHub = false; 129 | 130 | this.log.debug('Connected: ' + connectedToHub + ', Connecting: ' + connectingToHub); 131 | 132 | setTimeout(() => { //wait 5 sec to reconnect to Hub 133 | this.log.info('Reconnecting to Hub...'); 134 | this.connectToHub(); 135 | }, 5000); 136 | 137 | }, 1000*this.keepAlive); 138 | } 139 | } 140 | 141 | if (this.model == '2242') { //check every 10 sec to see if a request is in progress 142 | setInterval(()=> { 143 | if (typeof hub.status === 'undefined' && connectedToHub == true) { //undefined if no request in progress 144 | inUse = false; 145 | this.log.info('Closing connection to Hub...'); 146 | hub.close(); 147 | connectedToHub = false; 148 | } else { 149 | inUse = true; 150 | } 151 | }, 1000*this.checkInterval); 152 | } 153 | } 154 | 155 | getHubInfo() { 156 | hub.info((error, info) => { 157 | if (error) { 158 | this.log.warn('Error getting Hub info'); 159 | } else { 160 | this.hubID = info.id.toUpperCase(); 161 | this.log.debug('Hub/PLM id is ' + this.hubID); 162 | } 163 | }); 164 | } 165 | 166 | connectToHub (){ 167 | if (this.model == '2245') { 168 | this.log.debug('Connecting to Insteon Model 2245 Hub...'); 169 | connectingToHub = true; 170 | hub.httpClient(this.hubConfig, (had_error) => { 171 | this.log.info('Connected to Insteon Model 2245 Hub...'); 172 | hub.emit('connect'); 173 | connectedToHub = true; 174 | connectingToHub = false; 175 | if(eventListener_init == false) { 176 | this.eventListener(); 177 | } 178 | 179 | // this.getHubInfo(); 180 | }); 181 | } else if (this.model == '2243') { 182 | this.log.debug('Connecting to Insteon "Hub Pro" Hub...'); 183 | connectingToHub = true; 184 | hub.serial('/dev/ttyS4', {baudRate:19200}, (had_error) => { 185 | this.log.info('Connected to Insteon "Hub Pro" Hub...'); 186 | connectedToHub = true; 187 | connectingToHub = false; 188 | if(eventListener_init == false) { 189 | this.eventListener(); 190 | } 191 | 192 | // this.getHubInfo(); 193 | }); 194 | } else if (this.model == '2242') { 195 | this.log.debug('Connecting to Insteon Model 2242 Hub...'); 196 | connectingToHub = true; 197 | hub.connect(this.host, () => { 198 | this.log.info('Connected to Insteon Model 2242 Hub...'); 199 | hub.emit('connect'); 200 | connectedToHub = true; 201 | connectingToHub = false; 202 | 203 | if(this.keepAlive == 0 && eventListener_init == false) { 204 | this.eventListener(); 205 | } 206 | 207 | // this.getHubInfo(); 208 | }); 209 | } else { 210 | this.log.debug('Connecting to Insteon PLM...'); 211 | connectingToHub = true; 212 | hub.serial(this.host, {baudRate:19200}, (had_error) => { 213 | this.log.info('Connected to Insteon PLM...'); 214 | connectedToHub = true; 215 | connectingToHub = false; 216 | if(eventListener_init == false) { 217 | this.eventListener(); 218 | } 219 | 220 | // this.getHubInfo(); 221 | }); 222 | } 223 | } 224 | 225 | checkHubConnection () { 226 | if(connectingToHub == false) { 227 | if(connectedToHub == false) { 228 | this.log.info('Reconnecting to Hub...'); 229 | this.connectToHub(); 230 | } 231 | } 232 | return; 233 | } 234 | 235 | /** 236 | * This function is invoked when homebridge restores cached accessories from disk at startup. 237 | * It should be used to setup event handlers for characteristics and update respective values. 238 | */ 239 | configureAccessory(accessory: PlatformAccessory) { 240 | this.log.info('Loading accessory from cache:', accessory.displayName); 241 | 242 | // add the restored accessory to the accessories cache so we can track if it has already been registered 243 | this.accessories.push(accessory); 244 | } 245 | 246 | eventListener() { 247 | const eight_buttonArray = {'A': 1, 'B': 2, 'C': 3, 'D': 4, 'E': 5, 'F': 6, 'G': 7, 'H': 8}; 248 | const six_buttonArray = {'ON': 1, 'A': 3, 'B': 4, 'C': 5, 'D': 6, 'OFF': 1}; 249 | const four_buttonArray = { 250 | '1': eight_buttonArray['A'], 251 | '2': eight_buttonArray['B'], 252 | '3': eight_buttonArray['C'], 253 | '4': eight_buttonArray['D'], 254 | }; 255 | 256 | let buttonArray; 257 | 258 | const deviceIDs = this.deviceIDs; 259 | eventListener_init = true; 260 | 261 | this.log.info('Insteon event listener started...'); 262 | 263 | hub.on('command', (data) => { 264 | if (typeof data.standard !== 'undefined') { 265 | this.log.debug('Received command for ' + data.standard.id); 266 | 267 | const info = JSON.stringify(data); 268 | const id = data.standard.id.toUpperCase(); 269 | const command1 = data.standard.command1; 270 | const command2 = data.standard.command2; 271 | const messageType = data.standard.messageType; 272 | const gateway = data.standard.gatewayId.toUpperCase(); 273 | 274 | const isDevice = _.contains(deviceIDs, id, 0); 275 | 276 | if (isDevice) { 277 | let foundDevices: Array; 278 | foundDevices = this.insteonAccessories.filter((item) => { 279 | return item.id == id; 280 | }); 281 | 282 | if (gateway != this.hubID) { 283 | if(parseInt(gateway) != 1){ 284 | this.log.debug('Message is from keypad, filtering non-keypad devices.'); 285 | foundDevices = foundDevices.filter((item) => { 286 | return item.deviceType == 'keypad'; 287 | }); 288 | } 289 | } 290 | 291 | this.log.debug('Found ' + foundDevices.length + ' accessories matching ' + id); 292 | this.log.debug('Hub command: ' + info); 293 | 294 | let isFan = false; 295 | 296 | if (foundDevices.some((item)=> { 297 | return item['deviceType'] === 'fan'; 298 | })) { 299 | isFan = true; 300 | } else { 301 | isFan = false; 302 | } 303 | 304 | for (let i = 0, len = foundDevices.length; i < len; i++) { 305 | const foundDevice = foundDevices[i]; 306 | this.log.debug('Got event for ' + foundDevice.name + ' (' + foundDevice.id + ')'); 307 | 308 | switch (foundDevice.deviceType) { 309 | case 'lightbulb': 310 | case 'dimmer': 311 | case 'switch': 312 | if(isFan){ 313 | foundDevice.getStatus.call(foundDevice); 314 | foundDevice.lastUpdate = moment(); 315 | break; 316 | } 317 | 318 | if (command1 == '19' || command1 == '03' || command1 == '04' || /*(command1 == '00' && command2 != '00')||*/ (command1 == '06' && messageType == '1')) { //19 = status 319 | const level_int = parseInt(command2, 16) * (100 / 255); 320 | const level = Math.ceil(level_int); 321 | 322 | this.log.info('Got updated status for ' + foundDevice.name); 323 | 324 | if (foundDevice.dimmable) { 325 | foundDevice.service.getCharacteristic(this.platform.Characteristic.Brightness).updateValue(level); 326 | foundDevice.level = level; 327 | } 328 | 329 | if(level > 0){ 330 | foundDevice.currentState = true; 331 | } else { 332 | foundDevice.currentState = false; 333 | } 334 | 335 | foundDevice.service.getCharacteristic(this.platform.Characteristic.On).updateValue(foundDevice.currentState); 336 | foundDevice.lastUpdate = moment(); 337 | } 338 | 339 | if (command1 == 11 && messageType == '1') { //11 = on 340 | const level_int = parseInt(command2, 16)*(100/255); 341 | const level = Math.ceil(level_int); 342 | 343 | if (level == 0){ 344 | this.log.info('Got off event for ' + foundDevice.name); 345 | foundDevice.service.getCharacteristic(this.platform.Characteristic.On).updateValue(false); 346 | foundDevice.currentState = false; 347 | } else if (level > 0) { 348 | this.log.info('Got on event for ' + foundDevice.name); 349 | foundDevice.service.getCharacteristic(this.platform.Characteristic.On).updateValue(true); 350 | foundDevice.currentState = true; 351 | } 352 | 353 | if(foundDevice.dimmable){ 354 | if(messageType == 1){ 355 | foundDevice.service.getCharacteristic(this.platform.Characteristic.Brightness).updateValue(level); 356 | foundDevice.level = level; 357 | } else { 358 | setTimeout(()=> { 359 | foundDevice.getStatus.call(foundDevice); 360 | }, 5000); 361 | } 362 | } 363 | 364 | foundDevice.lastUpdate = moment(); 365 | foundDevice.getGroupMemberStatus.call(foundDevice); 366 | } 367 | 368 | if (command1 == 12) { //fast on 369 | 370 | this.log.info('Got fast on event for ' + foundDevice.name); 371 | 372 | if(foundDevice.dimmable){ 373 | foundDevice.service.getCharacteristic(this.platform.Characteristic.Brightness).updateValue(100); 374 | foundDevice.level = 100; 375 | } 376 | 377 | foundDevice.service.getCharacteristic(this.platform.Characteristic.On).updateValue(true); 378 | foundDevice.currentState = true; 379 | foundDevice.lastUpdate = moment(); 380 | foundDevice.getGroupMemberStatus.call(foundDevice); 381 | } 382 | 383 | if (command1 == 13 || command1 == 14) { //13 = off, 14= fast off 384 | if (command1 == 13) { 385 | this.log.info('Got off event for ' + foundDevice.name); 386 | } else { 387 | this.log.info('Got fast off event for ' + foundDevice.name); 388 | } 389 | 390 | if(foundDevice.dimmable){ 391 | foundDevice.service.getCharacteristic(this.platform.Characteristic.Brightness).updateValue(0); 392 | foundDevice.level = 0; 393 | } 394 | foundDevice.service.getCharacteristic(this.platform.Characteristic.On).updateValue(false); 395 | foundDevice.currentState = false; 396 | foundDevice.lastUpdate = moment(); 397 | if (messageType == '1') { 398 | foundDevice.getGroupMemberStatus.call(foundDevice); 399 | } 400 | } 401 | 402 | if (command1 == 18) { //18 = stop changing 403 | this.log.info('Got stop dimming event for ' + foundDevice.name); 404 | foundDevice.getStatus.call(foundDevice); 405 | } 406 | 407 | if (messageType == '6') { //group broadcast - manual button press 408 | let commandedState; 409 | let group; 410 | 411 | if(command1 == '06'){ 412 | commandedState = gateway.substring(0, 2); 413 | group = parseInt(gateway.substring(4, 6)); //button number 414 | } else { 415 | commandedState = command1; 416 | group = parseInt(gateway.substring(4, 6)); //button number 417 | } 418 | 419 | if(commandedState == 11) { 420 | this.log.info('Got on event for ' + foundDevice.name); 421 | foundDevice.currentState = true; 422 | foundDevice.lastUpdate = moment(); 423 | foundDevice.service.getCharacteristic(this.platform.Characteristic.On).updateValue(true); 424 | foundDevice.service.getCharacteristic(this.platform.Characteristic.Brightness).updateValue(100); 425 | foundDevice.getGroupMemberStatus.call(foundDevice); 426 | } 427 | 428 | if(commandedState == 13) { 429 | this.log.info('Got off event for ' + foundDevice.name); 430 | foundDevice.currentState = false; 431 | foundDevice.lastUpdate = moment(); 432 | foundDevice.service.getCharacteristic(this.platform.Characteristic.On).updateValue(false); 433 | foundDevice.service.getCharacteristic(this.platform.Characteristic.Brightness).updateValue(0); 434 | foundDevice.getGroupMemberStatus.call(foundDevice); 435 | } 436 | } 437 | 438 | break; 439 | 440 | case 'scene': 441 | case 'keypad': 442 | if (messageType == '3') { //cleanup of group broadcast from hub 443 | const group = parseInt(command2, 16); 444 | const foundGroupID = parseInt(foundDevice.groupID); 445 | 446 | //we're looping through devices with the same deviceID - if not the correct group, move on to the next 447 | if (foundGroupID !== group) { 448 | this.log.info('Event not for correct group (group: ' + group + ')'); 449 | break; 450 | } else { //got correct group, command1 contains commanded state 451 | this.log.info('Got updated status for ' + foundDevice.name); 452 | if(command1 == 11) { 453 | foundDevice.currentState = true; 454 | foundDevice.lastUpdate = moment(); 455 | foundDevice.service.getCharacteristic(this.platform.Characteristic.On).updateValue(true); 456 | } 457 | 458 | if(command1 == 13) { 459 | foundDevice.currentState = false; 460 | foundDevice.lastUpdate = moment(); 461 | foundDevice.service.getCharacteristic(this.platform.Characteristic.On).updateValue(false); 462 | } 463 | } 464 | } 465 | 466 | if (messageType == '6') { //group broadcast - manual button press 467 | let group; 468 | let commandedState; 469 | 470 | if(command1 == '06'){ 471 | commandedState = gateway.substring(0, 2); 472 | group = parseInt(gateway.substring(4, 6)); //button number 473 | } else { 474 | commandedState = command1; 475 | group = parseInt(gateway.substring(4, 6)); //button number 476 | } 477 | 478 | if(foundDevice.six_btn == true){ 479 | buttonArray = six_buttonArray; 480 | } else if (foundDevice.four_btn == true){ 481 | buttonArray = four_buttonArray; 482 | }else { 483 | buttonArray = eight_buttonArray; 484 | } 485 | 486 | const foundGroupID = buttonArray[foundDevice.keypadbtn]; 487 | if (foundGroupID !== group) { 488 | this.log.info('Event not for correct group (group: ' + group + ')'); 489 | break; 490 | } else { 491 | this.log.info('Got updated status for ' + foundDevice.name); 492 | if(commandedState == 11) { 493 | foundDevice.currentState = true; 494 | foundDevice.lastUpdate = moment(); 495 | foundDevice.service.getCharacteristic(this.platform.Characteristic.On).updateValue(true); 496 | } 497 | 498 | if(commandedState == 13) { 499 | foundDevice.currentState = false; 500 | foundDevice.lastUpdate = moment(); 501 | foundDevice.service.getCharacteristic(this.platform.Characteristic.On).updateValue(false); 502 | } 503 | } 504 | } 505 | 506 | if (messageType == '2') { //group cleanup 507 | 508 | const group = parseInt(command2, 16); //button number 509 | 510 | if(foundDevice.six_btn == true){ 511 | buttonArray = six_buttonArray; 512 | } else if (foundDevice.four_btn == true){ 513 | buttonArray = four_buttonArray; 514 | }else { 515 | buttonArray = eight_buttonArray; 516 | } 517 | 518 | const foundGroupID = buttonArray[foundDevice.keypadbtn]; 519 | if (foundGroupID !== group) { 520 | this.log.info('Event not for correct group (group: ' + group + ')'); 521 | break; 522 | } else { 523 | this.log.info('Got updated status for ' + foundDevice.name); 524 | if(command1 == 11) { 525 | foundDevice.currentState = true; 526 | foundDevice.lastUpdate = moment(); 527 | foundDevice.service.getCharacteristic(this.platform.Characteristic.On).updateValue(true); 528 | } 529 | 530 | if(command1 == 13) { 531 | foundDevice.currentState = false; 532 | foundDevice.lastUpdate = moment(); 533 | foundDevice.service.getCharacteristic(this.platform.Characteristic.On).updateValue(false); 534 | } 535 | } 536 | } 537 | 538 | foundDevice.getGroupMemberStatus.call(foundDevice); 539 | break; 540 | 541 | case 'fan': 542 | this.log.info('Got updated status for ' + foundDevice.name); 543 | if (command1 == 19) {//fan portion of fanlinc 544 | foundDevice.getFanState.call(foundDevice); 545 | } 546 | foundDevice.lastUpdate = moment(); 547 | break; 548 | 549 | case 'remote': 550 | this.log.info('Got updated status for ' + foundDevice.name); 551 | if (messageType == 6) { 552 | foundDevice.handleRemoteEvent.call(foundDevice, gateway, command1); 553 | } 554 | foundDevice.lastUpdate = moment(); 555 | break; 556 | 557 | case 'outlet': 558 | this.log.info('Got updated status for ' + foundDevice.name); 559 | foundDevice.getOutletState.call(foundDevice); 560 | foundDevice.lastUpdate = moment(); 561 | break; 562 | 563 | case 'blinds': 564 | case 'shades': 565 | this.log.info('Got updated status for ' + foundDevice.name); 566 | foundDevice.getPosition.call(foundDevice); 567 | foundDevice.lastUpdate = moment(); 568 | break; 569 | 570 | case 'smoke': 571 | this.log.info('Got updated status for ' + foundDevice.name); 572 | 573 | if(command1 == 11 && command2 == '01') { 574 | foundDevice.service.getCharacteristic(this.platform.Characteristic.SmokeDetected).updateValue(1); 575 | } else if(command1 == 11 && command2 == '02') { 576 | foundDevice.serviceCO.getCharacteristic(this.platform.Characteristic.CarbonMonoxideDetected).updateValue(1); 577 | } else if(command1 == 11 && command2 == '05') { 578 | foundDevice.service.getCharacteristic(this.platform.Characteristic.SmokeDetected).updateValue(0); 579 | } 580 | 581 | foundDevice.lastUpdate = moment(); 582 | break; 583 | 584 | case 'doorsensor': 585 | case 'windowsensor': 586 | case 'contactsensor': 587 | case 'leaksensor': 588 | case 'motionsensor': 589 | if(command2 == '03'){ //low battery 590 | this.log.info('Got low battery status for ' + foundDevice.name); 591 | foundDevice.statusLowBattery = true; 592 | foundDevice.service.getCharacteristic(this.platform.Characteristic.StatusLowBattery).updateValue(1); 593 | } else if (command2 !== '03') { 594 | foundDevice.service.getCharacteristic(this.platform.Characteristic.StatusLowBattery).updateValue(0); 595 | foundDevice.statusLowBattery = false; 596 | } 597 | foundDevice.lastUpdate = moment(); 598 | break; 599 | } 600 | } 601 | } 602 | 603 | // find all the devices that the current event device is a controller of 604 | const responders = _.filter(this.devices, (device)=> { 605 | return _.contains(device.controllers, id, 0); 606 | }); 607 | 608 | if (responders.length > 0) { 609 | this.log.debug(id + ' is a contoller of ' + responders.length + ' devices'); 610 | 611 | if(['11', '12', '13', '14'].indexOf(command1) +1){ //only really care about on/off commands 612 | 613 | for (let i=0, l=responders.length; i < l; i++){ 614 | const responderDevice: InsteonLocalAccessory = this.insteonAccessories.filter((item) => { 615 | return (item.id == responders[i].deviceID); 616 | })[0]; 617 | 618 | this.log.info('Getting status of responder device ' + responderDevice.name); 619 | responderDevice.getStatus.call(responderDevice); 620 | 621 | } 622 | } else { 623 | this.log.debug('Ignoring Controller Command: ' + command1); 624 | } 625 | } 626 | } 627 | }); 628 | } 629 | 630 | /** 631 | * This is an example method showing how to register discovered accessories. 632 | * Accessories must only be registered once, previously created accessories 633 | * must not be registered again to prevent "duplicate UUID" errors. 634 | */ 635 | discoverDevices() { 636 | if(typeof this.devices=== 'undefined'){ 637 | this.log.debug('No devices defined in config'); 638 | return; 639 | } 640 | 641 | //Remove accessories that are cached but no longer defined in the config 642 | this.accessories.forEach((accessory, index) => { 643 | const foundName = this.devices.filter((device) => { 644 | return (device.name == accessory.displayName); 645 | }); 646 | const inConfig = !!foundName.length; 647 | 648 | if(!inConfig){ 649 | this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); 650 | this.log.info('Removing ' + accessory.displayName +' from cache - no longer in config.'); 651 | this.accessories.splice(index, 1); 652 | } 653 | }); 654 | 655 | const numberDevices = this.devices.length; 656 | this.log.info('Found %s devices in config', numberDevices); 657 | 658 | for (const device of this.devices) { 659 | const uuid = this.api.hap.uuid.generate(device.name); 660 | const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); 661 | 662 | if(device.deviceID && device.deviceID.includes('.')){ 663 | device.deviceID = device.deviceID.replace(/\./g, ''); 664 | } 665 | 666 | if(device.deviceID){ 667 | this.deviceIDs.push(device.deviceID.toUpperCase()); 668 | } 669 | 670 | if (existingAccessory) { 671 | this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName); 672 | 673 | // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: 674 | existingAccessory.context.device = device; 675 | this.api.updatePlatformAccessories([existingAccessory]); 676 | 677 | const theAccessory = new InsteonLocalAccessory(this, existingAccessory); 678 | this.insteonAccessories.push(theAccessory); 679 | 680 | } else { 681 | this.log.info('Adding new accessory:', device.name); 682 | const accessory = new this.api.platformAccessory(device.name, uuid); 683 | accessory.context.device = device; 684 | 685 | const theAccessory = new InsteonLocalAccessory(this, accessory); 686 | this.insteonAccessories.push(theAccessory); 687 | 688 | this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); 689 | } 690 | } 691 | } 692 | 693 | initAPI() { 694 | const deviceIDs = this.deviceIDs; 695 | 696 | this.app.get('/light/:id/on', (req, res) => { 697 | const id = req.params.id.toUpperCase(); 698 | 699 | this.hub.light(id).turnOn().then((status) => { 700 | if (status.response) { 701 | res.sendStatus(200); 702 | 703 | const isDevice = _.contains(deviceIDs, id, 0); 704 | let foundDevice: any; 705 | 706 | if (isDevice) { 707 | foundDevice = this.insteonAccessories.filter((item) => { 708 | return item.id == id; 709 | }); 710 | } 711 | 712 | foundDevice = foundDevice[0]; 713 | setTimeout(()=> { 714 | foundDevice.getStatus.call(foundDevice); 715 | }, 1000); 716 | 717 | } else { 718 | res.sendStatus(404); 719 | } 720 | }); 721 | }); 722 | 723 | this.app.get('/light/:id/off', (req, res) => { 724 | const id = req.params.id.toUpperCase(); 725 | this.hub.light(id).turnOff().then((status) => { 726 | if (status.response) { 727 | res.sendStatus(200); 728 | 729 | const isDevice = _.contains(deviceIDs, id, 0); 730 | let foundDevice; 731 | 732 | if (isDevice) { 733 | foundDevice = this.insteonAccessories.filter((item) => { 734 | return item.id == id; 735 | }); 736 | } 737 | 738 | foundDevice = foundDevice[0]; 739 | foundDevice.getStatus.call(foundDevice); 740 | 741 | } else { 742 | res.sendStatus(404); 743 | } 744 | }); 745 | }); 746 | 747 | this.app.get('/light/:id/status', (req, res) => { 748 | const id = req.params.id; 749 | this.hub.light(id).level((err, level) => { 750 | res.json({ 751 | 'level': level, 752 | }); 753 | }); 754 | }); 755 | 756 | this.app.get('/light/:id/level/:targetLevel', (req, res) => { 757 | const id = req.params.id; 758 | const targetLevel = req.params.targetLevel; 759 | 760 | this.hub.light(id).level(targetLevel).then((status) => { 761 | if (status.response) { 762 | res.sendStatus(200); 763 | 764 | const isDevice = _.contains(deviceIDs, id, 0); 765 | let foundDevice; 766 | 767 | if (isDevice) { 768 | foundDevice = this.insteonAccessories.filter((item: any) => { 769 | return item.id == id; 770 | }); 771 | } 772 | 773 | foundDevice = foundDevice[0]; 774 | foundDevice.getStatus.call(foundDevice); 775 | 776 | } else { 777 | res.sendStatus(404); 778 | } 779 | }); 780 | }); 781 | 782 | this.app.get('/scene/:group/on', (req, res) => { 783 | const group = parseInt(req.params.group); 784 | this.hub.sceneOn(group).then((status) => { 785 | if (status.aborted) { 786 | res.sendStatus(404); 787 | } 788 | if (status.completed) { 789 | res.sendStatus(200); 790 | 791 | /* const isDevice = _.contains(deviceIDs, id, 0); 792 | let foundDevice; 793 | 794 | if (isDevice) { 795 | foundDevice = this.accessories.filter((item) => { 796 | return item.id == id; 797 | }); 798 | } 799 | 800 | foundDevice = foundDevice[0]; 801 | foundDevice.getSceneState.call(foundDevice); 802 | */ 803 | } else { 804 | res.sendStatus(404); 805 | } 806 | }); 807 | }); 808 | 809 | this.app.get('/scene/:group/off', (req, res) => { 810 | const group = parseInt(req.params.group); 811 | this.hub.sceneOff(group).then((status) => { 812 | if (status.aborted) { 813 | res.sendStatus(404); 814 | } 815 | if (status.completed) { 816 | res.sendStatus(200); 817 | /* 818 | const isDevice = _.contains(deviceIDs, id, 0); 819 | let foundDevice; 820 | 821 | if (isDevice) { 822 | foundDevice = this.accessories.filter((item) => { 823 | return item.id == id; 824 | }); 825 | } 826 | 827 | foundDevice = foundDevice[0]; 828 | foundDevice.getSceneState.call(foundDevice); 829 | */ 830 | } else { 831 | res.sendStatus(404); 832 | } 833 | }); 834 | }); 835 | 836 | this.app.get('/links', (req, res) => { 837 | this.hub.links((err, links) => { 838 | res.json(links); 839 | }); 840 | }); 841 | 842 | this.app.get('/links/:id', (req, res) => { 843 | const id = req.params.id; 844 | this.hub.links(id, (err, links) => { 845 | res.json(links); 846 | }); 847 | }); 848 | 849 | this.app.get('/info/:id', (req, res) => { 850 | const id = req.params.id; 851 | this.hub.info(id, (err, info) => { 852 | res.json(info); 853 | }); 854 | }); 855 | 856 | this.app.get('/iolinc/:id/relay_on', (req, res) => { 857 | const id = req.params.id; 858 | this.hub.ioLinc(id).relayOn().then((status) => { 859 | if (status.response) { 860 | res.sendStatus(200); 861 | 862 | const isDevice = _.contains(deviceIDs, id, 0); 863 | let foundDevice; 864 | 865 | if (isDevice) { 866 | foundDevice = this.insteonAccessories.filter((item: any) => { 867 | return item.id == id; 868 | }); 869 | } 870 | 871 | foundDevice = foundDevice[0]; 872 | 873 | setTimeout(() => { 874 | foundDevice.getSensorStatus.call(foundDevice); 875 | }, 1000 * foundDevice.gdo_delay); 876 | 877 | } else { 878 | res.sendStatus(404); 879 | } 880 | }); 881 | }); 882 | 883 | this.app.get('/iolinc/:id/relay_off', (req, res) => { 884 | const id = req.params.id; 885 | this.hub.ioLinc(id).relayOff().then((status) => { 886 | if (status.response) { 887 | res.sendStatus(200); 888 | 889 | const isDevice = _.contains(deviceIDs, id, 0); 890 | let foundDevice; 891 | 892 | if (isDevice) { 893 | foundDevice = this.insteonAccessories.filter((item: any) => { 894 | return item.id == id; 895 | }); 896 | } 897 | 898 | foundDevice = foundDevice[0]; 899 | 900 | setTimeout(() => { 901 | foundDevice.getSensorStatus.call(foundDevice); 902 | }, 1000 * foundDevice.gdo_delay); 903 | 904 | } else { 905 | res.sendStatus(404); 906 | } 907 | }); 908 | }); 909 | 910 | this.app.get('/iolinc/:id/sensor_status', (req, res) => { 911 | const id = req.params.id; 912 | this.hub.ioLinc(id).status((err, status) => { 913 | res.json(status.sensor); 914 | }); 915 | }); 916 | 917 | this.app.get('/iolinc/:id/relay_status', (req, res) => { 918 | const id = req.params.id; 919 | this.hub.ioLinc(id).status((err, status) => { 920 | res.json(status.relay); 921 | }); 922 | }); 923 | 924 | 925 | this.app.get('/fan/:id/level/:targetLevel', (req, res) => { 926 | const id = req.params.id; 927 | const targetLevel = req.params.targetLevel; 928 | 929 | hub.light(id).fan(targetLevel).then((status) => { 930 | if (status.response) { 931 | res.sendStatus(200); 932 | 933 | const isDevice = _.contains(this.deviceIDs, id, 0); 934 | let foundDevice; 935 | 936 | if (isDevice) { 937 | foundDevice = this.insteonAccessories.filter((item) => { 938 | return item.id == id; 939 | }); 940 | } 941 | 942 | foundDevice = foundDevice[0]; 943 | foundDevice.getStatus.call(foundDevice); 944 | 945 | } else { 946 | res.sendStatus(404); 947 | } 948 | }); 949 | }); 950 | 951 | this.app.get('/fan/:id/status', (req, res) => { 952 | const id = req.params.id; 953 | hub.light(id).fan((err, level) => { 954 | res.json({ 955 | 'level': level, 956 | }); 957 | }); 958 | }); 959 | } 960 | } -------------------------------------------------------------------------------- /src/InsteonLocalAccessory.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-case-declarations */ 2 | /* eslint-disable max-len */ 3 | import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge'; 4 | import { InsteonLocalPlatform } from './InsteonLocalPlatform'; 5 | 6 | import moment, { Moment } from 'moment'; 7 | import util from 'util'; 8 | import _ from 'underscore'; 9 | import insteonUtils from 'home-controller/lib/Insteon/utils'; 10 | const convertTemp = insteonUtils.convertTemp; 11 | 12 | export class InsteonLocalAccessory { 13 | service: Service; 14 | private readonly log: any; 15 | private hub: any; 16 | private light: any; 17 | 18 | device: any; 19 | name: string; 20 | id: string; 21 | deviceType: string; 22 | dimmable: boolean; 23 | level: number; 24 | currentState: any; 25 | disabled: boolean; 26 | lastUpdate: Moment; 27 | refreshInterval: number; 28 | targetKeypadID: string; 29 | targetKeypadSixBtn: number; 30 | targetKeypadBtn: string; 31 | setTargetKeypadCount: number; 32 | lastCommand: Moment; 33 | levelTimeout; 34 | six_btn; 35 | keypadbtn; 36 | buttonMap: string; 37 | pollTimer: NodeJS.Timeout; 38 | iolinc: any; 39 | invert_sensor: unknown; 40 | groupMembers: any; 41 | groupID: any; 42 | momentary: any; 43 | gdo_delay: any; 44 | valve_delay: any; 45 | stateless: any; 46 | button: any; 47 | four_btn: any; 48 | position: any; 49 | disableBatteryStatus: any; 50 | house: any; 51 | unit: any; 52 | statusLowBattery: boolean; 53 | heartbeatTimer: NodeJS.Timeout; 54 | deadDeviceTimer: NodeJS.Timeout; 55 | leaksensor: any; 56 | motionsensor: any; 57 | motionDetected: boolean; 58 | door: any; 59 | targetState: unknown; 60 | accessories: Array; 61 | handleRemoteEvent: any; 62 | serviceCO: any; 63 | thermostat: any; 64 | units: any; 65 | tempUnits: any; 66 | mode: string; 67 | currentTemp: any; 68 | targetTemp: any; 69 | refresh: number; 70 | targetKeypadFourBtn: any; 71 | 72 | constructor( 73 | private readonly platform: InsteonLocalPlatform, 74 | private readonly accessory: PlatformAccessory, 75 | ) { 76 | this.hub = platform.hub; 77 | this.log = this.platform.log; 78 | this.device = accessory.context.device; 79 | 80 | if(this.device.deviceID){ 81 | this.id = this.device.deviceID.toUpperCase(); 82 | this.id = this.id.trim().replace(/\./g, ''); 83 | } 84 | 85 | this.dimmable = (this.device.dimmable == 'yes') ? true : false; 86 | this.name = this.device.name; 87 | this.deviceType = this.device.deviceType; 88 | this.refreshInterval = this.device.refresh || this.platform.refreshInterval; 89 | this.disabled = this.device.disabled || false; 90 | 91 | this.targetKeypadID = this.device.targetKeypadID || []; 92 | this.targetKeypadSixBtn = this.device.targetKeypadSixBtn || []; 93 | this.targetKeypadFourBtn = this.device.targetKeypadFourBtn || []; 94 | this.targetKeypadBtn = this.device.targetKeypadBtn || []; 95 | this.setTargetKeypadCount = 0; 96 | 97 | if(typeof this.device.groupMembers !== 'undefined'){ 98 | this.device.groupMembers = this.device.groupMembers.replace(/\s*,\s*/g, ','); //remove spaces after commas 99 | const reg = /,|,\s/; 100 | this.groupMembers = this.device.groupMembers.split(reg); 101 | } 102 | 103 | if(this.id){ 104 | this.id = this.id.trim().replace(/\./g, ''); 105 | } 106 | 107 | this.accessory.on('identify', () =>{ 108 | this.identify(); 109 | }); 110 | 111 | if (this.deviceType == 'scene') { 112 | this.groupID = this.device.groupID; 113 | this.keypadbtn = this.device.keypadbtn; 114 | this.six_btn = this.device.six_btn; 115 | this.four_btn = this.device.four_btn; 116 | this.momentary = this.device.momentary || false; 117 | } 118 | 119 | if (this.deviceType == 'keypad') { 120 | this.keypadbtn = typeof(this.device.keypadbtn) === 'string' ? this.device.keypadbtn : '?'; 121 | this.six_btn = this.device.six_btn === true; 122 | this.four_btn = this.device.four_btn === true; 123 | } 124 | 125 | if (this.deviceType == 'iolinc') { 126 | this.gdo_delay = this.device.gdo_delay || 15; 127 | this.invert_sensor = this.device.invert_sensor || false; 128 | } 129 | 130 | if (this.deviceType == 'valve') { 131 | this.valve_delay = this.device.valve_delay || 5; 132 | this.invert_sensor = this.device.invert_sensor || false; 133 | } 134 | 135 | if (this.deviceType == 'remote') { 136 | this.button = this.device.remotebtn; 137 | this.stateless = this.device.stateless; 138 | this.four_btn = this.device.four_btn || false; 139 | } 140 | 141 | if (this.deviceType == 'outlet') { 142 | this.position = this.device.position || 'top'; 143 | } 144 | 145 | if (['motionsensor', 'doorsensor', 'windowsensor', 'contactsensor', 'leaksensor'].includes(this.deviceType)) { 146 | this.disableBatteryStatus = this.device.disableBatteryStatus || false; 147 | this.invert_sensor = this.device.invert_sensor || false; 148 | } 149 | 150 | if (this.deviceType == 'x10') { 151 | this.house = this.device.house || 'A'; 152 | this.unit = this.device.unit; 153 | } 154 | 155 | if (this.deviceType == 'thermostat') { 156 | //this.tempUnits = this.device.tempUnits; //Not currently used - determined from thermostat 157 | } 158 | 159 | if(this.refreshInterval > 0){ 160 | this.hub.once('connect', () =>{ 161 | if (['lightbulb', 'dimmer', 'switch', 'iolinc', 'scene', 'outlet', 'fan', 'shades', 'blinds', 'keypad'].includes(this.deviceType)) { 162 | setTimeout(() => { 163 | this.pollStatus(); 164 | }, (1000 * this.refreshInterval)); 165 | } 166 | }, 167 | ); 168 | } 169 | 170 | this.init(); 171 | } 172 | 173 | init(){ 174 | this.setAccessoryInformation(); 175 | 176 | switch (this.deviceType) { 177 | case 'lightbulb': 178 | case 'dimmer': 179 | this.service = this.accessory.getService(this.platform.Service.Lightbulb) || this.accessory.addService(this.platform.Service.Lightbulb); 180 | this.dimmable = true; 181 | this.service.getCharacteristic(this.platform.Characteristic.On).onSet(this.setBrightnessLevel.bind(this)); 182 | //this.service.getCharacteristic(Characteristic.On).on('set', this.setPowerState.bind(this)) 183 | 184 | if (this.dimmable) { 185 | this.service.getCharacteristic(this.platform.Characteristic.Brightness).onSet(this.setBrightnessLevel.bind(this)); 186 | } 187 | 188 | this.light = this.hub.light(this.id); 189 | this.light.emitOnAck = true; 190 | 191 | //Get initial state 192 | this.hub.once('connect', () => { 193 | this.getStatus.call(this); 194 | }); 195 | 196 | break; 197 | 198 | case 'fan': 199 | this.service = this.accessory.getService(this.platform.Service.Fan) || this.accessory.addService(this.platform.Service.Fan); 200 | 201 | this.service.getCharacteristic(this.platform.Characteristic.On).onSet(this.setFanState.bind(this)); 202 | this.service.getCharacteristic(this.platform.Characteristic.RotationSpeed).onSet(this.setFanState.bind(this)); 203 | 204 | this.light = this.hub.light(this.id); 205 | 206 | this.light.on('turnOn', (group, level) =>{ 207 | this.log.debug(this.name + ' turned on'); 208 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(true); 209 | this.getFanState.call(this); 210 | }); 211 | 212 | this.light.on('turnOff', () =>{ 213 | this.log.debug(this.name + ' turned off'); 214 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(false); 215 | this.service.getCharacteristic(this.platform.Characteristic.RotationSpeed).updateValue(0); 216 | }); 217 | 218 | //Get initial state 219 | this.hub.once('connect', () => { 220 | this.getFanState.call(this); 221 | }); 222 | 223 | break; 224 | 225 | case 'switch': 226 | this.service = this.accessory.getService(this.platform.Service.Switch) || this.accessory.addService(this.platform.Service.Switch); 227 | 228 | this.service.getCharacteristic(this.platform.Characteristic.On).onSet(this.setPowerState.bind(this)); 229 | 230 | this.light = this.hub.light(this.id); 231 | this.light.emitOnAck = true; 232 | 233 | this.hub.once('connect', () => { 234 | this.getStatus.call(this); 235 | }); 236 | 237 | break; 238 | 239 | case 'scene': 240 | this.service = this.accessory.getService(this.platform.Service.Switch) || this.accessory.addService(this.platform.Service.Switch); 241 | this.dimmable = false; 242 | 243 | this.service.getCharacteristic(this.platform.Characteristic.On).onSet(this.setSceneState.bind(this)); 244 | 245 | this.hub.once('connect', () => { 246 | if(this.id){ 247 | this.getSceneState.call(this); 248 | } 249 | }); 250 | 251 | break; 252 | 253 | case 'iolinc': 254 | case 'garage': 255 | this.service = this.accessory.getService(this.platform.Service.GarageDoorOpener) || this.accessory.addService(this.platform.Service.GarageDoorOpener); 256 | 257 | this.service.getCharacteristic(this.platform.Characteristic.ObstructionDetected).updateValue(false); 258 | this.service.getCharacteristic(this.platform.Characteristic.TargetDoorState).onSet(this.setRelayState.bind(this)); 259 | 260 | this.iolinc = this.hub.ioLinc(this.id); 261 | 262 | this.iolinc.on('sensorOn', () =>{ 263 | this.log.debug(this.name + ' sensor is on. invert_sensor = ' + this.invert_sensor); 264 | 265 | if(this.invert_sensor == false || this.invert_sensor == 'false') { //Door Closed (non-inverted): No delay to action, since sensor isn't triggered until door is fully closed. 266 | this.log.debug(' >>> No Delayed Action <<<'); 267 | actionDoorClosed(); 268 | } else { //Door Open (inverted): Add delay to action, since sensor is triggered immediately upon door closing. 269 | setTimeout(() =>{ 270 | this.log.debug(' >>> Delayed Action <<<'); 271 | actionDoorOpen(); 272 | }, 1000 * this.gdo_delay); 273 | } 274 | }); 275 | 276 | this.iolinc.on('sensorOff', () =>{ 277 | this.log.debug(this.name + ' sensor is off invert_sensor = ' + this.invert_sensor); 278 | 279 | if(this.invert_sensor == false || this.invert_sensor == 'false') { //Door Open (non-inverted): Add delay to action, since sensor is triggered immediately upon door opening. 280 | setTimeout(() =>{ 281 | this.log.debug(' >>> Delayed Action <<<'); 282 | actionDoorOpen(); 283 | }, 1000 * this.gdo_delay); 284 | } else { //Door Closed (inverted): No delay to action, since sensor isn't triggered until door fully opens. 285 | this.log.debug(' >>> No Delayed Action <<<'); 286 | actionDoorClosed(); 287 | } 288 | }); 289 | 290 | this.hub.once('connect', () => { 291 | this.getSensorStatus.call(this, () =>{/*do nothing for now*/}); 292 | }); 293 | 294 | const actionDoorClosed = () =>{ 295 | this.service.getCharacteristic(this.platform.Characteristic.TargetDoorState).updateValue(1); 296 | this.service.getCharacteristic(this.platform.Characteristic.CurrentDoorState).updateValue(1); 297 | this.currentState = true; 298 | }; 299 | 300 | const actionDoorOpen = () =>{ 301 | this.service.getCharacteristic(this.platform.Characteristic.TargetDoorState).updateValue(0); 302 | this.service.getCharacteristic(this.platform.Characteristic.CurrentDoorState).updateValue(0); 303 | this.currentState = false; 304 | }; 305 | 306 | break; 307 | 308 | case 'valve': 309 | this.service = this.accessory.getService(this.platform.Service.Valve) || this.accessory.addService(this.platform.Service.Valve); 310 | 311 | this.service.getCharacteristic(this.platform.Characteristic.Active).updateValue(0); 312 | this.service.getCharacteristic(this.platform.Characteristic.InUse).updateValue(0); 313 | this.service.getCharacteristic(this.platform.Characteristic.ValveType).updateValue(0); 314 | 315 | this.service.getCharacteristic(this.platform.Characteristic.Active).onSet(this.setRelayState.bind(this)); 316 | 317 | this.iolinc = this.hub.ioLinc(this.id); 318 | 319 | this.hub.once('connect', () => { 320 | this.getSensorStatus.call(this, () =>{/*do nothing for now*/}); 321 | }); 322 | 323 | break; 324 | 325 | case 'leaksensor': { 326 | this.service = this.accessory.getService(this.platform.Service.LeakSensor) || this.accessory.addService(this.platform.Service.LeakSensor); 327 | 328 | this.service.getCharacteristic(this.platform.Characteristic.LeakDetected).updateValue(0); //Initialize as dry 329 | 330 | this.service.getCharacteristic(this.platform.Characteristic.StatusLowBattery).updateValue(0); //0=normal, 1=low 331 | this.statusLowBattery = false; 332 | 333 | const buffer = 300 * 1000; //5 minute buffer 334 | if(this.disableBatteryStatus == false){ 335 | this.log.debug('Initializing heartbeat timer for ' + this.name); 336 | this.heartbeatTimer = setTimeout(() => { 337 | this.statusLowBattery = true; 338 | this.service.getCharacteristic(this.platform.Characteristic.StatusLowBattery).updateValue(1); //0=normal, 1=low 339 | }, (1000*24*60*60 + buffer)); 340 | } 341 | 342 | if(this.disableBatteryStatus == true){ //if battery status disabled, still notify when the device is dead 343 | this.log.debug('Initializing dead device timer for ' + this.name); 344 | this.deadDeviceTimer = setTimeout(() => { 345 | this.statusLowBattery = true; 346 | this.service.getCharacteristic(this.platform.Characteristic.StatusLowBattery).updateValue(1); //0=normal, 1=low 347 | }, (1000*48*60*60 + buffer)); 348 | } 349 | 350 | this.leaksensor = this.hub.leak(this.id); 351 | 352 | this.leaksensor.on('wet', () =>{ 353 | this.log.debug(this.name + ' sensor is wet'); 354 | this.service.getCharacteristic(this.platform.Characteristic.LeakDetected).updateValue(1); //wet 355 | }); 356 | 357 | this.leaksensor.on('dry', () =>{ 358 | this.log.debug(this.name + ' sensor is dry'); 359 | this.service.getCharacteristic(this.platform.Characteristic.LeakDetected).updateValue(0); //dry 360 | }); 361 | 362 | this.leaksensor.on('heartbeat', () =>{ 363 | this.log.debug('Heartbeat from ' + this.name); 364 | 365 | if(this.disableBatteryStatus == false){ 366 | clearTimeout(this.heartbeatTimer); 367 | this.heartbeatTimer = setTimeout(() => { 368 | this.statusLowBattery = true; 369 | this.service.getCharacteristic(this.platform.Characteristic.StatusLowBattery).updateValue(1); //0=normal, 1=low 370 | }, (1000*24*60*60 + buffer)); 371 | } 372 | 373 | if(this.disableBatteryStatus == true){ 374 | clearTimeout(this.deadDeviceTimer); 375 | this.deadDeviceTimer = setTimeout(() => { //if battery status disabled, still notify when the device is dead 376 | this.statusLowBattery = true; 377 | this.service.getCharacteristic(this.platform.Characteristic.StatusLowBattery).updateValue(1); //0=normal, 1=low 378 | }, (1000*48*60*60 + buffer)); 379 | } 380 | }); 381 | 382 | break; 383 | } 384 | case 'motionsensor': { 385 | this.service = this.accessory.getService(this.platform.Service.MotionSensor) || this.accessory.addService(this.platform.Service.MotionSensor); 386 | 387 | this.service.getCharacteristic(this.platform.Characteristic.MotionDetected).updateValue(0); //Initialize with no motion 388 | this.service.getCharacteristic(this.platform.Characteristic.StatusLowBattery).updateValue(0); //0=normal, 1=low 389 | this.statusLowBattery = false; 390 | 391 | const buffer = 300 * 1000; //5 minute buffer 392 | if(this.disableBatteryStatus == false){ 393 | this.log.debug('Initializing heartbeat timer for ' + this.name); 394 | this.heartbeatTimer = setTimeout(() => { 395 | this.statusLowBattery = true; 396 | this.service.getCharacteristic(this.platform.Characteristic.StatusLowBattery).updateValue(1); //0=normal, 1=low 397 | }, (1000*24*60*60 + buffer)); 398 | } 399 | 400 | if(this.disableBatteryStatus == true){ 401 | this.log.debug('Initializing dead device timer for ' + this.name); 402 | this.deadDeviceTimer = setTimeout(() => { //if battery status disabled, still notify when the device is dead 403 | this.statusLowBattery = true; 404 | this.service.getCharacteristic(this.platform.Characteristic.StatusLowBattery).updateValue(1); //0=normal, 1=low 405 | }, (1000*48*60*60 + buffer)); 406 | } 407 | 408 | this.motionsensor = this.hub.motion(this.id); 409 | 410 | this.motionsensor.on('motion', () =>{ 411 | this.log.debug(this.name + ' is on'); 412 | this.motionDetected = true; 413 | this.service.getCharacteristic(this.platform.Characteristic.MotionDetected).updateValue(1); 414 | }); 415 | 416 | this.motionsensor.on('clear', () =>{ 417 | this.log.debug(this.name + ' is off'); 418 | this.motionDetected = false; 419 | this.service.getCharacteristic(this.platform.Characteristic.MotionDetected).updateValue(0); 420 | }); 421 | 422 | this.motionsensor.on('heartbeat', () =>{ 423 | this.log.debug('Heartbeat from ' + this.name); 424 | 425 | if(this.disableBatteryStatus == false){ 426 | clearTimeout(this.heartbeatTimer); 427 | this.heartbeatTimer = setTimeout(() => { 428 | this.statusLowBattery = true; 429 | this.service.getCharacteristic(this.platform.Characteristic.StatusLowBattery).updateValue(1); //0=normal, 1=low 430 | }, (1000*24*60*60 + buffer)); 431 | } 432 | 433 | if(this.disableBatteryStatus == true){ 434 | clearTimeout(this.deadDeviceTimer); 435 | this.deadDeviceTimer = setTimeout(() => { //if battery status disabled, still notify when the device is dead 436 | this.statusLowBattery = true; 437 | this.service.getCharacteristic(this.platform.Characteristic.StatusLowBattery).updateValue(1); //0=normal, 1=low 438 | }, (1000*48*60*60 + buffer)); 439 | } 440 | }); 441 | 442 | break; 443 | } 444 | case 'doorsensor': 445 | case 'windowsensor': 446 | case 'contactsensor': { 447 | this.service = this.accessory.getService(this.platform.Service.ContactSensor) || this.accessory.addService(this.platform.Service.ContactSensor); 448 | 449 | this.service.getCharacteristic(this.platform.Characteristic.ContactSensorState).updateValue(0); //Initialize closed 450 | this.service.getCharacteristic(this.platform.Characteristic.StatusLowBattery).updateValue(0); //0=normal, 1=low 451 | this.statusLowBattery = false; 452 | 453 | const buffer = 300 * 1000; //5 minute buffer 454 | if(this.disableBatteryStatus == false){ 455 | this.log.debug('Initializing heartbeat timer for ' + this.name); 456 | this.heartbeatTimer = setTimeout(() => { 457 | this.statusLowBattery = true; 458 | this.service.getCharacteristic(this.platform.Characteristic.StatusLowBattery).updateValue(1); //0=normal, 1=low 459 | }, (1000*24*60*60 + buffer)); 460 | } 461 | 462 | if(this.disableBatteryStatus == true){ 463 | this.log.debug('Initializing dead device timer for ' + this.name); 464 | this.deadDeviceTimer = setTimeout(() => { //if battery status disabled, still notify when the device is dead 465 | this.statusLowBattery = true; 466 | this.service.getCharacteristic(this.platform.Characteristic.StatusLowBattery).updateValue(1); //0=normal, 1=low 467 | }, (1000*48*60*60 + buffer)); 468 | } 469 | 470 | this.door = this.hub.door(this.id); 471 | 472 | this.door.on('opened', () =>{ 473 | this.log.debug(this.name + ' is open'); 474 | this.log.debug('Invert sensor: ' + this.invert_sensor); 475 | 476 | if(this.invert_sensor == 'false' || this.invert_sensor == false) { 477 | this.currentState = true; 478 | this.service.getCharacteristic(this.platform.Characteristic.ContactSensorState).updateValue(1); 479 | } else if(this.invert_sensor == true || this.invert_sensor == 'true') { 480 | this.currentState = false; 481 | this.service.getCharacteristic(this.platform.Characteristic.ContactSensorState).updateValue(0); 482 | } 483 | }); 484 | 485 | this.door.on('closed', () =>{ 486 | this.log.debug(this.name + ' is closed'); 487 | this.log.debug('Invert sensor: ' + this.invert_sensor); 488 | 489 | if(this.invert_sensor == 'false' || this.invert_sensor == false) { 490 | this.currentState = false; 491 | this.service.getCharacteristic(this.platform.Characteristic.ContactSensorState).updateValue(0); 492 | } else if(this.invert_sensor == true || this.invert_sensor == 'true') { 493 | this.currentState = true; 494 | this.service.getCharacteristic(this.platform.Characteristic.ContactSensorState).updateValue(1); 495 | } 496 | }); 497 | 498 | this.door.on('heartbeat', () =>{ 499 | this.log.debug('Heartbeat from ' + this.name); 500 | 501 | if(this.disableBatteryStatus == false){ 502 | clearTimeout(this.heartbeatTimer); 503 | this.heartbeatTimer = setTimeout(() => { 504 | this.statusLowBattery = true; 505 | this.service.getCharacteristic(this.platform.Characteristic.StatusLowBattery).updateValue(1); //0=normal, 1=low 506 | }, (1000*24*60*60 + buffer)); 507 | } 508 | 509 | if(this.disableBatteryStatus == true){ 510 | clearTimeout(this.deadDeviceTimer); 511 | this.deadDeviceTimer = setTimeout(() => { //if battery status disabled, still notify when the device is dead 512 | this.statusLowBattery = true; 513 | this.service.getCharacteristic(this.platform.Characteristic.StatusLowBattery).updateValue(1); //0=normal, 1=low 514 | }, (1000*48*60*60 + buffer)); 515 | } 516 | }); 517 | 518 | break; 519 | } 520 | case 'remote': 521 | if (this.stateless) { 522 | this.service = this.accessory.getService(this.platform.Service.StatelessProgrammableSwitch) || this.accessory.addService(this.platform.Service.StatelessProgrammableSwitch); 523 | } else { 524 | this.service = this.accessory.getService(this.platform.Service.Switch) || this.accessory.addService(this.platform.Service.Switch); 525 | 526 | } 527 | 528 | this.dimmable = false; 529 | 530 | break; 531 | 532 | case 'outlet': 533 | this.service = this.accessory.getService(this.platform.Service.Outlet) || this.accessory.addService(this.platform.Service.Outlet); 534 | 535 | this.service.getCharacteristic(this.platform.Characteristic.OutletInUse).updateValue(true); 536 | 537 | this.service.getCharacteristic(this.platform.Characteristic.On).onSet(this.setOutletState.bind(this)); 538 | 539 | this.dimmable = false; 540 | 541 | this.hub.once('connect', () => { 542 | this.getOutletState.call(this); 543 | }); 544 | 545 | break; 546 | 547 | case 'shades': 548 | case 'blinds': 549 | this.service = this.accessory.getService(this.platform.Service.WindowCovering) || this.accessory.addService(this.platform.Service.WindowCovering); 550 | 551 | this.service.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(2); //stopped 552 | 553 | this.service.getCharacteristic(this.platform.Characteristic.TargetPosition).onSet(this.setPosition.bind(this)); 554 | 555 | this.light = this.hub.light(this.id); 556 | 557 | this.hub.once('connect', () => { 558 | this.getPosition.call(this); 559 | }); 560 | 561 | break; 562 | 563 | case 'keypad': 564 | this.service = this.accessory.getService(this.platform.Service.Switch) || this.accessory.addService(this.platform.Service.Switch); 565 | 566 | this.service.getCharacteristic(this.platform.Characteristic.On).onSet(this.setKeypadState.bind(this)); 567 | 568 | this.hub.once('connect', () => { 569 | this.getSceneState.call(this); 570 | }); 571 | 572 | break; 573 | 574 | case 'smoke': 575 | this.service = this.accessory.getService(this.platform.Service.SmokeSensor) || this.accessory.addService(this.platform.Service.SmokeSensor); 576 | 577 | this.service.getCharacteristic(this.platform.Characteristic.SmokeDetected).updateValue(0); //no smoke 578 | 579 | this.service = this.accessory.getService(this.platform.Service.CarbonMonoxideSensor) || this.accessory.addService(this.platform.Service.CarbonMonoxideSensor); 580 | this.service.getCharacteristic(this.platform.Characteristic.CarbonMonoxideDetected).updateValue(0); //no CO 581 | 582 | break; 583 | 584 | case 'x10': 585 | this.service = this.accessory.getService(this.platform.Service.Switch) || this.accessory.addService(this.platform.Service.Switch); 586 | 587 | this.service.getCharacteristic(this.platform.Characteristic.On).onSet(this.setX10PowerState.bind(this)); 588 | this.light = this.hub.x10(this.house, this.unit); 589 | 590 | break; 591 | 592 | case 'thermostat': 593 | this.service = this.accessory.getService(this.platform.Service.Thermostat) || this.accessory.addService(this.platform.Service.Thermostat); 594 | 595 | this.thermostat = this.hub.thermostat(this.id); 596 | this.thermostat.monitor(true); 597 | 598 | const refresh = this.refresh || 5*60*1000; //set to 5min unless 'refresh' defined at device level 599 | 600 | this.hub.once('connect', () => { 601 | this.getThermostatStatus.call(this); 602 | //get temp every `refresh` minutes (or 5min) 603 | setInterval(()=>{ 604 | this.getTemperature(); 605 | }, refresh); 606 | }); 607 | 608 | this.thermostat.on('cooling', () =>{ 609 | this.log.debug('Thermostat ' + this.name + ' is cooling'); 610 | this.mode = 'cool'; 611 | this.log.debug(this.name + ' mode changed to ' + this.mode); 612 | this.service.getCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState).updateValue(2); 613 | this.service.getCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState).updateValue(2); 614 | }); 615 | 616 | this.thermostat.on('heating', () =>{ 617 | this.log.debug('Thermostat ' + this.name + ' is heating'); 618 | this.mode = 'heat'; 619 | this.log.debug(this.name + ' mode changed to ' + this.mode); 620 | this.service.getCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState).updateValue(1); 621 | this.service.getCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState).updateValue(1); 622 | }); 623 | 624 | this.thermostat.on('off', () =>{ 625 | this.log.debug('Thermostat ' + this.name + ' is off'); 626 | this.mode = 'off'; 627 | this.log.debug(this.name + ' mode changed to ' + this.mode); 628 | this.service.getCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState).updateValue(0); 629 | this.service.getCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState).updateValue(0); 630 | }); 631 | 632 | this.service.getCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState) 633 | .onSet(this.setThermostatMode.bind(this)); 634 | 635 | this.service.getCharacteristic(this.platform.Characteristic.TargetTemperature) 636 | .onSet(this.setTemperature.bind(this)); 637 | 638 | break; 639 | } 640 | } 641 | 642 | setAccessoryInformation() { 643 | let deviceMAC; 644 | 645 | if(this.id){ 646 | deviceMAC = this.id.substr(0, 2) + '.' + this.id.substr(2, 2) + '.' + this.id.substr(4, 2); 647 | } else { 648 | deviceMAC = this.name; //Device without device id (ie, scene), set serial num to the name 649 | } 650 | // set accessory information 651 | this.accessory.getService(this.platform.Service.AccessoryInformation)! 652 | .setCharacteristic(this.platform.Characteristic.Manufacturer, 'Insteon') 653 | .setCharacteristic(this.platform.Characteristic.Model, 'Insteon') 654 | .setCharacteristic(this.platform.Characteristic.SerialNumber, deviceMAC); 655 | } 656 | 657 | async setBrightnessLevel(level: CharacteristicValue) { 658 | 659 | if(this.disabled){ 660 | this.log.debug('Device ' + this.name + ' is disabled'); 661 | return; 662 | } 663 | 664 | this.platform.checkHubConnection(); 665 | 666 | const now = moment(); 667 | let delta; 668 | 669 | if(typeof this.lastCommand === 'undefined'){ 670 | this.lastCommand = now; 671 | delta = -1; 672 | } else { 673 | delta = now.diff(this.lastCommand, 'milliseconds'); 674 | } 675 | 676 | const debounceTimer = 600; 677 | 678 | this.log.debug('Command for ' + this.name + ': ' + level + ', time: ' + this.lastCommand + ', delta: ' + delta); 679 | 680 | if (level == this.currentState) { 681 | this.log.debug('Discard on for ' + this.name + ' already at commanded state'); 682 | return; 683 | } else if (level === true && delta >= 0 && delta <= 50) { 684 | this.log.debug('Discard on for ' + this.name + ', sent too close to dim'); 685 | return; 686 | } else if (level === true) { 687 | level = 100; 688 | } else if (level === false) { 689 | level = 0; 690 | } 691 | 692 | this.lastCommand = now; 693 | 694 | clearTimeout(this.levelTimeout); 695 | 696 | this.levelTimeout = setTimeout(()=> { 697 | setLevel(level); 698 | }, debounceTimer); 699 | 700 | const setLevel = (level) =>{ 701 | this.hub.cancelPending(this.id); 702 | 703 | this.lastCommand = now; 704 | 705 | this.log('Setting level of ' + this.name + ' to ' + level + '%'); 706 | 707 | let hexLevel = Math.ceil(level * (255/100)).toString(16); 708 | hexLevel = '00'.substr(hexLevel.length) + hexLevel; 709 | const timeout = 0; 710 | 711 | const cmd = { 712 | cmd1: '11', 713 | cmd2: hexLevel, 714 | }; 715 | 716 | this.hub.directCommand(this.id, cmd, timeout, (error, status) => { 717 | if(error){ 718 | this.log('Error setting level of ' + this.name); 719 | this.getStatus.call(this); 720 | return; 721 | } 722 | 723 | this.level = level; 724 | this.service.getCharacteristic(this.platform.Characteristic.Brightness).updateValue(this.level); 725 | 726 | if (this.level > 0) { 727 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(true); 728 | this.currentState = true; 729 | } else if (this.level == 0) { 730 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(false); 731 | this.currentState = false; 732 | } 733 | 734 | this.log.debug(this.name + ' is ' + (this.currentState ? 'on' : 'off') + ' at ' + level + '%'); 735 | this.lastUpdate = moment(); 736 | 737 | //Check if any target keypad button(s) to process 738 | if(this.targetKeypadID.length > 0){ 739 | this.log.debug(this.targetKeypadID.length + ' target keypad(s) found for ' + this.name); 740 | 741 | for(let temp = 0; temp < this.targetKeypadID.length; temp++){ 742 | this.log.debug(' targetKeypadID[' + temp + '] = ' + '[' + this.targetKeypadID[temp] + ']'); 743 | } 744 | 745 | let count; 746 | for(count = 0; count < this.targetKeypadID.length; count++){ 747 | //this.log.debug(' count = ' + count) 748 | 749 | this.setTargetKeypadCount = count; 750 | 751 | //Async-Wait function to insure multiple keypads are processed in order 752 | const run = async () =>{ 753 | const promise = new Promise((resolve, reject) => this.setTargetKeypadBtn.call(this)); 754 | const result = await promise; // wait until the promise resolves 755 | return; // "done!" 756 | }; 757 | 758 | run(); 759 | } 760 | return; 761 | } 762 | return; 763 | }); 764 | }; 765 | } 766 | 767 | async getStatus() { 768 | let currentState; 769 | 770 | this.platform.checkHubConnection(); 771 | 772 | if(this.deviceType == 'scene' || this.deviceType == 'keypad'){ 773 | this.getSceneState.call(this); 774 | return; 775 | } 776 | 777 | this.log.info('Getting status for ' + this.name); 778 | 779 | this.light.level((err, level) => { 780 | if(err || level == null || typeof level === 'undefined'){ 781 | this.log('Error getting power state of ' + this.name); 782 | return; 783 | } else { 784 | if (level > 0) { 785 | currentState = true; 786 | } else { 787 | currentState = false; 788 | } 789 | 790 | this.currentState = currentState; 791 | this.level = level; 792 | this.log.debug(this.name + ' is ' + (currentState ? 'on' : 'off') + ' at ' + level + '%'); 793 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(this.currentState); 794 | if (this.dimmable) { 795 | this.service.getCharacteristic(this.platform.Characteristic.Brightness).updateValue(this.level); 796 | } 797 | this.lastUpdate = moment(); 798 | return; 799 | } 800 | }); 801 | } 802 | 803 | getSceneState() { 804 | const timeout = 0; 805 | 806 | const eight_buttonArray = { 807 | 'A': 0b00000001, 808 | 'B': 0b00000010, 809 | 'C': 0b00000100, 810 | 'D': 0b00001000, 811 | 'E': 0b00010000, 812 | 'F': 0b00100000, 813 | 'G': 0b01000000, 814 | 'H': 0b10000000}; 815 | 816 | const six_buttonArray = { 817 | 'A': eight_buttonArray['C'], 818 | 'B': eight_buttonArray['D'], 819 | 'C': eight_buttonArray['E'], 820 | 'D': eight_buttonArray['F'], 821 | 'ON': eight_buttonArray['A'] | eight_buttonArray['B'], 822 | 'OFF': eight_buttonArray['G'] | eight_buttonArray['H']}; 823 | 824 | const four_buttonArray = { 825 | '1': eight_buttonArray['A'], 826 | '2': eight_buttonArray['B'], 827 | '3': eight_buttonArray['C'], 828 | '4': eight_buttonArray['D'], 829 | }; 830 | 831 | let buttonArray; 832 | 833 | this.platform.checkHubConnection(); 834 | 835 | if(this.six_btn == true){ 836 | buttonArray = six_buttonArray; 837 | } else if (this.four_btn == true){ 838 | buttonArray = four_buttonArray; 839 | } else { 840 | buttonArray = eight_buttonArray; 841 | } 842 | 843 | let cmd; 844 | 845 | if(this.six_btn == true){ 846 | if (this.keypadbtn == 'ON') { 847 | cmd = { 848 | cmd1: '19', 849 | cmd2: '00', 850 | }; 851 | } else { 852 | cmd = { 853 | cmd1: '19', 854 | cmd2: '01', 855 | }; 856 | } 857 | } else { //eight button 858 | if (this.keypadbtn == 'A') { 859 | cmd = { 860 | cmd1: '19', 861 | cmd2: '00', 862 | }; 863 | } else { 864 | cmd = { 865 | cmd1: '19', 866 | cmd2: '01', 867 | }; 868 | } 869 | } 870 | 871 | this.log('Getting status for ' + this.name); 872 | 873 | this.hub.directCommand(this.id, cmd, timeout, (err, status)=> { 874 | if(err || status == null || typeof status === 'undefined' || typeof status.response === 'undefined' || typeof status.response.standard === 'undefined' || status.success == false){ 875 | this.log('Error getting power state of ' + this.name); 876 | } else { 877 | 878 | if (this.keypadbtn == 'ON' || (this.six_btn == false && this.keypadbtn == 'A') || (typeof this.six_btn === 'undefined' && this.keypadbtn == 'A')) { 879 | this.level = parseInt(status.response.standard.command2, 16); 880 | if (this.level > 0) { 881 | this.currentState = true; 882 | } else { 883 | this.currentState = false; 884 | } 885 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(this.currentState); 886 | this.lastUpdate = moment(); 887 | } 888 | 889 | const hexButtonMap = status.response.standard.command2; 890 | let binaryButtonMap = parseInt(hexButtonMap, 16).toString(2); 891 | binaryButtonMap = '00000000'.substr(binaryButtonMap.length) + binaryButtonMap; //pad to 8 digits 892 | this.buttonMap = binaryButtonMap; 893 | this.log.debug('Binary map: ' + this.buttonMap + ' (' + this.name + ')'); 894 | 895 | const decButtonMap = parseInt(binaryButtonMap, 2); 896 | const buttonNumber = buttonArray[this.keypadbtn]; 897 | let buttonState; 898 | 899 | if(decButtonMap & buttonNumber) { 900 | buttonState = true; 901 | } else { 902 | buttonState = false; 903 | } 904 | this.log.debug('Button ' + this.keypadbtn + ' state is ' + ((buttonState == true) ? 'on' : 'off')); 905 | 906 | if (buttonState ==true) { 907 | this.currentState = true; 908 | this.level = 100; 909 | } else { 910 | this.currentState = false; 911 | this.level = 0; 912 | } 913 | 914 | this.log.debug(this.name + ' is ' + (this.currentState ? 'on' : 'off')); 915 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(this.currentState); 916 | if (this.dimmable) { 917 | this.service.getCharacteristic(this.platform.Characteristic.Brightness).updateValue(this.level); 918 | } 919 | this.lastUpdate = moment(); 920 | } 921 | }); 922 | } 923 | 924 | pollStatus() { 925 | const now = moment(); 926 | const lastUpdate = this.lastUpdate; 927 | const delta = now.diff(lastUpdate, 'seconds'); 928 | 929 | if (delta < this.refreshInterval) { 930 | clearTimeout(this.pollTimer); 931 | this.pollTimer = setTimeout(() => { 932 | this.pollStatus.call(this); 933 | }, 1000 * (this.refreshInterval - delta)); 934 | } else { 935 | 936 | this.log('Polling status for ' + this.name + '...'); 937 | 938 | this.platform.checkHubConnection(); 939 | 940 | switch (this.deviceType) { 941 | case 'lightbulb': 942 | case 'dimmer': 943 | this.getStatus.call(this); 944 | setTimeout(() => { 945 | this.pollStatus.call(this); 946 | }, (1000 * this.refreshInterval)); 947 | break; 948 | 949 | case 'switch': 950 | this.getStatus.call(this); 951 | setTimeout(() => { 952 | this.pollStatus.call(this); 953 | }, (1000 * this.refreshInterval)); 954 | break; 955 | 956 | case 'iolinc': 957 | this.getSensorStatus.call(this, ()=>{/*do nothing for now*/}); 958 | setTimeout(() => { 959 | this.pollStatus.call(this); 960 | }, (1000 * this.refreshInterval)); 961 | break; 962 | 963 | case 'scene': 964 | this.getSceneState.call(this); 965 | setTimeout(() => { 966 | this.pollStatus.call(this); 967 | }, (1000 * this.refreshInterval)); 968 | break; 969 | 970 | case 'fan': 971 | this.getFanState.call(this); 972 | setTimeout(() => { 973 | this.pollStatus.call(this); 974 | }, (1000 * this.refreshInterval)); 975 | break; 976 | 977 | case 'outlet': 978 | this.getOutletState.call(this); 979 | setTimeout(() => { 980 | this.pollStatus.call(this); 981 | }, (1000 * this.refreshInterval)); 982 | break; 983 | } 984 | } 985 | } 986 | 987 | getOutletState() { 988 | const timeout = 0; 989 | const cmd = { 990 | cmd1: '19', 991 | cmd2: '01', 992 | }; 993 | 994 | this.platform.checkHubConnection(); 995 | 996 | this.log('Getting power state for ' + this.name); 997 | 998 | this.hub.directCommand(this.id, cmd, timeout, (error, status) => { 999 | if(error || typeof status === 'undefined' || typeof status.response === 'undefined' || typeof status.response.standard === 'undefined' || status.success == false){ 1000 | this.log('Error getting power state of ' + this.name); 1001 | return; 1002 | } 1003 | 1004 | //this.log.debug('Outlet status: ' + util.inspect(status)) 1005 | 1006 | if (status.success) { 1007 | const command2 = status.response.standard.command2.substr(1, 1); //0 = both off, 1 = top on, 2 = bottom on, 3 = both on 1008 | 1009 | switch (command2) { 1010 | case '0': 1011 | this.currentState = false; 1012 | break; 1013 | 1014 | case '1': 1015 | if(this.position =='bottom'){ 1016 | this.currentState = false; 1017 | } else { 1018 | this.currentState = true; 1019 | } 1020 | break; 1021 | 1022 | case '2': 1023 | if(this.position =='bottom'){ 1024 | this.currentState = true; 1025 | } else { 1026 | this.currentState = false; 1027 | } 1028 | break; 1029 | 1030 | case '3': 1031 | this.currentState = true; 1032 | break; 1033 | } 1034 | 1035 | this.log.debug(this.name + ' is ' + (this.currentState ? 'on' : 'off')); 1036 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(this.currentState); 1037 | this.lastUpdate = moment(); 1038 | } 1039 | }); 1040 | } 1041 | 1042 | getFanState() { 1043 | let currentState; 1044 | 1045 | this.platform.checkHubConnection(); 1046 | 1047 | this.log('Getting state for ' + this.name); 1048 | 1049 | this.light.fan((error, state) => { 1050 | if(error || typeof state === 'undefined'){ 1051 | this.log('Error getting power state of ' + this.name); 1052 | return; 1053 | } else { 1054 | switch(state) { 1055 | case 'off': 1056 | this.currentState = false; 1057 | this.level = 0; 1058 | 1059 | break; 1060 | 1061 | case 'low': 1062 | this.currentState = true; 1063 | this.level = 33; 1064 | 1065 | break; 1066 | 1067 | case 'medium': 1068 | this.currentState = true; 1069 | this.level = 66; 1070 | 1071 | break; 1072 | 1073 | case 'high': 1074 | this.currentState = true; 1075 | this.level = 100; 1076 | 1077 | break; 1078 | } 1079 | 1080 | this.log.debug(this.name + ' is ' + (this.currentState ? 'on' : 'off') + ' at ' + this.level + ' %'); 1081 | 1082 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(this.currentState); 1083 | this.service.getCharacteristic(this.platform.Characteristic.RotationSpeed).updateValue(this.level); //value from 1 to 100 1084 | } 1085 | }); 1086 | } 1087 | 1088 | getSensorStatus(callback) { 1089 | this.platform.checkHubConnection(); 1090 | 1091 | this.log.info('Getting sensor state for ' + this.name); 1092 | 1093 | this.iolinc.status((error, status)=> { 1094 | if (error || status == null || typeof status === 'undefined' || typeof status.sensor === 'undefined') { 1095 | return; 1096 | } else { 1097 | this.log.debug('Invert sensor: ' + this.invert_sensor); 1098 | if(this.invert_sensor == 'false' || this.invert_sensor == false) { 1099 | this.currentState = (status.sensor == 'on') ? true : false; 1100 | } else if(this.invert_sensor == true || this.invert_sensor == 'true') { 1101 | this.currentState = (status.sensor == 'off') ? true : false; 1102 | } 1103 | 1104 | this.log.debug(this.name + ' sensor is ' + status.sensor + ', currentState: ' + this.currentState); 1105 | 1106 | if (this.deviceType == 'iolinc') { 1107 | this.service.getCharacteristic(this.platform.Characteristic.TargetDoorState).updateValue(this.currentState); 1108 | this.service.getCharacteristic(this.platform.Characteristic.CurrentDoorState).updateValue(this.currentState); 1109 | } else { 1110 | ( 1111 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(this.currentState) 1112 | ); 1113 | } 1114 | 1115 | this.lastUpdate = moment(); 1116 | callback(); 1117 | } 1118 | }); 1119 | } 1120 | 1121 | identify() { 1122 | this.log.debug('Sending beep command to ' + this.name); 1123 | this.hub.directCommand(this.id, '30'); 1124 | } 1125 | 1126 | setFanState (level) { 1127 | if(this.disabled){ 1128 | this.log.debug('Device ' + this.name + ' is disabled'); 1129 | return; 1130 | } 1131 | 1132 | this.platform.checkHubConnection(); 1133 | 1134 | this.hub.cancelPending(this.id); 1135 | 1136 | const now = moment(); 1137 | let delta; 1138 | 1139 | if(typeof this.lastCommand === 'undefined'){ 1140 | this.lastCommand = now; 1141 | delta = -1; 1142 | } else { 1143 | delta = now.diff(this.lastCommand, 'milliseconds'); 1144 | } 1145 | 1146 | const debounceTimer = 600; 1147 | 1148 | if (level == this.currentState) { 1149 | this.log.debug('Discard on, already at commanded state'); 1150 | return; 1151 | } else if (level === true && delta >= 0 && delta <= 50) { 1152 | this.log.debug('Discard on, sent too close to dim'); 1153 | return; 1154 | } 1155 | 1156 | this.lastCommand = now; 1157 | 1158 | clearTimeout(this.levelTimeout); 1159 | 1160 | const setFanLevel = (level ) =>{ 1161 | let targetLevel; 1162 | 1163 | if(typeof level === 'number'){ 1164 | this.level = level; 1165 | 1166 | if (level == 0){ 1167 | targetLevel = 'off'; 1168 | } else if (level <= 33) { 1169 | targetLevel = 'low'; 1170 | } else if (level > 66) { 1171 | targetLevel = 'high'; 1172 | } else { 1173 | targetLevel = 'medium'; 1174 | } 1175 | } else if(typeof level === 'boolean'){ 1176 | if (level == false){ 1177 | targetLevel = 'off'; 1178 | this.level = 0; 1179 | } else if (level == true){ 1180 | targetLevel = 'on'; 1181 | this.level = 100; 1182 | } 1183 | } 1184 | 1185 | this.log('Setting speed of ' + this.name + ' to ' + targetLevel + ' (' + level + '%)'); 1186 | 1187 | this.light.fan(targetLevel).then((status)=> { 1188 | //this.log.debug('Status: ' + util.inspect(status)) 1189 | if (status.success) { 1190 | this.service.getCharacteristic(this.platform.Characteristic.RotationSpeed).updateValue(this.level); 1191 | 1192 | if (this.level > 0) { 1193 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(true); 1194 | this.currentState = true; 1195 | } else if (this.level == 0) { 1196 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(false); 1197 | this.currentState = false; 1198 | } 1199 | 1200 | this.log.debug(this.name + ' is ' + (this.currentState ? 'on' : 'off') + ' at ' + targetLevel + ' (' + level + '%)'); 1201 | this.lastUpdate = moment(); 1202 | return; 1203 | 1204 | } else { 1205 | this.log('Error setting level of ' + this.name); 1206 | return; 1207 | } 1208 | }); 1209 | }; 1210 | 1211 | this.levelTimeout = setTimeout(()=> { 1212 | setFanLevel(level); 1213 | }, debounceTimer); 1214 | 1215 | return; 1216 | } 1217 | 1218 | setPowerState(state) { 1219 | const powerOn = state ? 'on' : 'off'; 1220 | 1221 | if(this.disabled){ 1222 | this.log.debug('Device ' + this.name + ' is disabled'); 1223 | return; 1224 | } 1225 | 1226 | this.platform.checkHubConnection(); 1227 | 1228 | if (state !== this.currentState) { 1229 | this.log('Setting power state of ' + this.name + ' to ' + powerOn); 1230 | if (state) { 1231 | setTimeout(()=> { 1232 | this.light.turnOn().then((status) => { 1233 | setTimeout(()=> { 1234 | if (status.success) { //if command actually worked, do nothing 1235 | //do nothing 1236 | } else { //if command didn't work, check status to see what happened and update homekit 1237 | this.log('Error setting power state of ' + this.name + ' to ' + powerOn); 1238 | this.getStatus.call(this); 1239 | } 1240 | }, 0); 1241 | }); 1242 | }, 800); 1243 | 1244 | //assume that the command worked and report back to homekit 1245 | if (this.dimmable) { 1246 | this.service.getCharacteristic(this.platform.Characteristic.Brightness).updateValue(100); 1247 | } 1248 | this.level = 100; 1249 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(true); 1250 | this.currentState = true; 1251 | this.lastUpdate = moment(); 1252 | 1253 | this.getGroupMemberStatus(); 1254 | 1255 | //Check if any target keypad button(s) to process 1256 | if(this.targetKeypadID.length > 0){ 1257 | this.log.debug(this.targetKeypadID.length + ' target keypad(s) found for ' + this.name); 1258 | 1259 | for(let temp = 0; temp < this.targetKeypadID.length; temp++){ 1260 | this.log.debug(' targetKeypadID[' + temp + '] = ' + '[' + this.targetKeypadID[temp] + ']'); 1261 | } 1262 | 1263 | let count; 1264 | 1265 | for(count = 0; count < this.targetKeypadID.length; count++){ 1266 | //this.log.debug(' count = ' + count) 1267 | 1268 | this.setTargetKeypadCount = count; 1269 | 1270 | //Async-Wait function to insure multiple keypads are processed in order 1271 | const run = async () => { 1272 | const promise = new Promise((resolve, reject) => this.setTargetKeypadBtn.call(this)); 1273 | const result = await promise; // wait until the promise resolves 1274 | return; // "done!" 1275 | }; 1276 | 1277 | run(); 1278 | } 1279 | } 1280 | return; 1281 | } else { 1282 | this.light.turnOff().then((status) => { 1283 | setTimeout(()=> { 1284 | if (status.success) { //if command actually worked, do nothing 1285 | //do nothing 1286 | } else { //if command didn't work, check status to see what happened and update homekit 1287 | this.log('Error setting power state of ' + this.name + ' to ' + powerOn); 1288 | this.getStatus.call(this); 1289 | } 1290 | }, 0); 1291 | }); 1292 | 1293 | if (this.dimmable) { 1294 | this.service.getCharacteristic(this.platform.Characteristic.Brightness).updateValue(0); 1295 | } 1296 | this.level = 0; 1297 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(false); 1298 | this.currentState = false; 1299 | this.lastUpdate = moment(); 1300 | 1301 | this.getGroupMemberStatus(); 1302 | 1303 | //Check if any target keypad button(s) to process 1304 | if(this.targetKeypadID.length > 0){ 1305 | this.log.debug(this.targetKeypadID.length + ' target keypad(s) found for ' + this.name); 1306 | 1307 | for(let temp = 0; temp < this.targetKeypadID.length; temp++){ 1308 | this.log.debug(' targetKeypadID[' + temp + '] = ' + '[' + this.targetKeypadID[temp] + ']'); 1309 | } 1310 | 1311 | let count: number; 1312 | 1313 | for(count = 0; count < this.targetKeypadID.length; count++){ 1314 | //this.log.debug(' count = ' + count) 1315 | 1316 | this.setTargetKeypadCount = count; 1317 | 1318 | //Async-Wait function to insure multiple keypads are processed in order 1319 | const run = async () => { 1320 | const promise = new Promise((resolve, reject) => this.setTargetKeypadBtn.call(this)); 1321 | const result = await promise; // wait until the promise resolves 1322 | return; // "done!" 1323 | }; 1324 | 1325 | run(); 1326 | } 1327 | } 1328 | return; 1329 | } 1330 | } else { 1331 | this.currentState = state; 1332 | this.lastUpdate = moment(); 1333 | return; 1334 | } 1335 | } 1336 | 1337 | setTargetKeypadBtn (callback?) { 1338 | const timeout = 0; 1339 | 1340 | const eight_buttonArray = { 1341 | 'A': 7, 1342 | 'B': 6, 1343 | 'C': 5, 1344 | 'D': 4, 1345 | 'E': 3, 1346 | 'F': 2, 1347 | 'G': 1, 1348 | 'H': 0, 1349 | }; 1350 | 1351 | const six_buttonArray = { 1352 | 'A': eight_buttonArray['C'], 1353 | 'B': eight_buttonArray['D'], 1354 | 'C': eight_buttonArray['E'], 1355 | 'D': eight_buttonArray['F'], 1356 | }; 1357 | 1358 | const four_buttonArray = { 1359 | '1': eight_buttonArray['A'], 1360 | '2': eight_buttonArray['B'], 1361 | '3': eight_buttonArray['C'], 1362 | '4': eight_buttonArray['D'], 1363 | }; 1364 | 1365 | let buttonArray; 1366 | const index1 = this.setTargetKeypadCount; 1367 | 1368 | this.log(' also setting target keypad [' + this.targetKeypadID[index1] + '] button [' + this.targetKeypadBtn[index1] + '] to ' + this.currentState); 1369 | //this.log.debug(' index1 = ' + index1) 1370 | 1371 | this.platform.checkHubConnection(); 1372 | 1373 | const getButtonMap = (callback) => { 1374 | const command = { 1375 | cmd1: '19', 1376 | cmd2: '01', 1377 | }; 1378 | 1379 | //this.log.debug(' index1 = ' + index1) 1380 | this.log.debug(' Reading button map for target keypad [' + this.targetKeypadID[index1] + ']'); 1381 | 1382 | this.hub.directCommand(this.targetKeypadID[index1], command, timeout, (err, status)=> { 1383 | if(err || status == null || typeof status === 'undefined' || typeof status.response === 'undefined' 1384 | || typeof status.response.standard === 'undefined' || status.success == false){ 1385 | this.log('Error getting button states for target keypad [' + this.targetKeypadID[index1] + ']'); 1386 | this.log.debug('Err: ' + util.inspect(err)); 1387 | return; 1388 | } else { 1389 | const hexButtonMap = status.response.standard.command2; 1390 | let binaryButtonMap = parseInt(hexButtonMap, 16).toString(2); 1391 | binaryButtonMap = '00000000'.substr(binaryButtonMap.length) + binaryButtonMap; //pad to 8 digits 1392 | this.buttonMap = binaryButtonMap; 1393 | 1394 | this.log.debug(' Current button map: ' + binaryButtonMap); 1395 | //this.log.debug(' index1 = ' + index1) 1396 | 1397 | callback(); 1398 | } 1399 | }); 1400 | }; 1401 | 1402 | getButtonMap(()=> { 1403 | const currentButtonMap = this.buttonMap; //binary button states from getButtonMap 1404 | 1405 | //this.log.debug(' index1 = ' + index1) 1406 | 1407 | if(this.targetKeypadSixBtn[index1] == true){ 1408 | buttonArray = six_buttonArray; 1409 | this.log.debug(' Using 6-button keypad layout'); 1410 | } else if(this.targetKeypadFourBtn[index1] == true){ 1411 | buttonArray = four_buttonArray; 1412 | this.log.debug(' Using 4-button keypad layout'); 1413 | }else { 1414 | buttonArray = eight_buttonArray; 1415 | this.log.debug(' Using 8-button keypad layout'); 1416 | } 1417 | 1418 | const buttonNumber = buttonArray[this.targetKeypadBtn[index1]]; 1419 | 1420 | this.log.debug(' Target button: ' + this.targetKeypadBtn[index1]); 1421 | this.log.debug(' Button number: ' + buttonNumber); 1422 | 1423 | const binaryButtonMap = currentButtonMap.substring(0, buttonNumber) + 1424 | (this.currentState ? '1' : '0') + 1425 | currentButtonMap.substring(buttonNumber+1); 1426 | 1427 | this.log.debug(' New binary button map: ' + binaryButtonMap); 1428 | 1429 | const buttonMap = ('00'+parseInt(binaryButtonMap, 2).toString(16)).substr(-2).toUpperCase(); 1430 | 1431 | //this.log.debug(' New hex value: ' + buttonMap) 1432 | 1433 | const cmd = { 1434 | cmd1: '2E', 1435 | cmd2: '00', 1436 | extended: true, 1437 | userData: ['01', '09', buttonMap], 1438 | isStandardResponse: true, 1439 | }; 1440 | 1441 | this.hub.directCommand(this.targetKeypadID[index1], cmd, timeout, (err, status)=> { 1442 | 1443 | //this.log.debug(' index1 = ' + index1) 1444 | 1445 | if(err || status == null || typeof status === 'undefined' || typeof status.response === 'undefined' 1446 | || typeof status.response.standard === 'undefined' || status.success == false){ 1447 | 1448 | this.log('Error setting button state of target keypad [' + this.targetKeypadID[index1] + ']'); 1449 | this.log.debug('Err: ' + util.inspect(err)); 1450 | 1451 | if (typeof callback !== 'undefined') { 1452 | callback(err); 1453 | return 1; 1454 | } else { 1455 | return 1; 1456 | } 1457 | } else { 1458 | this.lastUpdate = moment(); 1459 | this.buttonMap = binaryButtonMap; 1460 | 1461 | if (typeof callback !== 'undefined') { 1462 | callback(null); 1463 | return 1; 1464 | } else { 1465 | return 1; 1466 | } 1467 | } 1468 | }); 1469 | 1470 | }); 1471 | } 1472 | 1473 | getGroupMemberStatus (){ 1474 | const deviceIDs = this.platform.deviceIDs; 1475 | 1476 | if(typeof this.groupMembers === 'undefined') { 1477 | this.log.debug('No group members defined for ' + this.name); 1478 | return; 1479 | } 1480 | 1481 | this.groupMembers.forEach((deviceID) =>{ 1482 | if (/^[0-9a-fA-F]{6}$/.test(deviceID)){ //group member defined by id 1483 | const isDefined = _.contains(deviceIDs, deviceID.toUpperCase(), 0); 1484 | if(isDefined){ 1485 | const groupDevice = this.platform.insteonAccessories.filter((item) => { 1486 | return (item.id == deviceID.toUpperCase()); 1487 | }); 1488 | 1489 | const theGroupDevice = groupDevice[0]; 1490 | 1491 | this.log('Getting status of scene device ' + theGroupDevice.name); 1492 | this.log.debug('Group device type ' + theGroupDevice.deviceType); 1493 | //Add slight delay to get correct status and ensure device is not mid-dim 1494 | setTimeout(()=> { 1495 | theGroupDevice.getStatus.call(theGroupDevice); 1496 | }, 2000); 1497 | } 1498 | } else { //group member defined by name 1499 | this.log.debug('Group device is a name...'); 1500 | const namedDev = this.platform.insteonAccessories.filter((item) => { 1501 | return (item.name == deviceID); 1502 | }); 1503 | 1504 | const theNamedDev = namedDev[0]; 1505 | this.log.debug('Found matching device with id ' + theNamedDev.id); 1506 | this.log('Getting status of scene device ' + theNamedDev.name); 1507 | setTimeout(()=> { 1508 | theNamedDev.getStatus.call(theNamedDev); 1509 | }, 2000); 1510 | } 1511 | }); 1512 | } 1513 | 1514 | setSceneState (state) { 1515 | const powerOn = state ? 'on' : 'off'; 1516 | const groupID = parseInt(this.groupID); 1517 | 1518 | if(this.disabled){ 1519 | this.log.debug('Device ' + this.name + ' is disabled'); 1520 | return; 1521 | } 1522 | 1523 | this.platform.checkHubConnection(); 1524 | 1525 | this.log('Setting power state of ' + this.name + ' to ' + powerOn); 1526 | 1527 | if (state) { 1528 | this.hub.sceneOn(groupID).then((status) => { 1529 | if (status.completed) { 1530 | this.level = 100; 1531 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(true); 1532 | this.currentState = true; 1533 | this.lastUpdate = moment(); 1534 | 1535 | this.getGroupMemberStatus(); 1536 | 1537 | this.log.debug ('Scene is set to momentary: ' + this.momentary); 1538 | if (this.momentary) { 1539 | setTimeout(()=> { 1540 | this.level = 0; 1541 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(false); 1542 | this.currentState = false; 1543 | }, 2000); 1544 | } 1545 | return; 1546 | } else { 1547 | this.log('Error setting power state of ' + this.name + ' to ' + powerOn); 1548 | return; 1549 | } 1550 | }); 1551 | } else { 1552 | this.hub.sceneOff(groupID).then((status) => { 1553 | if (status.completed) { 1554 | this.level = 0; 1555 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(false); 1556 | this.currentState = false; 1557 | this.lastUpdate = moment(); 1558 | 1559 | this.getGroupMemberStatus(); 1560 | 1561 | return; 1562 | } else { 1563 | this.log('Error setting power state of ' + this.name + ' to ' + powerOn); 1564 | return; 1565 | } 1566 | }); 1567 | } 1568 | } 1569 | 1570 | setRelayState(state) { 1571 | //0=open 1=close 1572 | if(this.disabled){ 1573 | this.log.debug('Device ' + this.name + ' is disabled'); 1574 | return; 1575 | } 1576 | 1577 | this.platform.checkHubConnection(); 1578 | 1579 | this.log('Setting ' + this.name + ' relay to ' + state); 1580 | 1581 | if (state !== this.currentState){ 1582 | this.iolinc.relayOn().then((status) =>{ 1583 | if (status.success) { 1584 | if (this.deviceType == 'iolinc') { 1585 | this.targetState = (this.currentState == 0) ? true : false; 1586 | this.log(' >>> New target state is ' + this.targetState + ((this.targetState == 0) ? ' (Open)' : ' (Closed)')); 1587 | return; 1588 | } else if (this.deviceType == 'valve') { 1589 | this.currentState = (this.currentState == 0) ? true : false; 1590 | this.service.getCharacteristic(this.platform.Characteristic.Active).updateValue(this.currentState); 1591 | this.service.getCharacteristic(this.platform.Characteristic.InUse).updateValue(this.currentState); 1592 | this.log('Setting ' + this.name + ' state to ' + this.currentState); 1593 | 1594 | setTimeout(() => { 1595 | this.getSensorStatus(() =>{ 1596 | this.lastUpdate = moment(); 1597 | this.service.getCharacteristic(this.platform.Characteristic.Active).updateValue(0); 1598 | this.service.getCharacteristic(this.platform.Characteristic.InUse).updateValue(0); 1599 | return; 1600 | }); 1601 | }, 1000 * this.valve_delay); 1602 | } 1603 | } else { 1604 | this.log('Error setting relay state of ' + this.name + ' to ' + state); 1605 | return; 1606 | } 1607 | }); 1608 | } else { 1609 | this.log(this.name + ' is already at commanded state'); 1610 | return; 1611 | } 1612 | } 1613 | 1614 | getPosition() { //get position for shades/blinds 1615 | 1616 | this.platform.checkHubConnection(); 1617 | 1618 | this.log('Getting status for ' + this.name); 1619 | 1620 | this.light.level((err, level) => { 1621 | if(err || level == null || typeof level === 'undefined'){ 1622 | this.log('Error getting power state of ' + this.name); 1623 | return; 1624 | } else { 1625 | 1626 | this.level = level; 1627 | this.log.debug(this.name + ' is set to ' + level + '%'); 1628 | 1629 | this.service.getCharacteristic(this.platform.Characteristic.CurrentPosition).updateValue(this.level); 1630 | this.service.getCharacteristic(this.platform.Characteristic.TargetPosition).updateValue(this.level); 1631 | this.lastUpdate = moment(); 1632 | 1633 | return; 1634 | } 1635 | }); 1636 | } 1637 | 1638 | setOutletState(state) { 1639 | const powerOn = state ? 'on' : 'off'; 1640 | 1641 | let cmd; 1642 | const timeout = 0; 1643 | 1644 | if(this.disabled){ 1645 | this.log.debug('Device ' + this.name + ' is disabled'); 1646 | return; 1647 | } 1648 | 1649 | this.platform.checkHubConnection(); 1650 | 1651 | this.log('Setting power state of ' + this.name + ' to ' + powerOn); 1652 | 1653 | if (state) { //state = true = on 1654 | if(this.position == 'bottom') { 1655 | cmd = { 1656 | extended: true, 1657 | cmd1: '11', 1658 | cmd2: 'FF', 1659 | userData: ['02'], 1660 | isStandardResponse: true, 1661 | }; 1662 | } else { 1663 | cmd = { 1664 | cmd1: '11', 1665 | cmd2: 'FF', 1666 | }; 1667 | } 1668 | 1669 | this.hub.directCommand(this.id, cmd, timeout, (error, status) => { 1670 | if(error || status == null || typeof status === 'undefined' || typeof status.response === 'undefined'){ 1671 | this.log('Error getting power state of ' + this.name); 1672 | this.log.debug('Error: ' + util.inspect(error)); 1673 | 1674 | return; 1675 | } else { 1676 | if (status.success) { 1677 | this.currentState = true; 1678 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(true); 1679 | this.lastUpdate = moment(); 1680 | 1681 | //Check if any target keypad button(s) to process 1682 | if(this.targetKeypadID.length > 0){ 1683 | this.log.debug(this.targetKeypadID.length + ' target keypad(s) found for ' + this.name); 1684 | 1685 | for(let temp = 0; temp < this.targetKeypadID.length; temp++){ 1686 | this.log.debug(' targetKeypadID[' + temp + '] = ' + '[' + this.targetKeypadID[temp] + ']'); 1687 | } 1688 | 1689 | let count; 1690 | 1691 | for(count = 0; count < this.targetKeypadID.length; count++){ 1692 | //this.log.debug(' count = ' + count) 1693 | 1694 | this.setTargetKeypadCount = count; 1695 | 1696 | //Async-Wait function to insure multiple keypads are processed in order 1697 | const run = async () => { 1698 | const promise = new Promise((resolve, reject) => this.setTargetKeypadBtn.call(this)); 1699 | const result = await promise; // wait until the promise resolves 1700 | return; // "done!" 1701 | }; 1702 | run(); 1703 | } 1704 | } 1705 | return; 1706 | } 1707 | } 1708 | }); 1709 | } else { //state = false = off 1710 | if(this.position == 'bottom') { 1711 | cmd = { 1712 | extended: true, 1713 | cmd1: '13', 1714 | cmd2: '00', 1715 | userData: ['02'], 1716 | isStandardResponse: true, 1717 | }; 1718 | } else { 1719 | cmd = { 1720 | cmd1: '13', 1721 | cmd2: '00', 1722 | }; 1723 | } 1724 | 1725 | this.hub.directCommand(this.id, cmd, timeout, (error, status) => { 1726 | if(error || status == null || typeof status === 'undefined' || typeof status.response === 'undefined'){ 1727 | this.log('Error getting power state of ' + this.name); 1728 | this.log.debug('Error: ' + util.inspect(error)); 1729 | return; 1730 | } else { 1731 | if (status.success) { 1732 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(false); 1733 | this.currentState = false; 1734 | this.lastUpdate = moment(); 1735 | 1736 | //Check if any target keypad button(s) to process 1737 | if(this.targetKeypadID.length > 0){ 1738 | this.log.debug(this.targetKeypadID.length + ' target keypad(s) found for ' + this.name); 1739 | 1740 | for(let temp = 0; temp < this.targetKeypadID.length; temp++){ 1741 | this.log.debug(' targetKeypadID[' + temp + '] = ' + '[' + this.targetKeypadID[temp] + ']'); 1742 | } 1743 | 1744 | let count; 1745 | 1746 | for(count = 0; count < this.targetKeypadID.length; count++){ 1747 | //this.log.debug(' count = ' + count) 1748 | 1749 | this.setTargetKeypadCount = count; 1750 | 1751 | //Async-Wait function to insure multiple keypads are processed in order 1752 | const run = async () => { 1753 | const promise = new Promise((resolve, reject) => this.setTargetKeypadBtn.call(this)); 1754 | const result = await promise; // wait until the promise resolves 1755 | return; // "done!" 1756 | }; 1757 | run(); 1758 | } 1759 | } 1760 | 1761 | return; 1762 | } 1763 | } 1764 | }); 1765 | } 1766 | } 1767 | 1768 | setKeypadState(state) { 1769 | if(this.disabled){ 1770 | this.log.debug('Device ' + this.name + ' is disabled'); 1771 | return; 1772 | } 1773 | 1774 | const timeout = 0; 1775 | const eight_buttonArray = { 1776 | 'A': 7, 1777 | 'B': 6, 1778 | 'C': 5, 1779 | 'D': 4, 1780 | 'E': 3, 1781 | 'F': 2, 1782 | 'G': 1, 1783 | 'H': 0, 1784 | }; 1785 | 1786 | const six_buttonArray = { 1787 | 'A': eight_buttonArray['C'], 1788 | 'B': eight_buttonArray['D'], 1789 | 'C': eight_buttonArray['E'], 1790 | 'D': eight_buttonArray['F'], 1791 | }; 1792 | 1793 | const four_buttonArray = { 1794 | '1': eight_buttonArray['A'], 1795 | '2': eight_buttonArray['B'], 1796 | '3': eight_buttonArray['C'], 1797 | '4': eight_buttonArray['D'], 1798 | }; 1799 | 1800 | let buttonArray; 1801 | 1802 | this.log('Setting state of ' + this.name + ' to ' + state); 1803 | 1804 | this.platform.checkHubConnection(); 1805 | 1806 | const getButtonMap = (callback) => { 1807 | const command = { 1808 | cmd1: '19', 1809 | cmd2: '01', 1810 | }; 1811 | 1812 | this.hub.directCommand(this.id, command, timeout, (err, status) =>{ 1813 | if(err || status == null || typeof status === 'undefined' || typeof status.response === 'undefined' || typeof status.response.standard === 'undefined' || status.success == false){ 1814 | this.log('Error getting power state of ' + this.name); 1815 | this.log.debug('Err: ' + util.inspect(err)); 1816 | return; 1817 | } else { 1818 | const hexButtonMap = status.response.standard.command2; 1819 | let binaryButtonMap = parseInt(hexButtonMap, 16).toString(2); 1820 | binaryButtonMap = '00000000'.substr(binaryButtonMap.length) + binaryButtonMap; //pad to 8 digits 1821 | 1822 | this.buttonMap = binaryButtonMap; 1823 | 1824 | this.log.debug('Binary map: ' + binaryButtonMap); 1825 | callback(); 1826 | } 1827 | }); 1828 | }; 1829 | 1830 | getButtonMap(() =>{ 1831 | const currentButtonMap = this.buttonMap; 1832 | console.log('Current: ' + currentButtonMap); 1833 | 1834 | if(this.six_btn == true){ 1835 | buttonArray = six_buttonArray; 1836 | } else if (this.four_btn == true){ 1837 | buttonArray = four_buttonArray; 1838 | } else { 1839 | buttonArray = eight_buttonArray; 1840 | } 1841 | 1842 | const buttonNumber = buttonArray[this.keypadbtn]; 1843 | console.log('button num: ' + buttonNumber); 1844 | const binaryButtonMap = currentButtonMap.substring(0, buttonNumber) + 1845 | (state ? '1' : '0') + 1846 | currentButtonMap.substring(buttonNumber+1); 1847 | console.log('New bin: ' + binaryButtonMap); 1848 | const buttonMap = ('00'+parseInt(binaryButtonMap, 2).toString(16)).substr(-2).toUpperCase(); 1849 | console.log('Hex: ' + buttonMap); 1850 | const cmd = { 1851 | cmd1: '2E', 1852 | cmd2: '00', 1853 | extended: true, 1854 | userData: ['01', '09', buttonMap], 1855 | isStandardResponse: true, 1856 | }; 1857 | 1858 | this.hub.directCommand(this.id, cmd, timeout, (err, status) =>{ 1859 | if(err || status == null || typeof status === 'undefined' || typeof status.response === 'undefined' || typeof status.response.standard === 'undefined' || status.success == false){ 1860 | this.log('Error setting state of ' + this.name); 1861 | this.log.debug('Err: ' + util.inspect(err)); 1862 | 1863 | return; 1864 | } else { 1865 | this.lastUpdate = moment(); 1866 | this.buttonMap = binaryButtonMap; 1867 | this.currentState = state; 1868 | 1869 | return; 1870 | } 1871 | }); 1872 | }); 1873 | } 1874 | 1875 | setX10PowerState(state) { 1876 | const powerOn = state ? 'on' : 'off'; 1877 | 1878 | if(this.disabled){ 1879 | this.log.debug('Device ' + this.name + ' is disabled'); 1880 | return; 1881 | } 1882 | 1883 | this.platform.checkHubConnection(); 1884 | 1885 | this.log('Setting power state of ' + this.name + ' to ' + powerOn); 1886 | if (state) { 1887 | this.light.turnOn(); 1888 | 1889 | //assume that the command worked and report back to homekit 1890 | this.level = 100; 1891 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(true); 1892 | this.currentState = true; 1893 | this.lastUpdate = moment(); 1894 | return; 1895 | } else { 1896 | this.light.turnOff(); 1897 | 1898 | this.level = 0; 1899 | this.service.getCharacteristic(this.platform.Characteristic.On).updateValue(false); 1900 | this.currentState = false; 1901 | this.lastUpdate = moment(); 1902 | 1903 | return; 1904 | } 1905 | } 1906 | 1907 | setPosition(level) { //get position for shades/blinds 1908 | const oldLevel = this.level; 1909 | 1910 | if(this.disabled){ 1911 | this.log.debug('Device ' + this.name + ' is disabled'); 1912 | return; 1913 | } 1914 | 1915 | this.platform.checkHubConnection(); 1916 | 1917 | this.hub.cancelPending(this.id); 1918 | 1919 | if (level > oldLevel){ 1920 | this.service.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(1); 1921 | } else { 1922 | this.service.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(0); 1923 | } 1924 | 1925 | this.log('Setting shades ' + this.name + ' to ' + level + '%'); 1926 | this.light.level(level).then((status) => { 1927 | if (status.success) { 1928 | this.level = level; 1929 | this.service.getCharacteristic(this.platform.Characteristic.CurrentPosition).updateValue(this.level); 1930 | 1931 | this.log.debug(this.name + ' is at ' + level + '%'); 1932 | this.lastUpdate = moment(); 1933 | this.service.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(2); 1934 | return; 1935 | } else { 1936 | this.log('Error setting level of ' + this.name); 1937 | return; 1938 | } 1939 | }); 1940 | } 1941 | 1942 | getThermostatStatus(){ 1943 | this.thermostat.status((err, status)=>{ 1944 | this.log('Getting thermostat ' + this.name + ' status'); 1945 | 1946 | if(err || status == null || !status || typeof(status) === 'undefined'){ 1947 | return new Error('Thermostat did not return status'); 1948 | } 1949 | this.log.debug('Status: ' + util.inspect(status)); 1950 | //get mode 1951 | if(status.mode == 'off'){ 1952 | this.service.getCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState).updateValue(0); 1953 | this.service.getCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState).updateValue(0); 1954 | this.mode = 'off'; 1955 | } else if(status.mode == 'heat'){ 1956 | this.service.getCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState).updateValue(1); 1957 | this.service.getCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState).updateValue(1); 1958 | this.mode = 'heat'; 1959 | } else { //cool 1960 | this.service.getCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState).updateValue(2); 1961 | this.service.getCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState).updateValue(2); 1962 | this.mode = 'cool'; 1963 | } 1964 | this.log.debug('[init]' + this.name + ' mode is ' + this.mode); 1965 | //get units 1966 | if(status.unit == 'C'){ 1967 | this.service.getCharacteristic(this.platform.Characteristic.TemperatureDisplayUnits).updateValue(0); 1968 | this.unit = 'C'; 1969 | } else { 1970 | this.service.getCharacteristic(this.platform.Characteristic.TemperatureDisplayUnits).updateValue(1); 1971 | this.unit = 'F'; 1972 | } 1973 | //get current temperature 1974 | if(status.temperature){ 1975 | if(status.unit == 'C'){ 1976 | this.currentTemp = status.temperature; 1977 | } else { 1978 | this.currentTemp = convertTemp('F', 'C', status.temperature); 1979 | } 1980 | this.service.getCharacteristic(this.platform.Characteristic.CurrentTemperature).updateValue(this.currentTemp); 1981 | } 1982 | //get target temperature 1983 | if(status.setpoints){ 1984 | if(this.mode == 'cool'){ 1985 | if(status.unit == 'C'){ 1986 | this.targetTemp = status.setpoints.cool; 1987 | } else { 1988 | this.targetTemp = convertTemp('F', 'C', status.setpoints.cool); 1989 | } 1990 | this.service.getCharacteristic(this.platform.Characteristic.TargetTemperature).updateValue(this.targetTemp); 1991 | } else if(this.mode == 'heat'){ 1992 | if(status.unit == 'C'){ 1993 | this.targetTemp = status.setpoints.heat; 1994 | } else { 1995 | this.targetTemp = convertTemp('F', 'C', status.setpoints.heat); 1996 | } 1997 | this.service.getCharacteristic(this.platform.Characteristic.TargetTemperature).updateValue(this.targetTemp); 1998 | } 1999 | } 2000 | }); 2001 | } 2002 | 2003 | async setTemperature(temp){ 2004 | if(this.disabled){ 2005 | this.log.debug('Device ' + this.name + ' is disabled'); 2006 | return; 2007 | } 2008 | 2009 | let theTemp; 2010 | 2011 | if(this.unit == 'F'){ 2012 | theTemp = Math.round(convertTemp('C', 'F', temp)); 2013 | } else { 2014 | theTemp = Math.round(temp); 2015 | } 2016 | 2017 | this.log('Setting ' + this.name + ' temperature to ' + theTemp); 2018 | 2019 | this.platform.checkHubConnection(); 2020 | this.lastUpdate = moment(); 2021 | 2022 | this.mode = await this.getThermostatMode(); 2023 | this.log.debug(this.name + ' mode is ' + this.mode); 2024 | 2025 | if(this.mode == 'heat') { 2026 | this.thermostat.heatTemp(theTemp, (err, response)=>{ 2027 | if(err || !response || typeof(response) === 'undefined'){ 2028 | return new Error('Thermostat did not return status'); 2029 | } 2030 | this.log.debug(util.inspect(response)); 2031 | 2032 | this.service.getCharacteristic(this.platform.Characteristic.TargetTemperature).updateValue(temp); 2033 | this.targetTemp = temp; 2034 | }); 2035 | } else { 2036 | this.thermostat.coolTemp(theTemp, (err, response)=>{ 2037 | if(err || !response || typeof(response) === 'undefined'){ 2038 | return new Error('Thermostat did not return status'); 2039 | } 2040 | this.log.debug(util.inspect(response)); 2041 | 2042 | this.service.getCharacteristic(this.platform.Characteristic.TargetTemperature).updateValue(temp); 2043 | this.targetTemp = temp; 2044 | }); 2045 | } 2046 | } 2047 | 2048 | getTemperature(){ 2049 | if(this.disabled){ 2050 | this.log.debug('Device ' + this.name + ' is disabled'); 2051 | return; 2052 | } 2053 | 2054 | this.log('Getting temperature of ' + this.name); 2055 | 2056 | this.platform.checkHubConnection(); 2057 | this.lastUpdate = moment(); 2058 | 2059 | this.thermostat.temp((temp)=>{ 2060 | if(!temp || typeof(temp) === 'undefined'){ 2061 | return new Error('Thermostat did not return current temp'); 2062 | } 2063 | 2064 | if(this.unit == 'C'){ 2065 | this.currentTemp = temp; 2066 | } else { 2067 | this.currentTemp = convertTemp('F', 'C', temp); 2068 | } 2069 | 2070 | this.service.getCharacteristic(this.platform.Characteristic.CurrentTemperature).updateValue(this.currentTemp); 2071 | }); 2072 | 2073 | 2074 | } 2075 | 2076 | setThermostatMode(mode){ 2077 | if(this.disabled){ 2078 | this.log.debug('Device ' + this.name + ' is disabled'); 2079 | return; 2080 | } 2081 | 2082 | this.platform.checkHubConnection(); 2083 | this.log.debug('Set ' + this.name + ' mode to ' + mode); 2084 | if(mode == 0) { 2085 | mode = 'off'; 2086 | } else { 2087 | mode == 1 ? mode='heat' : mode='cool'; 2088 | } 2089 | 2090 | this.lastUpdate = moment(); 2091 | this.thermostat.mode(mode, (err, response)=>{ 2092 | if(err || response == null || !response || typeof(response) === 'undefined'){ 2093 | return new Error('Thermostat did not return status'); 2094 | } 2095 | this.log.debug('Set ' + this.name + ' mode to ' + mode); 2096 | }); 2097 | } 2098 | 2099 | getThermostatMode() { 2100 | return this.thermostat.mode((err, mode)=>{ 2101 | if(err || !mode || typeof(mode) === 'undefined'){ 2102 | return new Error('Thermostat did not return current mode'); 2103 | } 2104 | 2105 | this.log.debug('Thermostat ' + this.name + ' mode is ' + mode); 2106 | return mode; 2107 | }); 2108 | } 2109 | } --------------------------------------------------------------------------------