First v2 dev commit
This commit is contained in:
parent
0ab30244a6
commit
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
|
||||
}
|
||||
1
README.md
Executable file → Normal file
1
README.md
Executable file → Normal file
@ -158,4 +158,3 @@ 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
|
||||
}
|
||||
|
||||
16
config.schema.json
Normal file
16
config.schema.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"pluginAlias": "SmartHomeNG-dev",
|
||||
"pluginType": "platform",
|
||||
"singular": true,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"title": "Name",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"default": "SmartHomeNG"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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"
|
||||
}
|
||||
}
|
||||
6400
package-lock.json
generated
Normal file
6400
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
53
package.json
Executable file → Normal file
53
package.json
Executable file → Normal file
@ -1,23 +1,44 @@
|
||||
{
|
||||
"name": "homebridge-smarthomeng",
|
||||
"version": "1.3.7",
|
||||
"description": "Platform plugin for SmartHomeNG: https://github.com/smarthomeNG/smarthome",
|
||||
"license": "GPL",
|
||||
"private": true,
|
||||
"displayName": "SmartHomeNG",
|
||||
"name": "homebridge-smarthomeng2",
|
||||
"version": "2.0.0",
|
||||
"description": "A short description about what your plugin does.",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/Foxi352/homebridge-smarthomeng2.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/Foxi352/homebridge-smarthomeng2/issues"
|
||||
},
|
||||
"engines": {
|
||||
"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"
|
||||
],
|
||||
"repository": {
|
||||
"type": "https",
|
||||
"url": "https://github.com/Foxi352/homebridge-smarthomeng.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/Foxi352/homebridge-smarthomeng/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.12.0",
|
||||
"homebridge": ">=0.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"websocket": ">=1.0.0"
|
||||
"lodash": "^4.17.21",
|
||||
"ws": "^8.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.10.9",
|
||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||
"@typescript-eslint/parser": "^5.0.0",
|
||||
"eslint": "^8.0.1",
|
||||
"homebridge": "^1.3.5",
|
||||
"nodemon": "^2.0.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-node": "^10.3.0",
|
||||
"typescript": "^4.4.4"
|
||||
}
|
||||
}
|
||||
|
||||
64
src/Accessories/Fan.ts
Normal file
64
src/Accessories/Fan.ts
Normal file
@ -0,0 +1,64 @@
|
||||
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;
|
||||
|
||||
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.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.shng.addMonitor(accessory.active, this.shngCallback.bind(this));
|
||||
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);
|
||||
}
|
||||
|
||||
shngCallback(value: unknown): void {
|
||||
this.platform.log.debug('shngCallback:', 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
260
src/Accessories/Lightbulb.ts
Normal file
260
src/Accessories/Lightbulb.ts
Normal file
@ -0,0 +1,260 @@
|
||||
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;
|
||||
};
|
||||
|
||||
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 = 0; // 0 = OnOff Light, 1 = Dimmable Light, 2 = RGB, 3 = RGBW
|
||||
|
||||
// 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.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));
|
||||
this.lightType = 1; // Dimmable light
|
||||
}
|
||||
|
||||
// If RGB or RGBW light
|
||||
if (accessory.r && accessory.g && accessory.b) {
|
||||
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));
|
||||
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));
|
||||
this.lightType = 2; // RGB light
|
||||
|
||||
if (accessory.w) {
|
||||
this.platform.shng.addMonitor(accessory.W, this.shngWCallback.bind(this));
|
||||
this.lightType = 3; // RGBW light
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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!", 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 {
|
||||
if (this.lightType === 3) {
|
||||
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));
|
||||
} else if (this.lightType === 2) {
|
||||
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));
|
||||
} else {
|
||||
this.platform.log.warn('Cannot update color of', this.name, 'because RGB(W) items are missing');
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return (((value - oldmin) * (newmax - newmin)) / (oldmax - oldmin)) + newmin;
|
||||
}
|
||||
}
|
||||
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.shngCallback.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;
|
||||
}
|
||||
|
||||
shngCallback(value: unknown): void {
|
||||
this.platform.log.debug('shngCallback:', 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
116
src/SmartHomeNG.ts
Normal file
116
src/SmartHomeNG.ts
Normal file
@ -0,0 +1,116 @@
|
||||
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 {
|
||||
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);
|
||||
};
|
||||
130
src/platform.ts
Normal file
130
src/platform.ts
Normal file
@ -0,0 +1,130 @@
|
||||
import {
|
||||
API,
|
||||
StaticPlatformPlugin,
|
||||
Logger,
|
||||
PlatformConfig,
|
||||
AccessoryPlugin,
|
||||
Service,
|
||||
Characteristic,
|
||||
} from 'homebridge';
|
||||
import { SmartHomeNG } from './SmartHomeNG';
|
||||
|
||||
import { OccupancySensor } from './Accessories/OccupancySensor';
|
||||
import { MotionSensor } from './Accessories/MotionSensor';
|
||||
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';
|
||||
|
||||
const uncapitalizeKeys = (obj) => {
|
||||
const isObject = o => Object.prototype.toString.apply(o) === '[object Object]';
|
||||
const isArray = o => 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());
|
||||
this.shng = new SmartHomeNG(this, 'ws://smarthome.iot.wagener.family:2424/');
|
||||
|
||||
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);
|
||||
|
||||
for (const accessory of accessories) {
|
||||
//this.log.debug(accessory);
|
||||
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;
|
||||
|
||||
// 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;
|
||||
|
||||
// TemperatureSensor
|
||||
case 'temperaturesensor':
|
||||
devices.push(new TemperatureSensor(this, accessory));
|
||||
break;
|
||||
|
||||
// TemperatureSensor
|
||||
case 'thermostat':
|
||||
devices.push(new Thermostat(this, accessory));
|
||||
break;
|
||||
|
||||
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';
|
||||
26
tsconfig.json
Normal file
26
tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2018", // ~node10
|
||||
"module": "commonjs",
|
||||
"lib": [
|
||||
"es2015",
|
||||
"es2016",
|
||||
"es2017",
|
||||
"es2018"
|
||||
],
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"noImplicitAny": false
|
||||
},
|
||||
"include": [
|
||||
"src/"
|
||||
],
|
||||
"exclude": [
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user