rmslist.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. // Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved.
  2. // Use of this source code is governed by the MIT-license that can be
  3. // found in the LICENSE file.
  4. package main
  5. import (
  6. "encoding/json"
  7. "fmt"
  8. "log"
  9. "net/url"
  10. "path"
  11. "strings"
  12. "github.com/spf13/pflag"
  13. "github.com/la5nta/wl2k-go/mailbox"
  14. "sort"
  15. "strconv"
  16. "github.com/la5nta/pat/internal/cmsapi"
  17. "github.com/pd0mz/go-maidenhead"
  18. )
  19. type rms struct {
  20. callsign string
  21. gridsq string
  22. distance float64
  23. azimuth float64
  24. modes string
  25. freq string
  26. dial string
  27. url *url.URL
  28. }
  29. type byDist []rms
  30. func (r byDist) Len() int { return len(r) }
  31. func (r byDist) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
  32. func (r byDist) Less(i, j int) bool { return r[i].distance < r[j].distance }
  33. func rmsListHandle(args []string) {
  34. set := pflag.NewFlagSet("rmslist", pflag.ExitOnError)
  35. mode := set.StringP("mode", "m", "", "")
  36. band := set.StringP("band", "b", "", "")
  37. forceDownload := set.BoolP("force-download", "d", false, "")
  38. byDistance := set.BoolP("sort-distance", "s", false, "")
  39. set.Parse(args)
  40. appDir, err := mailbox.DefaultAppDir()
  41. if err != nil {
  42. log.Fatal(err)
  43. }
  44. fileName := "rmslist"
  45. isDefaultServiceCode := len(config.ServiceCodes) == 1 && config.ServiceCodes[0] == "PUBLIC"
  46. if !isDefaultServiceCode {
  47. fileName += "-" + strings.Join(config.ServiceCodes, "-")
  48. }
  49. filePath := path.Join(appDir, fileName+".json") // Should be moved to a tmp-folder, along with logfile.
  50. var query string
  51. if len(set.Args()) > 0 {
  52. query = strings.ToUpper(set.Args()[0])
  53. }
  54. file, err := cmsapi.GetGatewayStatusCached(filePath, *forceDownload, config.ServiceCodes...)
  55. if err != nil {
  56. log.Fatal(err)
  57. }
  58. defer file.Close()
  59. var status cmsapi.GatewayStatus
  60. err = json.NewDecoder(file).Decode(&status)
  61. if err != nil {
  62. log.Fatal(err)
  63. }
  64. noLocator := false
  65. me, err := maidenhead.ParseLocator(config.Locator)
  66. if err != nil {
  67. log.Print("Missing or Invalid Locator, will not compute distance and Azimuth")
  68. noLocator = true
  69. }
  70. *mode = strings.ToLower(*mode)
  71. rList := []rms{}
  72. // Print gateways (separated by blank line)
  73. for _, gw := range status.Gateways {
  74. switch {
  75. case query != "" && !strings.HasPrefix(gw.Callsign, query):
  76. continue
  77. default:
  78. }
  79. for _, channel := range gw.Channels {
  80. r := rms{
  81. callsign: gw.Callsign,
  82. gridsq: channel.Gridsquare,
  83. modes: channel.SupportedModes,
  84. }
  85. f := Frequency(channel.Frequency)
  86. r.dial = f.Dial(channel.SupportedModes).String()
  87. r.freq = f.String()
  88. switch {
  89. case mode != nil && !strings.Contains(strings.ToLower(channel.SupportedModes), *mode):
  90. continue
  91. case !bands[*band].Contains(f):
  92. continue
  93. }
  94. r.distance = float64(0)
  95. r.azimuth = float64(0)
  96. if !noLocator {
  97. if them, err := maidenhead.ParseLocator(channel.Gridsquare); err == nil {
  98. r.distance = me.Distance(them)
  99. r.azimuth = me.Bearing(them)
  100. }
  101. }
  102. r.url = toURL(channel, gw.Callsign)
  103. rList = append(rList, r)
  104. }
  105. }
  106. if *byDistance {
  107. sort.Sort(byDist(rList))
  108. }
  109. fmtStr := "%-9.9s [%-6.6s] %-6.6s %3.3s %-15.15s %14.14s %14.14s %s\n"
  110. // Print header
  111. fmt.Printf(fmtStr, "callsign", "gridsq", "dist", "Az", "mode(s)", "dial freq", "center freq", "url") //TODO: "center frequency" of packet is wrong...
  112. for i := 0; i < len(rList); i++ {
  113. r := rList[i]
  114. distance := strconv.FormatFloat(r.distance, 'f', 0, 64)
  115. azimuth := strconv.FormatFloat(r.azimuth, 'f', 0, 64)
  116. fmt.Printf(fmtStr, r.callsign, r.gridsq, distance, azimuth, r.modes, r.dial, r.freq, r.url)
  117. if i+1 < len(rList) && rList[i].callsign != rList[i+1].callsign {
  118. fmt.Println("")
  119. }
  120. }
  121. }
  122. func toURL(gc cmsapi.GatewayChannel, targetcall string) *url.URL {
  123. freq := Frequency(gc.Frequency).Dial(gc.SupportedModes)
  124. url, _ := url.Parse(fmt.Sprintf("%s:///%s?freq=%v", toTransport(gc), targetcall, freq.KHz()))
  125. return url
  126. }
  127. var transports = []string{"winmor", "packet", "pactor", "ardop"}
  128. func toTransport(gc cmsapi.GatewayChannel) string {
  129. modes := strings.ToLower(gc.SupportedModes)
  130. for _, transport := range transports {
  131. if strings.Contains(modes, transport) {
  132. return transport
  133. }
  134. }
  135. return ""
  136. }