hoffmation-base 3.5.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
- dev.setAcState(new command_1.AcSetStateCommand(source,
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
- // The block travels inside the command so setAcState applies it centrally. As
156
- // before, only switching off pins the automatic.
157
- newDesiredState ? undefined : new command_1.BlockAutomaticCommand(source, 60 * 60 * 1000)));
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(enums_1.LogLevel.Alert, 'Not closing the shutter, as the window is open!');
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(enums_1.LogLevel.Alert, 'Not closing the shutter, as the window is half open!');
79
+ device.log(warningLevel, 'Not closing the shutter, as the window is half open!');
75
80
  }
76
81
  }
77
82
  }
@@ -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,35 @@ 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.setAcState(new command_1.AcSetStateCommand(action, enums_1.AcMode.Off, undefined, 'Something moved in the room and noCoolingOnMovement is set.'));
343
+ this.setAcState(new command_1.AcSetStateCommand(action, enums_1.AcMode.Off, undefined, 'Something moved in the room and noCoolingOnMovement is set.', this.buildMovementBlock(action)));
344
+ }
345
+ /**
346
+ * Keeps the unit off for as long as the room counts as occupied - renewed by every movement.
347
+ *
348
+ * Without a block, staying off depended on the five-minute interval check noticing that somebody
349
+ * is present, and coming back on depended on the last-leave callback arriving. The block replaces
350
+ * both with one timer: every movement pushes it out, and its expiry reverts to automatic by
351
+ * itself, so the room cools again shortly after it empties rather than at the next check.
352
+ *
353
+ * The duration is the room's own movementResetTimer - the same span that decides when the room
354
+ * counts as empty. Anything shorter would let the unit start up with somebody still sitting there;
355
+ * a separate constant would be a second answer to a question the room already answers.
356
+ *
357
+ * The collision strategy is deliberately left at its default (overrideIfGreater): a long press on
358
+ * the wall button sets a much longer block, and walking past the unit must not cut that short.
359
+ * @param c - The command the block chains from, so the log shows what triggered it
360
+ * @returns The block, or undefined when the device has no room to take the duration from
361
+ */
362
+ buildMovementBlock(c) {
363
+ var _a, _b;
364
+ const occupiedForSeconds = (_b = (_a = this.room) === null || _a === void 0 ? void 0 : _a.settings) === null || _b === void 0 ? void 0 : _b.movementResetTimer;
365
+ // No room means no answer to "how long does this count as occupied". Switching off without a
366
+ // block is what happened before this existed, and it is the honest fallback - a made-up
367
+ // duration would block the automatic for a span nothing chose.
368
+ if (occupiedForSeconds === undefined || occupiedForSeconds <= 0) {
369
+ return undefined;
370
+ }
371
+ return new command_1.BlockAutomaticCommand(c, occupiedForSeconds * 1000, 'noCoolingOnMovement', undefined, true);
345
372
  }
346
373
  onRoomLastLeave(action) {
347
374
  if (!this.settings.noCoolingOnMovement) {