Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61dd132fad | ||
|
|
a13242328e | ||
|
|
03618c6362 | ||
|
|
867a1ebbe3 | ||
|
|
6cb8f72028 | ||
|
|
4eba434852 | ||
|
|
568cc551fb | ||
|
|
89edac94e0 | ||
|
|
a8e1812d9f | ||
|
|
3ac1dbf683 | ||
|
|
4971bf389a | ||
|
|
d3e9629ec0 | ||
|
|
f49839b39f | ||
|
|
64e65d16ae | ||
|
|
d3a5407f29 | ||
|
|
d56a3a98e9 | ||
|
|
ed016e28c6 | ||
|
|
0a6eb90eb7 | ||
|
|
43e3fdf35f | ||
|
|
d07d621e45 | ||
|
|
c8d117a894 | ||
|
|
9a6c9e681d | ||
|
|
0394d789f8 | ||
|
|
84ed5cc74a | ||
|
|
59fe9963a1 | ||
|
|
0beada795f | ||
|
|
e2d30353fb | ||
|
|
1a2e5c051d | ||
|
|
5f0b025e25 | ||
|
|
f4700332cc | ||
|
|
323c313f3c | ||
|
|
ae63f6cdee | ||
|
|
bbc42dfb7e | ||
|
|
c6f596e9e5 | ||
|
|
3561e0c3d6 | ||
|
|
447e0a68ef | ||
|
|
6f373379ae | ||
|
|
6b46c9cdb8 | ||
|
|
77df0609ca | ||
|
|
abe95f516f | ||
|
|
a3e7b85f17 | ||
|
|
ba2505853c | ||
|
|
e0611aed2c | ||
|
|
9a1709591f | ||
|
|
a03b5f787b | ||
|
|
de26802e1d | ||
|
|
525f250f32 | ||
|
|
ff5b1f37b9 | ||
|
|
f7947cdc2a | ||
|
|
0cfb5d3b66 | ||
|
|
c66e843fc8 | ||
|
|
a7a266d470 | ||
|
|
7bbc75bf6b | ||
|
|
660fd07e2e | ||
|
|
3248262e7a | ||
|
|
60545b2909 | ||
|
|
bd2c931eaa | ||
|
|
63ee296462 | ||
|
|
2d45bc6e3d | ||
|
|
f9ae224ddc | ||
|
|
995eab7d20 | ||
|
|
b2a287934c | ||
|
|
0167799c54 | ||
|
|
f6450f9463 | ||
|
|
b95352b05b | ||
|
|
a8c5b1b9e6 | ||
|
|
9c360dff2d | ||
|
|
6eceb78843 | ||
|
|
7c5f4dd552 | ||
|
|
37eba7b60b | ||
|
|
b902603047 |
38
.eslintrc
Normal file
38
.eslintrc
Normal 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
44
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal 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
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal 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
|
||||
23
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
23
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal 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. -->
|
||||
38
.github/ISSUE_TEMPLATE/support-request.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/support-request.md
vendored
Normal 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
31
.github/workflows/build.yml
vendored
Normal 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
120
.gitignore
vendored
Normal 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
135
.npmignore
Normal 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
5
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
}
|
||||
8
.vscode/settings.json
vendored
Normal file
8
.vscode/settings.json
vendored
Normal 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
636
README.md
Executable file → Normal file
@ -1,156 +1,543 @@
|
||||
# homebridge-smarthomeng
|
||||
Homebridge plugin for SmartHomeNG. This is work in progress and not all devices are supported yet.
|
||||
|
||||
## Currently supported
|
||||
[](https://www.npmjs.com/package/homebridge-smarthomeng)
|
||||
[](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
|
||||
[](https://www.npmjs.com/package/homebridge-smarthomeng)
|
||||
[](https://www.npmjs.com/package/homebridge-smarthomeng)
|
||||
[](https://www.npmjs.com/package/homebridge-smarthomeng)
|
||||
[](https://www.npmjs.com/package/homebridge-smarthomeng)
|
||||
|
||||
## Currently supported accessories
|
||||
This plugin currently supports the following services (and characteristics):
|
||||
|
||||
* LightBulb (on/off, brightness, hue, saturation)
|
||||
* Fan (on/off)
|
||||
* Temperature sensor (current temperature)
|
||||
* Thermostat (current- / target temperature)
|
||||
* Window (current- / target position)
|
||||
* Window Covering (current- / target position)
|
||||
* Motion sensor (motion detected)
|
||||
* Occupancy sensor (motion detected)
|
||||
* Contact Sensor (contact state)
|
||||
* Switch (on/off)
|
||||
| Type | Description |
|
||||
|:-----------------------------------------|:--------------------------------------------------------|
|
||||
| [ContactSensor](#contact-sensor) | Simple contact sensor, for example for windows |
|
||||
| [Doorbell](#doorbell) | Doorbell, sends message to devices on ring |
|
||||
| [Fan](#fan) | Simple on/off fan, may be extended in future |
|
||||
| [GarageDoor](#garage-door) | Garage door opener |
|
||||
| [HumiditySensor](#humidity-sensor) | Humidity sensor |
|
||||
| [Lightbulb](#lightbulb) | Everything, from simple light to dimmable, RGB and RGBW |
|
||||
| [MotionSensor](#motion-sensor) | Detects and reports motion |
|
||||
| [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
|
||||
* [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
|
||||
### Install nodejs >= 0.12.
|
||||
You have to find out the right way for your OS.
|
||||
## Installation of plugin
|
||||
|
||||
|
||||
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
|
||||
Use [Homebridge Config UI X](https://github.com/oznu/homebridge-config-ui-x#readme) or install manually using ```npm install -g homebridge-smarthomeng```
|
||||
|
||||
|
||||
## 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": {
|
||||
"name": "HBDEV",
|
||||
"name": "SmartHomeNG",
|
||||
"username": "CC:22:3D:E3:DE:37",
|
||||
"port": 51826,
|
||||
"pin": "031-45-154"
|
||||
"port": 51138,
|
||||
"pin": "655-59-9284"
|
||||
},
|
||||
|
||||
"platforms": [
|
||||
{
|
||||
"platform": "SmartHomeNG",
|
||||
|
||||
"name": "SmartHomeNG",
|
||||
"host": "srvsmarthome.ha.swa.lu",
|
||||
"host": "smarthome.iot.wagener.family",
|
||||
"port": 2425,
|
||||
"tls": true,
|
||||
"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",
|
||||
"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",
|
||||
"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."
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -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
34
config.schema.json
Normal 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
679
index.js
@ -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
12
nodemon.json
Normal 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
5283
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
42
package.json
Executable file → Normal file
42
package.json
Executable file → Normal file
@ -1,23 +1,43 @@
|
||||
{
|
||||
"private": false,
|
||||
"displayName": "SmartHomeNG",
|
||||
"name": "homebridge-smarthomeng",
|
||||
"version": "1.3.7",
|
||||
"description": "Platform plugin for SmartHomeNG: https://github.com/smarthomeNG/smarthome",
|
||||
"license": "GPL",
|
||||
"keywords": [
|
||||
"homebridge-plugin"
|
||||
],
|
||||
"version": "2.0.8",
|
||||
"description": "SmartHomeNG plugin for Homebridge",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "https",
|
||||
"url": "https://github.com/Foxi352/homebridge-smarthomeng.git"
|
||||
"type": "git",
|
||||
"url": "git://github.com/Foxi352/homebridge-smarthomeng.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/Foxi352/homebridge-smarthomeng/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.12.0",
|
||||
"homebridge": ">=0.2.0"
|
||||
"node": ">=14.18.1",
|
||||
"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": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
66
src/Accessories/ContactSensor.ts
Normal file
66
src/Accessories/ContactSensor.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
66
src/Accessories/Doorbell.ts
Normal file
66
src/Accessories/Doorbell.ts
Normal 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
92
src/Accessories/Fan.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
130
src/Accessories/GarageDoor.ts
Normal file
130
src/Accessories/GarageDoor.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
59
src/Accessories/HumiditySensor.ts
Normal file
59
src/Accessories/HumiditySensor.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
300
src/Accessories/Lightbulb.ts
Normal file
300
src/Accessories/Lightbulb.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
59
src/Accessories/MotionSensor.ts
Normal file
59
src/Accessories/MotionSensor.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
63
src/Accessories/OccupancySensor.ts
Normal file
63
src/Accessories/OccupancySensor.ts
Normal 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
64
src/Accessories/Outlet.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
94
src/Accessories/SecuritySystem.ts
Normal file
94
src/Accessories/SecuritySystem.ts
Normal 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
64
src/Accessories/Switch.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
59
src/Accessories/TemperatureSensor.ts
Normal file
59
src/Accessories/TemperatureSensor.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
153
src/Accessories/Thermostat.ts
Normal file
153
src/Accessories/Thermostat.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
66
src/Accessories/VideoDoorbell.ts
Normal file
66
src/Accessories/VideoDoorbell.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
263
src/Accessories/WindowCovering.ts
Normal file
263
src/Accessories/WindowCovering.ts
Normal 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
117
src/SmartHomeNG.ts
Normal 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
11
src/index.ts
Normal 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
176
src/platform.ts
Normal 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
9
src/settings.ts
Normal 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
29
tsconfig.json
Normal 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"
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user