Files
spectr/internal/localuart/port.go
T
2026-09-03 13:08:01 +03:00

92 lines
2.4 KiB
Go

package localuart
import (
"bytes"
"io"
"strings"
"time"
"go.bug.st/serial"
)
const (
bufferSize = 4096
readTimeout = 50 * time.Millisecond
idle = 200 * time.Millisecond
silenceTimeout = 2 * time.Second // если вообще нет ответа — не виснуть
)
// Port — открытый напрямую USB-UART порт (без демона).
type Port struct {
name string
port serial.Port
}
// Open открывает порт по имени на заданной скорости.
func Open(name string, baud int) (*Port, error) {
mode := &serial.Mode{BaudRate: baud}
p, err := serial.Open(name, mode)
if err != nil {
return nil, err
}
if err := p.SetReadTimeout(readTimeout); err != nil {
_ = p.Close()
return nil, err
}
return &Port{name: name, port: p}, nil
}
// Name возвращает имя порта (например /dev/ttyUSB0 или COM3).
func (p *Port) Name() string { return p.name }
// Write отправляет команду устройству (как в internal/uart.UART).
func (p *Port) Write(cmd string) error {
_, err := p.port.Write([]byte(strings.TrimSpace(cmd) + "\r"))
return err
}
// ReadResponse читает ответ до паузы в потоке данных (idle), либо
// возвращает ошибку, если устройство вообще не ответило за silenceTimeout —
// это важно для standalone-режима: кнопка проверки не должна виснуть
// навсегда, если выбранный порт "не тот".
func (p *Port) ReadResponse() (string, error) {
buf := make([]byte, bufferSize)
var out bytes.Buffer
received := false
lastData := time.Now()
started := time.Now()
for {
n, err := p.port.Read(buf)
if err != nil && err != io.EOF {
return "", err
}
if n > 0 {
received = true
lastData = time.Now()
out.Write(buf[:n])
}
if received && time.Since(lastData) >= idle {
return strings.TrimSpace(out.String()), nil
}
if !received && time.Since(started) >= silenceTimeout {
return "", io.ErrNoProgress
}
}
}
// Exec отправляет команду и возвращает ответ устройства.
func (p *Port) Exec(cmd string) (string, error) {
if err := p.Write(cmd); err != nil {
return "", err
}
return p.ReadResponse()
}
// Close закрывает порт.
func (p *Port) Close() error {
return p.port.Close()
}