fix: relay auto-control no longer toggles on every launch/close
Closing OpsLog dropped the CAT, whose frequency went to 0; the auto-control read that as out of range and switched the relay OFF, then ON again at the next launch. Now a rule is skipped entirely when the frequency/band is unknown (CAT off), so relays keep their state across a close. And on the first evaluation after launch/save it reads the boards' LIVE state and only switches a relay that isn't already in the wanted position — no more clunk when it's already correct.
This commit is contained in:
+61
-10
@@ -96,8 +96,23 @@ func bandInList(bands []string, band string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// relayAction is one relay's computed desired state for this evaluation.
|
||||
type relayAction struct {
|
||||
dev string
|
||||
relay int
|
||||
want bool
|
||||
}
|
||||
|
||||
// applyRelayAuto evaluates every rule against the current frequency/band and
|
||||
// switches only the relays whose desired state changed since the last apply.
|
||||
// switches only the relays that are NOT already in the wanted position. Two things
|
||||
// it deliberately does NOT do, which used to make the relay clunk on every
|
||||
// launch/close:
|
||||
// - Never acts on an UNKNOWN frequency/band. When the CAT disconnects (app close)
|
||||
// the frequency drops to 0; reading that as "out of range" and switching the
|
||||
// relay off — then back on at the next launch — was the whole bug.
|
||||
// - Never commands a relay already in the right position: on the first evaluation
|
||||
// after launch/save it reads the boards' LIVE state, so a relay that's already
|
||||
// correct is left untouched instead of being re-sent.
|
||||
func (a *App) applyRelayAuto(freqHz int64, band string) {
|
||||
a.relayAutoMu.Lock()
|
||||
defer a.relayAutoMu.Unlock()
|
||||
@@ -110,8 +125,11 @@ func (a *App) applyRelayAuto(freqHz int64, band string) {
|
||||
a.relayAutoLast = map[string]bool{}
|
||||
}
|
||||
khz := float64(freqHz) / 1000.0
|
||||
band = strings.TrimSpace(band)
|
||||
|
||||
changed := false
|
||||
// Compute desired states, skipping rules whose input is unknown right now.
|
||||
var acts []relayAction
|
||||
needLive := false
|
||||
for _, r := range cfg.Rules {
|
||||
if r.Relay < 1 {
|
||||
continue
|
||||
@@ -119,8 +137,11 @@ func (a *App) applyRelayAuto(freqHz int64, band string) {
|
||||
var want bool
|
||||
switch r.Mode {
|
||||
case "freq":
|
||||
if freqHz <= 0 {
|
||||
continue // no known frequency (CAT off/closing) → leave the relay as-is
|
||||
}
|
||||
if r.FreqLoKHz <= 0 && r.FreqHiKHz <= 0 {
|
||||
continue // unconfigured range → leave the relay alone
|
||||
continue // unconfigured range
|
||||
}
|
||||
lo, hi := r.FreqLoKHz, r.FreqHiKHz
|
||||
if hi < lo {
|
||||
@@ -128,6 +149,9 @@ func (a *App) applyRelayAuto(freqHz int64, band string) {
|
||||
}
|
||||
want = khz >= lo && khz <= hi
|
||||
case "band":
|
||||
if band == "" {
|
||||
continue // no known band → leave the relay as-is
|
||||
}
|
||||
if len(r.Bands) == 0 {
|
||||
continue
|
||||
}
|
||||
@@ -135,16 +159,43 @@ func (a *App) applyRelayAuto(freqHz int64, band string) {
|
||||
default:
|
||||
continue // "off"/empty → not managed
|
||||
}
|
||||
|
||||
key := relayAutoKey(r.DeviceID, r.Relay)
|
||||
if last, ok := a.relayAutoLast[key]; ok && last == want {
|
||||
continue // no change → don't hammer the board
|
||||
acts = append(acts, relayAction{r.DeviceID, r.Relay, want})
|
||||
if _, ok := a.relayAutoLast[relayAutoKey(r.DeviceID, r.Relay)]; !ok {
|
||||
needLive = true
|
||||
}
|
||||
if err := a.StationSetRelay(r.DeviceID, r.Relay, want); err != nil {
|
||||
applog.Printf("relay auto: set %s relay %d = %v failed: %v", r.DeviceID, r.Relay, want, err)
|
||||
}
|
||||
if len(acts) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// First evaluation after launch/save: read the boards' LIVE relay states once
|
||||
// so we don't re-command a relay that's already in the wanted position.
|
||||
var live map[string]bool
|
||||
if needLive {
|
||||
live = map[string]bool{}
|
||||
for _, ds := range a.GetStationStatus() {
|
||||
for _, rl := range ds.Relays {
|
||||
live[relayAutoKey(ds.ID, rl.Number)] = rl.On
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changed := false
|
||||
for _, ac := range acts {
|
||||
key := relayAutoKey(ac.dev, ac.relay)
|
||||
cur, known := a.relayAutoLast[key]
|
||||
if !known && live != nil {
|
||||
cur, known = live[key]
|
||||
}
|
||||
if known && cur == ac.want {
|
||||
a.relayAutoLast[key] = ac.want // already in position — record it, don't switch
|
||||
continue
|
||||
}
|
||||
if err := a.StationSetRelay(ac.dev, ac.relay, ac.want); err != nil {
|
||||
applog.Printf("relay auto: set %s relay %d = %v failed: %v", ac.dev, ac.relay, ac.want, err)
|
||||
continue // don't cache a failed write — retry next change
|
||||
}
|
||||
a.relayAutoLast[key] = want
|
||||
a.relayAutoLast[key] = ac.want
|
||||
changed = true
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user