hoffmation-base 3.6.1 → 3.7.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.
Files changed (37) hide show
  1. package/lib/action/airQualitySensorChangeAction.d.ts +16 -0
  2. package/lib/action/airQualitySensorChangeAction.js +15 -0
  3. package/lib/action/index.d.ts +1 -0
  4. package/lib/action/index.js +3 -1
  5. package/lib/devices/sharedFunctions/airQualitySensor.d.ts +27 -0
  6. package/lib/devices/sharedFunctions/airQualitySensor.js +87 -0
  7. package/lib/devices/sharedFunctions/index.d.ts +1 -0
  8. package/lib/devices/sharedFunctions/index.js +1 -0
  9. package/lib/devices/unifi/index.d.ts +1 -0
  10. package/lib/devices/unifi/index.js +1 -0
  11. package/lib/devices/unifi/own-unifi-air-quality-sensor.d.ts +81 -0
  12. package/lib/devices/unifi/own-unifi-air-quality-sensor.js +166 -0
  13. package/lib/devices/unifi/own-unifi-camera.d.ts +6 -5
  14. package/lib/devices/unifi/own-unifi-camera.js +16 -24
  15. package/lib/devices/unifi/protect-module.d.ts +11 -0
  16. package/lib/devices/unifi/protect-module.js +16 -0
  17. package/lib/devices/unifi/unifi-logger.d.ts +5 -5
  18. package/lib/devices/unifi/unifi-logger.js +9 -8
  19. package/lib/devices/unifi/unifi-protect.d.ts +38 -7
  20. package/lib/devices/unifi/unifi-protect.js +199 -66
  21. package/lib/enums/DeviceCapability.d.ts +1 -0
  22. package/lib/enums/DeviceCapability.js +1 -0
  23. package/lib/enums/commandType.d.ts +1 -0
  24. package/lib/enums/commandType.js +1 -0
  25. package/lib/enums/deviceType.d.ts +1 -0
  26. package/lib/enums/deviceType.js +1 -0
  27. package/lib/interfaces/baseDevices/iAirQualityCollector.d.ts +23 -0
  28. package/lib/interfaces/baseDevices/iAirQualityCollector.js +2 -0
  29. package/lib/interfaces/baseDevices/iAirQualitySensor.d.ts +97 -0
  30. package/lib/interfaces/baseDevices/iAirQualitySensor.js +7 -0
  31. package/lib/interfaces/baseDevices/index.d.ts +2 -0
  32. package/lib/interfaces/baseDevices/index.js +2 -0
  33. package/lib/interfaces/iPersist.d.ts +6 -1
  34. package/lib/services/dbo/postgreSqlPersist.d.ts +3 -1
  35. package/lib/services/dbo/postgreSqlPersist.js +34 -1
  36. package/lib/tsconfig.tsbuildinfo +1 -1
  37. package/package.json +4 -3
@@ -1,22 +1,53 @@
1
+ import { OwnUnifiAirQualitySensor } from './own-unifi-air-quality-sensor';
1
2
  import { OwnUnifiCamera } from './own-unifi-camera';
2
3
  import { iDisposable, iUnifiProtectOptions } from '../../interfaces';
3
4
  export declare class UnifiProtect implements iDisposable {
4
5
  private readonly unifiLogger;
5
- private readonly _api;
6
6
  /**
7
7
  * Mapping for own devices
8
8
  */
9
9
  static readonly ownCameras: Map<string, OwnUnifiCamera>;
10
- private _deviceStates;
10
+ /**
11
+ * Mapping for own air quality sensors
12
+ */
13
+ static readonly ownAirQualitySensors: Map<string, OwnUnifiAirQualitySensor>;
14
+ private _client;
15
+ private _connecting;
16
+ private _disposed;
17
+ private _eventAbort;
11
18
  private _idMap;
12
- private _eventMap;
13
- private _lastUpdate;
19
+ private _ignoredIds;
20
+ private _sensorIdMap;
21
+ private _ignoredSensorIds;
14
22
  constructor(settings: iUnifiProtectOptions);
15
- private reconnect;
23
+ /**
24
+ * Connects to the NVR. A single atomic operation which logs in, bootstraps and opens the realtime channel.
25
+ * @param settings - The address and credentials of the NVR
26
+ */
27
+ private connect;
28
+ /**
29
+ * Renders a typed `ProtectError` including its errno code and the whole cause chain,
30
+ * as the message alone rarely names the actual failure.
31
+ * @param error - The rejected value
32
+ * @returns A single line description
33
+ */
34
+ private static describeError;
16
35
  dispose(): void;
17
36
  static addDevice(camera: OwnUnifiCamera): void;
37
+ /**
38
+ * Registers an air quality sensor to be fed with the readings of its Unifi counterpart.
39
+ * @param sensor - The device to register
40
+ */
41
+ static addAirQualitySensor(sensor: OwnUnifiAirQualitySensor): void;
18
42
  private initialize;
19
- private onMessage;
20
- private rememberAddEvent;
43
+ /**
44
+ * Consumes the realtime firehose until the client is disposed or the stream dies.
45
+ * @param client - The connected client whose event stream is consumed
46
+ */
47
+ private consumeEvents;
48
+ private onEvent;
49
+ private forwardToCamera;
50
+ private ownSensorFor;
51
+ private initializeSensor;
21
52
  private initializeCamera;
22
53
  }
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.UnifiProtect = void 0;
4
- const unifi_protect_1 = require("unifi-protect");
4
+ const protect_module_1 = require("./protect-module");
5
5
  const enums_1 = require("../../enums");
6
6
  const logging_1 = require("../../logging");
7
7
  const utils_1 = require("../../utils");
@@ -9,105 +9,234 @@ const unifi_logger_1 = require("./unifi-logger");
9
9
  class UnifiProtect {
10
10
  constructor(settings) {
11
11
  this.unifiLogger = new unifi_logger_1.UnifiLogger(enums_1.LogSource.UnifiProtect);
12
- this._deviceStates = new Map();
12
+ this._client = null;
13
+ this._connecting = false;
14
+ this._disposed = false;
15
+ this._eventAbort = null;
13
16
  this._idMap = new Map();
14
- this._eventMap = new Map();
15
- this._lastUpdate = new Date(0);
16
- this._api = new unifi_protect_1.ProtectApi(this.unifiLogger);
17
- this.reconnect(settings);
17
+ this._ignoredIds = new Set();
18
+ this._sensorIdMap = new Map();
19
+ this._ignoredSensorIds = new Set();
20
+ this.connect(settings);
18
21
  utils_1.Utils.guardedInterval(() => {
19
- if (new Date().getTime() - this._lastUpdate.getTime() < 180 * 1000) {
20
- // We had an update within the last 3 minutes --> no need to reconnect
22
+ if (this._disposed || this._connecting || this._client !== null) {
23
+ // Either shut down, already connecting or the client is up and recovers outages on its own.
21
24
  return;
22
25
  }
23
- this.reconnect(settings);
26
+ this.connect(settings);
24
27
  }, 5 * 60 * 1000);
25
28
  }
26
- reconnect(settings) {
27
- this._eventMap = new Map();
28
- this._api
29
- .login(settings.nvrAddress, settings.username, settings.password)
30
- .then((_loggedIn) => {
31
- this.initialize();
29
+ /**
30
+ * Connects to the NVR. A single atomic operation which logs in, bootstraps and opens the realtime channel.
31
+ * @param settings - The address and credentials of the NVR
32
+ */
33
+ connect(settings) {
34
+ this._connecting = true;
35
+ this._idMap = new Map();
36
+ this._ignoredIds = new Set();
37
+ this._sensorIdMap = new Map();
38
+ this._ignoredSensorIds = new Set();
39
+ (0, protect_module_1.loadProtectModule)()
40
+ .then((protect) => protect.ProtectClient.connect({
41
+ host: settings.nvrAddress,
42
+ username: settings.username,
43
+ password: settings.password,
44
+ log: this.unifiLogger,
45
+ }))
46
+ .then((client) => {
47
+ this._connecting = false;
48
+ if (this._disposed) {
49
+ void client[Symbol.asyncDispose]();
50
+ return;
51
+ }
52
+ this._client = client;
53
+ this.initialize(client);
32
54
  })
33
55
  .catch((error) => {
34
- logging_1.ServerLogService.writeLog(enums_1.LogLevel.Error, `Unifi-Protect: Login failed: ${error}`);
56
+ this._connecting = false;
57
+ logging_1.ServerLogService.writeLog(enums_1.LogLevel.Error, `Unifi-Protect: Login failed: ${UnifiProtect.describeError(error)}`);
35
58
  });
36
59
  }
60
+ /**
61
+ * Renders a typed `ProtectError` including its errno code and the whole cause chain,
62
+ * as the message alone rarely names the actual failure.
63
+ * @param error - The rejected value
64
+ * @returns A single line description
65
+ */
66
+ static describeError(error) {
67
+ if (!(error instanceof Error)) {
68
+ return `${error}`;
69
+ }
70
+ const parts = [`${error.name}: ${error.message}`];
71
+ const code = Reflect.get(error, 'code');
72
+ if (typeof code === 'string') {
73
+ parts.push(`code: ${code}`);
74
+ }
75
+ let cause = error.cause;
76
+ while (cause instanceof Error) {
77
+ parts.push(`caused by ${cause.name}: ${cause.message}`);
78
+ cause = cause.cause;
79
+ }
80
+ return parts.join(' | ');
81
+ }
37
82
  dispose() {
38
- this._api.logout();
83
+ var _a;
84
+ this._disposed = true;
85
+ (_a = this._eventAbort) === null || _a === void 0 ? void 0 : _a.abort();
86
+ this._eventAbort = null;
87
+ const client = this._client;
88
+ this._client = null;
89
+ client === null || client === void 0 ? void 0 : client[Symbol.asyncDispose]().catch((error) => {
90
+ logging_1.ServerLogService.writeLog(enums_1.LogLevel.Error, `Unifi-Protect: Disposal failed: ${error}`);
91
+ });
39
92
  }
40
93
  static addDevice(camera) {
41
94
  this.ownCameras.set(camera.unifiCameraName, camera);
42
95
  }
43
- async initialize() {
44
- this.unifiLogger.info('Unifi-Protect: Login successful');
45
- const bootstrap = await this._api.getBootstrap();
46
- if (!bootstrap || !this._api.bootstrap) {
47
- this.unifiLogger.error('Unifi-Protect: Bootstrap failed');
48
- return;
49
- }
50
- const info = this._api.bootstrap;
51
- for (const camera of info.cameras) {
96
+ /**
97
+ * Registers an air quality sensor to be fed with the readings of its Unifi counterpart.
98
+ * @param sensor - The device to register
99
+ */
100
+ static addAirQualitySensor(sensor) {
101
+ this.ownAirQualitySensors.set(sensor.unifiSensorName, sensor);
102
+ }
103
+ initialize(client) {
104
+ this.unifiLogger.info(`Unifi-Protect: Connected to "${client.controllerName}"`);
105
+ for (const camera of client.cameras) {
52
106
  this.initializeCamera(camera);
53
107
  }
54
- this._api.on('message', this.onMessage.bind(this));
108
+ for (const sensor of client.sensors) {
109
+ this.initializeSensor(sensor);
110
+ }
111
+ void this.consumeEvents(client);
112
+ }
113
+ /**
114
+ * Consumes the realtime firehose until the client is disposed or the stream dies.
115
+ * @param client - The connected client whose event stream is consumed
116
+ */
117
+ async consumeEvents(client) {
118
+ const abort = new AbortController();
119
+ this._eventAbort = abort;
120
+ try {
121
+ for await (const event of client.events({ signal: abort.signal })) {
122
+ this.onEvent(client, event);
123
+ }
124
+ }
125
+ catch (error) {
126
+ if (!abort.signal.aborted) {
127
+ logging_1.ServerLogService.writeLog(enums_1.LogLevel.Error, `Unifi-Protect: Event stream failed: ${error}`);
128
+ }
129
+ }
130
+ if (this._client === client) {
131
+ // The stream is the client's lifeline --> drop it, so the interval reconnects.
132
+ this._client = null;
133
+ void client[Symbol.asyncDispose]();
134
+ }
55
135
  }
56
- onMessage(packet) {
57
- var _a, _b, _c, _d;
58
- const payload = packet.payload;
59
- this._lastUpdate = new Date();
60
- switch (packet.header.modelKey) {
61
- case 'nvr':
136
+ onEvent(client, event) {
137
+ switch (event.kind) {
138
+ case 'bootstrapLoaded':
139
+ for (const camera of client.cameras) {
140
+ this.initializeCamera(camera);
141
+ }
142
+ for (const sensor of client.sensors) {
143
+ this.initializeSensor(sensor);
144
+ }
62
145
  break;
63
- default:
64
- // Lookup the device.
65
- let id = packet.header.id;
66
- let baseEvent;
67
- if (packet.header.action === 'add') {
68
- const addEvent = packet.payload;
69
- id = (_a = addEvent.camera) !== null && _a !== void 0 ? _a : addEvent.cameraId;
70
- this.rememberAddEvent(packet, addEvent);
146
+ case 'deviceAdded': {
147
+ if (event.modelKey === 'camera') {
148
+ const camera = client.camera(event.id);
149
+ if (camera !== undefined) {
150
+ this.initializeCamera(camera);
151
+ }
71
152
  }
72
- else if (packet.header.action === 'update' && this._eventMap.has(id)) {
73
- baseEvent = this._eventMap.get(id);
74
- id = (_c = (_b = baseEvent === null || baseEvent === void 0 ? void 0 : baseEvent.camera) !== null && _b !== void 0 ? _b : baseEvent === null || baseEvent === void 0 ? void 0 : baseEvent.cameraId) !== null && _c !== void 0 ? _c : '';
153
+ else if (event.modelKey === 'sensor') {
154
+ const sensor = client.sensor(event.id);
155
+ if (sensor !== undefined) {
156
+ this.initializeSensor(sensor);
157
+ }
75
158
  }
76
- const ownName = this._idMap.get(id);
77
- if (!ownName) {
159
+ break;
160
+ }
161
+ case 'devicePatched': {
162
+ if (event.modelKey !== 'sensor') {
78
163
  break;
79
164
  }
80
- const ownCamera = UnifiProtect.ownCameras.get(ownName);
81
- if (ownCamera !== undefined) {
165
+ // The patch carries the full airQuality block, but the projection is authoritative and always complete.
166
+ const ownSensor = this.ownSensorFor(event.id);
167
+ const sensor = client.sensor(event.id);
168
+ if (ownSensor !== undefined && sensor !== undefined) {
82
169
  utils_1.Utils.guardedFunction(() => {
83
- ownCamera.update(packet, baseEvent);
170
+ ownSensor.update(sensor.config);
84
171
  }, this);
85
- break;
86
172
  }
87
173
  break;
174
+ }
175
+ case 'deviceRemoved':
176
+ this._idMap.delete(event.id);
177
+ this._ignoredIds.delete(event.id);
178
+ this._sensorIdMap.delete(event.id);
179
+ this._ignoredSensorIds.delete(event.id);
180
+ break;
181
+ case 'motionDetected':
182
+ case 'smartDetect':
183
+ this.forwardToCamera(event.cameraId, event);
184
+ break;
88
185
  }
89
- // Update the internal list we maintain.
90
- if (packet.header.action === 'update') {
91
- this._deviceStates.set(packet.header.id, Object.assign((_d = this._deviceStates.get(packet.header.id)) !== null && _d !== void 0 ? _d : {}, payload));
186
+ }
187
+ forwardToCamera(cameraId, event) {
188
+ const ownName = this._idMap.get(cameraId);
189
+ if (!ownName) {
190
+ return;
191
+ }
192
+ const ownCamera = UnifiProtect.ownCameras.get(ownName);
193
+ if (ownCamera === undefined) {
194
+ return;
92
195
  }
196
+ utils_1.Utils.guardedFunction(() => {
197
+ ownCamera.update(event);
198
+ }, this);
93
199
  }
94
- rememberAddEvent(packet, addEvent) {
95
- if (packet.header.modelKey === 'event') {
96
- this._eventMap.set(addEvent.id, addEvent);
97
- utils_1.Utils.guardedTimeout(() => {
98
- this._eventMap.delete(addEvent.id);
99
- }, 5 * 60 * 1000, this);
200
+ ownSensorFor(sensorId) {
201
+ const ownName = this._sensorIdMap.get(sensorId);
202
+ if (!ownName) {
203
+ return undefined;
100
204
  }
205
+ return UnifiProtect.ownAirQualitySensors.get(ownName);
101
206
  }
102
- initializeCamera(data) {
103
- if (!data.name || !UnifiProtect.ownCameras.has(data.name)) {
104
- logging_1.ServerLogService.writeLog(enums_1.LogLevel.Info, `Unifi-Protect: Ignoring camera ${data.name}`);
207
+ initializeSensor(sensor) {
208
+ if (this._sensorIdMap.has(sensor.id) || this._ignoredSensorIds.has(sensor.id)) {
209
+ // Already known --> the projection is live, so there is nothing to refresh.
105
210
  return;
106
211
  }
107
- const camera = UnifiProtect.ownCameras.get(data.name);
108
- camera.initialize(data);
109
- logging_1.ServerLogService.writeLog(enums_1.LogLevel.Info, `Unifi-Protect: Camera ${data.name} (re)initialized`);
110
- this._idMap.set(data.id, data.name);
212
+ const name = sensor.name;
213
+ if (!name || !UnifiProtect.ownAirQualitySensors.has(name)) {
214
+ this._ignoredSensorIds.add(sensor.id);
215
+ logging_1.ServerLogService.writeLog(enums_1.LogLevel.Info, `Unifi-Protect: Ignoring sensor ${name}`);
216
+ return;
217
+ }
218
+ const ownSensor = UnifiProtect.ownAirQualitySensors.get(name);
219
+ this._sensorIdMap.set(sensor.id, name);
220
+ utils_1.Utils.guardedFunction(() => {
221
+ ownSensor.initialize(sensor);
222
+ }, this);
223
+ logging_1.ServerLogService.writeLog(enums_1.LogLevel.Info, `Unifi-Protect: Sensor ${name} (re)initialized`);
224
+ }
225
+ initializeCamera(camera) {
226
+ if (this._idMap.has(camera.id) || this._ignoredIds.has(camera.id)) {
227
+ // Already known --> the projection is live, so there is nothing to refresh.
228
+ return;
229
+ }
230
+ const name = camera.name;
231
+ if (!name || !UnifiProtect.ownCameras.has(name)) {
232
+ this._ignoredIds.add(camera.id);
233
+ logging_1.ServerLogService.writeLog(enums_1.LogLevel.Info, `Unifi-Protect: Ignoring camera ${name}`);
234
+ return;
235
+ }
236
+ const ownCamera = UnifiProtect.ownCameras.get(name);
237
+ ownCamera.initialize(camera);
238
+ logging_1.ServerLogService.writeLog(enums_1.LogLevel.Info, `Unifi-Protect: Camera ${name} (re)initialized`);
239
+ this._idMap.set(camera.id, name);
111
240
  }
112
241
  }
113
242
  exports.UnifiProtect = UnifiProtect;
@@ -115,3 +244,7 @@ exports.UnifiProtect = UnifiProtect;
115
244
  * Mapping for own devices
116
245
  */
117
246
  UnifiProtect.ownCameras = new Map();
247
+ /**
248
+ * Mapping for own air quality sensors
249
+ */
250
+ UnifiProtect.ownAirQualitySensors = new Map();
@@ -22,6 +22,7 @@ export declare enum DeviceCapability {
22
22
  loadMetering = 20,
23
23
  garageDoorOpener = 21,
24
24
  magnetSensor = 22,
25
+ airQualitySensor = 23,
25
26
  bluetoothDetector = 101,
26
27
  trackableDevice = 102,
27
28
  scene = 103,
@@ -26,6 +26,7 @@ var DeviceCapability;
26
26
  DeviceCapability[DeviceCapability["loadMetering"] = 20] = "loadMetering";
27
27
  DeviceCapability[DeviceCapability["garageDoorOpener"] = 21] = "garageDoorOpener";
28
28
  DeviceCapability[DeviceCapability["magnetSensor"] = 22] = "magnetSensor";
29
+ DeviceCapability[DeviceCapability["airQualitySensor"] = 23] = "airQualitySensor";
29
30
  DeviceCapability[DeviceCapability["bluetoothDetector"] = 101] = "bluetoothDetector";
30
31
  DeviceCapability[DeviceCapability["trackableDevice"] = 102] = "trackableDevice";
31
32
  DeviceCapability[DeviceCapability["scene"] = 103] = "scene";
@@ -14,6 +14,7 @@ export declare enum CommandType {
14
14
  ActuatorWriteStateToDeviceCommand = "ActuatorWriteStateToDeviceCommand",
15
15
  DimmerSetLightCommand = "DimmerSetLightCommand",
16
16
  FloorSetAllShuttersCommand = "FloorSetAllShuttersCommand",
17
+ AirQualitySensorChangeAction = "AirQualitySensorChangeAction",
17
18
  HumiditySensorChangeAction = "HumiditySensorChangeAction",
18
19
  LampSetLightCommand = "LampSetLightCommand",
19
20
  LampSetTimeBasedCommand = "LampSetTimeBasedCommand",
@@ -18,6 +18,7 @@ var CommandType;
18
18
  CommandType["ActuatorWriteStateToDeviceCommand"] = "ActuatorWriteStateToDeviceCommand";
19
19
  CommandType["DimmerSetLightCommand"] = "DimmerSetLightCommand";
20
20
  CommandType["FloorSetAllShuttersCommand"] = "FloorSetAllShuttersCommand";
21
+ CommandType["AirQualitySensorChangeAction"] = "AirQualitySensorChangeAction";
21
22
  CommandType["HumiditySensorChangeAction"] = "HumiditySensorChangeAction";
22
23
  CommandType["LampSetLightCommand"] = "LampSetLightCommand";
23
24
  CommandType["LampSetTimeBasedCommand"] = "LampSetTimeBasedCommand";
@@ -58,6 +58,7 @@ export declare enum DeviceType {
58
58
  TrackableDevice = 4002,
59
59
  Camera = 6001,
60
60
  Door = 6002,
61
+ UnifiAirQualitySensor = 6003,
61
62
  Victron = 7001,
62
63
  Dachs = 7002,
63
64
  DachsWarmWaterTemperature = 7003
@@ -62,6 +62,7 @@ var DeviceType;
62
62
  DeviceType[DeviceType["TrackableDevice"] = 4002] = "TrackableDevice";
63
63
  DeviceType[DeviceType["Camera"] = 6001] = "Camera";
64
64
  DeviceType[DeviceType["Door"] = 6002] = "Door";
65
+ DeviceType[DeviceType["UnifiAirQualitySensor"] = 6003] = "UnifiAirQualitySensor";
65
66
  DeviceType[DeviceType["Victron"] = 7001] = "Victron";
66
67
  DeviceType[DeviceType["Dachs"] = 7002] = "Dachs";
67
68
  DeviceType[DeviceType["DachsWarmWaterTemperature"] = 7003] = "DachsWarmWaterTemperature";
@@ -0,0 +1,23 @@
1
+ import { iRoomDevice } from './iRoomDevice';
2
+ import { AirQualitySensorChangeAction } from '../../action';
3
+ import { iAirQualityReadings, iAirQualitySensor } from './iAirQualitySensor';
4
+ /**
5
+ * This interface represents a device measuring air quality.
6
+ *
7
+ * For devices with {@link DeviceCapability.airQualitySensor} capability.
8
+ */
9
+ export interface iAirQualityCollector extends iRoomDevice {
10
+ /**
11
+ * Service which handles common aspects of the air quality sensor like persisting
12
+ */
13
+ readonly airQualitySensor: iAirQualitySensor;
14
+ /**
15
+ * The most recent air quality readings of this device
16
+ */
17
+ readonly airQuality: iAirQualityReadings;
18
+ /**
19
+ * Add a callback that is called when any air quality reading changes
20
+ * @param pCallback - The callback to fire
21
+ */
22
+ addAirQualityCallback(pCallback: (action: AirQualitySensorChangeAction) => void): void;
23
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,97 @@
1
+ import { iJsonOmitKeys } from '../iJsonOmitKeys';
2
+ import { AirQualitySensorChangeAction } from '../../action';
3
+ /**
4
+ * The value reported for a metric the sensor does not measure or has not reported yet.
5
+ */
6
+ export declare const UNDEFINED_AIR_QUALITY_VALUE = -1;
7
+ /**
8
+ * The set of air quality metrics a sensor can report.
9
+ *
10
+ * Devices differ widely in what they measure, so every member is always present but defaults to
11
+ * {@link UNDEFINED_AIR_QUALITY_VALUE} until the device reports it. This mirrors the sentinel approach of
12
+ * {@link UNDEFINED_TEMP_VALUE} and spares consumers a null check on every metric; use
13
+ * {@link iAirQualitySensor.reports} to tell "not measured" apart from a measured zero.
14
+ */
15
+ export interface iAirQualityReadings {
16
+ /**
17
+ * The air quality index as calculated by the device (lower is better)
18
+ */
19
+ aqi: number;
20
+ /**
21
+ * The carbon dioxide concentration in ppm
22
+ */
23
+ co2: number;
24
+ /**
25
+ * The nitrogen oxide index (1 equals background level)
26
+ */
27
+ nox: number;
28
+ /**
29
+ * Particulate matter up to 1.0 µm in µg/m³
30
+ */
31
+ pm1p0: number;
32
+ /**
33
+ * Particulate matter up to 2.5 µm in µg/m³
34
+ */
35
+ pm2p5: number;
36
+ /**
37
+ * Particulate matter up to 4.0 µm in µg/m³
38
+ */
39
+ pm4p0: number;
40
+ /**
41
+ * Particulate matter up to 10.0 µm in µg/m³
42
+ */
43
+ pm10p0: number;
44
+ /**
45
+ * The total volatile organic compounds in ppb
46
+ */
47
+ tvoc: number;
48
+ /**
49
+ * The vape detection reading of the device
50
+ */
51
+ vape: number;
52
+ /**
53
+ * The volatile organic compounds index (100 equals background level)
54
+ */
55
+ voc: number;
56
+ }
57
+ /**
58
+ * Common handling for a device measuring air quality.
59
+ */
60
+ export interface iAirQualitySensor extends iJsonOmitKeys {
61
+ /**
62
+ * The most recent readings of this sensor
63
+ */
64
+ readonly readings: iAirQualityReadings;
65
+ /**
66
+ * The timestamp in ms of the last received reading
67
+ */
68
+ lastSeen: number;
69
+ /**
70
+ * Applies new readings, firing the change callbacks if at least one value actually changed.
71
+ * @param readings - The metrics reported by the device, omitting the ones it does not measure
72
+ */
73
+ update(readings: Partial<iAirQualityReadings>): void;
74
+ /**
75
+ * Whether the device actually measures the given metric.
76
+ * @param metric - The metric to check
77
+ * @returns True if the device has reported a value for it
78
+ */
79
+ reports(metric: keyof iAirQualityReadings): boolean;
80
+ /**
81
+ * Persists the current air quality information to the database
82
+ */
83
+ persist(): void;
84
+ /**
85
+ * Adds a callback to be called when any of the readings change
86
+ * @param pCallback - The callback to be called
87
+ */
88
+ addAirQualityCallback(pCallback: (action: AirQualitySensorChangeAction) => void): void;
89
+ /**
90
+ * Frees up the resources of this sensor
91
+ */
92
+ dispose(): void;
93
+ /**
94
+ * @returns The serializable representation of this sensor
95
+ */
96
+ toJSON(): Partial<iAirQualitySensor>;
97
+ }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UNDEFINED_AIR_QUALITY_VALUE = void 0;
4
+ /**
5
+ * The value reported for a metric the sensor does not measure or has not reported yet.
6
+ */
7
+ exports.UNDEFINED_AIR_QUALITY_VALUE = -1;
@@ -12,6 +12,8 @@ export * from './iExcessEnergyConsumer';
12
12
  export * from './iGarageDoorOpener';
13
13
  export * from './iHandle';
14
14
  export * from './iHeater';
15
+ export * from './iAirQualityCollector';
16
+ export * from './iAirQualitySensor';
15
17
  export * from './iHumidityCollector';
16
18
  export * from './iIlluminationSensor';
17
19
  export * from './iLamp';
@@ -27,6 +27,8 @@ __exportStar(require("./iExcessEnergyConsumer"), exports);
27
27
  __exportStar(require("./iGarageDoorOpener"), exports);
28
28
  __exportStar(require("./iHandle"), exports);
29
29
  __exportStar(require("./iHeater"), exports);
30
+ __exportStar(require("./iAirQualityCollector"), exports);
31
+ __exportStar(require("./iAirQualitySensor"), exports);
30
32
  __exportStar(require("./iHumidityCollector"), exports);
31
33
  __exportStar(require("./iIlluminationSensor"), exports);
32
34
  __exportStar(require("./iLamp"), exports);
@@ -1,4 +1,4 @@
1
- import { iAcDevice, iActuator, iBaseDevice, iBatteryDevice, iButtonSwitch, iHandle, iHeater, iHumidityCollector, iIlluminationSensor, iMotionSensor, iShutter, iTemperatureCollector, iZigbeeDevice } from './baseDevices';
1
+ import { iAcDevice, iActuator, iAirQualityCollector, iBaseDevice, iBatteryDevice, iButtonSwitch, iHandle, iHeater, iHumidityCollector, iIlluminationSensor, iMotionSensor, iShutter, iTemperatureCollector, iZigbeeDevice } from './baseDevices';
2
2
  import { iTemperatureMeasurement } from './iTemperatureMeasurement';
3
3
  import { iRoomBase } from './iRoomBase';
4
4
  import { ButtonPressType } from '../enums';
@@ -113,6 +113,11 @@ export interface iPersist {
113
113
  * @param device - The device to persist data for
114
114
  */
115
115
  persistHumiditySensor(device: iHumidityCollector): void;
116
+ /**
117
+ * Persists data of an air quality sensor
118
+ * @param device - The device to persist data for
119
+ */
120
+ persistAirQualitySensor(device: iAirQualityCollector): void;
116
121
  /**
117
122
  * Persists data of a handle sensor
118
123
  * @param device - The device to persist data for
@@ -1,5 +1,5 @@
1
1
  import { PoolConfig } from 'pg';
2
- import { iAcDevice, iActuator, iBaseDevice, iBatteryDevice, iButtonSwitch, iDesiredShutterPosition, iHandle, iHeater, iHumidityCollector, iIlluminationSensor, iMotionSensor, iPersist, iRoomBase, iShutter, iShutterCalibration, iTemperatureCollector, iTemperatureMeasurement, iZigbeeDevice } from '../../interfaces';
2
+ import { iAcDevice, iActuator, iBaseDevice, iBatteryDevice, iButtonSwitch, iDesiredShutterPosition, iHandle, iHeater, iAirQualityCollector, iHumidityCollector, iIlluminationSensor, iMotionSensor, iPersist, iRoomBase, iShutter, iShutterCalibration, iTemperatureCollector, iTemperatureMeasurement, iZigbeeDevice } from '../../interfaces';
3
3
  import { CountToday, EnergyCalculation } from '../../models';
4
4
  import { ButtonPressType } from '../../enums';
5
5
  export declare class PostgreSqlPersist implements iPersist {
@@ -42,6 +42,8 @@ export declare class PostgreSqlPersist implements iPersist {
42
42
  /** @inheritDoc */
43
43
  persistHumiditySensor(device: iHumidityCollector): void;
44
44
  /** @inheritDoc */
45
+ persistAirQualitySensor(device: iAirQualityCollector): void;
46
+ /** @inheritDoc */
45
47
  persistBatteryDevice(device: iBatteryDevice): void;
46
48
  /** @inheritDoc */
47
49
  persistZigbeeDevice(device: iZigbeeDevice): void;