Skip to content

Commit

Permalink
Get Position from GPSd and convert into three styles.
Browse files Browse the repository at this point in the history
Add several (some undocumented) insertion tags that are required for the Winlink Check In form.
  • Loading branch information
n2ygk committed Mar 15, 2022
1 parent ccb4934 commit 8075aa1
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 0 deletions.
120 changes: 120 additions & 0 deletions internal/forms/forms.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"io"
"io/ioutil"
"log"
"math"
"net/http"
"os"
"path"
Expand All @@ -29,7 +30,9 @@ import (
"time"
"unicode/utf8"

"github.com/la5nta/pat/cfg"
"github.com/dimchansky/utfbom"
"github.com/la5nta/pat/internal/gpsd"
)

const (
Expand Down Expand Up @@ -59,6 +62,7 @@ type Config struct {
AppVersion string
LineReader func() string
UserAgent string
GPSd cfg.GPSdConfig
}

// Form holds information about a Winlink form template
Expand Down Expand Up @@ -704,6 +708,103 @@ func (m *Manager) findAbsPathForTemplatePath(tmplPath string) (string, error) {
return retVal, nil
}

func (m *Manager) gpsPos() (gpsd.Position, error) {
// return current GPS Position

addr := m.config.GPSd.Addr
if addr != "" {
conn, err := gpsd.Dial(addr)
if err != nil {
log.Printf("GPSd daemon: %s", err)
}
defer conn.Close()

conn.Watch(true)

log.Println("Waiting for position from GPSd...")
pos, err := conn.NextPosTimeout(3 * time.Second) // TODO: configurable
if err != nil {
log.Printf("GPSd: %s", err)
} else {
log.Printf("GPSd: %f %f", pos.Lat, pos.Lon)
}
return pos, err
} else {
return gpsd.Position{}, errors.New("GPSd: not configured.")
}
}

type gpsStyle int

const (
// documentation: https://www.winlink.org/sites/default/files/RMSE_FORMS/insertion_tags.zip
signedDecimal gpsStyle = iota // 41.1234 -73.4567
decimal // 46.3795N 121.5835W
degreeMinute // 46-22.77N 121-35.01W
)

func gpsFmt(style gpsStyle, pos gpsd.Position) string {
var northing string
var easting string
var latDegrees int
var latMinutes float64
var lonDegrees int
var lonMinutes float64

noPos := gpsd.Position{}
if pos == noPos {
return "(Not available)"
}
switch style {
case degreeMinute: {
latDegrees = int(math.Trunc(math.Abs(pos.Lat)))
latMinutes = (math.Abs(pos.Lat) - float64(latDegrees)) * 60
lonDegrees = int(math.Trunc(math.Abs(pos.Lon)))
lonMinutes = (math.Abs(pos.Lon) - float64(lonDegrees)) * 60
}
fallthrough
case decimal: {
if pos.Lat >= 0 {
northing = "N"
} else {
northing = "S"
}
if pos.Lon >= 0 {
easting = "E"
} else {
easting = "W"
}
}
}

switch style {
case signedDecimal: return fmt.Sprintf("%.4f %.4f",pos.Lat,pos.Lon)
case decimal: return fmt.Sprintf("%.4f%s %.4f%s", math.Abs(pos.Lat), northing, math.Abs(pos.Lon), easting)
case degreeMinute: return fmt.Sprintf("%02d-%05.2f%s %03d-%05.2f%s", latDegrees, latMinutes, northing, lonDegrees, lonMinutes, easting)
default: return "(Not available)"
}
}

func posToGridSquare(pos gpsd.Position) string {
// function translated from Winlink_Check_In_Intial.html
var (
U = "ABCDEFGHIJKLMNOPQRSTUVWX"
L = "abcdefghijklmnopqrstuvwx"
grid string
)
adjLat := pos.Lat + 90.0
adjLon := pos.Lon + 180.0
grid = grid + string(U[int(adjLon/20.0)]) + string(U[int(adjLat/10.0)])
nLat := int(adjLat) % 10
nLon := int(adjLon/2) % 10
grid = grid + fmt.Sprintf("%d%d", nLon, nLat)
rLat := (adjLat - math.Trunc(adjLat)) * 60.0
rLon := (adjLon - 2 * math.Trunc(adjLon/2)) * 60.0
grid = grid + string(L[int(math.Trunc(rLon/5))]) + string(L[int(math.Trunc(rLat/2.5))])
return grid
}


func (m *Manager) fillFormTemplate(absPathTemplate string, formDestURL string, placeholderRegEx *regexp.Regexp, formVars map[string]string) (string, error) {
fUnsanitized, err := os.Open(absPathTemplate)
if err != nil {
Expand Down Expand Up @@ -732,6 +833,13 @@ func (m *Manager) fillFormTemplate(absPathTemplate string, formDestURL string, p
nowDateUTC := now.UTC().Format("2006-01-02Z")
nowTimeUTC := now.UTC().Format("15:04:05Z")
udtg := strings.ToUpper(now.UTC().Format("021504Z Jan 2006"))
nowPos,err := m.gpsPos()
var validPos string
if err != nil {
validPos = "NO"
} else {
validPos = "YES"
}

scanner := bufio.NewScanner(bytes.NewReader(sanitizedFileContent))
for scanner.Scan() {
Expand All @@ -749,6 +857,18 @@ func (m *Manager) fillFormTemplate(absPathTemplate string, formDestURL string, p
l = strings.ReplaceAll(l, "{UDTG}", udtg)
l = strings.ReplaceAll(l, "{Time}", nowTime)
l = strings.ReplaceAll(l, "{UTime}", nowTimeUTC)
l = strings.ReplaceAll(l, "{GPS}", gpsFmt(degreeMinute, nowPos))
l = strings.ReplaceAll(l, "{GPS_DECIMAL}", gpsFmt(decimal, nowPos))
l = strings.ReplaceAll(l, "{GPS_SIGNED_DECIMAL}", gpsFmt(signedDecimal, nowPos))
// Lots of undocumented tags found in the Winlink check in form.
// Note also various ways of capitalizing. Perhaps best to do case insenstive string replacements....
l = strings.ReplaceAll(l, "{Latitude}", fmt.Sprintf("%.4f", nowPos.Lat))
l = strings.ReplaceAll(l, "{latitude}", fmt.Sprintf("%.4f", nowPos.Lat))
l = strings.ReplaceAll(l, "{Longitude}", fmt.Sprintf("%.4f", nowPos.Lon))
l = strings.ReplaceAll(l, "{longitude}", fmt.Sprintf("%.4f", nowPos.Lon))
l = strings.ReplaceAll(l, "{GridSquare}", posToGridSquare(nowPos))
l = strings.ReplaceAll(l, "{GPSValid}", fmt.Sprintf("%s ", validPos))
// l = strings.ReplaceAll(l, "{InternetAvailable}", "TO DO")
if placeholderRegEx != nil {
l = fillPlaceholders(l, placeholderRegEx, formVars)
}
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ func main() {
AppVersion: buildinfo.VersionStringShort(),
UserAgent: buildinfo.UserAgent(),
LineReader: readLine,
GPSd: config.GPSd,
})

// Make sure we clean up on exit, closing any open resources etc.
Expand Down

0 comments on commit 8075aa1

Please sign in to comment.