Compare commits

..

71 Commits

Author SHA1 Message Date
Serge Wagener
61dd132fad
Merge pull request #21 from Foxi352/dependabot/npm_and_yarn/ws-8.17.1
Bump ws from 8.12.1 to 8.17.1
2024-06-18 07:43:46 +02:00
Serge Wagener
a13242328e
Merge pull request #20 from Foxi352/dependabot/npm_and_yarn/braces-3.0.3
Bump braces from 3.0.2 to 3.0.3
2024-06-18 07:43:28 +02:00
dependabot[bot]
03618c6362
Bump ws from 8.12.1 to 8.17.1
Bumps [ws](https://github.com/websockets/ws) from 8.12.1 to 8.17.1.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.12.1...8.17.1)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-18 05:34:44 +00:00
dependabot[bot]
867a1ebbe3
Bump braces from 3.0.2 to 3.0.3
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-18 04:53:12 +00:00
Serge Wagener
6cb8f72028
Merge pull request #19 from Foxi352/dependabot/npm_and_yarn/ip-1.1.9
Bump ip from 1.1.8 to 1.1.9
2024-02-22 08:26:18 +01:00
dependabot[bot]
4eba434852
Bump ip from 1.1.8 to 1.1.9
Bumps [ip](https://github.com/indutny/node-ip) from 1.1.8 to 1.1.9.
- [Commits](https://github.com/indutny/node-ip/compare/v1.1.8...v1.1.9)

---
updated-dependencies:
- dependency-name: ip
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-22 02:09:05 +00:00
Serge Wagener
568cc551fb
Merge pull request #18 from Foxi352/dependabot/npm_and_yarn/xml2js-and-homebridge/dbus-native-0.5.0
Bump xml2js and @homebridge/dbus-native
2023-11-02 11:22:54 +01:00
dependabot[bot]
89edac94e0
Bump xml2js and @homebridge/dbus-native
Bumps [xml2js](https://github.com/Leonidas-from-XIV/node-xml2js) and [@homebridge/dbus-native](https://github.com/homebridge/dbus-native). These dependencies needed to be updated together.

Updates `xml2js` from 0.4.23 to 0.5.0
- [Commits](https://github.com/Leonidas-from-XIV/node-xml2js/commits/0.5.0)

Updates `@homebridge/dbus-native` from 0.5.0 to 0.5.1
- [Release notes](https://github.com/homebridge/dbus-native/releases)
- [Commits](https://github.com/homebridge/dbus-native/compare/v0.5.0...v0.5.1)

---
updated-dependencies:
- dependency-name: xml2js
  dependency-type: indirect
- dependency-name: "@homebridge/dbus-native"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-02 09:38:01 +00:00
Serge Wagener
a8e1812d9f
Merge pull request #17 from Foxi352/dependabot/npm_and_yarn/word-wrap-1.2.4
Bump word-wrap from 1.2.3 to 1.2.4
2023-11-02 10:37:34 +01:00
dependabot[bot]
3ac1dbf683
Bump word-wrap from 1.2.3 to 1.2.4
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-19 09:53:09 +00:00
Serge Wagener
4971bf389a Updated packages 2023-02-14 17:39:03 +01:00
Serge Wagener
d3e9629ec0
Merge pull request #16 from Foxi352/dependabot/npm_and_yarn/http-cache-semantics-4.1.1
Bump http-cache-semantics from 4.1.0 to 4.1.1
2023-02-14 17:21:42 +01:00
dependabot[bot]
f49839b39f
Bump http-cache-semantics from 4.1.0 to 4.1.1
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-04 06:47:54 +00:00
Serge Wagener
64e65d16ae Downgraded homebridge to 1.3.9 because of vuln. 2022-05-16 18:44:57 +02:00
Serge Wagener
d3a5407f29 Merge 2022-05-16 18:37:32 +02:00
Serge Wagener
d56a3a98e9 Added decimals to WindowCov., Plugin now verified 2022-05-16 18:35:15 +02:00
Serge Wagener
ed016e28c6
Merge pull request #15 from Foxi352/dependabot/npm_and_yarn/minimist-1.2.6
Bump minimist from 1.2.5 to 1.2.6
2022-05-16 16:51:37 +02:00
dependabot[bot]
0a6eb90eb7
Bump minimist from 1.2.5 to 1.2.6
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-01 21:16:05 +00:00
Serge Wagener
43e3fdf35f Remover "homebridge verified" until official approval 2022-03-18 08:24:17 +01:00
Serge Wagener
d07d621e45 Added check for empty accessories list 2022-03-18 08:23:38 +01:00
Serge Wagener
c8d117a894 Changed badge order 2022-03-17 16:45:58 +01:00
Serge Wagener
9a6c9e681d Links to 3rd party installation instructions 2022-03-16 17:41:14 +01:00
Serge Wagener
0394d789f8 Plugin now verified by homebridge dev's 2022-03-15 22:00:46 +01:00
Serge Wagener
84ed5cc74a Removed unneeded import 2022-03-15 16:40:24 +01:00
Serge Wagener
59fe9963a1 Merge branch 'master' of github.com:Foxi352/homebridge-smarthomeng 2022-03-15 16:39:23 +01:00
Serge Wagener
0beada795f
Update README.md
Added fan rotation speed to doc
2022-03-15 07:38:07 +01:00
Serge Wagener
e2d30353fb
Update Fan.ts 2022-03-15 07:36:22 +01:00
Serge Wagener
1a2e5c051d
Merge pull request #14 from thomas-kaltenbach/master
Add rotationspeed as fan charasteristics
2022-03-15 07:34:13 +01:00
Thomas Kaltenbach
5f0b025e25 Add rotationspped as charasteristics 2022-03-13 19:06:30 +01:00
Serge Wagener
f4700332cc Preparing for v2.0.5 release 2022-03-08 16:51:24 +01:00
Serge Wagener
323c313f3c Code formating 2022-03-08 16:51:01 +01:00
Serge Wagener
ae63f6cdee
Merge pull request #13 from chester4444/master
Added GarageDoor and HumiditySensor
2022-03-08 16:27:23 +01:00
Serge Wagener
bbc42dfb7e Added config section link 2022-03-08 16:24:52 +01:00
Chester
c6f596e9e5 Merge branch 'master' of https://github.com/chester4444/homebridge-smarthomeng 2022-03-08 15:15:19 +01:00
Chester
3561e0c3d6 added humidity sensor 2022-03-08 15:13:53 +01:00
Chester
447e0a68ef
added GarageDoor and HumiditySensor 2022-03-08 14:18:35 +01:00
Chester
6f373379ae
Adding humidity sensor 2022-03-07 21:12:55 +01:00
Chester
6b46c9cdb8
Added garage door description 2022-03-07 17:11:36 +01:00
Chester
77df0609ca garage door added 2022-03-07 16:44:43 +01:00
Chester
abe95f516f added garagedoor 2022-03-07 16:30:32 +01:00
Serge Wagener
a3e7b85f17 Added badges to README 2022-02-20 10:45:25 +01:00
Serge Wagener
ba2505853c Preparing for v2.0.4 release 2022-02-19 13:58:11 +01:00
Serge Wagener
e0611aed2c Sort accessories in README alphabetically 2022-02-19 13:39:05 +01:00
Serge Wagener
9a1709591f Fixed package.json 2022-02-19 13:36:38 +01:00
Serge Wagener
a03b5f787b Added SecuritySystem 2022-02-19 12:23:38 +01:00
Serge Wagener
de26802e1d Corrected motion sensor example 2022-02-16 10:33:09 +01:00
Serge Wagener
525f250f32 First beta v2.0.1 published 2022-02-15 13:52:11 +01:00
Serge Wagener
ff5b1f37b9 Added tilt angle support 2022-02-15 13:44:20 +01:00
Serge Wagener
f7947cdc2a Publishing beta 2022-02-15 13:22:08 +01:00
Serge Wagener
0cfb5d3b66 Added lightbulb 2022-02-15 12:33:20 +01:00
Serge Wagener
c66e843fc8 Added thermostat 2022-02-15 12:22:19 +01:00
Serge Wagener
a7a266d470 Added switch 2022-02-15 12:01:56 +01:00
Serge Wagener
7bbc75bf6b Added outlet 2022-02-15 11:45:44 +01:00
Serge Wagener
660fd07e2e Typo corrections 2022-02-15 11:41:13 +01:00
Serge Wagener
3248262e7a Working on README 2022-02-15 11:30:45 +01:00
Serge Wagener
60545b2909 Adding log line on setting items 2022-02-11 09:07:51 +01:00
Serge Wagener
bd2c931eaa Added transposing debug message on lightbulb 2022-02-10 18:44:40 +01:00
Serge Wagener
63ee296462 Removed -dev suffix 2022-02-10 18:19:41 +01:00
Serge Wagener
2d45bc6e3d Working on UI config 2022-02-10 18:15:59 +01:00
Serge Wagener
f9ae224ddc renamed package 2022-02-10 16:00:07 +01:00
Serge Wagener
995eab7d20 Fixed type in import 2022-02-10 15:47:18 +01:00
Serge Wagener
b2a287934c Added TLS support 2022-02-10 14:46:12 +01:00
Serge Wagener
0167799c54 Make code more readable 2022-02-10 13:46:47 +01:00
Serge Wagener
f6450f9463 Small cosmetics 2022-02-10 12:57:30 +01:00
Serge Wagener
b95352b05b Doorbell refinement, working on README 2022-02-10 12:01:24 +01:00
Serge Wagener
a8c5b1b9e6 Added ContactSensor 2022-02-09 20:21:42 +01:00
Serge Wagener
9c360dff2d Changed some logs from error to debug 2022-02-09 20:03:17 +01:00
Serge Wagener
6eceb78843 Added WindowCovering 2022-02-09 20:02:31 +01:00
Serge Wagener
7c5f4dd552 Working on WindowCovering 2022-02-08 20:32:40 +01:00
Serge Wagener
37eba7b60b Some cleanup 2022-02-08 17:47:24 +01:00
Serge Wagener
b902603047 First v2 dev commit 2022-02-08 17:01:36 +01:00
37 changed files with 8259 additions and 923 deletions

38
.eslintrc Normal file
View File

@ -0,0 +1,38 @@
{
"parser": "@typescript-eslint/parser",
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended" // uses the recommended rules from the @typescript-eslint/eslint-plugin
],
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"ignorePatterns": [
"dist"
],
"rules": {
"quotes": ["warn", "single", { "avoidEscape": true }],
"indent": ["warn", 4, { "SwitchCase": 1 }],
"semi": ["off"],
"comma-dangle": ["warn", "always-multiline"],
"dot-notation": "off",
"eqeqeq": "warn",
"curly": ["warn", "all"],
"brace-style": ["warn"],
"prefer-arrow-callback": ["warn"],
"max-len": ["warn", 140],
"no-console": ["warn"], // use the provided Homebridge log method instead
"no-non-null-assertion": ["off"],
"comma-spacing": ["error"],
"no-multi-spaces": ["warn", { "ignoreEOLComments": true }],
"no-trailing-spaces": ["warn"],
"lines-between-class-members": ["warn", "always", {"exceptAfterSingleLine": true}],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/semi": ["warn"],
"@typescript-eslint/member-delimiter-style": ["warn"]
}
}

44
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View File

@ -0,0 +1,44 @@
---
name: Bug Report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
<!-- You must use the issue template below when submitting a bug -->
**Describe The Bug:**
<!-- A clear and concise description of what the bug is. -->
**To Reproduce:**
<!-- Steps to reproduce the behavior. -->
**Expected behavior:**
<!-- A clear and concise description of what you expected to happen. -->
**Logs:**
```
Show the Homebridge logs here, remove any sensitive information.
```
**Plugin Config:**
```json
Show your Homebridge config.json here, remove any sensitive information.
```
**Screenshots:**
<!-- If applicable, add screenshots to help explain your problem. -->
**Environment:**
* **Plugin Version**:
* **Homebridge Version**: <!-- homebridge -V -->
* **Node.js Version**: <!-- node -v -->
* **NPM Version**: <!-- npm -v -->
* **Operating System**: <!-- Raspbian / Ubuntu / Debian / Windows / macOS / Docker / hb-service -->
<!-- Click the "Preview" tab before you submit to ensure the formatting is correct. -->

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
# blank_issues_enabled: false
# contact_links:
# - name: Homebridge Discord Community
# url: https://discord.gg/kqNCe2D
# about: Ask your questions in the #YOUR_CHANNEL_HERE channel

View File

@ -0,0 +1,23 @@
---
name: Feature Request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe:**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
**Describe the solution you'd like:**
<!-- A clear and concise description of what you want to happen. -->
**Describe alternatives you've considered:**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
**Additional context:**
<!-- Add any other context or screenshots about the feature request here. -->
<!-- Click the "Preview" tab before you submit to ensure the formatting is correct. -->

View File

@ -0,0 +1,38 @@
---
name: Support Request
about: Need help?
title: ''
labels: question
assignees: ''
---
<!-- You must use the issue template below when submitting a support request -->
**Describe Your Problem:**
<!-- A clear and concise description of what problem you are trying to solve. -->
**Logs:**
```
Show the Homebridge logs here, remove any sensitive information.
```
**Plugin Config:**
```json
Show your Homebridge config.json here, remove any sensitive information.
```
**Screenshots:**
<!-- If applicable, add screenshots to help explain your problem. -->
**Environment:**
* **Plugin Version**:
* **Homebridge Version**: <!-- homebridge -V -->
* **Node.js Version**: <!-- node -v -->
* **NPM Version**: <!-- npm -v -->
* **Operating System**: <!-- Raspbian / Ubuntu / Debian / Windows / macOS / Docker / hb-service -->
<!-- Click the "Preview" tab before you submit to ensure the formatting is correct. -->

31
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,31 @@
name: Build and Lint
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
# the Node.js versions to build on
node-version: [14.x, 15.x, 16.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm install
- name: Lint the project
run: npm run lint
- name: Build the project
run: npm run build
env:
CI: false

120
.gitignore vendored Normal file
View File

@ -0,0 +1,120 @@
# Ignore compiled code
dist
# ------------- Defaults ------------- #
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.pnp.*

135
.npmignore Normal file
View File

@ -0,0 +1,135 @@
# Ignore source code
src
# ------------- Defaults ------------- #
# gitHub actions
.github
# eslint
.eslintrc
# typescript
tsconfig.json
# vscode
.vscode
# nodemon
nodemon.json
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.pnp.*

5
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"recommendations": [
"dbaeumer.vscode-eslint"
]
}

8
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,8 @@
{
"files.eol": "\n",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"editor.rulers": [ 140 ],
"eslint.enable": true
}

636
README.md Executable file → Normal file
View File

@ -1,156 +1,543 @@
# homebridge-smarthomeng # homebridge-smarthomeng
Homebridge plugin for SmartHomeNG. This is work in progress and not all devices are supported yet.
## Currently supported [![npm](https://badgen.net/npm/v/homebridge-smarthomeng)](https://www.npmjs.com/package/homebridge-smarthomeng)
[![verified-by-homebridge](https://badgen.net/badge/homebridge/verified/purple)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
[![npm](https://badgen.net/badge/homebridge/>=1.3.5/green)](https://www.npmjs.com/package/homebridge-smarthomeng)
[![npm](https://badgen.net/npm/node/homebridge-smarthomeng)](https://www.npmjs.com/package/homebridge-smarthomeng)
[![npm](https://badgen.net/npm/dt/homebridge-smarthomeng)](https://www.npmjs.com/package/homebridge-smarthomeng)
[![npm](https://badgen.net/npm/dm/homebridge-smarthomeng)](https://www.npmjs.com/package/homebridge-smarthomeng)
## Currently supported accessories
This plugin currently supports the following services (and characteristics): This plugin currently supports the following services (and characteristics):
* LightBulb (on/off, brightness, hue, saturation) | Type | Description |
* Fan (on/off) |:-----------------------------------------|:--------------------------------------------------------|
* Temperature sensor (current temperature) | [ContactSensor](#contact-sensor) | Simple contact sensor, for example for windows |
* Thermostat (current- / target temperature) | [Doorbell](#doorbell) | Doorbell, sends message to devices on ring |
* Window (current- / target position) | [Fan](#fan) | Simple on/off fan, may be extended in future |
* Window Covering (current- / target position) | [GarageDoor](#garage-door) | Garage door opener |
* Motion sensor (motion detected) | [HumiditySensor](#humidity-sensor) | Humidity sensor |
* Occupancy sensor (motion detected) | [Lightbulb](#lightbulb) | Everything, from simple light to dimmable, RGB and RGBW |
* Contact Sensor (contact state) | [MotionSensor](#motion-sensor) | Detects and reports motion |
* Switch (on/off) | [OccupancySensor](#occupancy-sensor) | Detects presence in a room |
| [Outlet](#outlet) | Simple on/off wall outlet |
| [SecuritySystem](#security-system) | Intrusion alarm system |
| [Switch](#switch) | Simple on/off switch |
| [TemperatureSensor](#temperature-sensor) | Temperature sensor |
| [Thermostat](#thermostat) | Thermostat with temperature sensor and heating state |
| [WindowCovering](#window-covering) | Window covering (shutters, blinds, ...) |
## Requirements ## Requirements
* [SmartHomeNG](https://github.com/smarthomeNG/smarthome) * [SmartHomeNG](https://github.com/smarthomeNG/smarthome)
* [homebridge](https://www.npmjs.com/package/homebridge) * [Node.js >=14.18.1](https://nodejs.org/en/)
* [Homebridge](https://homebridge.io)
## Installation ## Installation of plugin
### Install nodejs >= 0.12.
You have to find out the right way for your OS.
Use [Homebridge Config UI X](https://github.com/oznu/homebridge-config-ui-x#readme) or install manually using ```npm install -g homebridge-smarthomeng```
Debian Jessie:
curl -sL https://deb.nodesource.com/setup_4.x | sudo bash -
sudo apt-get install -y nodejs
Alpine Linux: (--no-cache example is for building a docker image)
apk --no-cache add nodejs
### Install libavahi-compat-libdnssd-dev lib
Debian Jessie:
sudo apt-get install libavahi-compat-libdnssd-dev
Alpine Linux: (--no-cache example is for building a docker image)
apk --no-cache add dbus nodejs avahi avahi-compat-libdns_sd avahi-dev
### Install homebridge from NPM repository
npm install -g homebridge --unsafe-perm
### Install this plugin from NPM repository
npm install -g homebridge-smarthomeng --unsafe-perm
## Configuration ## Configuration
You have to create a config.json in .homebridge directory. You'll find that directory in your home folder. This is an example config file which just uses this plugin and some example SmartHomeNG items. If you already have a working homebridge installation just add the platform section into your existing config. If you are a new homebridge user you have to create a `config.json` file in the `.homebridge` directory. You'll find that directory in your home folder.
### Platform configuration
The following parameters are available to configure the plugin as platform in homebridge.
| Parameter | Possible values | Mandatory | Default | Description |
|:----------|:---------------------------------------|:----------|:--------|:------------------------------------|
| platform | Any \<string> | Yes | | Internal name of your platform |
| name | Any \<string> | Yes | | Visible name in HomeKit |
| host | IP address or FQDN of your SHNG server | Yes | | Your SHNG host |
| port | Port \<number> | No | 2424 | Listening port of websocket module. |
| tls | \<boolean> | No | False | Should TLS encryption be used. |
#### Example configuration:
```json
{
"platform": "SmartHomeNG",
"name": "SmartHomeNG",
"host": "smarthome.my.domain",
"port": 2425,
"tls": true,
}
```
### Common accessories characteristics
The following characteristics are valid for all accessories:
| Parameter | Possible values | Mandatory | Description |
|:-------------|:-------------------------------|:----------|:--------------------------------|
| type | Supported \<type> of accessory | Yes | Type from the [list of supported accessories](#currently-supported-accessories) |
| name | Any \<string> | Yes | Visible name in HomeKit |
| manufacturer | Any \<string> | No | Visible manufacturer in HomeKit |
| model | Any \<string> | No | Visible model in HomeKit |
#### Example:
```json
{
"type": "OccupancySensor",
"name": "Presence kitchen",
"manufacturer": "Preussen",
"model": "Motion 360 KNX",
}
```
### Contact sensor
This sensor shows the open / closed state of a contact (door, window, generic ...).
#### Characteristics in addition to [common characteristics](#common-accessories-characteristics)
| Parameter | Possible values | Mandatory | Description |
|:-------------|:----------------|:----------|:---------------------------------|
| ContactState | \<item> | Yes | SHNG item to monitor for contact |
#### Example:
```json
{
"type": "ContactSensor",
"name": "Window kitchen",
"ContactState": "EG.Kueche.Fenster"
}
```
### Doorbell
A doorbell is an accessory that simply sends a message to all devices enrolled in the home that someone rang the doorbell.
HomeKit displays a message that "This accessory is not currently supported by the Home app.".
Further investigation is needed, but for now it still works.
#### Characteristics in addition to [common characteristics](#common-accessories-characteristics)
| Parameter | Possible values | Mandatory | Description |
|:-------------|:----------------|:----------|:---------------------------------------|
| SinglePress | \<item> | Yes | SHNG item to monitor for doorbell ring |
#### Example:
```json
{
"type": "Doorbell",
"name": "Main door",
"SinglePress": "Technik.Asterisk.Klingel"
}
```
### Fan
For now this accessory only supports turning the fan on and off. Further improvements are possible, but i don't have the needed hardware for testing.
#### Characteristics in addition to [common characteristics](#common-accessories-characteristics)
| Parameter | Possible values | Mandatory | Description |
|:--------------|:----------------|:----------|:------------------------------------------------|
| Active | \<item> | Yes | SHNG item to set and get the fan state. |
| RotationSpeed | \<item> | No | SHNG item to set and get the fan rotation speed |
#### Example:
```json
{
"type": "Fan",
"name": "Fan bathroom",
"Active": "OG.Bad.Ventilator"
}
```
### Garage Door
This accessory is used for opening/closing garage doors or any other automatic gate.
#### Characteristics in addition to [common characteristics](#common-accessories-characteristics)
| Parameter | Possible values | Mandatory | Description |
|:--------------------|:----------------|:----------|:------------------------------------------------------------------|
| CurrentDoorState | \<item> | Yes | SHNG item to monitor the current door state |
| TargetDoorState | \<item> | Yes | SHNG item to monitor and set the target position |
| ObstructionDetected | \<item> | No | SHNG item to monitor if the door is blocked |
#### Additional comments
Valid values for 'CurrentDoorState':
* OPEN = 0
* CLOSED = 1
* OPENING = 2
* CLOSING = 3
* STOPPED = 4
Valid values for 'TargetDoorState':
* OPEN = 0
* CLOSED = 1
'ObstructionDetected' may be set 'true' if there is any physical problem opening/closing the door.
#### Example
```json
{
"type": "GarageDoor",
"name": "GarageRechts",
"currentdoorstate": "garage.rechts.cds",
"targetdoorstate": "garage.rechts.tds",
"obstructiondetected": "garage.rechts.od"
}
```
### Humidity sensor
This accessory shows the current relative humidity in %.
#### Characteristics in addition to [common characteristics](#common-accessories-characteristics)
| Parameter | Possible values | Mandatory | Description |
|:-------------------|:----------------|:----------|:-----------------------------------------------|
| CurrentHumidity | \<item> | Yes | SHNG item to monitor relative humidity in % |
#### Example:
```json
{
"type": "HumiditySensor",
"name": "Luftfeuchtigkeit Glashaus",
"CurrentHumidity": "Glashaus.Luftfeuchtigkeit"
}
```
### LightBulb
Lightbulb can be as simple as a generic on/off light, but can also be as complex as a full RGBW led strip.
#### Characteristics in addition to [common characteristics](#common-accessories-characteristics)
| Parameter | Possible values | Mandatory | Default | Description |
|:--------------|:----------------|:----------|:--------|:----------------------------------------------------------------|
| On | \<item> | Yes | | SHNG item to switch the lightbuld on or off |
| Brightness | \<item> | No | | SHNG item to monitor and set the brigtness for a dimmable light |
| BrightnessMin | \<number> | No | 0 | Your device's minimum value for brightness |
| BrightnessMax | \<number> | No | 100 | Your device's maximum value for brightness |
| Hue | \<item> | No | | SHNG item to get and set the HUE in case of a HSB light |
| Saturation | \<item> | No | | SHNG item to get and set the saturation in case of a HSB light |
| R | \<item> | No | | SHNG item for the RED color in case of RGB(W) light |
| RMin | \<number> | No | 0 | Your device's minimum value for the RED color |
| RMax | \<number> | No | 100 | Your device's maximum value for RED color |
| G | \<item> | No | | SHNG item for the GREEN color in case of RGB(W) light |
| GMin | \<number> | No | 0 | Your device's minimum value for the GREEN color |
| GMax | \<number> | No | 100 | Your device's maximum value for GREEN color |
| B | \<item> | No | | SHNG item for the BLUE color in case of RGB(W) light |
| BMin | \<number> | No | 0 | Your device's minimum value for the BLUE color |
| BMax | \<number> | No | 100 | Your device's maximum value for BLUE color |
#### Additional comments
HomeKit works with values between 0 and 100 where 0 is completely dim and 100 is maximum brightness.
My KNX installation, as example, needs values between 0 and 255.
The above optional min and max parameters allow you to specify the neede range for your device. The plugin then transposes the values in both directions.
#### Example (used for my KNX RGBW strip):
```json
{
"type": "Lightbulb",
"name": "RGBW strip living room",
"On": "EG.Stube.Ledleiste",
"Brightness": "EG.Stube.Ledleiste.dimmen",
"BrightnessMin": 0,
"BrightnessMax": 255,
"R": "EG.Stube.Ledleiste.R.dimmen",
"RMin": 0,
"RMax": 255,
"G": "EG.Stube.Ledleiste.G.dimmen",
"GMin": 0,
"GMax": 255,
"B": "EG.Stube.Ledleiste.B.dimmen",
"BMin": 0,
"BMax": 255,
"W": "EG.Stube.Ledleiste.W.dimmen",
"WMin": 0,
"WMax": 255
}
```
### Motion sensor
This sensor is tripped if it detects motion in a room.
#### Characteristics in addition to [common characteristics](#common-accessories-characteristics)
| Parameter | Possible values | Mandatory | Description |
|:---------------|:----------------|:----------|:--------------------------------|
| MotionDetected | \<item> | Yes | SHNG item to monitor for motion |
#### Example:
```json
{
"type": "MotionSensor",
"name": "Movement hallway",
"MotionDetected": "EG.Flur.Bewegung"
}
```
### Occupancy sensor
This sensor is tripped if it detects presence in a room.
#### Characteristics in addition to [common characteristics](#common-accessories-characteristics)
| Parameter | Possible values | Mandatory | Description |
|:------------------|:----------------|:----------|:----------------------------------|
| OccupancyDetected | \<item> | Yes | SHNG item to monitor for presence |
#### Example:
```json
{
"type": "OccupancySensor",
"name": "Presence bathroom",
"manufacturer": "Preussen",
"model": "Motion 360 KNX",
"OccupancyDetected": "OG.Bad.Praesenz"
}
```
### Outlet
This accessory can monitor and change the on/off state of a wall outlet. The outlet can be generic, a light, a fan, ...
#### Characteristics in addition to [common characteristics](#common-accessories-characteristics)
| Parameter | Possible values | Mandatory | Description |
|:----------|:----------------|:----------|:-------------------------------------|
| On | \<item> | Yes | SHNG item to switch outlet on or off |
#### Example:
```json
{
"type": "Outlet",
"name": "Christmas tree",
"On": "EG.Esszimmer.Steckdose"
}
```
### Security system
This accessory can pilote your intrusion security system. That system can be a physical one operated via SHNG, or a SHNG native logic.
#### Characteristics in addition to [common characteristics](#common-accessories-characteristics)
| Parameter | Possible values | Mandatory | Description |
|:-------------|:----------------|:----------|:-------------------------------------------------|
| CurrentState | \<item> | Yes | SHNG item to monitor for the current alarm state |
| TargetState | \<item> | Yes | SHNG item to set or get the target state |
#### Additional comments
Valid values for 'CurrentState':
* STAY_ARM = 0
* AWAY_ARM = 1
* NIGHT_ARM = 2
* DISARMED = 3
* ALARM_TRIGGERED = 4
Valid values for 'TargetState':
* STAY_ARM = 0
* AWAY_ARM = 1
* NIGHT_ARM = 2
* DISARMED = 3
#### Example:
```json
{
"type": "SecuritySystem",
"name": "Intrusion alarm",
"currentState": "Technik.Alarmanlage.Status.Ist",
"targetState": "Technik.Alarmanlage.Status.Soll"
}
```
### Switch
This accessory can monitor and change the on/off state of something. It is very similar to an outlet.
#### Characteristics in addition to [common characteristics](#common-accessories-characteristics)
| Parameter | Possible values | Mandatory | Description |
|:----------|:----------------|:----------|:----------------------------------------|
| On | \<item> | Yes | SHNG item to switch something on or off |
#### Example:
```json
{
"type": "Switch",
"name": "Music living-room",
"On": "EG.Stube.Radio"
}
```
### Temperature sensor
This sensor shows the actual temperature.
#### Characteristics in addition to [common characteristics](#common-accessories-characteristics)
| Parameter | Possible values | Mandatory | Description |
|:-------------------|:----------------|:----------|:-------------------------------------|
| CurrentTemperature | \<item> | Yes | SHNG item to monitor for temperature |
#### Example:
```json
{
"type": "TemperatureSensor",
"name": "Temperature WC",
"CurrentTemperature": "EG.WC.Temperatur"
}
```
### Thermostat
This sensor shows and sets the actual temperature. In addition it can show the actual heating / cooling state.
#### Characteristics in addition to [common characteristics](#common-accessories-characteristics)
| Parameter | Possible values | Mandatory | Description |
|:---------------------------|:----------------|:----------|:---------------------------------------------------------|
| CurrentTemperature | \<item> | Yes | SHNG item to monitor for temperature |
| TargetTemperature | \<item> | Yes | SHNG item to set target temperature |
| CurrentHeatingCoolingState | \<item> | Yes | SHNG item to monitor for current heating / cooling state |
#### Additional comments
CurrentHeatingCoolingState = 0 for OFF, 1 for HEAT and 2 for COOL
#### Example:
```json
{
"type": "Thermostat",
"name": "Temperature badroom",
"CurrentTemperature": "OG.SZSS.Temperatur",
"TargetTemperature": "OG.SZSS.Temperatur.Sollwert",
"CurrentHeatingCoolingState": "OG.SZSS.Temperatur.Status"
}
```
### Window covering
This accessory type can be used for shutters or blinds. Because the differnce between HomeKit and the controlling technology, for example KNX, can be significant this accessory has a lot of parameters. Luckily most are optional.
#### Characteristics in addition to [common characteristics](#common-accessories-characteristics)
| Parameter | Possible values | Mandatory | Default | Description |
|:---------------------------|:----------------|:----------|:--------|:------------------------------------------------------------------|
| CurrentPosition | \<item> | Yes | | SHNG item to monitor thecurrent position |
| TargetPosition | \<item> | Yes | | SHNG item to monitor and set the target position |
| CurrentPositionMin | \<number> | No | 0 | Your device's minimum value for current position |
| CurrentPositionMax | \<number> | No | 100 | Your device's maximum value for current position |
| CurrentPositionDecimals | \<number> | No | 0 | Number of decimals to round to |
| CurrentPositionInverted | \<boolean> | No | false | Should the values be inverted, ex: 0 for Homekit = 100 for device |
| TargetPositionMin | \<number> | No | 0 | Your device's minimum value for target position |
| TargetPositionMax | \<number> | No | 100 | Your device's maximum value for target position |
| TargetPositionDecimals | \<number> | No | 0 | Number of decimals to round to |
| TargetPositionInverted | \<boolean> | No | false | Should the values be inverted, ex: 0 for Homekit = 100 for device |
| CurrentHorizontalTiltAngle | \<item> | No | | SHNG item to monitor current horizontal tilt angle |
| TargetHorizontalTiltAngle | \<item> | No | | SHNG item to monitor and set the target horizontal tilt angle |
| CurrentVerticalTiltAngle | \<item> | No | | SHNG item to monitor current vertical tilt angle |
| TargetVerticalTiltAngle | \<item> | No | | SHNG item to monitor and set the target vertical tilt angle |
#### Additional comments
HomeKit works with values between 0 and 100 where 0 is completely closed and 100 is open.
My KNX installation, as example, needs values between 0 and 255 where 255 is completely closed and 0 is open.
The above optional parameters allow you to specify the neede range for your device. If needed the values can be inverted at the same time. The plugin then transposes the values in both directions.
#### Example (for use with most KNX shutters):
```json
{
"type": "WindowCovering",
"name": "Shutters office",
"CurrentPosition": "EG.Buero.Rolladen.Position",
"CurrentPositionMin": 0,
"CurrentPositionMax": 255,
"CurrentPositionInverted": true,
"TargetPosition": "EG.Buero.Rolladen.ZielPosition",
"TargetPositionMin": 0,
"TargetPositionMax": 255,
"TargetPositionInverted": true
}
```
### Example configuration file
This is an example config file which just uses this plugin and some example SmartHomeNG items.
```json
{ {
"bridge": { "bridge": {
"name": "HBDEV", "name": "SmartHomeNG",
"username": "CC:22:3D:E3:DE:37", "username": "CC:22:3D:E3:DE:37",
"port": 51826, "port": 51138,
"pin": "031-45-154" "pin": "655-59-9284"
}, },
"platforms": [ "platforms": [
{ {
"platform": "SmartHomeNG", "platform": "SmartHomeNG",
"name": "SmartHomeNG", "name": "SmartHomeNG",
"host": "srvsmarthome.ha.swa.lu", "host": "smarthome.iot.wagener.family",
"port": 2425,
"tls": true,
"accessories": [ "accessories": [
{ {
"name": "Temperatur Stube",
"type": "TemperatureSensor",
"currenttemperature": "EG.Stube.Temperatur"
},
{
"name": "Heizung Bad",
"type": "Thermostat",
"currenttemperature": "OG.Bad.Temperatur",
"targettemperature": "OG.Bad.Temperatur.Sollwert",
"targettemperatureminimum": 18,
"targettemperaturemaximum": 25
},
{
"name": "Ventilator Bad",
"type": "Fan",
"onoff": "OG.Bad.Ventilator"
},
{
"name": "Lüftung",
"type": "Fan",
"onoff": "EG.Lüftung"
"rotationSpeed": "EG.LüftungSpeed"
},
{
"name": "Schalter",
"type": "Switch",
"onoff": "EG.Esszimmer.Schalter"
},
{
"name": "Schaltsteckdose",
"type": "Outlet", "type": "Outlet",
"onoff": "EG.Esszimmer.Steckdose" "name": "Steckdose Esszimmer",
"On": "EG.Esszimmer.Steckdose"
}, },
{ {
"name": "Bürolicht",
"type": "Lightbulb",
"onoff": "EG.Buero.Licht"
},
{
"name": "Stubenlicht",
"type": "Lightbulb",
"onoff": "EG.Stube.Licht"
},
{
"name": "Schlafzimmerlicht",
"type": "Lightbulb",
"onoff": "OG.SZSS.Licht",
"brightness": "OG.SZSS.Licht.dimmen"
},
{
"name": "Rolladen Büro",
"type": "WindowCovering",
"updown": "EG.Buero.Rolladen.AufAb",
"currentposition": "EG.Buero.Rolladen.Position",
"targetposition": "EG.Buero.Rolladen.Position",
"inverted": true
},
{
"name": "Bewegungsmelder Küche",
"type": "MotionSensor",
"motionstate": "EG.Kueche.Praesenz"
},
{
"name": "Terassentür Küche",
"type": "ContactSensor",
"contactsensorstate": "EG.Kueche.Tuer",
"inverted": true
},
{
"name": "Fenster Esszimmer",
"type": "ContactSensor",
"contactsensorstate": "EG.Esszimmer.Fenster",
"inverted": true
},
{
"name": "Präsenzsmelder Esszimmer",
"type": "OccupancySensor", "type": "OccupancySensor",
"motiondetected": "EG.Esszimmer.Praesenz" "name": "Präsenz Büro",
"manufacturer": "Preussen",
"model": "Motion 360 KNX",
"OccupancyDetected": "EG.Buero.Praesenz"
},
{
"type": "MotionSensor",
"name": "Bewegung Flur",
"manufacturer": "Preussen",
"model": "Motion 360 KNX",
"MotionDetected": "EG.Flur.Praesenz"
},
{
"type": "ContactSensor",
"name": "Fenster Büro",
"ContactState": "EG.Buero.Fenster"
},
{
"type": "Doorbell",
"name": "Haustür",
"SinglePress": "Technik.Asterisk.Klingel"
},
{
"type": "Lightbulb",
"name": "Licht Büro",
"On": "EG.Buero.Deckenspots",
"Brightness": "EG.Buero.Deckenspots.dimmen",
"BrightnessMin": 0,
"BrightnessMax": 255
}
{
"type": "Lightbulb",
"name": "RGB Leiste Stube",
"On": "EG.Stube.Ledleiste",
"Brightness": "EG.Stube.Ledleiste.dimmen",
"BrightnessMin": 0,
"BrightnessMax": 255,
"R": "EG.Stube.Ledleiste.R.dimmen",
"RMin": 0,
"RMax": 255,
"G": "EG.Stube.Ledleiste.G.dimmen",
"GMin": 0,
"GMax": 255,
"B": "EG.Stube.Ledleiste.B.dimmen",
"BMin": 0,
"BMax": 255,
"W": "EG.Stube.Ledleiste.W.dimmen",
"WMin": 0,
"WMax": 255
},
{
"type": "Fan",
"name": "Ventilator Bad",
"Active": "OG.Bad.Ventilator"
},
{
"type": "Thermostat",
"name": "Temperatur Büro",
"CurrentTemperature": "EG.Buero.Temperatur",
"TargetTemperature": "EG.Buero.Temperatur.Sollwert",
"CurrentHeatingCoolingState": "EG.Buero.Temperatur.Modus"
},
{
"type": "WindowCovering",
"name": "Shutters office",
"CurrentPosition": "EG.Buero.Rolladen.Position",
"CurrentPositionMin": 0,
"CurrentPositionMax": 255,
"CurrentPositionInverted": true,
"TargetPosition": "EG.Buero.Rolladen.ZielPosition",
"TargetPositionMin": 0,
"TargetPositionMax": 255,
"TargetPositionInverted": true
} }
] ]
} }
], ],
@ -158,4 +545,5 @@ You have to create a config.json in .homebridge directory. You'll find that dire
"description": "This is my development config file." "description": "This is my development config file."
} }
```

View File

@ -1,109 +0,0 @@
var http = require("http");
var WebSocketClient = require('websocket').client
var colorOn = "\x1b[30;47m";
var colorOff = "\x1b[0m";
function SmartHomeNGConnection(platform, log, host, port) {
this.log = log;
this.platform = platform;
this.connected = false;
this.updateCallback = undefined;
this.tomonitor = [];
this.retryTimer = 10;
this.shng_host = host;
this.shng_port = port;
}
SmartHomeNGConnection.prototype.init = function () {
var that = this;
this.shng_ws = new WebSocketClient();
this.shng_ws.on('connect', function(connection) {
that.log('[SmartHomeNGConnection] connected to server!');
that.connected = true;
that.connection = connection;
that.idenfityMyself();
that.startMonitoring();
connection.on('message', function(message) { that.receive(message); });
connection.on('error', function(error) {
that.log(colorOn + '[SmartHomeNGConnection] WebSocket error: ' + error.toString() + colorOff)
});
connection.on('close', function(code, description) {
that.log(colorOn + '[SmartHomeNGConnection] Connection to smarthome lost, retrying in ' + that.retryTimer + ' seconds. ' + description + colorOff);
that.connected = false;
setTimeout(that.connect.bind(that), that.retryTimer * 1000);
});
});
this.shng_ws.on('connectFailed', function(errorDescription) {
that.connected = false;
that.log(colorOn + '[SmartHomeNGConnection] Connection error, retrying in ' + that.retryTimer + ' seconds. ' + errorDescription + colorOff);
setTimeout(that.connect.bind(that), that.retryTimer * 1000);
});
this.connect();
}
SmartHomeNGConnection.prototype.connect = function(host, ip) {
this.log("[SmartHomeNGConnection] Connecting to SmartHomeNG @ " + this.shng_host);
this.shng_ws.connect('ws://' + this.shng_host + ':' + this.shng_port + '/');
}
SmartHomeNGConnection.prototype.receive = function(message) {
var msg = JSON.parse(message.utf8Data);
//this.log(msg);
if (msg.items) {
for (int = 0; int < msg.items.length; int++) {
item = msg.items[int];
//this.log("[SmartHomeNGConnection] Received value " + item[1] + " for item " + item[0]);
if (this.updateCallback) {
this.updateCallback(item[0], item[1]);
}
}
}
}
SmartHomeNGConnection.prototype.setValue = function(item, value) {
var command = '{"cmd":"item","id":"' + item + '","val":"' + value + '"}';
if (this.connected) {
this.log("[SmartHomeNGConnection] Sending " + command + " to SmartHomeNG");
this.connection.send(command)
} else {
this.log("[SmartHomeNGConnection] Cannot switch " + item + ", no connection to SmartHomeNG !")
}
}
SmartHomeNGConnection.prototype.idenfityMyself = function() {
if (this.connected) {
var buffer = {};
buffer.cmd = 'identity';
buffer.sw = 'homebridge-SmarHomeNG';
buffer.ver = this.platform.version;
var command = JSON.stringify(buffer);
this.connection.send(command);
} else {
this.log("[SmartHomeNGConnection] Cannot identify myself, not connected !");
}
}
SmartHomeNGConnection.prototype.startMonitoring = function() {
if (this.connected && this.tomonitor.length > 0) {
var buffer = {};
this.log("[SmartHomeNGConnection] Start monitoring " + this.tomonitor);
buffer.cmd = 'monitor';
buffer.items = this.tomonitor;
var command = JSON.stringify(buffer);
this.connection.send(command);
} else {
this.log("[SmartHomeNGConnection] Cannot start monitoring, not connected !");
}
}
module.exports = {
SmartHomeNGConnection: SmartHomeNGConnection
}

34
config.schema.json Normal file
View File

@ -0,0 +1,34 @@
{
"pluginAlias": "SmartHomeNG",
"pluginType": "platform",
"singular": true,
"schema": {
"type": "object",
"properties": {
"name": {
"title": "Name",
"type": "string",
"required": true,
"default": "SmartHomeNG"
},
"host": {
"title": "SHNG Host",
"type": "string",
"required": true,
"default": "smarthome.local"
},
"port": {
"title": "SHNG Port",
"type": "number",
"required": true,
"default": 2424
},
"tls": {
"title": "TLS enabled",
"type": "boolean",
"required": true,
"default": false
}
}
}
}

679
index.js
View File

@ -1,679 +0,0 @@
/*****
* SmartHomeNG platform shim for use with nfarina's homebridge plugin system
* This work has been inspired by the homebridge-knx platform shim. Credits to snowdd1 !
*
*/
var Accessory, Service, Characteristic, UUIDGen;
var fs = require('fs');
var path = require('path');
var SmartHomeNGConnection = require("./SmartHomeNGConnection.js").SmartHomeNGConnection;
//var milliTimeout = 300; // used to block responses while swiping
var directCacheUpdate = true;
var monitoring = [];
var colorOn = "\x1b[30;47m";
var colorOff = "\x1b[0m";
module.exports = function(homebridge) {
// Accessory must be created from PlatformAccessory Constructor
Accessory = homebridge.platformAccessory;
// Service and Characteristic are from hap-nodejs
Service = homebridge.hap.Service;
Characteristic = homebridge.hap.Characteristic;
UUIDGen = homebridge.hap.uuid;
homebridge.registerPlatform("homebridge-smarthomeng", "SmartHomeNG", SmartHomeNGPlatform);
}
// Platform constructor
function SmartHomeNGPlatform(log, config, api) {
this.log = log;
this.config = config;
this.accessoriesCache = [];
this.supportedFunctions = ['onoff', 'brightness', 'hue', 'saturation', 'currentposition', 'targetposition', 'motiondetected'];
if (this.config["host"] != undefined) {
this.shng_host = this.config["host"];
} else {
this.shng_host = "localhost";
}
if (this.config["port"] != undefined) {
this.shng_port = this.config["port"];
} else {
this.shng_port = 2424;
}
var that = this;
this.version = this.getVersion();
this.log("SmartHomeNG Platform Plugin Version " + this.version)
this.shngcon = new SmartHomeNGConnection(this, this.log, this.shng_host, this.shng_port);
this.shngcon.updateCallback = this.update;
if (api) {
this.api = api;
this.api.on('didFinishLaunching', function() {
this.log("Finished loading " + this.accessoriesCache.length + " accessories");
// this.log(monitoring);
// Add supported SHNG items to monitoring
var tomonitor = [];
for(i = 0; i < monitoring.length; i++) {
var device = monitoring[i];
if(tomonitor.indexOf(device.item) == -1) {
tomonitor.push(device.item);
}
}
this.shngcon.tomonitor = tomonitor;
}.bind(this));
}
this.shngcon.init();
}
// Accessory constructor
function SmartHomeNGAccessory(log, config, shngcon) {
this.name = config.name;
this.config = config;
this.log = log;
this.shngcon = shngcon;
this.value = undefined;
this.manufacturername = 'SmartHomeNG';
/*this.log("CONSTRUCTOR: ");
this.log(device);
this.log("------")*/
}
SmartHomeNGPlatform.prototype = {
// Return our accessories to homebridge
accessories: function(callback) {
var that = this;
var foundAccessories = [];
this.log("Building list of accessories");
var configAccessories = this.config.accessories;
for (var i = 0; i < configAccessories.length; i++) {
this.log("Parsing accessory: '" + configAccessories[i].name + "'.");
var accessory = new SmartHomeNGAccessory(that.log, configAccessories[i], that.shngcon);
foundAccessories.push(accessory);
}
//this.log(foundAccessories)
this.accessoriesCache = foundAccessories;
callback(foundAccessories);
},
update: function (item, value) {
//this.log("CALLBACK: item " + item + " with value " + value);
for (var i = 0; i < monitoring.length; i++) {
// iterate through all registered addresses
if (monitoring[i].item == item) {
this.log("[" + monitoring[i].name + "] Got update for '" + monitoring[i].characteristic + "' from SmartHomeNG item '" + item + "' with value " + value + ".");
monitoring[i].lastValue = value;
monitoring[i].callback(item, value, monitoring[i].inverted);
}
}
},
// Get version info from package.json file
getVersion: function() {
var pjPath = path.join(__dirname, './package.json');
var pj = JSON.parse(fs.readFileSync(pjPath));
return pj.version;
}
}
SmartHomeNGAccessory.prototype = {
// Enumerate accessory services and characteristics
getServices: function() {
var that = this;
var myServices = [];
this.log("["+ this.name +"] Setting up services.");
// check if device type is set in config
if (!this.config.type) {
this.log("Ignoring '" + this.name + "' because no device type found, make sure to have the 'type' parameter in your config.json !");
return [];
}
// construct service and characteristics according to device type
var serial = "unknown";
switch (this.config.type.toLowerCase()) {
// Lightbulb service
case 'fan':
myServices.push(this.getFanService(this.config));
serial = this.config.onoff;
break;
case 'temperaturesensor':
myServices.push(this.getTemperatureSensorService(this.config));
break;
case 'thermostat':
myServices.push(this.getThermostatService(this.config));
break;
case 'lightbulb':
myServices.push(this.getLightbulbService(this.config));
serial = this.config.onoff;
break;
case 'window':
myServices.push(this.getWindowService(this.config));
break;
case 'switch':
myServices.push(this.getSwitchService(this.config));
break;
case 'outlet':
myServices.push(this.getOutletService(this.config));
break;
case 'windowcovering':
myServices.push(this.getWindowCoveringService(this.config));
break;
case 'occupancysensor':
myServices.push(this.getOccupancySensorService(this.config));
serial = this.config.motiondetected;
break;
case 'motionsensor':
myServices.push(this.getMotionSensorService(this.config));
serial = this.config.motiondetected;
break;
case 'contactsensor':
myServices.push(this.getContactSensorService(this.config));
serial = this.config.contactsensorstate;
break;
// If no supported type is found warn user and return empty services
default:
this.log("Ignoring '" + this.name + "' because device type '" + this.config.type + "' is not supported !");
return [];
break;
}
// device information service
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "Opensource Community")
.setCharacteristic(Characteristic.Model, "SmartHomeNG device")
.setCharacteristic(Characteristic.SerialNumber, serial);
myServices.push(informationService);
return myServices;
},
// Respond to identify request
identify: function(callback) {
this.log("Identify request for '" + this.name + "'.");
callback();
},
/** Registering routines
*
*/
shngregister_int: function(name, shngitem, characteristic) {
this.log("[" + name + "] Registering callback for '" + shngitem + "'.");
var callback = function (shngitem, value) {
//this.log("[" + this.name + "] callback for " + characteristic.displayName);
characteristic.setValue(value, undefined, 'fromSHNG');
}.bind(this);
monitoring.push({name: name, characteristic: characteristic.displayName, item: shngitem, callback: callback, inverted: false});
},
shngregister_float: function(name, shngitem, characteristic) {
this.log("[" + name + "] Registering callback for '" + shngitem + "'.");
var callback = function (shngitem, value) {
//this.log("[" + this.name + "] callback for " + characteristic.displayName);
characteristic.setValue(value, undefined, 'fromSHNG');
}.bind(this);
monitoring.push({name: name, characteristic: characteristic.displayName, item: shngitem, callback: callback, inverted: false});
},
shngregister_bool: function(name, shngitem, characteristic, inverted) {
this.log("[" + name + "] Registering callback for '" + shngitem + "'.");
var callback = function (shngitem, value, inverted) {
//this.log("[" + this.name + "] callback for " + characteristic.displayName);
characteristic.setValue(value ? (inverted ? 0:1) : (inverted ? 1:0), undefined, 'fromSHNG');
}.bind(this);
monitoring.push({name: name, characteristic: characteristic.displayName, item: shngitem, callback: callback, inverted: inverted});
},
shngregister_percent: function(name, shngitem, characteristic, inverted) {
this.log("[" + name + "] Registering callback for '" + shngitem + "'.");
var callback = function (shngitem, value, inverted) {
//this.log("[" + this.name + "] callback for " + characteristic.displayName + " with value " + value);
characteristic.setValue(inverted ? 100 - value : value, undefined, 'fromSHNG');
}.bind(this);
monitoring.push({name: name, characteristic: characteristic.displayName, item: shngitem, callback: callback, inverted: inverted});
},
shngregister_angle: function(name, shngitem, characteristic, inverted) {
this.log("[" + name + "] Registering callback for '" + shngitem + "'.");
var callback = function (shngitem, value, inverted) {
//this.log("[" + this.name + "] callback for " + characteristic.displayName);
value = value * 3.6;
characteristic.setValue(inverted ? 360 - value : value, undefined, 'fromSHNG');
}.bind(this);
monitoring.push({name: name, characteristic: characteristic.displayName, item: shngitem, callback: callback, inverted: inverted});
},
/** get methods
*
*/
getState: function(callback, shngitem, inverted) {
this.log("[" + this.name + "] Get value from cache for item " + shngitem + ".");
for (var i = 0; i < monitoring.length; i++) {
if (monitoring[i].item == shngitem) {
if (monitoring[i].lastValue != undefined) {
value = monitoring[i].lastValue;
this.log("[" + this.name + "] Found value " + value + " in cache.");
//monitoring[i].callback(item, value, monitoring[i].inverted);
callback(null, value);
return;
}
break;
}
}
callback();
},
/** method for direct cache update (optional)
* (this is triggered by updates from Homebridge to update lastValue directly)
*
*/
direct_update: function(item, value) {
if (directCacheUpdate) {
//this.log("DIRECT_UPDATE: item " + item + " with value " + value);
for (var i = 0; i < monitoring.length; i++) {
// iterate through all registered addresses
if (monitoring[i].item == item) {
monitoring[i].lastValue = value; // or only invalidate here and read from SmartHomeNG again ?
}
}
}
},
/** set methods used for creating callbacks
*
*/
setBooleanState: function(value, callback, context, shngitem, inverted) {
if (context === 'fromSHNG') {
if (callback) {
callback();
}
} else {
var numericValue = inverted ? 1:0;
if (value) {
numericValue = inverted ? 0:1;
}
this.log("[" + this.name + "] Setting " + shngitem + (inverted ? " (inverted)":"") + " boolean to %s", numericValue);
this.shngcon.setValue(shngitem, numericValue);
this.direct_update(shngitem, numericValue);
if (callback) callback();
}
},
setPercentage: function(value, callback, context, shngitem, inverted) {
if (context === 'fromSHNG') {
if (callback) {
callback();
}
} else {
var numericValue = 0;
value = ( value>=0 ? (value<=100 ? value:100):0 ); //ensure range 0..100
if (inverted) {
numericValue = 100 - value;
} else {
numericValue = value;
}
this.log("[" + this.name + "] Setting " + shngitem + " percentage to %s", numericValue);
this.shngcon.setValue(shngitem, numericValue);
this.direct_update(shngitem, numericValue);
if (callback) callback();
}
},
setAngle: function(value, callback, context, shngitem, inverted) {
if (context === 'fromSHNG') {
if (callback) {
callback();
}
} else {
var numericValue = 0;
value = ( value>=0 ? (value<=360 ? value:360):0 ); //ensure range 0..360
if (inverted) {
numericValue = 360 - value;
} else {
numericValue = value;
}
this.log("[" + this.name + "] Setting " + shngitem + " percentage to %s", numericValue);
numericValue = numericValue / 3.6; //convert Angle to Percentage (0-100)
this.shngcon.setValue(shngitem, numericValue);
this.direct_update(shngitem, numericValue);
if (callback) callback();
}
},
setInt: function(value, callback, context, shngitem) {
if (context === 'fromSHNG') {
if (callback) {
callback();
}
} else {
var numericValue = 0;
if (value && value>=0) {
numericValue = value;
}
this.log("["+ this.name +"] Setting " + shngitem + " int to %s", numericValue);
this.shngcon.setValue(shngitem, numericValue);
this.direct_update(shngitem, numericValue);
if (callback) callback();
}
},
setFloat: function(value, callback, context, shngitem) {
if (context === 'fromSHNG') {
if (callback) {
callback();
}
} else {
var numericValue = 0;
if (value && value>=0) {
numericValue = value;
}
this.log("["+ this.name +"] Setting " + shngitem + " float to %s", numericValue);
this.shngcon.setValue(shngitem, numericValue);
this.direct_update(shngitem, numericValue);
if (callback) callback();
}
},
/** bindCharacteristic
* initializes callbacks for 'set' events (from HK) and for SmartHomeNG monitoring events (to HK)
*/
// Bind characteristic to service during startup
bindCharacteristic: function(myService, characteristicType, valueType, shngitem, inverted, defaultValue) {
var myCharacteristic = myService.getCharacteristic(characteristicType);
//this.log("SHNGITEM: " + shngitem)
if (defaultValue) {
myCharacteristic.setValue(defaultValue);
}
switch (valueType) {
case "Int":
myCharacteristic.on('set', function(value, callback, context) {
this.setInt(value, callback, context, shngitem);
}.bind(this));
myCharacteristic.on('get', function(callback, context) {
this.getState(callback, shngitem, inverted);
}.bind(this));
this.shngregister_int(this.name, shngitem, myCharacteristic);
break;
case "Float":
myCharacteristic.on('set', function(value, callback, context) {
this.setFloat(value, callback, context, shngitem);
}.bind(this));
myCharacteristic.on('get', function(callback, context) {
this.getState(callback, shngitem, inverted);
}.bind(this));
this.shngregister_float(this.name, shngitem, myCharacteristic);
break;
case "Bool":
myCharacteristic.on('set', function(value, callback, context) {
this.setBooleanState(value, callback, context, shngitem, inverted);
}.bind(this));
myCharacteristic.on('get', function(callback, context) {
this.getState(callback, shngitem, inverted);
}.bind(this));
this.shngregister_bool(this.name, shngitem, myCharacteristic, inverted);
break;
case "Percent":
myCharacteristic.on('set', function(value, callback, context) {
this.setPercentage(value, callback, context, shngitem, inverted);
//myCharacteristic.timeout = Date.now()+milliTimeout;
}.bind(this));
myCharacteristic.on('get', function(callback, context) {
this.getState(callback, shngitem, inverted);
}.bind(this));
this.shngregister_percent(this.name, shngitem, myCharacteristic, inverted);
break;
case "Angle":
myCharacteristic.on('set', function(value, callback, context) {
this.setAngle(value, callback, context, shngitem, inverted);
//myCharacteristic.timeout = Date.now()+milliTimeout;
}.bind(this));
myCharacteristic.on('get', function(callback, context) {
this.getState(callback, shngitem, inverted);
}.bind(this));
this.shngregister_angle(this.name, shngitem, myCharacteristic, inverted);
break;
default:
this.log(colorOn + "[ERROR] unknown type passed: [" + valueType+"]"+ colorOff);
}
return myCharacteristic;
},
/**
* function getXXXXXXXService(config)
* returns a configured service object to the caller (accessory/device)
*
*/
// Create Temperature Sensor service
getTemperatureSensorService: function(config) {
var myService = new Service.TemperatureSensor(config.name,config.name);
// Current temperature
if (config.currenttemperature) {
this.log("["+ this.name +"] TemperatureSensor CurrentTemperature characteristic enabled");
this.bindCharacteristic(myService, Characteristic.CurrentTemperature, "Float", config.currenttemperature, false);
}
return myService;
},
// Create Thermostat service
getThermostatService: function(config) {
var myService = new Service.Thermostat(config.name,config.name);
// Current temperature
if (config.currenttemperature) {
this.log("["+ this.name +"] TemperatureSensor CurrentTemperature characteristic enabled");
this.bindCharacteristic(myService, Characteristic.CurrentTemperature, "Float", config.currenttemperature, false);
}
// Target temperature
if (config.targettemperature) {
this.log("["+ this.name +"] TemperatureSensor TargetTemperature characteristic enabled");
myService.getCharacteristic(Characteristic.TargetTemperature).setProps({
minValue: config.targettemperatureminimum || 0,
maxValue: config.targettemperaturemaximum || 40
});
this.bindCharacteristic(myService, Characteristic.TargetTemperature, "Float", config.targettemperature, false);
}
/*
if (config.temperaturedisplayunits && config.temperaturedisplayunits.toLowerCase() == 'fahrenheit') {
this.bindCharacteristic(myService, Characteristic.TemperatureDisplayUnits, "Int", Characteristic.TemperatureDisplayUnits.FAHRENHEIT, false);
} else {
this.bindCharacteristic(myService, Characteristic.TemperatureDisplayUnits, "Int", Characteristic.TemperatureDisplayUnits.CELSIUS, false);
}*/
return myService;
},
// Create Fan service
getFanService: function(config) {
var myService = new Service.Fan(config.name,config.name);
var inverted = false;
if (config.inverted) {
inverted = true;
}
// On (and Off)
if (config.onoff) {
this.log("["+ this.name +"] Fan on/off characteristic enabled");
this.bindCharacteristic(myService, Characteristic.On, "Bool", config.onoff, inverted);
}
// RotationSpeed
if (config.rotationSpeed) {
this.log("["+ this.name +"] Fan rotationSpeed characteristic enabled");
this.bindCharacteristic(myService, Characteristic.RotationSpeed, "Percent", config.rotationSpeed, inverted);
}
return myService;
},
// Create Lightbulb service
getLightbulbService: function(config) {
var myService = new Service.Lightbulb(config.name,config.name);
var inverted = false;
if (config.inverted) {
inverted = true;
}
// On (and Off)
if (config.onoff) {
this.log("["+ this.name +"] Lightbulb on/off characteristic enabled");
this.bindCharacteristic(myService, Characteristic.On, "Bool", config.onoff, inverted);
}
// Brightness if available
if (config.brightness) {
this.log("["+ this.name +"] Lightbulb Brightness characteristic enabled");
myService.addCharacteristic(Characteristic.Brightness); // it's an optional
this.bindCharacteristic(myService, Characteristic.Brightness, "Percent", config.brightness, inverted);
}
// Hue if available
if (config.hue) {
this.log("["+ this.name +"] Lightbulb Hue characteristic enabled");
myService.addCharacteristic(Characteristic.Hue); // it's an optional
this.bindCharacteristic(myService, Characteristic.Hue, "Angle", config.hue, inverted);
}
// Saturation if available
if (config.saturation) {
this.log("["+ this.name +"] Lightbulb Saturation characteristic enabled");
myService.addCharacteristic(Characteristic.Saturation); // it's an optional
this.bindCharacteristic(myService, Characteristic.Saturation, "Percent", config.saturation, inverted);
}
return myService;
},
// Create Window service
getWindowService: function(config) {
//this.log(config);
var myService = new Service.Window(config.name,config.name);
var inverted = false;
if (config.inverted) {
inverted = true;
}
if (config.currentposition) {
this.log("["+ this.name +"] Window CurrentPosition characteristic enabled");
this.bindCharacteristic(myService, Characteristic.CurrentPosition, "Percent", config.currentposition, inverted);
}
if (config.targetposition) {
this.log("["+ this.name +"] Window TargetPosition characteristic enabled");
this.bindCharacteristic(myService, Characteristic.TargetPosition, "Percent", config.targetposition, inverted);
}
this.log("["+ this.name +"] Window PositionState characteristic enabled");
this.bindCharacteristic(myService, Characteristic.PositionState, "Int", config.positionstate, inverted, Characteristic.PositionState.STOPPED);
return myService;
},
// Create WindowCovering service
getWindowCoveringService: function(config) {
//this.log(config);
var myService = new Service.WindowCovering(config.name,config.name);
var inverted = false;
if (config.inverted) {
inverted = true;
}
if (config.currentposition) {
this.log("["+ this.name +"] WindowCovering CurrentPosition characteristic enabled");
this.bindCharacteristic(myService, Characteristic.CurrentPosition, "Percent", config.currentposition, inverted);
}
if (config.targetposition) {
this.log("["+ this.name +"] WindowCovering TargetPosition characteristic enabled");
this.bindCharacteristic(myService, Characteristic.TargetPosition, "Percent", config.targetposition, inverted);
}
this.log("["+ this.name +"] WindowCovering PositionState characteristic enabled");
this.bindCharacteristic(myService, Characteristic.PositionState, "Int", config.positionstate, inverted, Characteristic.PositionState.STOPPED);
return myService;
},
// Create OccupancySensor service
getOccupancySensorService: function(config) {
var myService = new Service.OccupancySensor(config.name,config.name);
var inverted = false;
if (config.inverted) {
inverted = true;
}
if (config.motiondetected) {
this.log("["+ this.name +"] OccupancySensor OccupancyDetected characteristic enabled");
this.bindCharacteristic(myService, Characteristic.OccupancyDetected, "Bool", config.motiondetected, inverted);
}
return myService;
},
// Create MotionSensor service
getMotionSensorService: function(config) {
var myService = new Service.MotionSensor(config.name,config.name);
var inverted = false;
if (config.inverted) {
inverted = true;
}
if (config.motiondetected) {
this.log("["+ this.name +"] MotionSensor MotionDetected characteristic enabled");
this.bindCharacteristic(myService, Characteristic.MotionDetected, "Bool", config.motiondetected, inverted);
}
return myService;
},
// Create ContactSensor service
getContactSensorService: function(config) {
var myService = new Service.ContactSensor(config.name,config.name);
var inverted = false;
if (config.inverted) {
inverted = true;
}
if (config.contactsensorstate) {
this.log("["+ this.name +"] ContactSensor ContactSensorState characteristic enabled");
this.bindCharacteristic(myService, Characteristic.ContactSensorState, "Bool", config.contactsensorstate, inverted);
}
return myService;
},
// Create Switch service
getSwitchService: function(config) {
var myService = new Service.Switch(config.name,config.name);
var inverted = false;
if (config.inverted) {
inverted = true;
}
// On (and Off)
if (config.onoff) {
this.log("["+ this.name +"] Switch on/off characteristic enabled");
this.bindCharacteristic(myService, Characteristic.On, "Bool", config.onoff, inverted);
}
return myService;
},
// Create Outlet service
getOutletService: function(config) {
var myService = new Service.Outlet(config.name,config.name);
var inverted = false;
if (config.inverted) {
inverted = true;
}
// On (and Off)
if (config.onoff) {
this.log("["+ this.name +"] Outlet on/off characteristic enabled");
this.bindCharacteristic(myService, Characteristic.On, "Bool", config.onoff, inverted);
}
myService.getCharacteristic(Characteristic.OutletInUse).setValue(true);
return myService;
},
}

12
nodemon.json Normal file
View File

@ -0,0 +1,12 @@
{
"watch": [
"src"
],
"ext": "ts",
"ignore": [],
"exec": "tsc && homebridge -I -D",
"signal": "SIGTERM",
"env": {
"NODE_OPTIONS": "--trace-warnings"
}
}

5283
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

42
package.json Executable file → Normal file
View File

@ -1,23 +1,43 @@
{ {
"private": false,
"displayName": "SmartHomeNG",
"name": "homebridge-smarthomeng", "name": "homebridge-smarthomeng",
"version": "1.3.7", "version": "2.0.8",
"description": "Platform plugin for SmartHomeNG: https://github.com/smarthomeNG/smarthome", "description": "SmartHomeNG plugin for Homebridge",
"license": "GPL", "license": "Apache-2.0",
"keywords": [
"homebridge-plugin"
],
"repository": { "repository": {
"type": "https", "type": "git",
"url": "https://github.com/Foxi352/homebridge-smarthomeng.git" "url": "git://github.com/Foxi352/homebridge-smarthomeng.git"
}, },
"bugs": { "bugs": {
"url": "https://github.com/Foxi352/homebridge-smarthomeng/issues" "url": "https://github.com/Foxi352/homebridge-smarthomeng/issues"
}, },
"engines": { "engines": {
"node": ">=0.12.0", "node": ">=14.18.1",
"homebridge": ">=0.2.0" "homebridge": ">=1.3.5"
}, },
"main": "dist/index.js",
"scripts": {
"lint": "eslint src/**.ts --max-warnings=0",
"watch": "npm run build && npm link && nodemon",
"build": "rimraf ./dist && tsc",
"prepublishOnly": "npm run lint && npm run build"
},
"keywords": [
"homebridge-plugin"
],
"dependencies": { "dependencies": {
"websocket": ">=1.0.0" "ws": "^8.12.1"
},
"devDependencies": {
"@types/node": "^18.13.0",
"@typescript-eslint/eslint-plugin": "^5.52.0",
"@typescript-eslint/parser": "^5.52.0",
"eslint": "^8.34.0",
"homebridge": "^1.6.0",
"nodemon": "^2.0.20",
"rimraf": "^4.1.2",
"ts-node": "^10.9.1",
"typescript": "^4.9.5"
} }
} }

View File

@ -0,0 +1,66 @@
import {
AccessoryPlugin,
CharacteristicValue,
Service,
Nullable,
} from 'homebridge';
import { SmartHomeNGPlatform } from '../platform';
export class ContactSensor implements AccessoryPlugin {
private readonly deviceService: Service;
private readonly informationService: Service;
public name: string;
private contactState = this.platform.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED;
constructor(private readonly platform: SmartHomeNGPlatform, private readonly accessory) {
this.name = accessory.name;
this.deviceService = new this.platform.Service.ContactSensor(accessory.name);
// create handlers for required characteristics
this.deviceService.getCharacteristic(this.platform.Characteristic.MotionDetected)
.onGet(this.handleContactSensorStateGet.bind(this));
this.informationService =
new this.platform.Service.AccessoryInformation()
.setCharacteristic(this.platform.Characteristic.Manufacturer, accessory.manufacturer)
.setCharacteristic(this.platform.Characteristic.Model, accessory.model)
.setCharacteristic(this.platform.Characteristic.SerialNumber, accessory.contactstate);
this.platform.shng.addMonitor(accessory.contactstate, this.shngContactStateCallback.bind(this));
this.platform.log.info('ContactSensor', accessory.name, 'created!');
}
identify(): void {
this.platform.log.info('Identify!');
}
getServices(): Service[] {
return [this.informationService, this.deviceService];
}
handleContactSensorStateGet(): Nullable<CharacteristicValue> {
this.platform.log.debug(
'handleContactSensorStateGet:',
this.accessory.name, '=',
this.contactState === this.platform.Characteristic.ContactSensorState.CONTACT_DETECTED ? 'True' : 'False',
);
return this.contactState;
}
shngContactStateCallback(value: unknown): void {
this.platform.log.debug('shngContactStateCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'boolean') {
if (value) {
this.contactState = this.platform.Characteristic.ContactSensorState.CONTACT_DETECTED;
} else {
this.contactState = this.platform.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED;
}
} else {
this.platform.log.warn('Unknown type', typeof value, 'received for', this.accessory.name + ':', value);
}
this.deviceService.updateCharacteristic(this.platform.Characteristic.ContactSensorState, this.contactState);
}
}

View File

@ -0,0 +1,66 @@
import {
AccessoryPlugin,
CharacteristicValue,
Service,
Nullable,
} from 'homebridge';
import { SmartHomeNGPlatform } from '../platform';
export class Doorbell implements AccessoryPlugin {
private readonly deviceService: Service;
private readonly informationService: Service;
public name: string;
//private event = this.platform.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS;
constructor(private readonly platform: SmartHomeNGPlatform, private readonly accessory) {
this.name = accessory.name;
this.deviceService = new this.platform.Service.Doorbell(accessory.name);
// create handlers for required characteristics
this.deviceService.getCharacteristic(this.platform.Characteristic.ProgrammableSwitchEvent)
.onGet(this.handleProgrammableSwitchEventGet.bind(this));
this.informationService =
new this.platform.Service.AccessoryInformation()
.setCharacteristic(this.platform.Characteristic.Manufacturer, accessory.manufacturer)
.setCharacteristic(this.platform.Characteristic.Model, accessory.model)
.setCharacteristic(this.platform.Characteristic.SerialNumber, accessory.singlepress);
this.platform.shng.addMonitor(accessory.singlepress, this.shngSinglePressCallback.bind(this));
this.platform.log.info('Doorbell', accessory.name, 'created!');
}
identify(): void {
this.platform.log.info('Identify!');
}
getServices(): Service[] {
return [this.informationService, this.deviceService];
}
handleProgrammableSwitchEventGet(): Nullable<CharacteristicValue> {
this.platform.log.error(
'handleProgrammableSwitchEventGet:',
this.accessory.name, '=',
0,
);
return 0;
}
shngSinglePressCallback(value: unknown): void {
this.platform.log.debug('shngSinglePressCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'boolean') {
if (value) {
this.deviceService.updateCharacteristic(
this.platform.Characteristic.ProgrammableSwitchEvent,
this.platform.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS,
);
}
} else {
this.platform.log.warn('Unknown type', typeof value, 'received for', this.accessory.name + ':', value);
}
}
}

92
src/Accessories/Fan.ts Normal file
View File

@ -0,0 +1,92 @@
import {
AccessoryPlugin,
CharacteristicValue,
Service,
Nullable,
} from 'homebridge';
import { SmartHomeNGPlatform } from '../platform';
export class Fan implements AccessoryPlugin {
private readonly deviceService: Service;
private readonly informationService: Service;
public name: string;
private active = false;
private rotationSpeed = 100;
constructor(private readonly platform: SmartHomeNGPlatform, private readonly accessory) {
this.name = accessory.name;
this.deviceService = new this.platform.Service.Fanv2(accessory.name);
// create handlers for required characteristics
this.deviceService.getCharacteristic(this.platform.Characteristic.Active)
.onGet(this.getActive.bind(this))
.onSet(this.setActive.bind(this));
this.platform.shng.addMonitor(accessory.active, this.shngActiveCallback.bind(this));
if (accessory.rotationspeed) {
this.deviceService.getCharacteristic(this.platform.Characteristic.RotationSpeed)
.onGet(this.getRotationSpeed.bind(this))
.onSet(this.setRotationSpeed.bind(this));
this.platform.shng.addMonitor(accessory.rotationspeed, this.shngRotationSpeedCallback.bind(this));
}
this.informationService =
new this.platform.Service.AccessoryInformation()
.setCharacteristic(this.platform.Characteristic.Manufacturer, accessory.manufacturer)
.setCharacteristic(this.platform.Characteristic.Model, accessory.model)
.setCharacteristic(this.platform.Characteristic.SerialNumber, accessory.active);
this.platform.log.info("Fan '%s' created!", accessory.name);
}
identify(): void {
this.platform.log.info('Identify!');
}
getServices(): Service[] {
return [this.informationService, this.deviceService];
}
getActive(): Nullable<CharacteristicValue> {
this.platform.log.info('getActive:', this.accessory.name, 'is currently', (this.active ? 'ON' : 'OFF'));
return this.active;
}
setActive(value: CharacteristicValue) {
this.active = value as boolean;
this.platform.log.info('setActive:', this.accessory.name, 'was set to', (this.active ? 'ON' : 'OFF'));
this.platform.shng.setItem(this.accessory.active, this.active);
}
getRotationSpeed(): Nullable<CharacteristicValue> {
this.platform.log.info('getRotationSpeed:', this.accessory.name, 'is currently', this.rotationSpeed);
return this.rotationSpeed;
}
setRotationSpeed(value: CharacteristicValue) {
this.rotationSpeed = value as number;
this.platform.log.info('setRotationSpeed:', this.accessory.name, 'was set to', this.rotationSpeed);
this.platform.shng.setItem(this.accessory.rotationspeed, this.rotationSpeed);
}
shngActiveCallback(value: unknown): void {
this.platform.log.debug('shngActiveCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'boolean') {
this.active = value;
this.deviceService.updateCharacteristic(this.platform.Characteristic.Active, this.active);
} else {
this.platform.log.warn('Unknown type ', typeof value, 'received for', this.accessory.name + ':', value);
}
}
shngRotationSpeedCallback(value: unknown): void {
this.platform.log.debug('shngRotationSpeedCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'number') {
this.rotationSpeed = value;
this.deviceService.updateCharacteristic(this.platform.Characteristic.RotationSpeed, this.rotationSpeed);
} else {
this.platform.log.warn('Unknown type ', typeof value, 'received for', this.accessory.name + ':', value);
}
}
}

View File

@ -0,0 +1,130 @@
import {
AccessoryPlugin,
CharacteristicValue,
Service,
Nullable,
} from 'homebridge';
import { SmartHomeNGPlatform } from '../platform';
export class GarageDoor implements AccessoryPlugin {
private readonly deviceService: Service;
private readonly informationService: Service;
public name: string;
private targetDoorState = this.platform.Characteristic.CurrentDoorState.CLOSED;
private currentDoorState = this.platform.Characteristic.CurrentDoorState.STOPPED;
private obstructionDetected = false;
constructor(private readonly platform: SmartHomeNGPlatform, private readonly accessory) {
this.name = accessory.name;
this.deviceService = new this.platform.Service.GarageDoorOpener(accessory.name);
// create handlers for required characteristics
this.informationService =
new this.platform.Service.AccessoryInformation()
.setCharacteristic(this.platform.Characteristic.Manufacturer, accessory.manufacturer)
.setCharacteristic(this.platform.Characteristic.Model, accessory.model)
.setCharacteristic(this.platform.Characteristic.SerialNumber, accessory.currentdoorstate);
if (accessory.currentdoorstate) {
this.platform.shng.addMonitor(accessory.currentdoorstate, this.shngCurrentDoorStateCallback.bind(this));
this.deviceService.getCharacteristic(this.platform.Characteristic.CurrentDoorState)
.onGet(this.getCurrentDoorState.bind(this))
.onSet(this.setCurrentDoorState.bind(this));
} else {
this.platform.log.error('GarageDoor: missing \"currentdoorstate\" in config.json!');
}
if (accessory.targetdoorstate) {
this.platform.shng.addMonitor(accessory.targetdoorstate, this.shngTargetDoorStateCallback.bind(this));
this.deviceService.getCharacteristic(this.platform.Characteristic.TargetDoorState)
.onGet(this.getTargetDoorState.bind(this))
.onSet(this.setTargetDoorState.bind(this));
} else {
this.platform.log.error('GarageDoor: missing \"targetdoorstate\" in config.json!');
}
if (accessory.obstructiondetected) {
this.platform.shng.addMonitor(accessory.obstructiondetected, this.shngObstructionDetectedCallback.bind(this));
this.deviceService.getCharacteristic(this.platform.Characteristic.ObstructionDetected)
.onGet(this.getObstructionDetected.bind(this))
.onSet(this.setObstructionDetected.bind(this));
}
this.platform.log.info("GarageDoor '%s' created!", accessory.name);
}
identify(): void {
this.platform.log.info('Identify!');
}
getServices(): Service[] {
return [this.informationService, this.deviceService];
}
getCurrentDoorState(): Nullable<CharacteristicValue> {
this.platform.log.info('getCurrentDoorState:', this.accessory.name, 'is currently', this.currentDoorState);
return this.currentDoorState;
}
setCurrentDoorState(value: CharacteristicValue) {
this.currentDoorState = value as number;
this.platform.log.info('setCurrentDoorState:', this.accessory.name, 'was set to', this.currentDoorState);
this.platform.shng.setItem(this.accessory.currentdoorstate, this.currentDoorState);
}
getTargetDoorState(): Nullable<CharacteristicValue> {
this.platform.log.info('getTargetDoorState:', this.accessory.name, 'is currently', this.targetDoorState);
return this.targetDoorState;
}
setTargetDoorState(value: CharacteristicValue) {
this.targetDoorState = value as number;
this.platform.log.info('setTargetDoorState:', this.accessory.name, 'was set to', this.targetDoorState);
this.platform.shng.setItem(this.accessory.targetdoorstate, this.targetDoorState);
}
getObstructionDetected(): Nullable<CharacteristicValue> {
this.platform.log.info('getObstructionDetected:', this.accessory.name, 'is currently', this.obstructionDetected);
return this.obstructionDetected;
}
setObstructionDetected(value: CharacteristicValue) {
this.obstructionDetected = value as boolean;
this.platform.log.info('setObstructionDetected:', this.accessory.name, 'was set to', this.obstructionDetected);
this.platform.shng.setItem(this.accessory.obstructiondetected, this.obstructionDetected);
}
shngCurrentDoorStateCallback(value: unknown): void {
this.platform.log.debug('shngCurrentDoorStateCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'number') {
this.currentDoorState = value;
this.deviceService.updateCharacteristic(this.platform.Characteristic.CurrentDoorState, this.currentDoorState);
} else {
this.platform.log.warn('Unknown type ', typeof value, 'received for', this.accessory.name + ':', value);
}
}
shngTargetDoorStateCallback(value: unknown): void {
this.platform.log.debug('shngTargetDoorStateCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'number') {
this.targetDoorState = value;
this.deviceService.updateCharacteristic(this.platform.Characteristic.TargetDoorState, this.targetDoorState);
} else {
this.platform.log.warn('Unknown type ', typeof value, 'received for', this.accessory.name + ':', value);
}
}
shngObstructionDetectedCallback(value: unknown): void {
this.platform.log.debug('shngObstructionDetectedCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'boolean') {
this.obstructionDetected = value;
this.deviceService.updateCharacteristic(this.platform.Characteristic.ObstructionDetected, this.obstructionDetected);
} else {
this.platform.log.warn('Unknown type ', typeof value, 'received for', this.accessory.name + ':', value);
}
}
}

View File

@ -0,0 +1,59 @@
import {
AccessoryPlugin,
CharacteristicValue,
Service,
Nullable,
} from 'homebridge';
import { SmartHomeNGPlatform } from '../platform';
export class HumiditySensor implements AccessoryPlugin {
private readonly deviceService: Service;
private readonly informationService: Service;
public name: string;
private currentHumidity = 0;
constructor(private readonly platform: SmartHomeNGPlatform, private readonly accessory) {
this.name = accessory.name;
this.deviceService = new this.platform.Service.HumiditySensor(accessory.name);
// create handlers for required characteristics
this.deviceService.getCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity)
.onGet(this.getCurrentHumidity.bind(this));
this.informationService =
new this.platform.Service.AccessoryInformation()
.setCharacteristic(this.platform.Characteristic.Manufacturer, accessory.manufacturer)
.setCharacteristic(this.platform.Characteristic.Model, accessory.model)
.setCharacteristic(this.platform.Characteristic.SerialNumber, accessory.currenthumidity);
this.platform.shng.addMonitor(accessory.currenthumidity, this.shngCallback.bind(this));
this.platform.log.info('HumiditySensor', accessory.name, 'created!');
}
identify(): void {
this.platform.log.info('Identify!');
}
getServices(): Service[] {
return [this.informationService, this.deviceService];
}
getCurrentHumidity(): Nullable<CharacteristicValue> {
this.platform.log.debug('getCurrentHumidity:', this.accessory.name, '=', this.currentHumidity);
return this.currentHumidity;
}
shngCallback(value: unknown): void {
this.platform.log.debug('shngCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'number') {
this.currentHumidity = value;
} else {
this.platform.log.warn('Unknown type', typeof value, 'received for', this.accessory.name + ':', value);
}
this.deviceService.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, this.currentHumidity);
}
}

View File

@ -0,0 +1,300 @@
import {
AccessoryPlugin,
CharacteristicValue,
Service,
Nullable,
} from 'homebridge';
import { SmartHomeNGPlatform } from '../platform';
type RGBW = {
R: number;
G: number;
B: number;
W: number;
};
type RGB = {
R: number;
G: number;
B: number;
};
enum LightType { ONOFF, DIMMABLE, RGB, RGBW, HSB }
export class Lightbulb implements AccessoryPlugin {
private readonly deviceService: Service;
private readonly informationService: Service;
public name: string;
private switchOn = false;
private brightness = 0; brightnessMin = 0; private brightnessMax = 1000;
private R = 0; RMin = 0; RMax = 100;
private G = 0; GMin = 0; GMax = 100;
private B = 0; BMin = 0; BMax = 100;
private W = 0; WMin = 0; WMax = 100;
private hue = 0;
private saturation = 0;
private lightType = LightType.ONOFF;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(private readonly platform: SmartHomeNGPlatform, private readonly accessory: any) {
this.name = accessory.name;
this.deviceService = new this.platform.Service.Lightbulb(accessory.name);
// create handlers for required characteristics
this.deviceService.getCharacteristic(this.platform.Characteristic.On)
.onGet(this.getOn.bind(this))
.onSet(this.setOn.bind(this));
this.platform.shng.addMonitor(accessory.on, this.shngOnCallback.bind(this));
if (accessory.brightness) {
this.lightType = LightType.DIMMABLE;
}
if (accessory.hue && accessory.saturation && accessory.brightness) {
this.lightType = LightType.HSB;
}
if (accessory.r && accessory.g && accessory.b) {
this.lightType = LightType.RGB;
if (accessory.w) {
this.lightType = LightType.RGBW;
}
}
// Characteristic dimmable is valid for every light except simple ON/OFF
if (this.lightType !== LightType.ONOFF) {
this.deviceService.getCharacteristic(this.platform.Characteristic.Brightness)
.onGet(this.getBrightness.bind(this))
.onSet(this.setBrightness.bind(this));
this.platform.shng.addMonitor(accessory.brightness, this.shngBrightnessCallback.bind(this));
}
// If HSB, RGB or RGBW light
if (this.lightType === LightType.HSB || this.lightType === LightType.RGB || this.lightType === LightType.RGBW) {
this.deviceService.getCharacteristic(this.platform.Characteristic.Hue)
.onGet(this.getHue.bind(this))
.onSet(this.setHue.bind(this));
this.deviceService.getCharacteristic(this.platform.Characteristic.Saturation)
.onGet(this.getSaturation.bind(this))
.onSet(this.setSaturation.bind(this));
if (this.lightType === LightType.RGB || this.lightType === LightType.RGBW) {
this.platform.shng.addMonitor(accessory.R, this.shngRCallback.bind(this));
this.platform.shng.addMonitor(accessory.G, this.shngGCallback.bind(this));
this.platform.shng.addMonitor(accessory.B, this.shngBCallback.bind(this));
if (this.lightType === LightType.RGBW) {
this.platform.shng.addMonitor(accessory.W, this.shngWCallback.bind(this));
}
}
}
this.informationService =
new this.platform.Service.AccessoryInformation()
.setCharacteristic(this.platform.Characteristic.Manufacturer, accessory.manufacturer)
.setCharacteristic(this.platform.Characteristic.Model, accessory.model)
.setCharacteristic(this.platform.Characteristic.SerialNumber, accessory.on);
this.brightnessMax = accessory.brightnessmax ? accessory.brightnessmax : this.brightnessMax;
this.brightnessMin = accessory.brightnessmin ? accessory.brightnessmin : this.brightnessMin;
this.RMax = accessory.rmax ? accessory.rmax : this.RMax;
this.RMin = accessory.rmin ? accessory.rmin : this.RMin;
this.GMax = accessory.gmax ? accessory.gmax : this.GMax;
this.GMin = accessory.gmin ? accessory.gmin : this.GMin;
this.BMax = accessory.bmax ? accessory.bmax : this.BMax;
this.BMin = accessory.bmin ? accessory.bmin : this.BMin;
this.WMax = accessory.wmax ? accessory.wmax : this.WMax;
this.WMin = accessory.wmin ? accessory.wmin : this.WMin;
this.platform.log.info("Lightbulb '%s' created! (" + LightType[this.lightType] + ')', accessory.name);
}
identify(): void {
this.platform.log.info('Identify!');
}
getServices(): Service[] {
return [this.informationService, this.deviceService];
}
getOn(): Nullable<CharacteristicValue> {
this.platform.log.info('GetOn:', this.accessory.name, 'is currently', (this.switchOn ? 'ON' : 'OFF'));
return this.switchOn;
}
setOn(value: CharacteristicValue) {
this.switchOn = value as boolean;
this.platform.log.info('SetOn:', this.accessory.name, 'was set to', (this.switchOn ? 'ON' : 'OFF'));
this.platform.shng.setItem(this.accessory.on, this.switchOn);
}
getBrightness(): Nullable<CharacteristicValue> {
this.platform.log.info('getBrightness:', this.accessory.name, 'brightness is currently', this.brightness);
return this.brightness;
}
setBrightness(value: CharacteristicValue) {
this.brightness = value as number;
this.platform.log.info('setBrightness:', this.accessory.name, 'was set to', value);
this.platform.shng.setItem(
this.accessory.brightness,
this.convertRange(this.brightness, 0, 100, this.brightnessMin, this.brightnessMax),
);
this.updateColor();
}
getHue(): Nullable<CharacteristicValue> {
this.platform.log.info('getHue:', this.accessory.name, 'hue is currently', this.brightness);
return this.hue;
}
setHue(value: CharacteristicValue) {
this.hue = value as number;
this.platform.log.info('setHue:', this.accessory.name, 'was set to', value);
this.updateColor();
}
getSaturation(): Nullable<CharacteristicValue> {
this.platform.log.info('getSaturation:', this.accessory.name, 'saturation is currently', this.brightness);
return this.saturation;
}
setSaturation(value: CharacteristicValue) {
this.saturation = value as number;
this.platform.log.info('setSaturation:', this.accessory.name, 'was set to', value);
this.updateColor();
}
updateColor(): void {
switch (this.lightType) {
case LightType.RGBW: {
const rgbw: RGBW = this.hsb2rgbw(this.hue, this.saturation, this.brightness);
this.platform.shng.setItem(this.accessory.r, this.convertRange(rgbw.R, 0, 100, this.RMin, this.RMax));
this.platform.shng.setItem(this.accessory.g, this.convertRange(rgbw.G, 0, 100, this.GMin, this.GMax));
this.platform.shng.setItem(this.accessory.b, this.convertRange(rgbw.B, 0, 100, this.BMin, this.BMax));
this.platform.shng.setItem(this.accessory.w, this.convertRange(rgbw.W, 0, 100, this.WMin, this.WMax));
break;
}
case LightType.RGB: {
const rgb: RGB = this.hsb2rgb(this.hue, this.saturation, this.brightness);
this.platform.shng.setItem(this.accessory.r, this.convertRange(rgb.R, 0, 100, this.RMin, this.RMax));
this.platform.shng.setItem(this.accessory.g, this.convertRange(rgb.G, 0, 100, this.GMin, this.GMax));
this.platform.shng.setItem(this.accessory.b, this.convertRange(rgb.B, 0, 100, this.BMin, this.BMax));
break;
}
case LightType.HSB:
this.platform.shng.setItem(this.accessory.hue, this.hue);
this.platform.shng.setItem(this.accessory.saturation, this.saturation);
this.platform.shng.setItem(this.accessory.brightness, this.brightness);
break;
default:
this.platform.log.debug('Cannot update color of', this.name, 'because the light does not support colors');
break;
}
}
shngOnCallback(value: unknown): void {
this.platform.log.debug('shngOnCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'boolean') {
this.switchOn = value;
this.deviceService.updateCharacteristic(this.platform.Characteristic.On, this.switchOn);
} else {
this.platform.log.warn('Unknown type ', typeof value, 'received for', this.accessory.name + ':', value);
}
}
shngBrightnessCallback(value: unknown): void {
this.platform.log.debug('shngBrightnessCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'number') {
this.brightness = this.convertRange(value as number, this.brightnessMin, this.brightnessMax, 0, 100);
this.deviceService.updateCharacteristic(this.platform.Characteristic.Brightness, this.brightness);
} else {
this.platform.log.warn('Unknown type ', typeof value, 'received for', this.accessory.name + ':', value);
}
}
shngRCallback(value: unknown): void {
// TODO
}
shngGCallback(value: unknown): void {
// TODO
}
shngBCallback(value: unknown): void {
// TODO
}
shngWCallback(value: unknown): void {
// TODO
}
// Credits to: https://github.com/Sunoo/homebridge-gpio-rgbw-ledstrip/blob/master/src/index.ts
hsb2rgbw(H: number, S: number, B: number): RGBW {
const rgbw = { R: 0, G: 0, B: 0, W: 0 };
if (H === 0 && S === 0) {
rgbw.W = B;
} else {
const segment = Math.floor(H / 60);
const offset = H % 60;
const mid = B * offset / 60;
rgbw.W = Math.round(B / 100 * (100 - S));
if (segment === 0) {
rgbw.R = Math.round(S / 100 * B);
rgbw.G = Math.round(S / 100 * mid);
} else if (segment === 1) {
rgbw.R = Math.round(S / 100 * (B - mid));
rgbw.G = Math.round(S / 100 * B);
} else if (segment === 2) {
rgbw.G = Math.round(S / 100 * B);
rgbw.B = Math.round(S / 100 * mid);
} else if (segment === 3) {
rgbw.G = Math.round(S / 100 * (B - mid));
rgbw.B = Math.round(S / 100 * B);
} else if (segment === 4) {
rgbw.R = Math.round(S / 100 * mid);
rgbw.B = Math.round(S / 100 * B);
} else if (segment === 5) {
rgbw.R = Math.round(S / 100 * B);
rgbw.B = Math.round(S / 100 * (B - mid));
}
}
return rgbw;
}
// Credits to: https://www.30secondsofcode.org/js/s/hsb-to-rgb
hsb2rgb(H: number, S: number, B: number): RGB {
const rgb = { R: 0, G: 0, B: 0 };
S /= 100;
B /= 100;
const k = (n) => (n + H / 60) % 6;
const f = (n) => B * (1 - S * Math.max(0, Math.min(k(n), 4 - k(n), 1)));
rgb.R = f(5) + 100;
rgb.G = f(3) + 100;
rgb.B = f(1) + 100;
return rgb;
}
convertRange(value: number, oldmin: number, oldmax: number, newmin: number, newmax: number): number {
const result = (((value - oldmin) * (newmax - newmin)) / (oldmax - oldmin)) + newmin;
this.platform.log.debug(
'Transposing', value,
'from range', oldmin, '-', oldmax,
'to', newmin, '-', newmax,
'=', result,
);
return result;
}
}

View File

@ -0,0 +1,59 @@
import {
AccessoryPlugin,
CharacteristicValue,
Service,
Nullable,
} from 'homebridge';
import { SmartHomeNGPlatform } from '../platform';
export class MotionSensor implements AccessoryPlugin {
private readonly deviceService: Service;
private readonly informationService: Service;
public name: string;
private motionDetected = false;
constructor(private readonly platform: SmartHomeNGPlatform, private readonly accessory) {
this.name = accessory.name;
this.deviceService = new this.platform.Service.MotionSensor(accessory.name);
// create handlers for required characteristics
this.deviceService.getCharacteristic(this.platform.Characteristic.MotionDetected)
.onGet(this.handleMotionDetectedGet.bind(this));
this.informationService =
new this.platform.Service.AccessoryInformation()
.setCharacteristic(this.platform.Characteristic.Manufacturer, accessory.manufacturer)
.setCharacteristic(this.platform.Characteristic.Model, accessory.model)
.setCharacteristic(this.platform.Characteristic.SerialNumber, accessory.motiondetected);
this.platform.shng.addMonitor(accessory.motiondetected, this.shngCallback.bind(this));
this.platform.log.info('MotionSensor', accessory.name, 'created!');
}
identify(): void {
this.platform.log.info('Identify!');
}
getServices(): Service[] {
return [this.informationService, this.deviceService];
}
handleMotionDetectedGet(): Nullable<CharacteristicValue> {
this.platform.log.debug('handleMotionDetectedGet:', this.accessory.name, '=', this.motionDetected ? 'True' : 'False');
return this.motionDetected;
}
shngCallback(value: unknown): void {
this.platform.log.debug('shngCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'boolean') {
this.motionDetected = value;
} else {
this.platform.log.warn('Unknown type', typeof value, 'received for', this.accessory.name + ':', value);
}
this.deviceService.updateCharacteristic(this.platform.Characteristic.MotionDetected, this.motionDetected);
}
}

View File

@ -0,0 +1,63 @@
import {
AccessoryPlugin,
CharacteristicValue,
Service,
Nullable,
} from 'homebridge';
import { SmartHomeNGPlatform } from '../platform';
export class OccupancySensor implements AccessoryPlugin {
private readonly deviceService: Service;
private readonly informationService: Service;
public name: string;
private occupencyDetected = this.platform.Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED;
constructor(private readonly platform: SmartHomeNGPlatform, private readonly accessory) {
this.name = accessory.name;
this.deviceService = new this.platform.Service.OccupancySensor(accessory.name);
// create handlers for required characteristics
this.deviceService.getCharacteristic(this.platform.Characteristic.OccupancyDetected)
.onGet(this.handleOccupancyDetectedGet.bind(this));
this.informationService =
new this.platform.Service.AccessoryInformation()
.setCharacteristic(this.platform.Characteristic.Manufacturer, accessory.manufacturer)
.setCharacteristic(this.platform.Characteristic.Model, accessory.model)
.setCharacteristic(this.platform.Characteristic.SerialNumber, accessory.occupancydetected);
this.platform.shng.addMonitor(accessory.occupancydetected, this.shngAccupancyDetectedCallback.bind(this));
this.platform.log.info('OccupancySensor', accessory.name, 'created!');
}
identify(): void {
this.platform.log.info('Identify!');
}
getServices(): Service[] {
return [this.informationService, this.deviceService];
}
handleOccupancyDetectedGet(): Nullable<CharacteristicValue> {
this.platform.log.debug('handleOccupancyDetectedGet:', this.accessory.name, '=', this.occupencyDetected ? 'True' : 'False');
return this.occupencyDetected;
}
shngAccupancyDetectedCallback(value: unknown): void {
this.platform.log.debug('shngAccupancyDetectedCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'boolean') {
if (value) {
this.occupencyDetected = this.platform.Characteristic.OccupancyDetected.OCCUPANCY_DETECTED;
} else {
this.occupencyDetected = this.platform.Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED;
}
} else {
this.platform.log.warn('Unknown type', typeof value, 'received for', this.accessory.name + ':', value);
}
this.deviceService.updateCharacteristic(this.platform.Characteristic.OccupancyDetected, this.occupencyDetected);
}
}

64
src/Accessories/Outlet.ts Normal file
View File

@ -0,0 +1,64 @@
import {
AccessoryPlugin,
CharacteristicValue,
Service,
Nullable,
} from 'homebridge';
import { SmartHomeNGPlatform } from '../platform';
export class Outlet implements AccessoryPlugin {
private readonly deviceService: Service;
private readonly informationService: Service;
public name: string;
private switchOn = false;
constructor(private readonly platform: SmartHomeNGPlatform, private readonly accessory) {
this.name = accessory.name;
this.deviceService = new this.platform.Service.Outlet(accessory.name);
// create handlers for required characteristics
this.deviceService.getCharacteristic(this.platform.Characteristic.On)
.onGet(this.getOn.bind(this))
.onSet(this.setOn.bind(this));
this.informationService =
new this.platform.Service.AccessoryInformation()
.setCharacteristic(this.platform.Characteristic.Manufacturer, accessory.manufacturer)
.setCharacteristic(this.platform.Characteristic.Model, accessory.model)
.setCharacteristic(this.platform.Characteristic.SerialNumber, accessory.on);
this.platform.shng.addMonitor(accessory.on, this.shngCallback.bind(this));
this.platform.log.info("Outlet '%s' created!", accessory.name);
}
identify(): void {
this.platform.log.info('Identify!');
}
getServices(): Service[] {
return [this.informationService, this.deviceService];
}
getOn(): Nullable<CharacteristicValue> {
this.platform.log.info('GetOn:', this.accessory.name, 'is currently', (this.switchOn ? 'ON' : 'OFF'));
return this.switchOn;
}
setOn(value: CharacteristicValue) {
this.switchOn = value as boolean;
this.platform.log.info('SetOn:', this.accessory.name, 'was set to', (this.switchOn ? 'ON' : 'OFF'));
this.platform.shng.setItem(this.accessory.on, this.switchOn);
}
shngCallback(value: unknown): void {
this.platform.log.debug('shngCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'boolean') {
this.switchOn = value;
this.deviceService.updateCharacteristic(this.platform.Characteristic.On, this.switchOn);
} else {
this.platform.log.warn('Unknown type ', typeof value, 'received for', this.accessory.name + ':', value);
}
}
}

View File

@ -0,0 +1,94 @@
import {
AccessoryPlugin,
CharacteristicValue,
Service,
Nullable,
} from 'homebridge';
import { SmartHomeNGPlatform } from '../platform';
export class SecuritySystem implements AccessoryPlugin {
private readonly deviceService: Service;
private readonly informationService: Service;
public name: string;
private currentState = this.platform.Characteristic.SecuritySystemCurrentState.DISARMED;
private targetState = this.platform.Characteristic.SecuritySystemTargetState.DISARM;
constructor(private readonly platform: SmartHomeNGPlatform, private readonly accessory) {
this.name = accessory.name;
this.deviceService = new this.platform.Service.SecuritySystem(accessory.name);
// create handlers for required characteristics
this.deviceService.getCharacteristic(this.platform.Characteristic.SecuritySystemCurrentState)
.onGet(this.handleSecuritySystemCurrentStateGet.bind(this));
this.deviceService.getCharacteristic(this.platform.Characteristic.SecuritySystemTargetState)
.onGet(this.handleSecuritySystemTargetStateGet.bind(this))
.onSet(this.handleSecuritySystemTargetStateSet.bind(this));
this.informationService =
new this.platform.Service.AccessoryInformation()
.setCharacteristic(this.platform.Characteristic.Manufacturer, accessory.manufacturer)
.setCharacteristic(this.platform.Characteristic.Model, accessory.model)
.setCharacteristic(this.platform.Characteristic.SerialNumber, accessory.currentstate);
this.platform.shng.addMonitor(accessory.currentstate, this.shngCurrentStateCallback.bind(this));
this.platform.shng.addMonitor(accessory.targetState, this.shngTargetStateCallback.bind(this));
this.platform.log.info('SecuritySystem', accessory.name, 'created!');
}
identify(): void {
this.platform.log.info('Identify!');
}
getServices(): Service[] {
return [this.informationService, this.deviceService];
}
handleSecuritySystemCurrentStateGet(): Nullable<CharacteristicValue> {
this.platform.log.info(
'handleSecuritySystemCurrentStateGet:',
this.accessory.name, '=',
this.currentState,
);
return this.currentState;
}
handleSecuritySystemTargetStateGet(): Nullable<CharacteristicValue> {
this.platform.log.info(
'handleSecuritySystemTargetStateGet:', this.accessory.name,
'is currently', this.targetState,
);
return this.targetState;
}
handleSecuritySystemTargetStateSet(value: CharacteristicValue) {
this.targetState = value as number;
this.platform.log.info('handleSecuritySystemTargetStateSet:', this.accessory.name, 'was set to', this.targetState);
this.platform.shng.setItem(this.accessory.targetstate, this.targetState);
}
shngCurrentStateCallback(value: unknown): void {
this.platform.log.debug('shngCurrentStateCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'number') {
this.currentState = value;
this.deviceService.updateCharacteristic(this.platform.Characteristic.SecuritySystemCurrentState, this.currentState);
} else {
this.platform.log.warn('Unknown type ', typeof value, 'received for', this.accessory.name + ':', value);
}
}
shngTargetStateCallback(value: unknown): void {
this.platform.log.debug('shngTargetStateCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'number') {
this.targetState = value;
this.deviceService.updateCharacteristic(this.platform.Characteristic.SecuritySystemTargetState, this.targetState);
} else {
this.platform.log.warn('Unknown type ', typeof value, 'received for', this.accessory.name + ':', value);
}
}
}

64
src/Accessories/Switch.ts Normal file
View File

@ -0,0 +1,64 @@
import {
AccessoryPlugin,
CharacteristicValue,
Service,
Nullable,
} from 'homebridge';
import { SmartHomeNGPlatform } from '../platform';
export class Switch implements AccessoryPlugin {
private readonly deviceService: Service;
private readonly informationService: Service;
public name: string;
private switchOn = false;
constructor(private readonly platform: SmartHomeNGPlatform, private readonly accessory) {
this.name = accessory.name;
this.deviceService = new this.platform.Service.Switch(accessory.name);
// create handlers for required characteristics
this.deviceService.getCharacteristic(this.platform.Characteristic.On)
.onGet(this.getOn.bind(this))
.onSet(this.setOn.bind(this));
this.informationService =
new this.platform.Service.AccessoryInformation()
.setCharacteristic(this.platform.Characteristic.Manufacturer, accessory.manufacturer)
.setCharacteristic(this.platform.Characteristic.Model, accessory.model)
.setCharacteristic(this.platform.Characteristic.SerialNumber, accessory.onoff);
this.platform.shng.addMonitor(accessory.onoff, this.shngCallback.bind(this));
this.platform.log.info("Switch '%s' created!", accessory.name);
}
identify(): void {
this.platform.log.info('Identify!');
}
getServices(): Service[] {
return [this.informationService, this.deviceService];
}
getOn(): Nullable<CharacteristicValue> {
this.platform.log.info('GetOn:', this.accessory.name, 'is currently', (this.switchOn ? 'ON' : 'OFF'));
return this.switchOn;
}
setOn(value: CharacteristicValue) {
this.switchOn = value as boolean;
this.platform.log.info('SetOn:', this.accessory.name, 'was set to', (this.switchOn ? 'ON' : 'OFF'));
this.platform.shng.setItem(this.accessory.onoff, this.switchOn);
}
shngCallback(value: unknown): void {
this.platform.log.debug('shngCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'boolean') {
this.switchOn = value;
this.deviceService.updateCharacteristic(this.platform.Characteristic.On, this.switchOn);
} else {
this.platform.log.warn('Unknown type ', typeof value, 'received for', this.accessory.name + ':', value);
}
}
}

View File

@ -0,0 +1,59 @@
import {
AccessoryPlugin,
CharacteristicValue,
Service,
Nullable,
} from 'homebridge';
import { SmartHomeNGPlatform } from '../platform';
export class TemperatureSensor implements AccessoryPlugin {
private readonly deviceService: Service;
private readonly informationService: Service;
public name: string;
private currentTemperature = 0;
constructor(private readonly platform: SmartHomeNGPlatform, private readonly accessory) {
this.name = accessory.name;
this.deviceService = new this.platform.Service.TemperatureSensor(accessory.name);
// create handlers for required characteristics
this.deviceService.getCharacteristic(this.platform.Characteristic.CurrentTemperature)
.onGet(this.getCurrentTemperature.bind(this));
this.informationService =
new this.platform.Service.AccessoryInformation()
.setCharacteristic(this.platform.Characteristic.Manufacturer, accessory.manufacturer)
.setCharacteristic(this.platform.Characteristic.Model, accessory.model)
.setCharacteristic(this.platform.Characteristic.SerialNumber, accessory.currenttemperature);
this.platform.shng.addMonitor(accessory.currenttemperature, this.shngCallback.bind(this));
this.platform.log.info('TemperatureSensor', accessory.name, 'created!');
}
identify(): void {
this.platform.log.info('Identify!');
}
getServices(): Service[] {
return [this.informationService, this.deviceService];
}
getCurrentTemperature(): Nullable<CharacteristicValue> {
this.platform.log.debug('getCurrentTemperature:', this.accessory.name, '=', this.currentTemperature);
return this.currentTemperature;
}
shngCallback(value: unknown): void {
this.platform.log.debug('shngCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'number') {
this.currentTemperature = value;
} else {
this.platform.log.warn('Unknown type', typeof value, 'received for', this.accessory.name + ':', value);
}
this.deviceService.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, this.currentTemperature);
}
}

View File

@ -0,0 +1,153 @@
import {
AccessoryPlugin,
CharacteristicValue,
Service,
Nullable,
} from 'homebridge';
import { SmartHomeNGPlatform } from '../platform';
export class Thermostat implements AccessoryPlugin {
private readonly deviceService: Service;
private readonly informationService: Service;
public name: string;
private currentTemperature = 0;
private targetTemperature = 0;
private targetTemperatureDisplayUnit = this.platform.Characteristic.TemperatureDisplayUnits.CELSIUS;
private currentHeatingCoolingState = this.platform.Characteristic.CurrentHeatingCoolingState.OFF;
private targetHeatingCoolingState = this.platform.Characteristic.TargetHeatingCoolingState.AUTO;
constructor(private readonly platform: SmartHomeNGPlatform, private readonly accessory) {
this.name = accessory.name;
this.deviceService = new this.platform.Service.Thermostat(accessory.name);
// create handlers for required characteristics
this.deviceService.getCharacteristic(this.platform.Characteristic.CurrentTemperature)
.onGet(this.getCurrentTemperature.bind(this));
this.deviceService.getCharacteristic(this.platform.Characteristic.TargetTemperature)
.onGet(this.getTargetTemperature.bind(this))
.onSet(this.setTargetTemperature.bind(this));
this.deviceService.getCharacteristic(this.platform.Characteristic.TemperatureDisplayUnits)
.onGet(this.getTemperatureDisplayUnits.bind(this))
.onSet(this.setTemperatureDisplayUnits.bind(this));
this.deviceService.getCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState)
.onGet(this.getCurrentHeatingCoolingState.bind(this));
this.deviceService.getCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState)
.onGet(this.getTargetHeatingCoolingState.bind(this))
.onSet(this.setTargetHeatingCoolingState.bind(this));
this.informationService =
new this.platform.Service.AccessoryInformation()
.setCharacteristic(this.platform.Characteristic.Manufacturer, accessory.manufacturer)
.setCharacteristic(this.platform.Characteristic.Model, accessory.model)
.setCharacteristic(this.platform.Characteristic.SerialNumber, accessory.currenttemperature);
// Add items to SHNG WS monitor
this.platform.shng.addMonitor(accessory.currenttemperature, this.shngCurrentTemperatureCallback.bind(this));
this.platform.shng.addMonitor(accessory.targettemperature, this.shngTargetTemperatureCallback.bind(this));
this.platform.shng.addMonitor(accessory.currentheatingcoolingstate, this.shngCurrentHeatingCoolingStateCallback.bind(this));
// FUTURE: Add monitoring of target Heating / Cooling states amd maybe display units
this.platform.log.info('Thermostat', accessory.name, 'created!');
}
identify(): void {
this.platform.log.info('Identify!');
}
getServices(): Service[] {
return [this.informationService, this.deviceService];
}
getCurrentTemperature(): Nullable<CharacteristicValue> {
this.platform.log.debug('getCurrentTemperature:', this.accessory.name, '=', this.currentTemperature);
return this.currentTemperature;
}
getTargetTemperature(): Nullable<CharacteristicValue> {
this.platform.log.debug('getTargetTemperature:', this.accessory.name, '=', this.targetTemperature);
return this.targetTemperature;
}
setTargetTemperature(value: CharacteristicValue) {
this.platform.log.debug('setTargetTemperature:', this.accessory.name, 'to', value);
this.targetTemperature = value as number;
this.platform.shng.setItem(this.accessory.targettemperature, this.targetTemperature);
}
getTemperatureDisplayUnits(): Nullable<CharacteristicValue> {
this.platform.log.debug('getTemperatureDisplayUnits:', this.accessory.name, '=', this.targetTemperatureDisplayUnit);
return this.targetTemperatureDisplayUnit;
}
setTemperatureDisplayUnits(value: CharacteristicValue) {
this.platform.log.debug('setTemperatureDisplayUnits:', this.accessory.name, 'to', value);
this.targetTemperatureDisplayUnit = value as number;
// FUTURE: Bind this to SHNG if needed
}
getCurrentHeatingCoolingState(): Nullable<CharacteristicValue> {
this.platform.log.debug('getCurrentHeatingCoolingState:', this.accessory.name, '=', this.currentHeatingCoolingState);
return this.currentHeatingCoolingState;
}
getTargetHeatingCoolingState(): Nullable<CharacteristicValue> {
this.platform.log.debug('getTargetHeatingCoolingState:', this.accessory.name, '=', this.targetHeatingCoolingState);
return this.targetHeatingCoolingState;
}
setTargetHeatingCoolingState(value: CharacteristicValue) {
this.platform.log.debug('setTargetHeatingCoolingState:', this.accessory.name, 'to', value);
this.targetHeatingCoolingState = value as number;
// FUTURE: Bind this to SHNG if needed
}
shngCurrentTemperatureCallback(value: unknown): void {
this.platform.log.debug('shngCurrentTemperatureCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'number') {
this.currentTemperature = value;
} else {
this.platform.log.warn('Unknown type', typeof value, 'received for', this.accessory.name + ':', value);
}
this.deviceService.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, this.currentTemperature);
}
shngTargetTemperatureCallback(value: unknown): void {
this.platform.log.debug('shngTargetTemperatureCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'number') {
this.targetTemperature = value;
} else {
this.platform.log.warn('Unknown type', typeof value, 'received for', this.accessory.name + ':', value);
}
this.deviceService.updateCharacteristic(this.platform.Characteristic.TargetTemperature, this.targetTemperature);
}
shngCurrentHeatingCoolingStateCallback(value: unknown): void {
this.platform.log.debug('shngCurrentHeatingCoolingStateCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'number') {
switch (value) {
case 1:
this.currentHeatingCoolingState = this.platform.Characteristic.CurrentHeatingCoolingState.HEAT;
break;
case 2:
this.currentHeatingCoolingState = this.platform.Characteristic.CurrentHeatingCoolingState.COOL;
break;
default:
this.currentHeatingCoolingState = this.platform.Characteristic.CurrentHeatingCoolingState.OFF;
break;
}
this.deviceService.updateCharacteristic(
this.platform.Characteristic.CurrentHeatingCoolingState,
this.currentHeatingCoolingState,
);
} else {
this.platform.log.warn('Unknown type', typeof value, 'received for', this.accessory.name + ':', value);
}
}
}

View File

@ -0,0 +1,66 @@
import {
AccessoryPlugin,
CharacteristicValue,
Service,
Nullable,
} from 'homebridge';
import { SmartHomeNGPlatform } from '../platform';
export class Doorbell implements AccessoryPlugin {
private readonly deviceService: Service;
private readonly informationService: Service;
public name: string;
//private event = this.platform.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS;
constructor(private readonly platform: SmartHomeNGPlatform, private readonly accessory) {
this.name = accessory.name;
this.deviceService = new this.platform.Service.Doorbell(accessory.name);
// create handlers for required characteristics
this.deviceService.getCharacteristic(this.platform.Characteristic.ProgrammableSwitchEvent)
.onGet(this.handleProgrammableSwitchEventGet.bind(this));
this.informationService =
new this.platform.Service.AccessoryInformation()
.setCharacteristic(this.platform.Characteristic.Manufacturer, accessory.manufacturer)
.setCharacteristic(this.platform.Characteristic.Model, accessory.model)
.setCharacteristic(this.platform.Characteristic.SerialNumber, accessory.singlepress);
this.platform.shng.addMonitor(accessory.contactstate, this.shngCallback.bind(this));
this.platform.log.info('Doorbell', accessory.name, 'created!');
}
identify(): void {
this.platform.log.info('Identify!');
}
getServices(): Service[] {
return [this.informationService, this.deviceService];
}
handleProgrammableSwitchEventGet(): Nullable<CharacteristicValue> {
this.platform.log.debug(
'handleProgrammableSwitchEventGet:',
this.accessory.name, '=',
0,
);
return this.platform.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS;
}
shngCallback(value: unknown): void {
this.platform.log.debug('shngCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'boolean') {
if (value) {
this.deviceService.updateCharacteristic(
this.platform.Characteristic.ProgrammableSwitchEvent,
this.platform.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS,
);
}
} else {
this.platform.log.warn('Unknown type', typeof value, 'received for', this.accessory.name + ':', value);
}
}
}

View File

@ -0,0 +1,263 @@
import {
AccessoryPlugin,
CharacteristicValue,
Service,
Nullable,
} from 'homebridge';
import { SmartHomeNGPlatform } from '../platform';
export class WindowCovering implements AccessoryPlugin {
private readonly deviceService: Service;
private readonly informationService: Service;
public name: string;
private currentPosition = 0; private currentPositionMin = 0; private currentPositionMax = 100;
private currentPositionDecimals = 0; private currentPositionInverted = false;
private targetPosition = 0; private targetPositionMin = 0; private targetPositionMax = 100;
private targetPositionDecimals = 0; private targetPositionInverted = false;
private positionState = this.platform.Characteristic.PositionState.STOPPED;
private currentHorizontalTiltAngle = 0; private targetHorizontalTiltAngle = 0;
private currentVerticalTiltAngle = 0; private targetVerticalTiltAngle = 0;
constructor(private readonly platform: SmartHomeNGPlatform, private readonly accessory) {
this.name = accessory.name;
this.deviceService = new this.platform.Service.WindowCovering(accessory.name);
// create handlers for required characteristics
this.deviceService.getCharacteristic(this.platform.Characteristic.CurrentPosition)
.onGet(this.getCurrentPosition.bind(this));
this.deviceService.getCharacteristic(this.platform.Characteristic.TargetPosition)
.onGet(this.getTargetPosition.bind(this))
.onSet(this.setTargetPosition.bind(this));
this.deviceService.getCharacteristic(this.platform.Characteristic.CurrentHorizontalTiltAngle)
.onGet(this.getCurrentHorizontalTiltAngle.bind(this));
this.deviceService.getCharacteristic(this.platform.Characteristic.TargetHorizontalTiltAngle)
.onGet(this.getTargetHorizontalTiltAngle.bind(this))
.onSet(this.setTargetHorizontalTiltAngle.bind(this));
this.deviceService.getCharacteristic(this.platform.Characteristic.CurrentVerticalTiltAngle)
.onGet(this.getCurrentVerticalTiltAngle.bind(this));
this.deviceService.getCharacteristic(this.platform.Characteristic.TargetVerticalTiltAngle)
.onGet(this.getTargetVerticalTiltAngle.bind(this))
.onSet(this.setTargetVerticalTiltAngle.bind(this));
this.deviceService.getCharacteristic(this.platform.Characteristic.PositionState)
.onGet(this.getPositionState.bind(this));
this.informationService =
new this.platform.Service.AccessoryInformation()
.setCharacteristic(this.platform.Characteristic.Manufacturer, accessory.manufacturer)
.setCharacteristic(this.platform.Characteristic.Model, accessory.model)
.setCharacteristic(this.platform.Characteristic.SerialNumber, accessory.currentposition);
this.platform.shng.addMonitor(accessory.currentposition, this.shngCurrentPositionCallback.bind(this));
this.platform.shng.addMonitor(accessory.targetposition, this.shngTargetPositionCallback.bind(this));
if (accessory.currenthorizontaltiltangle) {
this.platform.shng.addMonitor(accessory.currenthorizontaltiltangle, this.shngCurrentHorizontalTiltAngleCallback.bind(this));
}
if (accessory.targethorizontaltiltangle) {
this.platform.shng.addMonitor(accessory.targethorizontaltiltangle, this.shngTargetHorizontalTiltAngleCallback.bind(this));
}
if (accessory.currentverticaltiltangle) {
this.platform.shng.addMonitor(accessory.currentverticaltiltangle, this.shngCurrentVerticalTiltAngleCallback.bind(this));
}
if (accessory.targetverticaltiltangle) {
this.platform.shng.addMonitor(accessory.targetverticaltiltangle, this.shngTargetVerticalTiltAngleCallback.bind(this));
}
this.currentPositionMax = accessory.currentpositionmax ? accessory.currentpositionmax : this.currentPositionMax;
this.currentPositionMin = accessory.currentpositionmin ? accessory.currentpositionmin : this.currentPositionMin;
this.currentPositionDecimals = accessory.currentpositiondecimals ? accessory.currentpositiondecimals : this.currentPositionDecimals;
this.currentPositionInverted = accessory.currentpositioninverted ? accessory.currentpositioninverted : this.currentPositionInverted;
this.targetPositionMax = accessory.targetpositionmax ? accessory.targetpositionmax : this.targetPositionMax;
this.targetPositionMin = accessory.targetpositionmin ? accessory.targetpositionmin : this.targetPositionMin;
this.targetPositionDecimals = accessory.targetpositiondecimals ? accessory.targetpositiondecimals : this.targetPositionDecimals;
this.targetPositionInverted = accessory.targetpositioninverted ? accessory.targetpositioninverted : this.targetPositionInverted;
this.platform.log.info("WindowCovering '%s' created!", accessory.name);
}
identify(): void {
this.platform.log.info('Identify!');
}
getServices(): Service[] {
return [this.informationService, this.deviceService];
}
getCurrentPosition(): Nullable<CharacteristicValue> {
this.platform.log.info('getCurrentPosition:', this.accessory.name, 'is currently', this.currentPosition);
return this.currentPosition;
}
getPositionState(): Nullable<CharacteristicValue> {
this.platform.log.info('getPositionState:', this.accessory.name, 'is currently', this.positionState);
return this.positionState;
}
getTargetPosition(): Nullable<CharacteristicValue> {
this.platform.log.info('getTargetPosition:', this.accessory.name, 'is currently', this.targetPosition);
return this.targetPosition;
}
setTargetPosition(value: CharacteristicValue) {
this.targetPosition = value as number;
this.platform.log.info('setTargetPosition:', this.accessory.name, 'was set to', this.targetPosition);
const transposedTarget = this.convertRange(
value as number,
0, 100,
this.targetPositionMin, this.targetPositionMax,
this.targetPositionDecimals, this.targetPositionInverted,
);
this.platform.shng.setItem(this.accessory.targetposition, transposedTarget);
}
getCurrentHorizontalTiltAngle(): Nullable<CharacteristicValue> {
this.platform.log.info('getCurrentHorizontalTiltAngle:', this.accessory.name, 'is currently', this.currentHorizontalTiltAngle);
return this.currentHorizontalTiltAngle;
}
getTargetHorizontalTiltAngle(): Nullable<CharacteristicValue> {
this.platform.log.info('getTargetHorizontalTiltAngle:', this.accessory.name, 'is currently', this.targetHorizontalTiltAngle);
return this.targetHorizontalTiltAngle;
}
setTargetHorizontalTiltAngle(value: CharacteristicValue) {
this.targetHorizontalTiltAngle = value as number;
this.platform.log.info('setTargetHorizontalTiltAngle:', this.accessory.name, 'was set to', this.targetHorizontalTiltAngle);
this.platform.shng.setItem(this.accessory.targethorizontaltiltangle, this.targetHorizontalTiltAngle);
}
getCurrentVerticalTiltAngle(): Nullable<CharacteristicValue> {
this.platform.log.info('getCurrentVerticalTiltAngle:', this.accessory.name, 'is currently', this.currentVerticalTiltAngle);
return this.currentVerticalTiltAngle;
}
getTargetVerticalTiltAngle(): Nullable<CharacteristicValue> {
this.platform.log.info('getTargetVerticalTiltAngle:', this.accessory.name, 'is currently', this.targetVerticalTiltAngle);
return this.targetVerticalTiltAngle;
}
setTargetVerticalTiltAngle(value: CharacteristicValue) {
this.targetVerticalTiltAngle = value as number;
this.platform.log.info('setTargetVerticalTiltAngle:', this.accessory.name, 'was set to', this.targetVerticalTiltAngle);
this.platform.shng.setItem(this.accessory.targetverticaltiltangle, this.targetVerticalTiltAngle);
}
shngCurrentPositionCallback(value: unknown): void {
this.platform.log.debug('shngCurrentPositionCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'number') {
this.currentPosition = this.convertRange(
value as number,
this.currentPositionMin, this.currentPositionMax,
0, 100,
0, this.currentPositionInverted,
);
this.deviceService.updateCharacteristic(this.platform.Characteristic.CurrentPosition, this.currentPosition);
} else {
this.platform.log.warn('Unknown type ', typeof value, 'received for', this.accessory.name + ':', value);
}
this.updateDirection();
}
shngTargetPositionCallback(value: unknown): void {
this.platform.log.debug('shngTargetPositionCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'number') {
this.targetPosition = this.convertRange(
value as number,
this.targetPositionMin, this.targetPositionMax,
0, 100,
0, this.targetPositionInverted,
);
this.deviceService.updateCharacteristic(this.platform.Characteristic.TargetPosition, this.targetPosition);
} else {
this.platform.log.warn('Unknown type ', typeof value, 'received for', this.accessory.name + ':', value);
}
this.updateDirection();
}
shngCurrentHorizontalTiltAngleCallback(value: unknown): void {
this.platform.log.debug('shngCurrentHorizontalTiltAngleCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'number') {
this.currentHorizontalTiltAngle = value;
} else {
this.platform.log.warn('Unknown type ', typeof value, 'received for', this.accessory.name + ':', value);
}
}
shngTargetHorizontalTiltAngleCallback(value: unknown): void {
this.platform.log.debug('shngTargetHorizontalTiltAngleCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'number') {
this.targetHorizontalTiltAngle = value;
} else {
this.platform.log.warn('Unknown type ', typeof value, 'received for', this.accessory.name + ':', value);
}
}
shngCurrentVerticalTiltAngleCallback(value: unknown): void {
this.platform.log.debug('shngCurrentVerticalTiltAngleCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'number') {
this.currentVerticalTiltAngle = value;
} else {
this.platform.log.warn('Unknown type ', typeof value, 'received for', this.accessory.name + ':', value);
}
}
shngTargetVerticalTiltAngleCallback(value: unknown): void {
this.platform.log.debug('shngTargetVerticalTiltAngleCallback:', this.accessory.name, '=', value, '(' + typeof value + ')');
if (typeof value === 'number') {
this.targetVerticalTiltAngle = value;
} else {
this.platform.log.warn('Unknown type ', typeof value, 'received for', this.accessory.name + ':', value);
}
}
updateDirection() {
let direction;
if (this.targetPosition < this.currentPosition) {
direction = 'DECREASING';
this.positionState = this.platform.Characteristic.PositionState.DECREASING;
} else if (this.targetPosition > this.currentPosition) {
direction = 'INCREASING';
this.positionState = this.platform.Characteristic.PositionState.INCREASING;
} else {
direction = 'STOPPED';
this.positionState = this.platform.Characteristic.PositionState.STOPPED;
}
this.platform.log.debug(
'updateDirection for', this.accessory.name,
': current =', this.currentPosition,
'target =', this.targetPosition,
'direction:', direction,
);
}
// eslint-disable-next-line max-len
convertRange(value: number, oldmin: number, oldmax: number, newmin: number, newmax: number, decimals: number, inverted: boolean): number {
let result = (((value - oldmin) * (newmax - newmin)) / (oldmax - oldmin)) + newmin;
if(decimals > 0) {
result = parseFloat(result.toFixed(decimals));
} else {
result = Math.round(result);
}
if (inverted) {
result = newmax - result;
}
this.platform.log.debug(
'Transposing', value,
'from range', oldmin, '-', oldmax,
'to', newmin, '-', newmax,
'with inverted', inverted,
'and', decimals, 'decimals',
'=', result,
);
return result;
}
}

117
src/SmartHomeNG.ts Normal file
View File

@ -0,0 +1,117 @@
import WebSocket from 'ws';
import { SmartHomeNGPlatform } from './platform';
export class SmartHomeNG {
private ws: WebSocket;
private tomonitor = {};
public connected = false;
constructor(private readonly platform: SmartHomeNGPlatform, private url: string, private autoReconnectInterval = 10) {
this.platform.log.debug('SHNG constructor');
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.on('open', () => {
this.platform.log.info('Connected to SmartHomeNG on', this.url);
this.connected = true;
this.identifyMyself();
this.startMonitoring();
});
this.ws.on('message', (data: string) => {
this.receive(data);
});
this.ws.on('error', (error) => {
this.platform.log.error('WebSocket error: ' + error.toString());
});
this.ws.on('close', (code: number) => {
this.platform.log.warn('Lost connection (Code: ' + code + '). Reconnecting in', this.autoReconnectInterval, 'seconds.');
this.connected = false;
if (this.autoReconnectInterval > 0) {
this.reconnect();
}
});
}
reconnect() {
this.ws.removeAllListeners();
setTimeout(() => {
this.platform.log.info('WebSocketClient: reconnecting...');
this.connect();
}, this.autoReconnectInterval * 1000);
}
identifyMyself(): void {
const protocol = { 'cmd': 'proto', 'ver': 4 };
const identify = { 'cmd': 'identity', 'sw': 'homebridge-smarthomeng', 'ver': '2.0' };
this.send(protocol);
this.send(identify);
}
addMonitor(item: string, callback: (value: unknown) => void): void {
if (item !== null && item !== 'undefined') {
this.tomonitor[item] = callback;
}
}
startMonitoring(): void {
const itemlist: string[] = [];
for (const item in this.tomonitor) {
itemlist.push(item);
}
if (itemlist.length > 0) {
this.platform.log.info('Start monitoring ' + itemlist);
const buffer = {
'cmd': 'monitor',
'items': itemlist,
};
this.send(buffer);
} else {
this.platform.log.warn('No need to start monitoring because no items are defined !');
}
}
setItem(item: string, value: unknown): void {
this.platform.log.info('Sending value', value, 'for', item);
const command = { 'cmd': 'item', 'id': item, 'val': value };
this.send(command);
}
send(buffer: unknown): boolean {
const command = JSON.stringify(buffer);
if (this.connected) {
this.platform.log.debug('WS: sending:', command);
this.ws.send(command);
return true;
} else {
this.platform.log.warn('Error sending command !');
return false;
}
}
receive(data: string): void {
const msg = JSON.parse(data);
this.platform.log.debug('WS: received: %s', data);
if (msg.cmd === 'item' && msg.items) {
for (const item of msg.items) {
const name = item[0];
const value = item[1];
if (name in this.tomonitor) {
const callback = this.tomonitor[name];
this.platform.log.info('Received value', value, 'for', name, 'callback', callback);
callback(value);
} else {
this.platform.log.debug('Ignoring unmonitored item', name);
}
}
}
}
}

11
src/index.ts Normal file
View File

@ -0,0 +1,11 @@
import { API } from 'homebridge';
import { PLATFORM_NAME } from './settings';
import { SmartHomeNGPlatform } from './platform';
/**
* This method registers the platform with Homebridge
*/
export = (api: API) => {
api.registerPlatform(PLATFORM_NAME, SmartHomeNGPlatform);
};

176
src/platform.ts Normal file
View File

@ -0,0 +1,176 @@
import {
API,
StaticPlatformPlugin,
Logger,
PlatformConfig,
AccessoryPlugin,
Service,
Characteristic,
} from 'homebridge';
import { SmartHomeNG } from './SmartHomeNG';
import { Switch } from './Accessories/Switch';
import { Outlet } from './Accessories/Outlet';
import { Fan } from './Accessories/Fan';
import { Lightbulb } from './Accessories/Lightbulb';
import { TemperatureSensor } from './Accessories/TemperatureSensor';
import { Thermostat } from './Accessories/Thermostat';
import { WindowCovering } from './Accessories/WindowCovering';
import { ContactSensor } from './Accessories/ContactSensor';
import { Doorbell } from './Accessories/Doorbell';
import { SecuritySystem } from './Accessories/SecuritySystem';
import { OccupancySensor } from './Accessories/OccupancySensor';
import { MotionSensor } from './Accessories/MotionSensor';
import { GarageDoor } from './Accessories/GarageDoor';
import { HumiditySensor } from './Accessories/HumiditySensor';
function uncapitalizeKeys(obj): Record<string, unknown> {
function isObject(o: unknown): boolean {
return Object.prototype.toString.apply(o) === '[object Object]';
}
function isArray(o: unknown): boolean {
return Object.prototype.toString.apply(o) === '[object Array]';
}
const transformedObj = isArray(obj) ? [] : {};
for (const key in obj) {
const transformedKey = key.toLowerCase();
if (isObject(obj[key]) || isArray(obj[key])) {
transformedObj[transformedKey] = uncapitalizeKeys(obj[key]);
} else {
transformedObj[transformedKey] = obj[key];
}
}
return transformedObj;
}
export class SmartHomeNGPlatform implements StaticPlatformPlugin {
public readonly Service: typeof Service = this.api.hap.Service;
public readonly Characteristic: typeof Characteristic = this.api.hap.Characteristic;
public shng: SmartHomeNG;
constructor(
public readonly log: Logger,
public readonly config: PlatformConfig,
public readonly api: API,
) {
log.debug('Using config file', api.user.configPath());
const host = this.config.host ? this.config.host : 'smarthome.local';
const port = this.config.port ? this.config.port : '2424';
const protocol = this.config.tls ? 'wss' : 'ws';
const url = protocol + '://' + host + ':' + port + '/';
this.shng = new SmartHomeNG(this, url);
this.api.on('didFinishLaunching', () => {
log.debug('Executed didFinishLaunching callback');
this.shng.connect();
});
}
accessories(callback: (foundAccessories: AccessoryPlugin[]) => void): void {
this.log.debug('Building accessories list...');
const devices: AccessoryPlugin[] = [];
// convert all configured accessory keys to lowercase to tolerate user case errors
const accessories = JSON.parse(JSON.stringify(uncapitalizeKeys(this.config.accessories)));
this.log.debug(accessories);
if (Object.keys(accessories).length > 0) {
for (const accessory of accessories) {
if (!accessory.manufacturer) {
accessory.manufacturer = 'SmartHomeNG';
}
if (!accessory.model) {
accessory.model = 'SHNG Item';
}
if (accessory.type !== '') {
this.log.info('Parsing accessory:', accessory.name, 'of type', accessory.type);
// set lo lowercase to ignore user case errors
switch (accessory.type.toLowerCase()) {
// Presence sensor
case 'occupancysensor':
devices.push(new OccupancySensor(this, accessory));
break;
// Motion sensor
case 'motionsensor':
devices.push(new MotionSensor(this, accessory));
break;
// Contact sensor
case 'contactsensor':
devices.push(new ContactSensor(this, accessory));
break;
// Doorbell
case 'doorbell':
devices.push(new Doorbell(this, accessory));
break;
// Security system
case 'securitysystem':
devices.push(new SecuritySystem(this, accessory));
break;
// Switch
case 'switch':
devices.push(new Switch(this, accessory));
break;
// Lightbulb
case 'lightbulb':
devices.push(new Lightbulb(this, accessory));
break;
// Outlet
case 'outlet':
devices.push(new Outlet(this, accessory));
break;
// Fan (uses Fanv2)
case 'fan':
devices.push(new Fan(this, accessory));
break;
// Temperature sensor
case 'temperaturesensor':
devices.push(new TemperatureSensor(this, accessory));
break;
// Thermostat
case 'thermostat':
devices.push(new Thermostat(this, accessory));
break;
// WindowCovering
case 'windowcovering':
devices.push(new WindowCovering(this, accessory));
break;
// Garage door
case 'garagedoor':
devices.push(new GarageDoor(this, accessory));
break;
// Humidity sensor
case 'humiditysensor':
devices.push(new HumiditySensor(this, accessory));
break;
// Show error for (yet ?) unsupported device
default:
this.log.warn('Accessory type', accessory.type, 'for', accessory.name, 'not recognized !');
break;
}
} else {
this.log.warn('Ignoring accessory (no type given): ' + accessory);
}
}
}
callback(devices);
this.log.debug('Finished building accessories list');
}
}

9
src/settings.ts Normal file
View File

@ -0,0 +1,9 @@
/**
* This is the name of the platform that users will use to register the plugin in the Homebridge config.json
*/
export const PLATFORM_NAME = 'SmartHomeNG';
/**
* This must match the name of your plugin as defined the package.json
*/
export const PLUGIN_NAME = 'homebridge-smarthomeng';

29
tsconfig.json Normal file
View File

@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "ES2019",
"module": "commonjs",
"lib": [
"es2015",
"es2016",
"es2017",
"es2018",
"es2019"
],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"noImplicitAny": false
},
"include": [
"src/",
"src/Accessories/.ts"
],
"exclude": [
"**/*.spec.ts",
"node_modules"
]
}