smarthome: keep the devices under the cap, and stop asserting what the house did not do
States sorted every entity by id and cut at MaxEntities. Entity ids sort by domain prefix, so binary_sensor came first and forty slots went to connectivity and update-available rows: propose found nothing controllable, and homeSummary, reading the same list, said everything was off with the lights on. The cap stays, because a tool name the 1.7B half-remembers is a wrong act. What changes is which forty. Controllable domains are taken first and round-robin, so every switch and light is in before any sensor. CallService reported done for a call that changed nothing. Home Assistant answers a service call with the states it changed, and a removed entity or an offline integration gets 200 and an empty array. That is the one place Maven asserts something about the physical world, so an empty array is now ErrUnknownEntity. The confirm turn on a house row was a column, not an invariant. The proposal is destructive, but /tools writes the checkbox through on enable, so unticking it once made an unlock row that ran on first hearing. Exec now demands the second turn for any smarthome row whatever the column says, and lock is out of the default domain set so a bare block does not propose an unlock for every door. wireSmartHome enumerated the house synchronously, inside wireVoice, before the socket was serving and inside the unlock handler. A box that black-holes the connection held the daemon's start for the per-call timeout. The first propose moved onto the ticker goroutine. Smaller: an unreachable lamp is counted apart from an off one, a truncated on-list says how many it left out, refresh has a floor of a minute, and the http url is documented as a deliberate wg-only choice. Found in review of #80. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TrVSBKe3RFDF4fGYKWYQnX
This commit is contained in:
@@ -150,6 +150,15 @@ func (e *Executor) Exec(ctx context.Context, name string, args []string, confirm
|
||||
if e.home == nil {
|
||||
return "", ErrNotEnabled
|
||||
}
|
||||
// The confirm turn on a house row is structural, not a column. The
|
||||
// proposal is written destructive=true, but /tools writes the checkbox
|
||||
// straight through on enable (destructive=excluded.destructive), so
|
||||
// unticking it once turned home_lock_front_door_unlock into a row that
|
||||
// ran on first hearing. Nothing any surface writes can remove the
|
||||
// second turn from a physical device.
|
||||
if !confirmed {
|
||||
return "", ErrNeedsConfirm
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, e.timeout)
|
||||
defer cancel()
|
||||
return e.home.CallService(ctx, entityID, service)
|
||||
|
||||
@@ -262,3 +262,33 @@ func TestExecSmartHomeRow(t *testing.T) {
|
||||
t.Fatal(`"smarthome" was run as a binary`)
|
||||
}
|
||||
}
|
||||
|
||||
// The confirm turn on a house row survives the destructive column being wrong.
|
||||
// ProposeSmartHomeTool writes destructive=true, but /tools reads the checkbox
|
||||
// from the form and EnableTool writes destructive=excluded.destructive, so
|
||||
// unticking it once turned home_lock_front_door_unlock into a row that opened
|
||||
// the front door on first hearing. The guarantee has to be structural.
|
||||
func TestExecSmartHomeRowConfirmsEvenWhenNotMarkedDestructive(t *testing.T) {
|
||||
api := fakeAPI{tools: map[string]ipc.Tool{
|
||||
"home_lock_front_door_unlock": {
|
||||
Name: "home_lock_front_door_unlock", Scope: "smarthome:lock",
|
||||
Cmd: []string{"smarthome", "lock.front_door", "unlock"},
|
||||
// The column Kami unticked on /tools.
|
||||
Destructive: false, Status: "enabled",
|
||||
},
|
||||
}}
|
||||
fh := &fakeHome{}
|
||||
e := NewExecutor(api, time.Second).WithHome(fh)
|
||||
if _, err := e.Exec(context.Background(), "home_lock_front_door_unlock", nil, false); !errors.Is(err, ErrNeedsConfirm) {
|
||||
t.Fatalf("err = %v, want ErrNeedsConfirm", err)
|
||||
}
|
||||
if fh.calls != 0 {
|
||||
t.Fatal("the front door was unlocked without a confirm turn")
|
||||
}
|
||||
if _, err := e.Exec(context.Background(), "home_lock_front_door_unlock", nil, true); err != nil {
|
||||
t.Fatalf("confirmed: %v", err)
|
||||
}
|
||||
if fh.calls != 1 {
|
||||
t.Fatalf("calls = %d, want 1 after the confirm turn", fh.calls)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user