108 lines
2.4 KiB
Go
108 lines
2.4 KiB
Go
package scpi
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// transport is the request/response channel to an instrument. The raw-socket
|
|
// implementation below speaks line-oriented SCPI over TCP (the common
|
|
// "SCPI raw" / port 5025 convention). The interface is kept narrow so a VXI-11
|
|
// (ONC-RPC) transport can be added later without touching the data source.
|
|
type transport interface {
|
|
// query sends cmd and returns the instrument's single-line response.
|
|
query(cmd string) (string, error)
|
|
// write sends cmd and does not wait for a response.
|
|
write(cmd string) error
|
|
close()
|
|
}
|
|
|
|
// rawSocket is a line-based SCPI transport over a single TCP connection. The
|
|
// connection is dialled lazily and dropped on any I/O error so the next call
|
|
// reconnects. Calls are serialised by mu because SCPI is request/response.
|
|
type rawSocket struct {
|
|
addr string
|
|
timeout time.Duration
|
|
terminator string
|
|
|
|
mu sync.Mutex
|
|
conn net.Conn
|
|
br *bufio.Reader
|
|
}
|
|
|
|
func newRawSocket(addr string, timeout time.Duration, terminator string) *rawSocket {
|
|
if timeout <= 0 {
|
|
timeout = 3 * time.Second
|
|
}
|
|
if terminator == "" {
|
|
terminator = "\n"
|
|
}
|
|
return &rawSocket{addr: addr, timeout: timeout, terminator: terminator}
|
|
}
|
|
|
|
func (s *rawSocket) dialLocked() error {
|
|
if s.conn != nil {
|
|
return nil
|
|
}
|
|
conn, err := net.DialTimeout("tcp", s.addr, s.timeout)
|
|
if err != nil {
|
|
return fmt.Errorf("scpi: dial %s: %w", s.addr, err)
|
|
}
|
|
s.conn = conn
|
|
s.br = bufio.NewReader(conn)
|
|
return nil
|
|
}
|
|
|
|
func (s *rawSocket) closeLocked() {
|
|
if s.conn != nil {
|
|
_ = s.conn.Close()
|
|
s.conn = nil
|
|
s.br = nil
|
|
}
|
|
}
|
|
|
|
func (s *rawSocket) close() {
|
|
s.mu.Lock()
|
|
s.closeLocked()
|
|
s.mu.Unlock()
|
|
}
|
|
|
|
func (s *rawSocket) sendLocked(cmd string) error {
|
|
_ = s.conn.SetDeadline(time.Now().Add(s.timeout))
|
|
if _, err := s.conn.Write([]byte(cmd + s.terminator)); err != nil {
|
|
s.closeLocked()
|
|
return fmt.Errorf("scpi: write: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *rawSocket) write(cmd string) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if err := s.dialLocked(); err != nil {
|
|
return err
|
|
}
|
|
return s.sendLocked(cmd)
|
|
}
|
|
|
|
func (s *rawSocket) query(cmd string) (string, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if err := s.dialLocked(); err != nil {
|
|
return "", err
|
|
}
|
|
if err := s.sendLocked(cmd); err != nil {
|
|
return "", err
|
|
}
|
|
line, err := s.br.ReadString('\n')
|
|
if err != nil {
|
|
s.closeLocked()
|
|
return "", fmt.Errorf("scpi: read: %w", err)
|
|
}
|
|
return strings.TrimRight(line, "\r\n"), nil
|
|
}
|