fix: cache relay-board drivers so Denkovi/USB-serial boards stay connected

The station-control code rebuilt a fresh driver on every status poll and every
relay set. Stateful boards (Denkovi FTDI D2XX, USB-serial) hold an OS handle only
one opener can own, so the first poll opened the board and leaked the handle, and
every poll after failed with 'device in use' — the relays greyed out a second
after Save and auto-control never switched. Drivers are now cached per device and
reused, closed on config change. Adds a Test-connection button + detect feedback
in the device editor (reported by VK4MA).
This commit is contained in:
2026-07-21 09:31:08 +02:00
parent 615df0dc10
commit 880ecdbbb5
10 changed files with 174 additions and 14 deletions
+9 -2
View File
@@ -27,6 +27,11 @@ type Device interface {
Count() int // number of user-controllable relays
Status(ctx context.Context) ([]bool, error) // state of each relay (index 0 = relay 1)
Set(ctx context.Context, relay int, on bool) error // relay is 1-based
// Close releases any OS handle the driver holds (serial port, FTDI handle).
// Network boards hold nothing and no-op. MUST be called when a cached driver is
// discarded so the port/handle is freed for the next open — stateful boards
// (Denkovi, USB-serial) can only be opened by one handle at a time.
Close() error
}
func httpClient() *http.Client { return &http.Client{Timeout: 5 * time.Second} }
@@ -62,7 +67,8 @@ type webswitch struct {
// NewWebswitch builds a WebSwitch 1216H client (5 relays).
func NewWebswitch(host string) Device { return &webswitch{host: host, count: 5} }
func (w *webswitch) Count() int { return w.count }
func (w *webswitch) Count() int { return w.count }
func (w *webswitch) Close() error { return nil } // stateless HTTP, nothing to release
func (w *webswitch) Set(ctx context.Context, relay int, on bool) error {
if relay < 1 || relay > w.count {
@@ -118,7 +124,8 @@ func NewKMTronic(host, user, pass string) Device {
return &kmtronic{host: host, user: user, pass: pass, count: 8}
}
func (k *kmtronic) Count() int { return k.count }
func (k *kmtronic) Count() int { return k.count }
func (k *kmtronic) Close() error { return nil } // stateless HTTP, nothing to release
func (k *kmtronic) Set(ctx context.Context, relay int, on bool) error {
if relay < 1 || relay > k.count {