homebridge-http-webhooks-plus 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +543 -0
- package/LICENSE +674 -0
- package/README.md +505 -0
- package/config.schema.json +1821 -0
- package/docs/COMPATIBILITY.md +73 -0
- package/index.js +35 -0
- package/package.json +56 -0
- package/src/Constants.js +12 -0
- package/src/Server.js +192 -0
- package/src/Util.js +51 -0
- package/src/homekit/HttpWebHooksPlatform.js +136 -0
- package/src/homekit/accessories/HttpWebHookCarbonDioxideSensorAccessory.js +75 -0
- package/src/homekit/accessories/HttpWebHookDoorbellAccessory.js +139 -0
- package/src/homekit/accessories/HttpWebHookFanv2Accessory.js +320 -0
- package/src/homekit/accessories/HttpWebHookGarageDoorOpenerAccessory.js +142 -0
- package/src/homekit/accessories/HttpWebHookLightBulbAccessory.js +153 -0
- package/src/homekit/accessories/HttpWebHookLockMechanismAccessory.js +125 -0
- package/src/homekit/accessories/HttpWebHookOutletAccessory.js +125 -0
- package/src/homekit/accessories/HttpWebHookPushButtonAccessory.js +93 -0
- package/src/homekit/accessories/HttpWebHookSecurityAccessory.js +104 -0
- package/src/homekit/accessories/HttpWebHookSensorAccessory.js +169 -0
- package/src/homekit/accessories/HttpWebHookStatelessSwitchAccessory.js +101 -0
- package/src/homekit/accessories/HttpWebHookSwitchAccessory.js +95 -0
- package/src/homekit/accessories/HttpWebHookThermostatAccessory.js +164 -0
- package/src/homekit/accessories/HttpWebHookValveAccessory.js +151 -0
- package/src/homekit/accessories/HttpWebHookWindowCoveringAccessory.js +203 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Migrating to HTTP Webhooks Plus 0.2.1
|
|
2
|
+
|
|
3
|
+
This release republishes the upstream 0.2.0 implementation under the independent
|
|
4
|
+
package name `homebridge-http-webhooks-plus`. All `src/` files, the configuration
|
|
5
|
+
schema, runtime dependencies and declared runtime requirements are unchanged.
|
|
6
|
+
The package name passed to Homebridge registration is updated; the platform and
|
|
7
|
+
accessory aliases are preserved. No new state modes or feedback API ship in 0.2.1.
|
|
8
|
+
|
|
9
|
+
## Existing HomeKit accessories
|
|
10
|
+
|
|
11
|
+
Keep the existing `"platform": "HttpWebHooks"` configuration and every configured
|
|
12
|
+
accessory ID and name. Preserve the existing main/child bridge username, pairing,
|
|
13
|
+
port, persistent storage, service types and accessory configuration. Do not remove
|
|
14
|
+
the bridge or accessories in Apple Home, recreate the platform, clear cached
|
|
15
|
+
accessories or reset pairing as part of this package replacement.
|
|
16
|
+
|
|
17
|
+
Homebridge's static-platform UUID construction uses the configured platform
|
|
18
|
+
identifier and the accessory UUID base/name. With the existing unqualified
|
|
19
|
+
`HttpWebHooks` identifier and unchanged configuration, this release preserves
|
|
20
|
+
those inputs and the complete accessory implementation. Package-registration
|
|
21
|
+
tests and exact upstream runtime fingerprints protect this release boundary.
|
|
22
|
+
The owner's actual HomeKit rooms, scenes and automations still need checking
|
|
23
|
+
after a backed-up migration; source checks cannot verify a particular Home.
|
|
24
|
+
|
|
25
|
+
A package-qualified identifier such as
|
|
26
|
+
`homebridge-http-webhooks.HttpWebHooks` needs a separately reviewed migration:
|
|
27
|
+
changing its prefix can change the UUID seed. The same caution applies to
|
|
28
|
+
package-qualified standalone accessory identifiers. Do not blindly replace
|
|
29
|
+
every occurrence of the old package name in configuration or persistent data.
|
|
30
|
+
|
|
31
|
+
## Package replacement
|
|
32
|
+
|
|
33
|
+
1. Record the installed version and location, Node/Homebridge versions and any
|
|
34
|
+
locally modified source files. Make a private backup of the package, full
|
|
35
|
+
Homebridge configuration, persistent storage and pairing data.
|
|
36
|
+
2. Stop integrations that issue commands or publish state, then stop Homebridge
|
|
37
|
+
using the service manager appropriate to your installation.
|
|
38
|
+
3. Replace the old package with the pinned Plus version in the same Homebridge
|
|
39
|
+
plugin installation location. Do not load both packages together: their
|
|
40
|
+
platform and accessory aliases intentionally overlap.
|
|
41
|
+
4. If top-level plugin allowlists or disabled-plugin lists are used, migrate
|
|
42
|
+
the package entry deliberately. Retain the platform/accessory configuration
|
|
43
|
+
and bridge identity. Check external tools for saved package paths or integrity
|
|
44
|
+
receipts before restarting them.
|
|
45
|
+
5. Start Homebridge, verify the loaded package, and check that all existing
|
|
46
|
+
accessories remain in the same rooms and automations. Then resume dependent
|
|
47
|
+
integrations after their read-only readiness checks pass.
|
|
48
|
+
|
|
49
|
+
Installation directories vary between system packages, containers, global npm
|
|
50
|
+
and development setups. Use the existing Homebridge installation method; a
|
|
51
|
+
global npm command in an unrelated Node installation can affect the wrong copy.
|
|
52
|
+
|
|
53
|
+
## Locally patched installations
|
|
54
|
+
|
|
55
|
+
The public npm artifact contains generic upstream behavior. A locally patched
|
|
56
|
+
installation needs its patches preserved separately during this first migration.
|
|
57
|
+
Compare the exact original files and patched files, retain the installed hashes
|
|
58
|
+
and backups, and adapt any deployment receipts or saved paths. Do not assume
|
|
59
|
+
ordinary reinstalling retains changes made inside `node_modules`.
|
|
60
|
+
|
|
61
|
+
The package has no dependency on any particular controller or deployment script.
|
|
62
|
+
Future configurable features will allow integrations to stop patching installed
|
|
63
|
+
source. Integration-specific migration logic belongs to those integrations.
|
|
64
|
+
|
|
65
|
+
## State behavior retained from upstream
|
|
66
|
+
|
|
67
|
+
`targetdoorstate` represents the requested final door state; `currentdoorstate`
|
|
68
|
+
represents the reported state. Setting either through a webhook is a state update.
|
|
69
|
+
An existing HomeKit command handler can also optimistically publish final state
|
|
70
|
+
when its outgoing HTTP command succeeds. This first release preserves that
|
|
71
|
+
behavior, including existing limitations; HTTP acceptance does not prove physical
|
|
72
|
+
completion. Garage obstruction reporting is a separate characteristic and is
|
|
73
|
+
not a lock feature. See the README for the existing webhook fields.
|
package/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
var HttpWebHooksPlatform = require('./src/homekit/HttpWebHooksPlatform');
|
|
2
|
+
var HttpWebHookSensorAccessory = require('./src/homekit/accessories/HttpWebHookSensorAccessory');
|
|
3
|
+
var HttpWebHookSwitchAccessory = require('./src/homekit/accessories/HttpWebHookSwitchAccessory');
|
|
4
|
+
var HttpWebHookPushButtonAccessory = require('./src/homekit/accessories/HttpWebHookPushButtonAccessory');
|
|
5
|
+
var HttpWebHookDoorbellAccessory = require('./src/homekit/accessories/HttpWebHookDoorbellAccessory');
|
|
6
|
+
var HttpWebHookLightBulbAccessory = require('./src/homekit/accessories/HttpWebHookLightBulbAccessory');
|
|
7
|
+
var HttpWebHookThermostatAccessory = require('./src/homekit/accessories/HttpWebHookThermostatAccessory');
|
|
8
|
+
var HttpWebHookOutletAccessory = require('./src/homekit/accessories/HttpWebHookOutletAccessory');
|
|
9
|
+
var HttpWebHookSecurityAccessory = require('./src/homekit/accessories/HttpWebHookSecurityAccessory');
|
|
10
|
+
var HttpWebHookGarageDoorOpenerAccessory = require('./src/homekit/accessories/HttpWebHookGarageDoorOpenerAccessory');
|
|
11
|
+
var HttpWebHookStatelessSwitchAccessory = require('./src/homekit/accessories/HttpWebHookStatelessSwitchAccessory');
|
|
12
|
+
var HttpWebHookLockMechanismAccessory = require('./src/homekit/accessories/HttpWebHookLockMechanismAccessory');
|
|
13
|
+
var HttpWebHookWindowCoveringAccessory = require('./src/homekit/accessories/HttpWebHookWindowCoveringAccessory');
|
|
14
|
+
var HttpWebHookFanv2Accessory = require('./src/homekit/accessories/HttpWebHookFanv2Accessory');
|
|
15
|
+
var HttpWebHookCarbonDioxideSensoryAccessory = require('./src/homekit/accessories/HttpWebHookCarbonDioxideSensorAccessory');
|
|
16
|
+
var HttpWebHookValveAccessory = require('./src/homekit/accessories/HttpWebHookValveAccessory');
|
|
17
|
+
|
|
18
|
+
module.exports = function(homebridge) {
|
|
19
|
+
homebridge.registerPlatform("homebridge-http-webhooks-plus", "HttpWebHooks", HttpWebHooksPlatform);
|
|
20
|
+
homebridge.registerAccessory("homebridge-http-webhooks-plus", "HttpWebHookSensor", HttpWebHookSensorAccessory);
|
|
21
|
+
homebridge.registerAccessory("homebridge-http-webhooks-plus", "HttpWebHookSwitch", HttpWebHookSwitchAccessory);
|
|
22
|
+
homebridge.registerAccessory("homebridge-http-webhooks-plus", "HttpWebHookPushButton", HttpWebHookPushButtonAccessory);
|
|
23
|
+
homebridge.registerAccessory("homebridge-http-webhooks-plus", "HttpWebHookDoorbell", HttpWebHookDoorbellAccessory);
|
|
24
|
+
homebridge.registerAccessory("homebridge-http-webhooks-plus", "HttpWebHookLight", HttpWebHookLightBulbAccessory);
|
|
25
|
+
homebridge.registerAccessory("homebridge-http-webhooks-plus", "HttpWebHookThermostat", HttpWebHookThermostatAccessory);
|
|
26
|
+
homebridge.registerAccessory("homebridge-http-webhooks-plus", "HttpWebHookOutlet", HttpWebHookOutletAccessory);
|
|
27
|
+
homebridge.registerAccessory("homebridge-http-webhooks-plus", "HttpWebHookSecurity", HttpWebHookSecurityAccessory);
|
|
28
|
+
homebridge.registerAccessory("homebridge-http-webhooks-plus", "HttpWebHookGarageDoorOpener", HttpWebHookGarageDoorOpenerAccessory);
|
|
29
|
+
homebridge.registerAccessory("homebridge-http-webhooks-plus", "HttpWebHookStatelessSwitch", HttpWebHookStatelessSwitchAccessory);
|
|
30
|
+
homebridge.registerAccessory("homebridge-http-webhooks-plus", "HttpWebHookLockMechanism", HttpWebHookLockMechanismAccessory);
|
|
31
|
+
homebridge.registerAccessory("homebridge-http-webhooks-plus", "HttpWebHookWindowCovering", HttpWebHookWindowCoveringAccessory);
|
|
32
|
+
homebridge.registerAccessory("homebridge-http-webhooks-plus", "HttpWebHookFanv2", HttpWebHookFanv2Accessory);
|
|
33
|
+
homebridge.registerAccessory("homebridge-http-webhooks-plus", "HttpWebHookCarbonDioxideSensor", HttpWebHookCarbonDioxideSensoryAccessory);
|
|
34
|
+
homebridge.registerAccessory("homebridge-http-webhooks-plus", "HttpWebHookValve", HttpWebHookValveAccessory);
|
|
35
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "homebridge-http-webhooks-plus",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"displayName": "Homebridge HTTP Webhooks Plus",
|
|
5
|
+
"description": "HTTP webhooks and virtual accessories for Homebridge; independently maintained fork of homebridge-http-webhooks.",
|
|
6
|
+
"license": "GPL-3.0",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"homebridge-plugin",
|
|
9
|
+
"homebridge",
|
|
10
|
+
"plugin",
|
|
11
|
+
"http",
|
|
12
|
+
"webhook",
|
|
13
|
+
"httpwebhooks",
|
|
14
|
+
"http-webhooks",
|
|
15
|
+
"http webhooks",
|
|
16
|
+
"smart",
|
|
17
|
+
"homekit",
|
|
18
|
+
"siri"
|
|
19
|
+
],
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/pponce/homebridge-http-webhooks-plus.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/pponce/homebridge-http-webhooks-plus/issues"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=9.11.2",
|
|
29
|
+
"homebridge": "^1.8.4||^2.0.0-beta"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"http-auth": "^4.2.0",
|
|
33
|
+
"node-persist": "^2.1.0",
|
|
34
|
+
"request": "^2.88.2",
|
|
35
|
+
"selfsigned": "^2.4.1"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/pponce/homebridge-http-webhooks-plus#readme",
|
|
38
|
+
"author": "benzman81",
|
|
39
|
+
"files": [
|
|
40
|
+
"index.js",
|
|
41
|
+
"src/",
|
|
42
|
+
"config.schema.json",
|
|
43
|
+
"README.md",
|
|
44
|
+
"CHANGELOG.md",
|
|
45
|
+
"LICENSE",
|
|
46
|
+
"docs/COMPATIBILITY.md"
|
|
47
|
+
],
|
|
48
|
+
"scripts": {
|
|
49
|
+
"test": "node --test test/*.test.cjs",
|
|
50
|
+
"prepublishOnly": "npm test"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public",
|
|
54
|
+
"registry": "https://registry.npmjs.org/"
|
|
55
|
+
}
|
|
56
|
+
}
|
package/src/Constants.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
DEFAULT_REQUEST_TIMEOUT : 10000,
|
|
3
|
+
DEFAULT_PORT : 51828,
|
|
4
|
+
DEFAULT_SENSOR_TIMEOUT : 5000,
|
|
5
|
+
DEFAULT_CACHE_DIR : "./.node-persist/storage",
|
|
6
|
+
DEFAULT_LISTEN_HOST : "0.0.0.0",
|
|
7
|
+
COVERS_REQUEST_TIMEOUT : 35000,
|
|
8
|
+
CONTEXT_FROM_WEBHOOK : "fromHTTPWebhooks",
|
|
9
|
+
CONTEXT_FROM_TIMEOUTCALL : "fromTimeoutCall",
|
|
10
|
+
CERT_VERSION : 2,
|
|
11
|
+
CERT_DAYS : 365
|
|
12
|
+
};
|
package/src/Server.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
const Constants = require('./Constants');
|
|
2
|
+
|
|
3
|
+
var request = require("request");
|
|
4
|
+
var http = require('http');
|
|
5
|
+
var https = require('https');
|
|
6
|
+
var url = require('url');
|
|
7
|
+
var auth = require('http-auth');
|
|
8
|
+
var fs = require('fs');
|
|
9
|
+
var Service, Characteristic;
|
|
10
|
+
|
|
11
|
+
function Server(ServiceParam, CharacteristicParam, platform, platformConfig) {
|
|
12
|
+
Service = ServiceParam;
|
|
13
|
+
Characteristic = CharacteristicParam;
|
|
14
|
+
|
|
15
|
+
this.platform = platform;
|
|
16
|
+
this.log = platform.log;
|
|
17
|
+
this.storage = platform.storage;
|
|
18
|
+
|
|
19
|
+
this.webhookPort = platformConfig["webhook_port"] || Constants.DEFAULT_PORT;
|
|
20
|
+
this.webhookListenHost = platformConfig["webhook_listen_host"] || Constants.DEFAULT_LISTEN_HOST;
|
|
21
|
+
this.webhookEnableCORS = platformConfig["webhook_enable_cors"] || false;
|
|
22
|
+
this.httpAuthUser = platformConfig["http_auth_user"] || null;
|
|
23
|
+
this.httpAuthPass = platformConfig["http_auth_pass"] || null;
|
|
24
|
+
this.https = platformConfig["https"] === true;
|
|
25
|
+
this.httpsKeyFile = platformConfig["https_keyfile"];
|
|
26
|
+
this.httpsCertFile = platformConfig["https_certfile"];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
Server.prototype.setAccessories = function(accessories) {
|
|
30
|
+
this.accessories = accessories;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
Server.prototype.createSSLCertificate = function() {
|
|
34
|
+
this.log("Generating new ssl certificate.");
|
|
35
|
+
var selfsigned = require('selfsigned');
|
|
36
|
+
var certAttrs = [{ name: 'homebridgeHttpWebhooks', value: 'homebridgeHttpWebhooks.com' , type: 'homebridgeHttpWebhooks'}];
|
|
37
|
+
var certOpts = { days: Constants.CERT_DAYS};
|
|
38
|
+
certOpts.extensions = [{
|
|
39
|
+
name: 'subjectAltName',
|
|
40
|
+
altNames: [{
|
|
41
|
+
type: 2,
|
|
42
|
+
value: 'homebridgeHttpWebhooks.com'
|
|
43
|
+
}, {
|
|
44
|
+
type: 2,
|
|
45
|
+
value: 'localhost'
|
|
46
|
+
}]
|
|
47
|
+
}];
|
|
48
|
+
var pems = selfsigned.generate(certAttrs, certOpts);
|
|
49
|
+
var cachedSSLCert = pems;
|
|
50
|
+
cachedSSLCert.timestamp = Date.now();
|
|
51
|
+
cachedSSLCert.certVersion = Constants.CERT_VERSION;
|
|
52
|
+
return cachedSSLCert;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
Server.prototype.getSSLServerOptions = function() {
|
|
56
|
+
var sslServerOptions = {};
|
|
57
|
+
if(this.https) {
|
|
58
|
+
if(!this.httpsKeyFile || !this.httpsCertFile) {
|
|
59
|
+
this.log("Using automatic created ssl certificate.");
|
|
60
|
+
var cachedSSLCert = this.storage.getItemSync("http-webhook-ssl-cert");
|
|
61
|
+
if(cachedSSLCert) {
|
|
62
|
+
var certVersion = cachedSSLCert.certVersion;
|
|
63
|
+
var timestamp = Date.now() - cachedSSLCert.timestamp;
|
|
64
|
+
var diffInDays = timestamp/1000/60/60/24;
|
|
65
|
+
if(diffInDays > Constants.CERT_DAYS - 1 || certVersion !== Constants.CERT_VERSION) {
|
|
66
|
+
cachedSSLCert = null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if(!cachedSSLCert) {
|
|
70
|
+
cachedSSLCert = this.createSSLCertificate();
|
|
71
|
+
this.storage.setItemSync("http-webhook-ssl-cert", cachedSSLCert);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
sslServerOptions = {
|
|
75
|
+
key: cachedSSLCert.private,
|
|
76
|
+
cert: cachedSSLCert.cert
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
sslServerOptions = {
|
|
81
|
+
key: fs.readFileSync(this.httpsKeyFile),
|
|
82
|
+
cert: fs.readFileSync(this.httpsCertFile)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return sslServerOptions;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
Server.prototype.createServerCallback = function() {
|
|
90
|
+
return (function(request, response) {
|
|
91
|
+
if(this.webhookEnableCORS) {
|
|
92
|
+
// Based on https://gist.github.com/balupton/3696140
|
|
93
|
+
response.setHeader('Access-Control-Allow-Origin', '*');
|
|
94
|
+
response.setHeader('Access-Control-Request-Method', '*');
|
|
95
|
+
response.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET, POST');
|
|
96
|
+
response.setHeader('Access-Control-Allow-Headers', '*');
|
|
97
|
+
if (request.method === 'OPTIONS') {
|
|
98
|
+
response.writeHead(200);
|
|
99
|
+
response.end();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
var theUrl = request.url;
|
|
104
|
+
var theUrlParts = url.parse(theUrl, true);
|
|
105
|
+
var theUrlParams = theUrlParts.query;
|
|
106
|
+
var body = [];
|
|
107
|
+
request.on('error', (function(err) {
|
|
108
|
+
this.log("[ERROR Http WebHook Server] Reason: %s.", err);
|
|
109
|
+
}).bind(this)).on('data', function(chunk) {
|
|
110
|
+
body.push(chunk);
|
|
111
|
+
}).on('end', (function() {
|
|
112
|
+
body = Buffer.concat(body).toString();
|
|
113
|
+
|
|
114
|
+
response.on('error', function(err) {
|
|
115
|
+
this.log("[ERROR Http WebHook Server] Reason: %s.", err);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
response.statusCode = 200;
|
|
119
|
+
response.setHeader('Content-Type', 'application/json');
|
|
120
|
+
|
|
121
|
+
if (!theUrlParams.accessoryId) {
|
|
122
|
+
response.statusCode = 404;
|
|
123
|
+
response.setHeader("Content-Type", "text/plain");
|
|
124
|
+
var errorText = "[ERROR Http WebHook Server] No accessoryId in request.";
|
|
125
|
+
this.log(errorText);
|
|
126
|
+
response.write(errorText);
|
|
127
|
+
response.end();
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
var responseBody = null;
|
|
131
|
+
var accessoryId = theUrlParams.accessoryId;
|
|
132
|
+
var found = false;
|
|
133
|
+
for (var i = 0; i < this.accessories.length; i++) {
|
|
134
|
+
var accessory = this.accessories[i];
|
|
135
|
+
if (accessory.id === accessoryId) {
|
|
136
|
+
responseBody = accessory.changeFromServer(theUrlParams);
|
|
137
|
+
found = true;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if(responseBody) {
|
|
142
|
+
response.write(JSON.stringify(responseBody));
|
|
143
|
+
response.end();
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
response.statusCode = 404;
|
|
147
|
+
response.setHeader("Content-Type", "text/plain");
|
|
148
|
+
var errorText = "[ERROR Http WebHook Server] AccessoryId '"+theUrlParams.accessoryId+"' did not return a response body from 'changeFromServer'.";
|
|
149
|
+
if(!found) {
|
|
150
|
+
errorText = "[ERROR Http WebHook Server] AccessoryId '"+theUrlParams.accessoryId+"' not found.";
|
|
151
|
+
}
|
|
152
|
+
this.log(errorText);
|
|
153
|
+
response.write(errorText);
|
|
154
|
+
response.end();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}).bind(this));
|
|
158
|
+
}).bind(this);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
Server.prototype.start = function() {
|
|
162
|
+
var sslServerOptions = this.getSSLServerOptions();
|
|
163
|
+
|
|
164
|
+
var serverCallback = this.createServerCallback();
|
|
165
|
+
|
|
166
|
+
if (this.httpAuthUser && this.httpAuthPass) {
|
|
167
|
+
var httpAuthUser = this.httpAuthUser;
|
|
168
|
+
var httpAuthPass = this.httpAuthPass;
|
|
169
|
+
basicAuth = auth.basic({
|
|
170
|
+
realm : "Auth required"
|
|
171
|
+
}, function(username, password, callback) {
|
|
172
|
+
callback(username === httpAuthUser && password === httpAuthPass);
|
|
173
|
+
});
|
|
174
|
+
if(this.https) {
|
|
175
|
+
https.createServer(basicAuth, sslServerOptions, serverCallback).listen(this.webhookPort, this.webhookListenHost);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
http.createServer(basicAuth, serverCallback).listen(this.webhookPort, this.webhookListenHost);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
if(this.https) {
|
|
183
|
+
https.createServer(sslServerOptions, serverCallback).listen(this.webhookPort, this.webhookListenHost);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
http.createServer(serverCallback).listen(this.webhookPort, this.webhookListenHost);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
this.log("Started server for webhooks on port '%s' listening for host '%s'.", this.webhookPort, this.webhookListenHost);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
module.exports = Server;
|
package/src/Util.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const Constants = require('./Constants');
|
|
2
|
+
|
|
3
|
+
var request = require("request");
|
|
4
|
+
|
|
5
|
+
const callHttpApi = function(log, urlToCall, urlMethod, urlBody, urlForm, urlHeaders, rejectUnauthorized, homeKitCallback, context, onSuccessCallback, onFailureCallback, timeout) {
|
|
6
|
+
if (urlToCall !== "" && context !== Constants.CONTEXT_FROM_WEBHOOK) {
|
|
7
|
+
var theRequest = {
|
|
8
|
+
method : urlMethod,
|
|
9
|
+
url : urlToCall,
|
|
10
|
+
timeout : timeout || Constants.DEFAULT_REQUEST_TIMEOUT,
|
|
11
|
+
headers : JSON.parse(urlHeaders),
|
|
12
|
+
rejectUnauthorized: rejectUnauthorized
|
|
13
|
+
};
|
|
14
|
+
if (urlMethod === "POST" || urlMethod === "PUT" || urlMethod === "PATCH") {
|
|
15
|
+
if (urlForm) {
|
|
16
|
+
log("Adding Form " + urlForm);
|
|
17
|
+
theRequest.form = JSON.parse(urlForm);
|
|
18
|
+
}
|
|
19
|
+
else if (urlBody) {
|
|
20
|
+
log("Adding Body " + urlBody);
|
|
21
|
+
theRequest.body = urlBody;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
request(theRequest, (function(err, response, body) {
|
|
25
|
+
var statusCode = response && response.statusCode ? response.statusCode : -1;
|
|
26
|
+
log("Request to '%s' finished with status code '%s' and body '%s'.", urlToCall, statusCode, body, err);
|
|
27
|
+
if (!err && statusCode >= 200 && statusCode < 300) {
|
|
28
|
+
if (onSuccessCallback) {
|
|
29
|
+
onSuccessCallback();
|
|
30
|
+
}
|
|
31
|
+
homeKitCallback(null);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
if (onFailureCallback) {
|
|
35
|
+
onFailureCallback();
|
|
36
|
+
}
|
|
37
|
+
homeKitCallback(err || new Error("Request to '" + urlToCall + "' was not succesful."));
|
|
38
|
+
}
|
|
39
|
+
}).bind(this));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
if (onSuccessCallback) {
|
|
43
|
+
onSuccessCallback();
|
|
44
|
+
}
|
|
45
|
+
homeKitCallback(null);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
module.exports = {
|
|
50
|
+
callHttpApi : callHttpApi
|
|
51
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const Constants = require('../Constants');
|
|
2
|
+
const Server = require('../Server');
|
|
3
|
+
|
|
4
|
+
var HttpWebHookSensorAccessory = require('./accessories/HttpWebHookSensorAccessory');
|
|
5
|
+
var HttpWebHookSwitchAccessory = require('./accessories/HttpWebHookSwitchAccessory');
|
|
6
|
+
var HttpWebHookPushButtonAccessory = require('./accessories/HttpWebHookPushButtonAccessory');
|
|
7
|
+
var HttpWebHookDoorbellAccessory = require('./accessories/HttpWebHookDoorbellAccessory');
|
|
8
|
+
var HttpWebHookLightBulbAccessory = require('./accessories/HttpWebHookLightBulbAccessory');
|
|
9
|
+
var HttpWebHookThermostatAccessory = require('./accessories/HttpWebHookThermostatAccessory');
|
|
10
|
+
var HttpWebHookOutletAccessory = require('./accessories/HttpWebHookOutletAccessory');
|
|
11
|
+
var HttpWebHookSecurityAccessory = require('./accessories/HttpWebHookSecurityAccessory');
|
|
12
|
+
var HttpWebHookGarageDoorOpenerAccessory = require('./accessories/HttpWebHookGarageDoorOpenerAccessory');
|
|
13
|
+
var HttpWebHookStatelessSwitchAccessory = require('./accessories/HttpWebHookStatelessSwitchAccessory');
|
|
14
|
+
var HttpWebHookLockMechanismAccessory = require('./accessories/HttpWebHookLockMechanismAccessory');
|
|
15
|
+
var HttpWebHookWindowCoveringAccessory = require('./accessories/HttpWebHookWindowCoveringAccessory');
|
|
16
|
+
var HttpWebHookFanv2Accessory = require('./accessories/HttpWebHookFanv2Accessory');
|
|
17
|
+
var HttpWebHookCarbonDioxideSensoryAccessory = require('./accessories/HttpWebHookCarbonDioxideSensorAccessory');
|
|
18
|
+
var HttpWebHookValveAccessory = require('./accessories/HttpWebHookValveAccessory');
|
|
19
|
+
|
|
20
|
+
var Service, Characteristic;
|
|
21
|
+
|
|
22
|
+
function HttpWebHooksPlatform(log, config, homebridge) {
|
|
23
|
+
Service = homebridge.hap.Service;
|
|
24
|
+
Characteristic = homebridge.hap.Characteristic;
|
|
25
|
+
|
|
26
|
+
this.log = log;
|
|
27
|
+
this.cacheDirectory = config["cache_directory"] || Constants.DEFAULT_CACHE_DIR;
|
|
28
|
+
this.storage = require('node-persist');
|
|
29
|
+
this.storage.initSync({
|
|
30
|
+
dir : this.cacheDirectory
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
this.sensors = config["sensors"] || [];
|
|
34
|
+
this.switches = config["switches"] || [];
|
|
35
|
+
this.pushButtons = config["pushbuttons"] || [];
|
|
36
|
+
this.doorbells = config["doorbells"] || [];
|
|
37
|
+
this.lights = config["lights"] || [];
|
|
38
|
+
this.thermostats = config["thermostats"] || [];
|
|
39
|
+
this.outlets = config["outlets"] || [];
|
|
40
|
+
this.security = config["security"] || [];
|
|
41
|
+
this.garageDoorOpeners = config["garagedooropeners"] || [];
|
|
42
|
+
this.statelessSwitches = config["statelessswitches"] || [];
|
|
43
|
+
this.windowCoverings = config["windowcoverings"] || [];
|
|
44
|
+
this.lockMechanisms = config["lockmechanisms"] || [];
|
|
45
|
+
this.fanv2s = config["fanv2s"] || [];
|
|
46
|
+
this.co2sensors = config["co2sensors"] || [];
|
|
47
|
+
this.valves = config["valves"] || [];
|
|
48
|
+
|
|
49
|
+
this.server = new Server(Service, Characteristic, this, config);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
HttpWebHooksPlatform.prototype.accessories = function(callback) {
|
|
53
|
+
var accessories = [];
|
|
54
|
+
|
|
55
|
+
for (var i = 0; i < this.sensors.length; i++) {
|
|
56
|
+
var sensor = new HttpWebHookSensorAccessory(Service, Characteristic, this, this.sensors[i]);
|
|
57
|
+
accessories.push(sensor);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (var i = 0; i < this.switches.length; i++) {
|
|
61
|
+
var switchAccessory = new HttpWebHookSwitchAccessory(Service, Characteristic, this, this.switches[i]);
|
|
62
|
+
accessories.push(switchAccessory);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (var i = 0; i < this.pushButtons.length; i++) {
|
|
66
|
+
var pushButtonsAccessory = new HttpWebHookPushButtonAccessory(Service, Characteristic, this, this.pushButtons[i]);
|
|
67
|
+
accessories.push(pushButtonsAccessory);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for (var i = 0; i < this.doorbells.length; i++) {
|
|
71
|
+
var doorbellAccessory = new HttpWebHookDoorbellAccessory(Service, Characteristic, this, this.doorbells[i]);
|
|
72
|
+
accessories.push(doorbellAccessory);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (var i = 0; i < this.lights.length; i++) {
|
|
76
|
+
var lightAccessory = new HttpWebHookLightBulbAccessory(Service, Characteristic, this, this.lights[i]);
|
|
77
|
+
accessories.push(lightAccessory);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (var i = 0; i < this.thermostats.length; i++) {
|
|
81
|
+
var thermostatAccessory = new HttpWebHookThermostatAccessory(Service, Characteristic, this, this.thermostats[i]);
|
|
82
|
+
accessories.push(thermostatAccessory);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (var i = 0; i < this.outlets.length; i++) {
|
|
86
|
+
var outletAccessory = new HttpWebHookOutletAccessory(Service, Characteristic, this, this.outlets[i]);
|
|
87
|
+
accessories.push(outletAccessory);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (var i = 0; i < this.security.length; i++) {
|
|
91
|
+
var securityAccessory = new HttpWebHookSecurityAccessory(Service, Characteristic, this, this.security[i]);
|
|
92
|
+
accessories.push(securityAccessory);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (var i = 0; i < this.garageDoorOpeners.length; i++) {
|
|
96
|
+
var garageDoorOpenerAccessory = new HttpWebHookGarageDoorOpenerAccessory(Service, Characteristic, this, this.garageDoorOpeners[i]);
|
|
97
|
+
accessories.push(garageDoorOpenerAccessory);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
for (var i = 0; i < this.windowCoverings.length; i++) {
|
|
101
|
+
var WindowCoveringAccessory = new HttpWebHookWindowCoveringAccessory(Service, Characteristic, this, this.windowCoverings[i]);
|
|
102
|
+
accessories.push(WindowCoveringAccessory);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (var i = 0; i < this.statelessSwitches.length; i++) {
|
|
106
|
+
var statelessSwitchAccessory = new HttpWebHookStatelessSwitchAccessory(Service, Characteristic, this, this.statelessSwitches[i]);
|
|
107
|
+
accessories.push(statelessSwitchAccessory);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (var i = 0; i < this.lockMechanisms.length; i++) {
|
|
111
|
+
var lockMechanismAccessory = new HttpWebHookLockMechanismAccessory(Service, Characteristic, this, this.lockMechanisms[i]);
|
|
112
|
+
accessories.push(lockMechanismAccessory);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (var i = 0; i < this.fanv2s.length; i++) {
|
|
116
|
+
var fanv2Accessory = new HttpWebHookFanv2Accessory(Service, Characteristic, this, this.fanv2s[i]);
|
|
117
|
+
accessories.push(fanv2Accessory);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
for (var i = 0; i < this.co2sensors.length; i++) {
|
|
121
|
+
var co2sensorAccessory = new HttpWebHookCarbonDioxideSensoryAccessory(Service, Characteristic, this, this.co2sensors[i]);
|
|
122
|
+
accessories.push(co2sensorAccessory);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (var i = 0; i < this.valves.length; i++) {
|
|
126
|
+
var valveAccessory = new HttpWebHookValveAccessory(Service, Characteristic, this, this.valves[i]);
|
|
127
|
+
accessories.push(valveAccessory);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this.server.setAccessories(accessories);
|
|
131
|
+
this.server.start();
|
|
132
|
+
|
|
133
|
+
callback(accessories);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
module.exports = HttpWebHooksPlatform;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const Constants = require('../../Constants');
|
|
2
|
+
|
|
3
|
+
function HttpWebHookCarbonDioxideSensorAccessory(ServiceParam, CharacteristicParam, platform, sensorConfig) {
|
|
4
|
+
Service = ServiceParam;
|
|
5
|
+
Characteristic = CharacteristicParam;
|
|
6
|
+
|
|
7
|
+
this.platform = platform;
|
|
8
|
+
this.log = platform.log;
|
|
9
|
+
this.storage = platform.storage;
|
|
10
|
+
|
|
11
|
+
this.id = sensorConfig["id"];
|
|
12
|
+
this.name = sensorConfig["name"];
|
|
13
|
+
this.type = "co2";
|
|
14
|
+
this.co2PeakLevel = sensorConfig["co2_peak_level"] || 1200;
|
|
15
|
+
|
|
16
|
+
this.informationService = new Service.AccessoryInformation();
|
|
17
|
+
this.informationService.setCharacteristic(Characteristic.Manufacturer, "HttpWebHooksPlatform");
|
|
18
|
+
this.informationService.setCharacteristic(Characteristic.Model, "HttpWebHookCarbonDioxideSensorAccessory-" + this.name);
|
|
19
|
+
this.informationService.setCharacteristic(Characteristic.SerialNumber, "HttpWebHookCarbonDioxideSensorAccessory-" + this.id);
|
|
20
|
+
|
|
21
|
+
this.service = new Service.CarbonDioxideSensor(this.name);
|
|
22
|
+
this.service.getCharacteristic(Characteristic.CarbonDioxideLevel).on('get', this.getCarbonDioxideLevel.bind(this));
|
|
23
|
+
this.service.getCharacteristic(Characteristic.CarbonDioxideDetected).on('get', this.getCarbonDioxideDetected.bind(this));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
HttpWebHookCarbonDioxideSensorAccessory.prototype.changeFromServer = function(urlParams) {
|
|
27
|
+
var cached = this.storage.getItemSync("http-webhook-" + this.id) || 0;
|
|
28
|
+
if (urlParams.value === undefined) {
|
|
29
|
+
this.log.debug(this.name + ": No urlValue");
|
|
30
|
+
return {
|
|
31
|
+
"success" : true,
|
|
32
|
+
"state" : cached
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
var urlValue = urlParams.value;
|
|
36
|
+
var co2Detected = urlValue > this.co2PeakLevel;
|
|
37
|
+
this.log.debug(this.name + ": urlValue: "+ urlValue);
|
|
38
|
+
this.log.debug(this.name + ": co2Detected: "+ co2Detected);
|
|
39
|
+
this.storage.setItemSync("http-webhook-carbon-dioxide-level-" + this.id, urlValue);
|
|
40
|
+
this.storage.setItemSync("http-webhook-carbon-dioxide-detected-" + this.id, co2Detected);
|
|
41
|
+
this.log.debug(this.name + ": cached: "+ cached);
|
|
42
|
+
this.log.debug(this.name + ": cached !== urlValue: "+ (cached !== urlValue));
|
|
43
|
+
if (cached !== urlValue) {
|
|
44
|
+
this.log(this.name + ": Change HomeKit value for " + this.type + " sensor to '%s'.", urlValue);
|
|
45
|
+
this.service.getCharacteristic(Characteristic.CarbonDioxideLevel).updateValue(urlValue, undefined, Constants.CONTEXT_FROM_WEBHOOK);
|
|
46
|
+
this.service.getCharacteristic(Characteristic.CarbonDioxideDetected).updateValue(co2Detected ? Characteristic.CarbonDioxideDetected.CO2_LEVELS_ABNORMAL : Characteristic.CarbonDioxideDetected.CO2_LEVELS_NORMAL, undefined, Constants.CONTEXT_FROM_WEBHOOK);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
"success" : true
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
HttpWebHookCarbonDioxideSensorAccessory.prototype.getCarbonDioxideLevel = function(callback) {
|
|
54
|
+
this.log.debug(this.name + ": Getting carbon dioxide level for '%s'...", this.id);
|
|
55
|
+
var temp = this.storage.getItemSync("http-webhook-carbon-dioxide-level-" + this.id);
|
|
56
|
+
if (temp === undefined) {
|
|
57
|
+
temp = 0;
|
|
58
|
+
}
|
|
59
|
+
callback(null, temp);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
HttpWebHookCarbonDioxideSensorAccessory.prototype.getCarbonDioxideDetected = function(callback) {
|
|
63
|
+
this.log.debug(this.name + ": Getting carbon dioxide detected state for '%s'...", this.id);
|
|
64
|
+
var state = this.storage.getItemSync("http-webhook-carbon-dioxide-detected-" + this.id);
|
|
65
|
+
if (state === undefined) {
|
|
66
|
+
state = false;
|
|
67
|
+
}
|
|
68
|
+
callback(null, state ? Characteristic.CarbonDioxideDetected.CO2_LEVELS_ABNORMAL : Characteristic.CarbonDioxideDetected.CO2_LEVELS_NORMAL);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
HttpWebHookCarbonDioxideSensorAccessory.prototype.getServices = function() {
|
|
72
|
+
return [ this.service, this.informationService ];
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
module.exports = HttpWebHookCarbonDioxideSensorAccessory;
|