Fix fixture process transitions

This commit is contained in:
Gabriel Brown
2026-08-17 16:59:15 -04:00
parent a325ec807e
commit 4f93522f7d
2 changed files with 94 additions and 9 deletions
@@ -32,6 +32,9 @@ Singleton {
property bool actionStreamFinished: false property bool actionStreamFinished: false
property bool actionExited: false property bool actionExited: false
property int actionExitCode: 0 property int actionExitCode: 0
property bool fixtureTransitionDraining: false
property bool fixtureTransitionStreamFinished: false
property bool fixtureTransitionExited: false
readonly property var visibleEntities: root.selectedEntities.slice(0, 4) readonly property var visibleEntities: root.selectedEntities.slice(0, 4)
readonly property int discoveredCount: root.catalog.length readonly property int discoveredCount: root.catalog.length
@@ -193,8 +196,12 @@ Singleton {
} }
function startNextAction(): void { function startNextAction(): void {
if (actionProc.running || root.activeAction !== null || root.actionQueue.length === 0) if (root.fixtureTransitionDraining
|| actionProc.running
|| root.activeAction !== null
|| root.actionQueue.length === 0) {
return; return;
}
root.activeAction = root.actionQueue[0]; root.activeAction = root.actionQueue[0];
root.actionResponseText = ""; root.actionResponseText = "";
@@ -220,6 +227,11 @@ Singleton {
} }
function handleActionStreamFinished(text: string): void { function handleActionStreamFinished(text: string): void {
if (root.fixtureTransitionDraining) {
root.fixtureTransitionStreamFinished = true;
fixtureTransitionTimer.restart();
return;
}
if (root.activeAction === null || root.actionStreamFinished) if (root.activeAction === null || root.actionStreamFinished)
return; return;
root.actionResponseText = text; root.actionResponseText = text;
@@ -228,6 +240,11 @@ Singleton {
} }
function handleActionExited(exitCode: int): void { function handleActionExited(exitCode: int): void {
if (root.fixtureTransitionDraining) {
root.fixtureTransitionExited = true;
fixtureTransitionTimer.restart();
return;
}
if (root.activeAction === null || root.actionExited) if (root.activeAction === null || root.actionExited)
return; return;
root.actionExited = true; root.actionExited = true;
@@ -350,13 +367,49 @@ Singleton {
root.actionExitCode = 0; root.actionExitCode = 0;
} }
function beginFixtureTransition(): void {
const shouldDrain = root.fixtureMode
&& root.fixtureProcessMode !== ""
&& (actionProc.running || root.activeAction !== null);
const streamAlreadyFinished = root.actionStreamFinished;
const exitAlreadyObserved = root.actionExited;
fixtureTransitionTimer.stop();
root.fixtureTransitionDraining = shouldDrain;
root.fixtureTransitionStreamFinished = shouldDrain && streamAlreadyFinished;
root.fixtureTransitionExited = shouldDrain && exitAlreadyObserved;
root.resetActionState();
if (!shouldDrain)
return;
if (actionProc.running)
actionProc.running = false;
fixtureTransitionTimer.restart();
}
function tryFinishFixtureTransition(): void {
if (!root.fixtureTransitionDraining)
return;
if (actionProc.running) {
fixtureTransitionTimer.restart();
return;
}
if (!root.fixtureTransitionStreamFinished || !root.fixtureTransitionExited)
return;
root.fixtureTransitionDraining = false;
root.fixtureTransitionStreamFinished = false;
root.fixtureTransitionExited = false;
queueAdvanceTimer.restart();
}
function applyFixture(name: string): void { function applyFixture(name: string): void {
if (["ready", "stale", "unavailable", "missing-selected", "action-error", if (["ready", "stale", "unavailable", "missing-selected", "action-error",
"process-actions", "process-no-output"].indexOf(name) < 0) "process-actions", "process-no-output"].indexOf(name) < 0)
return; return;
root.beginFixtureTransition();
root.fixtureMode = true; root.fixtureMode = true;
root.resetActionState();
root.fixtureProcessMode = name === "process-actions" root.fixtureProcessMode = name === "process-actions"
? "success" ? "success"
: (name === "process-no-output" ? "no-output" : ""); : (name === "process-no-output" ? "no-output" : "");
@@ -389,6 +442,7 @@ Singleton {
} }
function clearFixture(): void { function clearFixture(): void {
root.beginFixtureTransition();
root.fixtureMode = false; root.fixtureMode = false;
root.fixtureProcessMode = ""; root.fixtureProcessMode = "";
root.phase = "loading"; root.phase = "loading";
@@ -397,7 +451,6 @@ Singleton {
root.fixtureFavorites = []; root.fixtureFavorites = [];
root.stale = false; root.stale = false;
root.lastError = ""; root.lastError = "";
root.resetActionState();
root.refresh(); root.refresh();
} }
@@ -427,6 +480,8 @@ Singleton {
id: queueAdvanceTimer id: queueAdvanceTimer
interval: 0 interval: 0
onTriggered: { onTriggered: {
if (root.fixtureTransitionDraining)
return;
if (actionProc.running) { if (actionProc.running) {
queueAdvanceTimer.restart(); queueAdvanceTimer.restart();
return; return;
@@ -435,6 +490,12 @@ Singleton {
} }
} }
Timer {
id: fixtureTransitionTimer
interval: 0
onTriggered: root.tryFinishFixtureTransition()
}
Timer { Timer {
interval: 60000 interval: 60000
repeat: true repeat: true
@@ -45,12 +45,15 @@ trap cleanup EXIT
start_test_shell() { start_test_shell() {
stop_test_shell || fail 'pre-existing branch shell did not stop cleanly' stop_test_shell || fail 'pre-existing branch shell did not stop cleanly'
qs_for_test --daemonize >"$shell_log" 2>&1 for _attempt in 1 2; do
for _ in $(seq 1 80); do qs_for_test --daemonize >"$shell_log" 2>&1
if qs_for_test ipc show 2>/dev/null | rg '^target home-assistant$' >/dev/null; then for _ in $(seq 1 80); do
return if qs_for_test ipc show 2>/dev/null | rg '^target home-assistant$' >/dev/null; then
fi return
sleep 0.1 fi
sleep 0.1
done
stop_test_shell || fail 'failed branch-shell attempt did not stop cleanly'
done done
sed -n '1,200p' "$shell_log" >&2 sed -n '1,200p' "$shell_log" >&2
fail 'isolated branch shell did not start' fail 'isolated branch shell did not start'
@@ -185,6 +188,27 @@ wait_for_home_status '
(.entities[] | select(.id == "light.fixture_hall") | .active == true) (.entities[] | select(.id == "light.fixture_hall") | .active == true)
' 'nonzero no-output Home action did not fail safely and continue the queue' ' 'nonzero no-output Home action did not fail safely and continue the queue'
qs_for_test ipc call home-assistant fixture process-actions >/dev/null
qs_for_test ipc call home-assistant brightness light.fixture_living 88 >/dev/null
jq -e '
.busyEntityIds == ["light.fixture_living"] and
.pendingBrightness["light.fixture_living"] == 88
' <<<"$(qs_for_test ipc call home-assistant status)" >/dev/null \
|| fail 'fixture transition setup did not start an in-flight action'
qs_for_test ipc call home-assistant fixture process-no-output >/dev/null
qs_for_test ipc call home-assistant toggle light.fixture_hall >/dev/null
wait_for_home_status '
.phase == "ready" and
.busy == false and
.busyEntityIds == [] and
.pendingBrightness == {} and
.entityErrors == {} and
(.entities[] |
select(.id == "light.fixture_living") |
.active == false and .brightnessPct == 36) and
(.entities[] | select(.id == "light.fixture_hall") | .active == true)
' 'switching process fixtures stranded or misattributed the new action'
qs_for_test ipc call home-assistant fixture missing-selected >/dev/null qs_for_test ipc call home-assistant fixture missing-selected >/dev/null
jq -e ' jq -e '
.fixture == true and .fixture == true and