[WIP] eliminated data races & formatted files
This commit is contained in:
parent
a4a739c64b
commit
09791727a4
1
.gitignore
vendored
1
.gitignore
vendored
@ -161,3 +161,4 @@ Temporary Items
|
|||||||
.env
|
.env
|
||||||
gpsconfig.yml
|
gpsconfig.yml
|
||||||
config.yml
|
config.yml
|
||||||
|
_db
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
335
core/pipeline.go
335
core/pipeline.go
@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
165
core/service.go
165
core/service.go
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
1
go.mod
@ -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
1
go.sum
@ -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=
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
216
ublox/decode.go
216
ublox/decode.go
@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
118
web/http.go
118
web/http.go
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user