forked from Firehed/homebridge-plugin-generic-switch
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
151 lines (134 loc) · 4.06 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/*
* Expected config:
*
* {
* "platform": "GenericSwitch",
* "host": "http://your-switch.local",
* // Optional values
* "model": "model name",
* "manufacturer": "manufacturer name",
* "sn": "serial number",
* "cache": 15, // seconds
* "api": {
* "get": {
* "route": "/",
* "on": "on",
* "off": "off",
* },
* "set": {
* "route": "/power",
* "param": "state",
* "on": "on",
* "off": "off",
* }
* }
* }
*/
var Accessory, Characteristic, Service, UUIDGen;
const platformName = 'generic-switch';
const platformPrettyName = 'GenericSwitch';
const fetch = require('node-fetch');
module.exports = (homebridge) => {
Accessory = homebridge.platformAccessory;
Characteristic = homebridge.hap.Characteristic;
Service = homebridge.hap.Service;
UUIDGen = homebridge.hap.uuid;
homebridge.registerAccessory(platformName, platformPrettyName, GenericSwitch, true);
};
class GenericSwitch {
// These values are provided via Homebridge
constructor(log, config) {
if (!config) {
log('Ignoring switch - no config');
return;
}
log('Generic switch plugin loaded');
this.log = log;
const { api, host, model, sn, manufacturer, cache } = config;
this.host = host;
this.model = model || 'Unknown';
this.sn = sn || 'XXXXXX';
this.manufacturer = manufacturer || 'Generic';
this.api = api || {
get: {
route: "/",
on: "on",
off: "off",
},
set: {
route: "/power",
param: "state",
on: "on",
off: "off",
},
};
// State caching variables: when the projector is changing state, it
// reports the _current_ rather than the _target_ state. This will cache
// the last known state (either from polling or toggling it) for 15s
this.lastState = null
this.lastChecked = null
this.checkInterval = 1000 * (cache || 15); // milliseconds
[this.infoService, this.switchService] = this.createServices();
}
createServices = () => {
const infoService = new Service.AccessoryInformation();
infoService
.setCharacteristic(Characteristic.Manufacturer, this.manufacturer)
.setCharacteristic(Characteristic.Model, this.model)
.setCharacteristic(Characteristic.SerialNumber, this.sn);
const switchService = new Service.Switch(this.name);
switchService
.getCharacteristic(Characteristic.On)
.on('get', this.getPower)
.on('set', this.setPower);
return [infoService, switchService];
};
getServices = () => {
return [
this.infoService,
this.switchService,
];
}
getPower = (cb) => {
if (this.lastChecked && this.lastChecked > (Date.now() - this.checkInterval)) {
this.log.debug("Using cached power state");
return cb(null, this.lastState);
}
fetch(this.host + this.api.get.route)
.then(res => {
if (!res.ok) {
throw new Error(res.status + ' ' + res.statusText);
}
return res;
})
.then(res => res.text())
.then(text => {
let on;
if (text === this.api.get.on) {
on = true;
} else if (text === this.api.get.off) {
on = false;
} else {
this.log.error('Invalid response from get request: ', text);
on = false;
}
this.lastState = on;
this.lastChecked = Date.now();
cb(null, on);
});
}
setPower = (on, cb) => {
const state = on ? this.api.set.on : this.api.set.off;
// There's a weird interaction (pair of bugs) where this fetch wrapper
// lowercases all of the HTTP header keys, and the ESP8266WebServer library
// won't parse the POST body unless the Content-Length header is formatted
// exactly as such. Fortunately, throwing the value in the query string
// allows it to go through just fine.
fetch(this.host + this.api.set.route + '?' + this.api.set.param + '=' + state, { method: "POST" })
.then(_ => {
this.lastState = on;
this.lastChecked = Date.now();
cb();
});
}
}