120 lines
2.7 KiB
Go
120 lines
2.7 KiB
Go
package aprs
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
type decoder interface {
|
|
CanDecode(*Frame) bool
|
|
Decode(*Frame) (Data, error)
|
|
}
|
|
|
|
var decoders []decoder
|
|
|
|
func init() {
|
|
decoders = []decoder{
|
|
positionDecoder{},
|
|
micEDecoder{},
|
|
messageDecoder{},
|
|
queryDecoder{},
|
|
}
|
|
}
|
|
|
|
// Raw is the string encoded raw frame payload.
|
|
type Raw string
|
|
|
|
func (p Raw) Type() Type {
|
|
var t Type
|
|
if len(p) > 0 {
|
|
t = Type(p[0])
|
|
}
|
|
return t
|
|
}
|
|
|
|
// Data represents a decoded payload data.
|
|
type Data interface {
|
|
String() string
|
|
}
|
|
|
|
// Type of payload.
|
|
type Type byte
|
|
|
|
var typeName = map[Type]string{
|
|
0x1c: "Current Mic-E Data (Rev 0 beta)",
|
|
0x1d: "Old Mic-E Data (Rev 0 beta)",
|
|
'!': "Position without timestamp (no APRS messaging), or Ultimeter 2000 WX Station",
|
|
'#': "Peet Bros U-II Weather Station",
|
|
'$': "Raw GPS data or Ultimeter 2000",
|
|
'%': "Agrelo DFJr / MicroFinder",
|
|
'"': "Old Mic-E Data (but Current data for TM-D700)",
|
|
')': "Item",
|
|
'*': "Peet Bros U-II Weather Station",
|
|
',': "Invalid data or test data",
|
|
'/': "Position with timestamp (no APRS messaging)",
|
|
':': "Message",
|
|
';': "Object",
|
|
'<': "Station Capabilities",
|
|
'=': "Position without timestamp (with APRS messaging)",
|
|
'>': "Status",
|
|
'?': "Query",
|
|
'@': "Position with timestamp (with APRS messaging)",
|
|
'T': "Telemetry data",
|
|
'[': "Maidenhead grid locator beacon (obsolete)",
|
|
'_': "Weather Report (without position)",
|
|
'`': "Current Mic-E Data (not used in TM-D700)",
|
|
'{': "User-Defined APRS packet format",
|
|
'}': "Third-party traffic",
|
|
}
|
|
|
|
func (t Type) String() string {
|
|
if s, ok := typeName[t]; ok {
|
|
return s
|
|
}
|
|
return fmt.Sprintf("unknown %02x", byte(t))
|
|
}
|
|
|
|
func (t Type) IsMessage() bool {
|
|
return t == ':'
|
|
}
|
|
|
|
func (t Type) IsThirdParty() bool {
|
|
return t == '}'
|
|
}
|
|
|
|
var matchTimestamp = regexp.MustCompile(`^[0-9]{6}[zh/]`)
|
|
|
|
func hasTimestamp(s string) bool {
|
|
return matchTimestamp.MatchString(s)
|
|
}
|
|
|
|
func parseTimestamp(s string) (t time.Time, comment string, err error) {
|
|
// log.Printf("parse timestamp %q", s)
|
|
var hh, mm, ss int
|
|
if hh, err = strconv.Atoi(s[0:2]); err != nil {
|
|
return
|
|
}
|
|
if mm, err = strconv.Atoi(s[2:4]); err != nil {
|
|
return
|
|
}
|
|
if ss, err = strconv.Atoi(s[4:6]); err != nil {
|
|
return
|
|
}
|
|
|
|
now := time.Now()
|
|
switch s[6] {
|
|
case 'z': // DDHHMMz zulu time
|
|
now = now.UTC()
|
|
return time.Date(now.Year(), now.Month(), hh, mm, ss, 0, 0, time.UTC), s[7:], nil
|
|
case '/': // DDHHMM/ local time
|
|
return time.Date(now.Year(), now.Month(), hh, mm, ss, 0, 0, now.Location()), s[7:], nil
|
|
case 'h': // HHMMSSh zulu time
|
|
now = now.UTC()
|
|
return time.Date(now.Year(), now.Month(), now.Day(), hh, mm, ss, 0, time.UTC), s[7:], nil
|
|
default:
|
|
return time.Time{}, "", fmt.Errorf("aprs: invalid/unknown timestamp marker %q", s[6])
|
|
}
|
|
}
|