Skip to content

Commit c2b307c

Browse files
Tiwasclaude
andcommitted
fix(clg-collection): run member fanout in background to avoid 10s timeout
Homey enforces a 10 s budget on capability listeners. The Collection's inherited onoff listener awaited a full fanout that, on flaky Z-Wave or Zigbee lamps, can spend 20-30 s in retry/verify. Result: a red "Timeout after 10000ms" toast on every toggle even though most lamps respond fine. Drop the fanout into the background; surface failures via alarm_config and a triggerError that names which member groups had unresponsive lamps. Same pattern for the other long-running flow handlers (turn_on/off, pause/resume, apply_state, set_external_lux, set_red_threshold, force_red_mode). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 89087c7 commit c2b307c

2 files changed

Lines changed: 26 additions & 12 deletions

File tree

no.tiwas.booleantoolbox/.homeychangelog.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,5 +88,8 @@
8888
},
8989
"1.10.13": {
9090
"en": "Fix: Toggling a Circadian Light Group Collection now flips each member group's own on/off state (not only the underlying lamps), so the member tiles update as expected."
91+
},
92+
"1.10.14": {
93+
"en": "Fix: Circadian Light Group Collection no longer shows a 'Timeout after 10000ms' toast when a single lamp inside a member group is unresponsive. Member fanout now runs in the background, and the timeline error names which groups had unresponsive members."
9194
}
9295
}

no.tiwas.booleantoolbox/drivers/circadian-light-group-collection/device.js

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,21 @@ class CircadianLightGroupCollectionDevice extends CircadianLightGroupDevice {
9999
const failed = result.failed || [];
100100
await this.setCapabilityValue('alarm_config', failed.length > 0).catch(this.error);
101101
if (failed.length > 0) {
102-
await this.triggerError(`${failed.length} Circadian Light Group member(s) failed during ${label}`);
102+
const names = failed.map(e => e.name || e.id).join(', ');
103+
await this.triggerError(`${label}: ${failed.length} group(s) had unresponsive members: ${names}`);
103104
}
104105
return result;
105106
}
106107

108+
fanoutInBackground(label, taskFn, verifyFn = null) {
109+
// Capability listeners and several Flow actions must return within Homey's 10 s
110+
// budget — but lamps behind a member group can take 20-30 s on retries. Drop the
111+
// fanout into the background; failures surface via alarm_config + triggerError.
112+
this.runForMemberGroups(label, taskFn, verifyFn).catch(err => {
113+
this.error(`Collection background fanout '${label}' failed:`, err);
114+
});
115+
}
116+
107117
async applyCurrentProfile({ reason = 'manual' } = {}) {
108118
if (this.deleted) return false;
109119
const result = await this.runForMemberGroups(`apply_${reason}`, async (device) => {
@@ -130,15 +140,15 @@ class CircadianLightGroupCollectionDevice extends CircadianLightGroupDevice {
130140

131141
async onFlowTurnOn() {
132142
await this.setCollectionOnoff(true);
133-
await this.runForMemberGroups('turn_on', async (device) => {
143+
this.fanoutInBackground('turn_on', async (device) => {
134144
await device.onFlowTurnOn();
135145
}, async (device) => device.getCapabilityValue('onoff') === true);
136146
return true;
137147
}
138148

139149
async onFlowTurnOff() {
140150
await this.setCollectionOnoff(false);
141-
await this.runForMemberGroups('turn_off', async (device) => {
151+
this.fanoutInBackground('turn_off', async (device) => {
142152
await device.onFlowTurnOff();
143153
}, async (device) => device.getCapabilityValue('onoff') === false);
144154
return true;
@@ -164,59 +174,60 @@ class CircadianLightGroupCollectionDevice extends CircadianLightGroupDevice {
164174

165175
async onFlowTurnOnAllMembers() {
166176
// For a Collection, "all members" = the member CLG devices themselves.
167-
// Use onFlowTurnOn so each member's onoff capability flips and its lights follow.
168-
await this.runForMemberGroups('turn_on_all_members', async (device) => {
177+
// Fire-and-forget so the caller (capability listener or Flow action) returns within
178+
// Homey's 10 s budget even when an underlying lamp doesn't respond.
179+
this.fanoutInBackground('turn_on_all_members', async (device) => {
169180
await device.onFlowTurnOn();
170181
}, async (device) => device.getCapabilityValue('onoff') === true);
171182
return true;
172183
}
173184

174185
async onFlowTurnOffAllMembers() {
175-
await this.runForMemberGroups('turn_off_all_members', async (device) => {
186+
this.fanoutInBackground('turn_off_all_members', async (device) => {
176187
await device.onFlowTurnOff();
177188
}, async (device) => device.getCapabilityValue('onoff') === false);
178189
return true;
179190
}
180191

181192
async onFlowPause(args) {
182193
await super.onFlowPause(args);
183-
await this.runForMemberGroups('pause', async (device) => {
194+
this.fanoutInBackground('pause', async (device) => {
184195
await device.onFlowPause(args);
185196
}, async (device) => device.getCapabilityValue('clg_paused') === true);
186197
return true;
187198
}
188199

189200
async onFlowResume() {
190-
await this.runForMemberGroups('resume', async (device) => {
201+
this.fanoutInBackground('resume', async (device) => {
191202
await device.onFlowResume();
192203
}, async (device) => device.getCapabilityValue('clg_paused') !== true);
193204
await super.onFlowResume();
194205
return true;
195206
}
196207

197208
async onFlowSetExternalLux(args) {
198-
await this.runForMemberGroups('set_external_lux', async (device) => {
209+
this.fanoutInBackground('set_external_lux', async (device) => {
199210
await device.onFlowSetExternalLux(args);
200211
});
201212
return true;
202213
}
203214

204215
async onFlowSetRedThreshold(args) {
205-
await this.runForMemberGroups('set_red_threshold', async (device) => {
216+
this.fanoutInBackground('set_red_threshold', async (device) => {
206217
await device.onFlowSetRedThreshold(args);
207218
});
208219
return true;
209220
}
210221

211222
async onFlowApplyState(args) {
212-
await this.runForMemberGroups('apply_state', async (device) => {
223+
this.fanoutInBackground('apply_state', async (device) => {
213224
await device.onFlowApplyState(args);
214225
});
215226
return true;
216227
}
217228

218229
async onFlowForceRedMode(args) {
219-
await this.runForMemberGroups('force_red_mode', async (device) => {
230+
this.fanoutInBackground('force_red_mode', async (device) => {
220231
await device.onFlowForceRedMode(args);
221232
});
222233
return true;

0 commit comments

Comments
 (0)