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: "#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("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 }