homebridge-gree-ac 1.0.1 → 2.0.0
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 +19 -0
- package/LICENSE +176 -21
- package/README.md +180 -42
- package/config.schema.json +277 -0
- package/dist/commands.d.ts +121 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +123 -0
- package/dist/commands.js.map +1 -0
- package/dist/crypto.d.ts +6 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +19 -0
- package/dist/crypto.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/platform.d.ts +35 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +203 -0
- package/dist/platform.js.map +1 -0
- package/dist/platformAccessory.d.ts +89 -0
- package/dist/platformAccessory.d.ts.map +1 -0
- package/dist/platformAccessory.js +867 -0
- package/dist/platformAccessory.js.map +1 -0
- package/dist/settings.d.ts +51 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +50 -0
- package/dist/settings.js.map +1 -0
- package/dist/tsAccessory.d.ts +36 -0
- package/dist/tsAccessory.d.ts.map +1 -0
- package/dist/tsAccessory.js +65 -0
- package/dist/tsAccessory.js.map +1 -0
- package/greedevice.jpg +0 -0
- package/greemac.jpg +0 -0
- package/package.json +51 -23
- package/uiconfig.jpg +0 -0
- package/app/commandEnums.js +0 -137
- package/app/deviceFactory.js +0 -286
- package/app/encryptionService.js +0 -39
- package/example.config.json +0 -20
- package/index.js +0 -339
package/app/deviceFactory.js
DELETED
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const dgram = require('dgram');
|
|
4
|
-
const socket = dgram.createSocket('udp4');
|
|
5
|
-
const encryptionService = require('./encryptionService')();
|
|
6
|
-
const cmd = require('./commandEnums');
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Class representing a single connected device
|
|
10
|
-
*/
|
|
11
|
-
class Device {
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Create device model and establish UDP connection with remote host
|
|
15
|
-
* @param {object} [options] Options
|
|
16
|
-
* @param {string} [options.address] HVAC IP address
|
|
17
|
-
* @callback [options.onStatus] Callback function run on each status update
|
|
18
|
-
* @callback [options.onUpdate] Callback function run after command
|
|
19
|
-
* @callback [options.onConnected] Callback function run once connection is established
|
|
20
|
-
*/
|
|
21
|
-
constructor(options) {
|
|
22
|
-
|
|
23
|
-
// Set defaults
|
|
24
|
-
this.options = {
|
|
25
|
-
host: options.host || '192.168.1.255',
|
|
26
|
-
onStatus: options.onStatus || function() {},
|
|
27
|
-
onUpdate: options.onUpdate || function() {},
|
|
28
|
-
onConnected: options.onConnected || function() {},
|
|
29
|
-
onError: options.onError || function() {},
|
|
30
|
-
onDisconnected: options.onDisconnected || function() {},
|
|
31
|
-
updateInterval: options.updateInterval || 10000
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Device object
|
|
36
|
-
* @typedef {object} Device
|
|
37
|
-
* @property {string} id - ID
|
|
38
|
-
* @property {string} name - Name
|
|
39
|
-
* @property {string} address - IP address
|
|
40
|
-
* @property {number} port - Port number
|
|
41
|
-
* @property {boolean} bound - If is already bound
|
|
42
|
-
* @property {object} props - Properties
|
|
43
|
-
*/
|
|
44
|
-
this.device = {};
|
|
45
|
-
|
|
46
|
-
// Initialize connection and bind with device
|
|
47
|
-
this._connectToDevice(this.options.host);
|
|
48
|
-
|
|
49
|
-
// Handle incoming messages
|
|
50
|
-
socket.on('message', (msg, rinfo) => this._handleResponse(msg, rinfo));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Initialize connection
|
|
55
|
-
* @param {string} address - IP/host address
|
|
56
|
-
*/
|
|
57
|
-
_connectToDevice(address) {
|
|
58
|
-
try {
|
|
59
|
-
socket.bind(() => {
|
|
60
|
-
const message = new Buffer(JSON.stringify({t: 'scan'}));
|
|
61
|
-
|
|
62
|
-
socket.setBroadcast(false);
|
|
63
|
-
socket.send(message, 0, message.length, 7000, address);
|
|
64
|
-
});
|
|
65
|
-
} catch (err) {
|
|
66
|
-
const timeout = 5
|
|
67
|
-
this.options.onDisconnected(this.device);
|
|
68
|
-
setTimeout(() => {
|
|
69
|
-
this._connectToDevice(address);
|
|
70
|
-
}, timeout * 1000);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Register new device locally
|
|
76
|
-
* @param {string} id - CID received in handshake message
|
|
77
|
-
* @param {string} name - Device name received in handshake message
|
|
78
|
-
* @param {string} address - IP/host address
|
|
79
|
-
* @param {number} port - Port number
|
|
80
|
-
*/
|
|
81
|
-
_setDevice (id, name, address, port) {
|
|
82
|
-
this.device.id = id;
|
|
83
|
-
this.device.name = name;
|
|
84
|
-
this.device.address = address;
|
|
85
|
-
this.device.port = port;
|
|
86
|
-
this.device.bound = false;
|
|
87
|
-
this.device.props = {};
|
|
88
|
-
|
|
89
|
-
console.log('[GreeAC] New device registered: %s - %s', this.device.name, this.device.address);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Send binding request to device
|
|
94
|
-
* @param {Device} device Device object
|
|
95
|
-
*/
|
|
96
|
-
_sendBindRequest(device) {
|
|
97
|
-
const message = {
|
|
98
|
-
mac: this.device.id,
|
|
99
|
-
t: 'bind',
|
|
100
|
-
uid: 0
|
|
101
|
-
};
|
|
102
|
-
const encryptedBoundMessage = encryptionService.encrypt(message);
|
|
103
|
-
const request = {
|
|
104
|
-
cid: 'app',
|
|
105
|
-
i: 1,
|
|
106
|
-
t: 'pack',
|
|
107
|
-
uid: 0,
|
|
108
|
-
pack: encryptedBoundMessage
|
|
109
|
-
};
|
|
110
|
-
const toSend = new Buffer(JSON.stringify(request));
|
|
111
|
-
socket.send(toSend, 0, toSend.length, device.port, device.address);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Confirm device is bound and update device status on list
|
|
116
|
-
* @param {String} id - Device ID
|
|
117
|
-
* @param {String} key - Encryption key
|
|
118
|
-
*/
|
|
119
|
-
_confirmBinding(id, key) {
|
|
120
|
-
this.device.bound = true;
|
|
121
|
-
this.device.key = key;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Confirm device is bound and update device status on list
|
|
126
|
-
* @param {Device} device - Device
|
|
127
|
-
*/
|
|
128
|
-
_requestDeviceStatus (device) {
|
|
129
|
-
const message = {
|
|
130
|
-
cols: Object.keys(cmd).map(key => cmd[key].code),
|
|
131
|
-
mac: device.id,
|
|
132
|
-
t: 'status'
|
|
133
|
-
};
|
|
134
|
-
this._sendRequest(message, device.address, device.port);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Handle UDP response from device
|
|
139
|
-
* @param {string} msg Serialized JSON string with message
|
|
140
|
-
* @param {object} rinfo Additional request information
|
|
141
|
-
* @param {string} rinfo.address IP/host address
|
|
142
|
-
* @param {number} rinfo.port Port number
|
|
143
|
-
*/
|
|
144
|
-
_handleResponse(msg, rinfo) {
|
|
145
|
-
|
|
146
|
-
const message = JSON.parse(msg + '');
|
|
147
|
-
|
|
148
|
-
// Extract encrypted package from message using device key (if available)
|
|
149
|
-
const pack = encryptionService.decrypt(message, (this.device || {}).key);
|
|
150
|
-
|
|
151
|
-
// If package type is response to handshake
|
|
152
|
-
if (pack.t === 'dev') {
|
|
153
|
-
this._setDevice(message.cid, pack.name, rinfo.address, rinfo.port);
|
|
154
|
-
this._sendBindRequest(this.device);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// If package type is binding confirmation
|
|
159
|
-
if (pack.t === 'bindok' && this.device.id) {
|
|
160
|
-
this._confirmBinding(message.cid, pack.key);
|
|
161
|
-
|
|
162
|
-
// Start requesting device status on set interval
|
|
163
|
-
setInterval(this._requestDeviceStatus.bind(this, this.device), this.options.updateInterval);
|
|
164
|
-
this.options.onConnected(this.device)
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// If package type is device status
|
|
169
|
-
if (pack.t === 'dat' && this.device.bound) {
|
|
170
|
-
pack.cols.forEach((col, i) => {
|
|
171
|
-
this.device.props[col] = pack.dat[i];
|
|
172
|
-
});
|
|
173
|
-
this.options.onStatus(this.device);
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// If package type is response, update device properties
|
|
178
|
-
if (pack.t === 'res' && this.device.bound) {
|
|
179
|
-
pack.opt.forEach((opt, i) => {
|
|
180
|
-
this.device.props[opt] = pack.val[i];
|
|
181
|
-
});
|
|
182
|
-
this.options.onUpdate(this.device);
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
this.options.onError(this.device);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Send commands to a bound device
|
|
191
|
-
* @param {string[]} commands List of commands
|
|
192
|
-
* @param {number[]} values List of values
|
|
193
|
-
*/
|
|
194
|
-
_sendCommand (commands = [], values = []) {
|
|
195
|
-
const message = {
|
|
196
|
-
opt: commands,
|
|
197
|
-
p: values,
|
|
198
|
-
t: 'cmd'
|
|
199
|
-
};
|
|
200
|
-
this._sendRequest(message);
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Send request to a bound device
|
|
205
|
-
* @param {object} message
|
|
206
|
-
* @param {string[]} message.opt
|
|
207
|
-
* @param {number[]} message.p
|
|
208
|
-
* @param {string} message.t
|
|
209
|
-
* @param {string} [address] IP/host address
|
|
210
|
-
* @param {number} [port] Port number
|
|
211
|
-
*/
|
|
212
|
-
_sendRequest (message, address = this.device.address, port = this.device.port) {
|
|
213
|
-
const encryptedMessage = encryptionService.encrypt(message, this.device.key);
|
|
214
|
-
const request = {
|
|
215
|
-
cid: 'app',
|
|
216
|
-
i: 0,
|
|
217
|
-
t: 'pack',
|
|
218
|
-
uid: 0,
|
|
219
|
-
pack: encryptedMessage
|
|
220
|
-
};
|
|
221
|
-
const serializedRequest = new Buffer(JSON.stringify(request));
|
|
222
|
-
socket.send(serializedRequest, 0, serializedRequest.length, port, address);
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Turn on/off
|
|
227
|
-
* @param {boolean} value State
|
|
228
|
-
*/
|
|
229
|
-
setPower (value) {
|
|
230
|
-
this._sendCommand(
|
|
231
|
-
[cmd.power.code],
|
|
232
|
-
[value ? 1 : 0]
|
|
233
|
-
);
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Set temperature
|
|
238
|
-
* @param {number} value Temperature
|
|
239
|
-
* @param {number} [unit=0] Units (defaults to Celsius)
|
|
240
|
-
*/
|
|
241
|
-
setTemp (value, unit = cmd.temperatureUnit.value.celsius) {
|
|
242
|
-
this._sendCommand(
|
|
243
|
-
[cmd.temperatureUnit.code, cmd.temperature.code],
|
|
244
|
-
[unit, value]
|
|
245
|
-
);
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Set mode
|
|
250
|
-
* @param {number} value Mode value (0-4)
|
|
251
|
-
*/
|
|
252
|
-
setMode (value) {
|
|
253
|
-
this._sendCommand(
|
|
254
|
-
[cmd.mode.code],
|
|
255
|
-
[value]
|
|
256
|
-
);
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Set fan speed
|
|
261
|
-
* @param {number} value Fan speed value (0-5)
|
|
262
|
-
*/
|
|
263
|
-
setFanSpeed (value) {
|
|
264
|
-
this._sendCommand(
|
|
265
|
-
[cmd.fanSpeed.code],
|
|
266
|
-
[value]
|
|
267
|
-
);
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Set vertical swing
|
|
272
|
-
* @param {number} value Vertical swing value (0-11)
|
|
273
|
-
*/
|
|
274
|
-
setSwingVert (value) {
|
|
275
|
-
this._sendCommand(
|
|
276
|
-
[cmd.swingVert.code],
|
|
277
|
-
[value]
|
|
278
|
-
);
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
module.exports.connect = function(options) {
|
|
284
|
-
return new Device(options);
|
|
285
|
-
};
|
|
286
|
-
|
package/app/encryptionService.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const crypto = require('crypto');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Module containing encryption services
|
|
7
|
-
* @param {String} key AES general key
|
|
8
|
-
*/
|
|
9
|
-
module.exports = function(defaultKey = 'a3K8Bx%2r8Y7#xDh') {
|
|
10
|
-
const EncryptionService = {
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Decrypt UDP message
|
|
14
|
-
* @param {object} input Response object
|
|
15
|
-
* @param {string} input.pack Encrypted JSON string
|
|
16
|
-
* @param {string} [key] AES key
|
|
17
|
-
*/
|
|
18
|
-
decrypt: (input, key = defaultKey) => {
|
|
19
|
-
const decipher = crypto.createDecipheriv('aes-128-ecb', key, '');
|
|
20
|
-
const str = decipher.update(input.pack, 'base64', 'utf8');
|
|
21
|
-
const response = JSON.parse(str + decipher.final('utf8'));
|
|
22
|
-
return response;
|
|
23
|
-
},
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Encrypt UDP message
|
|
27
|
-
* @param {object} output Request object
|
|
28
|
-
* @param {string} [key] AES key
|
|
29
|
-
*/
|
|
30
|
-
encrypt: (output, key = defaultKey) => {
|
|
31
|
-
const cipher = crypto.createCipheriv('aes-128-ecb', key, '');
|
|
32
|
-
const str = cipher.update(JSON.stringify(output), 'utf8', 'base64');
|
|
33
|
-
const request = str + cipher.final('base64');
|
|
34
|
-
return request;
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
return EncryptionService;
|
|
39
|
-
};
|
package/example.config.json
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"bridge": {
|
|
3
|
-
"name": "Homebridge",
|
|
4
|
-
"username": "CC:22:3D:E3:CE:30",
|
|
5
|
-
"port": 51826,
|
|
6
|
-
"pin": "123-45-568"
|
|
7
|
-
},
|
|
8
|
-
"accessories": [
|
|
9
|
-
{
|
|
10
|
-
"accessory": "GreeAC",
|
|
11
|
-
"host": "192.168.1.X",
|
|
12
|
-
"name": "Office AC"
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"accessory": "GreeAC",
|
|
16
|
-
"host": "192.168.1.Y",
|
|
17
|
-
"name": "Bedroom AC"
|
|
18
|
-
}
|
|
19
|
-
]
|
|
20
|
-
}
|
package/index.js
DELETED
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const commands = require('./app/commandEnums');
|
|
3
|
-
var Accessory, Service, Characteristic;
|
|
4
|
-
var mqtt = require('mqtt');
|
|
5
|
-
|
|
6
|
-
module.exports = function(homebridge) {
|
|
7
|
-
Accessory = homebridge.platformAccessory;
|
|
8
|
-
Service = homebridge.hap.Service;
|
|
9
|
-
Characteristic = homebridge.hap.Characteristic;
|
|
10
|
-
|
|
11
|
-
homebridge.registerAccessory('homebridge-gree-ac', 'GreeAC', GreeAC);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function GreeAC(log, config) {
|
|
15
|
-
this.log = log;
|
|
16
|
-
this.name = config.name || 'Gree AC';
|
|
17
|
-
this.host = config.host || '192.168.1.255';
|
|
18
|
-
this.debug = ((config.debug || "false" ) === "true");
|
|
19
|
-
this.updateInterval = config.updateInterval || 10000;
|
|
20
|
-
this.currentTempTopic = config['currentTempTopic'] || "hvac/temperature";
|
|
21
|
-
this.mqttUrl = config['mqttUrl'] || "mqtt://127.0.0.1";
|
|
22
|
-
this.client_Id = 'mqttjs_' + Math.random().toString(16).substr(2, 8);
|
|
23
|
-
|
|
24
|
-
this.mqttOptions = {
|
|
25
|
-
keepalive: 10,
|
|
26
|
-
clientId: this.client_Id,
|
|
27
|
-
protocolId: 'MQTT',
|
|
28
|
-
protocolVersion: 4,
|
|
29
|
-
clean: true,
|
|
30
|
-
reconnectPeriod: 1000,
|
|
31
|
-
connectTimeout: 30 * 1000,
|
|
32
|
-
serialnumber: config["serial"] || this.client_Id,
|
|
33
|
-
max_temperature: config["maxTemperature"] || 100,
|
|
34
|
-
min_temperature: config["minTemperature"] || -50,
|
|
35
|
-
username: config["username"],
|
|
36
|
-
password: config["password"],
|
|
37
|
-
will: {
|
|
38
|
-
topic: 'WillMsg',
|
|
39
|
-
payload: 'Connection Closed abnormally..!',
|
|
40
|
-
qos: 0,
|
|
41
|
-
retain: false
|
|
42
|
-
},
|
|
43
|
-
rejectUnauthorized: false
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
this.client = mqtt.connect(this.mqttUrl, this.mqttOptions);
|
|
47
|
-
|
|
48
|
-
this.TargetHeatingCoolingState = Characteristic.TargetHeatingCoolingState.OFF;
|
|
49
|
-
this.TargetTemperature = 22;
|
|
50
|
-
this.CurrentTemperature = 0;
|
|
51
|
-
this.services = [];
|
|
52
|
-
|
|
53
|
-
this.GreeACService = new Service.Thermostat(this.name);
|
|
54
|
-
this.temperatureDisplayUnits = Characteristic.TemperatureDisplayUnits.CELSIUS;
|
|
55
|
-
|
|
56
|
-
this.GreeACService
|
|
57
|
-
.getCharacteristic(Characteristic.TemperatureDisplayUnits)
|
|
58
|
-
.on('get', this.getTemperatureDisplayUnits.bind(this))
|
|
59
|
-
.on('set', this.setTemperatureDisplayUnits.bind(this));
|
|
60
|
-
|
|
61
|
-
this.GreeACService
|
|
62
|
-
.getCharacteristic(Characteristic.TargetHeatingCoolingState)
|
|
63
|
-
.on('set', this.setTargetHeatingCoolingState.bind(this))
|
|
64
|
-
.on('get', this.getTargetHeatingCoolingState.bind(this));
|
|
65
|
-
|
|
66
|
-
this.GreeACService
|
|
67
|
-
.getCharacteristic(Characteristic.CurrentHeatingCoolingState)
|
|
68
|
-
.on('get', this.getTargetHeatingCoolingState.bind(this));
|
|
69
|
-
|
|
70
|
-
this.GreeACService
|
|
71
|
-
.getCharacteristic(Characteristic.TargetTemperature)
|
|
72
|
-
.setProps({
|
|
73
|
-
maxValue: 30,
|
|
74
|
-
minValue: 16,
|
|
75
|
-
minStep: 1
|
|
76
|
-
})
|
|
77
|
-
.on('set', this.setTargetTemperature.bind(this))
|
|
78
|
-
.on('get', this.getTargetTemperature.bind(this));
|
|
79
|
-
|
|
80
|
-
this.GreeACService
|
|
81
|
-
.getCharacteristic(Characteristic.CurrentTemperature)
|
|
82
|
-
.setProps({
|
|
83
|
-
maxValue: 30,
|
|
84
|
-
minValue: 16,
|
|
85
|
-
minStep: 1
|
|
86
|
-
})
|
|
87
|
-
.on('get', this.getCurrentTemperature.bind(this));
|
|
88
|
-
|
|
89
|
-
// this.GreeACService
|
|
90
|
-
// .getCharacteristic(Characteristic.SwingMode)
|
|
91
|
-
// .setProps({
|
|
92
|
-
// minValue: 0,
|
|
93
|
-
// maxValue: 11,
|
|
94
|
-
// minStep: 1
|
|
95
|
-
// })
|
|
96
|
-
// .on('get', this.getSwing.bind(this))
|
|
97
|
-
// .on('set', this.setSwing.bind(this));
|
|
98
|
-
|
|
99
|
-
// this.GreeACService
|
|
100
|
-
// .getCharacteristic(Characteristic.RotationSpeed)
|
|
101
|
-
// .setProps({
|
|
102
|
-
// minValue: 0,
|
|
103
|
-
// maxValue: 5,
|
|
104
|
-
// minStep: 1
|
|
105
|
-
// })
|
|
106
|
-
// .on('get', this.getRotationSpeed.bind(this))
|
|
107
|
-
// .on('set', this.setRotationSpeed.bind(this));
|
|
108
|
-
|
|
109
|
-
this.services.push(this.GreeACService);
|
|
110
|
-
|
|
111
|
-
this.serviceInfo = new Service.AccessoryInformation();
|
|
112
|
-
|
|
113
|
-
this.serviceInfo
|
|
114
|
-
.setCharacteristic(Characteristic.Manufacturer, 'Gree')
|
|
115
|
-
.setCharacteristic(Characteristic.Model, 'Bora A5')
|
|
116
|
-
.setCharacteristic(Characteristic.SerialNumber, 'GREEBORAA5');
|
|
117
|
-
|
|
118
|
-
this.services.push(this.serviceInfo);
|
|
119
|
-
|
|
120
|
-
this.discover();
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
GreeAC.prototype = {
|
|
124
|
-
|
|
125
|
-
discover: function(){
|
|
126
|
-
var accessory = this;
|
|
127
|
-
var log = this.log;
|
|
128
|
-
var host = this.host;
|
|
129
|
-
var debug = this.debug;
|
|
130
|
-
|
|
131
|
-
const deviceOptions = {
|
|
132
|
-
host: host,
|
|
133
|
-
updateInterval: this.updateInterval,
|
|
134
|
-
onStatus: (deviceModel) => {
|
|
135
|
-
var device_power_status = deviceModel.props[commands.power.code];
|
|
136
|
-
var device_mode_status = deviceModel.props[commands.mode.code];
|
|
137
|
-
var device_temperature = deviceModel.props[commands.temperature.code];
|
|
138
|
-
var device_swingvert = deviceModel.props[commands.swingVert.code] === 0 ? 0:1;
|
|
139
|
-
accessory.TargetTemperature = parseFloat(device_temperature);
|
|
140
|
-
|
|
141
|
-
if (device_power_status === commands.power.value.off) {
|
|
142
|
-
accessory.TargetHeatingCoolingState = Characteristic.TargetHeatingCoolingState.OFF;
|
|
143
|
-
} else if (device_mode_status === commands.mode.value.auto) {
|
|
144
|
-
accessory.TargetHeatingCoolingState = Characteristic.TargetHeatingCoolingState.AUTO;
|
|
145
|
-
} else if (device_mode_status === commands.mode.value.cool) {
|
|
146
|
-
accessory.TargetHeatingCoolingState = Characteristic.TargetHeatingCoolingState.COOL;
|
|
147
|
-
} else if (device_mode_status === commands.mode.value.heat) {
|
|
148
|
-
accessory.TargetHeatingCoolingState = Characteristic.TargetHeatingCoolingState.HEAT;
|
|
149
|
-
} else {
|
|
150
|
-
accessory.TargetHeatingCoolingState = Characteristic.TargetHeatingCoolingState.AUTO;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
this.GreeACService
|
|
154
|
-
.getCharacteristic(Characteristic.TargetHeatingCoolingState)
|
|
155
|
-
.updateValue(accessory.TargetHeatingCoolingState);
|
|
156
|
-
|
|
157
|
-
this.GreeACService
|
|
158
|
-
.getCharacteristic(Characteristic.TargetTemperature)
|
|
159
|
-
.updateValue(accessory.TargetTemperature);
|
|
160
|
-
|
|
161
|
-
if (accessory.debug === true) {
|
|
162
|
-
this.log.info('Get mode: %s', device_mode_status.toString());
|
|
163
|
-
this.log.info('Get temperature %s', device_temperature.toString());
|
|
164
|
-
this.log.info('Get fanspeed %s', deviceModel.props[commands.fanSpeed.code].toString());
|
|
165
|
-
this.log.info('Get swingvert %s', device_swingvert.toString());
|
|
166
|
-
this.log.info('Get powerstatus %s', device_power_status.toString());
|
|
167
|
-
}
|
|
168
|
-
},
|
|
169
|
-
onUpdate: (deviceModel) => {
|
|
170
|
-
this.log.info('Status updated on %s', deviceModel.name)
|
|
171
|
-
},
|
|
172
|
-
onConnected: (deviceModel) => {
|
|
173
|
-
var accesorry = this;
|
|
174
|
-
accesorry.log.info('Connecting on %s with ip address %s[%d] debug=%s: %s', deviceModel.name, deviceModel.address, deviceModel.port, accesorry.debug, deviceModel.bound ? "SUCCESS": "FAIL");
|
|
175
|
-
accesorry.client.subscribe(this.currentTempTopic);
|
|
176
|
-
accesorry.client.on('message', function (topic, message) {
|
|
177
|
-
try {
|
|
178
|
-
data = JSON.parse(message);
|
|
179
|
-
} catch (e) {
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (data === null) {return null}
|
|
184
|
-
var that = this;
|
|
185
|
-
that.temperature = parseFloat(data);
|
|
186
|
-
if (!isNaN(that.temperature)) {
|
|
187
|
-
if (accesorry.debug === true) {
|
|
188
|
-
accesorry.log.info('Current temperature is ' + that.temperature);
|
|
189
|
-
}
|
|
190
|
-
accesorry.CurrentTemperature = parseFloat(that.temperature);
|
|
191
|
-
accesorry.GreeACService
|
|
192
|
-
.getCharacteristic(Characteristic.CurrentTemperature)
|
|
193
|
-
.updateValue(accesorry.CurrentTemperature);
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
},
|
|
197
|
-
onError: (deviceModel) => {
|
|
198
|
-
if (this.debug === true) {
|
|
199
|
-
this.log.info('Connecting on %s with ip address %s[%d]: %s', deviceModel.name, deviceModel.address, deviceModel.port, deviceModel.bound ? "SUCCESS": "FAIL");
|
|
200
|
-
}
|
|
201
|
-
},
|
|
202
|
-
onDisconnected: (deviceModel) => {
|
|
203
|
-
if (this.debug === true ) {
|
|
204
|
-
this.log.info('Connecting on %s with ip address %s[%d]: %s', deviceModel.name, deviceModel.address, deviceModel.port, deviceModel.bound ? "SUCCESS": "FAIL");
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
accessory.hvac = require('./app/deviceFactory').connect(deviceOptions);
|
|
209
|
-
},
|
|
210
|
-
|
|
211
|
-
setTemperatureDisplayUnits: function(value, callback) {
|
|
212
|
-
if (this.debug === true) {
|
|
213
|
-
this.log.info("setTemperatureDisplayUnits from %s to %s", this.temperatureDisplayUnits, value);
|
|
214
|
-
}
|
|
215
|
-
this.temperatureDisplayUnits = value;
|
|
216
|
-
callback(null);
|
|
217
|
-
},
|
|
218
|
-
|
|
219
|
-
getTemperatureDisplayUnits: function(callback) {
|
|
220
|
-
try {
|
|
221
|
-
this.temperatureDisplayUnits = this.hvac.device.props[commands.temperatureUnit.code];
|
|
222
|
-
if (this.debug === true) {
|
|
223
|
-
this.log.info("getTemperatureDisplayUnits: ", this.temperatureDisplayUnits);
|
|
224
|
-
}
|
|
225
|
-
callback(null, this.temperatureDisplayUnits);
|
|
226
|
-
} catch (err) {callback();}
|
|
227
|
-
},
|
|
228
|
-
|
|
229
|
-
getTargetHeatingCoolingState: function(callback) {
|
|
230
|
-
try
|
|
231
|
-
{
|
|
232
|
-
var state = Characteristic.TargetHeatingCoolingState.AUTO;
|
|
233
|
-
var accessory = this;
|
|
234
|
-
if (this.hvac.device.props[commands.power.code] === commands.power.value.off) {
|
|
235
|
-
state = Characteristic.TargetHeatingCoolingState.OFF;
|
|
236
|
-
} else if (this.hvac.device.props[commands.mode.code] === commands.mode.value.auto) {
|
|
237
|
-
state = Characteristic.TargetHeatingCoolingState.AUTO;
|
|
238
|
-
} else if (this.hvac.device.props[commands.mode.code] === commands.mode.value.cool) {
|
|
239
|
-
state = Characteristic.TargetHeatingCoolingState.COOL;
|
|
240
|
-
} else if (this.hvac.device.props[commands.mode.code] === commands.mode.value.heat) {
|
|
241
|
-
state = Characteristic.TargetHeatingCoolingState.HEAT;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
accessory.TargetHeatingCoolingState = state;
|
|
245
|
-
callback(null, this.TargetHeatingCoolingState);
|
|
246
|
-
} catch (err) {
|
|
247
|
-
if (this.debug === true) {
|
|
248
|
-
this.log.info("getTemperatureDisplayUnits: error communicating with device");
|
|
249
|
-
}
|
|
250
|
-
callback();
|
|
251
|
-
}
|
|
252
|
-
},
|
|
253
|
-
|
|
254
|
-
setTargetHeatingCoolingState: function(TargetHeatingCoolingState, callback, context) {
|
|
255
|
-
try {
|
|
256
|
-
if(context !== 'fromSetValue') {
|
|
257
|
-
this.TargetHeatingCoolingState = TargetHeatingCoolingState;
|
|
258
|
-
if (this.TargetHeatingCoolingState === Characteristic.TargetHeatingCoolingState.OFF) {
|
|
259
|
-
this.hvac.setPower(commands.power.value.off);
|
|
260
|
-
if (this.debug === true){
|
|
261
|
-
this.log.info('setTargetHeatingCoolingState: Set mode power off');
|
|
262
|
-
}
|
|
263
|
-
} else {
|
|
264
|
-
if (this.hvac.device.props[commands.power.code] === commands.power.value.off) {
|
|
265
|
-
this.hvac.setPower(commands.power.value.on);
|
|
266
|
-
if (this.debug === true){
|
|
267
|
-
this.log.info('setTargetHeatingCoolingState: Set mode power on');
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (this.TargetHeatingCoolingState === Characteristic.TargetHeatingCoolingState.HEAT) {
|
|
272
|
-
this.hvac.setMode(commands.mode.value['heat']);
|
|
273
|
-
if (this.debug === true) {
|
|
274
|
-
this.log.info('setTargetHeatingCoolingState: Set mode to HEAT');
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (this.TargetHeatingCoolingState === Characteristic.TargetHeatingCoolingState.COOL) {
|
|
279
|
-
this.hvac.setMode(commands.mode.value['cool']);
|
|
280
|
-
if (this.debug === true) {
|
|
281
|
-
this.log.info('setTargetHeatingCoolingState: Set mode to COOL');
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (this.TargetHeatingCoolingState === Characteristic.TargetHeatingCoolingState.AUTO) {
|
|
286
|
-
this.hvac.setMode(commands.mode.value['auto']);
|
|
287
|
-
if (this.debug === true) {
|
|
288
|
-
this.log.info('setTargetHeatingCoolingState: Set mode to AUTO');
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
callback();
|
|
294
|
-
} catch (err) {callback();}
|
|
295
|
-
},
|
|
296
|
-
|
|
297
|
-
getTargetTemperature: function(callback) {
|
|
298
|
-
try {
|
|
299
|
-
this.TargetTemperature = parseInt(this.hvac.device.props[commands.temperature.code]);
|
|
300
|
-
callback(null, this.TargetTemperature);
|
|
301
|
-
} catch (err) {callback();}
|
|
302
|
-
},
|
|
303
|
-
|
|
304
|
-
setTargetTemperature: function(TargetTemperature, callback, context) {
|
|
305
|
-
if(context !== 'fromSetValue') {
|
|
306
|
-
this.TargetTemperature = TargetTemperature;
|
|
307
|
-
// if ac is off, then turn it on by setting to auto mode
|
|
308
|
-
if (this.TargetHeatingCoolingState == Characteristic.TargetHeatingCoolingState.OFF) {
|
|
309
|
-
this.hvac.setPower(commands.power.value.on);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Update current temperature
|
|
313
|
-
// this.GreeACService
|
|
314
|
-
// .getCharacteristic(Characteristic.CurrentTemperature)
|
|
315
|
-
// .updateValue(parseFloat(TargetTemperature));
|
|
316
|
-
this.hvac.setTemp(parseInt(TargetTemperature));
|
|
317
|
-
if (this.debug === true) {
|
|
318
|
-
this.log.info('setTargetTemperature: Set temperature: ' + TargetTemperature);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
callback();
|
|
322
|
-
},
|
|
323
|
-
|
|
324
|
-
getCurrentTemperature: function(callback) {
|
|
325
|
-
if (this.debug === true) {
|
|
326
|
-
this.log.info("getCurrentTemperature: current temperature is %s", this.CurrentTemperature);
|
|
327
|
-
}
|
|
328
|
-
callback(null, parseFloat(this.CurrentTemperature));
|
|
329
|
-
},
|
|
330
|
-
|
|
331
|
-
identify: function(callback) {
|
|
332
|
-
callback();
|
|
333
|
-
},
|
|
334
|
-
|
|
335
|
-
getServices: function() {
|
|
336
|
-
return this.services;
|
|
337
|
-
}
|
|
338
|
-
};
|
|
339
|
-
|