hoffmation-base 3.5.0 → 3.6.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/lib/devices/groups/heatGroup.js +15 -5
- package/lib/devices/sharedFunctions/shutterUtils.js +7 -2
- package/lib/interfaces/iBlockAutomaticHandler.d.ts +4 -0
- package/lib/services/ac/ac-device.d.ts +18 -0
- package/lib/services/ac/ac-device.js +40 -2
- package/lib/services/blockAutomaticHandler.d.ts +11 -0
- package/lib/services/blockAutomaticHandler.js +17 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -149,12 +149,22 @@ class HeatGroup extends base_group_1.BaseGroup {
|
|
|
149
149
|
const devs = this.getOwnAcDevices();
|
|
150
150
|
this.log(enums_1.LogLevel.Debug, `set ${devs.length} Ac's to new State: ${newDesiredState}`);
|
|
151
151
|
for (const dev of devs) {
|
|
152
|
-
|
|
152
|
+
const command = new command_1.AcSetStateCommand(source,
|
|
153
153
|
// Undefined mode means "just switch it on"; the device resolves which mode that is.
|
|
154
|
-
newDesiredState ? undefined : enums_1.AcMode.Off, undefined, 'HeatGroup setAc'
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
|
|
154
|
+
newDesiredState ? undefined : enums_1.AcMode.Off, undefined, 'HeatGroup setAc');
|
|
155
|
+
// As before, only switching off pins the automatic - and the block travels inside the command
|
|
156
|
+
// so setAcState applies it centrally.
|
|
157
|
+
//
|
|
158
|
+
// The duration now comes from the device's own settings, built the way every other device
|
|
159
|
+
// builds its block (see ShutterUtils.setLevel). It used to be a hard-coded hour here, which
|
|
160
|
+
// is exactly what the configured default resolves to - so nothing changes today; what changes
|
|
161
|
+
// is that a single unit can be given a shorter block without touching group code. Building it
|
|
162
|
+
// from the command rather than from `source` also puts the block into the reason chain, so the
|
|
163
|
+
// log says which switch-off set it.
|
|
164
|
+
if (!newDesiredState) {
|
|
165
|
+
command.disableAutomaticCommand = dev.settings.buildBlockAutomaticCommand(command);
|
|
166
|
+
}
|
|
167
|
+
dev.setAcState(command);
|
|
158
168
|
}
|
|
159
169
|
}
|
|
160
170
|
deleteAutomaticPoint(name) {
|
|
@@ -62,16 +62,21 @@ class ShutterUtils {
|
|
|
62
62
|
}
|
|
63
63
|
device.logCommand(c);
|
|
64
64
|
if (device.window !== undefined) {
|
|
65
|
+
// Only a manual command deserves an alert, because only then is someone waiting for a shutter
|
|
66
|
+
// that will not move. The automatic run meets an open window every evening, and an Alert is
|
|
67
|
+
// what reaches Telegram - a nightly push for an expected skip. Info keeps the decision
|
|
68
|
+
// reconstructable without waking anybody.
|
|
69
|
+
const warningLevel = c.isManual ? enums_1.LogLevel.Alert : enums_1.LogLevel.Info;
|
|
65
70
|
if (device.window.griffeInPosition(enums_1.WindowPosition.open) > 0 && pPosition < 100) {
|
|
66
71
|
if (!c.skipOpenWarning) {
|
|
67
|
-
device.log(
|
|
72
|
+
device.log(warningLevel, 'Not closing the shutter, as the window is open!');
|
|
68
73
|
}
|
|
69
74
|
return;
|
|
70
75
|
}
|
|
71
76
|
if (device.window.griffeInPosition(enums_1.WindowPosition.tilted) > 0 && pPosition < 50) {
|
|
72
77
|
pPosition = 50;
|
|
73
78
|
if (!c.skipOpenWarning) {
|
|
74
|
-
device.log(
|
|
79
|
+
device.log(warningLevel, 'Not closing the shutter, as the window is half open!');
|
|
75
80
|
}
|
|
76
81
|
}
|
|
77
82
|
}
|
|
@@ -11,6 +11,10 @@ export interface iBlockAutomaticHandler {
|
|
|
11
11
|
*
|
|
12
12
|
*/
|
|
13
13
|
readonly automaticBlockActive: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the active block was set by a person (Manual, API or Force) rather than by a rule.
|
|
16
|
+
*/
|
|
17
|
+
readonly automaticBlockedByUser: boolean;
|
|
14
18
|
/**
|
|
15
19
|
*
|
|
16
20
|
*/
|
|
@@ -93,6 +93,24 @@ export declare abstract class AcDevice extends RoomBaseDevice implements iExcess
|
|
|
93
93
|
*/
|
|
94
94
|
protected automaticCheck(c: AcPerformAutomaticCheckCommand): void;
|
|
95
95
|
private onRoomAnyMovement;
|
|
96
|
+
/**
|
|
97
|
+
* Keeps the unit off for as long as the room counts as occupied - renewed by every movement.
|
|
98
|
+
*
|
|
99
|
+
* Without a block, staying off depended on the five-minute interval check noticing that somebody
|
|
100
|
+
* is present, and coming back on depended on the last-leave callback arriving. The block replaces
|
|
101
|
+
* both with one timer: every movement pushes it out, and its expiry reverts to automatic by
|
|
102
|
+
* itself, so the room cools again shortly after it empties rather than at the next check.
|
|
103
|
+
*
|
|
104
|
+
* The duration is the room's own movementResetTimer - the same span that decides when the room
|
|
105
|
+
* counts as empty. Anything shorter would let the unit start up with somebody still sitting there;
|
|
106
|
+
* a separate constant would be a second answer to a question the room already answers.
|
|
107
|
+
*
|
|
108
|
+
* The collision strategy is deliberately left at its default (overrideIfGreater): a long press on
|
|
109
|
+
* the wall button sets a much longer block, and walking past the unit must not cut that short.
|
|
110
|
+
* @param c - The command the block chains from, so the log shows what triggered it
|
|
111
|
+
* @returns The block, or undefined when the device has no room to take the duration from
|
|
112
|
+
*/
|
|
113
|
+
private buildMovementBlock;
|
|
96
114
|
private onRoomLastLeave;
|
|
97
115
|
/** @inheritDoc */
|
|
98
116
|
toJSON(): Partial<AcDevice>;
|
|
@@ -100,7 +100,6 @@ class AcDevice extends devices_1.RoomBaseDevice {
|
|
|
100
100
|
}
|
|
101
101
|
utils_1.Utils.guardedFunction(() => {
|
|
102
102
|
var _a, _b;
|
|
103
|
-
// TODO: Maybe change to any Movement
|
|
104
103
|
(_a = this.room.PraesenzGroup) === null || _a === void 0 ? void 0 : _a.addAnyMovementCallback(this.onRoomAnyMovement.bind(this));
|
|
105
104
|
(_b = this.room.PraesenzGroup) === null || _b === void 0 ? void 0 : _b.addLastLeftCallback(this.onRoomLastLeave.bind(this));
|
|
106
105
|
this._movementCallbackAdded = true;
|
|
@@ -341,7 +340,46 @@ class AcDevice extends devices_1.RoomBaseDevice {
|
|
|
341
340
|
if (!this.settings.noCoolingOnMovement || !this.on || this.mode === enums_1.AcMode.Heating) {
|
|
342
341
|
return;
|
|
343
342
|
}
|
|
344
|
-
this
|
|
343
|
+
// Somebody asked for this unit to run and pinned it - a configured rule does not overrule that.
|
|
344
|
+
// This is the case the report was about: switching the AC on from the phone pins it for an hour,
|
|
345
|
+
// the next movement a minute later switched it off anyway, and the pin then kept the automatic
|
|
346
|
+
// from ever switching it back on. The unit stayed off for the full hour.
|
|
347
|
+
//
|
|
348
|
+
// Only a person's pin counts. The block this path sets itself is an automatic one, so a later
|
|
349
|
+
// movement can still renew it.
|
|
350
|
+
if (this.blockAutomationHandler.automaticBlockedByUser) {
|
|
351
|
+
this.log(enums_1.LogLevel.Debug, 'Movement detected, but the AC was pinned by hand - leaving it alone.');
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
this.setAcState(new command_1.AcSetStateCommand(action, enums_1.AcMode.Off, undefined, 'Something moved in the room and noCoolingOnMovement is set.', this.buildMovementBlock(action)));
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Keeps the unit off for as long as the room counts as occupied - renewed by every movement.
|
|
358
|
+
*
|
|
359
|
+
* Without a block, staying off depended on the five-minute interval check noticing that somebody
|
|
360
|
+
* is present, and coming back on depended on the last-leave callback arriving. The block replaces
|
|
361
|
+
* both with one timer: every movement pushes it out, and its expiry reverts to automatic by
|
|
362
|
+
* itself, so the room cools again shortly after it empties rather than at the next check.
|
|
363
|
+
*
|
|
364
|
+
* The duration is the room's own movementResetTimer - the same span that decides when the room
|
|
365
|
+
* counts as empty. Anything shorter would let the unit start up with somebody still sitting there;
|
|
366
|
+
* a separate constant would be a second answer to a question the room already answers.
|
|
367
|
+
*
|
|
368
|
+
* The collision strategy is deliberately left at its default (overrideIfGreater): a long press on
|
|
369
|
+
* the wall button sets a much longer block, and walking past the unit must not cut that short.
|
|
370
|
+
* @param c - The command the block chains from, so the log shows what triggered it
|
|
371
|
+
* @returns The block, or undefined when the device has no room to take the duration from
|
|
372
|
+
*/
|
|
373
|
+
buildMovementBlock(c) {
|
|
374
|
+
var _a, _b;
|
|
375
|
+
const occupiedForSeconds = (_b = (_a = this.room) === null || _a === void 0 ? void 0 : _a.settings) === null || _b === void 0 ? void 0 : _b.movementResetTimer;
|
|
376
|
+
// No room means no answer to "how long does this count as occupied". Switching off without a
|
|
377
|
+
// block is what happened before this existed, and it is the honest fallback - a made-up
|
|
378
|
+
// duration would block the automatic for a span nothing chose.
|
|
379
|
+
if (occupiedForSeconds === undefined || occupiedForSeconds <= 0) {
|
|
380
|
+
return undefined;
|
|
381
|
+
}
|
|
382
|
+
return new command_1.BlockAutomaticCommand(c, occupiedForSeconds * 1000, 'noCoolingOnMovement', undefined, true);
|
|
345
383
|
}
|
|
346
384
|
onRoomLastLeave(action) {
|
|
347
385
|
if (!this.settings.noCoolingOnMovement) {
|
|
@@ -9,11 +9,22 @@ export declare class BlockAutomaticHandler implements iBlockAutomaticHandler {
|
|
|
9
9
|
private readonly _logger;
|
|
10
10
|
private readonly _restoreAutomatic;
|
|
11
11
|
private _automaticBlockedUntil;
|
|
12
|
+
private _blockSetByUser;
|
|
12
13
|
private _restoreAutomaticStateTimeout;
|
|
13
14
|
constructor(restoreAutomaticCb: (c: RestoreTargetAutomaticValueCommand) => void, _logger: (level: LogLevel, message: string, logDebugType?: LogDebugType) => void);
|
|
14
15
|
get automaticBlockedUntil(): Date;
|
|
15
16
|
private set automaticBlockedUntil(value);
|
|
16
17
|
get automaticBlockActive(): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Whether the active block was set by a person rather than by another rule.
|
|
20
|
+
*
|
|
21
|
+
* The expiry date alone cannot answer that, and a rule that has to decide whether it may overrule
|
|
22
|
+
* the current state needs to: at the end of the day the user's choice outranks a configured
|
|
23
|
+
* automatism. Manual, API and Force all count - they are the same decision reaching the house
|
|
24
|
+
* through different doors.
|
|
25
|
+
* @returns True while a block set by a person is still running.
|
|
26
|
+
*/
|
|
27
|
+
get automaticBlockedByUser(): boolean;
|
|
17
28
|
disableAutomatic(c: BlockAutomaticCommand): void;
|
|
18
29
|
disableAutomaticUntil(c: BlockAutomaticUntilCommand): void;
|
|
19
30
|
liftAutomaticBlock(c: BlockAutomaticLiftBlockCommand): void;
|
|
@@ -16,6 +16,7 @@ class BlockAutomaticHandler {
|
|
|
16
16
|
constructor(restoreAutomaticCb, _logger) {
|
|
17
17
|
this._logger = _logger;
|
|
18
18
|
this._automaticBlockedUntil = new Date(0);
|
|
19
|
+
this._blockSetByUser = false;
|
|
19
20
|
this._restoreAutomaticStateTimeout = null;
|
|
20
21
|
this._restoreAutomatic = restoreAutomaticCb;
|
|
21
22
|
}
|
|
@@ -28,6 +29,18 @@ class BlockAutomaticHandler {
|
|
|
28
29
|
get automaticBlockActive() {
|
|
29
30
|
return this._automaticBlockedUntil > new Date();
|
|
30
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Whether the active block was set by a person rather than by another rule.
|
|
34
|
+
*
|
|
35
|
+
* The expiry date alone cannot answer that, and a rule that has to decide whether it may overrule
|
|
36
|
+
* the current state needs to: at the end of the day the user's choice outranks a configured
|
|
37
|
+
* automatism. Manual, API and Force all count - they are the same decision reaching the house
|
|
38
|
+
* through different doors.
|
|
39
|
+
* @returns True while a block set by a person is still running.
|
|
40
|
+
*/
|
|
41
|
+
get automaticBlockedByUser() {
|
|
42
|
+
return this.automaticBlockActive && this._blockSetByUser;
|
|
43
|
+
}
|
|
31
44
|
disableAutomatic(c) {
|
|
32
45
|
this.disableAutomaticUntil(new command_1.BlockAutomaticUntilCommand(c, new Date(utils_1.Utils.nowMS() + c.durationMS), '', c.onCollideAction, c.revertToAutomaticAtBlockLift));
|
|
33
46
|
}
|
|
@@ -41,6 +54,9 @@ class BlockAutomaticHandler {
|
|
|
41
54
|
}
|
|
42
55
|
this._logger(enums_1.LogLevel.Info, c.logMessage);
|
|
43
56
|
this.automaticBlockedUntil = c.targetDate;
|
|
57
|
+
// Recorded here rather than derived later: the command is gone once this returns, and the level
|
|
58
|
+
// that pinned the device is exactly what a rule needs to know before overruling it.
|
|
59
|
+
this._blockSetByUser = c.isForceAction;
|
|
44
60
|
if (c.revertToAutomaticAtBlockLift) {
|
|
45
61
|
const revertCommand = new command_1.RestoreTargetAutomaticValueCommand(c, 'Restore to automatic state after block.');
|
|
46
62
|
revertCommand.overrideCommandSource = enums_1.CommandSource.Automatic;
|
|
@@ -53,6 +69,7 @@ class BlockAutomaticHandler {
|
|
|
53
69
|
liftAutomaticBlock(c) {
|
|
54
70
|
this.removeRestoreTimeout();
|
|
55
71
|
this.automaticBlockedUntil = new Date(0);
|
|
72
|
+
this._blockSetByUser = false;
|
|
56
73
|
if (c.revertToAutomatic) {
|
|
57
74
|
this._restoreAutomatic(new command_1.RestoreTargetAutomaticValueCommand(c));
|
|
58
75
|
}
|