340 lines
10 KiB
Go
340 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.")
|
|
fc.StartFlexClient()
|
|
}
|
|
} 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.Debugf("Trying to connect to FlexRadio at %s:%s", fc.Address, fc.Port)
|
|
|
|
fc.Conn, err = net.DialTCP("tcp", nil, addr)
|
|
if err != nil {
|
|
Log.Errorf("Could not connect to FlexRadio on %s", Cfg.Flex.IP)
|
|
Log.Error("Retrying to connect to FlexRadio in 5 seconds")
|
|
time.Sleep(time.Second * 5)
|
|
fc.StartFlexClient()
|
|
}
|
|
Log.Infof("Connected to FlexRadio 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: "#ffeaeaea",
|
|
BackgroundColor: "#ff000000",
|
|
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 = "#ff3bf908"
|
|
flexSpot.Priority = "1"
|
|
flexSpot.BackgroundColor = "#ff000000"
|
|
flexSpot.Comment = flexSpot.Comment + " [New DXCC]"
|
|
} else if spot.DX == Cfg.SQLite.Callsign {
|
|
flexSpot.Color = "#ffff0000"
|
|
flexSpot.Priority = "1"
|
|
flexSpot.BackgroundColor = "#ff000000"
|
|
} else if spot.CallsignWorked {
|
|
flexSpot.Color = "#ff000000"
|
|
flexSpot.BackgroundColor = "#ff00c0c0"
|
|
flexSpot.Priority = "5"
|
|
flexSpot.Comment = flexSpot.Comment + " [Worked]"
|
|
} else if spot.NewMode && spot.NewBand {
|
|
flexSpot.Color = "#ffc603fc"
|
|
flexSpot.Priority = "1"
|
|
flexSpot.BackgroundColor = "#ff000000"
|
|
flexSpot.Comment = flexSpot.Comment + " [New Band & Mode]"
|
|
} else if spot.NewMode && !spot.NewBand {
|
|
flexSpot.Color = "#fff9a908"
|
|
flexSpot.Priority = "2"
|
|
flexSpot.BackgroundColor = "#ff000000"
|
|
flexSpot.Comment = flexSpot.Comment + " [New Mode]"
|
|
} else if spot.NewBand && !spot.NewMode {
|
|
flexSpot.Color = "#fff9f508"
|
|
flexSpot.Priority = "3"
|
|
flexSpot.BackgroundColor = "#ff000000"
|
|
flexSpot.Comment = flexSpot.Comment + " [New Band]"
|
|
} else if !spot.NewBand && !spot.NewMode && !spot.NewDXCC && !spot.CallsignWorked {
|
|
flexSpot.Color = "#ffeaeaea"
|
|
flexSpot.Priority = "5"
|
|
flexSpot.BackgroundColor = "#ff000000"
|
|
}
|
|
|
|
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("udp", ":4992")
|
|
if err != nil {
|
|
Log.Errorf("Could not receive UDP packets to discover FlexRadio: ", err)
|
|
}
|
|
|
|
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=.*`)
|
|
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
|
|
}
|