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 } 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 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), 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 %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++ } 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", } if spot.NewDXCC { flexSpot.Color = "#3bf908" flexSpot.Priority = "1" flexSpot.BackgroundColor = "#000000" } if spot.NewBand || spot.NewMode && spot.Mode != "" { flexSpot.Color = "#f9f508" flexSpot.Priority = "2" flexSpot.BackgroundColor = "#000000" } if !spot.NewBand && !spot.NewMode && !spot.NewDXCC && !spot.CallsignWorked { flexSpot.Color = "#eaeaea" flexSpot.Priority = "5" flexSpot.BackgroundColor = "#000000" } if spot.CallsignWorked { flexSpot.Color = "#000000" flexSpot.BackgroundColor = "#00c0c0" flexSpot.Priority = "5" } flexSpot.Comment = strings.ReplaceAll(flexSpot.Comment, " ", "\u00A0") flexSpot.Comment = flexSpot.Comment + "\u00A0" + "[" + flexSpot.Mode + "]" srcFlexSpot, err := fc.Repo.FindDXSameBand(flexSpot) if err != nil { fc.Log.Error("could not find the DX in the database: ", err) } 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) 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.CommandNumber, flexSpot.FrequencyMhz, flexSpot.DX, flexSpot.Mode, flexSpot.Source, flexSpot.SpotterCallsign, flexSpot.TimeStamp, flexSpot.LifeTime, flexSpot.Comment, flexSpot.Color, flexSpot.BackgroundColor, flexSpot.Priority) CommandNumber++ stringDelete := fmt.Sprintf("C%v|spot remove %v", CommandNumber, srcFlexSpot.FlexSpotNumber) fc.DeleteAndSendSpot(stringSpot, stringDelete) } CommandNumber++ } func (fc *FlexClient) SendSpot(stringSpot string) { fc.Write(stringSpot) } func (fc *FlexClient) DeleteAndSendSpot(stringSpot string, deleteSpot string) { fc.Write(deleteSpot) 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) } // msgRaw := strings.TrimSpace(message) // fc.Log.Info(msgRaw) // Response when spot is added 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 { fc.Repo.DeleteSpotByFlexSpotNumber(respDelete[1]) fc.Log.Infof("deleted spot %v from database", 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 }