You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
118 lines
2.9 KiB
118 lines
2.9 KiB
package serial
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type LinkMonitorOptions struct {
|
|
Interval time.Duration
|
|
}
|
|
|
|
// DisabledLinkStateController keeps modem control inputs available for
|
|
// diagnostics without using CTS/DSR/DCD as a cable-presence signal.
|
|
type DisabledLinkStateController struct {
|
|
Reason string
|
|
}
|
|
|
|
func (c DisabledLinkStateController) LinkStates(ctx context.Context) ([]LinkState, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
reason := c.Reason
|
|
if reason == "" {
|
|
reason = "modem control signals are excluded from connection status"
|
|
}
|
|
states := make([]LinkState, 0, ChannelCount)
|
|
for index := 1; index <= ChannelCount; index++ {
|
|
states = append(states, LinkState{
|
|
Channel: fmt.Sprintf("CH%02d", index), State: "unknown", Signal: "unknown",
|
|
Available: false, Message: reason,
|
|
})
|
|
}
|
|
return states, nil
|
|
}
|
|
|
|
// LinkMonitor owns periodic modem-signal polling. Socket/API requests only
|
|
// copy its cache, so frequent UI refreshes cannot multiply TTY ioctl traffic.
|
|
type LinkMonitor struct {
|
|
source LinkStateController
|
|
interval time.Duration
|
|
|
|
mu sync.RWMutex
|
|
states []LinkState
|
|
}
|
|
|
|
func NewLinkMonitor(source LinkStateController, options LinkMonitorOptions) (*LinkMonitor, error) {
|
|
if source == nil {
|
|
return nil, errors.New("serial link-state source is required")
|
|
}
|
|
interval := options.Interval
|
|
if interval <= 0 {
|
|
interval = time.Second
|
|
}
|
|
if interval < 100*time.Millisecond {
|
|
interval = 100 * time.Millisecond
|
|
}
|
|
if interval > 10*time.Second {
|
|
interval = 10 * time.Second
|
|
}
|
|
states := make([]LinkState, 0, ChannelCount)
|
|
for index := 1; index <= ChannelCount; index++ {
|
|
states = append(states, LinkState{
|
|
Channel: fmt.Sprintf("CH%02d", index), State: "unknown", Signal: "unknown",
|
|
Message: "serial modem status monitor is starting",
|
|
})
|
|
}
|
|
return &LinkMonitor{source: source, interval: interval, states: states}, nil
|
|
}
|
|
|
|
func (m *LinkMonitor) Run(ctx context.Context) {
|
|
if m == nil {
|
|
return
|
|
}
|
|
for {
|
|
started := time.Now()
|
|
m.refresh(ctx)
|
|
remaining := m.interval - time.Since(started)
|
|
if remaining < 0 {
|
|
remaining = 0
|
|
}
|
|
if !waitForProbe(ctx, remaining) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *LinkMonitor) LinkStates(ctx context.Context) ([]LinkState, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
return append([]LinkState(nil), m.states...), nil
|
|
}
|
|
|
|
func (m *LinkMonitor) refresh(parent context.Context) {
|
|
ctx, cancel := context.WithTimeout(parent, m.interval)
|
|
defer cancel()
|
|
states, err := m.source.LinkStates(ctx)
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if err == nil && len(states) > 0 {
|
|
m.states = append(m.states[:0], states...)
|
|
return
|
|
}
|
|
message := "serial modem status refresh returned no state"
|
|
if err != nil {
|
|
message = err.Error()
|
|
}
|
|
for index := range m.states {
|
|
m.states[index].State = "unknown"
|
|
m.states[index].Available = false
|
|
m.states[index].Message = message
|
|
}
|
|
}
|
|
|