FlexDXCluster/flexradio.go
2024-10-30 22:46:48 +07:00

339 lines
10 KiB
Go

package main
import (
"bufio"
"fmt"
"net"
"os"
"regexp"
"strings"
"time"
)
var CommandNumber int = 1
type FlexSpot struct {
ID int
CommandNumber int
FlexSpotNumber int
DX string
FrequencyMhz string
FrequencyHz string
Band string
Mode string
FlexMode string
Source string
SpotterCallsign string
TimeStamp int64
UTCTime string
LifeTime string
Priority string
Comment string
Color string
BackgroundColor string
NewDXCC bool
NewBand bool
NewMode bool
Worked bool
}
type Discovery struct {
IP string
NickName string
Model string
Serial string
Version string
}
type FlexClient struct {
Address string
Port string
Timeout time.Duration
LogWriter *bufio.Writer
Reader *bufio.Reader
Writer *bufio.Writer
Conn *net.TCPConn
SpotChanToFlex chan TelnetSpot
MsgChan chan string
Repo FlexDXClusterRepository
TCPServer *TCPServer
IsConnected bool
}
func NewFlexClient(repo FlexDXClusterRepository, TCPServer *TCPServer, SpotChanToFlex chan TelnetSpot) *FlexClient {
return &FlexClient{
Port: "4992",
SpotChanToFlex: SpotChanToFlex,
MsgChan: TCPServer.MsgChan,
Repo: repo,
TCPServer: TCPServer,
IsConnected: false,
}
}
func (fc *FlexClient) StartFlexClient() {
if Cfg.Flex.IP == "" && !Cfg.Flex.Discover {
Log.Errorln("You must either turn FlexRadio Discovery on or provide an IP address for the Flex")
} else if Cfg.Flex.Discover {
ok, d := DiscoverFlexRadio()
if ok {
fc.Address = d.IP
Log.Infof("Found: %s with Nick: %s, Version: %s, Serial: %s - using IP: %s", d.Model, d.NickName, d.Version, d.Serial, d.IP)
} else {
Log.Errorln("Could not discover any FlexRadio on the network, please provide an IP address in the config file.")
}
} else if Cfg.Flex.IP != "" {
fc.Address = Cfg.Flex.IP
}
if fc.Address != "" {
addr, err := net.ResolveTCPAddr("tcp", fc.Address+":"+fc.Port)
if err != nil {
Log.Error("Cannot resolve Telnet Client address")
}
fc.LogWriter = bufio.NewWriter(os.Stdout)
fc.Timeout = 600 * time.Second
Log.Infof("Trying to connect to flex radio at %s:%s", fc.Address, fc.Port)
fc.Conn, err = net.DialTCP("tcp", nil, addr)
if err != nil {
Log.Errorf("Could not connect to flex radio on %s", Cfg.Flex.IP)
Log.Error("Retrying to connect to flex radio in 5 seconds")
time.Sleep(time.Second * 5)
fc.StartFlexClient()
}
Log.Infof("Connected to flex radio at %s:%s", fc.Address, fc.Port)
fc.IsConnected = true
go func() {
for message := range fc.SpotChanToFlex {
fc.SendSpottoFlex(message)
}
}()
fc.Reader = bufio.NewReader(fc.Conn)
fc.Writer = bufio.NewWriter(fc.Conn)
err = fc.Conn.SetKeepAlive(true)
if err != nil {
Log.Error("error while setting keep alive")
}
go fc.ReadLine()
subSpotAllCmd := fmt.Sprintf("C%v|sub spot all", CommandNumber)
fc.Write(subSpotAllCmd)
CommandNumber++
clrSpotAllCmd := fmt.Sprintf("C%v|spot clear", CommandNumber)
fc.Write(clrSpotAllCmd)
CommandNumber++
Log.Debug("Subscribed to spot on FlexRadio and Deleted all spots from panadapter")
} else {
Log.Errorln("You must either turn FlexRadio Discovery on or provide an IP address for the Flex")
}
}
func (fc *FlexClient) SendSpottoFlex(spot TelnetSpot) {
freq := FreqMhztoHz(spot.Frequency)
flexSpot := FlexSpot{
CommandNumber: CommandNumber,
DX: spot.DX,
FrequencyMhz: freq,
FrequencyHz: spot.Frequency,
Band: spot.Band,
Mode: spot.Mode,
Source: "FlexDXCluster",
SpotterCallsign: spot.Spotter,
TimeStamp: time.Now().Unix(),
UTCTime: spot.Time,
LifeTime: Cfg.Flex.SpotLife,
Comment: spot.Comment,
Color: "#eaeaea",
BackgroundColor: "#000000",
Priority: "5",
NewDXCC: spot.NewDXCC,
NewBand: spot.NewBand,
NewMode: spot.NewMode,
Worked: spot.CallsignWorked,
}
flexSpot.Comment = flexSpot.Comment + " [" + flexSpot.Mode + "] [" + flexSpot.SpotterCallsign + "]"
// If new DXCC
if spot.NewDXCC {
flexSpot.Color = "#3bf908"
flexSpot.Priority = "1"
flexSpot.BackgroundColor = "#000000"
flexSpot.Comment = flexSpot.Comment + " [New DXCC]"
} else if spot.DX == Cfg.SQLite.Callsign {
flexSpot.Color = "#ff0000"
flexSpot.Priority = "1"
flexSpot.BackgroundColor = "#000000"
} else if spot.CallsignWorked {
flexSpot.Color = "#000000"
flexSpot.BackgroundColor = "#00c0c0"
flexSpot.Priority = "5"
flexSpot.Comment = flexSpot.Comment + " [Worked]"
} else if spot.NewMode && spot.NewBand {
flexSpot.Color = "#c603fc"
flexSpot.Priority = "1"
flexSpot.BackgroundColor = "#000000"
flexSpot.Comment = flexSpot.Comment + " [New Band & Mode]"
} else if spot.NewMode && !spot.NewBand {
flexSpot.Color = "#f9a908"
flexSpot.Priority = "2"
flexSpot.BackgroundColor = "#000000"
flexSpot.Comment = flexSpot.Comment + " [New Mode]"
} else if spot.NewBand && !spot.NewMode {
flexSpot.Color = "#f9f508"
flexSpot.Priority = "3"
flexSpot.BackgroundColor = "#000000"
flexSpot.Comment = flexSpot.Comment + " [New Band]"
} else if !spot.NewBand && !spot.NewMode && !spot.NewDXCC && !spot.CallsignWorked {
flexSpot.Color = "#eaeaea"
flexSpot.Priority = "5"
flexSpot.BackgroundColor = "#000000"
}
flexSpot.Comment = strings.ReplaceAll(flexSpot.Comment, " ", "\u00A0")
srcFlexSpot, err := fc.Repo.FindDXSameBand(flexSpot)
if err != nil {
Log.Debugf("could not find the DX in the database: ", err)
}
var stringSpot string
if srcFlexSpot.DX == "" {
fc.Repo.CreateSpot(flexSpot)
stringSpot = fmt.Sprintf("C%v|spot add rx_freq=%v callsign=%s mode=%s source=%s spotter_callsign=%s timestamp=%v lifetime_seconds=%s comment=%s color=%s background_color=%s priority=%s", flexSpot.CommandNumber, flexSpot.FrequencyMhz,
flexSpot.DX, flexSpot.Mode, flexSpot.Source, flexSpot.SpotterCallsign, flexSpot.TimeStamp, flexSpot.LifeTime, flexSpot.Comment, flexSpot.Color, flexSpot.BackgroundColor, flexSpot.Priority)
CommandNumber++
} else if srcFlexSpot.DX != "" && srcFlexSpot.Band == flexSpot.Band {
fc.Repo.UpdateSpotSameBand(flexSpot)
stringSpot = fmt.Sprintf("C%v|spot set %v rx_freq=%v callsign=%s mode=%s source=%s spotter_callsign=%s timestamp=%v lifetime_seconds=%s comment=%s color=%s background_color=%s priority=%s", flexSpot.CommandNumber, srcFlexSpot.FlexSpotNumber, flexSpot.FrequencyMhz,
flexSpot.DX, flexSpot.Mode, flexSpot.Source, flexSpot.SpotterCallsign, flexSpot.TimeStamp, flexSpot.LifeTime, flexSpot.Comment, flexSpot.Color, flexSpot.BackgroundColor, flexSpot.Priority)
CommandNumber++
} else if srcFlexSpot.DX != "" && srcFlexSpot.Band != flexSpot.Band {
fc.Repo.CreateSpot(flexSpot)
stringSpot = fmt.Sprintf("C%v|spot add rx_freq=%v callsign=%s mode=%s source=%s spotter_callsign=%s timestamp=%v lifetime_seconds=%s comment=%s color=%s background_color=%s priority=%s", flexSpot.CommandNumber, flexSpot.FrequencyMhz,
flexSpot.DX, flexSpot.Mode, flexSpot.Source, flexSpot.SpotterCallsign, flexSpot.TimeStamp, flexSpot.LifeTime, flexSpot.Comment, flexSpot.Color, flexSpot.BackgroundColor, flexSpot.Priority)
CommandNumber++
}
fc.SendSpot(stringSpot)
// Log.Debugf("Sending spot to FlexRadio: %s", stringSpot)
}
func (fc *FlexClient) SendSpot(stringSpot string) {
fc.Write(stringSpot)
}
func (fc *FlexClient) ReadLine() {
for {
message, err := fc.Reader.ReadString(byte('\n'))
if err != nil {
Log.Errorf("Error reading message from flexradio closing program: %s", err)
os.Exit(1)
}
// Log.Debugf("Received message from FlexRadio: %s", strings.Trim(message, "\n"))
regRespSpot := *regexp.MustCompile(`R(\d+)\|0\|(\d+)\n`)
respSpot := regRespSpot.FindStringSubmatch(message)
if len(respSpot) > 0 {
spot, _ := fc.Repo.FindSpotByCommandNumber(respSpot[1])
_, err := fc.Repo.UpdateFlexSpotNumberByID(respSpot[2], *spot)
if err != nil {
Log.Errorf("Could not update flex spot number in database: %s", err)
}
}
// Response when a spot is clicked
regTriggerSpot := *regexp.MustCompile(`.*spot (\d+) triggered.*\n`)
respTrigger := regTriggerSpot.FindStringSubmatch(message)
if len(respTrigger) > 0 {
spot, err := fc.Repo.FindSpotByFlexSpotNumber(respTrigger[1])
if err != nil {
Log.Errorf("could not find spot by flex spot number in database: %s", err)
}
msg := fmt.Sprintf(`To ALL de %s <%s> : Clicked on "%s" at %s`, Cfg.SQLite.Callsign, spot.UTCTime, spot.DX, spot.FrequencyHz)
if len(fc.TCPServer.Clients) > 0 {
fc.MsgChan <- msg
Log.Infof("%s clicked on spot \"%s\" at %s", Cfg.SQLite.Callsign, spot.DX, spot.FrequencyMhz)
}
}
// Status when a spot is deleted
regSpotDeleted := *regexp.MustCompile(`S\d+\|spot (\d+) removed`)
respDelete := regSpotDeleted.FindStringSubmatch(message)
if len(respDelete) > 0 {
// spot, _ := fc.Repo.FindSpotByFlexSpotNumber(respDelete[1])
fc.Repo.DeleteSpotByFlexSpotNumber(respDelete[1])
// Log.Debugf("Spot: DX: %s - Spotter: %s - Freq: %s - Band: %s - FlexID: %v deleted from database", spot.DX, spot.SpotterCallsign, spot.FrequencyHz, spot.Band, respDelete[1])
}
}
}
// Write sends raw data to remove telnet server
func (fc *FlexClient) Write(data string) (n int, err error) {
n, err = fc.Writer.Write([]byte(data + "\n"))
if err == nil {
err = fc.Writer.Flush()
}
return
}
func DiscoverFlexRadio() (bool, *Discovery) {
if Cfg.Flex.Discover {
Log.Infoln("FlexRadio Discovery is turned on...searching for radio on the network")
pc, err := net.ListenPacket("udp4", ":4992")
if err != nil {
Log.Errorln("Could not receive UDP packets to discover FlexRadio")
}
buf := make([]byte, 1024)
for {
n, _, err := pc.ReadFrom(buf)
if err != nil {
Log.Errorln("Could not read data on UDP port 4992")
}
discoverRe := regexp.MustCompile(`discovery_protocol_version=.*\smodel=(.*)\sserial=(.*)\sversion=(.*)\snickname=(.*)\scallsign=.*\sip=(.*)\sport=.*\s+`)
match := discoverRe.FindStringSubmatch(string(buf[:n]))
if len(match) > 0 {
d := &Discovery{
NickName: match[4],
Model: match[1],
Serial: match[2],
Version: match[3],
IP: match[5],
}
return true, d
}
}
} else {
Log.Infoln("FlexRadio Discovery is turned off...using IP provided in the config file")
}
return false, nil
}