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.

119 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
}
}