protocol/aprs: refactored
This commit is contained in:
119
protocol/aprs/data.go
Normal file
119
protocol/aprs/data.go
Normal file
@@ -0,0 +1,119 @@
|
||||
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])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user