update
This commit is contained in:
parent
12cd1d9d14
commit
5db76a7c6e
119
HTTPServer.go
Normal file
119
HTTPServer.go
Normal file
@ -0,0 +1,119 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var tmpl *template.Template
|
||||
var listNew = []New{}
|
||||
|
||||
type New struct {
|
||||
DX string
|
||||
Status string
|
||||
NewDXCC bool
|
||||
NewMode bool
|
||||
NewBand bool
|
||||
}
|
||||
|
||||
type HTTPServer struct {
|
||||
router *mux.Router
|
||||
Log4OMRepo Log4OMContactsRepository
|
||||
Repo FlexDXClusterRepository
|
||||
Log *log.Logger
|
||||
FlexClient FlexClient
|
||||
}
|
||||
|
||||
func NewHTTPServer(cRepo Log4OMContactsRepository, fRepo FlexDXClusterRepository, fClient FlexClient, log *log.Logger) *HTTPServer {
|
||||
|
||||
gRouter := mux.NewRouter()
|
||||
|
||||
return &HTTPServer{
|
||||
router: gRouter,
|
||||
Log4OMRepo: cRepo,
|
||||
Repo: fRepo,
|
||||
Log: log,
|
||||
FlexClient: fClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HTTPServer) SetRoutes() {
|
||||
s.router.HandleFunc("/", s.Homepage)
|
||||
s.router.HandleFunc("/spots", s.GetSpots).Methods("GET")
|
||||
s.router.HandleFunc("/spotscount", s.GetSpotsCount).Methods("GET")
|
||||
s.router.HandleFunc("/spotters", s.GetSpotters).Methods("GET")
|
||||
s.router.HandleFunc("/new", s.GetNew).Methods("GET")
|
||||
}
|
||||
|
||||
func (s *HTTPServer) StartHTTPServer() {
|
||||
|
||||
go func() {
|
||||
for spot := range s.FlexClient.FlexSpotChan {
|
||||
s.GetListofNew(spot)
|
||||
}
|
||||
}()
|
||||
|
||||
tmpl, _ = template.ParseGlob("templates/*.html")
|
||||
s.SetRoutes()
|
||||
s.Log.Infof("starting HTTP server on %s:%s", Cfg.HTTPServer.Host, Cfg.HTTPServer.Port)
|
||||
err := http.ListenAndServe(Cfg.HTTPServer.Host+":"+Cfg.HTTPServer.Port, s.router)
|
||||
if err != nil {
|
||||
s.Log.Warn("cannot start HTTP server: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HTTPServer) Homepage(w http.ResponseWriter, r *http.Request) {
|
||||
err := tmpl.ExecuteTemplate(w, "home.html", nil)
|
||||
if err != nil {
|
||||
s.Log.Error("error executing home template: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HTTPServer) GetSpots(w http.ResponseWriter, r *http.Request) {
|
||||
spots := s.Repo.GetAllSpots("25")
|
||||
tmpl.ExecuteTemplate(w, "spot", spots)
|
||||
}
|
||||
|
||||
func (s *HTTPServer) GetSpotsCount(w http.ResponseWriter, r *http.Request) {
|
||||
spots := s.Repo.GetAllSpots("0")
|
||||
count := len(spots)
|
||||
tmpl.ExecuteTemplate(w, "spotCount", count)
|
||||
}
|
||||
|
||||
func (s *HTTPServer) GetSpotters(w http.ResponseWriter, r *http.Request) {
|
||||
spotters := s.Repo.GetSpotters()
|
||||
tmpl.ExecuteTemplate(w, "spotters", spotters)
|
||||
}
|
||||
|
||||
func (s *HTTPServer) GetNew(w http.ResponseWriter, r *http.Request) {
|
||||
tmpl.ExecuteTemplate(w, "new", listNew)
|
||||
}
|
||||
|
||||
func (s *HTTPServer) GetListofNew(spot FlexSpot) {
|
||||
new := New{}
|
||||
|
||||
new.DX = spot.DX
|
||||
|
||||
if spot.NewDXCC {
|
||||
new.Status = fmt.Sprintf("New DXCC (%s) (%s)", spot.Band, spot.Mode)
|
||||
new.NewDXCC = true
|
||||
} else if !spot.NewBand && spot.NewMode && spot.Mode != "" {
|
||||
new.Status = fmt.Sprintf("New Mode (%s) (%s)", spot.Band, spot.Mode)
|
||||
new.NewMode = true
|
||||
} else if spot.NewBand && spot.NewMode && spot.Mode != "" {
|
||||
new.Status = fmt.Sprintf("New Band (%s) & Mode (%s)", spot.Band, spot.Mode)
|
||||
new.NewBand = true
|
||||
new.NewMode = true
|
||||
}
|
||||
|
||||
if new.Status != "" {
|
||||
if len(listNew) > 10 {
|
||||
listNew = append(listNew[:0], listNew[1:]...)
|
||||
}
|
||||
listNew = append(listNew, new)
|
||||
}
|
||||
}
|
171
TCPClient.go
Normal file
171
TCPClient.go
Normal file
@ -0,0 +1,171 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var spotRe *regexp.Regexp = regexp.MustCompile(`DX\sde\s([\w\d]+).*:\s+(\d+.\d)\s+([\w\d]+)\s+(CW|SSB|FT8|FT4|RTTY|USB|LSB)?\s+(.*)\s\s\s+([\d]+\w{1})`)
|
||||
var count int = 0
|
||||
|
||||
type TCPClient struct {
|
||||
Login string
|
||||
Password string
|
||||
Address string
|
||||
Port string
|
||||
Timeout time.Duration
|
||||
LogWriter *bufio.Writer
|
||||
Reader *bufio.Reader
|
||||
Writer *bufio.Writer
|
||||
Conn *net.TCPConn
|
||||
TCPServer TCPServer
|
||||
FlexClient FlexClient
|
||||
MsgChan chan string
|
||||
CmdChan chan string
|
||||
SpotChan chan TelnetSpot
|
||||
Log *log.Logger
|
||||
Config *Config
|
||||
}
|
||||
|
||||
func NewTCPClient(TCPServer *TCPServer, FlexClient *FlexClient, log *log.Logger) *TCPClient {
|
||||
return &TCPClient{
|
||||
Address: Cfg.Cluster.Server,
|
||||
Port: Cfg.Cluster.Port,
|
||||
Login: Cfg.Cluster.Login,
|
||||
MsgChan: TCPServer.MsgChan,
|
||||
CmdChan: TCPServer.CmdChan,
|
||||
SpotChan: FlexClient.SpotChan,
|
||||
Log: log,
|
||||
TCPServer: *TCPServer,
|
||||
FlexClient: *FlexClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TCPClient) setDefaultParams() {
|
||||
if c.Timeout == 0 {
|
||||
c.Timeout = 600 * time.Second
|
||||
}
|
||||
if c.LogWriter == nil {
|
||||
c.LogWriter = bufio.NewWriter(os.Stdout)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TCPClient) StartClient() {
|
||||
var err error
|
||||
|
||||
addr, err := net.ResolveTCPAddr("tcp", c.Address+":"+c.Port)
|
||||
if err != nil {
|
||||
c.Log.Error("cannot resolve Telnet Client address:", err)
|
||||
}
|
||||
|
||||
c.setDefaultParams()
|
||||
c.Conn, err = net.DialTCP("tcp", nil, addr)
|
||||
if err != nil {
|
||||
c.Log.Error("cannot connect to Telnet Client:", err)
|
||||
}
|
||||
c.Log.Infof("connected to DX cluster %s:%s", c.Address, c.Port)
|
||||
|
||||
err = c.Conn.SetKeepAlive(true)
|
||||
if err != nil {
|
||||
c.Log.Error("error while setting keep alive:", err)
|
||||
}
|
||||
|
||||
c.Reader = bufio.NewReader(c.Conn)
|
||||
c.Writer = bufio.NewWriter(c.Conn)
|
||||
|
||||
go func() {
|
||||
for message := range c.TCPServer.CmdChan {
|
||||
c.Log.Infof("Received DX Command: %s", message)
|
||||
message := message + "\n"
|
||||
c.WriteString(message)
|
||||
}
|
||||
}()
|
||||
|
||||
go c.ReadLine()
|
||||
}
|
||||
|
||||
func (c *TCPClient) Close() {
|
||||
c.Writer.WriteString("bye")
|
||||
}
|
||||
|
||||
func (c *TCPClient) SetFilters() {
|
||||
if Cfg.Cluster.FT8 {
|
||||
c.Write([]byte("set/ft8\r\n"))
|
||||
c.Log.Debug("FT8 is on as defined in the config file")
|
||||
}
|
||||
|
||||
if Cfg.Cluster.Skimmer {
|
||||
c.Write([]byte("set/skimmer\r\n"))
|
||||
c.Log.Debug("Skimmer is on as defined in the config file")
|
||||
}
|
||||
|
||||
if !Cfg.Cluster.FT8 {
|
||||
c.Write([]byte("set/noft8\r\n"))
|
||||
c.Log.Debug("FT8 is off as defined in the config file")
|
||||
}
|
||||
|
||||
if !Cfg.Cluster.Skimmer {
|
||||
c.Write([]byte("set/noskimmer\r\n"))
|
||||
c.Log.Debug("Skimmer is off as defined in the config file")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TCPClient) ReadLine() {
|
||||
|
||||
for {
|
||||
message, err := c.Reader.ReadString('\n')
|
||||
if err != nil {
|
||||
c.Log.Errorf("Error reading message: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(message, "Login: \r\n") || strings.Contains(message, "Please enter your call: \r\n") {
|
||||
c.Log.Debug("Found login prompt...sending callsign")
|
||||
c.Write([]byte(c.Login + "\r\n"))
|
||||
time.Sleep(time.Second * 2)
|
||||
c.SetFilters()
|
||||
time.Sleep(time.Second * 1)
|
||||
if Cfg.Cluster.Command != "" {
|
||||
c.WriteString(Cfg.Cluster.Command)
|
||||
}
|
||||
c.Log.Info("start receiving spots")
|
||||
}
|
||||
|
||||
ProcessTelnetSpot(spotRe, message, c.SpotChan, c.Log)
|
||||
|
||||
// Send the spot message to TCP server
|
||||
if len(c.TCPServer.Clients) > 0 {
|
||||
if count == 0 {
|
||||
// wait 3 seconds before sending messages to allow the client to connect
|
||||
time.Sleep(time.Second * 3)
|
||||
count++
|
||||
}
|
||||
c.MsgChan <- message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write sends raw data to remove telnet server
|
||||
func (tc *TCPClient) Write(data []byte) (n int, err error) {
|
||||
n, err = tc.Writer.Write(data)
|
||||
if err == nil {
|
||||
err = tc.Writer.Flush()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (tc *TCPClient) WriteString(data string) (n int, err error) {
|
||||
n, err = tc.Writer.Write([]byte(data))
|
||||
if err == nil {
|
||||
err = tc.Writer.Flush()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
129
TCPServer.go
Normal file
129
TCPServer.go
Normal file
@ -0,0 +1,129 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
err error
|
||||
)
|
||||
|
||||
type TCPServer struct {
|
||||
Address string
|
||||
Port string
|
||||
Clients map[net.Conn]bool
|
||||
Mutex *sync.Mutex
|
||||
LogWriter *bufio.Writer
|
||||
Reader *bufio.Reader
|
||||
Writer *bufio.Writer
|
||||
Conn net.Conn
|
||||
Listener net.Listener
|
||||
MsgChan chan string
|
||||
CmdChan chan string
|
||||
Log *log.Logger
|
||||
Config *Config
|
||||
}
|
||||
|
||||
func NewTCPServer(address string, port string, log *log.Logger) *TCPServer {
|
||||
return &TCPServer{
|
||||
Address: address,
|
||||
Port: port,
|
||||
Clients: make(map[net.Conn]bool),
|
||||
MsgChan: make(chan string),
|
||||
CmdChan: make(chan string),
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TCPServer) StartServer() {
|
||||
s.LogWriter = bufio.NewWriter(os.Stdout)
|
||||
s.Listener, err = net.Listen("tcp", Cfg.TelnetServer.Host+":"+Cfg.TelnetServer.Port)
|
||||
if err != nil {
|
||||
s.Log.Info("could not create telnet server")
|
||||
}
|
||||
|
||||
defer s.Listener.Close()
|
||||
|
||||
s.Log.Infof("telnet server listening on %s:%s", Cfg.TelnetServer.Host, Cfg.TelnetServer.Port)
|
||||
|
||||
go func() {
|
||||
for message := range s.MsgChan {
|
||||
s.broadcastMessage(message)
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
s.Conn, err = s.Listener.Accept()
|
||||
s.Log.Info("client connected", s.Conn.RemoteAddr().String())
|
||||
if err != nil {
|
||||
s.Log.Error("could not accept connections to telnet server")
|
||||
continue
|
||||
}
|
||||
s.Mutex.Lock()
|
||||
s.Clients[s.Conn] = true
|
||||
s.Mutex.Unlock()
|
||||
|
||||
go s.handleConnection()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TCPServer) handleConnection() {
|
||||
s.Conn.Write([]byte("Welcome to the FlexDXCluster telnet server! Type 'bye' to exit.\n"))
|
||||
|
||||
s.Reader = bufio.NewReader(s.Conn)
|
||||
s.Writer = bufio.NewWriter(s.Conn)
|
||||
|
||||
for {
|
||||
|
||||
message, err := s.Reader.ReadString('\n')
|
||||
if err != nil {
|
||||
s.Mutex.Lock()
|
||||
delete(s.Clients, s.Conn)
|
||||
s.Mutex.Unlock()
|
||||
s.Log.Infof("client %s disconnected", s.Conn.RemoteAddr().String())
|
||||
return
|
||||
}
|
||||
|
||||
message = strings.TrimSpace(message)
|
||||
|
||||
// if message is by then disconnect
|
||||
if message == "bye" {
|
||||
s.Mutex.Lock()
|
||||
delete(s.Clients, s.Conn)
|
||||
s.Mutex.Unlock()
|
||||
s.Log.Infof("client %s disconnected", s.Conn.RemoteAddr().String())
|
||||
}
|
||||
|
||||
if strings.Contains(message, "DX") && message != "SH/DX 30" {
|
||||
// send DX spot to the client
|
||||
s.CmdChan <- message
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TCPServer) Write(message string) (n int, err error) {
|
||||
_, err = s.Writer.Write([]byte(message))
|
||||
if err == nil {
|
||||
err = s.Writer.Flush()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *TCPServer) broadcastMessage(message string) {
|
||||
s.Mutex.Lock()
|
||||
defer s.Mutex.Unlock()
|
||||
for client := range s.Clients {
|
||||
_, err := client.Write([]byte(message))
|
||||
if err != nil {
|
||||
fmt.Println("error while sending message to clients:", client.RemoteAddr())
|
||||
}
|
||||
}
|
||||
}
|
24
clublog.go
Normal file
24
clublog.go
Normal file
@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func CheckClubogDXCC(callsign string) (string, error) {
|
||||
// Clublog check DXCC
|
||||
clublogURL := "https://clublog.org/dxcc?call=" + callsign + "&api=5767f19333363a9ef432ee9cd4141fe76b8adf38"
|
||||
resp, err := http.Get(clublogURL)
|
||||
if err != nil {
|
||||
fmt.Println("error while getting DXCC from Clublog")
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Println("could not get dxcc from clublog", err)
|
||||
}
|
||||
return string(body), nil
|
||||
}
|
82
config.go
Normal file
82
config.go
Normal file
@ -0,0 +1,82 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var Cfg *Config
|
||||
|
||||
type Config struct {
|
||||
General struct {
|
||||
DeleteLogFileAtStart bool `yaml:"delete_log_file_at_start"`
|
||||
LogToFile bool `yaml:"log_to_file"`
|
||||
LogLevel string `yaml:"log_level"`
|
||||
HTTPServer bool `yaml:"httpserver"`
|
||||
TelnetServer bool `yaml:"telnetserver"`
|
||||
FlexRadioSpot bool `yaml:"flexradiospot"`
|
||||
} `yaml:"general"`
|
||||
SQLite struct {
|
||||
SQLitePath string `yaml:"sqlite_path"`
|
||||
Callsign string `yaml:"callsign"`
|
||||
} `yaml:"sqlite"`
|
||||
|
||||
Cluster struct {
|
||||
Server string `yaml:"server"`
|
||||
Port string `yaml:"port"`
|
||||
Login string `yaml:"login"`
|
||||
Skimmer bool `yaml:"skimmer"`
|
||||
FT8 bool `yaml:"ft8"`
|
||||
Command string `yanl:"command"`
|
||||
} `yaml:"cluster"`
|
||||
|
||||
Flex struct {
|
||||
IP string `yaml:"ip"`
|
||||
SpotLife string `yaml:"spot_life"`
|
||||
} `yaml:"flex"`
|
||||
|
||||
Clublog struct {
|
||||
Api string `yaml:"api"`
|
||||
} `yaml:"clublog"`
|
||||
|
||||
TelnetServer struct {
|
||||
Host string `yaml:"host"`
|
||||
Port string `yaml:"port"`
|
||||
} `yaml:"telnetserver"`
|
||||
|
||||
HTTPServer struct {
|
||||
Host string `yaml:"host"`
|
||||
Port string `yaml:"port"`
|
||||
} `yaml:"httpserver"`
|
||||
}
|
||||
|
||||
func NewConfig(configPath string) *Config {
|
||||
Cfg = &Config{}
|
||||
|
||||
file, err := os.Open(configPath)
|
||||
if err != nil {
|
||||
log.Println("could not open config file")
|
||||
}
|
||||
defer file.Close()
|
||||
d := yaml.NewDecoder(file)
|
||||
|
||||
if err := d.Decode(&Cfg); err != nil {
|
||||
log.Println("could not decod config file")
|
||||
}
|
||||
|
||||
return Cfg
|
||||
}
|
||||
|
||||
func ValidateConfigPath(path string) error {
|
||||
s, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s.IsDir() {
|
||||
return fmt.Errorf("'%s' is a directory, not a normal file", path)
|
||||
}
|
||||
return nil
|
||||
}
|
386
database.go
Normal file
386
database.go
Normal file
@ -0,0 +1,386 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Contact struct {
|
||||
Callsign string
|
||||
Band string
|
||||
Mode string
|
||||
DXCC string
|
||||
StationCallsign string
|
||||
Country string
|
||||
}
|
||||
|
||||
type Spotter struct {
|
||||
Spotter string
|
||||
NumberofSpots string
|
||||
}
|
||||
|
||||
type Log4OMContactsRepository struct {
|
||||
db *sql.DB
|
||||
Log *log.Logger
|
||||
}
|
||||
|
||||
type FlexDXClusterRepository struct {
|
||||
db *sql.DB
|
||||
Log *log.Logger
|
||||
}
|
||||
|
||||
func NewLog4OMContactsRepository(filePath string, log *log.Logger) *Log4OMContactsRepository {
|
||||
db, err := sql.Open("sqlite3", filePath)
|
||||
if err != nil {
|
||||
fmt.Println("Cannot open db", err)
|
||||
}
|
||||
return &Log4OMContactsRepository{
|
||||
db: db,
|
||||
Log: log}
|
||||
}
|
||||
|
||||
func NewFlexDXDatabase(filePath string, log *log.Logger) *FlexDXClusterRepository {
|
||||
|
||||
db, err := sql.Open("sqlite3", filePath)
|
||||
if err != nil {
|
||||
fmt.Println("Cannot open db", err)
|
||||
}
|
||||
|
||||
log.Info("Opening SQLite database")
|
||||
|
||||
_, err = db.ExecContext(
|
||||
context.Background(),
|
||||
`CREATE TABLE IF NOT EXISTS "spots" (
|
||||
"id" INTEGER NOT NULL UNIQUE,
|
||||
"commandNumber" INTEGER NOT NULL UNIQUE,
|
||||
"flexSpotNumber" INTEGER,
|
||||
"dx" TEXT NOT NULL,
|
||||
"freqMhz" TEXT,
|
||||
"freqHz" TEXT,
|
||||
"band" TEXT,
|
||||
"mode" TEXT,
|
||||
"spotter" INTEGER,
|
||||
"flexMode" TEXT,
|
||||
"source" TEXT,
|
||||
"time" TEXT,
|
||||
"timestamp" INTEGER,
|
||||
"lifeTime" TEXT,
|
||||
"priority" TEXT,
|
||||
"comment" TEXT,
|
||||
"color" TEXT,
|
||||
"backgroundColor" INTEGER,
|
||||
PRIMARY KEY("id" AUTOINCREMENT)
|
||||
)`,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Warn("Cannot create table", err)
|
||||
}
|
||||
|
||||
return &FlexDXClusterRepository{
|
||||
db: db,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Log4OMContactsRepository) ListByCountry(countryID string) ([]*Contact, error) {
|
||||
rows, err := r.db.Query("SELECT callsign, band, mode, dxcc, stationcallsign, country FROM log WHERE dxcc = ?", countryID)
|
||||
if err != nil {
|
||||
log.Error("could not query database", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
contacts := []*Contact{}
|
||||
for rows.Next() {
|
||||
c := Contact{}
|
||||
if err := rows.Scan(&c.Callsign, &c.Band, &c.Mode, &c.DXCC, &c.StationCallsign, &c.Country); err != nil {
|
||||
log.Error("could not query database", err)
|
||||
return nil, err
|
||||
}
|
||||
contacts = append(contacts, &c)
|
||||
}
|
||||
return contacts, nil
|
||||
}
|
||||
|
||||
func (r *Log4OMContactsRepository) ListByCountryMode(countryID string, mode string) ([]*Contact, error) {
|
||||
|
||||
if mode == "USB" || mode == "LSB" {
|
||||
|
||||
rows, err := r.db.Query("SELECT callsign, band, mode, dxcc, stationcallsign, country FROM log WHERE dxcc = ? AND (mode = ? OR mode = ?)", countryID, "USB", "LSB")
|
||||
if err != nil {
|
||||
log.Error("could not query database", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
contacts := []*Contact{}
|
||||
for rows.Next() {
|
||||
c := Contact{}
|
||||
if err := rows.Scan(&c.Callsign, &c.Band, &c.Mode, &c.DXCC, &c.StationCallsign, &c.Country); err != nil {
|
||||
log.Error("could not query database", err)
|
||||
return nil, err
|
||||
}
|
||||
contacts = append(contacts, &c)
|
||||
}
|
||||
return contacts, nil
|
||||
|
||||
} else {
|
||||
|
||||
rows, err := r.db.Query("SELECT callsign, band, mode, dxcc, stationcallsign, country FROM log WHERE dxcc = ? AND mode = ?", countryID, mode)
|
||||
if err != nil {
|
||||
log.Error("could not query the database", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
contacts := []*Contact{}
|
||||
for rows.Next() {
|
||||
c := Contact{}
|
||||
if err := rows.Scan(&c.Callsign, &c.Band, &c.Mode, &c.DXCC, &c.StationCallsign, &c.Country); err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
contacts = append(contacts, &c)
|
||||
}
|
||||
return contacts, nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Log4OMContactsRepository) ListByCountryBand(countryID string, band string) ([]*Contact, error) {
|
||||
rows, err := r.db.Query("SELECT callsign, band, mode, dxcc, stationcallsign, country FROM log WHERE dxcc = ? AND band = ?", countryID, band)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
contacts := []*Contact{}
|
||||
for rows.Next() {
|
||||
c := Contact{}
|
||||
if err := rows.Scan(&c.Callsign, &c.Band, &c.Mode, &c.DXCC, &c.StationCallsign, &c.Country); err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
contacts = append(contacts, &c)
|
||||
}
|
||||
return contacts, nil
|
||||
}
|
||||
|
||||
func (r *Log4OMContactsRepository) ListByCallSign(callSign string, band string, mode string) ([]*Contact, error) {
|
||||
rows, err := r.db.Query("SELECT callsign, band, mode, dxcc, stationcallsign, country FROM log WHERE callsign = ? AND band = ? AND mode = ?", callSign, band, mode)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
contacts := []*Contact{}
|
||||
for rows.Next() {
|
||||
c := Contact{}
|
||||
if err := rows.Scan(&c.Callsign, &c.Band, &c.Mode, &c.DXCC, &c.StationCallsign, &c.Country); err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
contacts = append(contacts, &c)
|
||||
}
|
||||
return contacts, nil
|
||||
}
|
||||
|
||||
func (r *FlexDXClusterRepository) GetAllSpots(limit string) []FlexSpot {
|
||||
|
||||
Spots := []FlexSpot{}
|
||||
|
||||
var query string
|
||||
|
||||
if limit == "0" {
|
||||
query = "SELECT * from spots ORDER BY id DESC"
|
||||
} else {
|
||||
query = fmt.Sprintf("SELECT * from spots ORDER BY id DESC LIMIT %s", limit)
|
||||
}
|
||||
|
||||
rows, err := r.db.Query(query)
|
||||
|
||||
if err != nil {
|
||||
r.Log.Error(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
s := FlexSpot{}
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&s.ID, &s.CommandNumber, &s.FlexSpotNumber, &s.DX, &s.FrequencyMhz, &s.FrequencyHz, &s.Band, &s.Mode, &s.SpotterCallsign, &s.FlexMode, &s.Source, &s.UTCTime, &s.TimeStamp, &s.LifeTime, &s.Priority,
|
||||
&s.Comment, &s.Color, &s.BackgroundColor); err != nil {
|
||||
fmt.Println(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
Spots = append(Spots, s)
|
||||
}
|
||||
|
||||
return Spots
|
||||
}
|
||||
|
||||
func (r *FlexDXClusterRepository) GetSpotters() []Spotter {
|
||||
|
||||
sList := []Spotter{}
|
||||
|
||||
rows, err := r.db.Query("select spotter, count(*) as occurences from spots group by spotter order by occurences desc, spotter limit 7")
|
||||
|
||||
if err != nil {
|
||||
r.Log.Error(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
s := Spotter{}
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&s.Spotter, &s.NumberofSpots); err != nil {
|
||||
fmt.Println(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
sList = append(sList, s)
|
||||
}
|
||||
|
||||
return sList
|
||||
}
|
||||
|
||||
func (r *FlexDXClusterRepository) FindDXSameBand(spot FlexSpot) (*FlexSpot, error) {
|
||||
rows, err := r.db.Query("SELECT * from spots WHERE dx = ? AND band = ?", spot.DX, spot.Band)
|
||||
if err != nil {
|
||||
r.Log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
s := FlexSpot{}
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&s.ID, &s.CommandNumber, &s.FlexSpotNumber, &s.DX, &s.FrequencyMhz, &s.FrequencyHz, &s.Band, &s.Mode, &s.SpotterCallsign, &s.FlexMode, &s.Source, &s.UTCTime, &s.TimeStamp, &s.LifeTime, &s.Priority,
|
||||
&s.Comment, &s.Color, &s.BackgroundColor); err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func (r *FlexDXClusterRepository) CreateSpot(spot FlexSpot) {
|
||||
query := "INSERT INTO `spots` (`commandNumber`, `flexSpotNumber`, `dx`, `freqMhz`, `freqHz`, `band`, `mode`, `spotter`, `flexMode`, `source`, `time`, `timestamp`, `lifeTime`, `priority`, `comment`, `color`, `backgroundColor`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
insertResult, err := r.db.ExecContext(context.Background(), query, spot.CommandNumber, spot.CommandNumber, spot.DX, spot.FrequencyMhz, spot.FrequencyHz, spot.Band, spot.Mode, spot.SpotterCallsign, spot.FlexMode, spot.Source, spot.UTCTime, time.Now().Unix(), spot.LifeTime, spot.Priority, spot.Comment, spot.Color, spot.BackgroundColor)
|
||||
if err != nil {
|
||||
log.Errorf("cannot insert spot in database: %s", err)
|
||||
}
|
||||
|
||||
_, err = insertResult.LastInsertId()
|
||||
if err != nil {
|
||||
log.Errorf("impossible to retrieve last inserted id: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (r *FlexDXClusterRepository) UpdateSpotSameBand(spot FlexSpot) error {
|
||||
_, err := r.db.Exec(`UPDATE spots SET commandNumber = ?, DX = ?, freqMhz = ?, freqHz = ?, band = ?, mode = ?, spotter = ?, flexMode = ?, source = ?, time = ?, timestamp = ?, lifeTime = ?, priority = ?, comment = ?, color = ?, backgroundColor = ? WHERE DX = ? AND band = ?`,
|
||||
spot.CommandNumber, spot.DX, spot.FrequencyMhz, spot.FrequencyHz, spot.Band, spot.Mode, spot.SpotterCallsign, spot.FlexMode, spot.Source, spot.UTCTime, spot.TimeStamp, spot.LifeTime, spot.Priority, spot.Comment, spot.Color, spot.BackgroundColor, spot.DX, spot.Band)
|
||||
if err != nil {
|
||||
r.Log.Errorf("could not update database: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *FlexDXClusterRepository) FindSpotByCommandNumber(commandNumber string) (*FlexSpot, error) {
|
||||
rows, err := r.db.Query("SELECT * from spots WHERE commandNumber = ?", commandNumber)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
s := FlexSpot{}
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&s.ID, &s.CommandNumber, &s.FlexSpotNumber, &s.DX, &s.FrequencyMhz, &s.FrequencyHz, &s.Band, &s.Mode, &s.SpotterCallsign, &s.FlexMode, &s.Source, &s.UTCTime, &s.TimeStamp, &s.LifeTime, &s.Priority,
|
||||
&s.Comment, &s.Color, &s.BackgroundColor); err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func (r *FlexDXClusterRepository) FindSpotByFlexSpotNumber(spotNumber string) (*FlexSpot, error) {
|
||||
rows, err := r.db.Query("SELECT * from spots WHERE flexSpotNumber = ?", spotNumber)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
s := FlexSpot{}
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&s.ID, &s.CommandNumber, &s.FlexSpotNumber, &s.DX, &s.FrequencyMhz, &s.FrequencyHz, &s.Band, &s.Mode, &s.SpotterCallsign, &s.FlexMode, &s.Source, &s.UTCTime, &s.TimeStamp, &s.LifeTime, &s.Priority,
|
||||
&s.Comment, &s.Color, &s.BackgroundColor); err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func (r *FlexDXClusterRepository) UpdateFlexSpotNumberByID(flexSpotNumber string, spot FlexSpot) (*FlexSpot, error) {
|
||||
flexSpotNumberInt, _ := strconv.Atoi(flexSpotNumber)
|
||||
rows, err := r.db.Query(`UPDATE spots SET flexSpotNumber = ? WHERE id = ? RETURNING *`, flexSpotNumberInt, spot.ID)
|
||||
if err != nil {
|
||||
r.Log.Errorf("could not update database: %s", err)
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
s := FlexSpot{}
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&s.ID, &s.CommandNumber, &s.FlexSpotNumber, &s.DX, &s.FrequencyMhz, &s.FrequencyHz, &s.Band, &s.Mode, &s.SpotterCallsign, &s.FlexMode, &s.Source, &s.UTCTime, &s.TimeStamp, &s.LifeTime, &s.Priority,
|
||||
&s.Comment, &s.Color, &s.BackgroundColor); err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func (r *FlexDXClusterRepository) DeleteSpotByFlexSpotNumber(flexSpotNumber string) {
|
||||
flexSpotNumberInt, _ := strconv.Atoi(flexSpotNumber)
|
||||
query := "DELETE from spots WHERE flexSpotNumber = ?"
|
||||
_, err := r.db.Exec(query, flexSpotNumberInt)
|
||||
if err != nil {
|
||||
r.Log.Errorf("could not delete spot %v from database", flexSpotNumberInt)
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteDatabase(filePath string, log *log.Logger) {
|
||||
_, err := os.Stat(filePath)
|
||||
if !os.IsNotExist(err) {
|
||||
err := os.Remove(filePath)
|
||||
if err != nil {
|
||||
log.Error("could not delete existing database")
|
||||
}
|
||||
log.Info("deleting existing database")
|
||||
}
|
||||
}
|
268
flexradio.go
Normal file
268
flexradio.go
Normal file
@ -0,0 +1,268 @@
|
||||
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.Debug("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"
|
||||
} 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"
|
||||
} else if spot.NewMode {
|
||||
flexSpot.Color = "#f9a908"
|
||||
flexSpot.Priority = "1"
|
||||
flexSpot.BackgroundColor = "#000000"
|
||||
} else if spot.NewBand {
|
||||
flexSpot.Color = "#f9f508"
|
||||
flexSpot.Priority = "1"
|
||||
flexSpot.BackgroundColor = "#000000"
|
||||
} else if !spot.NewBand && !spot.NewMode && !spot.NewDXCC && !spot.CallsignWorked {
|
||||
flexSpot.Color = "#eaeaea"
|
||||
flexSpot.Priority = "5"
|
||||
flexSpot.BackgroundColor = "#000000"
|
||||
}
|
||||
|
||||
flexSpot.Comment = flexSpot.Comment + "[" + flexSpot.Mode + "] [" + flexSpot.SpotterCallsign + "]"
|
||||
flexSpot.Comment = strings.ReplaceAll(flexSpot.Comment, " ", "\u00A0")
|
||||
|
||||
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
|
||||
|
||||
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 && 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++
|
||||
|
||||
} 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++
|
||||
|
||||
}
|
||||
|
||||
if Cfg.General.FlexRadioSpot {
|
||||
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
|
||||
}
|
70
log.go
Normal file
70
log.go
Normal file
@ -0,0 +1,70 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
prefixed "github.com/x-cray/logrus-prefixed-formatter"
|
||||
)
|
||||
|
||||
func NewLog() *log.Logger {
|
||||
|
||||
if Cfg.General.DeleteLogFileAtStart {
|
||||
if _, err := os.Stat("flexradio.log"); err == nil {
|
||||
os.Remove("flexradio.log")
|
||||
}
|
||||
}
|
||||
|
||||
var w io.Writer
|
||||
if Cfg.General.LogToFile {
|
||||
f, err := os.OpenFile("flexradio.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
w = io.MultiWriter(os.Stdout, f)
|
||||
} else {
|
||||
w = io.Writer(os.Stdout)
|
||||
}
|
||||
|
||||
l := &log.Logger{
|
||||
Out: w,
|
||||
Formatter: &prefixed.TextFormatter{
|
||||
DisableColors: false,
|
||||
TimestampFormat: "02-01-2006 15:04:05",
|
||||
FullTimestamp: true,
|
||||
ForceFormatting: true,
|
||||
},
|
||||
}
|
||||
|
||||
if Cfg.General.LogLevel == "DEBUG" {
|
||||
l.Level = log.DebugLevel
|
||||
} else if Cfg.General.LogLevel == "INFO" {
|
||||
l.Level = log.InfoLevel
|
||||
} else if Cfg.General.LogLevel == "WARN" {
|
||||
l.Level = log.WarnLevel
|
||||
} else {
|
||||
l.Level = log.InfoLevel
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// Info ...
|
||||
func Info(format string, v ...interface{}) {
|
||||
log.Infof(format, v...)
|
||||
}
|
||||
|
||||
// Warn ...
|
||||
func Warn(format string, v ...interface{}) {
|
||||
log.Warnf(format, v...)
|
||||
}
|
||||
|
||||
// Error ...
|
||||
func Error(format string, v ...interface{}) {
|
||||
log.Errorf(format, v...)
|
||||
}
|
||||
|
||||
func Debug(format string, v ...interface{}) {
|
||||
log.Debugf(format, v...)
|
||||
}
|
87
main.go
Normal file
87
main.go
Normal file
@ -0,0 +1,87 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func ParseFlags() (string, error) {
|
||||
// String that contains the configured configuration path
|
||||
var configPath string
|
||||
|
||||
// Set up a CLI flag called "-config" to allow users
|
||||
// to supply the configuration file
|
||||
flag.StringVar(&configPath, "config", "./config.yml", "path to config file")
|
||||
|
||||
// Actually parse the flags
|
||||
flag.Parse()
|
||||
|
||||
// Validate the path first
|
||||
if err := ValidateConfigPath(configPath); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Return the configuration path
|
||||
return configPath, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
// Generate our config based on the config supplied
|
||||
// by the user in the flags
|
||||
cfgPath, err := ParseFlags()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
cfg := NewConfig(cfgPath)
|
||||
|
||||
log := NewLog()
|
||||
|
||||
log.Info("config loaded.")
|
||||
log.Infof("Callsign: %s", cfg.SQLite.Callsign)
|
||||
|
||||
DeleteDatabase("./flex.sqlite", log)
|
||||
|
||||
fRepo := NewFlexDXDatabase("flex.sqlite", log)
|
||||
defer fRepo.db.Close()
|
||||
|
||||
cRepo := NewLog4OMContactsRepository(cfg.SQLite.SQLitePath, log)
|
||||
defer cRepo.db.Close()
|
||||
|
||||
TCPServer := NewTCPServer(cfg.TelnetServer.Host, cfg.TelnetServer.Port, log)
|
||||
FlexClient := NewFlexClient(*fRepo, *TCPServer, log)
|
||||
TCPClient := NewTCPClient(TCPServer, FlexClient, log)
|
||||
HTTPServer := NewHTTPServer(*cRepo, *fRepo, *FlexClient, log)
|
||||
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
|
||||
|
||||
go FlexClient.StartFlexClient()
|
||||
go TCPClient.StartClient()
|
||||
go TCPServer.StartServer()
|
||||
|
||||
go HTTPServer.StartHTTPServer()
|
||||
|
||||
for sig := range sigCh {
|
||||
log.Infof("received signal: %v, shutting down TCP Client.", sig)
|
||||
|
||||
TCPClient.Close()
|
||||
|
||||
if err := fRepo.db.Close(); err != nil {
|
||||
log.Error("failed to close the database connection properly")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := cRepo.db.Close(); err != nil {
|
||||
log.Error("failed to close Log4OM database connection properly")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
}
|
195
spot.go
Normal file
195
spot.go
Normal file
@ -0,0 +1,195 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type TelnetSpot struct {
|
||||
DX string
|
||||
Spotter string
|
||||
Frequency string
|
||||
Mode string
|
||||
Band string
|
||||
Time string
|
||||
DXCC string
|
||||
Comment string
|
||||
CommandNumber int
|
||||
FlexSpotNumber int
|
||||
NewDXCC bool
|
||||
NewBand bool
|
||||
NewMode bool
|
||||
CallsignWorked bool
|
||||
}
|
||||
|
||||
func ProcessTelnetSpot(re *regexp.Regexp, spotRaw string, SpotChan chan TelnetSpot, log *log.Logger) {
|
||||
match := re.FindStringSubmatch(spotRaw)
|
||||
|
||||
if len(match) != 0 {
|
||||
spot := TelnetSpot{
|
||||
DX: match[3],
|
||||
Spotter: match[1],
|
||||
Frequency: match[2],
|
||||
Mode: match[4],
|
||||
Comment: strings.Trim(match[5], " "),
|
||||
Time: match[6],
|
||||
}
|
||||
|
||||
spot.GetBand()
|
||||
spot.GuessMode()
|
||||
spot.DXCC, _ = CheckClubogDXCC(spot.DX)
|
||||
spot.CallsignWorked = false
|
||||
spot.NewBand = false
|
||||
spot.NewMode = false
|
||||
spot.NewDXCC = false
|
||||
|
||||
contactRepo := NewLog4OMContactsRepository(Cfg.SQLite.SQLitePath, log)
|
||||
|
||||
defer contactRepo.db.Close()
|
||||
|
||||
contacts, _ := contactRepo.ListByCountry(spot.DXCC)
|
||||
contactsMode, _ := contactRepo.ListByCountryMode(spot.DXCC, spot.Mode)
|
||||
contactsBand, _ := contactRepo.ListByCountryBand(spot.DXCC, spot.Band)
|
||||
contactsCall, _ := contactRepo.ListByCallSign(spot.DX, spot.Band, spot.Mode)
|
||||
|
||||
if len(contacts) == 0 {
|
||||
switch spot.DXCC {
|
||||
case "997":
|
||||
spot.NewDXCC = false
|
||||
case "1000":
|
||||
spot.NewDXCC = false
|
||||
default:
|
||||
spot.NewDXCC = true
|
||||
}
|
||||
} else if len(contactsMode) == 0 {
|
||||
spot.NewMode = true
|
||||
} else if len(contactsBand) == 0 {
|
||||
spot.NewBand = true
|
||||
} else if len(contactsCall) > 0 {
|
||||
spot.CallsignWorked = true
|
||||
}
|
||||
|
||||
if spot.NewDXCC {
|
||||
log.Debugf("(** New DXCC **) DX: %s - Spotter: %s - Freq: %s - Band: %s - Mode: %s - Comment: %s - Time: %s - Command: %v, FlexSpot: %v",
|
||||
spot.DX, spot.Spotter, spot.Frequency, spot.Band, spot.Mode, spot.Comment, spot.Time, spot.CommandNumber, spot.FlexSpotNumber)
|
||||
}
|
||||
|
||||
if !spot.NewDXCC && spot.NewBand && spot.NewMode {
|
||||
log.Debugf("(** New Band/Mode **) DX: %s - Spotter: %s - Freq: %s - Band: %s - Mode: %s - Comment: %s - Time: %s - DXCC: %s",
|
||||
spot.DX, spot.Spotter, spot.Frequency, spot.Band, spot.Mode, spot.Comment, spot.Time, spot.DXCC)
|
||||
}
|
||||
|
||||
if !spot.NewDXCC && spot.NewBand && !spot.NewMode {
|
||||
log.Debugf("(** New Band **) DX: %s - Spotter: %s - Freq: %s - Band: %s - Mode: %s - Comment: %s - Time: %s - DXCC: %s",
|
||||
spot.DX, spot.Spotter, spot.Frequency, spot.Band, spot.Mode, spot.Comment, spot.Time, spot.DXCC)
|
||||
}
|
||||
|
||||
if !spot.NewDXCC && !spot.NewBand && spot.NewMode && spot.Mode != "" {
|
||||
log.Debugf("(** New Mode **) DX: %s - Spotter: %s - Freq: %s - Band: %s - Mode: %s - Comment: %s - Time: %s - DXCC: %s",
|
||||
spot.DX, spot.Spotter, spot.Frequency, spot.Band, spot.Mode, spot.Comment, spot.Time, spot.DXCC)
|
||||
}
|
||||
|
||||
if !spot.NewDXCC && !spot.NewBand && !spot.NewMode && spot.CallsignWorked {
|
||||
log.Debugf("(** Worked **) DX: %s - Spotter: %s - Freq: %s - Band: %s - Mode: %s - Comment: %s - Time: %s - DXCC: %s",
|
||||
spot.DX, spot.Spotter, spot.Frequency, spot.Band, spot.Mode, spot.Comment, spot.Time, spot.DXCC)
|
||||
}
|
||||
|
||||
if !spot.NewDXCC && !spot.NewBand && !spot.NewMode {
|
||||
log.Debugf("DX: %s - Spotter: %s - Freq: %s - Band: %s - Mode: %s - Comment: %s - Time: %s - DXCC: %s",
|
||||
spot.DX, spot.Spotter, spot.Frequency, spot.Band, spot.Mode, spot.Comment, spot.Time, spot.DXCC)
|
||||
}
|
||||
|
||||
// send spot to SpotChan to Flex Client to send the spot to Flex radio
|
||||
SpotChan <- spot
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (spot *TelnetSpot) GetBand() {
|
||||
switch true {
|
||||
case strings.HasPrefix(spot.Frequency, "1.8"):
|
||||
spot.Band = "160M"
|
||||
if spot.Mode == "SSB" {
|
||||
spot.Mode = "LSB"
|
||||
}
|
||||
case strings.HasPrefix(spot.Frequency, "3"):
|
||||
spot.Band = "80M"
|
||||
if spot.Mode == "SSB" {
|
||||
spot.Mode = "LSB"
|
||||
}
|
||||
case strings.HasPrefix(spot.Frequency, "7"):
|
||||
spot.Band = "40M"
|
||||
if spot.Mode == "SSB" {
|
||||
spot.Mode = "LSB"
|
||||
}
|
||||
case strings.HasPrefix(spot.Frequency, "10"):
|
||||
spot.Band = "30M"
|
||||
case strings.HasPrefix(spot.Frequency, "14"):
|
||||
spot.Band = "20M"
|
||||
if spot.Mode == "SSB" {
|
||||
spot.Mode = "USB"
|
||||
}
|
||||
case strings.HasPrefix(spot.Frequency, "18"):
|
||||
spot.Band = "17M"
|
||||
if spot.Mode == "SSB" {
|
||||
spot.Mode = "USB"
|
||||
}
|
||||
case strings.HasPrefix(spot.Frequency, "21"):
|
||||
spot.Band = "15M"
|
||||
if spot.Mode == "SSB" {
|
||||
spot.Mode = "USB"
|
||||
}
|
||||
case strings.HasPrefix(spot.Frequency, "24"):
|
||||
spot.Band = "12M"
|
||||
if spot.Mode == "SSB" {
|
||||
spot.Mode = "USB"
|
||||
}
|
||||
case strings.HasPrefix(spot.Frequency, "28"):
|
||||
spot.Band = "10M"
|
||||
if spot.Mode == "SSB" {
|
||||
spot.Mode = "USB"
|
||||
}
|
||||
case strings.HasPrefix(spot.Frequency, "29"):
|
||||
spot.Band = "10M"
|
||||
if spot.Mode == "SSB" {
|
||||
spot.Mode = "USB"
|
||||
}
|
||||
default:
|
||||
spot.Band = "N/A"
|
||||
}
|
||||
}
|
||||
|
||||
func (spot *TelnetSpot) GuessMode() {
|
||||
if spot.Mode == "" {
|
||||
freqInt, err := strconv.ParseFloat(spot.Frequency, 32)
|
||||
if err != nil {
|
||||
fmt.Println("could not convert frequency string in float64:", err)
|
||||
}
|
||||
|
||||
switch spot.Band {
|
||||
case "160M":
|
||||
if freqInt <= 1840 && freqInt >= 1800 {
|
||||
spot.Mode = "CW"
|
||||
}
|
||||
case "40M":
|
||||
if freqInt <= 7045.49 && freqInt >= 7000 {
|
||||
spot.Mode = "CW"
|
||||
} else if freqInt <= 7048.49 && freqInt >= 7045.49 {
|
||||
spot.Mode = "FT4"
|
||||
} else if freqInt <= 7073.99 && freqInt > 7048.49 {
|
||||
spot.Mode = "CW"
|
||||
} else if freqInt <= 7077 && freqInt > 7073.99 {
|
||||
spot.Mode = "FT8"
|
||||
} else if freqInt <= 7200 && freqInt > 7077 {
|
||||
spot.Mode = "LSB"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
34
utils.go
Normal file
34
utils.go
Normal file
@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func FreqMhztoHz(freq string) string {
|
||||
frequency, err := strconv.ParseFloat(freq, 64)
|
||||
if err != nil {
|
||||
log.Println("could not convert frequency string to int", err)
|
||||
}
|
||||
|
||||
frequency = frequency / 1000
|
||||
|
||||
return strconv.FormatFloat(frequency, 'f', 6, 64)
|
||||
}
|
||||
|
||||
func FreqHztoMhz(freq string) string {
|
||||
frequency, err := strconv.ParseFloat(freq, 64)
|
||||
if err != nil {
|
||||
log.Println("could not convert frequency string to int", err)
|
||||
}
|
||||
|
||||
frequency = frequency * 1000
|
||||
|
||||
return strconv.FormatFloat(frequency, 'f', 6, 64)
|
||||
}
|
||||
|
||||
func roundFloat(val float64, precision uint) float64 {
|
||||
ratio := math.Pow(10, float64(precision))
|
||||
return math.Round(val*ratio) / ratio
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user