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:
@@ -20,7 +20,8 @@ func NewDenkovi(serial string, count int) Device {
|
||||
return denkoviStub{count: count}
|
||||
}
|
||||
|
||||
func (s denkoviStub) Count() int { return s.count }
|
||||
func (s denkoviStub) Count() int { return s.count }
|
||||
func (denkoviStub) Close() error { return nil }
|
||||
func (denkoviStub) Status(context.Context) ([]bool, error) {
|
||||
return nil, fmt.Errorf("Denkovi USB relay board is only supported on Windows")
|
||||
}
|
||||
|
||||
@@ -121,6 +121,19 @@ func (d *denkovi) Set(ctx context.Context, relay int, on bool) error {
|
||||
return d.writeLocked()
|
||||
}
|
||||
|
||||
// Close releases the FTDI handle so the board can be reopened later (only one
|
||||
// handle may hold a D2XX device at a time).
|
||||
func (d *denkovi) Close() error {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
if d.opened {
|
||||
procClose.Call(d.h)
|
||||
d.opened = false
|
||||
d.h = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *denkovi) Status(ctx context.Context) ([]bool, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -37,6 +37,19 @@ func NewSerialRelay(port string, count int) Device {
|
||||
|
||||
func (s *serialRelay) Count() int { return s.count }
|
||||
|
||||
// Close releases the COM port so it can be reopened later (only one handle may
|
||||
// hold a serial port at a time).
|
||||
func (s *serialRelay) Close() error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.port != nil {
|
||||
err := s.port.Close()
|
||||
s.port = nil
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *serialRelay) ensureOpen() error {
|
||||
if s.port != nil {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user