251 lines
6.1 KiB
Go
251 lines
6.1 KiB
Go
package driver
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/tarm/serial"
|
|
)
|
|
|
|
// FT897Driver implementiert das RigDriver-Interface für den Yaesu FT-897.
|
|
// Er nutzt eine aggressive Puffer-Bereinigung, um Bit-Verschiebungen bei hoher Last zu verhindern.
|
|
type FT897Driver struct {
|
|
mu sync.Mutex
|
|
port io.ReadWriteCloser
|
|
cachedMode string // Interner RAM-Puffer für die Betriebsart
|
|
}
|
|
|
|
// NewFT897Driver initialisiert die Instanz und setzt den Standardmodus auf USB.
|
|
func NewFT897Driver() *FT897Driver {
|
|
return &FT897Driver{
|
|
cachedMode: "USB",
|
|
}
|
|
}
|
|
|
|
// Connect öffnet die serielle Schnittstelle mit optimierten Timeouts.
|
|
func (d *FT897Driver) Connect(portPath string, baud int) error {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
if d.port != nil {
|
|
return fmt.Errorf("port bereits geöffnet")
|
|
}
|
|
|
|
// Ein kürzeres Timeout von 100ms zwingt io.ReadFull dazu, bei unvollständigen
|
|
// Paketen sofort abzubrechen, anstatt das gesamte gRPC-System zu verlangsamen.
|
|
config := &serial.Config{
|
|
Name: portPath,
|
|
Baud: baud,
|
|
ReadTimeout: time.Millisecond * 100,
|
|
}
|
|
|
|
sp, err := serial.OpenPort(config)
|
|
if err != nil {
|
|
return fmt.Errorf("fehler beim öffnen des cat-ports %s: %w", portPath, err)
|
|
}
|
|
|
|
d.port = sp
|
|
return nil
|
|
}
|
|
|
|
// Disconnect schließt das serielle Gerät sauber.
|
|
func (d *FT897Driver) Disconnect() error {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
if d.port == nil {
|
|
return nil
|
|
}
|
|
|
|
err := d.port.Close()
|
|
d.port = nil
|
|
return err
|
|
}
|
|
|
|
// clearSerialBuffer liest alle im OS-Puffer festsitzenden Alt-Bytes ungelesen weg.
|
|
// Dies verhindert effektiv, dass alte Datenpakete nachfolgende Antworten asynchron verschieben.
|
|
func (d *FT897Driver) clearSerialBuffer() {
|
|
// Da tarm/serial im non-blocking/timeout Modus arbeitet, lesen wir in einer
|
|
// kurzen Schleife so lange, bis keine Daten mehr anstehen (Timeout schlägt an).
|
|
dummy := make([]byte, 32)
|
|
for {
|
|
// Nutze ein extrem kurzes Zeitfenster, um nicht spürbar zu blockieren
|
|
n, _ := d.port.Read(dummy)
|
|
if n == 0 {
|
|
break // Puffer ist komplett leer und sauber
|
|
}
|
|
}
|
|
}
|
|
|
|
// SetFrequency sendet die neue Frequenz im korrekten BCD-Format.
|
|
func (d *FT897Driver) SetFrequency(hz int64) error {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
if d.port == nil {
|
|
return fmt.Errorf("transceiver nicht verbunden")
|
|
}
|
|
|
|
// 1. Altlasten aus dem seriellen Puffer spülen
|
|
d.clearSerialBuffer()
|
|
|
|
// 2. Skalierung auf 10-Hz-Schritte des Yaesu
|
|
remain := hz / 10
|
|
|
|
// 3. BCD packen
|
|
var bcd [4]byte
|
|
for i := 3; i >= 0; i-- {
|
|
digit1 := remain % 10
|
|
remain /= 10
|
|
digit2 := remain % 10
|
|
remain /= 10
|
|
bcd[i] = byte((digit2 << 4) | digit1)
|
|
}
|
|
|
|
cmd := []byte{bcd[0], bcd[1], bcd[2], bcd[3], 0x01}
|
|
|
|
if _, err := d.port.Write(cmd); err != nil {
|
|
return fmt.Errorf("fehler beim senden des set-frequency befehls: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetFrequency liest den 5-Byte Block aus und aktualisiert Frequenz und Modus zeitgleich.
|
|
func (d *FT897Driver) GetFrequency() (int64, error) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
if d.port == nil {
|
|
return 0, fmt.Errorf("transceiver nicht verbunden")
|
|
}
|
|
|
|
// 1. ABSOLUT KRITISCH: Vor der Anfrage den Puffer flushen!
|
|
// Verhindert, dass wir Bytes aus der vorherigen Abfrage mitlesen.
|
|
d.clearSerialBuffer()
|
|
|
|
// 2. 5-Byte Status-Abfrage senden (Opcode 0x03)
|
|
if _, err := d.port.Write([]byte{0x00, 0x00, 0x00, 0x00, 0x03}); err != nil {
|
|
return 0, fmt.Errorf("fehler beim senden der statusabfrage: %w", err)
|
|
}
|
|
|
|
// 3. Den Block einlesen
|
|
buf := make([]byte, 5)
|
|
if _, err := io.ReadFull(d.port, buf); err != nil {
|
|
return 0, fmt.Errorf("cat-lesefehler (puffer asynchron): %w", err)
|
|
}
|
|
|
|
// 4. Frequenz aus Byte 0-3 rekonstruieren
|
|
var freq int64
|
|
for i := 0; i < 4; i++ {
|
|
high := int64(buf[i] >> 4)
|
|
low := int64(buf[i] & 0x0F)
|
|
freq = freq*100 + (high*10 + low)
|
|
}
|
|
freq = freq * 10
|
|
|
|
// 5. Modus aus Byte 4 isolieren und im Cache sichern
|
|
switch buf[4] & 0x0F {
|
|
case 0x00: d.cachedMode = "LSB"
|
|
case 0x01: d.cachedMode = "USB"
|
|
case 0x02: d.cachedMode = "CW"
|
|
case 0x04: d.cachedMode = "AM"
|
|
case 0x08: d.cachedMode = "FM"
|
|
}
|
|
|
|
return freq, nil
|
|
}
|
|
|
|
// GetMode liest kollisionsfrei die Betriebsart aus dem RAM-Cache.
|
|
func (d *FT897Driver) GetMode() (string, error) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
return d.cachedMode, nil
|
|
}
|
|
|
|
// SetMode schaltet die Betriebsart um und aktualisiert sofort den Cache.
|
|
func (d *FT897Driver) SetMode(mode string) error {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
if d.port == nil {
|
|
return fmt.Errorf("transceiver nicht verbunden")
|
|
}
|
|
|
|
// Puffer vor dem Schreiben reinigen
|
|
d.clearSerialBuffer()
|
|
|
|
var modeByte byte
|
|
switch mode {
|
|
case "LSB": modeByte = 0x00
|
|
case "USB": modeByte = 0x01
|
|
case "CW": modeByte = 0x02
|
|
case "AM": modeByte = 0x04
|
|
case "FM": modeByte = 0x08
|
|
case "FT8": modeByte = 0x01
|
|
default: return fmt.Errorf("modus %s wird vom ft897-treiber nicht unterstützt", mode)
|
|
}
|
|
|
|
cmd := []byte{modeByte, 0x00, 0x00, 0x00, 0x07}
|
|
if _, err := d.port.Write(cmd); err != nil {
|
|
return fmt.Errorf("fehler beim senden des modus-befehls: %w", err)
|
|
}
|
|
|
|
// Cache direkt updaten, um Rücksprünge zu verhindern
|
|
if mode == "FT8" {
|
|
d.cachedMode = "USB"
|
|
} else {
|
|
d.cachedMode = mode
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetPTT schaltet den Sender (Opcode 0x08) oder Empfänger (Opcode 0x88).
|
|
func (d *FT897Driver) SetPTT(active bool) error {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
if d.port == nil {
|
|
return fmt.Errorf("transceiver nicht verbunden")
|
|
}
|
|
|
|
d.clearSerialBuffer()
|
|
|
|
opcode := byte(0x08)
|
|
if !active {
|
|
opcode = 0x88
|
|
}
|
|
|
|
cmd := []byte{0x00, 0x00, 0x00, 0x00, opcode}
|
|
_, err := d.port.Write(cmd)
|
|
return err
|
|
}
|
|
|
|
// GetSMeter fragt die Signalstärke ab (Opcode 0xE7).
|
|
func (d *FT897Driver) GetSMeter() (int, error) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
if d.port == nil {
|
|
return 0, fmt.Errorf("transceiver nicht verbunden")
|
|
}
|
|
|
|
// Vor der S-Meter-Abfrage Puffer reinigen
|
|
d.clearSerialBuffer()
|
|
|
|
if _, err := d.port.Write([]byte{0x00, 0x00, 0x00, 0x00, 0xE7}); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
buf := make([]byte, 1)
|
|
if _, err := io.ReadFull(d.port, buf); err != nil {
|
|
return 0, fmt.Errorf("fehler beim lesen des s-meter-bytes: %w", err)
|
|
}
|
|
|
|
rawStrength := int(buf[0] & 0x0F)
|
|
sMeterValue := (rawStrength * 9) / 15
|
|
return sMeterValue, nil
|
|
}
|