homebridge-nest-accfactory 0.2.11 → 0.3.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 +28 -0
- package/README.md +14 -7
- package/config.schema.json +118 -0
- package/dist/HomeKitDevice.js +203 -77
- package/dist/HomeKitHistory.js +1 -1
- package/dist/config.js +207 -0
- package/dist/devices.js +118 -0
- package/dist/index.js +2 -1
- package/dist/nexustalk.js +46 -48
- package/dist/{camera.js → plugins/camera.js} +216 -241
- package/dist/{doorbell.js → plugins/doorbell.js} +32 -30
- package/dist/plugins/floodlight.js +91 -0
- package/dist/plugins/heatlink.js +17 -0
- package/dist/{protect.js → plugins/protect.js} +26 -43
- package/dist/{tempsensor.js → plugins/tempsensor.js} +15 -19
- package/dist/{thermostat.js → plugins/thermostat.js} +426 -383
- package/dist/{weather.js → plugins/weather.js} +26 -60
- package/dist/protobuf/nest/services/apigateway.proto +31 -1
- package/dist/protobuf/nest/trait/firmware.proto +207 -89
- package/dist/protobuf/nest/trait/hvac.proto +1052 -312
- package/dist/protobuf/nest/trait/located.proto +51 -8
- package/dist/protobuf/nest/trait/network.proto +366 -36
- package/dist/protobuf/nest/trait/occupancy.proto +145 -17
- package/dist/protobuf/nest/trait/product/protect.proto +57 -43
- package/dist/protobuf/nest/trait/resourcedirectory.proto +8 -0
- package/dist/protobuf/nest/trait/sensor.proto +7 -1
- package/dist/protobuf/nest/trait/service.proto +3 -1
- package/dist/protobuf/nest/trait/structure.proto +60 -14
- package/dist/protobuf/nest/trait/ui.proto +41 -1
- package/dist/protobuf/nest/trait/user.proto +6 -1
- package/dist/protobuf/nest/trait/voiceassistant.proto +2 -1
- package/dist/protobuf/nestlabs/eventingapi/v1.proto +20 -1
- package/dist/protobuf/root.proto +1 -0
- package/dist/protobuf/wdl.proto +18 -2
- package/dist/protobuf/weave/common.proto +2 -1
- package/dist/protobuf/weave/trait/heartbeat.proto +41 -1
- package/dist/protobuf/weave/trait/power.proto +1 -0
- package/dist/protobuf/weave/trait/security.proto +10 -1
- package/dist/streamer.js +74 -78
- package/dist/system.js +1213 -1264
- package/dist/webrtc.js +39 -34
- package/package.json +11 -11
- package/dist/floodlight.js +0 -97
|
@@ -1,26 +1,33 @@
|
|
|
1
1
|
// Nest Thermostat
|
|
2
2
|
// Part of homebridge-nest-accfactory
|
|
3
3
|
//
|
|
4
|
-
// Code version 15/10/2024
|
|
5
4
|
// Mark Hulskamp
|
|
6
5
|
'use strict';
|
|
7
6
|
|
|
8
|
-
// Define our modules
|
|
9
|
-
import HomeKitDevice from './HomeKitDevice.js';
|
|
10
|
-
|
|
11
7
|
// Define nodejs module requirements
|
|
12
8
|
import path from 'node:path';
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
|
|
12
|
+
// Define our modules
|
|
13
|
+
import HomeKitDevice from '../HomeKitDevice.js';
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
// Define constants
|
|
16
|
+
const LOW_BATTERY_LEVEL = 10; // Low battery level percentage
|
|
15
17
|
const MIN_TEMPERATURE = 9; // Minimum temperature for Nest Thermostat
|
|
16
18
|
const MAX_TEMPERATURE = 32; // Maximum temperature for Nest Thermostat
|
|
19
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Make a defined for JS __dirname
|
|
17
20
|
|
|
18
21
|
export default class NestThermostat extends HomeKitDevice {
|
|
22
|
+
static TYPE = 'Thermostat';
|
|
23
|
+
static VERSION = '2025.06.15';
|
|
24
|
+
|
|
19
25
|
batteryService = undefined;
|
|
20
26
|
occupancyService = undefined;
|
|
21
27
|
humidityService = undefined;
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
switchService = undefined; // Hotwater heating boost control
|
|
29
|
+
fanService = undefined; // Fan control
|
|
30
|
+
dehumidifierService = undefined; // dehumidifier (only) control
|
|
24
31
|
externalCool = undefined; // External module function
|
|
25
32
|
externalHeat = undefined; // External module function
|
|
26
33
|
externalFan = undefined; // External module function
|
|
@@ -31,247 +38,192 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
// Class functions
|
|
34
|
-
async
|
|
41
|
+
async setupDevice() {
|
|
35
42
|
// Setup the thermostat service if not already present on the accessory
|
|
36
|
-
this.thermostatService = this.
|
|
37
|
-
if (this.thermostatService === undefined) {
|
|
38
|
-
this.thermostatService = this.accessory.addService(this.hap.Service.Thermostat, '', 1);
|
|
39
|
-
}
|
|
43
|
+
this.thermostatService = this.addHKService(this.hap.Service.Thermostat, '', 1);
|
|
40
44
|
this.thermostatService.setPrimaryService();
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
// Used to indicate active temperature if the thermostat is using its temperature sensor data
|
|
44
|
-
// or an external temperature sensor ie: Nest Temperature Sensor
|
|
45
|
-
this.thermostatService.addCharacteristic(this.hap.Characteristic.StatusActive);
|
|
46
|
-
}
|
|
47
|
-
if (this.thermostatService.testCharacteristic(this.hap.Characteristic.StatusFault) === false) {
|
|
48
|
-
this.thermostatService.addCharacteristic(this.hap.Characteristic.StatusFault);
|
|
49
|
-
}
|
|
50
|
-
if (this.thermostatService.testCharacteristic(this.hap.Characteristic.LockPhysicalControls) === false) {
|
|
51
|
-
// Setting can only be accessed via Eve App (or other 3rd party).
|
|
52
|
-
this.thermostatService.addCharacteristic(this.hap.Characteristic.LockPhysicalControls);
|
|
53
|
-
}
|
|
54
|
-
if (
|
|
55
|
-
this.deviceData?.has_air_filter === true &&
|
|
56
|
-
this.thermostatService.testCharacteristic(this.hap.Characteristic.FilterChangeIndication) === false
|
|
57
|
-
) {
|
|
58
|
-
// Setup air filter change characteristic
|
|
59
|
-
this.thermostatService.addCharacteristic(this.hap.Characteristic.FilterChangeIndication);
|
|
60
|
-
}
|
|
61
|
-
if (
|
|
62
|
-
this.deviceData?.has_air_filter === false &&
|
|
63
|
-
this.thermostatService.testCharacteristic(this.hap.Characteristic.FilterChangeIndication) === true
|
|
64
|
-
) {
|
|
65
|
-
// No longer configured to have an air filter, so remove characteristic from the accessory
|
|
66
|
-
this.thermostatService.removeCharacteristic(this.hap.Characteristic.FilterChangeIndication);
|
|
67
|
-
}
|
|
46
|
+
// Setup set characteristics
|
|
68
47
|
|
|
69
|
-
if
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
) {
|
|
73
|
-
// We have the capability for a humidifier, so setup target humidity characterisitc
|
|
74
|
-
this.thermostatService.addCharacteristic(this.hap.Characteristic.TargetRelativeHumidity);
|
|
75
|
-
}
|
|
48
|
+
// Used to indicate active temperature if the thermostat is using its temperature sensor data
|
|
49
|
+
// or an external temperature sensor ie: Nest Temperature Sensor
|
|
50
|
+
this.addHKCharacteristic(this.thermostatService, this.hap.Characteristic.StatusActive);
|
|
76
51
|
|
|
77
|
-
|
|
78
|
-
this.deviceData?.has_humidifier === false &&
|
|
79
|
-
this.thermostatService.testCharacteristic(this.hap.Characteristic.TargetRelativeHumidity) === true
|
|
80
|
-
) {
|
|
81
|
-
// No longer configured to use a humdifier, so remove characteristic from the accessory
|
|
82
|
-
this.thermostatService.removeCharacteristic(this.hap.Characteristic.TargetRelativeHumidity);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (this.thermostatService.testCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity) === false) {
|
|
86
|
-
this.thermostatService.addCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity);
|
|
87
|
-
}
|
|
52
|
+
this.addHKCharacteristic(this.thermostatService, this.hap.Characteristic.StatusFault);
|
|
88
53
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
this.thermostatService.getCharacteristic(this.hap.Characteristic.TargetHeatingCoolingState).setProps({
|
|
99
|
-
validValues: [this.hap.Characteristic.TargetHeatingCoolingState.OFF, this.hap.Characteristic.TargetHeatingCoolingState.COOL],
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
if (this.deviceData?.can_cool === true && this.deviceData?.can_heat === true) {
|
|
103
|
-
// heat and cool
|
|
104
|
-
this.thermostatService.getCharacteristic(this.hap.Characteristic.TargetHeatingCoolingState).setProps({
|
|
105
|
-
validValues: [
|
|
106
|
-
this.hap.Characteristic.TargetHeatingCoolingState.OFF,
|
|
107
|
-
this.hap.Characteristic.TargetHeatingCoolingState.HEAT,
|
|
108
|
-
this.hap.Characteristic.TargetHeatingCoolingState.COOL,
|
|
109
|
-
this.hap.Characteristic.TargetHeatingCoolingState.AUTO,
|
|
110
|
-
],
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
if (this.deviceData?.can_cool === false && this.deviceData?.can_heat === false) {
|
|
114
|
-
// only off mode
|
|
115
|
-
this.thermostatService.getCharacteristic(this.hap.Characteristic.TargetHeatingCoolingState).setProps({
|
|
116
|
-
validValues: [this.hap.Characteristic.TargetHeatingCoolingState.OFF],
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Set default ranges - based on celsuis ranges to which the Nest Thermostat operates
|
|
121
|
-
this.thermostatService.getCharacteristic(this.hap.Characteristic.CurrentTemperature).setProps({
|
|
122
|
-
minStep: 0.5,
|
|
123
|
-
});
|
|
124
|
-
this.thermostatService.getCharacteristic(this.hap.Characteristic.TargetTemperature).setProps({
|
|
125
|
-
minStep: 0.5,
|
|
126
|
-
minValue: MIN_TEMPERATURE,
|
|
127
|
-
maxValue: MAX_TEMPERATURE,
|
|
128
|
-
});
|
|
129
|
-
this.thermostatService.getCharacteristic(this.hap.Characteristic.CoolingThresholdTemperature).setProps({
|
|
130
|
-
minStep: 0.5,
|
|
131
|
-
minValue: MIN_TEMPERATURE,
|
|
132
|
-
maxValue: MAX_TEMPERATURE,
|
|
133
|
-
});
|
|
134
|
-
this.thermostatService.getCharacteristic(this.hap.Characteristic.HeatingThresholdTemperature).setProps({
|
|
135
|
-
minStep: 0.5,
|
|
136
|
-
minValue: MIN_TEMPERATURE,
|
|
137
|
-
maxValue: MAX_TEMPERATURE,
|
|
54
|
+
this.addHKCharacteristic(this.thermostatService, this.hap.Characteristic.LockPhysicalControls, {
|
|
55
|
+
onSet: (value) => {
|
|
56
|
+
this.setChildlock('', value);
|
|
57
|
+
},
|
|
58
|
+
onGet: () => {
|
|
59
|
+
return this.deviceData.temperature_lock === true
|
|
60
|
+
? this.hap.Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED
|
|
61
|
+
: this.hap.Characteristic.LockPhysicalControls.CONTROL_LOCK_DISABLED;
|
|
62
|
+
},
|
|
138
63
|
});
|
|
139
64
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
this.thermostatService.getCharacteristic(this.hap.Characteristic.TargetHeatingCoolingState).onSet((value) => {
|
|
145
|
-
this.setMode(value);
|
|
146
|
-
});
|
|
147
|
-
this.thermostatService.getCharacteristic(this.hap.Characteristic.TargetTemperature).onSet((value) => {
|
|
148
|
-
this.setTemperature(this.hap.Characteristic.TargetTemperature, value);
|
|
149
|
-
});
|
|
150
|
-
this.thermostatService.getCharacteristic(this.hap.Characteristic.CoolingThresholdTemperature).onSet((value) => {
|
|
151
|
-
this.setTemperature(this.hap.Characteristic.CoolingThresholdTemperature, value);
|
|
152
|
-
});
|
|
153
|
-
this.thermostatService.getCharacteristic(this.hap.Characteristic.HeatingThresholdTemperature).onSet((value) => {
|
|
154
|
-
this.setTemperature(this.hap.Characteristic.HeatingThresholdTemperature, value);
|
|
155
|
-
});
|
|
156
|
-
this.thermostatService.getCharacteristic(this.hap.Characteristic.LockPhysicalControls).onSet((value) => {
|
|
157
|
-
this.setChildlock('', value);
|
|
65
|
+
this.addHKCharacteristic(this.thermostatService, this.hap.Characteristic.CurrentRelativeHumidity, {
|
|
66
|
+
onGet: () => {
|
|
67
|
+
return this.deviceData.current_humidity;
|
|
68
|
+
},
|
|
158
69
|
});
|
|
159
70
|
|
|
160
|
-
this.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
71
|
+
this.addHKCharacteristic(this.thermostatService, this.hap.Characteristic.TemperatureDisplayUnits, {
|
|
72
|
+
onSet: (value) => {
|
|
73
|
+
this.setDisplayUnit(value);
|
|
74
|
+
},
|
|
75
|
+
onGet: () => {
|
|
76
|
+
return this.deviceData.temperature_scale === 'C'
|
|
77
|
+
? this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS
|
|
78
|
+
: this.hap.Characteristic.TemperatureDisplayUnits.FAHRENHEIT;
|
|
79
|
+
},
|
|
164
80
|
});
|
|
165
|
-
|
|
166
|
-
|
|
81
|
+
|
|
82
|
+
this.addHKCharacteristic(this.thermostatService, this.hap.Characteristic.CurrentTemperature, {
|
|
83
|
+
props: { minStep: 0.5 },
|
|
84
|
+
onGet: () => {
|
|
85
|
+
return this.deviceData.current_temperature;
|
|
86
|
+
},
|
|
167
87
|
});
|
|
168
|
-
|
|
169
|
-
|
|
88
|
+
|
|
89
|
+
this.addHKCharacteristic(this.thermostatService, this.hap.Characteristic.TargetTemperature, {
|
|
90
|
+
onSet: (value) => {
|
|
91
|
+
this.setTemperature(this.hap.Characteristic.TargetTemperature, value);
|
|
92
|
+
},
|
|
93
|
+
onGet: () => {
|
|
94
|
+
return this.getTemperature(this.hap.Characteristic.TargetTemperature);
|
|
95
|
+
},
|
|
170
96
|
});
|
|
171
|
-
|
|
172
|
-
|
|
97
|
+
|
|
98
|
+
this.addHKCharacteristic(this.thermostatService, this.hap.Characteristic.CoolingThresholdTemperature, {
|
|
99
|
+
props: {
|
|
100
|
+
minStep: 0.5,
|
|
101
|
+
minValue: MIN_TEMPERATURE,
|
|
102
|
+
maxValue: MAX_TEMPERATURE,
|
|
103
|
+
},
|
|
104
|
+
onSet: (value) => {
|
|
105
|
+
this.setTemperature(this.hap.Characteristic.CoolingThresholdTemperature, value);
|
|
106
|
+
},
|
|
107
|
+
onGet: () => {
|
|
108
|
+
return this.getTemperature(this.hap.Characteristic.CoolingThresholdTemperature);
|
|
109
|
+
},
|
|
173
110
|
});
|
|
174
|
-
|
|
175
|
-
|
|
111
|
+
|
|
112
|
+
this.addHKCharacteristic(this.thermostatService, this.hap.Characteristic.HeatingThresholdTemperature, {
|
|
113
|
+
props: {
|
|
114
|
+
minStep: 0.5,
|
|
115
|
+
minValue: MIN_TEMPERATURE,
|
|
116
|
+
maxValue: MAX_TEMPERATURE,
|
|
117
|
+
},
|
|
118
|
+
onSet: (value) => {
|
|
119
|
+
this.setTemperature(this.hap.Characteristic.HeatingThresholdTemperature, value);
|
|
120
|
+
},
|
|
121
|
+
onGet: () => {
|
|
122
|
+
return this.getTemperature(this.hap.Characteristic.HeatingThresholdTemperature);
|
|
123
|
+
},
|
|
176
124
|
});
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
:
|
|
125
|
+
|
|
126
|
+
this.addHKCharacteristic(this.thermostatService, this.hap.Characteristic.TargetHeatingCoolingState, {
|
|
127
|
+
props: {
|
|
128
|
+
validValues:
|
|
129
|
+
this.deviceData?.can_cool === true && this.deviceData?.can_heat === true
|
|
130
|
+
? [
|
|
131
|
+
this.hap.Characteristic.TargetHeatingCoolingState.OFF,
|
|
132
|
+
this.hap.Characteristic.TargetHeatingCoolingState.HEAT,
|
|
133
|
+
this.hap.Characteristic.TargetHeatingCoolingState.COOL,
|
|
134
|
+
this.hap.Characteristic.TargetHeatingCoolingState.AUTO,
|
|
135
|
+
]
|
|
136
|
+
: this.deviceData?.can_heat === true
|
|
137
|
+
? [this.hap.Characteristic.TargetHeatingCoolingState.OFF, this.hap.Characteristic.TargetHeatingCoolingState.HEAT]
|
|
138
|
+
: this.deviceData?.can_cool === true
|
|
139
|
+
? [this.hap.Characteristic.TargetHeatingCoolingState.OFF, this.hap.Characteristic.TargetHeatingCoolingState.COOL]
|
|
140
|
+
: [this.hap.Characteristic.TargetHeatingCoolingState.OFF],
|
|
141
|
+
},
|
|
142
|
+
onSet: (value) => {
|
|
143
|
+
this.setMode(value);
|
|
144
|
+
},
|
|
145
|
+
onGet: () => {
|
|
146
|
+
return this.getMode();
|
|
147
|
+
},
|
|
181
148
|
});
|
|
182
149
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
150
|
+
if (this.deviceData?.has_air_filter === true) {
|
|
151
|
+
// We have the capability for an air filter, so setup filter change characterisitc
|
|
152
|
+
this.addHKCharacteristic(this.thermostatService, this.hap.Characteristic.FilterChangeIndication);
|
|
153
|
+
}
|
|
154
|
+
if (this.deviceData?.has_air_filter === false) {
|
|
155
|
+
// No longer configured to have an air filter, so remove characteristic from the accessory
|
|
156
|
+
this.thermostatService.removeCharacteristic(this.hap.Characteristic.FilterChangeIndication);
|
|
187
157
|
}
|
|
158
|
+
|
|
159
|
+
// Setup occupancy service if not already present on the accessory
|
|
160
|
+
this.occupancyService = this.addHKService(this.hap.Service.OccupancySensor, '', 1);
|
|
188
161
|
this.thermostatService.addLinkedService(this.occupancyService);
|
|
189
162
|
|
|
190
163
|
// Setup battery service if not already present on the accessory
|
|
191
|
-
this.batteryService = this.
|
|
192
|
-
if (this.batteryService === undefined) {
|
|
193
|
-
this.batteryService = this.accessory.addService(this.hap.Service.Battery, '', 1);
|
|
194
|
-
}
|
|
164
|
+
this.batteryService = this.addHKService(this.hap.Service.Battery, '', 1);
|
|
195
165
|
this.batteryService.setHiddenService(true);
|
|
196
166
|
this.thermostatService.addLinkedService(this.batteryService);
|
|
197
167
|
|
|
198
168
|
// Setup fan service if supported by the thermostat and not already present on the accessory
|
|
199
|
-
this.fanService = this.accessory.getService(this.hap.Service.Fanv2);
|
|
200
169
|
if (this.deviceData?.has_fan === true) {
|
|
201
|
-
|
|
202
|
-
this.fanService = this.accessory.addService(this.hap.Service.Fanv2, '', 1);
|
|
203
|
-
}
|
|
204
|
-
if (this.fanService.testCharacteristic(this.hap.Characteristic.RotationSpeed) === false) {
|
|
205
|
-
this.fanService.addCharacteristic(this.hap.Characteristic.RotationSpeed);
|
|
206
|
-
}
|
|
207
|
-
this.fanService.getCharacteristic(this.hap.Characteristic.RotationSpeed).setProps({
|
|
208
|
-
minStep: 100 / this.deviceData.fan_max_speed,
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
this.thermostatService.addLinkedService(this.fanService);
|
|
212
|
-
|
|
213
|
-
this.fanService.getCharacteristic(this.hap.Characteristic.Active).onSet((value) => {
|
|
214
|
-
this.setFan(
|
|
215
|
-
value,
|
|
216
|
-
value === this.hap.Characteristic.Active.ACTIVE ? (this.deviceData.fan_timer_speed / this.deviceData.fan_max_speed) * 100 : 0,
|
|
217
|
-
);
|
|
218
|
-
});
|
|
219
|
-
this.fanService.getCharacteristic(this.hap.Characteristic.RotationSpeed).onSet((value) => {
|
|
220
|
-
this.setFan(value !== 0 ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE, value);
|
|
221
|
-
});
|
|
222
|
-
this.fanService.getCharacteristic(this.hap.Characteristic.Active).onGet(() => {
|
|
223
|
-
return this.deviceData.fan_state === true ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE;
|
|
224
|
-
});
|
|
225
|
-
this.fanService.getCharacteristic(this.hap.Characteristic.RotationSpeed).onGet(() => {
|
|
226
|
-
return (this.deviceData.fan_timer_speed / this.deviceData.fan_max_speed) * 100;
|
|
227
|
-
});
|
|
170
|
+
this.#setupFan();
|
|
228
171
|
}
|
|
229
|
-
if (this.deviceData?.has_fan === false
|
|
172
|
+
if (this.deviceData?.has_fan === false) {
|
|
230
173
|
// No longer have a Fan configured and service present, so removed it
|
|
231
|
-
this.accessory.
|
|
174
|
+
this.fanService = this.accessory.getService(this.hap.Service.Fanv2);
|
|
175
|
+
if (this.fanService !== undefined) {
|
|
176
|
+
this.accessory.removeService(this.fanService);
|
|
177
|
+
}
|
|
232
178
|
this.fanService = undefined;
|
|
233
179
|
}
|
|
234
180
|
|
|
235
181
|
// Setup dehumifider service if supported by the thermostat and not already present on the accessory
|
|
236
|
-
this.dehumidifierService = this.accessory.getService(this.hap.Service.HumidifierDehumidifier);
|
|
237
182
|
if (this.deviceData?.has_dehumidifier === true) {
|
|
238
|
-
|
|
239
|
-
this.dehumidifierService = this.accessory.addService(this.hap.Service.HumidifierDehumidifier, '', 1);
|
|
240
|
-
}
|
|
241
|
-
this.thermostatService.addLinkedService(this.dehumidifierService);
|
|
242
|
-
|
|
243
|
-
this.dehumidifierService.getCharacteristic(this.hap.Characteristic.TargetHumidifierDehumidifierState).setProps({
|
|
244
|
-
validValues: [this.hap.Characteristic.TargetHumidifierDehumidifierState.DEHUMIDIFIER],
|
|
245
|
-
});
|
|
246
|
-
this.dehumidifierService.getCharacteristic(this.hap.Characteristic.Active).onSet((value) => {
|
|
247
|
-
this.setDehumidifier(value);
|
|
248
|
-
});
|
|
249
|
-
this.dehumidifierService.getCharacteristic(this.hap.Characteristic.Active).onGet(() => {
|
|
250
|
-
return this.deviceData.dehumidifier_state === true
|
|
251
|
-
? this.hap.Characteristic.Active.ACTIVE
|
|
252
|
-
: this.hap.Characteristic.Active.INACTIVE;
|
|
253
|
-
});
|
|
183
|
+
this.#setupDehumidifier();
|
|
254
184
|
}
|
|
255
|
-
if (this.deviceData?.has_dehumidifier === false
|
|
185
|
+
if (this.deviceData?.has_dehumidifier === false) {
|
|
256
186
|
// No longer have a dehumidifier configured and service present, so removed it
|
|
257
|
-
this.accessory.
|
|
187
|
+
this.dehumidifierService = this.accessory.getService(this.hap.Service.HumidifierDehumidifier);
|
|
188
|
+
if (this.dehumidifierService !== undefined) {
|
|
189
|
+
this.accessory.removeService(this.dehumidifierService);
|
|
190
|
+
}
|
|
258
191
|
this.dehumidifierService = undefined;
|
|
259
192
|
}
|
|
260
193
|
|
|
261
194
|
// Setup humdity service if configured to be seperate and not already present on the accessory
|
|
262
|
-
this.humidityService = this.accessory.getService(this.hap.Service.HumiditySensor);
|
|
263
195
|
if (this.deviceData?.humiditySensor === true) {
|
|
264
|
-
|
|
265
|
-
this.humidityService = this.accessory.addService(this.hap.Service.HumiditySensor, '', 1);
|
|
266
|
-
}
|
|
196
|
+
this.humidityService = this.addHKService(this.hap.Service.HumiditySensor, '', 1);
|
|
267
197
|
this.thermostatService.addLinkedService(this.humidityService);
|
|
198
|
+
|
|
199
|
+
this.addHKCharacteristic(this.humidityService, this.hap.Characteristic.CurrentRelativeHumidity, {
|
|
200
|
+
onGet: () => {
|
|
201
|
+
return this.deviceData.current_humidity;
|
|
202
|
+
},
|
|
203
|
+
});
|
|
268
204
|
}
|
|
269
|
-
if (this.deviceData?.humiditySensor === false
|
|
205
|
+
if (this.deviceData?.humiditySensor === false) {
|
|
270
206
|
// No longer have a seperate humidity sensor configure and service present, so removed it
|
|
271
|
-
this.accessory.
|
|
207
|
+
this.humidityService = this.accessory.getService(this.hap.Service.HumiditySensor);
|
|
208
|
+
if (this.humidityService !== undefined) {
|
|
209
|
+
this.accessory.removeService(this.humidityService);
|
|
210
|
+
}
|
|
272
211
|
this.humidityService = undefined;
|
|
273
212
|
}
|
|
274
213
|
|
|
214
|
+
// Setup hotwater heating boost service if supported by the thermostat and not already present on the accessory
|
|
215
|
+
if (this.deviceData?.has_hot_water_control === true) {
|
|
216
|
+
this.#setupHotwaterBoost();
|
|
217
|
+
}
|
|
218
|
+
if (this.deviceData?.has_hot_water_control === false) {
|
|
219
|
+
// No longer have hotwater heating boost configured and service present, so removed it
|
|
220
|
+
this.switchService = this.accessory.getService(this.hap.Service.Switch);
|
|
221
|
+
if (this.switchService !== undefined) {
|
|
222
|
+
this.accessory.removeService(this.switchService);
|
|
223
|
+
}
|
|
224
|
+
this.switchService = undefined;
|
|
225
|
+
}
|
|
226
|
+
|
|
275
227
|
// Setup linkage to EveHome app if configured todo so
|
|
276
228
|
if (
|
|
277
229
|
this.deviceData?.eveHistory === true &&
|
|
@@ -288,42 +240,17 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
288
240
|
// Attempt to load any external modules for this thermostat
|
|
289
241
|
// We support external cool/heat/fan/dehumidifier module functions
|
|
290
242
|
// This is all undocumented on how to use, as its for my specific use case :-)
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (typeof externalModule?.default === 'function') {
|
|
303
|
-
loadedModule = externalModule.default(this.log, options);
|
|
304
|
-
}
|
|
305
|
-
// eslint-disable-next-line no-unused-vars
|
|
306
|
-
} catch (error) {
|
|
307
|
-
this?.log?.warn && this.log.warn('Failed to load specified external module for thermostat "%s"', module);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return loadedModule;
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
this.externalCool = await loadExternalModule(this.deviceData?.externalCool);
|
|
314
|
-
this.externalHeat = await loadExternalModule(this.deviceData?.externalHeat);
|
|
315
|
-
this.externalFan = await loadExternalModule(this.deviceData?.externalFan);
|
|
316
|
-
this.externalDehumidifier = await loadExternalModule(this.deviceData?.externalDehumidifier);
|
|
317
|
-
|
|
318
|
-
// Create extra details for output
|
|
319
|
-
let postSetupDetails = [];
|
|
320
|
-
this.humidityService !== undefined && postSetupDetails.push('Seperate humidity sensor');
|
|
321
|
-
this.externalCool !== undefined && postSetupDetails.push('Using external cooling module');
|
|
322
|
-
this.externalHeat !== undefined && postSetupDetails.push('Using external heating module');
|
|
323
|
-
this.externalFan !== undefined && postSetupDetails.push('Using external fan module');
|
|
324
|
-
this.externalDehumidifier !== undefined && postSetupDetails.push('Using external dehumidification module');
|
|
325
|
-
|
|
326
|
-
return postSetupDetails;
|
|
243
|
+
this.externalCool = await this.#loadExternalModule(this.deviceData?.externalCool, ['cool', 'off']);
|
|
244
|
+
this.externalHeat = await this.#loadExternalModule(this.deviceData?.externalHeat, ['heat', 'off']);
|
|
245
|
+
this.externalFan = await this.#loadExternalModule(this.deviceData?.externalFan, ['fan', 'off']);
|
|
246
|
+
this.externalDehumidifier = await this.#loadExternalModule(this.deviceData?.externalDehumidifier, ['dehumidifier', 'off']);
|
|
247
|
+
|
|
248
|
+
// Extra setup details for output
|
|
249
|
+
this.humidityService !== undefined && this.postSetupDetail('Seperate humidity sensor');
|
|
250
|
+
this.externalCool !== undefined && this.postSetupDetail('Using external cooling module');
|
|
251
|
+
this.externalHeat !== undefined && this.postSetupDetail('Using external heating module');
|
|
252
|
+
this.externalFan !== undefined && this.postSetupDetail('Using external fan module');
|
|
253
|
+
this.externalDehumidifier !== undefined && this.postSetupDetail('Using external dehumidification module');
|
|
327
254
|
}
|
|
328
255
|
|
|
329
256
|
setFan(fanState, speed) {
|
|
@@ -339,30 +266,42 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
339
266
|
this.fanService.updateCharacteristic(this.hap.Characteristic.Active, fanState);
|
|
340
267
|
this.fanService.updateCharacteristic(this.hap.Characteristic.RotationSpeed, speed);
|
|
341
268
|
|
|
342
|
-
this?.log?.info
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
);
|
|
269
|
+
this?.log?.info?.(
|
|
270
|
+
'Set fan on thermostat "%s" to "%s"',
|
|
271
|
+
this.deviceData.description,
|
|
272
|
+
fanState === this.hap.Characteristic.Active.ACTIVE ? 'On with fan speed of ' + speed + '%' : 'Off',
|
|
273
|
+
);
|
|
348
274
|
}
|
|
349
275
|
}
|
|
350
276
|
|
|
351
277
|
setDehumidifier(dehumidiferState) {
|
|
352
278
|
this.set({
|
|
353
279
|
uuid: this.deviceData.nest_google_uuid,
|
|
354
|
-
|
|
280
|
+
dehumidifier_state: dehumidiferState === this.hap.Characteristic.Active.ACTIVE ? true : false,
|
|
355
281
|
});
|
|
356
282
|
this.dehumidifierService.updateCharacteristic(this.hap.Characteristic.Active, dehumidiferState);
|
|
357
283
|
|
|
358
|
-
this?.log?.info
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
284
|
+
this?.log?.info?.(
|
|
285
|
+
'Set dehumidifer on thermostat "%s" to "%s"',
|
|
286
|
+
this.deviceData.description,
|
|
287
|
+
dehumidiferState === this.hap.Characteristic.Active.ACTIVE
|
|
288
|
+
? 'On with target humidity level of ' + this.deviceData.target_humidity + '%'
|
|
289
|
+
: 'Off',
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
setHotwaterBoost(hotwaterState) {
|
|
294
|
+
this.set({
|
|
295
|
+
uuid: this.deviceData.nest_google_uuid,
|
|
296
|
+
hot_water_boost_active: { state: hotwaterState === true, time: this.deviceData.hotWaterBoostTime },
|
|
297
|
+
});
|
|
298
|
+
this.switchService.updateCharacteristic(this.hap.Characteristic.On, hotwaterState);
|
|
299
|
+
|
|
300
|
+
this?.log?.info?.(
|
|
301
|
+
'Set hotwater boost heating on thermostat "%s" to "%s"',
|
|
302
|
+
this.deviceData.description,
|
|
303
|
+
hotwaterState === true ? 'On for ' + formatDuration(this.deviceData.hotWaterBoostTime) : 'Off',
|
|
304
|
+
);
|
|
366
305
|
}
|
|
367
306
|
|
|
368
307
|
setDisplayUnit(temperatureUnit) {
|
|
@@ -372,12 +311,11 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
372
311
|
});
|
|
373
312
|
this.thermostatService.updateCharacteristic(this.hap.Characteristic.TemperatureDisplayUnits, temperatureUnit);
|
|
374
313
|
|
|
375
|
-
this?.log?.info
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
);
|
|
314
|
+
this?.log?.info?.(
|
|
315
|
+
'Set temperature units on thermostat "%s" to "%s"',
|
|
316
|
+
this.deviceData.description,
|
|
317
|
+
temperatureUnit === this.hap.Characteristic.TemperatureDisplayUnits.CELSIUS ? '°C' : '°F',
|
|
318
|
+
);
|
|
381
319
|
}
|
|
382
320
|
|
|
383
321
|
setMode(thermostatMode) {
|
|
@@ -443,7 +381,7 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
443
381
|
|
|
444
382
|
this.set({ uuid: this.deviceData.nest_google_uuid, hvac_mode: mode });
|
|
445
383
|
|
|
446
|
-
this?.log?.info
|
|
384
|
+
this?.log?.info?.('Set mode on "%s" to "%s"', this.deviceData.description, mode);
|
|
447
385
|
}
|
|
448
386
|
}
|
|
449
387
|
|
|
@@ -479,17 +417,16 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
479
417
|
) {
|
|
480
418
|
this.set({ uuid: this.deviceData.nest_google_uuid, target_temperature: temperature });
|
|
481
419
|
|
|
482
|
-
this?.log?.info
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
this.
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
);
|
|
420
|
+
this?.log?.info?.(
|
|
421
|
+
'Set %s%s temperature on "%s" to "%s °C"',
|
|
422
|
+
this.deviceData.hvac_mode.toUpperCase().includes('ECO') ? 'eco mode ' : '',
|
|
423
|
+
this.thermostatService.getCharacteristic(this.hap.Characteristic.TargetHeatingCoolingState).value ===
|
|
424
|
+
this.hap.Characteristic.TargetHeatingCoolingState.HEAT
|
|
425
|
+
? 'heating'
|
|
426
|
+
: 'cooling',
|
|
427
|
+
this.deviceData.description,
|
|
428
|
+
temperature,
|
|
429
|
+
);
|
|
493
430
|
}
|
|
494
431
|
if (
|
|
495
432
|
characteristic.UUID === this.hap.Characteristic.HeatingThresholdTemperature.UUID &&
|
|
@@ -498,13 +435,12 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
498
435
|
) {
|
|
499
436
|
this.set({ uuid: this.deviceData.nest_google_uuid, target_temperature_low: temperature });
|
|
500
437
|
|
|
501
|
-
this?.log?.info
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
);
|
|
438
|
+
this?.log?.info?.(
|
|
439
|
+
'Set %sheating temperature on "%s" to "%s °C"',
|
|
440
|
+
this.deviceData.hvac_mode.toUpperCase().includes('ECO') ? 'eco mode ' : '',
|
|
441
|
+
this.deviceData.description,
|
|
442
|
+
temperature,
|
|
443
|
+
);
|
|
508
444
|
}
|
|
509
445
|
if (
|
|
510
446
|
characteristic.UUID === this.hap.Characteristic.CoolingThresholdTemperature.UUID &&
|
|
@@ -513,13 +449,12 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
513
449
|
) {
|
|
514
450
|
this.set({ uuid: this.deviceData.nest_google_uuid, target_temperature_high: temperature });
|
|
515
451
|
|
|
516
|
-
this?.log?.info
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
);
|
|
452
|
+
this?.log?.info?.(
|
|
453
|
+
'Set %scooling temperature on "%s" to "%s °C"',
|
|
454
|
+
this.deviceData.hvac_mode.toUpperCase().includes('ECO') ? 'eco mode ' : '',
|
|
455
|
+
this.deviceData.description,
|
|
456
|
+
temperature,
|
|
457
|
+
);
|
|
523
458
|
}
|
|
524
459
|
|
|
525
460
|
this.thermostatService.updateCharacteristic(characteristic, temperature); // Update HomeKit with value
|
|
@@ -567,15 +502,14 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
567
502
|
temperature_lock: value === this.hap.Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED ? true : false,
|
|
568
503
|
});
|
|
569
504
|
|
|
570
|
-
this?.log?.info
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
);
|
|
505
|
+
this?.log?.info?.(
|
|
506
|
+
'Setting Childlock on "%s" to "%s"',
|
|
507
|
+
this.deviceData.description,
|
|
508
|
+
value === this.hap.Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED ? 'Enabled' : 'Disabled',
|
|
509
|
+
);
|
|
576
510
|
}
|
|
577
511
|
|
|
578
|
-
|
|
512
|
+
updateDevice(deviceData) {
|
|
579
513
|
if (
|
|
580
514
|
typeof deviceData !== 'object' ||
|
|
581
515
|
this.thermostatService === undefined ||
|
|
@@ -630,7 +564,7 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
630
564
|
this.batteryService.updateCharacteristic(this.hap.Characteristic.BatteryLevel, deviceData.battery_level);
|
|
631
565
|
this.batteryService.updateCharacteristic(
|
|
632
566
|
this.hap.Characteristic.StatusLowBattery,
|
|
633
|
-
deviceData.battery_level >
|
|
567
|
+
deviceData.battery_level > LOW_BATTERY_LEVEL
|
|
634
568
|
? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL
|
|
635
569
|
: this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW,
|
|
636
570
|
);
|
|
@@ -661,23 +595,7 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
661
595
|
if (deviceData.has_fan !== this.deviceData.has_fan) {
|
|
662
596
|
if (deviceData.has_fan === true && this.deviceData.has_fan === false && this.fanService === undefined) {
|
|
663
597
|
// Fan has been added
|
|
664
|
-
this
|
|
665
|
-
|
|
666
|
-
if (this.fanService.testCharacteristic(this.hap.Characteristic.RotationSpeed) === false) {
|
|
667
|
-
this.fanService.addCharacteristic(this.hap.Characteristic.RotationSpeed);
|
|
668
|
-
}
|
|
669
|
-
this.fanService.getCharacteristic(this.hap.Characteristic.RotationSpeed).setProps({
|
|
670
|
-
minStep: 100 / this.deviceData.fan_max_speed,
|
|
671
|
-
});
|
|
672
|
-
this.thermostatService.addLinkedService(this.fanService);
|
|
673
|
-
|
|
674
|
-
this.fanService.getCharacteristic(this.hap.Characteristic.Active).onSet((value) => {
|
|
675
|
-
this.setFan(value);
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
this.fanService.getCharacteristic(this.hap.Characteristic.Active).onGet(() => {
|
|
679
|
-
return this.deviceData.fan_state === true ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE;
|
|
680
|
-
});
|
|
598
|
+
this.#setupFan();
|
|
681
599
|
}
|
|
682
600
|
if (deviceData.has_fan === false && this.deviceData.has_fan === true && this.fanService !== undefined) {
|
|
683
601
|
// Fan has been removed
|
|
@@ -685,34 +603,18 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
685
603
|
this.fanService = undefined;
|
|
686
604
|
}
|
|
687
605
|
|
|
688
|
-
this?.log?.info
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
);
|
|
606
|
+
this?.log?.info?.(
|
|
607
|
+
'Fan setup on thermostat "%s" has changed. Fan was',
|
|
608
|
+
deviceData.description,
|
|
609
|
+
this.fanService === undefined ? 'removed' : 'added',
|
|
610
|
+
);
|
|
694
611
|
}
|
|
695
612
|
|
|
696
613
|
// Check for dehumidifer setup change on thermostat
|
|
697
614
|
if (deviceData.has_dehumidifier !== this.deviceData.has_dehumidifier) {
|
|
698
615
|
if (deviceData.has_dehumidifier === true && this.deviceData.has_dehumidifier === false && this.dehumidifierService === undefined) {
|
|
699
616
|
// Dehumidifier has been added
|
|
700
|
-
this
|
|
701
|
-
this.thermostatService.addLinkedService(this.dehumidifierService);
|
|
702
|
-
|
|
703
|
-
this.dehumidifierService.getCharacteristic(this.hap.Characteristic.TargetHumidifierDehumidifierState).setProps({
|
|
704
|
-
validValues: [this.hap.Characteristic.TargetHumidifierDehumidifierState.DEHUMIDIFIER],
|
|
705
|
-
});
|
|
706
|
-
|
|
707
|
-
this.dehumidifierService.getCharacteristic(this.hap.Characteristic.Active).onSet((value) => {
|
|
708
|
-
this.setDehumidifier(value);
|
|
709
|
-
});
|
|
710
|
-
|
|
711
|
-
this.dehumidifierService.getCharacteristic(this.hap.Characteristic.Active).onGet(() => {
|
|
712
|
-
return this.deviceData.dehumidifier_state === true
|
|
713
|
-
? this.hap.Characteristic.Active.ACTIVE
|
|
714
|
-
: this.hap.Characteristic.Active.INACTIVE;
|
|
715
|
-
});
|
|
617
|
+
this.#setupDehumidifier();
|
|
716
618
|
}
|
|
717
619
|
if (deviceData.has_dehumidifier === false && this.deviceData.has_dehumidifier === true && this.dehumidifierService !== undefined) {
|
|
718
620
|
// Dehumidifer has been removed
|
|
@@ -720,12 +622,41 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
720
622
|
this.dehumidifierService = undefined;
|
|
721
623
|
}
|
|
722
624
|
|
|
723
|
-
this?.log?.info
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
625
|
+
this?.log?.info?.(
|
|
626
|
+
'Dehumidifier setup on thermostat "%s" has changed. Dehumidifier was',
|
|
627
|
+
deviceData.description,
|
|
628
|
+
this.dehumidifierService === undefined ? 'removed' : 'added',
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Check for hotwater heating boost setup change on thermostat
|
|
633
|
+
if (deviceData.has_hot_water_control !== this.deviceData.has_hot_water_control) {
|
|
634
|
+
if (
|
|
635
|
+
deviceData.has_hot_water_control === true &&
|
|
636
|
+
this.deviceData.has_hot_water_control === false &&
|
|
637
|
+
this.switchService === undefined
|
|
638
|
+
) {
|
|
639
|
+
// hotwater heating boost has been added
|
|
640
|
+
this.switchService = this.accessory.getService(this.hap.Service.Switch);
|
|
641
|
+
if (this.deviceData.has_hot_water_control === true) {
|
|
642
|
+
this.#setupHotwaterBoost();
|
|
643
|
+
}
|
|
644
|
+
if (
|
|
645
|
+
deviceData.has_hot_water_control === false &&
|
|
646
|
+
this.deviceData.has_hot_water_control === true &&
|
|
647
|
+
this.switchService !== undefined
|
|
648
|
+
) {
|
|
649
|
+
// hotwater heating boost has been removed
|
|
650
|
+
this.accessory.removeService(this.switchService);
|
|
651
|
+
this.switchService = undefined;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
this?.log?.info?.(
|
|
656
|
+
'hotwater heating boost setup on thermostat "%s" has changed. Hotwater heating boost was',
|
|
657
|
+
deviceData.description,
|
|
658
|
+
this.switchService === undefined ? 'removed' : 'added',
|
|
659
|
+
);
|
|
729
660
|
}
|
|
730
661
|
|
|
731
662
|
if (deviceData.can_cool !== this.deviceData.can_cool || deviceData.can_heat !== this.deviceData.can_heat) {
|
|
@@ -762,7 +693,7 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
762
693
|
});
|
|
763
694
|
}
|
|
764
695
|
|
|
765
|
-
this?.log?.info
|
|
696
|
+
this?.log?.info?.('Heating/cooling setup on thermostat on "%s" has changed', deviceData.description);
|
|
766
697
|
}
|
|
767
698
|
|
|
768
699
|
// Update current mode temperatures
|
|
@@ -808,7 +739,7 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
808
739
|
historyEntry.target = { low: deviceData.target_temperature_low, high: deviceData.target_temperature_high };
|
|
809
740
|
}
|
|
810
741
|
if (deviceData.can_cool === false && deviceData.can_heat === false && deviceData.hvac_mode.toUpperCase() === 'OFF') {
|
|
811
|
-
// off mode
|
|
742
|
+
// off mode
|
|
812
743
|
this.thermostatService.updateCharacteristic(this.hap.Characteristic.TargetTemperature, deviceData.target_temperature);
|
|
813
744
|
this.thermostatService.updateCharacteristic(
|
|
814
745
|
this.hap.Characteristic.TargetHeatingCoolingState,
|
|
@@ -819,22 +750,18 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
819
750
|
|
|
820
751
|
// Update current state
|
|
821
752
|
if (deviceData.hvac_state.toUpperCase() === 'HEATING') {
|
|
822
|
-
if (this.deviceData.hvac_state.toUpperCase() === 'COOLING' && this.externalCool
|
|
753
|
+
if (this.deviceData.hvac_state.toUpperCase() === 'COOLING' && typeof this.externalCool?.off === 'function') {
|
|
823
754
|
// Switched to heating mode and external cooling external code was being used, so stop cooling via cooling external code
|
|
824
|
-
|
|
825
|
-
this.externalCool.off();
|
|
826
|
-
}
|
|
755
|
+
this.externalCool.off();
|
|
827
756
|
}
|
|
828
757
|
if (
|
|
829
758
|
(this.deviceData.hvac_state.toUpperCase() !== 'HEATING' ||
|
|
830
759
|
deviceData.target_temperature_low !== this.deviceData.target_temperature_low) &&
|
|
831
|
-
this.externalHeat
|
|
760
|
+
typeof this.externalHeat?.heat === 'function'
|
|
832
761
|
) {
|
|
833
762
|
// Switched to heating mode and external heating external code is being used
|
|
834
763
|
// Start heating via heating external code OR adjust heating target temperature due to change
|
|
835
|
-
|
|
836
|
-
this.externalHeat.heat(deviceData.deviceData.target_temperature_low);
|
|
837
|
-
}
|
|
764
|
+
this.externalHeat.heat(deviceData.target_temperature_low);
|
|
838
765
|
}
|
|
839
766
|
this.thermostatService.updateCharacteristic(
|
|
840
767
|
this.hap.Characteristic.CurrentHeatingCoolingState,
|
|
@@ -843,22 +770,18 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
843
770
|
historyEntry.status = 2; // heating
|
|
844
771
|
}
|
|
845
772
|
if (deviceData.hvac_state.toUpperCase() === 'COOLING') {
|
|
846
|
-
if (this.deviceData.hvac_state.toUpperCase() === 'HEATING' && this.externalHeat
|
|
773
|
+
if (this.deviceData.hvac_state.toUpperCase() === 'HEATING' && typeof this.externalHeat?.off === 'function') {
|
|
847
774
|
// Switched to cooling mode and external heating external code was being used, so stop heating via heating external code
|
|
848
|
-
|
|
849
|
-
this.externalHeat.off();
|
|
850
|
-
}
|
|
775
|
+
this.externalHeat.off();
|
|
851
776
|
}
|
|
852
777
|
if (
|
|
853
778
|
(this.deviceData.hvac_state.toUpperCase() !== 'COOLING' ||
|
|
854
779
|
deviceData.target_temperature_high !== this.deviceData.target_temperature_high) &&
|
|
855
|
-
this.externalCool
|
|
780
|
+
typeof this.externalCool?.cool === 'function'
|
|
856
781
|
) {
|
|
857
782
|
// Switched to cooling mode and external cooling external code is being used
|
|
858
783
|
// Start cooling via cooling external code OR adjust cooling target temperature due to change
|
|
859
|
-
|
|
860
|
-
this.externalCool.cool(deviceData.target_temperature_high);
|
|
861
|
-
}
|
|
784
|
+
this.externalCool.cool(deviceData.target_temperature_high);
|
|
862
785
|
}
|
|
863
786
|
this.thermostatService.updateCharacteristic(
|
|
864
787
|
this.hap.Characteristic.CurrentHeatingCoolingState,
|
|
@@ -867,17 +790,13 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
867
790
|
historyEntry.status = 3; // cooling
|
|
868
791
|
}
|
|
869
792
|
if (deviceData.hvac_state.toUpperCase() === 'OFF') {
|
|
870
|
-
if (this.deviceData.hvac_state.toUpperCase() === 'COOLING' && this.externalCool
|
|
871
|
-
// Switched to off mode and external cooling external code was being used, so stop cooling via cooling external code
|
|
872
|
-
|
|
873
|
-
this.externalCool.off();
|
|
874
|
-
}
|
|
793
|
+
if (this.deviceData.hvac_state.toUpperCase() === 'COOLING' && typeof this.externalCool?.off === 'function') {
|
|
794
|
+
// Switched to off mode and external cooling external code was being used, so stop cooling via cooling external code{
|
|
795
|
+
this.externalCool.off();
|
|
875
796
|
}
|
|
876
|
-
if (this.deviceData.hvac_state.toUpperCase() === 'HEATING' && this.externalHeat
|
|
797
|
+
if (this.deviceData.hvac_state.toUpperCase() === 'HEATING' && typeof this.externalHeat?.off === 'function') {
|
|
877
798
|
// Switched to off mode and external heating external code was being used, so stop heating via heating external code
|
|
878
|
-
|
|
879
|
-
this.externalHeat.off();
|
|
880
|
-
}
|
|
799
|
+
this.externalHeat.off();
|
|
881
800
|
}
|
|
882
801
|
this.thermostatService.updateCharacteristic(
|
|
883
802
|
this.hap.Characteristic.CurrentHeatingCoolingState,
|
|
@@ -885,18 +804,16 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
885
804
|
);
|
|
886
805
|
historyEntry.status = 0; // off
|
|
887
806
|
}
|
|
807
|
+
|
|
888
808
|
if (this.fanService !== undefined) {
|
|
889
|
-
|
|
809
|
+
// fan status on or off
|
|
810
|
+
if (this.deviceData.fan_state === false && deviceData.fan_state === true && typeof this.externalFan?.fan === 'function') {
|
|
890
811
|
// Fan mode was switched on and external fan external code is being used, so start fan via fan external code
|
|
891
|
-
|
|
892
|
-
this.externalFan.fan(0); // Fan speed will be auto
|
|
893
|
-
}
|
|
812
|
+
this.externalFan.fan(0); // Fan speed will be auto
|
|
894
813
|
}
|
|
895
|
-
if (this.deviceData.fan_state === true && deviceData.fan_state === false && this.externalFan
|
|
814
|
+
if (this.deviceData.fan_state === true && deviceData.fan_state === false && typeof this.externalFan?.off === 'function') {
|
|
896
815
|
// Fan mode was switched off and external fan external code was being used, so stop fan via fan external code
|
|
897
|
-
|
|
898
|
-
this.externalFan.off();
|
|
899
|
-
}
|
|
816
|
+
this.externalFan.off();
|
|
900
817
|
}
|
|
901
818
|
|
|
902
819
|
this.fanService.updateCharacteristic(
|
|
@@ -907,40 +824,43 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
907
824
|
this.fanService.updateCharacteristic(
|
|
908
825
|
this.hap.Characteristic.Active,
|
|
909
826
|
deviceData.fan_state === true ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE,
|
|
910
|
-
);
|
|
827
|
+
);
|
|
911
828
|
historyEntry.status = 1; // fan
|
|
912
829
|
}
|
|
830
|
+
|
|
913
831
|
if (this.dehumidifierService !== undefined) {
|
|
832
|
+
// dehumidifier status on or off
|
|
914
833
|
if (
|
|
915
834
|
this.deviceData.dehumidifier_state === false &&
|
|
916
835
|
deviceData.dehumidifier_state === true &&
|
|
917
|
-
this.externalDehumidifier
|
|
836
|
+
typeof this.externalDehumidifier?.dehumidifier === 'function'
|
|
918
837
|
) {
|
|
919
838
|
// Dehumidifier mode was switched on and external dehumidifier external code is being used
|
|
920
839
|
// Start dehumidifier via dehumidifier external code
|
|
921
|
-
|
|
922
|
-
this.externalDehumidifier.dehumififier(0);
|
|
923
|
-
}
|
|
840
|
+
this.externalDehumidifier.dehumidifier(0);
|
|
924
841
|
}
|
|
925
842
|
if (
|
|
926
843
|
this.deviceData.dehumidifier_state === true &&
|
|
927
844
|
deviceData.dehumidifier_state === false &&
|
|
928
|
-
this.externalDehumidifier
|
|
845
|
+
typeof this.externalDehumidifier?.off === 'function'
|
|
929
846
|
) {
|
|
930
847
|
// Dehumidifier mode was switched off and external dehumidifier external code was being used
|
|
931
848
|
// Stop dehumidifier via dehumidifier external code
|
|
932
|
-
|
|
933
|
-
this.externalDehumidifier.off();
|
|
934
|
-
}
|
|
849
|
+
this.externalDehumidifier.off();
|
|
935
850
|
}
|
|
936
851
|
|
|
937
852
|
this.dehumidifierService.updateCharacteristic(
|
|
938
853
|
this.hap.Characteristic.Active,
|
|
939
854
|
deviceData.dehumidifier_state === true ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE,
|
|
940
|
-
);
|
|
855
|
+
);
|
|
941
856
|
historyEntry.status = 4; // dehumidifier
|
|
942
857
|
}
|
|
943
858
|
|
|
859
|
+
if (this.switchService !== undefined) {
|
|
860
|
+
// Hotwater boost status on or off
|
|
861
|
+
this.switchService.updateCharacteristic(this.hap.Characteristic.On, deviceData.hot_water_boost_active === true);
|
|
862
|
+
}
|
|
863
|
+
|
|
944
864
|
// Log thermostat metrics to history only if changed to previous recording
|
|
945
865
|
if (this.thermostatService !== undefined && typeof this.historyService?.addHistory === 'function') {
|
|
946
866
|
let tempEntry = this.historyService.lastHistory(this.thermostatService);
|
|
@@ -1056,4 +976,127 @@ export default class NestThermostat extends HomeKitDevice {
|
|
|
1056
976
|
//});
|
|
1057
977
|
}
|
|
1058
978
|
}
|
|
979
|
+
|
|
980
|
+
#setupFan() {
|
|
981
|
+
this.fanService = this.addHKService(this.hap.Service.Fanv2, '', 1);
|
|
982
|
+
this.addHKCharacteristic(this.hap.Service.Fanv2, this.hap.Characteristic.RotationSpeed);
|
|
983
|
+
this.thermostatService.addLinkedService(this.fanService);
|
|
984
|
+
|
|
985
|
+
this.addHKCharacteristic(this.thermostatService, this.hap.Characteristic.Active, {
|
|
986
|
+
onSet: (value) =>
|
|
987
|
+
this.setFan(
|
|
988
|
+
value,
|
|
989
|
+
value === this.hap.Characteristic.Active.ACTIVE ? (this.deviceData.fan_timer_speed / this.deviceData.fan_max_speed) * 100 : 0,
|
|
990
|
+
),
|
|
991
|
+
onGet: () => {
|
|
992
|
+
return this.deviceData.fan_state === true ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE;
|
|
993
|
+
},
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
this.addHKCharacteristic(this.thermostatService, this.hap.Characteristic.RotationSpeed, {
|
|
997
|
+
props: { minStep: 100 / this.deviceData.fan_max_speed },
|
|
998
|
+
onSet: (value) => this.setFan(value !== 0 ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE, value),
|
|
999
|
+
onGet: () => {
|
|
1000
|
+
return (this.deviceData.fan_timer_speed / this.deviceData.fan_max_speed) * 100;
|
|
1001
|
+
},
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
#setupDehumidifier() {
|
|
1006
|
+
this.dehumidifierService = this.addHKService(this.hap.Service.HumidifierDehumidifier, '', 1);
|
|
1007
|
+
this.thermostatService.addLinkedService(this.dehumidifierService);
|
|
1008
|
+
|
|
1009
|
+
this.addHKCharacteristic(this.dehumidifierService, this.hap.Characteristic.TargetHumidifierDehumidifierState, {
|
|
1010
|
+
props: { validValues: [this.hap.Characteristic.TargetHumidifierDehumidifierState.DEHUMIDIFIER] },
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
this.addHKCharacteristic(this.dehumidifierService, this.hap.Characteristic.Active, {
|
|
1014
|
+
onSet: (value) => this.setDehumidifier(value),
|
|
1015
|
+
onGet: () => {
|
|
1016
|
+
return this.deviceData.dehumidifier_state === true
|
|
1017
|
+
? this.hap.Characteristic.Active.ACTIVE
|
|
1018
|
+
: this.hap.Characteristic.Active.INACTIVE;
|
|
1019
|
+
},
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
#setupHotwaterBoost() {
|
|
1024
|
+
this.switchService = this.addHKService(this.hap.Service.Switch, '', 1);
|
|
1025
|
+
this.thermostatService.addLinkedService(this.switchService);
|
|
1026
|
+
|
|
1027
|
+
this.addHKCharacteristic(this.switchService, this.hap.Characteristic.On, {
|
|
1028
|
+
onSet: (value) => this.setHotwaterBoost(value),
|
|
1029
|
+
onGet: () => {
|
|
1030
|
+
return this.deviceData?.hot_water_boost_active === true;
|
|
1031
|
+
},
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
async #loadExternalModule(module, expectedFunctions = []) {
|
|
1036
|
+
if (typeof module !== 'string' || module === '' || Array.isArray(expectedFunctions) === false) {
|
|
1037
|
+
return undefined;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Helper to resolve a module path, defaulting to plugin dir and falling back from .mjs to .js
|
|
1041
|
+
const resolveModulePath = async (basePath) => {
|
|
1042
|
+
let hasExtension = path.extname(basePath) !== '';
|
|
1043
|
+
let isRelative = basePath.startsWith('./') || basePath.startsWith('../');
|
|
1044
|
+
let isAbsolute = path.isAbsolute(basePath);
|
|
1045
|
+
let resolvedBase = isAbsolute ? basePath : isRelative ? path.resolve(basePath) : path.resolve(__dirname, basePath);
|
|
1046
|
+
let finalPath = resolvedBase;
|
|
1047
|
+
|
|
1048
|
+
if (hasExtension === false) {
|
|
1049
|
+
let mjsPath = `${resolvedBase}.mjs`;
|
|
1050
|
+
let jsPath = `${resolvedBase}.js`;
|
|
1051
|
+
|
|
1052
|
+
try {
|
|
1053
|
+
await fs.access(mjsPath);
|
|
1054
|
+
finalPath = mjsPath;
|
|
1055
|
+
} catch {
|
|
1056
|
+
try {
|
|
1057
|
+
await fs.access(jsPath);
|
|
1058
|
+
finalPath = jsPath;
|
|
1059
|
+
} catch {
|
|
1060
|
+
finalPath = mjsPath; // fallback to mjs even if not found
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
return finalPath;
|
|
1065
|
+
};
|
|
1066
|
+
|
|
1067
|
+
let loadedModule = undefined;
|
|
1068
|
+
|
|
1069
|
+
try {
|
|
1070
|
+
let values = module.match(/'[^']*'|[^\s]+/g)?.map((v) => v.replace(/^'(.*)'$/, '$1')) || [];
|
|
1071
|
+
let script = await resolveModulePath(values[0]);
|
|
1072
|
+
let options = values.slice(1);
|
|
1073
|
+
let externalModule = await import(script);
|
|
1074
|
+
|
|
1075
|
+
if (typeof externalModule?.default === 'function') {
|
|
1076
|
+
let moduleExports = externalModule.default(this.log, options);
|
|
1077
|
+
let valid = Object.fromEntries(
|
|
1078
|
+
expectedFunctions.filter((fn) => typeof moduleExports[fn] === 'function').map((fn) => [fn, moduleExports[fn]]),
|
|
1079
|
+
);
|
|
1080
|
+
loadedModule = Object.keys(valid).length > 0 ? valid : undefined;
|
|
1081
|
+
}
|
|
1082
|
+
// eslint-disable-next-line no-unused-vars
|
|
1083
|
+
} catch (error) {
|
|
1084
|
+
let shortName =
|
|
1085
|
+
typeof module === 'string'
|
|
1086
|
+
? module
|
|
1087
|
+
.trim()
|
|
1088
|
+
.match(/'[^']*'|[^\s]+/)?.[0]
|
|
1089
|
+
?.replace(/^'(.*)'$/, '$1')
|
|
1090
|
+
: '';
|
|
1091
|
+
this?.log?.warn?.('Failed to load external module "%s" for thermostat "%s"', shortName, this.deviceData.description);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
return loadedModule;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
function formatDuration(seconds) {
|
|
1099
|
+
return `${
|
|
1100
|
+
seconds >= 3600 ? `${Math.floor(seconds / 3600)} hr${Math.floor(seconds / 3600) > 1 ? 's' : ''} ` : ''
|
|
1101
|
+
}${Math.floor((seconds % 3600) / 60)} min${Math.floor((seconds % 3600) / 60) !== 1 ? 's' : ''}`;
|
|
1059
1102
|
}
|