95 lines
2.5 KiB
Go
95 lines
2.5 KiB
Go
package robot
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
_ "time/tzdata" // zone database for SetTime inside the distroless image
|
|
|
|
"git.i3omb.com/gronod/ha-n95-local-control/internal/ctl"
|
|
)
|
|
|
|
// readyCommand pairs one outbound ctl body with the command name registered
|
|
// in the correlator and surfaced in traces.
|
|
type readyCommand struct {
|
|
name string
|
|
out ctl.Outbound
|
|
}
|
|
|
|
// readySequence is the §8 fan-out: SetTime, the five Gets, then GetLifeSpan
|
|
// for SideBrush, Brush and DustCaseHeap. SetTime carries the current Unix
|
|
// second and the container-local UTC offset taken from now; Go division
|
|
// truncates toward zero so a negative offset keeps its sign on both parts.
|
|
func readySequence(now time.Time) []readyCommand {
|
|
_, offset := now.Zone()
|
|
hours := offset / 3600
|
|
minutes := (offset % 3600) / 60
|
|
|
|
seq := []readyCommand{{
|
|
name: "SetTime",
|
|
out: ctl.Outbound{
|
|
TD: "SetTime",
|
|
Inner: []byte(fmt.Sprintf(`<time t="%d" tz="%d" tzm="%d"/>`, now.Unix(), hours, minutes)),
|
|
},
|
|
}}
|
|
for _, td := range []string{"GetBatteryInfo", "GetCleanState", "GetChargeState", "GetCleanSpeed", "GetSched"} {
|
|
seq = append(seq, readyCommand{name: td, out: ctl.Outbound{TD: td}})
|
|
}
|
|
for _, kind := range []string{"SideBrush", "Brush", "DustCaseHeap"} {
|
|
seq = append(seq, readyCommand{
|
|
name: "GetLifeSpan",
|
|
out: ctl.Outbound{
|
|
TD: "GetLifeSpan",
|
|
Attrs: []ctl.Attr{{Name: "type", Value: kind}},
|
|
},
|
|
})
|
|
}
|
|
return seq
|
|
}
|
|
|
|
func cleanOutbound(cleanType, act, fan string) ctl.Outbound {
|
|
return ctl.Outbound{
|
|
TD: "Clean",
|
|
Inner: []byte(fmt.Sprintf(`<clean type="%s" speed="%s" act="%s"/>`,
|
|
escAttr(cleanType), escAttr(fan), escAttr(act))),
|
|
}
|
|
}
|
|
|
|
func chargeOutbound(kind string) ctl.Outbound {
|
|
return ctl.Outbound{
|
|
TD: "Charge",
|
|
Inner: []byte(fmt.Sprintf(`<charge type="%s"/>`, escAttr(kind))),
|
|
}
|
|
}
|
|
|
|
func playSoundOutbound() ctl.Outbound {
|
|
return ctl.Outbound{
|
|
TD: "PlaySound",
|
|
Attrs: []ctl.Attr{{Name: "sid", Value: "0"}},
|
|
}
|
|
}
|
|
|
|
func setCleanSpeedOutbound(speed string) ctl.Outbound {
|
|
return ctl.Outbound{
|
|
TD: "SetCleanSpeed",
|
|
Attrs: []ctl.Attr{{Name: "speed", Value: speed}},
|
|
}
|
|
}
|
|
|
|
// moveOutbound encodes Move without a ctl id; phase 05 originates it. The
|
|
// codec and its sid-only correlation are exercised by this phase's tests.
|
|
func moveOutbound(action string) ctl.Outbound {
|
|
return ctl.Outbound{
|
|
TD: "Move",
|
|
OmitCtlID: true,
|
|
Inner: []byte(fmt.Sprintf(`<move action="%s"/>`, escAttr(action))),
|
|
}
|
|
}
|
|
|
|
func escAttr(v string) string {
|
|
var b strings.Builder
|
|
_ = xml.EscapeText(&b, []byte(v))
|
|
return b.String()
|
|
}
|