Skip to content

Commit

Permalink
Add several (some undocumented) insertion tags that are required for …
Browse files Browse the repository at this point in the history
…the Winlink Check In form.
  • Loading branch information
n2ygk committed Mar 15, 2022
1 parent ccb4934 commit 9a16f31
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 0 deletions.
125 changes: 125 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 @@ -30,6 +31,8 @@ import (
"unicode/utf8"

"github.com/dimchansky/utfbom"
"github.com/la5nta/pat/cfg"
"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,108 @@ 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 +838,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 +862,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 9a16f31

Please sign in to comment.