remote-shack/internal/driver/prosistel.go

138 lines
3 KiB
Go

package driver
import (
"fmt"
"io"
"strconv"
"strings"
"sync"
"time"
"github.com/tarm/serial"
)
// ProSistelDriver implementiert das RotorDriver-Interface für ProSistel-Controller.
type ProSistelDriver struct {
mu sync.Mutex
port io.ReadWriteCloser
}
// NewProSistelDriver initialisiert eine neue Instanz des Treibers.
func NewProSistelDriver() *ProSistelDriver {
return &ProSistelDriver{}
}
// Connect öffnet die serielle Schnittstelle zum ProSistel-Controller.
func (p *ProSistelDriver) Connect(portPath string, baud int) error {
p.mu.Lock()
defer p.mu.Unlock()
if p.port != nil {
return fmt.Errorf("port bereits geöffnet")
}
config := &serial.Config{
Name: portPath,
Baud: baud,
ReadTimeout: time.Millisecond * 500,
}
sp, err := serial.OpenPort(config)
if err != nil {
return fmt.Errorf("fehler beim öffnen von %s: %w", portPath, err)
}
p.port = sp
return nil
}
// Disconnect schließt die serielle Schnittstelle sauber.
func (p *ProSistelDriver) Disconnect() error {
p.mu.Lock()
defer p.mu.Unlock()
if p.port == nil {
return nil
}
err := p.port.Close()
p.port = nil
return err
}
// SetPosition sendet die Zielkoordinaten an den ProSistel-Controller.
func (p *ProSistelDriver) SetPosition(azimuth, elevation float64) error {
p.mu.Lock()
defer p.mu.Unlock()
if p.port == nil {
return fmt.Errorf("treiber nicht verbunden")
}
azInt := int(azimuth + 0.5)
elInt := int(elevation + 0.5)
if azInt < 0 { azInt = 0 }
if azInt > 360 { azInt = 360 }
cmd := fmt.Sprintf("*O%03d*W%03d\r", azInt, elInt)
_, err := p.port.Write([]byte(cmd))
if err != nil {
return fmt.Errorf("fehler beim senden des set-befehls: %w", err)
}
return nil
}
// GetPosition liest die aktuellen Ist-Werte des Rotors aus.
func (p *ProSistelDriver) GetPosition() (az, el float64, err error) {
p.mu.Lock()
defer p.mu.Unlock()
if p.port == nil {
return 0, 0, fmt.Errorf("treiber nicht verbunden")
}
az, err = p.queryValue("*A000\r", "A")
if err != nil {
return 0, 0, fmt.Errorf("fehler bei azimut-abfrage: %w", err)
}
el, err = p.queryValue("*E000\r", "E")
if err != nil {
return 0, 0, fmt.Errorf("fehler bei elevations-abfrage: %w", err)
}
return az, el, nil
}
// Hilfsfunktion zum Senden einer Anfrage und Parsen der Antwort
func (p *ProSistelDriver) queryValue(cmd string, prefix string) (float64, error) {
if _, err := p.port.Write([]byte(cmd)); err != nil {
return 0, err
}
buf := make([]byte, 32)
n, err := p.port.Read(buf)
if err != nil && err != io.EOF {
return 0, err
}
response := strings.TrimSpace(string(buf[:n]))
if response == "" {
return 0, fmt.Errorf("keine antwort vom controller erhalten")
}
cleaned := response
cleaned = strings.ReplaceAll(cleaned, prefix+"=", "")
cleaned = strings.ReplaceAll(cleaned, prefix, "")
cleaned = strings.TrimSpace(cleaned)
val, err := strconv.ParseFloat(cleaned, 64)
if err != nil {
return 0, fmt.Errorf("ungültiges antwortformat '%s': %w", response, err)
}
return val, nil
}