package main import ( "bufio" "fmt" "net" "os" "regexp" "strings" "time" log "github.com/sirupsen/logrus" ) 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 FlexClient struct { Address string Port string Timeout time.Duration LogWriter *bufio.Writer Reader *bufio.Reader Writer *bufio.Writer Conn *net.TCPConn SpotChan chan TelnetSpot MsgChan chan string FlexSpotChan chan FlexSpot Repo FlexDXClusterRepository Log *log.Logger TCPServer TCPServer } func NewFlexClient(repo FlexDXClusterRepository, TCPServer TCPServer, log *log.Logger) *FlexClient { return &FlexClient{ Address: Cfg.Flex.IP, Port: "4992", SpotChan: make(chan TelnetSpot), FlexSpotChan: make(chan FlexSpot), MsgChan: TCPServer.MsgChan, Repo: repo, TCPServer: TCPServer, Log: log, } } func (fc *FlexClient) StartFlexClient() { var err error addr, err := net.ResolveTCPAddr("tcp", fc.Address+":"+fc.Port) if err != nil { fc.Log.Error("cannot resolve Telnet Client address:", err) } fc.LogWriter = bufio.NewWriter(os.Stdout) fc.Timeout = 600 * time.Second fc.Conn, err = net.DialTCP("tcp", nil, addr) if err != nil { fc.Log.Error("could not connect to flex radio, exiting...", err) os.Exit(1) } fc.Log.Infof("connected to flex radio at %s:%s", fc.Address, fc.Port) go func() { for message := range fc.SpotChan { fc.SendSpottoFlex(message) } }() fc.Reader = bufio.NewReader(fc.Conn) fc.Writer = bufio.NewWriter(fc.Conn) err = fc.Conn.SetKeepAlive(true) if err != nil { fc.Log.Error("error while setting keep alive:", err) } 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++ fc.Log.Info("Subscribed to spot on FlexRadio and Deleted all spots from panadapter") } 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, } // If new DXCC if spot.NewDXCC { flexSpot.Color = "#3bf908" flexSpot.Priority = "1" flexSpot.BackgroundColor = "#000000" } // if New Band if spot.NewBand { flexSpot.Color = "#f9f508" flexSpot.Priority = "2" flexSpot.BackgroundColor = "#000000" } // if New Mode if spot.NewMode { flexSpot.Color = "#f9a908" flexSpot.Priority = "2" flexSpot.BackgroundColor = "#000000" } // If not New DXCC nor Mode nor Band and never worked if !spot.NewBand && !spot.NewMode && !spot.NewDXCC && !spot.CallsignWorked { flexSpot.Color = "#eaeaea" flexSpot.Priority = "5" flexSpot.BackgroundColor = "#000000" } // If station worked already if spot.CallsignWorked { flexSpot.Color = "#000000" flexSpot.BackgroundColor = "#00c0c0" flexSpot.Priority = "5" } if spot.DX == Cfg.SQLite.Callsign { flexSpot.Color = "#ff0000" flexSpot.Priority = "2" flexSpot.BackgroundColor = "#000000" } flexSpot.Comment = strings.ReplaceAll(flexSpot.Comment, " ", "\u00A0") flexSpot.Comment = flexSpot.Comment + "\u00A0" + "[" + flexSpot.Mode + "] [" + flexSpot.SpotterCallsign + "]" srcFlexSpot, err := fc.Repo.FindDXSameBand(flexSpot) if err != nil { fc.Log.Error("could not find the DX in the database: ", err) } // send FlexSpot to HTTP Server fc.FlexSpotChan <- flexSpot 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++ fc.SendSpot(stringSpot) } else if srcFlexSpot.DX != "" && srcFlexSpot.Band == flexSpot.Band && srcFlexSpot.FrequencyMhz != flexSpot.FrequencyMhz { 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++ fc.SendSpot(stringSpot) } 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) } } func (fc *FlexClient) SendSpot(stringSpot string) { fc.Write(stringSpot) } func (fc *FlexClient) ReadLine() { for { message, err := fc.Reader.ReadString(byte('\n')) if err != nil { fc.Log.Errorf("error reading message from flexradio closing program: %s", err) os.Exit(1) } // fc.Log.Info(message) 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 { fc.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 { fc.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 fc.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]) fc.Log.Infof("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 }