250 lines
7 KiB
Go
250 lines
7 KiB
Go
package driver
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/tarm/serial"
|
|
)
|
|
|
|
// FT897Driver implementiert das RigDriver-Interface für den Yaesu FT-897 (CAT-Protokoll).
|
|
type FT897Driver struct {
|
|
mu sync.Mutex
|
|
port io.ReadWriteCloser
|
|
}
|
|
|
|
// NewFT897Driver initialisiert eine neue Instanz für das Yaesu-CAT-Protokoll.
|
|
func NewFT897Driver() *FT897Driver {
|
|
return &FT897Driver{}
|
|
}
|
|
|
|
// Connect öffnet die serielle Schnittstelle (z.B. FTDI-Kabel / CT-62 Ersatz).
|
|
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")
|
|
}
|
|
|
|
config := &serial.Config{
|
|
Name: portPath,
|
|
Baud: baud,
|
|
ReadTimeout: time.Millisecond * 300, // Schnelles Timeout für flüssiges VFO-Streaming
|
|
}
|
|
|
|
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.
|
|
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
|
|
}
|
|
|
|
// GetFrequency liest die aktuelle Betriebsfrequenz des Funkgeräts aus.
|
|
// Der Yaesu FT-897 liefert bei der Statusabfrage (Opcode 0x03) ein 5-Byte Paket zurück.
|
|
// Die ersten 4 Bytes enthalten die Frequenz als BCD gepackt in 10-Hz-Schritten.
|
|
// Das 5. Byte enthält die aktuelle Betriebsart (Mode).
|
|
func (d *FT897Driver) GetFrequency() (int64, error) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
// 1. Sicherheitshalt: Prüfen, ob die serielle Verbindung aktiv ist
|
|
if d.port == nil {
|
|
return 0, fmt.Errorf("transceiver nicht verbunden")
|
|
}
|
|
|
|
// 2. CAT-Abfragebefehl an das Funkgerät senden.
|
|
// Das Paket besteht aus 4 Dummy-Bytes und dem Opcode 0x03 (Read Status).
|
|
cmd := []byte{0x00, 0x00, 0x00, 0x00, 0x03}
|
|
if _, err := d.port.Write(cmd); err != nil {
|
|
return 0, fmt.Errorf("fehler beim senden der statusabfrage (opcode 0x03): %w", err)
|
|
}
|
|
|
|
// 3. Die 5-Byte-Antwort der Transceiver-Firmware blockierend einlesen
|
|
buf := make([]byte, 5)
|
|
if _, err := io.ReadFull(d.port, buf); err != nil {
|
|
return 0, fmt.Errorf("fehler beim lesen der 5-byte cat-antwort: %w", err)
|
|
}
|
|
|
|
// 4. BCD-Entschlüsselung (Rechnerische Rekonstruktion der Frequenz)
|
|
// Wir laufen durch die ersten 4 Bytes. Jedes Byte enthält zwei Dezimalziffern.
|
|
var freq int64
|
|
for i := 0; i < 4; i++ {
|
|
// Bit-Shifting: Die oberen 4 Bit isolieren und nach rechts schieben (z.B. 0x14 -> 1)
|
|
high := int64(buf[i] >> 4)
|
|
// Bit-Maskierung: Die unteren 4 Bit isolieren (z.B. 0x14 & 0x0F -> 4)
|
|
low := int64(buf[i] & 0x0F)
|
|
|
|
// Die bestehende Zahl um zwei Dezimalstellen nach links schieben
|
|
// und die beiden neu decodierten Ziffern am Ende anfügen.
|
|
freq = freq*100 + (high*10 + low)
|
|
}
|
|
|
|
// 5. SKALIERUNGS-KORREKTUR:
|
|
// Da der Yaesu FT-897 die Frequenz im BCD-Register nativ in 10-Hz-Schritten ausgibt,
|
|
// müssen wir das Zwischenergebnis zwingend mit 10 multiplizieren.
|
|
// Beispiel: Aus 14436000 (10-Hz-Einheiten) wird 144360000 Hz (Echte Hertz).
|
|
freq = freq * 10
|
|
|
|
return freq, nil
|
|
}
|
|
|
|
// SetFrequency stellt die Betriebsfrequenz des Funkgeräts in Hertz (Hz) ein.
|
|
// Der Yaesu FT-897 erwartet ein 5-Byte-Paket: 4 Bytes Frequenz im BCD-Format
|
|
// (aufgelöst in 10-Hz-Schritten) und als 5. Byte den Opcode 0x01.
|
|
// Beispiel: 145.600.000 Hz wird zu den BCD-Bytes [0x14, 0x56, 0x00, 0x00] + 0x01.
|
|
func (d *FT897Driver) SetFrequency(hz int64) error {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
// 1. Sicherheitshalt: Verbindung prüfen
|
|
if d.port == nil {
|
|
return fmt.Errorf("transceiver nicht verbunden")
|
|
}
|
|
|
|
// 2. SKALIERUNGS-ANPASSUNG:
|
|
// Da das CAT-Protokoll des Yaesu die Frequenz in 10-Hz-Schritten verarbeitet,
|
|
// müssen wir den übergebenen Hertz-Wert zuerst durch 10 teilen.
|
|
// Beispiel: 145600000 Hz / 10 = 14560000 (8-stellige Basis für die 4 BCD-Bytes).
|
|
remain := hz / 10
|
|
|
|
// 3. BCD-Verschlüsselung (Packen der Ziffern von hinten nach vorne)
|
|
var bcd [4]byte
|
|
for i := 3; i >= 0; i-- {
|
|
// Isoliere die niederwertige Ziffer (Low-Nibble, z.B. 0)
|
|
digit1 := remain % 10
|
|
remain /= 10
|
|
|
|
// Isoliere die höherwertige Ziffer des Paars (High-Nibble, z.B. 0)
|
|
digit2 := remain % 10
|
|
remain /= 10
|
|
|
|
// Schiebe die zweite Ziffer in die oberen 4 Bits (Bitwise OR) und speichere das Byte
|
|
bcd[i] = byte((digit2 << 4) | digit1)
|
|
}
|
|
|
|
// 4. Befehlspaket zusammensetzen: 4 Bytes Frequenz-BCD + 1 Byte Schreib-Opcode (0x01)
|
|
cmd := []byte{bcd[0], bcd[1], bcd[2], bcd[3], 0x01}
|
|
|
|
// 5. Paket über die serielle Schnittstelle an den Yaesu abfeuern
|
|
if _, err := d.port.Write(cmd); err != nil {
|
|
return fmt.Errorf("fehler beim senden des set-frequency befehls: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetMode schaltet das Modulationsrelais im FT-897 um (Opcode 0x07).
|
|
func (d *FT897Driver) SetMode(mode string) error {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
if d.port == nil {
|
|
return fmt.Errorf("transceiver nicht verbunden")
|
|
}
|
|
|
|
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 // FT8 nutzt technisch das USB-Seitenband
|
|
default: return fmt.Errorf("modus %s wird vom ft897-treiber nicht unterstützt", mode)
|
|
}
|
|
|
|
cmd := []byte{modeByte, 0x00, 0x00, 0x00, 0x07}
|
|
_, err := d.port.Write(cmd)
|
|
return err
|
|
}
|
|
|
|
// GetMode holt das aktuelle Status-Byte vom Gerät.
|
|
func (d *FT897Driver) GetMode() (string, error) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
if d.port == nil {
|
|
return "",fmt.Errorf("transceiver nicht verbunden")
|
|
}
|
|
|
|
if _, err := d.port.Write([]byte{0x00, 0x00, 0x00, 0x00, 0x03}); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
buf := make([]byte, 5)
|
|
if _, err := io.ReadFull(d.port, buf); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// KORREKTUR: Expliziter Index-Zugriff auf das fünfte Byte (buf[4]) für das Mode-Register
|
|
switch buf[4] & 0x0F {
|
|
case 0x00: return "LSB", nil
|
|
case 0x01: return "USB", nil
|
|
case 0x02: return "CW", nil
|
|
case 0x04: return "AM", nil
|
|
case 0x08: return "FM", nil
|
|
}
|
|
return "USB", nil // Fallback für digitale Untermodi
|
|
}
|
|
|
|
// SetPTT schaltet den Sender ein (Opcode 0x08) oder aus (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")
|
|
}
|
|
|
|
opcode := byte(0x08)
|
|
if !active {
|
|
opcode = 0x88
|
|
}
|
|
|
|
cmd := []byte{0x00, 0x00, 0x00, 0x00, opcode}
|
|
_, err := d.port.Write(cmd)
|
|
return err
|
|
}
|
|
|
|
// GetSMeter fragt das S-Meter über den internen ADC-Status 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")
|
|
}
|
|
|
|
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, err
|
|
}
|
|
|
|
// KORREKTUR: Zugriff auf buf[0] statt auf das gesamte Slice
|
|
rawStrength := int(buf[0] & 0x0F)
|
|
sMeterValue := (rawStrength * 9) / 15
|
|
return sMeterValue, nil
|
|
}
|