Files

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()
}