nppilot/experiments/gps.go
2014-01-05 14:32:38 +01:00

258 lines
4.6 KiB
Go

package main
import (
"bytes"
"errors"
"io"
"log"
"os"
"regexp"
"strconv"
"strings"
"fmt"
)
var matcher *regexp.Regexp
func init() {
matcher = regexp.MustCompile(`\$([A-Z]+,.+)\*(\w\w)$`)
}
type Message interface {
}
type GGA struct {
Time float64
Latitude float64
Longitude float64
Quality int
NumSatelites int
HDOP float64
Altitude float64
}
type VTG struct {
TrueCourse float64
// MagneticCourse float64
Speed float64
SpeedInKmh float64
Mode string
}
type RMC struct {
Time float64
Status string
Latitude float64
Longitude float64
Speed float64
Track float64
Date string
Mode string
}
type GPSLink struct {
Sentences uint
UnrecognisedType uint
ChecksumError uint
ParseError uint
DecodeError uint
}
type Parser struct {
fields []string
Err error
}
func (parser *Parser) Error() error {
return parser.Err
}
func (parser *Parser) setError(text string) {
if parser.Err == nil {
parser.Err = errors.New(text)
}
}
func (parser *Parser) updateError(err error) {
if err != nil {
parser.Err = err
}
}
func (parser *Parser) get(idx int, fallback string) string {
if idx >= len(parser.fields) {
parser.setError("Field out of range.")
return fallback
} else {
value := parser.fields[idx]
if value == "" {
parser.setError("Field is empty.")
return fallback
} else {
return value
}
}
}
func (parser *Parser) parseInt(value string) int {
f, err := strconv.ParseInt(value, 0, 0)
parser.updateError(err)
return int(f)
}
func (parser *Parser) parseFloat(value string) float64 {
f, err := strconv.ParseFloat(value, 64)
parser.updateError(err)
return f
}
func (parser *Parser) GetString(idx int) string {
return parser.get(idx, "")
}
func (parser *Parser) GetInt(idx int) int {
return parser.parseInt(parser.get(idx, "0"))
}
func (parser *Parser) GetFloat(idx int) float64 {
value := parser.get(idx, "0")
f, err := strconv.ParseFloat(value, 64)
parser.updateError(err)
return f
}
func (parser *Parser) GetTime(idx int) float64 {
value := parser.parseFloat(parser.GetString(idx))
hhmm := int(value / 100)
hours, minutes := hhmm/100, hhmm%100
seconds := value - float64(hhmm*100)
return float64(hours*3600+minutes*60) + seconds
}
func (parser *Parser) GetLatLong(idx int) float64 {
value := parser.GetFloat(idx)
dd := int(value / 100)
mm := value - float64(dd*100)
decimal := float64(dd) + mm/60
direction := parser.get(idx+1, "W")
if direction == "W" || direction == "S" {
decimal = -decimal
}
return decimal
}
func checksum(frame string) uint8 {
var sum uint8
for _, ch := range frame {
sum ^= uint8(ch)
}
return sum
}
func decode(frame string) (msg Message, err error) {
parser := &Parser{strings.Split(frame, ","), nil}
log.Println(parser)
switch parser.GetString(0) {
case "GPGGA":
msg = &GGA{
Time: parser.GetTime(1),
Latitude: parser.GetLatLong(2),
Longitude: parser.GetLatLong(4),
Quality: parser.GetInt(6),
NumSatelites: parser.GetInt(7),
HDOP: parser.GetFloat(8),
Altitude: parser.GetFloat(9),
}
case "GPVTG":
msg = &VTG{
TrueCourse: parser.GetFloat(1),
Speed: parser.GetFloat(5),
SpeedInKmh: parser.GetFloat(7),
Mode: parser.GetString(9),
}
case "GPRMC":
msg = &RMC{
Time: parser.GetTime(1),
Status: parser.GetString(2),
Latitude: parser.GetLatLong(3),
Longitude: parser.GetLatLong(5),
Speed: parser.GetFloat(7),
Track: parser.GetFloat(8),
Date: parser.GetString(9),
Mode: parser.GetString(12),
}
}
return msg, parser.Err
}
func (link *GPSLink) dispatch(frame *bytes.Buffer) {
if frame.Len() == 0 {
return
}
parts := matcher.FindStringSubmatch(frame.String())
if len(parts) == 0 {
link.ParseError += 1
return
}
link.Sentences += 1
sum := checksum(parts[1])
got, err := strconv.ParseUint(parts[2], 16, 8)
if err != nil || uint8(got) != sum {
link.ChecksumError += 1
return
}
msg, err := decode(parts[1])
if err != nil {
link.DecodeError += 1
} else if msg != nil {
fmt.Printf("%T %v\n", msg, msg)
}
}
func (link *GPSLink) Read(port io.Reader) {
got := make([]byte, 128)
var frame bytes.Buffer
for {
n, err := port.Read(got)
if n > 0 {
for _, ch := range got[:n] {
switch ch {
case '$':
frame.Reset()
frame.WriteByte(ch)
case '\r':
case '\n':
link.dispatch(&frame)
frame.Reset()
default:
frame.WriteByte(ch)
}
}
}
if n == 0 || err != nil {
break
}
}
}
func main() {
link := &GPSLink{}
link.Read(os.Stdin)
}