92 lines
2.4 KiB
Go
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()
|
|
}
|