feat: Denkovi USB 4/8 (selectable) + generic USB-serial relay board (CH340/LCUS)
- Denkovi (FT245 D2XX) now selectable as 4 or 8 relays (a 4-relay board just uses the low nibble); NewDenkovi takes a count, driven by the device's Channels. - New 'usbrelay' backend: cheap CH340/LCUS USB-serial boards over a COM port using the common A0 protocol ([0xA0][relay][state][checksum]); write-only, so Status is a shadow. Channel count configurable (1/2/4/8/16). - StationDevice gains Channels; deviceRelayCount() drives label/relay counts for the variable-size types. Device editor: channel selector + COM-port picker (usbrelay) / FTDI-serial (denkovi). Both untested on hardware; the A0 command set / Denkovi bit order may need a tweak after a live test.
This commit is contained in:
@@ -11397,14 +11397,28 @@ func boolStr(b bool) string {
|
||||
// StationDevice is one configured relay board for the Station Control tab.
|
||||
type StationDevice struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"` // "webswitch" | "kmtronic" | "denkovi"
|
||||
Type string `json:"type"` // "webswitch" | "kmtronic" | "denkovi" | "usbrelay"
|
||||
Name string `json:"name"`
|
||||
Host string `json:"host"`
|
||||
Host string `json:"host"` // IP for network boards; FTDI serial for denkovi; COM port for usbrelay
|
||||
User string `json:"user,omitempty"` // KMTronic HTTP auth (blank = none)
|
||||
Pass string `json:"pass,omitempty"`
|
||||
Channels int `json:"channels,omitempty"` // usbrelay only: number of relays (1/2/4/8/16)
|
||||
Labels []string `json:"labels"` // per-relay label (index 0 = relay 1)
|
||||
}
|
||||
|
||||
// deviceRelayCount is the relay count for a configured device — fixed by type,
|
||||
// except the Denkovi (4 or 8) and the generic USB-serial board, whose channel
|
||||
// count the user picks.
|
||||
func deviceRelayCount(d StationDevice) int {
|
||||
if d.Type == "usbrelay" || d.Type == "denkovi" {
|
||||
if d.Channels >= 1 {
|
||||
return d.Channels
|
||||
}
|
||||
return 8
|
||||
}
|
||||
return relayCountFor(d.Type)
|
||||
}
|
||||
|
||||
// relayCountFor returns a device type's fixed relay count.
|
||||
func relayCountFor(typ string) int {
|
||||
switch typ {
|
||||
@@ -11422,7 +11436,10 @@ func deviceDriver(d StationDevice) relaydev.Device {
|
||||
return relaydev.NewKMTronic(d.Host, d.User, d.Pass)
|
||||
case "denkovi":
|
||||
// Host carries the FTDI serial number (e.g. "DAE0006K"), not a hostname.
|
||||
return relaydev.NewDenkovi(d.Host)
|
||||
return relaydev.NewDenkovi(d.Host, deviceRelayCount(d))
|
||||
case "usbrelay":
|
||||
// Host carries the COM port (e.g. "COM5"); CH340/LCUS "A0" serial protocol.
|
||||
return relaydev.NewSerialRelay(d.Host, deviceRelayCount(d))
|
||||
default:
|
||||
return relaydev.NewWebswitch(d.Host)
|
||||
}
|
||||
@@ -11458,7 +11475,7 @@ func (a *App) SaveStationDevices(devs []StationDevice) error {
|
||||
}
|
||||
for i := range devs {
|
||||
switch devs[i].Type {
|
||||
case "kmtronic", "denkovi", "webswitch":
|
||||
case "kmtronic", "denkovi", "usbrelay", "webswitch":
|
||||
// known type — keep
|
||||
default:
|
||||
devs[i].Type = "webswitch"
|
||||
@@ -11466,7 +11483,7 @@ func (a *App) SaveStationDevices(devs []StationDevice) error {
|
||||
if strings.TrimSpace(devs[i].ID) == "" {
|
||||
devs[i].ID = fmt.Sprintf("dev%d-%d", i, time.Now().UnixNano())
|
||||
}
|
||||
n := relayCountFor(devs[i].Type)
|
||||
n := deviceRelayCount(devs[i])
|
||||
for len(devs[i].Labels) < n {
|
||||
devs[i].Labels = append(devs[i].Labels, "")
|
||||
}
|
||||
@@ -11507,7 +11524,7 @@ func (a *App) GetStationStatus() []StationDeviceStatus {
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
d := devs[i]
|
||||
n := relayCountFor(d.Type)
|
||||
n := deviceRelayCount(d)
|
||||
ds := StationDeviceStatus{ID: d.ID, Name: d.Name, Type: d.Type}
|
||||
ctx, cancel := context.WithTimeout(a.ctx, 6*time.Second)
|
||||
defer cancel()
|
||||
|
||||
Reference in New Issue
Block a user