[WIP] eliminated data races & formatted files

This commit is contained in:
Timo Volkmann 2020-12-12 02:29:22 +01:00
parent a4a739c64b
commit 09791727a4
17 changed files with 1158 additions and 910 deletions

1
.gitignore vendored
View File

@ -161,3 +161,4 @@ Temporary Items
.env .env
gpsconfig.yml gpsconfig.yml
config.yml config.yml
_db

View File

@ -1,65 +1,66 @@
package main package main
import ( import (
"git.timovolkmann.de/gyrogpsc/core" "git.timovolkmann.de/gyrogpsc/core"
"git.timovolkmann.de/gyrogpsc/storage" "git.timovolkmann.de/gyrogpsc/storage"
"git.timovolkmann.de/gyrogpsc/web" "git.timovolkmann.de/gyrogpsc/web"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/viper" "github.com/spf13/viper"
"time" "time"
) )
func main() { func main() {
conf := configurationFromFile() conf := configurationFromFile()
repo := storage.NewRepository(conf) repo := storage.NewRepository(conf)
disp := core.NewDispatcher() disp := core.NewDispatcher()
service := core.TrackingService(repo, disp, conf) service := core.TrackingService(repo, disp, conf)
go func() { go func() {
service.NewTracking(core.TCP, core.SERIAL) service.NewSetup(core.TCP, core.SERIAL)
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
service.StartRecord() service.StartRecord()
time.Sleep(20 * time.Second) time.Sleep(5 * time.Second)
service.StopRecord() service.StopRecord()
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
service.NewTracking(core.TCP) service.NewSetup(core.TCP)
service.NewTracking(core.SERIAL) time.Sleep(5 * time.Second)
time.Sleep(5 * time.Second) service.StartRecord()
service.StartRecord() time.Sleep(5 * time.Second)
time.Sleep(20 * time.Second) service.StopRecord()
service.StopRecord() time.Sleep(5 * time.Second)
}() service.StopAll()
}()
web.CreateServer(service, disp, conf) web.CreateServer(service, disp, conf)
} }
func configurationFromFile() *core.Configuration { func configurationFromFile() *core.Configuration {
viper.SetDefault("collectors.porttcp", ":3010") viper.SetDefault("collectors.porttcp", ":3010")
viper.SetDefault("collectors.portserial", "/dev/tty.usbmodem14201") viper.SetDefault("collectors.portserial", "/dev/tty.usbmodem14201")
viper.SetDefault("webserver.port", ":3011") viper.SetDefault("webserver.port", ":3011")
viper.SetDefault("pipeline.publishIntervalMs", 50) viper.SetDefault("pipeline.publishIntervalMs", 50)
viper.SetDefault("pipeline.syncUpdateIntervalMs", 494) viper.SetDefault("pipeline.syncUpdateIntervalMs", 494)
viper.SetDefault("debuglevel", "INFO") viper.SetDefault("debuglevel", "INFO")
viper.SetConfigName("gpsconfig") // name of config file (without extension) viper.SetConfigName("gpsconfig") // name of config file (without extension)
viper.SetConfigType("yaml") viper.SetConfigType("yaml")
viper.AddConfigPath(".") viper.AddConfigPath(".")
viper.AddConfigPath("./../../") viper.AddConfigPath("./../../")
if err := viper.ReadInConfig(); err != nil { if err := viper.ReadInConfig(); err != nil {
logrus.Warn("couldn't find config file. using standard configuration") logrus.Warn("couldn't find config file. using standard configuration")
} }
c := core.Configuration{} c := core.Configuration{}
if err := viper.Unmarshal(&c); err != nil { if err := viper.Unmarshal(&c); err != nil {
logrus.Debug("couldn't load config...") logrus.Debug("couldn't load config...")
logrus.Error(err) logrus.Error(err)
} }
lvl, err := logrus.ParseLevel(c.Debuglevel) lvl, err := logrus.ParseLevel(c.Debuglevel)
if err != nil { if err != nil {
logrus.Error(err) logrus.Error(err)
} }
logrus.SetLevel(lvl) logrus.SetLevel(lvl)
return &c return &c
} }

View File

@ -1,37 +1,37 @@
package main package main
import ( import (
"git.timovolkmann.de/gyrogpsc/core" "git.timovolkmann.de/gyrogpsc/core"
"git.timovolkmann.de/gyrogpsc/storage" "git.timovolkmann.de/gyrogpsc/storage"
"git.timovolkmann.de/gyrogpsc/web" "git.timovolkmann.de/gyrogpsc/web"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
func main() { func main() {
conf := &core.Configuration{} conf := &core.Configuration{}
configurationFromFile(conf) configurationFromFile(conf)
repo := storage.NewRepository(conf) repo := storage.NewRepository(conf)
disp := core.NewDispatcher() disp := core.NewDispatcher()
service := core.TrackingService(repo, disp, conf) service := core.TrackingService(repo, disp, conf)
web.CreateServer(service, disp, conf) web.CreateServer(service, disp, conf)
} }
func configurationFromFile(c *core.Configuration) { func configurationFromFile(c *core.Configuration) {
viper.SetDefault("TcpCollectorPort", ":3010") viper.SetDefault("TcpCollectorPort", ":3010")
viper.SetDefault("SerialCollectorPort", "/dev/tty.usbmodem14201") viper.SetDefault("SerialCollectorPort", "/dev/tty.usbmodem14201")
viper.SetDefault("HttpPort", "layouts") viper.SetDefault("HttpPort", "layouts")
viper.SetDefault("publishIntervalMs", 50) viper.SetDefault("publishIntervalMs", 50)
viper.SetDefault("syncUpdateIntervalMs", 494) viper.SetDefault("syncUpdateIntervalMs", 494)
viper.SetConfigName("gpsconfig") // name of config file (without extension) viper.SetConfigName("gpsconfig") // name of config file (without extension)
viper.SetConfigType("yaml") viper.SetConfigType("yaml")
viper.AddConfigPath(".") viper.AddConfigPath(".")
viper.AddConfigPath("./../../") viper.AddConfigPath("./../../")
viper.Unmarshal(c) viper.Unmarshal(c)
logrus.Println(c) logrus.Println(c)
} }

View File

@ -1,184 +1,199 @@
package core package core
import ( import (
"fmt" "fmt"
"git.timovolkmann.de/gyrogpsc/ublox" "git.timovolkmann.de/gyrogpsc/ublox"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"go.bug.st/serial" "go.bug.st/serial"
"net" "net"
"os" "os"
"sync"
) )
type Collector interface { type Collector interface {
Collect() Collect()
Stop() Close()
} }
type CollectorType uint8 type CollectorType uint8
const ( const (
SERIAL CollectorType = iota SERIAL CollectorType = iota
TCP TCP
) )
var tcpSingleton *tcpCollector var tcpSingleton *tcpCollector
func NewCollector(typ CollectorType, proc Pusher, config *Configuration) Collector { func NewCollector(typ CollectorType, proc Pusher, config *Configuration) Collector {
var coll Collector var coll Collector
switch typ { switch typ {
case SERIAL: case SERIAL:
coll = newSerial(proc, config) coll = newSerial(proc, config)
case TCP: case TCP:
if tcpSingleton == nil { if tcpSingleton == nil {
tcpSingleton = newTcp(proc, config) tcpSingleton = newTcp(proc, config)
} else { } else {
tcpSingleton.SetProcessor(proc) tcpSingleton.SetProcessor(proc)
} }
coll = tcpSingleton coll = tcpSingleton
default: default:
panic("selected collector type not implemented") panic("selected collector type not implemented")
} }
return coll return coll
} }
type serialCollector struct { type serialCollector struct {
active bool active bool
proc Pusher proc Pusher
config *Configuration config *Configuration
mu sync.RWMutex
} }
func (s *serialCollector) isSerialCollActive() bool {
s.mu.RLock()
defer s.mu.RUnlock()
return s.active
}
func (s *serialCollector) Collect() { func (s *serialCollector) Collect() {
s.active = true s.mu.Lock()
go func() { s.active = true
logrus.Println("start serial collector") s.mu.Unlock()
mode := &serial.Mode{ go func() {
BaudRate: 115200, logrus.Println("start serial collector")
} mode := &serial.Mode{
port, err := serial.Open(s.config.Collectors.SerialCollectorPort, mode) BaudRate: 115200,
if err != nil { }
logrus.Fatalln(err.Error()) port, err := serial.Open(s.config.Collectors.SerialCollectorPort, mode)
} if err != nil {
defer port.Close() logrus.Fatalln("can't open serial port:", err.Error())
}
defer port.Close()
decoder := ublox.NewDecoder(port) decoder := ublox.NewDecoder(port)
for s.active { for s.isSerialCollActive() {
meas, err := decoder.Decode() meas, err := decoder.Decode()
if err != nil { if err != nil {
if err.Error() == "NMEA not implemented" { if err.Error() == "NMEA not implemented" {
continue continue
} }
logrus.Println("serial read err:", err) logrus.Println("serial read err:", err)
break break
} }
sd, err := ConvertUbxToSensorData(meas) sd, err := ConvertUbxToSensorData(meas)
if err != nil { if err != nil {
logrus.Println("convert err:", err, meas, sd) logrus.Println("convert err:", err, meas, sd)
continue continue
} }
// skip irrelevant messages // skip irrelevant messages
if sd == nil { if sd == nil {
continue continue
} }
err = s.proc.Push(sd) err = s.proc.Push(sd)
if err != nil { if err != nil {
logrus.Println("process err:", err, *sd) logrus.Println("process err:", err, *sd)
continue continue
} }
} }
logrus.Println("serial collector stopped") logrus.Println("serial collector stopped")
}() }()
} }
func (s *serialCollector) Stop() {
s.active = false
func (s *serialCollector) Close() {
s.mu.Lock()
s.active = false
s.mu.Unlock()
} }
func newSerial(proc Pusher, config *Configuration) *serialCollector { func newSerial(proc Pusher, config *Configuration) *serialCollector {
return &serialCollector{ return &serialCollector{
active: false, active: false,
proc: proc, proc: proc,
config: config, config: config,
} }
} }
type tcpCollector struct { type tcpCollector struct {
active bool active bool
processor Pusher processor Pusher
//config *Configuration //config *Configuration
} }
func (t *tcpCollector) Collect() { func (t *tcpCollector) Collect() {
t.active = true t.active = true
} }
func (t *tcpCollector) Stop() { func (t *tcpCollector) Close() {
t.active = false t.active = false
} }
func (t *tcpCollector) SetProcessor(p Pusher) { func (t *tcpCollector) SetProcessor(p Pusher) {
t.processor = p t.processor = p
} }
func newTcp(proc Pusher, config *Configuration) *tcpCollector { func newTcp(proc Pusher, config *Configuration) *tcpCollector {
logrus.Println("start tcp collector") logrus.Println("start tcp collector")
listener, err := net.Listen("tcp", config.Collectors.TcpCollectorPort) listener, err := net.Listen("tcp", config.Collectors.TcpCollectorPort)
if err != nil { if err != nil {
fmt.Println("Error listening:", err.Error()) fmt.Println("Error listening:", err)
//os.Exit(1) //os.Exit(1)
} }
coll := &tcpCollector{ coll := &tcpCollector{
active: false, active: false,
processor: proc, processor: proc,
} }
go func() { go func() {
for { for {
// Listen for an incoming connection. // Listen for an incoming connection.
conn, err := listener.Accept() conn, err := listener.Accept()
if err != nil { if err != nil {
fmt.Println("Error accepting: ", err.Error()) fmt.Println("Error accepting: ", err.Error())
os.Exit(1) os.Exit(1)
} }
logrus.Println("...new incoming tcp connection...") logrus.Println("...new incoming tcp connection...")
// Handle connections in a new goroutine. // Handle connections in a new goroutine.
go coll.jsonHandler(conn) go coll.jsonHandler(conn)
} }
}() }()
return coll return coll
} }
// handles incoming tcp connections with json payload. // handles incoming tcp connections with json payload.
func (c *tcpCollector) jsonHandler(conn net.Conn) { func (c *tcpCollector) jsonHandler(conn net.Conn) {
defer conn.Close() defer conn.Close()
// TRY reader := bufio.NewReader(conn) OR NewScanner(conn) // TRY reader := bufio.NewReader(conn) OR NewScanner(conn)
buf := make([]byte, 2048) buf := make([]byte, 2048)
for { for {
// Read the incoming connection into the buffer. // Read the incoming connection into the buffer.
n, err := conn.Read(buf) n, err := conn.Read(buf)
if err != nil { if err != nil {
fmt.Println("TCP error - reading from connection:", n, err.Error()) fmt.Println("TCP error - reading from connection:", n, err.Error())
break break
} }
//json := pretty.Pretty(buf[:n]) //json := pretty.Pretty(buf[:n])
//fmt.Println(string(json)) //fmt.Println(string(json))
//fmt.Println(string(buf[:n])) //fmt.Println(string(buf[:n]))
sd, err := ConvertSensorDataPhone(buf[:n]) sd, err := ConvertSensorDataPhone(buf[:n])
if err != nil { if err != nil {
logrus.Println(err) logrus.Println(err)
continue continue
} }
if !c.active { if !c.active {
//time.Sleep(50 * time.Millisecond) //time.Sleep(50 * time.Millisecond)
continue continue
} }
err = c.processor.Push(sd) err = c.processor.Push(sd)
if err != nil { if err != nil {
logrus.Fatalln(err) logrus.Fatalln(err)
} }
} }
} }

View File

@ -1,16 +1,16 @@
package core package core
type Configuration struct { type Configuration struct {
Collectors struct { Collectors struct {
TcpCollectorPort string `mapstructure:"porttcp"` TcpCollectorPort string `mapstructure:"porttcp"`
SerialCollectorPort string `mapstructure:"portserial"` SerialCollectorPort string `mapstructure:"portserial"`
} `mapstructure:"collectors"` } `mapstructure:"Collectors"`
Webserver struct { Webserver struct {
Port string `mapstructure:"port"` Port string `mapstructure:"port"`
} `mapstructure:"webserver"` } `mapstructure:"webserver"`
Pipeline struct { Pipeline struct {
PublishIntervalMs int `mapstructure:"publishintervalms"` PublishIntervalMs int `mapstructure:"publishintervalms"`
SyncUpdateIntervalMs int `mapstructure:"syncupdateintervalms"` SyncUpdateIntervalMs int `mapstructure:"syncupdateintervalms"`
} `mapstructure:"pipeline"` } `mapstructure:"pipeline"`
Debuglevel string `mapstructure:"debuglevel"` Debuglevel string `mapstructure:"debuglevel"`
} }

View File

@ -1,43 +1,44 @@
package core package core
import ( import (
"errors" "errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
type dispatcher struct { type dispatcher struct {
listeners map[int16]chan string listeners map[int16]chan string
counter int16 counter int16
} }
func NewDispatcher() *dispatcher { func NewDispatcher() *dispatcher {
return &dispatcher{ return &dispatcher{
listeners: make(map[int16]chan string), listeners: make(map[int16]chan string),
counter: 0, counter: 0,
} }
} }
func (d *dispatcher) Publish(message string) { func (d *dispatcher) Publish(message string) {
logrus.Debugf("publish to %v listeners:\n%v\n", len(d.listeners), message) logrus.Debugf("publish to %v listeners:\n%v\n", len(d.listeners))
for _, ch := range d.listeners { logrus.Debug(message)
ch <- message for _, ch := range d.listeners {
} ch <- message
}
} }
func (d *dispatcher) Subscribe() (id int16, receiver <-chan string) { func (d *dispatcher) Subscribe() (id int16, receiver <-chan string) {
key := d.counter key := d.counter
d.counter++ d.counter++
rec := make(chan string) rec := make(chan string)
d.listeners[key] = rec d.listeners[key] = rec
return key, rec return key, rec
} }
func (d *dispatcher) Unsubscribe(id int16) error { func (d *dispatcher) Unsubscribe(id int16) error {
receiver, ok := d.listeners[id] receiver, ok := d.listeners[id]
if !ok { if !ok {
return errors.New("no subscription with id") return errors.New("no subscription with id")
} }
delete(d.listeners, id) delete(d.listeners, id)
close(receiver) close(receiver)
return nil return nil
} }

View File

@ -3,44 +3,40 @@ package core
import "github.com/google/uuid" import "github.com/google/uuid"
type Subscriber interface { type Subscriber interface {
Subscribe() (int16, <-chan string) Subscribe() (int16, <-chan string)
Unsubscribe(id int16) error Unsubscribe(id int16) error
} }
type Publisher interface { type Publisher interface {
Publish(message string) Publish(message string)
} }
type Pusher interface { type Pusher interface {
Push(data *sensorData) error Push(data *sensorData) error
} }
type Storer interface { type Storer interface {
EnqueuePair(tcp sensorData, ser sensorData) EnqueuePair(tcp sensorData, ser sensorData)
EnqueueRaw(data sensorData) EnqueueRaw(data sensorData)
} }
type Repo interface { type Repo interface {
Save(tracking Tracking) error Save(tracking Tracking) error
LoadAll() ([]TrackingMetadata, error) LoadAll() ([]TrackingMetadata, error)
Load(id uuid.UUID) (Tracking, error) Load(id uuid.UUID) (Tracking, error)
} }
type Service interface { type Service interface {
AllTrackings() AllTrackings()
NewTracking(cols ...CollectorType) NewSetup(cols ...CollectorType)
StartRecord() StartRecord()
StopRecord() StopRecord()
Reset() StopAll()
LoadTracking(trackingId uuid.UUID) LoadTracking(trackingId uuid.UUID)
DeleteTracking(trackingId uuid.UUID) DeleteTracking(trackingId uuid.UUID)
StartReplay() StartReplay()
PauseReplay() PauseReplay()
StopReplay() StopReplay()
} }

View File

@ -1,204 +1,225 @@
package core package core
import ( import (
"encoding/json" "context"
"errors" "encoding/json"
"github.com/google/go-cmp/cmp" "errors"
"github.com/google/go-cmp/cmp/cmpopts" "github.com/google/go-cmp/cmp"
"github.com/sirupsen/logrus" "github.com/google/go-cmp/cmp/cmpopts"
"sync" "github.com/sirupsen/logrus"
"time" "golang.org/x/sync/semaphore"
"sync"
"time"
) )
type pipeline struct { type pipeline struct {
active bool active bool
record bool record bool
synchroniz synchronizer synchroniz synchronizer
buffer pipeBuffer buffer pipeBuffer
publisher Publisher publisher Publisher
storer Storer storer Storer
publishTicker *time.Ticker publishTicker *time.Ticker
mu sync.RWMutex
sema *semaphore.Weighted
} }
// pipe implements Runner & Pusher // pipe implements Runner & Pusher
func NewPipeline(d Publisher, s Storer, conf *Configuration) *pipeline { func NewPipeline(d Publisher, s Storer, conf *Configuration) *pipeline {
return &pipeline{ return &pipeline{
false, false,
false, false,
synchronizer{ synchronizer{
//bufferSize: 100, //bufferSize: 100,
mutex: &sync.RWMutex{}, mutex: &sync.RWMutex{},
updateTicker: time.NewTicker(time.Duration(conf.Pipeline.SyncUpdateIntervalMs) * time.Millisecond), updateTicker: time.NewTicker(time.Duration(conf.Pipeline.SyncUpdateIntervalMs) * time.Millisecond),
}, },
pipeBuffer{ pipeBuffer{
tcpMutex: &sync.Mutex{}, tcpMutex: &sync.Mutex{},
serialMutex: &sync.Mutex{}, serialMutex: &sync.Mutex{},
}, },
d, d,
s, s,
time.NewTicker(time.Duration(conf.Pipeline.PublishIntervalMs) * time.Millisecond), time.NewTicker(time.Duration(conf.Pipeline.PublishIntervalMs) * time.Millisecond),
} sync.RWMutex{},
semaphore.NewWeighted(2),
}
}
func (p *pipeline) isPipeActive() bool {
p.mu.RLock()
defer p.mu.RUnlock()
return p.active
} }
func (p *pipeline) Run() { func (p *pipeline) Run() {
p.active = true p.sema.Acquire(context.Background(), 2)
logrus.Println("pipe: processing service started") p.mu.Lock()
go func() { p.active = true
for p.active { p.mu.Unlock()
<-p.synchroniz.updateTicker.C logrus.Println("pipe: processing service started")
err := p.refreshDelay() go func() {
if err != nil { for p.isPipeActive() {
logrus.Debugln(err) <-p.synchroniz.updateTicker.C
} err := p.refreshDelay()
} if err != nil {
logrus.Println("pipe: updater stopped") logrus.Debugln(err)
}() }
go func() { }
for p.active { p.sema.Release(1)
<-p.publishTicker.C logrus.Println("pipe: updater stopped")
err := p.publish() }()
if err != nil && err.Error() != "no data available" { go func() {
logrus.Debug(err) for p.isPipeActive() {
} <-p.publishTicker.C
} err := p.publish()
logrus.Println("pipe: publisher stopped") if err != nil && err.Error() != "no data available" {
}() logrus.Debug(err)
}
}
p.sema.Release(1)
logrus.Println("pipe: publisher stopped")
}()
} }
func (p *pipeline) Record() { func (p *pipeline) Record() {
p.record = true p.record = true
} }
func (p *pipeline) Stop() { func (p *pipeline) StopRecord() {
p.record = false p.record = false
} }
func (p *pipeline) publish() error { func (p *pipeline) publish() error {
p.buffer.tcpMutex.Lock() p.buffer.tcpMutex.Lock()
p.buffer.serialMutex.Lock() p.buffer.serialMutex.Lock()
if (p.buffer.MeasTcp == sensorData{} && p.buffer.MeasSerial == sensorData{}) { if (p.buffer.MeasTcp == sensorData{} && p.buffer.MeasSerial == sensorData{}) {
p.buffer.tcpMutex.Unlock() p.buffer.tcpMutex.Unlock()
p.buffer.serialMutex.Unlock() p.buffer.serialMutex.Unlock()
return errors.New("no data available") return errors.New("no data available")
} }
if cmp.Equal(p.buffer.MeasTcp, p.buffer.LastMeasTcp, cmpopts.IgnoreUnexported(sensorData{})) && if cmp.Equal(p.buffer.MeasTcp, p.buffer.LastMeasTcp, cmpopts.IgnoreUnexported(sensorData{})) &&
cmp.Equal(p.buffer.MeasSerial, p.buffer.LastMeasSerial, cmpopts.IgnoreUnexported(sensorData{})) { cmp.Equal(p.buffer.MeasSerial, p.buffer.LastMeasSerial, cmpopts.IgnoreUnexported(sensorData{})) {
p.buffer.tcpMutex.Unlock() p.buffer.tcpMutex.Unlock()
p.buffer.serialMutex.Unlock() p.buffer.serialMutex.Unlock()
return errors.New("same data") return errors.New("same data")
} }
logrus.Debug("") logrus.Debug("")
logrus.Debugf("MEAS old: %v", p.buffer.LastMeasTcp) logrus.Debugf("SER old: %v", p.buffer.LastMeasSerial)
logrus.Debugf("MEAS new: %v", p.buffer.MeasTcp) logrus.Debugf("SER new: %v", p.buffer.MeasSerial)
logrus.Debug("") logrus.Debugf("TCP old: %v", p.buffer.LastMeasTcp)
p.buffer.LastMeasTcp = p.buffer.MeasTcp logrus.Debugf("TCP new: %v", p.buffer.MeasTcp)
p.buffer.LastMeasSerial = p.buffer.MeasSerial logrus.Debug("")
p.storer.EnqueuePair(p.buffer.MeasTcp, p.buffer.MeasSerial) p.buffer.LastMeasTcp = p.buffer.MeasTcp
p.buffer.LastMeasSerial = p.buffer.MeasSerial
p.storer.EnqueuePair(p.buffer.MeasTcp, p.buffer.MeasSerial)
data := map[string]sensorData{ data := map[string]sensorData{
string(SOURCE_TCP): p.buffer.MeasTcp, string(SOURCE_TCP): p.buffer.MeasTcp,
string(SOURCE_SERIAL): p.buffer.MeasSerial, string(SOURCE_SERIAL): p.buffer.MeasSerial,
} }
p.buffer.tcpMutex.Unlock() p.buffer.tcpMutex.Unlock()
p.buffer.serialMutex.Unlock() p.buffer.serialMutex.Unlock()
jdata, err := json.Marshal(data) jdata, err := json.Marshal(data)
//logrus.Println(string(pretty.Pretty(jdata))) //logrus.Println(string(pretty.Pretty(jdata)))
if err != nil { if err != nil {
return err return err
} }
p.publisher.Publish(string(jdata)) p.publisher.Publish(string(jdata))
return nil return nil
} }
type pipeBuffer struct { type pipeBuffer struct {
MeasTcp sensorData MeasTcp sensorData
MeasSerial sensorData MeasSerial sensorData
LastMeasTcp sensorData LastMeasTcp sensorData
LastMeasSerial sensorData LastMeasSerial sensorData
tcpMutex *sync.Mutex tcpMutex *sync.Mutex
serialMutex *sync.Mutex serialMutex *sync.Mutex
} }
type UnixNanoTime int64 type UnixNanoTime int64
type synchronizer struct { type synchronizer struct {
tcpSerialDelayMs int64 tcpSerialDelayMs int64
mutex *sync.RWMutex mutex *sync.RWMutex
updateTicker *time.Ticker updateTicker *time.Ticker
} }
func (p *pipeline) refreshDelay() error { func (p *pipeline) refreshDelay() error {
p.synchroniz.mutex.RLock() p.synchroniz.mutex.RLock()
if p.synchroniz.tcpSerialDelayMs != 0 { if p.synchroniz.tcpSerialDelayMs != 0 {
logrus.Println("Delay TCP/SERIAL", p.synchroniz.tcpSerialDelayMs) logrus.Println("Delay TCP/SERIAL", p.synchroniz.tcpSerialDelayMs)
} }
p.synchroniz.mutex.RUnlock() p.synchroniz.mutex.RUnlock()
p.buffer.serialMutex.Lock() p.buffer.serialMutex.Lock()
p.buffer.tcpMutex.Lock() p.buffer.tcpMutex.Lock()
tcpTime := time.Unix(0, p.buffer.MeasTcp.Timestamp) tcpTime := time.Unix(0, p.buffer.MeasTcp.Timestamp)
serTime := time.Unix(0, p.buffer.MeasSerial.Timestamp) serTime := time.Unix(0, p.buffer.MeasSerial.Timestamp)
p.buffer.tcpMutex.Unlock() p.buffer.tcpMutex.Unlock()
p.buffer.serialMutex.Unlock() p.buffer.serialMutex.Unlock()
if tcpTime.UnixNano() == 0 || serTime.UnixNano() == 0 { if tcpTime.UnixNano() == 0 || serTime.UnixNano() == 0 {
return errors.New("no sync possible. check if both collectors running. otherwise check GPS fix") return errors.New("no sync possible. check if both Collectors running. otherwise check GPS fix")
} }
currentDelay := tcpTime.Sub(serTime).Milliseconds() currentDelay := tcpTime.Sub(serTime).Milliseconds()
if currentDelay > 5000 || currentDelay < -5000 { if currentDelay > 5000 || currentDelay < -5000 {
p.synchroniz.mutex.Lock() p.synchroniz.mutex.Lock()
p.synchroniz.tcpSerialDelayMs = 0 p.synchroniz.tcpSerialDelayMs = 0
p.synchroniz.mutex.Unlock() p.synchroniz.mutex.Unlock()
return errors.New("skipping synchronisation! time not properly configured or facing network problems.") return errors.New("skipping synchronisation! time not properly configured or facing network problems.")
} }
logrus.Debug("TCP", tcpTime.String()) logrus.Debug("TCP", tcpTime.String())
logrus.Debug("SER", serTime.String()) logrus.Debug("SER", serTime.String())
logrus.Debug("Difference", tcpTime.Sub(serTime).Milliseconds(), "ms") logrus.Debug("Difference", tcpTime.Sub(serTime).Milliseconds(), "ms")
delay := tcpTime.Sub(serTime).Milliseconds() delay := tcpTime.Sub(serTime).Milliseconds()
p.synchroniz.mutex.Lock() p.synchroniz.mutex.Lock()
p.synchroniz.tcpSerialDelayMs += delay p.synchroniz.tcpSerialDelayMs += delay
p.synchroniz.mutex.Unlock() p.synchroniz.mutex.Unlock()
return nil return nil
} }
func (p *pipeline) Push(data *sensorData) error { func (p *pipeline) Push(data *sensorData) error {
if data == nil { if data == nil {
return errors.New("nil processing not allowed") return errors.New("nil processing not allowed")
} }
//logrus.Println("push data to pipe:", string(data.source)) //logrus.Println("push data to pipe:", string(data.source))
p.storer.EnqueueRaw(*data) p.storer.EnqueueRaw(*data)
switch data.source { switch data.source {
case SOURCE_TCP: case SOURCE_TCP:
go p.pushTcpDataToBuffer(*data) go p.pushTcpDataToBuffer(*data)
case SOURCE_SERIAL: case SOURCE_SERIAL:
go p.pushSerialDataToBuffer(*data) go p.pushSerialDataToBuffer(*data)
default: default:
panic("pipe: invalid data source") panic("pipe: invalid data source")
} }
return nil return nil
} }
func (p *pipeline) pushTcpDataToBuffer(data sensorData) { func (p *pipeline) pushTcpDataToBuffer(data sensorData) {
p.synchroniz.mutex.RLock() p.synchroniz.mutex.RLock()
if p.synchroniz.tcpSerialDelayMs > 0 { if p.synchroniz.tcpSerialDelayMs > 0 {
time.Sleep(time.Duration(p.synchroniz.tcpSerialDelayMs) * time.Millisecond) time.Sleep(time.Duration(p.synchroniz.tcpSerialDelayMs) * time.Millisecond)
} }
p.synchroniz.mutex.RLock() p.synchroniz.mutex.RLock()
p.buffer.tcpMutex.Lock() p.buffer.tcpMutex.Lock()
p.buffer.MeasTcp = p.buffer.MeasTcp.ConsolidateExTime(data) p.buffer.MeasTcp = p.buffer.MeasTcp.ConsolidateExTime(data)
p.buffer.tcpMutex.Unlock() p.buffer.tcpMutex.Unlock()
} }
func (p *pipeline) pushSerialDataToBuffer(data sensorData) { func (p *pipeline) pushSerialDataToBuffer(data sensorData) {
p.synchroniz.mutex.RLock() p.synchroniz.mutex.RLock()
if p.synchroniz.tcpSerialDelayMs < 0 { if p.synchroniz.tcpSerialDelayMs < 0 {
time.Sleep(time.Duration(-p.synchroniz.tcpSerialDelayMs) * time.Millisecond) time.Sleep(time.Duration(-p.synchroniz.tcpSerialDelayMs) * time.Millisecond)
} }
p.synchroniz.mutex.RUnlock() p.synchroniz.mutex.RUnlock()
p.buffer.serialMutex.Lock() p.buffer.serialMutex.Lock()
p.buffer.MeasSerial = p.buffer.MeasSerial.ConsolidateEpochsOnly(data) p.buffer.MeasSerial = p.buffer.MeasSerial.ConsolidateEpochsOnly(data)
p.buffer.serialMutex.Unlock() p.buffer.serialMutex.Unlock()
} }
func (p *pipeline) Close() { func (p *pipeline) Close() {
p.active = false p.mu.Lock()
p.active = false
p.mu.Unlock()
} }

View File

@ -1,174 +1,174 @@
package core package core
import ( import (
"errors" "errors"
"git.timovolkmann.de/gyrogpsc/ublox" "git.timovolkmann.de/gyrogpsc/ublox"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"math" "math"
"time" "time"
) )
type sourceId string type sourceId string
const ( const (
SOURCE_TCP sourceId = "SOURCE_TCP" SOURCE_TCP sourceId = "SOURCE_TCP"
SOURCE_SERIAL sourceId = "SOURCE_SERIAL" SOURCE_SERIAL sourceId = "SOURCE_SERIAL"
) )
type sensorData struct { type sensorData struct {
itow uint32 itow uint32
source sourceId source sourceId
ServerTime time.Time ServerTime time.Time
Timestamp int64 Timestamp int64
Position [3]float64 Position [3]float64
Orientation [3]float64 Orientation [3]float64
} }
type recordPair struct { type recordPair struct {
RecordTime time.Time RecordTime time.Time
data map[sourceId]sensorData data map[sourceId]sensorData
} }
type rawRecord struct { type rawRecord struct {
RecordTime time.Time RecordTime time.Time
sensorData sensorData
} }
func (s sensorData) isSameEpoch(n sensorData) bool { func (s sensorData) isSameEpoch(n sensorData) bool {
if n.itow == 0 { if n.itow == 0 {
return false return false
} }
return s.itow == n.itow return s.itow == n.itow
} }
// Consolidates two sensordata elements if they are in the same epoch // Consolidates two sensordata elements if they are in the same epoch
func (s sensorData) ConsolidateEpochsOnly(n sensorData) sensorData { func (s sensorData) ConsolidateEpochsOnly(n sensorData) sensorData {
s.checkSources(&n) s.checkSources(&n)
if s.isSameEpoch(n) { if s.isSameEpoch(n) {
null := sensorData{} null := sensorData{}
if n.Timestamp == null.Timestamp { if n.Timestamp == null.Timestamp {
n.Timestamp = s.Timestamp n.Timestamp = s.Timestamp
} }
if n.Position == null.Position { if n.Position == null.Position {
n.Position = s.Position n.Position = s.Position
} }
if n.Orientation == null.Orientation { if n.Orientation == null.Orientation {
n.Orientation = s.Orientation n.Orientation = s.Orientation
} }
} }
return n return n
} }
// Consolidates two sensordata elements but ignores timestamps // Consolidates two sensordata elements but ignores timestamps
func (s sensorData) ConsolidateExTime(n sensorData) sensorData { func (s sensorData) ConsolidateExTime(n sensorData) sensorData {
s.checkSources(&n) s.checkSources(&n)
null := sensorData{} null := sensorData{}
if n.Position == null.Position { if n.Position == null.Position {
n.Position = s.Position n.Position = s.Position
} }
if n.Orientation == null.Orientation { if n.Orientation == null.Orientation {
n.Orientation = s.Orientation n.Orientation = s.Orientation
} }
return n return n
} }
func (s *sensorData) checkSources(n *sensorData) { func (s *sensorData) checkSources(n *sensorData) {
if (s.source != n.source && *s != sensorData{}) { if (s.source != n.source && *s != sensorData{}) {
logrus.Println(s) logrus.Println(s)
logrus.Println(n) logrus.Println(n)
logrus.Fatalln("Do not consolidate sensorData from different Sources") logrus.Fatalln("Do not consolidate sensorData from different Sources")
} }
} }
var ( var (
errNotImplemented = errors.New("message not implemented") errNotImplemented = errors.New("message not implemented")
errRawMessage = errors.New("raw message") errRawMessage = errors.New("raw message")
) )
func ConvertUbxToSensorData(msg interface{}) (*sensorData, error) { func ConvertUbxToSensorData(msg interface{}) (*sensorData, error) {
sd := &sensorData{ sd := &sensorData{
ServerTime: time.Now(), ServerTime: time.Now(),
source: SOURCE_SERIAL, source: SOURCE_SERIAL,
} }
switch v := msg.(type) { switch v := msg.(type) {
case *ublox.NavPvt: case *ublox.NavPvt:
//logrus.Println("NAV-PVT") //logrus.Println("NAV-PVT")
sd.itow = v.ITOW_ms sd.itow = v.ITOW_ms
sd.Timestamp = time.Date(int(v.Year_y), time.Month(v.Month_month), int(v.Day_d), int(v.Hour_h), int(v.Min_min), int(v.Sec_s), int(v.Nano_ns), time.UTC).UnixNano() sd.Timestamp = time.Date(int(v.Year_y), time.Month(v.Month_month), int(v.Day_d), int(v.Hour_h), int(v.Min_min), int(v.Sec_s), int(v.Nano_ns), time.UTC).UnixNano()
sd.Position[0] = float64(v.Lat_dege7) / 1e+7 sd.Position[0] = float64(v.Lat_dege7) / 1e+7
sd.Position[1] = float64(v.Lon_dege7) / 1e+7 sd.Position[1] = float64(v.Lon_dege7) / 1e+7
sd.Position[2] = float64(v.HMSL_mm) / 1e+3 // mm in m sd.Position[2] = float64(v.HMSL_mm) / 1e+3 // mm in m
case *ublox.HnrPvt: case *ublox.HnrPvt:
//logrus.Println("HNR-PVT") //logrus.Println("HNR-PVT")
sd.itow = v.ITOW_ms sd.itow = v.ITOW_ms
sd.Timestamp = time.Date(int(v.Year_y), time.Month(v.Month_month), int(v.Day_d), int(v.Hour_h), int(v.Min_min), int(v.Sec_s), int(v.Nano_ns), time.UTC).UnixNano() sd.Timestamp = time.Date(int(v.Year_y), time.Month(v.Month_month), int(v.Day_d), int(v.Hour_h), int(v.Min_min), int(v.Sec_s), int(v.Nano_ns), time.UTC).UnixNano()
sd.Position[0] = float64(v.Lat_dege7) / 1e+7 sd.Position[0] = float64(v.Lat_dege7) / 1e+7
sd.Position[1] = float64(v.Lon_dege7) / 1e+7 sd.Position[1] = float64(v.Lon_dege7) / 1e+7
sd.Position[2] = float64(v.HMSL_mm) / 1e+3 // mm in m sd.Position[2] = float64(v.HMSL_mm) / 1e+3 // mm in m
case *ublox.NavAtt: case *ublox.NavAtt:
//logrus.Println("NAV-ATT") //logrus.Println("NAV-ATT")
sd.itow = v.ITOW_ms sd.itow = v.ITOW_ms
sd.Orientation[0] = float64(v.Pitch_deg) * 1e-5 sd.Orientation[0] = float64(v.Pitch_deg) * 1e-5
sd.Orientation[1] = float64(v.Roll_deg) * 1e-5 sd.Orientation[1] = float64(v.Roll_deg) * 1e-5
sd.Orientation[2] = float64(v.Heading_deg) * 1e-5 sd.Orientation[2] = float64(v.Heading_deg) * 1e-5
case *ublox.RawMessage: case *ublox.RawMessage:
//class := make([]byte, 2) //class := make([]byte, 2)
//binary.LittleEndian.PutUint16(class, v.ClassID()) //binary.LittleEndian.PutUint16(class, v.ClassID())
//logrus.Printf("%#v, %#v", class[0],class[1]) //logrus.Printf("%#v, %#v", class[0],class[1])
return nil, nil return nil, nil
default: default:
return nil, errNotImplemented return nil, errNotImplemented
} }
return sd, nil return sd, nil
} }
func ConvertSensorDataPhone(jsonData []byte) (*sensorData, error) { func ConvertSensorDataPhone(jsonData []byte) (*sensorData, error) {
if gjson.Get(string(jsonData), "os").String() == "hyperimu" { if gjson.Get(string(jsonData), "os").String() == "hyperimu" {
return convertAndroidHyperImu(jsonData) return convertAndroidHyperImu(jsonData)
} }
return convertIPhoneSensorLog(jsonData) return convertIPhoneSensorLog(jsonData)
} }
func convertIPhoneSensorLog(jsonData []byte) (*sensorData, error) { func convertIPhoneSensorLog(jsonData []byte) (*sensorData, error) {
timestamp := gjson.Get(string(jsonData), "locationTimestamp_since1970").Float() timestamp := gjson.Get(string(jsonData), "locationTimestamp_since1970").Float()
lat := gjson.Get(string(jsonData), "locationLatitude").Float() lat := gjson.Get(string(jsonData), "locationLatitude").Float()
lon := gjson.Get(string(jsonData), "locationLongitude").Float() lon := gjson.Get(string(jsonData), "locationLongitude").Float()
alt := gjson.Get(string(jsonData), "locationAltitude").Float() alt := gjson.Get(string(jsonData), "locationAltitude").Float()
pitch := gjson.Get(string(jsonData), "motionPitch").Float() * 180 / math.Pi pitch := gjson.Get(string(jsonData), "motionPitch").Float() * 180 / math.Pi
roll := gjson.Get(string(jsonData), "motionRoll").Float() * 180 / math.Pi roll := gjson.Get(string(jsonData), "motionRoll").Float() * 180 / math.Pi
yaw := gjson.Get(string(jsonData), "motionYaw").Float() * 180 / math.Pi yaw := gjson.Get(string(jsonData), "motionYaw").Float() * 180 / math.Pi
sd := &sensorData{ sd := &sensorData{
ServerTime: time.Now(), ServerTime: time.Now(),
source: SOURCE_TCP, source: SOURCE_TCP,
Timestamp: int64(timestamp * float64(time.Second)), Timestamp: int64(timestamp * float64(time.Second)),
Position: [3]float64{lat, lon, alt}, Position: [3]float64{lat, lon, alt},
Orientation: [3]float64{pitch, roll, yaw}, Orientation: [3]float64{pitch, roll, yaw},
//Timestamp: time.Unix(0, prep.Timestamp * int64(time.Millisecond)), //Timestamp: time.Unix(0, prep.Timestamp * int64(time.Millisecond)),
} }
//logrus.Println(string(pretty.Pretty(jsonData))) //logrus.Println(string(pretty.Pretty(jsonData)))
//logrus.Println(sd) //logrus.Println(sd)
return sd, nil return sd, nil
} }
func convertAndroidHyperImu(jsonData []byte) (*sensorData, error) { func convertAndroidHyperImu(jsonData []byte) (*sensorData, error) {
timestamp := gjson.Get(string(jsonData), "Timestamp").Int() timestamp := gjson.Get(string(jsonData), "Timestamp").Int()
lat := gjson.Get(string(jsonData), "GPS.0").Float() lat := gjson.Get(string(jsonData), "GPS.0").Float()
lon := gjson.Get(string(jsonData), "GPS.1").Float() lon := gjson.Get(string(jsonData), "GPS.1").Float()
alt := gjson.Get(string(jsonData), "GPS.2").Float() alt := gjson.Get(string(jsonData), "GPS.2").Float()
pitch := gjson.Get(string(jsonData), "orientation.0").Float() pitch := gjson.Get(string(jsonData), "orientation.0").Float()
roll := gjson.Get(string(jsonData), "orientation.1").Float() roll := gjson.Get(string(jsonData), "orientation.1").Float()
yaw := gjson.Get(string(jsonData), "orientation.2").Float() yaw := gjson.Get(string(jsonData), "orientation.2").Float()
sd := &sensorData{ sd := &sensorData{
ServerTime: time.Now(), ServerTime: time.Now(),
source: SOURCE_TCP, source: SOURCE_TCP,
Timestamp: timestamp * int64(time.Millisecond), Timestamp: timestamp * int64(time.Millisecond),
Position: [3]float64{lat, lon, alt}, Position: [3]float64{lat, lon, alt},
Orientation: [3]float64{pitch, roll, yaw}, Orientation: [3]float64{pitch, roll, yaw},
//Timestamp: time.Unix(0, prep.Timestamp * int64(time.Millisecond)), //Timestamp: time.Unix(0, prep.Timestamp * int64(time.Millisecond)),
} }
return sd, nil return sd, nil
} }

View File

@ -1,104 +1,149 @@
package core package core
import ( import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"time" "time"
) )
type OpMode uint8 type OpMode uint8
const ( const (
STOPPED OpMode = iota STOPPED OpMode = iota
LIVE LIVE
REPLAY RECORDING
REPLAY
) )
type trackingService struct { type trackingService struct {
current *Tracking current *Tracking
config *Configuration config *Configuration
pipe *pipeline pipe *pipeline
repo Repo repo Repo
opMode OpMode opMode OpMode
collectors []Collector collectors []Collector
} }
func TrackingService(r Repo, d Publisher, c *Configuration) *trackingService { func TrackingService(r Repo, d Publisher, c *Configuration) *trackingService {
t := &Tracking{} t := &Tracking{}
return &trackingService{ ts := &trackingService{
current: t, current: t,
opMode: STOPPED, opMode: STOPPED,
config: c, config: c,
repo: r, repo: r,
pipe: NewPipeline(d, t, c), pipe: NewPipeline(d, t, c),
collectors: nil, collectors: nil,
} }
//ts.pipe.Run()
return ts
} }
//const(
// errA error = errors.New("A")
//)
func (t *trackingService) AllTrackings() { func (t *trackingService) AllTrackings() {
panic("implement me") panic("implement me")
} }
func (t *trackingService) NewTracking(cols ...CollectorType) { func (t *trackingService) NewSetup(cols ...CollectorType) {
logrus.Debug("new tracking:", cols) logrus.Info("SERVICE: NEW SETUP")
t.opMode = LIVE if t.opMode == RECORDING {
t.collectors = nil logrus.Println("trackingservice: no reset while recording")
for _, col := range cols { return
t.collectors = append(t.collectors, NewCollector(col, t.pipe, t.config)) }
} if t.opMode == LIVE {
*t.current = emptyTracking() logrus.Println("trackingservice: stop currently running setup before creating new one")
t.current.collectors = cols t.StopAll()
for _, e := range t.collectors { }
e.Collect() logrus.Debug("new tracking:", cols)
} t.opMode = LIVE
t.pipe.Run() t.collectors = nil
for _, col := range cols {
t.collectors = append(t.collectors, NewCollector(col, t.pipe, t.config))
}
t.safelyReplaceTracking(emptyTracking())
t.current.Collectors = cols
for _, e := range t.collectors {
e.Collect()
}
t.pipe.Run()
//time.Sleep(3 * time.Second)
} }
func (t *trackingService) StartRecord() { func (t *trackingService) StartRecord() {
if t.opMode != LIVE { logrus.Info("SERVICE: START RECORD")
logrus.Println("trackingservice: wrong mode of operation") if t.opMode != LIVE {
} logrus.Println("trackingservice: wrong mode of operation")
t.current.TimeCreated = time.Now() return
t.pipe.Record() }
t.opMode = RECORDING
t.current.TimeCreated = time.Now()
t.pipe.Record()
} }
func (t *trackingService) StopRecord() { func (t *trackingService) StopRecord() {
if t.opMode != LIVE { logrus.Info("SERVICE: STOP RECORD")
logrus.Println("trackingservice: wrong mode of operation") if t.opMode != RECORDING {
} logrus.Println("trackingservice: couldn't stop. not recording")
t.pipe.Stop() return
for _, e := range t.collectors { }
e.Stop() t.opMode = LIVE
} t.pipe.StopRecord()
err := t.repo.Save(*t.current)
if err != nil { m1.Lock()
logrus.Println(err) m2.Lock()
} err := t.repo.Save(*t.current)
t.NewTracking(t.current.collectors...) m2.Unlock()
m1.Unlock()
if err != nil {
logrus.Println(err)
}
t.safelyReplaceTracking(emptyTracking())
} }
func (t *trackingService) Reset() { func (t *trackingService) StopAll() {
t.opMode = STOPPED logrus.Info("SERVICE: STOP ALL")
t.collectors = nil if t.opMode == RECORDING {
logrus.Println("trackingservice: stop recording gracefully")
t.StopRecord()
}
t.opMode = STOPPED
t.pipe.Close()
for _, e := range t.collectors {
e.Close()
}
t.collectors = nil
t.safelyReplaceTracking(emptyTracking())
} }
func (t *trackingService) DeleteTracking(trackingId uuid.UUID) { func (t *trackingService) DeleteTracking(trackingId uuid.UUID) {
panic("implement me") panic("implement me")
} }
func (t *trackingService) StartReplay() { func (t *trackingService) StartReplay() {
panic("implement me") panic("implement me")
} }
func (t *trackingService) PauseReplay() { func (t *trackingService) PauseReplay() {
panic("implement me") panic("implement me")
} }
func (t *trackingService) StopReplay() { func (t *trackingService) StopReplay() {
panic("implement me") panic("implement me")
} }
func (t *trackingService) LoadTracking(trackingId uuid.UUID) { func (t *trackingService) LoadTracking(trackingId uuid.UUID) {
} }
func (t *trackingService) safelyReplaceTracking(tr Tracking) {
m1.Lock()
m2.Lock()
*t.current = tr
m2.Unlock()
m1.Unlock()
}

View File

@ -1,60 +1,62 @@
package core package core
import ( import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"sync" "sync"
"time" "time"
) )
var m1 sync.RWMutex
var m2 sync.RWMutex
type Tracking struct { type Tracking struct {
TrackingMetadata TrackingMetadata
Records []recordPair Records []recordPair
Rawdata []rawRecord Rawdata []rawRecord
mu sync.Mutex
} }
type TrackingMetadata struct { type TrackingMetadata struct {
UUID uuid.UUID UUID uuid.UUID
TimeCreated time.Time TimeCreated time.Time
collectors []CollectorType Collectors []CollectorType
} }
func (s *Tracking) EnqueuePair(tcp sensorData, ser sensorData) { func (s *Tracking) EnqueuePair(tcp sensorData, ser sensorData) {
s.mu.Lock() rp := recordPair{
defer s.mu.Unlock() RecordTime: time.Now(),
rp := recordPair{ data: map[sourceId]sensorData{
RecordTime: time.Now(), tcp.source: tcp,
data: map[sourceId]sensorData{ ser.source: ser,
tcp.source: tcp, },
ser.source: ser, }
}, m1.Lock()
} s.Records = append(s.Records, rp)
s.Records = append(s.Records, rp) logrus.Debugln("tracking Records: len->", len(s.Records))
logrus.Debugln("tracking Records: len->", len(s.Records)) m1.Unlock()
} }
func (s *Tracking) EnqueueRaw(data sensorData) { func (s *Tracking) EnqueueRaw(data sensorData) {
s.mu.Lock() sr := rawRecord{
defer s.mu.Unlock() time.Now(),
sr := rawRecord{ data,
time.Now(), }
data, m1.Lock()
} s.Rawdata = append(s.Rawdata, sr)
s.Rawdata = append(s.Rawdata, sr) logrus.Debugln("raw data points: len->", len(s.Rawdata))
logrus.Debugln("raw data points: len->", len(s.Records)) m1.Unlock()
} }
func emptyTracking() Tracking { func emptyTracking() Tracking {
return Tracking{ return Tracking{
TrackingMetadata: TrackingMetadata{ TrackingMetadata: TrackingMetadata{
UUID: uuid.New(), UUID: uuid.New(),
}, },
Records: []recordPair{}, Records: []recordPair{},
Rawdata: []rawRecord{}, Rawdata: []rawRecord{},
} }
} }
func (s *Tracking) isEmpty() bool { func (s *Tracking) isEmpty() bool {
return len(s.Rawdata)+len(s.Records) == 0 return len(s.Rawdata)+len(s.Records) == 0
} }

1
go.mod
View File

@ -14,4 +14,5 @@ require (
github.com/tidwall/gjson v1.6.0 github.com/tidwall/gjson v1.6.0
github.com/tidwall/pretty v1.0.2 // indirect github.com/tidwall/pretty v1.0.2 // indirect
go.bug.st/serial v1.1.1 go.bug.st/serial v1.1.1
golang.org/x/sync v0.0.0-20190423024810-112230192c58
) )

1
go.sum
View File

@ -304,6 +304,7 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View File

@ -1,33 +1,197 @@
package storage package storage
import ( import (
"git.timovolkmann.de/gyrogpsc/core" "encoding/binary"
"github.com/dgraph-io/badger/v2" "encoding/json"
"github.com/google/uuid" "git.timovolkmann.de/gyrogpsc/core"
"github.com/sirupsen/logrus" "github.com/dgraph-io/badger/v2"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
"os"
"path/filepath"
) )
// Must implement Repo // Must implement Repo
type badgerStore struct { type badgerStore struct {
db *badger.DB trackings *badger.DB
records *badger.DB
rawdata *badger.DB
} }
func NewRepository(c *core.Configuration) *badgerStore { func NewRepository(c *core.Configuration) *badgerStore {
db, err := badger.Open(badger.DefaultOptions(".")) dir, _ := os.Getwd()
if err != nil { logrus.Debug(dir)
logrus.Warn(err) if _, err := os.Stat(filepath.Join(dir,"_db")); os.IsNotExist(err) {
} os.Mkdir(filepath.Join(dir,"_db"), os.ModePerm)
return &badgerStore{db} }
tr, err := badger.Open(badger.DefaultOptions("_db/trackings"))
dp, err := badger.Open(badger.DefaultOptions("_db/records"))
rd, err := badger.Open(badger.DefaultOptions("_db/raw"))
if err != nil {
logrus.Error(err)
}
return &badgerStore{trackings: tr, records: dp, rawdata: rd}
} }
func (r *badgerStore) Save(tracking core.Tracking) error { func (r *badgerStore) isDbAvailable() bool {
panic("implement me") return r.trackings.IsClosed() || r.records.IsClosed() || r.rawdata.IsClosed()
} }
func (r *badgerStore) Save(tr core.Tracking) error {
if ok := r.isDbAvailable(); ok {
logrus.Error("unable to write to database. database closed!")
return badger.ErrDBClosed
}
ts, err := tr.TimeCreated.MarshalText()
if err != nil {
logrus.Error(err, tr)
}
logrus.Info("save tracking:", tr.TimeCreated)
meta, err := json.Marshal(tr.TrackingMetadata)
if err != nil {
logrus.Error(err, tr)
return err
}
err = r.records.Update(func(txn *badger.Txn) error {
for _, v := range tr.Records {
k := createDataKey(tr.UUID, v.RecordTime.UnixNano())
j, err := json.Marshal(v)
if err != nil {
return err
}
txn.Set(k, j)
}
return nil
})
if err != nil {
logrus.Error(err, tr)
return err
}
err = r.records.Update(func(txn *badger.Txn) error {
for _, v := range tr.Rawdata {
k := createDataKey(tr.UUID, v.Timestamp)
j, err := json.Marshal(v)
if err != nil {
return err
}
txn.Set(k, j)
}
return nil
})
if err != nil {
logrus.Error(err, tr)
return err
}
err = r.trackings.Update(func(txn *badger.Txn) error {
err := txn.Set(ts, meta)
return err
})
if err != nil {
logrus.Error(err, tr)
return err
}
logrus.Info("sucessfully saved tracking")
return nil
}
//func (r *badgerStore) Save(tracking *core.Tracking) error {
// ts, err := tracking.TimeCreated.MarshalText()
// if err != nil {
// logrus.Error(err, tracking)
// }
// logrus.Info("save tracking:", ts)
// meta, err := json.Marshal(tracking.TrackingMetadata)
// if err != nil {
// logrus.Error(err, tracking)
// return err
// }
// wg := sync.WaitGroup{}
// wg.Add(3)
// ch := make(chan error, 3)
// go func() {
// defer wg.Done()
// err = r.records.Update(func(txn *badger.Txn) error {
// for _, v := range tracking.Records {
// k := createDataKey(tracking.UUID, v.RecordTime.UnixNano())
// j, err := json.Marshal(v)
// if err != nil {
// return err
// }
// txn.Set(k, j)
// }
// return nil
// })
// ch <- err
// }()
// go func() {
// defer wg.Done()
// err = r.records.Update(func(txn *badger.Txn) error {
// for _, v := range tracking.Rawdata {
// k := createDataKey(tracking.UUID, v.Timestamp)
// j, err := json.Marshal(v)
// if err != nil {
// return err
// }
// txn.Set(k, j)
// }
// return nil
// })
// ch <- err
// }()
// go func() {
// defer wg.Done()
// err = r.trackings.Update(func(txn *badger.Txn) error {
// err := txn.Set(ts, meta)
// return err
// })
// ch <- err
// }()
// wg.Wait()
// for {
// select {
// case err := <-ch:
// if err != nil {
// logrus.Error(err, tracking)
// return err
// }
// default:
// close(ch)
// break
// }
// }
// return nil
//}
func (r *badgerStore) LoadAll() ([]core.TrackingMetadata, error) { func (r *badgerStore) LoadAll() ([]core.TrackingMetadata, error) {
panic("implement me") panic("implement me")
} }
func (r *badgerStore) Load(id uuid.UUID) (core.Tracking, error) { func (r *badgerStore) Load(id uuid.UUID) (core.Tracking, error) {
panic("implement me") panic("implement me")
}
func createDataKey(uid uuid.UUID, timestamp int64) []byte {
prefix, err := uid.MarshalText()
if err != nil || timestamp < 0 {
logrus.Error("unable to create key", err)
}
suffix := make([]byte, 8)
binary.LittleEndian.PutUint64(suffix, uint64(timestamp))
return append(prefix, suffix...)
}
func unmarshalDataKey(key []byte) (uuid.UUID, int64) {
if len(key) != 24 {
panic("corrupted key")
}
prefix := key[0:15]
suffix := key[15:24]
uid, err := uuid.FromBytes(prefix)
if err != nil {
panic("corrupted key")
}
timestamp := int64(binary.LittleEndian.Uint64(suffix))
return uid, timestamp
} }

View File

@ -6,152 +6,152 @@
package ublox package ublox
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"io" "io"
) )
// A decoder scans an io stream into UBX (0xB5-0x62 separated) or NMEA ("$xxx,,,,*FF\r\n") frames. // A decoder scans an io stream into UBX (0xB5-0x62 separated) or NMEA ("$xxx,,,,*FF\r\n") frames.
// If you have an unmixed stream of NMEA-only data you can use nmea.Decode() on bufio.Scanner.Bytes() directly. // If you have an unmixed stream of NMEA-only data you can use nmea.Decode() on bufio.Scanner.Bytes() directly.
type decoder struct { type decoder struct {
s *bufio.Scanner s *bufio.Scanner
} }
// NewDecoder creates a new bufio Scanner with a splitfunc that can handle both UBX and NMEA frames. // NewDecoder creates a new bufio Scanner with a splitfunc that can handle both UBX and NMEA frames.
func NewDecoder(r io.Reader) *decoder { func NewDecoder(r io.Reader) *decoder {
d := bufio.NewScanner(r) d := bufio.NewScanner(r)
d.Split(splitFunc) d.Split(splitFunc)
return &decoder{s: d} return &decoder{s: d}
} }
// Assume we're either at the start of an NMEA sentence or at the start of a UBX message // Assume we're either at the start of an NMEA sentence or at the start of a UBX message
// if not, skip to the first $ or UBX SOM. // if not, skip to the first $ or UBX SOM.
func splitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) { func splitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) {
if len(data) == 0 { if len(data) == 0 {
return 0, nil, nil return 0, nil, nil
} }
switch data[0] { switch data[0] {
case '$': case '$':
return bufio.ScanLines(data, atEOF) return bufio.ScanLines(data, atEOF)
case 0xB5: case 0xB5:
if len(data) < 8 { if len(data) < 8 {
if atEOF { if atEOF {
return len(data), nil, io.ErrUnexpectedEOF return len(data), nil, io.ErrUnexpectedEOF
} }
return 0, nil, nil return 0, nil, nil
} }
sz := 8 + int(data[4]) + int(data[5])*256 sz := 8 + int(data[4]) + int(data[5])*256
if data[1] == 0x62 { if data[1] == 0x62 {
if sz <= len(data) { if sz <= len(data) {
return sz, data[:sz], nil return sz, data[:sz], nil
} }
if sz <= bufio.MaxScanTokenSize { if sz <= bufio.MaxScanTokenSize {
return 0, nil, nil return 0, nil, nil
} }
} }
} }
// resync to SOM or $ // resync to SOM or $
data = data[1:] data = data[1:]
i1 := bytes.IndexByte(data, '$') i1 := bytes.IndexByte(data, '$')
if i1 < 0 { if i1 < 0 {
i1 = len(data) i1 = len(data)
} }
i2 := bytes.IndexByte(data, 0xB5) i2 := bytes.IndexByte(data, 0xB5)
if i2 < 0 { if i2 < 0 {
i2 = len(data) i2 = len(data)
} }
if i1 > i2 { if i1 > i2 {
i1 = i2 i1 = i2
} }
return 1 + i1, nil, nil return 1 + i1, nil, nil
} }
// Decode reads on NMEA or UBX frame and calls decodeUbx accordingly to parse the message, while skipping NMEA. // Decode reads on NMEA or UBX frame and calls decodeUbx accordingly to parse the message, while skipping NMEA.
func (d *decoder) Decode() (msg interface{}, err error) { func (d *decoder) Decode() (msg interface{}, err error) {
if !d.s.Scan() { if !d.s.Scan() {
if err = d.s.Err(); err == nil { if err = d.s.Err(); err == nil {
err = io.EOF err = io.EOF
} }
return nil, err return nil, err
} }
switch d.s.Bytes()[0] { switch d.s.Bytes()[0] {
case '$': case '$':
return nil, errors.New("NMEA not implemented") return nil, errors.New("NMEA not implemented")
//return nmea.Decode(d.s.Bytes()) //return nmea.Decode(d.s.Bytes())
case 0xB5: case 0xB5:
return decodeUbx(d.s.Bytes()) return decodeUbx(d.s.Bytes())
} }
panic("impossible frame") panic("impossible frame")
} }
var ( var (
errInvalidFrame = errors.New("invalid UBX frame") errInvalidFrame = errors.New("invalid UBX frame")
errInvalidChkSum = errors.New("invalid UBX checksum") errInvalidChkSum = errors.New("invalid UBX checksum")
) )
func decodeUbx(frame []byte) (msg Message, err error) { func decodeUbx(frame []byte) (msg Message, err error) {
buf := bytes.NewReader(frame) buf := bytes.NewReader(frame)
var header struct { var header struct {
Preamble uint16 Preamble uint16
ClassID uint16 ClassID uint16
Length uint16 Length uint16
} }
if err := binary.Read(buf, binary.LittleEndian, &header); err != nil { if err := binary.Read(buf, binary.LittleEndian, &header); err != nil {
return nil, err return nil, err
} }
if header.Preamble != 0x62B5 { if header.Preamble != 0x62B5 {
return nil, errInvalidFrame return nil, errInvalidFrame
} }
if buf.Len()+2 < int(header.Length) { if buf.Len()+2 < int(header.Length) {
return nil, io.ErrShortBuffer return nil, io.ErrShortBuffer
} }
var a, b byte var a, b byte
for _, v := range frame[2 : header.Length+6] { for _, v := range frame[2 : header.Length+6] {
a += byte(v) a += byte(v)
b += a b += a
} }
if frame[header.Length+6] != a || frame[header.Length+7] != b { if frame[header.Length+6] != a || frame[header.Length+7] != b {
return nil, errInvalidChkSum return nil, errInvalidChkSum
} }
switch header.ClassID { switch header.ClassID {
case 0x0105: // ACK-ACK case 0x0105: // ACK-ACK
fmt.Println("ACK-ACK not implemented") fmt.Println("ACK-ACK not implemented")
//msg = &AckAck{} //msg = &AckAck{}
case 0x0005: // ACK-NAK case 0x0005: // ACK-NAK
fmt.Println("ACK-NAK not implemented") fmt.Println("ACK-NAK not implemented")
//msg = &AckNak{} //msg = &AckNak{}
case 0x0701: // NAV-PVT case 0x0701: // NAV-PVT
msg = &NavPvt{} msg = &NavPvt{}
case 0x0028: // HNR-PVT case 0x0028: // HNR-PVT
msg = &HnrPvt{} msg = &HnrPvt{}
case 0x0501: // NAV-ATT case 0x0501: // NAV-ATT
msg = &NavAtt{} msg = &NavAtt{}
default: default:
} }
if msg != nil { if msg != nil {
err = binary.Read(buf, binary.LittleEndian, msg) err = binary.Read(buf, binary.LittleEndian, msg)
} else { } else {
msg = &RawMessage{classID: header.ClassID, Data: append([]byte(nil), frame[6:len(frame)-2]...)} msg = &RawMessage{classID: header.ClassID, Data: append([]byte(nil), frame[6:len(frame)-2]...)}
} }
//fmt.Println(msg) //fmt.Println(msg)
return msg, err return msg, err
} }

View File

@ -1,7 +1,7 @@
package ublox package ublox
type Message interface { type Message interface {
ClassID() uint16 ClassID() uint16
} }
//type UbxMessage interface { //type UbxMessage interface {
@ -11,90 +11,90 @@ type Message interface {
//} //}
type RawMessage struct { type RawMessage struct {
classID uint16 classID uint16
Data []byte Data []byte
} }
func (msg *RawMessage) ClassID() uint16 { return msg.classID } func (msg *RawMessage) ClassID() uint16 { return msg.classID }
type NavPvt struct { type NavPvt struct {
ITOW_ms uint32 // - GPS time of week of the navigation epoch. See the description of iTOW for details. ITOW_ms uint32 // - GPS time of week of the navigation epoch. See the description of iTOW for details.
Year_y uint16 // - Year (UTC) Year_y uint16 // - Year (UTC)
Month_month byte // - Month, range 1..12 (UTC) Month_month byte // - Month, range 1..12 (UTC)
Day_d byte // - Day of month, range 1..31 (UTC) Day_d byte // - Day of month, range 1..31 (UTC)
Hour_h byte // - Hour of day, range 0..23 (UTC) Hour_h byte // - Hour of day, range 0..23 (UTC)
Min_min byte // - Minute of hour, range 0..59 (UTC) Min_min byte // - Minute of hour, range 0..59 (UTC)
Sec_s byte // - Seconds of minute, range 0..60 (UTC) Sec_s byte // - Seconds of minute, range 0..60 (UTC)
Valid NavPVTValid // - Validity flags (see graphic below) Valid NavPVTValid // - Validity flags (see graphic below)
TAcc_ns uint32 // - Time accuracy estimate (UTC) TAcc_ns uint32 // - Time accuracy estimate (UTC)
Nano_ns int32 // - Fraction of second, range -1e9 .. 1e9 (UTC) Nano_ns int32 // - Fraction of second, range -1e9 .. 1e9 (UTC)
FixType NavPVTFixType // - GNSSfix Type FixType NavPVTFixType // - GNSSfix Type
Flags NavPVTFlags // - Fix status flags (see graphic below) Flags NavPVTFlags // - Fix status flags (see graphic below)
Flags2 NavPVTFlags2 // - Additional flags (see graphic below) Flags2 NavPVTFlags2 // - Additional flags (see graphic below)
NumSV byte // - Number of satellites used in Nav Solution NumSV byte // - Number of satellites used in Nav Solution
Lon_dege7 int32 // 1e-7 Longitude Lon_dege7 int32 // 1e-7 Longitude
Lat_dege7 int32 // 1e-7 Latitude Lat_dege7 int32 // 1e-7 Latitude
Height_mm int32 // - Height above ellipsoid Height_mm int32 // - Height above ellipsoid
HMSL_mm int32 // - Height above mean sea level HMSL_mm int32 // - Height above mean sea level
HAcc_mm uint32 // - Horizontal accuracy estimate HAcc_mm uint32 // - Horizontal accuracy estimate
VAcc_mm uint32 // - Vertical accuracy estimate VAcc_mm uint32 // - Vertical accuracy estimate
VelN_mm_s int32 // - NED north velocity VelN_mm_s int32 // - NED north velocity
VelE_mm_s int32 // - NED east velocity VelE_mm_s int32 // - NED east velocity
VelD_mm_s int32 // - NED down velocity VelD_mm_s int32 // - NED down velocity
GSpeed_mm_s int32 // - Ground Speed (2-D) GSpeed_mm_s int32 // - Ground Speed (2-D)
HeadMot_dege5 int32 // 1e-5 Heading of motion (2-D) HeadMot_dege5 int32 // 1e-5 Heading of motion (2-D)
SAcc_mm_s uint32 // - Speed accuracy estimate SAcc_mm_s uint32 // - Speed accuracy estimate
HeadAcc_dege5 uint32 // 1e-5 Heading accuracy estimate (both motion and vehicle) HeadAcc_dege5 uint32 // 1e-5 Heading accuracy estimate (both motion and vehicle)
PDOPe2 uint16 // 0.01 Position DOP PDOPe2 uint16 // 0.01 Position DOP
Flags3 NavPVTFlags3 // - Additional flags (see graphic below) Flags3 NavPVTFlags3 // - Additional flags (see graphic below)
Reserved1 [5]byte // - Reserved Reserved1 [5]byte // - Reserved
HeadVeh_dege5 int32 // 1e-5 Heading of vehicle (2-D), this is only valid when headVehValid is set, otherwise the output is set to the heading of motion HeadVeh_dege5 int32 // 1e-5 Heading of vehicle (2-D), this is only valid when headVehValid is set, otherwise the output is set to the heading of motion
MagDec_dege2 int16 // 1e-2 Magnetic declination. Only supported in ADR 4.10 and later. MagDec_dege2 int16 // 1e-2 Magnetic declination. Only supported in ADR 4.10 and later.
MagAcc_deg2e uint16 // 1e-2 Magnetic declination accuracy. Only supported in ADR 4.10 and later. MagAcc_deg2e uint16 // 1e-2 Magnetic declination accuracy. Only supported in ADR 4.10 and later.
} }
func (NavPvt) ClassID() uint16 { return 0x0701 } func (NavPvt) ClassID() uint16 { return 0x0701 }
type HnrPvt struct { type HnrPvt struct {
ITOW_ms uint32 // - GPS time of week of the navigation epoch. See the description of iTOW for details. ITOW_ms uint32 // - GPS time of week of the navigation epoch. See the description of iTOW for details.
Year_y uint16 // - Year (UTC) Year_y uint16 // - Year (UTC)
Month_month byte // - Month, range 1..12 (UTC) Month_month byte // - Month, range 1..12 (UTC)
Day_d byte // - Day of month, range 1..31 (UTC) Day_d byte // - Day of month, range 1..31 (UTC)
Hour_h byte // - Hour of day, range 0..23 (UTC) Hour_h byte // - Hour of day, range 0..23 (UTC)
Min_min byte // - Minute of hour, range 0..59 (UTC) Min_min byte // - Minute of hour, range 0..59 (UTC)
Sec_s byte // - Seconds of minute, range 0..60 (UTC) Sec_s byte // - Seconds of minute, range 0..60 (UTC)
Valid byte // - Validity flags (see graphic below) Valid byte // - Validity flags (see graphic below)
Nano_ns int32 // - Fraction of second, range -1e9 .. 1e9 (UTC) Nano_ns int32 // - Fraction of second, range -1e9 .. 1e9 (UTC)
FixType byte // - GNSSfix Type FixType byte // - GNSSfix Type
Flags byte // - Fix status flags (see graphic below) Flags byte // - Fix status flags (see graphic below)
Reserved [2]byte Reserved [2]byte
Lon_dege7 int32 // 1e-7 Longitude Lon_dege7 int32 // 1e-7 Longitude
Lat_dege7 int32 // 1e-7 Latitude Lat_dege7 int32 // 1e-7 Latitude
Height_mm int32 // - Height above ellipsoid Height_mm int32 // - Height above ellipsoid
HMSL_mm int32 // - Height above mean sea level HMSL_mm int32 // - Height above mean sea level
GSpeed_mm_s int32 // - Ground Speed (2-D) GSpeed_mm_s int32 // - Ground Speed (2-D)
Speed_mm_s int32 // Speed (3-D) Speed_mm_s int32 // Speed (3-D)
HeadMot_dege5 int32 // 1e-5 Heading of motion (2-D) HeadMot_dege5 int32 // 1e-5 Heading of motion (2-D)
HeadVeh_dege5 int32 // 1e-5 Heading of vehicle (2-D), this is only valid when headVehValid is set, otherwise the output is set to the heading of motion HeadVeh_dege5 int32 // 1e-5 Heading of vehicle (2-D), this is only valid when headVehValid is set, otherwise the output is set to the heading of motion
HAcc uint32 // 1e-5 Heading accuracy estimate (both motion and vehicle) HAcc uint32 // 1e-5 Heading accuracy estimate (both motion and vehicle)
VAcc uint32 // 1e-5 Heading accuracy estimate (both motion and vehicle) VAcc uint32 // 1e-5 Heading accuracy estimate (both motion and vehicle)
SAcc uint32 // 1e-5 Heading accuracy estimate (both motion and vehicle) SAcc uint32 // 1e-5 Heading accuracy estimate (both motion and vehicle)
HeadAcc_dege5 uint32 // 1e-5 Heading accuracy estimate (both motion and vehicle) HeadAcc_dege5 uint32 // 1e-5 Heading accuracy estimate (both motion and vehicle)
Reserved1 [4]byte // - Reserved Reserved1 [4]byte // - Reserved
} }
func (HnrPvt) ClassID() uint16 { return 0x0028 } func (HnrPvt) ClassID() uint16 { return 0x0028 }
type NavAtt struct { type NavAtt struct {
ITOW_ms uint32 // - GPS time of week of the navigation epoch. See the description of iTOW for details. ITOW_ms uint32 // - GPS time of week of the navigation epoch. See the description of iTOW for details.
Version byte Version byte
Reserved1 [3]byte Reserved1 [3]byte
Roll_deg int32 Roll_deg int32
Pitch_deg int32 Pitch_deg int32
Heading_deg int32 Heading_deg int32
AccRoll_deg uint32 AccRoll_deg uint32
AccPitch_deg uint32 AccPitch_deg uint32
AccHeading_deg uint32 AccHeading_deg uint32
} }
func (NavAtt) ClassID() uint16 { return 0x0501 } func (NavAtt) ClassID() uint16 { return 0x0501 }
@ -104,43 +104,43 @@ func (NavAtt) ClassID() uint16 { return 0x0501 }
type NavPVTFixType byte type NavPVTFixType byte
const ( const (
NavPVTNoFix NavPVTFixType = iota NavPVTNoFix NavPVTFixType = iota
NavPVTDeadReckoning NavPVTDeadReckoning
NavPVTFix2D NavPVTFix2D
NavPVTFix3D NavPVTFix3D
NavPVTGNSS NavPVTGNSS
NavPVTTimeOnly NavPVTTimeOnly
) )
type NavPVTValid byte type NavPVTValid byte
const ( const (
NavPVTValidDate NavPVTValid = (1 << iota) // valid UTC Date (see Time Validity section for details) NavPVTValidDate NavPVTValid = (1 << iota) // valid UTC Date (see Time Validity section for details)
NavPVTValidTime // valid UTC time of day (see Time Validity section for details) NavPVTValidTime // valid UTC time of day (see Time Validity section for details)
NavPVTFullyResolved // UTC time of day has been fully resolved (no seconds uncertainty). Cannot be used to check if time is completely solved. NavPVTFullyResolved // UTC time of day has been fully resolved (no seconds uncertainty). Cannot be used to check if time is completely solved.
NavPVTValidMag // valid magnetic declination NavPVTValidMag // valid magnetic declination
) )
type NavPVTFlags byte type NavPVTFlags byte
const ( const (
NavPVTGnssFixOK NavPVTFlags = 1 << 0 // valid fix (i.e within DOP & accuracy masks) NavPVTGnssFixOK NavPVTFlags = 1 << 0 // valid fix (i.e within DOP & accuracy masks)
NavPVTDiffSoln NavPVTFlags = 1 << 1 // differential corrections were applied NavPVTDiffSoln NavPVTFlags = 1 << 1 // differential corrections were applied
NavPVTHeadVehValid NavPVTFlags = 1 << 5 // heading of vehicle is valid, only set if the receiver is in sensor fusion mode NavPVTHeadVehValid NavPVTFlags = 1 << 5 // heading of vehicle is valid, only set if the receiver is in sensor fusion mode
NavPVTCarrSolnFloat NavPVTFlags = 1 << 6 // carrier phase range solution with floating ambiguities NavPVTCarrSolnFloat NavPVTFlags = 1 << 6 // carrier phase range solution with floating ambiguities
NavPVTCarrSolnFixed NavPVTFlags = 1 << 7 // carrier phase range solution with fixed ambiguities NavPVTCarrSolnFixed NavPVTFlags = 1 << 7 // carrier phase range solution with fixed ambiguities
) )
type NavPVTFlags2 byte type NavPVTFlags2 byte
const ( const (
NavPVTConfirmedAvai NavPVTFlags2 = 1 << 5 // information about UTC Date and Time of Day validity confirmation is available (see Time Validity section for details) NavPVTConfirmedAvai NavPVTFlags2 = 1 << 5 // information about UTC Date and Time of Day validity confirmation is available (see Time Validity section for details)
NavPVTConfirmedDate NavPVTFlags2 = 1 << 6 // UTC Date validity could be confirmed (see Time Validity section for details) NavPVTConfirmedDate NavPVTFlags2 = 1 << 6 // UTC Date validity could be confirmed (see Time Validity section for details)
NavPVTConfirmedTime NavPVTFlags2 = 1 << 7 // UTC Time of Day could be confirmed (see Time Validity section for details) NavPVTConfirmedTime NavPVTFlags2 = 1 << 7 // UTC Time of Day could be confirmed (see Time Validity section for details)
) )
type NavPVTFlags3 byte type NavPVTFlags3 byte
const ( const (
NavPVTInvalidLlh NavPVTFlags3 = (1 << iota) // 1 = Invalid lon, lat, height and hMSL NavPVTInvalidLlh NavPVTFlags3 = (1 << iota) // 1 = Invalid lon, lat, height and hMSL
) )

View File

@ -1,85 +1,85 @@
package web package web
import ( import (
"git.timovolkmann.de/gyrogpsc/core" "git.timovolkmann.de/gyrogpsc/core"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/websocket/v2" "github.com/gofiber/websocket/v2"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"html/template" "html/template"
) )
func CreateServer(s core.Service, sub core.Subscriber, c *core.Configuration) { func CreateServer(s core.Service, sub core.Subscriber, c *core.Configuration) {
app := fiber.New() app := fiber.New()
app.Static("/static", "static") app.Static("/static", "static")
// Application Main Page // Application Main Page
app.Get("/", fiberHomeHandler) app.Get("/", fiberHomeHandler)
// Websocket // Websocket
app.Get("/", websocket.New(createFiberWebsocketHandler(sub))) app.Get("/", websocket.New(createFiberWebsocketHandler(sub)))
// TODO: Get all SerialPorts // TODO: Get all SerialPorts
// app.Get("/serialports") // app.Get("/serialports")
// Tracking persistence controls // Tracking persistence controls
trackings := app.Group("/trackings") trackings := app.Group("/trackings")
trackings.Get("/", stubhander()) // Get all trackings Metadata trackings.Get("/", stubhander()) // Get all trackings Metadata
trackings.Post("/", stubhander()) // Initialize new tracking, open websocket and prepare for automatic recording. Toggle ?serial=true and ?tcp=true. Returns trackingId trackings.Post("/", stubhander()) // Initialize new tracking, open websocket and prepare for automatic recording. Toggle ?serial=true and ?tcp=true. Returns trackingId
trackings.Patch("/", stubhander()) // Starts recording trackings.Patch("/", stubhander()) // Starts recording
trackings.Put("/", stubhander()) // Stops current recording. Returns trackingId if record was successful trackings.Put("/", stubhander()) // Stops current recording. Returns trackingId if record was successful
trackings.Delete("/", stubhander()) // Stops websocket connection, pipelines and collectors trackings.Delete("/", stubhander()) // Stops websocket connection, pipelines and collectors
trackings.Get("/:trackingId", stubhander()) // Gets Tracking Metadata and loads sensorRecords from storage. trackings.Get("/:trackingId", stubhander()) // Gets Tracking Metadata and loads sensorRecords from storage.
trackings.Delete("/:trackingId", stubhander()) // Deletes Tracking from storage trackings.Delete("/:trackingId", stubhander()) // Deletes Tracking from storage
trackings.Post("/current", stubhander()) // Starts Replay. trackings.Post("/current", stubhander()) // Starts Replay.
trackings.Patch("/current", stubhander()) // Pauses Replay. trackings.Patch("/current", stubhander()) // Pauses Replay.
trackings.Put("/current", stubhander()) // Stops Replay. trackings.Put("/current", stubhander()) // Stops Replay.
logrus.Fatal(app.Listen(c.Webserver.Port)) logrus.Fatal(app.Listen(c.Webserver.Port))
} }
func stubhander() fiber.Handler { func stubhander() fiber.Handler {
return func(ctx *fiber.Ctx) error { return func(ctx *fiber.Ctx) error {
return nil return nil
} }
} }
func createFiberWebsocketHandler(s core.Subscriber) func(conn *websocket.Conn) { func createFiberWebsocketHandler(s core.Subscriber) func(conn *websocket.Conn) {
return func(c *websocket.Conn) { return func(c *websocket.Conn) {
// Handle and discard inbound messages // Handle and discard inbound messages
go func() { go func() {
for { for {
if _, _, err := c.NextReader(); err != nil { if _, _, err := c.NextReader(); err != nil {
c.Close() c.Close()
break break
} }
} }
}() }()
dispatcherId, channel := s.Subscribe() dispatcherId, channel := s.Subscribe()
defer s.Unsubscribe(dispatcherId) defer s.Unsubscribe(dispatcherId)
for { for {
cmsg := <-channel cmsg := <-channel
err := c.WriteMessage(websocket.TextMessage, []byte(cmsg)) err := c.WriteMessage(websocket.TextMessage, []byte(cmsg))
if err != nil { if err != nil {
logrus.Println("write:", err) logrus.Println("write:", err)
break break
} }
} }
} }
} }
func fiberHomeHandler(c *fiber.Ctx) error { func fiberHomeHandler(c *fiber.Ctx) error {
tpl, err := template.ParseFiles("static/index.html") tpl, err := template.ParseFiles("static/index.html")
if err != nil { if err != nil {
return err return err
} }
err = tpl.Execute(c, "://"+c.Hostname()+"/") err = tpl.Execute(c, "://"+c.Hostname()+"/")
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }