Compare commits

...

115 Commits

Author SHA1 Message Date
c54945d763 fixed link to docs 2021-04-20 17:48:04 +02:00
7fc0588e4e docs 2021-04-19 17:21:04 +02:00
0da3531d14 added devdocs 2021-04-19 17:18:24 +02:00
ba5f87d567 updated dependecy to support apple silicon 2021-04-19 17:11:02 +02:00
58af1e1a61 updated readme.md 2021-01-15 20:24:56 +01:00
793d68d3ee updated readme.md 2021-01-15 20:16:12 +01:00
a40a8c5ffd added JSDoc to refull.js and websocket.js 2021-01-15 19:08:08 +01:00
35f1b3f45f speed chart fix 2021-01-15 18:41:11 +01:00
196bbfd05b addDistanceFix 2021-01-15 18:08:33 +01:00
9199977b89 addDistanceFix 2021-01-15 17:53:32 +01:00
2d2b823592 comments 2021-01-15 17:37:24 +01:00
1398bae5b1 Merge branch 'develop' into frank-dev2 2021-01-15 17:11:18 +01:00
b303568a0f fix no serial data error on tracking analysis page 2021-01-15 17:06:24 +01:00
b0e18cb212 fixed tcp collector 2021-01-15 14:51:51 +01:00
0fbc6e2758 added credits for ublox package 2021-01-15 14:03:15 +01:00
435bf451a5 readme updated 2021-01-15 13:49:51 +01:00
3cc6264f2a comments 2021-01-15 12:22:04 +01:00
32bb0e7785 Merge branch 'develop' into frank-dev2 2021-01-15 12:19:58 +01:00
6cda14bc32 comments and fixes 2021-01-15 12:18:09 +01:00
8e0134aa9b add more info to some labels 2021-01-15 11:57:03 +01:00
80e1fd0b2b calc distance difference from speed 2021-01-15 10:11:32 +01:00
4cee6c8f4a Merge branch 'develop' into frank-dev2
# Conflicts:
#	static/scripts/accChart.js
#	static/scripts/refull.js
2021-01-14 14:53:44 +01:00
9e05dd1aba fix distVincenty() to using var instead of let again 2021-01-14 14:50:48 +01:00
5ee814d1a2 try to fix addDistances 2021-01-14 14:19:58 +01:00
f45cea959d adds JSDoc to some functions and to file headers 2021-01-13 19:43:56 +01:00
f6ca896691 add line let dat = JSON.parse(evt.data) deleted by mistake 2021-01-13 14:51:55 +01:00
c3246200d0 clean up js code 2021-01-13 13:16:59 +01:00
205c0bdec0 Merge branch 'develop' into frank-dev2 2021-01-11 12:40:36 +01:00
9766839118 updated readme.md 2021-01-11 11:45:16 +01:00
8369ccc8da submodule 2021-01-11 11:33:16 +01:00
fb704b32c5 imports submodule 2021-01-11 11:24:14 +01:00
fe0ae5f827 modify accuracy chart 2021-01-11 11:13:28 +01:00
55c090e42a correct last commit 2021-01-11 10:56:46 +01:00
da5e98870f add smartphone accuracy data to chart again 2021-01-11 10:47:08 +01:00
917632b964 added submodule 2021-01-11 09:42:30 +01:00
55c9ad4f76 Merge branch 'develop' into frank-dev2
# Conflicts:
#	static/scripts/websocket.js
2021-01-11 08:29:44 +01:00
9dd271b3aa implements green and red lamp tolerance functionality 2021-01-11 08:23:01 +01:00
ed60415f27 jetzt hoffentlich xD 2021-01-11 02:08:41 +01:00
b4a3b09fb1 backend bugfixes 2021-01-11 01:18:38 +01:00
730207c637 frontend bugfixes 2021-01-11 00:21:32 +01:00
1301333629 BIG FRONTEND REFRESH =) 2021-01-11 00:01:00 +01:00
9f91328c08 fixed weird behavior 2021-01-11 00:00:30 +01:00
481f62fb6c added http logger 2021-01-11 00:00:00 +01:00
0cdaafcc44 frontend cleanup 2021-01-10 19:05:25 +01:00
0ce9deeb2e missing cubes.js 2021-01-10 16:45:28 +01:00
f5e3efa635 Merge branch 'develop' into cubes 2021-01-10 16:42:49 +01:00
4bb37ab4ff implemented rotation calibration 2021-01-10 16:42:05 +01:00
36f2e35231 add lamps to visualize smartphone accuracy 2021-01-10 01:56:30 +01:00
0c8cc110ab Merge branch 'develop' of https://git.timovolkmann.de/tvolkmann/gyrogpsc into develop 2021-01-10 00:32:28 +01:00
ecd0b8ecf5 websocket auto reconnect 2021-01-09 13:30:06 +01:00
3547f0e377 sort loaded trackings in dropdown 2021-01-09 12:09:41 +01:00
76f788c685 add serial speeds to chart 2021-01-09 02:31:15 +01:00
8c7812e737 Merge branch 'develop' into frank-dev2 2021-01-09 02:21:33 +01:00
2cb1f0daa4 vincenty and covering measurement time offset 2021-01-09 00:38:08 +01:00
c2a3fe5732 Merge branch 'develop' into frank-dev2 2021-01-08 23:59:52 +01:00
536bf387c8 distance 2021-01-08 22:47:30 +01:00
1632419420 fixed deadlock 2021-01-08 22:09:32 +01:00
fd3f440ab5 clear map data when loadin other replay 2021-01-08 21:33:54 +01:00
26fe6469a5 clean up code 2021-01-08 21:30:33 +01:00
3ec0f58975 Merge branch 'develop' into frank-dev2 2021-01-08 17:50:15 +01:00
dd7107b1a9 changed formula for calculating earth distances 2021-01-08 17:48:41 +01:00
6f6da95780 fix error for some records in acc chart 2021-01-08 16:33:35 +01:00
2e6a9b9281 updated readme 2021-01-08 09:57:10 +01:00
ec11f2541d implements chart for ublox accuracy and distance between ublox and smartphone pos. in fullreplay 2021-01-08 07:17:27 +01:00
e74f0fd895 Merge branch 'develop' into frank-dev2 2021-01-07 20:00:42 +01:00
863f919471 add acc Chart 2021-01-07 17:18:05 +01:00
f5a6ec00b2 changed fix for old record 2021-01-07 16:59:52 +01:00
9171452057 more readme, small state machine change and fixed wrong websocket close 2021-01-07 15:40:07 +01:00
e611b21d80 corrects calculation of speed in kmh 2021-01-07 09:54:13 +01:00
4c3b640a54 Merge branch 'develop' into frank-dev2 2021-01-07 09:44:34 +01:00
e4dda5b308 cleanup code 2021-01-07 09:41:25 +01:00
4a21d4edfa add chart that shows tcp and serial speed over time 2021-01-07 09:38:54 +01:00
c3f9240801 adds accuracy chart for trackings 2021-01-06 22:33:16 +01:00
a7f6e2b9a1 readme.md update 2021-01-06 17:29:22 +01:00
e66f631c2f added readme, commented functionality, cleaned up 2021-01-06 17:05:30 +01:00
f07d27b325 no replay if not needed and no multiple replays at same time 2021-01-06 13:37:29 +01:00
b1dcc863e5 shows trackingstate REPLAY and PIPELINE CLOSED 2021-01-05 13:13:01 +01:00
acee3f9465 adds speed chart for serial and tcp 2021-01-05 12:56:34 +01:00
7805e89f5f add speeds chart in replay 2021-01-04 21:09:51 +01:00
c7698fa06e add button to open full replay view 2021-01-04 05:01:48 +01:00
ca73b7c14e some backend fixes 2021-01-02 16:44:34 +01:00
6ded3ec741 implemented replayFull.html scripts 2021-01-02 16:44:13 +01:00
06790ba8c8 removed old replay page 2021-01-02 11:54:03 +01:00
b45f27f831 add first draft of replay page layout 2020-12-31 22:37:17 +01:00
bc5fb7b6d7 remove compass.js file from index 2020-12-31 16:02:24 +01:00
8c18b2d3f5 change map style, line opacity and zoom 2020-12-31 15:51:19 +01:00
39efd32509 change compass view and functionality 2020-12-29 02:44:08 +01:00
954301f57d stylesheet 2020-12-28 15:37:49 +01:00
e0ebf17cab some improvements 2020-12-28 14:12:28 +01:00
c7140d8394 Merge branch 'frank-dev2' into timo 2020-12-28 11:14:33 +01:00
53351c7349 pipeline ordering 2020-12-28 11:06:41 +01:00
bc2976c0db redesign speedometer 2020-12-28 09:20:00 +01:00
2a9bb7f888 add compass functionality 2020-12-28 08:26:23 +01:00
c0ae64b035 add compass 2020-12-28 06:53:49 +01:00
292d93622f adds compass to the page 2020-12-27 18:19:13 +01:00
c59d482333 adds TCP speed data 2020-12-27 17:15:57 +01:00
5fb76a76ca improves error handling when there is no sensor data 2020-12-27 14:57:17 +01:00
1bde599b46 catch errors if there is no sensor data 2020-12-27 02:20:18 +01:00
fffda650bb convert speed to kmh and display it in browser 2020-12-25 22:19:37 +01:00
a64aa0db7c make map follow one sensor if other is not connected 2020-12-25 19:48:23 +01:00
8a26d8bf61 ignore if no incoming TCP or serial data 2020-12-25 19:28:37 +01:00
f42e2d29b0 Rearrange/Sync stream 2020-12-23 14:59:01 +01:00
104fcb8066 Merge branch 'develop' of https://git.timovolkmann.de/tvolkmann/gyrogpsc into develop 2020-12-21 14:03:23 +01:00
b0b0b487dc add speedometer 2020-12-21 13:36:52 +01:00
4d6b607070 Merge branch 'frank-dev2' into timo 2020-12-19 11:50:27 +01:00
ed0b0443c6 added replay template 2020-12-19 11:49:58 +01:00
c8d3fa58ab backup database 2020-12-19 11:49:29 +01:00
798367bb5e adds checkboxes for TCP and SERIAL before opening a pipeline 2020-12-19 08:52:42 +01:00
2aafb0024d Merge branch 'develop' into frank-dev2 2020-12-18 15:54:21 +01:00
d7b849d94b add HTTP request for POST 2020-12-18 15:40:40 +01:00
100787f047 added fields to sensordata 2020-12-18 12:30:26 +01:00
6003b19d2c modified filtering of 0 Values 2020-12-17 14:46:35 +01:00
a4cd8d88d9 ignore 0 Values in Orientation and Position data 2020-12-16 12:18:50 +01:00
fa1256b3c9 Merge branch 'develop' into frank-dev2 2020-12-15 13:30:21 +01:00
379d174f2b add function to ignore 0 Values 2020-12-15 13:29:05 +01:00
74 changed files with 2786 additions and 1725 deletions

1
.gitignore vendored
View File

@ -162,3 +162,4 @@ Temporary Items
gpsconfig.yml
config.yml
_db
/build

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "static/indicators"]
path = static/indicators
url = https://github.com/sebmatton/jQuery-Flight-Indicators.git

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="PROJECT" libraries="{Chart, Chart.js}" />
<file url="PROJECT" libraries="{Chart, Chart.js, mapbox-gl, three.js}" />
</component>
</project>
</project>

1
.idea/vcs.xml generated
View File

@ -2,5 +2,6 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/static/indicators" vcs="Git" />
</component>
</project>

Binary file not shown.

View File

@ -0,0 +1 @@
ˆ‰œœ+¥¶Îz {Hello Badger

View File

@ -0,0 +1 @@
38895

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
;µÐÕ0.Ñþý|uG…Hello Badger

View File

@ -0,0 +1 @@
38895

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
枯華虌𤥢崱v叐<76>Hello Badger

View File

@ -0,0 +1 @@
27554

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
ßÁuðÓJäe»|?^<5E>¢˜Hello Badger

View File

@ -0,0 +1 @@
38895

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
ˆ‰œœ+¥¶Îz {Hello Badger

View File

@ -0,0 +1 @@
38895

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
;µÐÕ0.Ñþý|uG…Hello Badger

View File

@ -0,0 +1 @@
38895

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
枯華虌𤥢崱v叐<76>Hello Badger

View File

@ -0,0 +1 @@
6958

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
ßÁuðÓJäe»|?^<5E>¢˜Hello Badger

View File

@ -0,0 +1 @@
6958

Binary file not shown.

View File

@ -1,53 +1,42 @@
/**
only for testing purposes
*/
package main
import (
"git.timovolkmann.de/gyrogpsc/core"
"git.timovolkmann.de/gyrogpsc/storage"
"git.timovolkmann.de/gyrogpsc/web"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
"net/http"
_ "net/http/pprof"
"os"
"time"
)
func main() {
//f, err := os.Create("_profile.pprof")
//if err != nil {
// logrus.Fatal(err)
//}
//pprof.StartCPUProfile(f)
go func() {
logrus.Println(http.ListenAndServe("localhost:6060", nil))
}()
//defer pprof.StopCPUProfile()
conf := configurationFromFile()
logrus.Debug(conf)
repo := storage.NewRepository(conf)
disp := core.NewDispatcher()
service := core.TrackingService(repo, disp, conf)
service := core.NewTrackingService(conf, repo, disp)
go func() {
service.StartPipeline(core.TCP, core.SERIAL)
time.Sleep(5 * time.Second)
service.StartRecord()
time.Sleep(10 * time.Second)
service.StopRecord()
time.Sleep(5 * time.Second)
service.StartPipeline(core.TCP, core.SERIAL)
time.Sleep(5 * time.Second)
service.StartRecord()
time.Sleep(60 * time.Second)
service.StopRecord()
time.Sleep(2 * time.Second)
service.StopAll()
time.Sleep(1 * time.Second)
// Long Run
//service.LoadTracking(uuid.MustParse("06b05aa3-6a13-4ffb-8ac7-cd35dfc0f949"))
// Tunnel
service.LoadTracking(uuid.MustParse("c3dbee7c-512a-4cc8-9804-21f0f2cf3c22"), true)
//pprof.StopCPUProfile()
os.Exit(0)
//os.Exit(0)
}()
web.CreateServer(service, disp, conf)

View File

@ -5,47 +5,26 @@ import (
"git.timovolkmann.de/gyrogpsc/storage"
"git.timovolkmann.de/gyrogpsc/web"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
"net/http"
_ "net/http/pprof"
)
func main() {
conf := configurationFromFile()
logrus.Debug(conf)
// launch profiling server in goroutine to debug performance issues
go func() {
logrus.Println(http.ListenAndServe("localhost:6060", nil))
}()
// load configuration from file
conf := core.ConfigurationFromFile()
// initialize persistence layer
repo := storage.NewRepository(conf)
// initialize message distribution layer
disp := core.NewDispatcher()
service := core.TrackingService(conf, repo, disp)
// initialize core logic service and inject
service := core.NewTrackingService(conf, repo, disp)
// launch webserver
web.CreateServer(service, disp, conf)
}
func configurationFromFile() *core.Configuration {
viper.SetDefault("collectors.porttcp", ":3010")
viper.SetDefault("collectors.portserial", "/dev/tty.usbmodem14201")
viper.SetDefault("webserver.port", ":3011")
viper.SetDefault("pipeline.publishIntervalMs", 50)
viper.SetDefault("pipeline.syncUpdateIntervalMs", 494)
viper.SetDefault("debuglevel", "INFO")
viper.SetConfigName("gpsconfig") // name of config file (without extension)
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
viper.AddConfigPath("./../../")
if err := viper.ReadInConfig(); err != nil {
logrus.Warn("couldn't find config file. using standard configuration")
}
c := core.Configuration{}
if err := viper.Unmarshal(&c); err != nil {
logrus.Debug("couldn't load config...")
logrus.Error(err)
}
lvl, err := logrus.ParseLevel(c.Debuglevel)
if err != nil {
logrus.Error(err)
}
logrus.SetLevel(lvl)
return &c
}

View File

@ -1,7 +1,6 @@
package core
import (
"bufio"
"fmt"
"git.timovolkmann.de/gyrogpsc/ublox"
"github.com/sirupsen/logrus"
@ -70,7 +69,7 @@ func (s *serialCollector) Collect() {
}
}
if err != nil {
logrus.Fatal(err)
logrus.Errorln(err)
}
//}
}
@ -212,25 +211,25 @@ func (t *tcpCollector) connectionHandler(conn net.Conn) {
conn.Close()
}()
sc := bufio.NewScanner(conn)
maxSkip := 0
loop:
for sc.Scan() {
buf := make([]byte, 2048)
skipped := 0
//loop:
for {
// Read the incoming connection into the buffer.
res := append([]byte{}, sc.Bytes()...)
if err2 := sc.Err(); err2 != nil {
logrus.Warn("lost tcp link:", err2)
n, err := conn.Read(buf)
if err != nil {
fmt.Println("TCP error - reading from connection:", n, err.Error())
break
}
select {
case t.out <- res:
maxSkip = 0
case t.out <- buf:
skipped = 0
default:
logrus.Traceln("skip collecting tcp messages")
if maxSkip >= 10 {
break loop
}
maxSkip++
//if skipped >= 10 {
// break loop
//}
skipped++
}
}
}

View File

@ -1,5 +1,11 @@
package core
import (
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
)
// This struct represents and holds all configurable parameters
type Configuration struct {
Collectors struct {
TcpCollectorPort string `mapstructure:"porttcp"`
@ -14,3 +20,33 @@ type Configuration struct {
} `mapstructure:"pipeline"`
Debuglevel string `mapstructure:"debuglevel"`
}
// Call this function to load configuration from gpsconfig.yml
func ConfigurationFromFile() *Configuration {
viper.SetDefault("collectors.porttcp", ":3010")
viper.SetDefault("collectors.portserial", "/dev/tty.usbmodem14201")
viper.SetDefault("webserver.port", ":3011")
viper.SetDefault("pipeline.publishIntervalMs", 50)
viper.SetDefault("pipeline.syncUpdateIntervalMs", 494)
viper.SetDefault("debuglevel", "INFO")
viper.SetConfigName("gpsconfig") // name of config file (without extension)
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
viper.AddConfigPath("./../../")
if err := viper.ReadInConfig(); err != nil {
logrus.Warn("couldn't find config file. using standard configuration")
}
c := Configuration{}
if err := viper.Unmarshal(&c); err != nil {
logrus.Debug("couldn't load config...")
logrus.Error(err)
}
lvl, err := logrus.ParseLevel(c.Debuglevel)
if err != nil {
logrus.Error(err)
}
logrus.SetLevel(lvl)
return &c
}

View File

@ -10,16 +10,18 @@ import (
"time"
)
// holds all sensor data and metadata for a specific recording
type Tracking struct {
TrackingMetadata
Data []SensorData
}
// holds all metadata for a specific recording
type TrackingMetadata struct {
UUID uuid.UUID
TimeCreated time.Time
Collectors []CollectorType
Size int
Size int
}
func newTracking() Tracking {
@ -27,7 +29,7 @@ func newTracking() Tracking {
TrackingMetadata: TrackingMetadata{
UUID: uuid.New(),
},
Data: []SensorData{},
Data: []SensorData{},
}
}
@ -38,7 +40,7 @@ func (s *Tracking) isEmpty() bool {
return len(s.Data) == 0
}
// enumerate sources
type SourceId string
const (
@ -46,8 +48,7 @@ const (
SOURCE_SERIAL SourceId = "SOURCE_SERIAL"
)
var timeex int64
// internal unified representation for smartphone and ublox sensor data
type SensorData struct {
//MsgClass string
//FixType string
@ -56,14 +57,16 @@ type SensorData struct {
latency int
Servertime time.Time
Timestamp time.Time
Position [3]float64 `json:",omitempty"`
PosAcc [2]float64 `json:",omitempty"`//[H,V]
Orientation [3]float64 `json:",omitempty"`
Speed float64 `json:",omitempty"`
PosHeading float64 `json:",omitempty"` // Course / Heading of Motion
HeadingAcc float64 `json:",omitempty"`
Gyroscope [3]float64 `json:",omitempty"`
LinearAcc [3]float64 `json:",omitempty"`
Position [3]float64 //`json:",omitempty"`
HAcc float64 //`json:",omitempty"`//[H,V]
VAcc float64 //`json:",omitempty"`//[H,V]
Orientation [3]float64 //`json:",omitempty"`
Speed float64 //`json:",omitempty"`
HeadDevice float64 //`json:",omitempty"` // Course / Heading of Motion
HeadMotion float64 //`json:",omitempty"` // Course / Heading of Motion
HeadingAcc float64 //`json:",omitempty"`
Gyroscope [3]float64 //`json:",omitempty"`
LinearAcc [3]float64 //`json:",omitempty"`
}
@ -128,6 +131,9 @@ var (
errRawMessage = errors.New("raw message")
)
var lastTimeOffsetIphone int64
var lastTimeOffsetUblox int64
func ConvertUbxSensorData(msg interface{}) (*SensorData, error) {
sd := &SensorData{
//Servertime: time.Now().UTC(),
@ -141,6 +147,12 @@ func ConvertUbxSensorData(msg interface{}) (*SensorData, error) {
sd.Position[0] = float64(v.Lat_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.HAcc = float64(v.HAcc_mm) / 1000
sd.VAcc = float64(v.VAcc_mm) / 1000
sd.HeadMotion = float64(v.HeadMot_dege5) / 1e+5
sd.HeadDevice = float64(v.HeadVeh_dege5) / 1e+5
sd.HeadingAcc = float64(v.HeadAcc_dege5) / 1e+5
sd.Speed = float64(v.GSpeed_mm_s) * 1e-3
case *ublox.HnrPvt:
//logrus.Println("HNR-PVT")
sd.itow = v.ITOW_ms
@ -148,6 +160,12 @@ func ConvertUbxSensorData(msg interface{}) (*SensorData, error) {
sd.Position[0] = float64(v.Lat_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.HAcc = float64(v.HAcc) / 1000
sd.VAcc = float64(v.VAcc) / 1000
sd.HeadMotion = float64(v.HeadMot_dege5) / 1e+5
sd.HeadDevice = float64(v.HeadVeh_dege5) / 1e+5
sd.HeadingAcc = float64(v.HeadAcc_dege5) / 1e+5
sd.Speed = float64(v.GSpeed_mm_s) * 1e-3 // mm in m/s
case *ublox.NavAtt:
//logrus.Println("NAV-ATT")
sd.itow = v.ITOW_ms
@ -155,13 +173,17 @@ func ConvertUbxSensorData(msg interface{}) (*SensorData, error) {
sd.Orientation[1] = float64(v.Roll_deg) * 1e-5
sd.Orientation[2] = float64(v.Heading_deg) * 1e-5
case *ublox.RawMessage:
//class := make([]byte, 2)
//binary.LittleEndian.PutUint16(class, v.ClassID())
//logrus.Printf("%#v, %#v", class[0],class[1])
return nil, nil
default:
return nil, errNotImplemented
}
if !sd.Timestamp.IsZero() && sd.Timestamp.Nanosecond() != 0 {
lastTimeOffsetUblox = time.Now().UnixNano() - sd.Timestamp.UnixNano()
} else {
sd.Timestamp = time.Now().UTC().Add(time.Duration(lastTimeOffsetUblox))
}
return sd, nil
}
@ -180,12 +202,19 @@ func convertIPhoneSensorLog(jsonData []byte) (*SensorData, error) {
pitch := gjson.Get(string(jsonData), "motionPitch").Float() * 180 / math.Pi
roll := gjson.Get(string(jsonData), "motionRoll").Float() * 180 / math.Pi
yaw := gjson.Get(string(jsonData), "motionYaw").Float() * 180 / math.Pi
hAcc := gjson.Get(string(jsonData), "locationHorizontalAccuracy").Float()
vAcc := gjson.Get(string(jsonData), "locationVerticalAccuracy").Float()
headingAcc := gjson.Get(string(jsonData), "locationHeadingAccuracy").Float()
headMotion := gjson.Get(string(jsonData), "locationCourse").Float()
headDevice := gjson.Get(string(jsonData), "locationTrueHeading").Float()
speed := gjson.Get(string(jsonData), "locationSpeed").Float()
var ts time.Time
if timestamp != 0 {
ts = time.Unix(0, int64(timestamp*float64(time.Second))).UTC()
timeex = time.Now().UnixNano() - ts.UnixNano()
} else if timeex != 0 {
ts = time.Now().Add(time.Duration(timeex)).UTC()
lastTimeOffsetIphone = time.Now().UnixNano() - ts.UnixNano()
} else {
ts = time.Now().UTC().Add(time.Duration(lastTimeOffsetIphone))
}
//if ts == time.Date()
sd := &SensorData{
@ -194,6 +223,12 @@ func convertIPhoneSensorLog(jsonData []byte) (*SensorData, error) {
Timestamp: ts,
Position: [3]float64{lat, lon, alt},
Orientation: [3]float64{pitch, roll, yaw},
HAcc: hAcc,
VAcc: vAcc,
HeadingAcc: headingAcc,
HeadMotion: headMotion,
HeadDevice: headDevice,
Speed: speed,
}
if (*sd == SensorData{}) {
return nil, errors.New("iphone sensorlog: convert empty")
@ -209,6 +244,13 @@ func convertAndroidHyperImu(jsonData []byte) (*SensorData, error) {
pitch := gjson.Get(string(jsonData), "orientation.0").Float()
roll := gjson.Get(string(jsonData), "orientation.1").Float()
yaw := gjson.Get(string(jsonData), "orientation.2").Float()
// following properties not available for HyperIMU
//hAcc := gjson.Get(string(jsonData), "locationHorizontalAccuracy").Float()
//vAcc := gjson.Get(string(jsonData), "locationVerticalAccuracy").Float()
//headingAcc := gjson.Get(string(jsonData), "locationHeadingAccuracy").Float()
//headMotion := gjson.Get(string(jsonData), "locationCourse").Float()
//headDevice := gjson.Get(string(jsonData), "locationTrueHeading").Float()
//speed := gjson.Get(string(jsonData), "locationSpeed").Float()
sd := &SensorData{
//Servertime: time.Now().UTC(),

View File

@ -6,12 +6,14 @@ import (
"golang.org/x/sync/semaphore"
)
// dispatcher is responsible to distribute messages to subscribed listeners
type dispatcher struct {
listeners map[int16]chan string
counter int16
sem *semaphore.Weighted
}
// Returns initialized dispatcher.
func NewDispatcher() *dispatcher {
return &dispatcher{
listeners: make(map[int16]chan string),
@ -20,19 +22,20 @@ func NewDispatcher() *dispatcher {
}
}
// disable or enable streaming without removing all listeners from dispatcher
func (d *dispatcher) SetStreaming(s bool) bool {
if ok := d.sem.TryAcquire(1); s && ok {
// if i want to turn on and can get semaphore then return success
// if you want to turn on and can get semaphore then return success
return true
} else if !s && !ok {
// if i want to turn off and cant get semaphore, i can safely turn off by releasing semaphore and return success
// if you want to turn off and cant get semaphore, you can safely turn off by releasing semaphore and return success
d.sem.Release(1)
return true
}
return false
}
// if closed, dispatcher will not forward published messages and drops them.
func (d *dispatcher) IsClosed() bool {
if d.sem.TryAcquire(1) {
d.sem.Release(1)
@ -41,16 +44,23 @@ func (d *dispatcher) IsClosed() bool {
return false
}
// publishes message to all subscribed listeners.
// if dispatcher closed, dispatcher will not forward published messages and drops them.
func (d *dispatcher) Publish(message string) {
if d.IsClosed() { return }
if d.IsClosed() {
return
}
logrus.Tracef("publishing to %v listeners\n", len(d.listeners))
logrus.Trace(message)
for _, ch := range d.listeners {
ch <- message
select {
case ch <- message:
default:
logrus.Traceln("dispatcher: skip closed channel")
}
}
}
// Registers new client as listener and returns his id and a channel which is used to receive all messages.
func (d *dispatcher) Subscribe() (id int16, receiver <-chan string) {
key := d.counter
d.counter++
@ -59,6 +69,8 @@ func (d *dispatcher) Subscribe() (id int16, receiver <-chan string) {
return key, rec
}
// Unsubscribes Listener with given ID.
// if listener with given ID exists, it will be deleted and no error will be returned.
func (d *dispatcher) Unsubscribe(id int16) error {
receiver, ok := d.listeners[id]
if !ok {

View File

@ -2,25 +2,25 @@ package core
import "github.com/google/uuid"
// abstraction for dispatcher to make it replaceable
type Subscriber interface {
Subscribe() (int16, <-chan string)
Unsubscribe(id int16) error
}
// abstraction for dispatcher to make it replaceable
type Publisher interface {
Publish(message string)
Streamer
}
type Processor interface {
Push(data *SensorData) error
}
// implementing struct should be responsible for message forwarding (to client)
type Streamer interface {
SetStreaming(s bool) (ok bool)
IsClosed() bool
}
// abstraction for persistance layer
type Storer interface {
Save(tracking Tracking) error
LoadAll() ([]TrackingMetadata, error)

View File

@ -1,239 +0,0 @@
package core
import (
"context"
"encoding/json"
"errors"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/sirupsen/logrus"
"golang.org/x/sync/semaphore"
"sync"
"time"
)
type pipeline struct {
active bool
record bool
synchroniz synchronizer
buffer pipeBuffer
publisher Publisher
storer Tracker
publishTicker *time.Ticker
mu sync.RWMutex
sema *semaphore.Weighted
}
// pipeline implements Runner & Processor
func NewPipeline(d Publisher, s Tracker, conf *Configuration) *pipeline {
return &pipeline{
false,
false,
synchronizer{
//bufferSize: 100,
mutex: &sync.RWMutex{},
updateTicker: time.NewTicker(time.Duration(conf.Pipeline.SyncUpdateIntervalMs) * time.Millisecond),
},
pipeBuffer{
tcpMutex: &sync.Mutex{},
serialMutex: &sync.Mutex{},
},
d,
s,
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) isPipeRecording() bool {
p.mu.RLock()
defer p.mu.RUnlock()
return p.record
}
func (p *pipeline) Run() {
p.sema.Acquire(context.Background(), 1) // !!! n=2 wenn synchronizer mitläuft
p.mu.Lock()
p.active = true
p.mu.Unlock()
logrus.Println("pipeline: processing service started")
//go func() {
// for p.isPipeActive() {
// <-p.synchroniz.updateTicker.C
// err := p.refreshDelay()
// if err != nil {
// logrus.Debugln(err)
// }
// }
// p.sema.Release(1)
// logrus.Println("pipeline: updater stopped")
//}()
go func() {
for p.isPipeActive() {
<-p.publishTicker.C
err := p.publish()
if err != nil && err.Error() != "no data available" {
logrus.Trace(err)
}
}
p.sema.Release(1)
logrus.Println("pipeline: publisher stopped")
}()
}
func (p *pipeline) Record() {
p.record = true
}
func (p *pipeline) StopRecord() {
p.record = false
}
func (p *pipeline) Push(data *SensorData) error {
if (data == nil || *data == SensorData{}) {
return errors.New("no data")
}
//logrus.Println("push data to pipeline:", string(data.source))
switch data.source {
case SOURCE_TCP:
go p.pushTcpDataToBuffer(*data)
case SOURCE_SERIAL:
go p.pushSerialDataToBuffer(*data)
default:
panic("pipeline: invalid data source")
}
return nil
}
func (p *pipeline) publish() error {
p.buffer.serialMutex.Lock()
p.buffer.tcpMutex.Lock()
if (p.buffer.MeasTcp == SensorData{} && p.buffer.MeasSerial == SensorData{}) {
p.buffer.tcpMutex.Unlock()
p.buffer.serialMutex.Unlock()
return errors.New("no data available")
}
if cmp.Equal(p.buffer.MeasTcp, p.buffer.LastMeasTcp, cmpopts.IgnoreUnexported(SensorData{})) &&
cmp.Equal(p.buffer.MeasSerial, p.buffer.LastMeasSerial, cmpopts.IgnoreUnexported(SensorData{})) {
p.buffer.tcpMutex.Unlock()
p.buffer.serialMutex.Unlock()
return errors.New("same data")
}
logrus.Debugf("")
logrus.Tracef("SER old: %-40s %-40s %v %v", p.buffer.LastMeasSerial.Timestamp.Format(time.RFC3339Nano), p.buffer.LastMeasSerial.Servertime.Format(time.RFC3339Nano), p.buffer.LastMeasSerial.Position, p.buffer.LastMeasSerial.Orientation)
logrus.Debugf("SER new: %-40s %-40s %v %v", p.buffer.MeasSerial.Timestamp.Format(time.RFC3339Nano), p.buffer.MeasSerial.Servertime.Format(time.RFC3339Nano), p.buffer.MeasSerial.Position, p.buffer.MeasSerial.Orientation)
logrus.Tracef("TCP old: %-40s %-40s %v %v", p.buffer.LastMeasTcp.Timestamp.Format(time.RFC3339Nano), p.buffer.LastMeasTcp.Servertime.Format(time.RFC3339Nano), p.buffer.LastMeasTcp.Position, p.buffer.LastMeasTcp.Orientation)
logrus.Debugf("TCP new: %-40s %-40s %v %v", p.buffer.MeasTcp.Timestamp.Format(time.RFC3339Nano), p.buffer.MeasTcp.Servertime.Format(time.RFC3339Nano), p.buffer.MeasTcp.Position, p.buffer.MeasTcp.Orientation)
p.buffer.LastMeasTcp = p.buffer.MeasTcp
p.buffer.LastMeasSerial = p.buffer.MeasSerial
data := map[string]interface{}{}
if p.buffer.MeasTcp.source == SOURCE_TCP {
data[string(SOURCE_TCP)] = p.buffer.MeasTcp
}
if p.buffer.MeasSerial.source == SOURCE_SERIAL {
data[string(SOURCE_SERIAL)] = p.buffer.MeasSerial
}
p.buffer.tcpMutex.Unlock()
p.buffer.serialMutex.Unlock()
jdata, err := json.Marshal(data)
//logrus.Println(string(pretty.Pretty(jdata)))
if err != nil {
return err
}
p.publisher.Publish(string(jdata))
return nil
}
type pipeBuffer struct {
MeasTcp SensorData
MeasSerial SensorData
LastMeasTcp SensorData
LastMeasSerial SensorData
tcpMutex *sync.Mutex
serialMutex *sync.Mutex
}
type UnixNanoTime int64
type synchronizer struct {
tcpSerialDelayMs int64
mutex *sync.RWMutex
updateTicker *time.Ticker
}
func (p *pipeline) refreshDelay() error {
logrus.Debugf("refreshing delay...")
p.buffer.serialMutex.Lock()
p.buffer.tcpMutex.Lock()
tcpTime := p.buffer.MeasTcp.Timestamp
serTime := p.buffer.MeasSerial.Timestamp
p.buffer.tcpMutex.Unlock()
p.buffer.serialMutex.Unlock()
if tcpTime.IsZero() || serTime.IsZero() || tcpTime.UnixNano() == 0 || serTime.UnixNano() == 0 {
return errors.New("sync not possible. zero time value detected")
}
logrus.Debug("TCP", tcpTime.Format(time.RFC3339Nano))
logrus.Debug("SER", serTime.Format(time.RFC3339Nano))
currentDelay := tcpTime.Sub(serTime).Milliseconds()
p.synchroniz.mutex.Lock()
defer p.synchroniz.mutex.Unlock()
logrus.Debugf("old delay-> %vms...", p.synchroniz.tcpSerialDelayMs)
if currentDelay > 5000 || currentDelay < -5000 {
p.synchroniz.tcpSerialDelayMs = 0
return errors.New("skipping synchronisation! time not properly configured or facing network problems.")
}
p.synchroniz.tcpSerialDelayMs += currentDelay
logrus.Infof("new delay-> %vms", p.synchroniz.tcpSerialDelayMs)
return nil
}
func (p *pipeline) pushTcpDataToBuffer(data SensorData) {
data.Servertime = time.Now().UTC()
if p.isPipeRecording() {
p.storer.Put(data)
}
p.synchroniz.mutex.RLock()
if p.synchroniz.tcpSerialDelayMs > 0 {
time.Sleep(time.Duration(p.synchroniz.tcpSerialDelayMs) * time.Millisecond)
}
p.synchroniz.mutex.RUnlock()
p.buffer.tcpMutex.Lock()
p.buffer.MeasTcp = data
//p.buffer.MeasTcp = p.buffer.MeasTcp.ConsolidateExTime(data)
p.buffer.tcpMutex.Unlock()
}
func (p *pipeline) pushSerialDataToBuffer(data SensorData) {
data.Servertime = time.Now().UTC()
if p.isPipeRecording() {
p.storer.Put(data)
}
p.synchroniz.mutex.RLock()
if p.synchroniz.tcpSerialDelayMs < 0 {
time.Sleep(time.Duration(-p.synchroniz.tcpSerialDelayMs) * time.Millisecond)
}
p.synchroniz.mutex.RUnlock()
p.buffer.serialMutex.Lock()
p.buffer.MeasSerial = data
//p.buffer.MeasSerial = p.buffer.MeasSerial.ConsolidateEpochsOnly(data)
p.buffer.serialMutex.Unlock()
}
func (p *pipeline) Close() {
p.mu.Lock()
p.active = false
p.mu.Unlock()
}

View File

@ -5,36 +5,30 @@ import (
ext "github.com/reugn/go-streams/extension"
"github.com/reugn/go-streams/flow"
"github.com/sirupsen/logrus"
"github.com/tidwall/pretty"
"time"
)
type pipelineX struct {
collNet *ext.ChanSource
collSer *ext.ChanSource
transNet *flow.FlatMap
transSer *flow.FlatMap
flowDelay *flow.Map
flowStore *flow.Map
flowJson *flow.Map
sinkPub *publishSink
}
type pipelineRecord struct{}
func NewPipelineX(p Publisher, s Tracker, netChan chan interface{}, serialChan chan interface{}) *pipelineX {
func NewRecordPipeline(p Publisher, s Tracker, netChan chan interface{}, serialChan chan interface{}) *pipelineRecord {
// set pipeline up and wire it together
collNet := ext.NewChanSource(netChan)
collSer := ext.NewChanSource(serialChan)
transNet := flow.NewFlatMap(transformNetFunc, 8)
transSer := flow.NewFlatMap(transformSerFunc, 8)
flowDelay := flow.NewMap(delayFunc(), 8)
flowStore := flow.NewMap(storeFunc(s), 8)
//flowJson := flow.NewMap(jsonFunc, 8)
transNet := flow.NewFlatMap(transformNetFunc, 1)
transSer := flow.NewFlatMap(transformSerFunc, 1)
flowStore := flow.NewMap(storeFunc(s), 1)
dataSanitizer := flow.NewMap(replaySanitizeFunc(), 1)
flowJson := flow.NewMap(jsonFunc, 1)
sinkPub := newPublishSink(p)
// wire up and execute
demux := flow.Merge(collNet.Via(transNet), collSer.Via(transSer))
//go demux.Via(flowDelay).Via(flowStore).Via(flowJson).To(sinkPub)
go demux.Via(flowDelay).Via(flowStore).To(sinkPub)
return &pipelineX{}
go demux.Via(flowStore).Via(dataSanitizer).Via(flowJson).To(sinkPub)
return &pipelineRecord{}
}
func storeFunc(s Tracker) flow.MapFunc {
@ -53,34 +47,37 @@ func storeFunc(s Tracker) flow.MapFunc {
}
logrus.Debugf("%-14v %-40s %-40s %v %v", sd.Source(), sd.Timestamp.Format(time.RFC3339Nano), sd.Servertime.Format(time.RFC3339Nano), sd.Position, sd.Orientation)
data := map[string]interface{}{}
if sd.Source() == SOURCE_TCP {
data[string(SOURCE_TCP)] = *sd
}
if sd.Source() == SOURCE_SERIAL {
data[string(SOURCE_SERIAL)] = *sd
}
jdata, err := json.Marshal(data)
//logrus.Println(string(pretty.Pretty(jdata)))
if err != nil {
logrus.Fatalln(err)
}
return string(jdata)
//runtime.Gosched()
return sd
}
}
type timeDelay struct {
offsets map[SourceId]int
}
func delayFunc() flow.MapFunc {
//td := &timeDelay{}
return func(i interface{}) interface{} {
return i
func jsonFunc(i interface{}) interface{} {
var sd *SensorData
switch v := i.(type) {
case SensorData:
sd = &v
case *SensorData:
sd = v
default:
panic("jsonFunc: wrong Type")
}
data := map[string]interface{}{}
if sd.Source() == SOURCE_TCP {
data[string(SOURCE_TCP)] = *sd
}
if sd.Source() == SOURCE_SERIAL {
data[string(SOURCE_SERIAL)] = *sd
}
jdata, err := json.Marshal(data)
logrus.Traceln(string(pretty.Pretty(jdata)))
if err != nil {
logrus.Fatalln(err)
}
return string(jdata)
}
func transformNetFunc(i interface{}) []interface{} {
@ -115,6 +112,9 @@ func transformSerFunc(i interface{}) []interface{} {
return append(returnSlice, sd)
}
// Publish sink will pass data to dispatcher after flowing through the stream processing
// matches api to use it with github.com/reugn/go-streams/flow
type publishSink struct {
in chan interface{}
p Publisher

216
core/pipeline_replay.go Normal file
View File

@ -0,0 +1,216 @@
package core
import (
"container/heap"
"github.com/reugn/go-streams"
ext "github.com/reugn/go-streams/extension"
"github.com/reugn/go-streams/flow"
"github.com/sirupsen/logrus"
"runtime"
"sort"
"sync"
"time"
)
type pipelineReplay struct {
stopChan chan struct{}
replayChan chan interface{}
}
func (p *pipelineReplay) Stop() {
defer func() {
if recover() != nil {
logrus.Debugln("replay channel already closed")
}
}()
logrus.Debugln("send stop signal...")
select {
case p.stopChan <- struct{}{}:
logrus.Debugln("stop signal sent")
//default:
// logrus.Debugln("stop signal skipped")
}
}
func NewReplayPipeline(p Publisher, t *Tracking) *pipelineReplay {
r := &pipelineReplay{make(chan struct{}), nil}
// set pipeline up and wire it together
r.replayChan = r.channelFromTracking(t)
collNet := ext.NewChanSource(r.replayChan)
dataSanitizer := flow.NewMap(replaySanitizeFunc(), 1)
//flowReorder := NewRearranger()
flowJson := flow.NewMap(jsonFunc, 1)
sinkPub := newPublishSink(p)
// wire up and execute
//go collNet.Via(dataSanitizer).Via(flowJson).To(sinkPub)
go collNet.Via(dataSanitizer).Via(flowJson).To(sinkPub)
return r
}
func (p *pipelineReplay) channelFromTracking(t *Tracking) chan interface{} {
ch := make(chan interface{})
sort.Slice(t.Data, func(i, j int) bool { return t.Data[i].Servertime.Before(t.Data[j].Servertime) })
go func() {
lastTs := t.Data[0].Servertime.UnixNano()
lastTsNow := time.Now().UTC().UnixNano()
i := 0
br:
for i <= len(t.Data)-1 {
durationSinceLastEvent := t.Data[i].Servertime.UnixNano() - lastTs
timeCounter := time.Now().UTC().UnixNano() - lastTsNow
if timeCounter >= durationSinceLastEvent {
logrus.Traceln("replay tracking: ", t.Data[i])
ch <- &(t.Data[i])
lastTs = t.Data[i].Servertime.UnixNano()
lastTsNow = time.Now().UTC().UnixNano()
i++
}
select {
case <-p.stopChan:
logrus.Debugln("received stop signal: replay stopped")
break br
default:
}
}
logrus.Infoln("replay: tracking replay finished")
select {
case <-p.stopChan:
logrus.Debugln("received stop signal: replay pipeline closed")
}
close(p.replayChan)
}()
return ch
}
func replaySanitizeFunc() flow.MapFunc {
var lastTimeOffsetIphone int64
var lastTimeOffsetUblox int64
return func(i interface{}) interface{} {
sd := i.(*SensorData)
if !(sd.Timestamp.IsZero() || sd.Timestamp.Nanosecond() == 0) {
lastOffset := sd.Servertime.UnixNano() - sd.Timestamp.UnixNano()
if sd.Source() == SOURCE_TCP {
lastTimeOffsetIphone = lastOffset
}
if sd.Source() == SOURCE_SERIAL {
lastTimeOffsetUblox = lastOffset
}
} else {
var lastOff int64
if sd.Source() == SOURCE_TCP {
lastOff = lastTimeOffsetIphone
}
if sd.Source() == SOURCE_SERIAL {
lastOff = lastTimeOffsetUblox
}
sd.Timestamp = sd.Servertime.Add(time.Duration(lastOff))
}
//if sd.Servertime.Before(time.Unix(1608422400, 0)) && sd.Speed != 0 && sd.Source() == SOURCE_SERIAL {
// sd.Speed = sd.Speed * 3.6
//}
return sd
}
}
// The Rearranger is not used but kept, for later experiments.
func NewRearranger() *rearranger {
rearran := &rearranger{
queue: &flow.PriorityQueue{},
in: make(chan interface{}),
out: make(chan interface{}),
done: make(chan struct{}),
}
go rearran.receive()
go rearran.emit()
return rearran
}
type rearranger struct {
sync.RWMutex
queue *flow.PriorityQueue
in chan interface{}
out chan interface{}
done chan struct{}
lastTimestamp int64
lastTimeNanoNow int64
}
// Verify rearranger satisfies the Flow interface.
var _ streams.Flow = (*rearranger)(nil)
func (r *rearranger) In() chan<- interface{} {
return r.in
}
func (r *rearranger) Out() <-chan interface{} {
return r.out
}
func (r *rearranger) Via(flow streams.Flow) streams.Flow {
go r.transmit(flow)
return flow
}
func (r *rearranger) To(sink streams.Sink) {
r.transmit(sink)
}
// submit emitted windows to the next Inlet
func (r *rearranger) transmit(inlet streams.Inlet) {
for elem := range r.Out() {
inlet.In() <- elem
}
close(inlet.In())
}
func (r *rearranger) receive() {
for elem := range r.in {
ts := r.timestamp(elem)
//if r.lastTimestamp == 0 {
// r.lastTimestamp = ts //- (500 * 1e+6) // Delay
// r.lastTimeNanoNow = time.Now().UTC().UnixNano()
//}
item := flow.NewItem(elem, ts, 0)
r.Lock()
heap.Push(r.queue, item)
r.Unlock()
logrus.Debugln("item recieved.")
runtime.Gosched()
}
close(r.done)
close(r.out)
}
// emit pops data from ordered priority queue
func (r *rearranger) emit() {
for {
if r.queue.Len() <= 10 {
continue
}
tnow := time.Now().UTC().UnixNano()
logrus.Debugln("popping item: ")
r.Lock()
item := heap.Pop(r.queue).(*flow.Item)
r.Unlock()
v := item.Msg.(*SensorData)
r.out <- v
r.lastTimestamp = v.Timestamp.UnixNano()
r.lastTimeNanoNow = tnow
select {
case <-r.done:
return
default:
}
}
}
func (r *rearranger) timestamp(elem interface{}) int64 {
v := elem.(*SensorData)
return v.Timestamp.UnixNano()
}

View File

@ -1 +0,0 @@
package core

View File

@ -10,6 +10,7 @@ import (
"time"
)
// indicates state of the TrackingService
type OpMode uint8
const (
@ -19,21 +20,7 @@ const (
REPLAY
)
type Service interface {
AllTrackings() ([]TrackingMetadata, error)
StartPipeline(cols ...CollectorType) (string, error)
StartRecord() (string, error)
StopRecord() (*TrackingMetadata, error)
StopAll() (*TrackingMetadata, error)
LoadTracking(trackingId uuid.UUID) (*Tracking, error)
DeleteTracking(trackingId uuid.UUID)
StartReplay()
PauseReplay()
StopReplay()
}
// structs implementing this interface are expected to cache sensor data passed by Put(data) while recording and dropping data if not recording
type Tracker interface {
Put(data SensorData)
Recorder
@ -44,37 +31,42 @@ type Recorder interface {
IsRecording() bool
}
type trackingService struct {
opMode OpMode
tracking *Tracking
pipeline *pipelineX
// this struct holds all relevant references for the TrackingService
type TrackingService struct {
opMode OpMode
tracking *Tracking
//pipeline *pipelineRecord
replaypipe *pipelineReplay
collectors []Collector
store Storer
publisher Publisher
config *Configuration
recSem *semaphore.Weighted
mu *sync.RWMutex
mu *sync.RWMutex
}
func TrackingService(c *Configuration, s Storer, p Publisher) *trackingService {
// constructor
func NewTrackingService(c *Configuration, s Storer, p Publisher) *TrackingService {
t := &Tracking{}
ts := &trackingService{
ts := &TrackingService{
tracking: t,
opMode: STOPPED,
collectors: nil,
recSem: semaphore.NewWeighted(1),
mu: &sync.RWMutex{},
mu: &sync.RWMutex{},
config: c,
store: s,
publisher: p,
}
// first initialize of tcp collector to to open tcp port
// first call to to open tcp port. makes app ready to accept connections
NewCollector(c, TCP)
return ts
}
func (t *trackingService) Put(data SensorData) {
// caches sensordata while recording
func (t *TrackingService) Put(data SensorData) {
if !t.IsRecording() {
return
}
@ -85,7 +77,8 @@ func (t *trackingService) Put(data SensorData) {
t.mu.Unlock()
}
func (t *trackingService) SetRecording(s bool) (ok bool) {
// changes recording state of service
func (t *TrackingService) SetRecording(s bool) (ok bool) {
if okay := t.recSem.TryAcquire(1); okay && s {
// if i want to turn on and can get semaphore then return success
return true
@ -97,7 +90,7 @@ func (t *trackingService) SetRecording(s bool) (ok bool) {
return false
}
func (t *trackingService) IsRecording() bool {
func (t *TrackingService) IsRecording() bool {
if t.recSem.TryAcquire(1) {
t.recSem.Release(1)
return false
@ -105,23 +98,27 @@ func (t *trackingService) IsRecording() bool {
return true
}
func (t *trackingService) StartPipeline(cols ...CollectorType) (string, error) {
// creates a new Pipeline with requested collectors
func (t *TrackingService) StartLivetracking(cols ...CollectorType) (string, error) {
logrus.Info("SERVICE: NEW PIPELINE")
// check if state machine is in right state
if t.opMode == RECORDING {
txt := "trackingservice: please stop recording before resetting pipeline"
logrus.Warn(txt)
return "RECORDING", errors.New(txt)
}
if t.opMode == REPLAY {
t.StopAll()
}
if t.opMode == LIVE {
txt := "trackingservice: stop tracking running stream before creating new one"
logrus.Warnln(txt)
//t.StopAll()
//time.Sleep(1000 * time.Millisecond)
return "record already running since: " + t.tracking.TimeCreated.String(), errors.New(txt)
}
logrus.Debugln("new tracking:", cols)
t.opMode = LIVE
// create and start collectors
t.collectors = nil
var tcp, ser chan interface{}
for _, col := range cols {
@ -138,20 +135,24 @@ func (t *trackingService) StartPipeline(cols ...CollectorType) (string, error) {
t.safelyReplaceTracking(newTracking())
t.tracking.Collectors = cols
t.pipeline = NewPipelineX(t.publisher, t, tcp, ser)
// finally create pipeline
NewRecordPipeline(t.publisher, t, tcp, ser)
t.publisher.SetStreaming(true)
logrus.Debugln("current State:", t.opMode)
//time.Sleep(3 * time.Second)
return "LIVE", nil
}
func (t *trackingService) AllTrackings() ([]TrackingMetadata, error) {
// retrieves all trackings. metadata only.
func (t *TrackingService) AllTrackings() ([]TrackingMetadata, error) {
logrus.Info("SERVICE: GET ALL TRACKINGS")
data, err := t.store.LoadAll()
logrus.Debugln("current State:", t.opMode)
return data, err
}
func (t *trackingService) StartRecord() (string, error) {
// starts recording and returns state
func (t *TrackingService) StartRecord() (string, error) {
logrus.Info("SERVICE: START RECORD")
if t.opMode != LIVE {
if t.opMode == RECORDING {
@ -167,10 +168,12 @@ func (t *trackingService) StartRecord() (string, error) {
t.opMode = RECORDING
t.tracking.TimeCreated = time.Now()
t.SetRecording(true)
logrus.Debugln("current State:", t.opMode)
return "record started at: " + t.tracking.TimeCreated.String(), nil
}
func (t *trackingService) StopRecord() (*TrackingMetadata, error) {
// stops recording and returns metadata of current tracking if successfully stopped
func (t *TrackingService) StopRecord() (*TrackingMetadata, error) {
logrus.Info("SERVICE: STOP RECORD")
if t.opMode != RECORDING {
txt := "trackingservice: couldn't stop. not recording"
@ -185,57 +188,92 @@ func (t *trackingService) StopRecord() (*TrackingMetadata, error) {
logrus.Error(err)
}
t.opMode = LIVE
//time.Sleep(20 * time.Millisecond)
tm := t.tracking.TrackingMetadata
t.safelyReplaceTracking(newTracking())
t.tracking.Collectors = tm.Collectors
logrus.Debugln("current State:", t.opMode)
return &tm, err
}
func (t *trackingService) StopAll() (*TrackingMetadata, error) {
// stops live tracking and recording. if theres no active recording, no metadata will be returned
func (t *TrackingService) StopAll() (*TrackingMetadata, error) {
logrus.Info("SERVICE: STOP ALL")
var tm *TrackingMetadata = nil
var err error
t.publisher.SetStreaming(false)
for _, e := range t.collectors {
e.Stop()
}
if t.replaypipe != nil {
t.replaypipe.Stop()
t.replaypipe = nil
}
// let buffer run empty after collectors stopped
time.Sleep(time.Millisecond * 5)
t.publisher.SetStreaming(false)
if t.opMode == RECORDING {
logrus.Info("trackingservice: gracefully stop recording ")
tm, err = t.StopRecord()
}
t.opMode = STOPPED
logrus.Debugln("current State:", t.opMode)
return tm, err
}
func (t *trackingService) LoadTracking(trackingId uuid.UUID) (*Tracking, error) {
// retrieves tracking with all data and starts replay pipeline if desired.
// in that case the application behaves like in live mode.
func (t *TrackingService) LoadTracking(trackingId uuid.UUID, replay bool) (*Tracking, error) {
logrus.Info("SERVICE: LOAD TRACKING")
if t.opMode == RECORDING {
txt := "trackingservice: please stop recording before load another tracking"
logrus.Warn(txt)
return nil, errors.New(txt)
}
if t.opMode == REPLAY || t.opMode == LIVE {
t.StopAll()
}
logrus.Info("LOAD TRACKING from database")
tracking, err := t.store.Load(trackingId)
fixSpeedValues(tracking)
if err != nil {
return nil, err
}
if replay == false {
return tracking, nil
}
t.safelyReplaceTracking(*tracking)
if t.replaypipe != nil {
select {
case <-t.replaypipe.stopChan:
logrus.Warnln("blocking channel closed")
default:
}
t.replaypipe = nil
}
t.replaypipe = NewReplayPipeline(t.publisher, t.tracking)
t.publisher.SetStreaming(true)
t.opMode = REPLAY
logrus.Debugln("current State:", t.opMode)
return t.tracking, nil
}
func (t *trackingService) DeleteTracking(trackingId uuid.UUID) {
func (t *TrackingService) DeleteTracking(trackingId uuid.UUID) {
panic("implement me")
}
func (t *trackingService) StartReplay() {
panic("implement me")
}
func (t *trackingService) PauseReplay() {
panic("implement me")
}
func (t *trackingService) StopReplay() {
panic("implement me")
}
func (t *trackingService) safelyReplaceTracking(tr Tracking) {
// helper function to replace tracking held by service. makes sure to keep the pointer and change only underlying data.
func (t *TrackingService) safelyReplaceTracking(tr Tracking) {
t.recSem.Acquire(context.Background(), 1)
*t.tracking = tr
t.recSem.Release(1)
}
// helper function to fixSpeedValues wrong values recorded before 12/20/2020
func fixSpeedValues(tracking *Tracking) {
logrus.Debugln("fixing speed values")
for i := 0; i < len(tracking.Data); i++ {
if tracking.Data[i].Servertime.Before(time.Unix(1608422400, 0)) && tracking.Data[i].Source() == SOURCE_SERIAL && tracking.Data[i].Speed != 0 {
tracking.Data[i].Speed = tracking.Data[i].Speed * 3.6
}
}
}

BIN
devdocs.pdf Normal file

Binary file not shown.

View File

@ -10,11 +10,6 @@ collectors:
porttcp: ":3010"
portserial: "/dev/tty.usbmodem14201"
# processing pipeline configurations
pipeline:
publishIntervalMs: 50
syncUpdateIntervalMs: 494
debuglevel: "INFO"
#// ErrorLevel level. Logs. Used for errors that should definitely be noted.
@ -29,4 +24,4 @@ debuglevel: "INFO"
#InfoLevel
#// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
#DebugLevel
#DebugLevel

12
go.mod
View File

@ -3,18 +3,24 @@ module git.timovolkmann.de/gyrogpsc
go 1.15
require (
github.com/DataDog/zstd v1.4.6-0.20200617134701-89f69fb7df32 // indirect
github.com/dgraph-io/badger/v2 v2.2007.2
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/gofiber/fiber/v2 v2.2.4
github.com/gofiber/template v1.6.6
github.com/gofiber/websocket/v2 v2.0.2
github.com/google/go-cmp v0.5.2
github.com/golang/protobuf v1.4.2 // indirect
github.com/google/go-cmp v0.5.2 // indirect
github.com/google/uuid v1.1.2
github.com/reugn/go-streams v0.5.2
github.com/klauspost/compress v1.11.0 // indirect
github.com/reugn/go-streams v0.6.3
github.com/sirupsen/logrus v1.6.0
github.com/spf13/viper v1.7.1
github.com/tidwall/gjson v1.6.0
github.com/tidwall/pretty v1.0.2
go.bug.st/serial v1.1.1
go.bug.st/serial v1.1.3
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
golang.org/x/sys v0.0.0-20201211090839-8ad439b19e0f // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
)

160
go.sum
View File

@ -11,14 +11,11 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/99designs/keyring v1.1.5 h1:wLv7QyzYpFIyMSwOADq1CLTF9KbjbBfcnfmOGJ64aO4=
github.com/99designs/keyring v1.1.5/go.mod h1:7hsVvt2qXgtadGevGJ4ujg+u8m6SpJ5TpHqTozIPqf0=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet/v3 v3.0.1/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/DataDog/zstd v1.4.6-0.20200617134701-89f69fb7df32 h1:/gZKpgSMydtrih81nvUhlkXpZIUfthKShSCVbRzBt9Y=
github.com/DataDog/zstd v1.4.6-0.20200617134701-89f69fb7df32/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
@ -30,48 +27,24 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=
github.com/Shopify/sarama v1.27.1 h1:iUlzHymqWsITyttu6KxazcAz8WEj5FqcwFK/oEi7rE8=
github.com/Shopify/sarama v1.27.1/go.mod h1:g5s5osgELxgM+Md9Qni9rzo7Rbt+vvFQI4bt/Mc93II=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/aerospike/aerospike-client-go v3.1.0+incompatible h1:ggcqXpZOCBlMptXPooX9MQfOa8aKIPhdCqLwAjR5M9U=
github.com/aerospike/aerospike-client-go v3.1.0+incompatible/go.mod h1:zj8LBEnWBDOVEIJt8LvaRvDG5ARAoa5dBeHaB472NRc=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4=
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/apache/pulsar-client-go v0.2.0 h1:7teu0FaXzzKPjDdUNjA7dVYKFjCy6OVX5as6nUww4qk=
github.com/apache/pulsar-client-go v0.2.0/go.mod h1:POSPPmXv1RuoM7FzHaS3NurCSOopwin2ekGK2PcOgVM=
github.com/apache/pulsar-client-go/oauth2 v0.0.0-20200715083626-b9f8c5cedefb h1:E1P0FudxDdj2RhbveZC9i3PwukLCA/4XQSkBS/dw6/I=
github.com/apache/pulsar-client-go/oauth2 v0.0.0-20200715083626-b9f8c5cedefb/go.mod h1:0UtvvETGDdvXNDCHa8ZQpxl+w3HbdFtfYZvDHLgWGTY=
github.com/ardielle/ardielle-go v1.5.2 h1:TilHTpHIQJ27R1Tl/iITBzMwiUGSlVfiVhwDNGM3Zj4=
github.com/ardielle/ardielle-go v1.5.2/go.mod h1:I4hy1n795cUhaVt/ojz83SNVCYIGsAFAONtv2Dr7HUI=
github.com/ardielle/ardielle-tools v1.5.4/go.mod h1:oZN+JRMnqGiIhrzkRN9l26Cej9dEx4jeNG6A+AdkShk=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q=
github.com/bombsimon/wsl/v3 v3.1.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc=
github.com/boynton/repl v0.0.0-20170116235056-348863958e3e/go.mod h1:Crc/GCZ3NXDVCio7Yr0o+SSrytpcFhLmVCIzi0s49t4=
github.com/cbroglie/mustache v1.2.0/go.mod h1:gomHsVlF4zTcsY2H8d7U9SipCYbbrAks5breARbqAM0=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@ -85,9 +58,9 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/goselect v0.1.1 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0=
github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -96,31 +69,18 @@ github.com/dgraph-io/badger/v2 v2.2007.2 h1:EjjK0KqwaFMlPin1ajhP943VPENHJdEz1KLI
github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE=
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA=
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dimfeld/httptreemux v5.0.1+incompatible/go.mod h1:rbUlSV+CCpv/SuqUTP/8Bk2O3LyUV436/yaRGkhP6Z0=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dvsekhvalnov/jose2go v0.0.0-20180829124132-7f401d37b68a h1:mq+R6XEM6lJX5VlLyZIrUSP8tSuJp82xTK89hvBwJbU=
github.com/dvsekhvalnov/jose2go v0.0.0-20180829124132-7f401d37b68a/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM=
github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q=
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
github.com/fasthttp/websocket v1.4.3 h1:qjhRJ/rTy4KB8oBxljEC00SDt6HUY9jLRfM601SUdS4=
github.com/fasthttp/websocket v1.4.3/go.mod h1:5r4oKssgS7W6Zn6mPWap3NWzNPJNzUUh3baWTOhcYQk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/flosch/pongo2/v4 v4.0.1/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@ -128,13 +88,10 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
github.com/go-critic/go-critic v0.5.0/go.mod h1:4jeRh3ZAVnRYhuWdOEvwzVqLUpxMSoAT0xZ74JsTPlo=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
@ -153,7 +110,6 @@ github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2
github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/gofiber/fiber/v2 v2.1.0/go.mod h1:aG+lMkwy3LyVit4CnmYUbUdgjpc3UYOltvlJZ78rgQ0=
github.com/gofiber/fiber/v2 v2.2.2/go.mod h1:Aso7/M+EQOinVkWp4LUYjdlTpKTBoCk2Qo4djnMsyHE=
github.com/gofiber/fiber/v2 v2.2.4 h1:t2V2SxlbQGdt8+SS/Mo+tQB0pDQn7OajKdA72qHcBVw=
@ -165,8 +121,6 @@ github.com/gofiber/websocket/v2 v2.0.2/go.mod h1:7VBnzEVRK0K0eTIVc5GbXPF1JWUFnll
github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@ -174,7 +128,6 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@ -203,14 +156,11 @@ github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@ -223,17 +173,13 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
github.com/gookit/color v1.2.4/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@ -246,8 +192,6 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@ -259,43 +203,29 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jawher/mow.cli v1.0.4/go.mod h1:5hQj2V8g+qYmLUVWqu4Wuja1pI57M83EChYLVZ0sMKk=
github.com/jawher/mow.cli v1.1.0/go.mod h1:aNaQlc7ozF3vw6IJ2dHjp2ZFiA4ozMIYY6PyuRJwlUg=
github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s=
github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d h1:Z+RDyXzjKE0i2sTjZ/b1uxiGtPhFy34Ou/Tk0qwN0kM=
github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.10.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.10.5/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.10.7 h1:7rix8v8GpI3ZBb0nSozFRgbtXKv+hOe+qfEpZqybrAg=
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.10.8/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.0 h1:wJbzvpYMVGG9iTI9VxpnNZfd4DzMPoCWze3GgSqz8yg=
github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
@ -317,12 +247,10 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-slim v0.0.0-20200618151855-bde33eecb5ee/go.mod h1:ma9TUJeni8LGZMJvOwbAv/FOwiwqIMQN570LnpqCBSM=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
@ -331,39 +259,26 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c=
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nishanths/exhaustive v0.0.0-20200525081945-8e46705b6132/go.mod h1:wBEpHwM2OdmeNpdCvRPUlkEbBuaFmcK4Wv8Q7FuGW3c=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI=
github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -372,31 +287,18 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
github.com/quasilyte/go-ruleguard v0.1.2-0.20200318202121-b00d7a75d3d8/go.mod h1:CGFX09Ci3pq9QZdj86B+VGIdNj4VyCo2iPOGS9esB/k=
github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ=
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/reugn/go-streams v0.5.2 h1:LlNx/CjqA+/B0q1hk3uQTEUGuXrqX4JwcwVsXhfgdFA=
github.com/reugn/go-streams v0.5.2/go.mod h1:j5OY7xE5VAdc6tHfCVsazjyX0ujVKlGwaBXlW/DXnuE=
github.com/reugn/go-streams v0.6.3 h1:b+RqXgcKyOKxU5aX1ddIXCia1UBGeNpO/+yzo0CBVuE=
github.com/reugn/go-streams v0.6.3/go.mod h1:OsEa/+BuietQY5sJUCm5nrZujfWtuKIv7KQVCZCXYGg=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
@ -415,7 +317,6 @@ github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxr
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
@ -432,12 +333,10 @@ github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@ -448,10 +347,8 @@ github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
@ -485,19 +382,15 @@ github.com/valyala/fasthttp v1.17.0/go.mod h1:jjraHZVbKOXftJfsOYoAjaeygpj5hr8erm
github.com/valyala/quicktemplate v1.5.0/go.mod h1:v7yYWpBEiutDyNfVaph6oC/yKwejzVyTX/2cwwHxyok=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a h1:0R4NLDRDZX6JcmhJgXi5E4b8Wg84ihbmUKp/GvSPEzc=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yahoo/athenz v1.8.55 h1:xGhxN3yLq334APyn0Zvcc+aqu78Q7BBhYJevM3EtTW0=
github.com/yahoo/athenz v1.8.55/go.mod h1:G7LLFUH7Z/r4QAB7FfudfuA7Am/eCzO1GlzBhDL6Kv0=
github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5ukMK0AMWEhFaL/lrEOaephfuoiARg=
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
go.bug.st/serial v1.1.1 h1:5J1DpaIaSIruBi7jVnKXnhRS+YQ9+2PLJMtIZKoIgnc=
go.bug.st/serial v1.1.1/go.mod h1:VmYBeyJWp5BnJ0tw2NUJHZdJTGl2ecBGABHlzRK1knY=
go.bug.st/serial v1.1.3 h1:YEBxJa9pKS9Wdg46B/jiaKbvvbUrjhZZZITfJHEJhaE=
go.bug.st/serial v1.1.3/go.mod h1:8TT7u/SwwNIpJ8QaG4s+HTjFt9ReXs2cdOU7ZEk50Dk=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@ -510,11 +403,8 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -552,27 +442,19 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0 h1:5kGOVHlq0euqwzgTC9Vu15p6fV1Wi0ArVi8da2urnVg=
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-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-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -584,7 +466,6 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -595,23 +476,16 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201211090839-8ad439b19e0f h1:QdHQnPce6K4XQewki9WNbG5KOROuDzqO3NaYjI1cXJ0=
golang.org/x/sys v0.0.0-20201211090839-8ad439b19e0f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -625,7 +499,6 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -644,7 +517,6 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -665,7 +537,6 @@ golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200702044944-0cc1aa72b347/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -699,11 +570,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
@ -712,31 +580,15 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4=
gopkg.in/jcmturner/gokrb5.v7 v7.5.0 h1:a9tsXlIDD9SKxotJMK3niV7rPZAJeX2aD/0yg3qlIrg=
gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU=
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

51
readme.md Normal file
View File

@ -0,0 +1,51 @@
# gyropsc ⚡
**Realtime Location Dashboard 🚀**
Comprehensive documentation can be found [here](/tvolkmann/gyrogpsc/src/branch/develop/devdocs.pdf).
# Quickstart
First copy `example_config.yml` to `gpsconfig.yml` and adjust your parameters. if you intend to use this tool with ublox over USB or Serial, you need to set the name of your port in this config file. otherwise the program will panic.
## Requirements
* Win, Linux or Mac Computer, ideally with serial port and WiFi
* GPS device (Android or iOS smartphone and/or ublox 8/M8 series module)
* download latest release and unzip it
## Preparation
Make sure you prepared your GPS device like described in chapter 3.3 of the Documentation.
Start gyrogpsc **before** connecting your smartphone via TCP. Make sure you updated the config file with the correct COM port for your ublox device, before starting the Serial Collector from the UI.
## Run
Execute `gyropgsc-$OS`, according to your operating system. Make sure the containing folder is your working directory.
# Build and run from source
## Requirements
* git
* go >1.15
## Run
1. `git clone --recursive https://git.timovolkmann.de/tvolkmann/gyrogpsc.git`
2. run `go install` from project root
3. run `go run cmd/server/server.go` from project root to start application or just `go build cmd/server/server.go` for building it only
4. open `http://localhost:3011`
## Build
For your platform only: `go build cmd/server/server.go`
**Cross-Platform Builds:**
For Windows 64-bit: `GOOS=windows GOARCH=amd64 go build -o gyrogpsc-win.exe cmd/server/server.go`
For MacOS: `GOOS=darwin GOARCH=amd64 go build -o gyrogpsc-mac cmd/server/server.go`
For Linux: `GOOS=linux GOARCH=arm64 go build -o gyrogpsc-linux cmd/server/server.go`
Make sure that binaries have execute permissions on Mac and Linux. Currently only working on 64-bit systems. Cross Platform commands only working on Mac & Linux.
## Static files and demo database
### Demo database
To use demo data, copy `backups/_db-210115-01/` to folder `_db/` in project root. The prebuilt binaries zip-file already contains demo data.
### Static files
All static files (`static/` and `templates/`) must be in the root of working directory when running gyrogpsc. That's already the case if you follow "Run" instructions.

1
static/indicators Submodule

@ -0,0 +1 @@
Subproject commit 8e879d5461753b0f06e171847408e618d51c9838

56
static/replayStyle.css Normal file
View File

@ -0,0 +1,56 @@
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
.legend {
background-color: #fff;
border-radius: 3px;
bottom: 30px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
padding: 10px;
position: absolute;
right: 10px;
z-index: 1;
}
.legend h4 {
margin: 0 0 10px;
}
.legend div span {
border-radius: 50%;
display: inline-block;
height: 10px;
margin-right: 5px;
width: 10px;
}
body{margin:0; padding:0; font-size:13px; font-family:Georgia, "Times New Roman", Times, serif; color:#919191; background-color:#232323;}
.float-child {
width: 45%;
float: left;
padding: 10px;
}
canvas {
position: absolute;
}
.sceneMap {
width: 1000px;
height: 500px;
border: 0px solid #CCC;
margin: 20px;
perspective: 400px;
}
.scene {
width: 200px;
height: 200px;
border: 0px solid #CCC;
margin: 70px;
perspective: 400px;
}
label { margin-right: 10px; }

114
static/scripts/accChart.js Normal file
View File

@ -0,0 +1,114 @@
/**
* This file defines a line chart used in the full replay page, showing Ublox horizontal accuracy,
* smartphone horizontal accuracy and distance between the positions of the Ublox
* and the Smartphone at a same time in meters, over time.
*
* @authors Timo Volkmann, Frank Herkommer.
*/
//list of all horizontal accuracies sent in by the Ublox
let allAccSerial = []
//list of all coordinates sent in by the Ublox
let allSerialCoords = []
//Defines the chart and its properties
let ctx1 = document.getElementById('accChart').getContext('2d');
let accChart = new Chart(ctx1, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Ublox Horizontal acc. (m)',
backgroundColor: 'rgba(255, 255, 255, 1)',
borderColor: 'rgba(255, 255, 255, 1)',
borderWidth: 1,
fill: false,
pointRadius: 0.5,
lineTension: 0.5,
data: []
},
{
label: 'Smartphone Horizontal acc. (m)',
backgroundColor: 'rgb(185,190,45)',
borderColor: 'rgb(185,190,45)',
borderWidth: 1,
fill: false,
pointRadius: 0.5,
lineTension: 0.5,
data: []
},
{
label: 'Distance Ublox - Smartphone (m)',
backgroundColor: 'rgba(30, 130, 76, 1)',
borderColor: 'rgba(30, 130, 76, 1)',
borderWidth: 1,
fill: false,
pointRadius: 0.5,
lineTension: 0.5,
data: []
}]
},
options: {
scales: {
yAxes: [{
ticks: {
min: 0,
max: 20,
}
}],
xAxes: [{
type: 'time',
time: {
unit: 'second'
}
}]
},
animation: {
duration: 0
}
}
});
/**
* Function to calculate the distance between to coordinates at a same time using the vincenty algorithm.
*
* @param data all collected data.
*/
function addDistances(data){
//collect all horizontal accuracies from serial source and tcp source.
let serialHAccs = data.filter(el => el.ser != null).map(el => {
return el.ser.HAcc
})
let tcpHAccs = data.filter(el => el.tcp != null).map(el => {
return el.tcp.HAcc
})
//apply vincenty algorithm on coordinates from serial and tcp source.
let distances = data.filter(el => el.ser != null && el.tcp != null).map((el, i, arr) => {
// return distVincenty(el.ser.Position, el.tcp.Position)
const plaindist = distVincenty(el.ser.Position, el.tcp.Position)
arr[i]['distance'] = plaindist
// if closest measurements not happening in the exact same millisecond,
// calculate traveled distance for the time difference and substract it from result
// bear in mind that this is not exact because the two measurements are not always exactly aligned with
// the direction of motion. that's okay because we only need to estimate the accuracy
arr[i]['distanceCleanAbs'] = plaindist - Math.abs(el.ser.Speed / 1000 * el.differenceMs)
return arr[i].distanceCleanAbs // plaindist - Math.abs(el.ser.Speed / 1000 * el.differenceMs)
})
let timelabels = data.filter(el => el.tcp != null).map(el => {
return el.tcp.Timestamp
})
if (timelabels.length === 0) {
timelabels = data.filter(el => el.ser != null).map(el => {
return el.ser.Timestamp
})
}
//add the data to the chart and update the hart
accChart.data.labels = timelabels
accChart.data.datasets[0].data = serialHAccs
accChart.data.datasets[1].data = tcpHAccs
accChart.data.datasets[2].data = distances
accChart.update()
}

View File

@ -0,0 +1,79 @@
/**
* This file defines an horizontal bar chart used in the live tracking page, showing Ublox horizontal accuracy,
* smartphone horizontal accuracy and distance between the positions of the Ublox
* and the Smartphone at a same time in meters.
*
* @authors Timo Volkmann, Frank Herkommer.
*/
//Defines the chart and its properties.
let ctx = document.getElementById("accuracy").getContext('2d');
let barChart = new Chart(ctx, {
type: 'horizontalBar',
data: {
labels: ["Meters"],
datasets: [{
label: 'Ublox H. acc.',
data: [0, 0],
backgroundColor: "rgba(214, 69, 65, 1)"
}, {
label: 'Smartphone H. acc.',
data: [0, 0],
backgroundColor: "rgba(30, 139, 195, 1)"
}, {
label: 'Dist. Ublox-Smartphone',
data: [0],
backgroundColor: "rgba(30, 139, 0, 1)"
}]
},
options: {
scales: {
xAxes: [{
ticks: {
min: 0,
max: 10
}
}]
},
legend: {
display: true,
enabled: true
},
tooltips: {
enabled: false,
display: false
}
}
});
/**
* Function to add the Horizontal and Vertical accuracy sent by the Ublox to the chart.
* Called every time a message is received over serial connection from the server.
* @param hacc horizontal accuracy in meters.
* @param vacc vertical accuracy in meters
*/
function addSerialAccuracy(hacc, vacc){
barChart.data.datasets[0].data = [hacc, vacc];
barChart.update();
}
/**
* Function to add the Horizontal and Vertical accuracy sent by the Smartphone to the chart.
* Called every time a message is received over tcp connection from the server.
* @param hacc horizontal accuracy in meters.
* @param vacc vertical accuracy in meters
*/
function addTCPAccuracy(hacc, vacc){
barChart.data.datasets[1].data = [hacc, vacc];
barChart.update();
}
/**
* Function to add the calculated distance between coordinates sent by the Ublox and the coordinates
* sent by the smartphone to the chart.
* @param dist distance calculated in meters.
*/
function addDistanceToBarChart(dist){
barChart.data.datasets[2].data = [dist];
barChart.update();
}

View File

@ -1,43 +0,0 @@
var ctx = document.getElementById('myChart').getContext('2d');
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: new Array(GRAPH_RES),
datasets: [{
label: 'Z',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 1,
pointRadius: 0,
lineTension: 0.5,
data: new Array(GRAPH_RES)
}]
},
options: {
scales: {
yAxes: [{
ticks: {
// beginAtZero: true
min: -100,
max: 100
}
}]
},
animation: {
duration: 0
}
}
});
myChart.data.labels.fill("", 0, GRAPH_RES);
myChart.data.datasets.forEach((dataset) => dataset.data.fill(0, 0, GRAPH_RES))
function addData(data) {
myChart.data.labels.push("");
myChart.data.datasets.forEach((dataset) => {
dataset.data.push(data);
});
while (myChart.data.labels.length >= GRAPH_RES) {
myChart.data.labels.shift();
myChart.data.datasets.forEach((dataset) => dataset.data.shift())
}
myChart.update();
};

View File

@ -0,0 +1,78 @@
/**
* Function to calculate distance between two coordinates using the haversine algorithm (less precise).
* @param coord1 first set of coordinates.
* @param coord2 second set of coordinates.
* @returns {number} distance between the two points in meters.
*/
function distanceInMetersBetweenEarthCoordinates(coord1, coord2) {
let long1 = coord1[0]
let lat1 = coord1[1]
let long2 = coord2[0]
let lat2 = coord2[1]
let earthRadiusM = 6371000
let phi1 = lat1 * Math.PI / 180
let phi2 = lat2 * Math.PI / 180
let dlat = (lat2-lat1) * Math.PI / 180
let dlong = (long2 - long1) * Math.PI / 180
let a = Math.sin(dlat/2) * Math.sin(dlat/2) +
Math.cos(phi1) * Math.cos(phi2) *
Math.sin(dlong/2) * Math.sin(dlong/2)
let c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
return earthRadiusM * c
}
Number.prototype.toRad = function () { return this * Math.PI / 180; }
/**
* Function to calculate distance between two coordinates using the vincenty algorithm (more precise).
* @param coord1 first set of coordinates.
* @param coord2 second set of coordinates.
* @returns {string|number} distance between the two point is meters.
*/
function distVincenty(coord1, coord2) {
const lon1 = coord1[0]
const lat1 = coord1[1]
const lon2 = coord2[0]
const lat2 = coord2[1]
var a = 6378137, b = 6356752.314245, f = 1/298.257223563; // WGS-84 ellipsoid params
var L = (lon2-lon1).toRad()
var U1 = Math.atan((1-f) * Math.tan(lat1.toRad()));
var U2 = Math.atan((1-f) * Math.tan(lat2.toRad()));
var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);
var lambda = L, lambdaP, iterLimit = 100;
do {
var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
var sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) +
(cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda));
if (sinSigma===0) return 0; // co-incident points
var cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda;
var sigma = Math.atan2(sinSigma, cosSigma);
var sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
var cosSqAlpha = 1 - sinAlpha*sinAlpha;
var cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha;
if (isNaN(cos2SigmaM)) cos2SigmaM = 0; // equatorial line: cosSqAlpha=0 (§6)
var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
lambdaP = lambda;
lambda = L + (1-C) * f * sinAlpha *
(sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
} while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0);
if (iterLimit===0) return NaN // formula failed to converge
var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
var s = b*A*(sigma-deltaSigma);
s = s.toFixed(3); // round to 1mm precision
return s;
}

View File

@ -1,737 +0,0 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[
9.196275472640991,
49.12280972701122
],
[
9.19629693031311,
49.122757066649065
],
[
9.196267426013947,
49.1227219597099
],
[
9.196237921714783,
49.12268685274588
],
[
9.196205735206604,
49.122642969005895
],
[
9.196168184280396,
49.12259908522708
],
[
9.196090400218964,
49.122556956762885
],
[
9.196015298366547,
49.12252536039125
],
[
9.195816814899445,
49.122421794365344
],
[
9.19578731060028,
49.12246041223081
],
[
9.195752441883085,
49.122495519355134
],
[
9.195720255374908,
49.1225306264546
],
[
9.195685386657715,
49.12257099958827
],
[
9.195658564567566,
49.122602595930815
],
[
9.195634424686432,
49.12263419225324
],
[
9.195604920387268,
49.12266052250654
],
[
9.19557273387909,
49.12268334204809
],
[
9.195548593997955,
49.12271318297122
],
[
9.195532500743866,
49.12273951318259
],
[
9.195497632026672,
49.12276057734161
],
[
9.19557273387909,
49.12281850373281
],
[
9.195524454116821,
49.12285712128936
],
[
9.195497632026672,
49.12287818539843
],
[
9.195476174354553,
49.122904515522194
],
[
9.195586144924162,
49.12300983587742
],
[
9.195840954780579,
49.1232152099268
],
[
9.19605016708374,
49.1233942532759
],
[
9.196152091026306,
49.1235346789797
],
[
9.196280837059021,
49.12368212554077
],
[
9.196393489837646,
49.12382606104651
],
[
9.196511507034302,
49.123924358225025
],
[
9.196581244468689,
49.12404371882266
],
[
9.196720719337463,
49.124173610911306
],
[
9.196838736534119,
49.12435967195905
],
[
9.196951389312744,
49.124501850213676
],
[
9.197013080120087,
49.1245896143649
],
[
9.19705867767334,
49.12465280445759
],
[
9.19714719057083,
49.12476865275172
],
[
9.197292029857635,
49.12495120102961
],
[
9.197361767292023,
49.12506529336202
],
[
9.197455644607544,
49.125175874910624
],
[
9.197522699832916,
49.12529347723932
],
[
9.197605848312378,
49.12543565281655
],
[
9.197745323181152,
49.125690163634275
],
[
9.197841882705688,
49.12584989044582
],
[
9.19790893793106,
49.12598328826778
],
[
9.197978675365448,
49.12612195141281
],
[
9.198064506053925,
49.12627290072483
],
[
9.19819325208664,
49.126525652032335
],
[
9.198254942893982,
49.12668011053043
],
[
9.198305904865265,
49.12679770929164
],
[
9.198405146598816,
49.12695743253551
],
[
9.198488295078278,
49.12716981099699
],
[
9.198619723320007,
49.12720315959838
],
[
9.198799431324005,
49.127229487425765
],
[
9.199027419090271,
49.12716630061657
],
[
9.19931173324585,
49.127129441607394
],
[
9.199504852294922,
49.12708556179883
],
[
9.19965773820877,
49.12704870272959
],
[
9.199883043766022,
49.12700482284956
],
[
9.200116395950317,
49.1269732293119
],
[
9.200357794761658,
49.12690653177737
],
[
9.200548231601715,
49.12688546937944
],
[
9.20065551996231,
49.126911797375485
],
[
9.200770854949951,
49.12695743253551
],
[
9.200899600982666,
49.12702061961085
],
[
9.20103371143341,
49.12706800986451
],
[
9.201240241527557,
49.12711891045688
],
[
9.20141190290451,
49.12714874871086
],
[
9.2015540599823,
49.127191750868846
],
[
9.201587587594986,
49.127187362895256
],
[
9.201736450195312,
49.1271601574503
],
[
9.201810210943222,
49.127157524664504
],
[
9.201871901750565,
49.12715313668789
],
[
9.201933592557907,
49.12710925690026
],
[
9.202055633068085,
49.12697498450896
],
[
9.20224204659462,
49.12691530777389
],
[
9.20237347483635,
49.126887224579605
],
[
9.202480763196945,
49.126842466955814
],
[
9.20254647731781,
49.12681613892294
],
[
9.202671200037003,
49.126769626030736
],
[
9.202869683504105,
49.126692396981234
],
[
9.202970266342163,
49.12665729282806
],
[
9.203054755926132,
49.12662218865002
],
[
9.203156679868698,
49.12659147247387
],
[
9.203227758407593,
49.126569532336404
],
[
9.203289449214935,
49.126551980219446
],
[
9.203341752290726,
49.12653179527724
],
[
9.203158020973206,
49.126483526903876
],
[
9.203088283538818,
49.12646334193381
],
[
9.203002452850342,
49.12644140173965
],
[
9.202857613563538,
49.12640278697433
],
[
9.202712774276733,
49.126357151303814
],
[
9.202632308006287,
49.126325557353475
],
[
9.202412366867065,
49.12626236939239
],
[
9.202256798744202,
49.126213223144774
],
[
9.202122688293455,
49.12616758729978
],
[
9.201961755752563,
49.12612546186714
],
[
9.2018061876297,
49.12607982594142
],
[
9.201698899269104,
49.126037700434225
],
[
9.201596975326538,
49.125999085354515
],
[
9.20129120349884,
49.125883239934964
],
[
9.201119542121887,
49.125827072361396
],
[
9.200921058654785,
49.12578143616102
],
[
9.200685024261473,
49.125676121692166
],
[
9.200513362884521,
49.12560591192202
],
[
9.200336337089539,
49.12554623353922
],
[
9.199842810630798,
49.125360176943595
],
[
9.199681878089905,
49.12527592467067
],
[
9.1995370388031,
49.12522677744532
],
[
9.199456572532654,
49.12518816173411
],
[
9.19932246208191,
49.12513199337321
],
[
9.199220538139343,
49.12508635653314
],
[
9.199113249778748,
49.125023166992925
],
[
9.199016690254211,
49.12496699844514
],
[
9.198925495147705,
49.1248932771296
],
[
9.198732376098633,
49.12480551351584
],
[
9.198582172393799,
49.124721260300475
],
[
9.198474884033203,
49.124647538619605
],
[
9.198367595672607,
49.12459839077146
],
[
9.198265671730042,
49.12455626400573
],
[
9.198126196861267,
49.124542221742516
],
[
9.198099374771118,
49.12450360549822
],
[
9.198126196861267,
49.12441584119478
],
[
9.198174476623533,
49.12433509789843
],
[
9.19828176498413,
49.12430701324279
],
[
9.19831931591034,
49.12427190740087
],
[
9.198254942893982,
49.12420871682283
],
[
9.198115468025208,
49.124149036758475
],
[
9.197981357574463,
49.124085846023824
],
[
9.197836518287659,
49.124026165811614
],
[
9.197707772254944,
49.12395595370529
],
[
9.197557568550108,
49.12386467781845
],
[
9.197487831115723,
49.12381201857656
],
[
9.197385907173157,
49.123717231800214
],
[
9.197214245796204,
49.12367510428589
],
[
9.197010397911072,
49.123566274708274
],
[
9.196908473968506,
49.123474998104186
],
[
9.196704626083374,
49.123401274570526
],
[
9.196602702140808,
49.123348614836544
],
[
9.196484684944153,
49.123260848488954
],
[
9.196339845657349,
49.12319414596088
],
[
9.196216464042664,
49.12313095400942
],
[
9.196135997772217,
49.12306776197748
],
[
9.19607162475586,
49.12299754851418
],
[
9.195937514305115,
49.12295893106699
],
[
9.195835590362549,
49.12289222813283
],
[
9.195733666419983,
49.12282903579667
],
[
9.195638447999954,
49.122754433629495
],
[
9.195600226521492,
49.12273293063105
],
[
9.195574074983597,
49.122708355764296
],
[
9.195574074983597,
49.12269431297779
],
[
9.195623695850372,
49.1226412136555
],
[
9.195666611194609,
49.12260961733753
],
[
9.195688739418983,
49.12258021519025
],
[
9.195733666419983,
49.12253106529318
],
[
9.19575646519661,
49.12250034658276
],
[
9.195783287286758,
49.12247269972715
],
[
9.195801392197609,
49.12244988008865
],
[
9.195815473794937,
49.122438470265486
],
[
9.195830225944519,
49.12242047784666
],
[
9.195859730243683,
49.122431887674
],
[
9.195901304483414,
49.122448563570735
],
[
9.195931479334831,
49.12246787249679
],
[
9.195965006947517,
49.12248367070343
],
[
9.195998534560204,
49.122498152388445
],
[
9.19603407382965,
49.12251307290797
],
[
9.196063578128815,
49.12253501484029
],
[
9.196093752980232,
49.122546424641236
],
[
9.196127280592918,
49.1225600286313
],
[
9.19615812599659,
49.12258109286655
],
[
9.196201711893082,
49.12261927177015
],
[
9.196238592267036,
49.12267237111598
],
[
9.196262061595917,
49.12270045669739
],
[
9.196281507611275,
49.122722837383684
],
[
9.196305647492409,
49.122746534569934
],
[
9.196302965283394,
49.1227693540719
],
[
9.196292236447334,
49.12279392890845
],
[
9.196286201477049,
49.122811482355665
],
[
9.19628955423832,
49.12282815812477
],
[
9.196309000253677,
49.1228479057388
]
]
}
}
]
}

View File

@ -0,0 +1,211 @@
let width = document.getElementById("viewport").offsetWidth
let height = 300;
let renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(width, height);
document.getElementById("viewport").appendChild(renderer.domElement);
let scene = new THREE.Scene();
let cubeGeometry = new THREE.CubeGeometry(100, 100/2, 100*1.5);
let cubeGeometry2 = new THREE.CubeGeometry(120, 120/2, 120*1.5);
const color = new THREE.Color("rgb(255, 0, 0)");
let cubeMaterial = new THREE.MeshPhongMaterial({
color: color,
opacity: 1,
transparent: true,
});
const color2 = new THREE.Color("rgb(48,117,255)");
let cubeMaterial2 = new THREE.MeshPhongMaterial({
color: color2,
opacity: 0.5,
transparent: true,
});
let cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
let cube2 = new THREE.Mesh(cubeGeometry2, cubeMaterial2);
scene.add(cube);
scene.add(cube2);
let camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 6000);
camera.position.y = 100;
camera.position.z = 240;
camera.lookAt(cube.position);
scene.add(camera);
let skyboxGeometry = new THREE.CubeGeometry(5000, 5000, 5000);
let skyboxMaterial = new THREE.MeshBasicMaterial({ color: 0x232323, side: THREE.BackSide });
let skybox = new THREE.Mesh(skyboxGeometry, skyboxMaterial);
scene.add(skybox);
const colorl = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(colorl, intensity);
let pointLight = new THREE.PointLight(0xffffff);
pointLight.position.set(0, 300, 200);
light.position.set(0, 300, 200);
scene.add(pointLight);
cube.position.x = 0
cube2.position.x = 0
// calibration globals
let manCalibration = new THREE.Euler( 0, 0, 0, 'XYZ' )
let calibrationRot = new THREE.Quaternion()
let calPitch = 0
let calRoll = 0
let calYaw = 0
let quaternionOffset = document.getElementById("quaternionOffset")
// function to set current cube rotation from sensor data with respect to calibration
function renderTCP(x, y, z) {
let calibration = new THREE.Quaternion().setFromEuler(manCalibration)
let eul = new THREE.Euler( x, y, z, 'YXZ' );
cube2.quaternion.setFromEuler(eul).multiply(calibrationRot).multiply(calibration)
quaternionOffset.innerHTML = `Lage Abweichung: ${(cube2.quaternion.angleTo(cube.quaternion) * 180 / Math.PI).toFixed(2) }°`
renderer.render(scene, camera);
}
// function to set current cube rotation from sensor data
function renderSerial(x, y, z) {
let eul = new THREE.Euler( x, y, z, 'YXZ' ); // XYZ XZY YZX YXZ ZXY ZYX
cube.quaternion.setFromEuler(eul)
renderer.render(scene, camera);
}
renderTCP(0, 0, 0);
renderSerial(0, 0, 0);
let pitchRange = document.getElementById("pitchRange");
let yawRange = document.getElementById("yawRange");
let rollRange = document.getElementById("rollRange");
pitchRange.oninput = () => {
manCalibration.x = pitchRange.value * Math.PI / 180
}
yawRange.oninput = () => {
manCalibration.y = yawRange.value * Math.PI / 180
}
rollRange.oninput = () => {
manCalibration.z = rollRange.value * Math.PI / 180
}
function delCalibration(evt) {
calibrationRot = new THREE.Quaternion()
manCalibration = new THREE.Euler( 0, 0, 0, 'XYZ' )
pitchRange.value = 0
yawRange.value = 0
rollRange.value = 0
calRoll = 0
calPitch = 0
}
let calState = false;
function manualCalibration(evt) {
let con = document.getElementById("manCalContainer")
console.log("mancal", con.style.display)
if (calState === false) {
delCalibration()
calState = !calState
console.log("mancal ON")
con.style.display = "block"
} else {
delCalibration()
calState = !calState
console.log("mancal OFF")
con.style.display = "none"
manCalibration = new THREE.Euler( 0, 0, 0, 'XYZ' )
pitchRange.value = 0
yawRange.value = 0
rollRange.value = 0
}
}
function calibrate(evt) {
let serOrientation = cube.quaternion.clone()
let tcpOrientation = cube2.quaternion.clone().multiply(calibrationRot.clone().invert())
let diff = tcpOrientation.invert().multiply(serOrientation)
let old = new THREE.Euler().setFromQuaternion( cube2.quaternion, 'YXZ' )
let dif = new THREE.Euler().setFromQuaternion( diff )
console.log("OLD:","pitch", old.x * 180/Math.PI, "yaw", old.y * 180/Math.PI, "roll", old.z * 180/Math.PI)
console.log("DIFF:","pitch", dif.x * 180/Math.PI, "yaw", dif.y * 180/Math.PI, "roll", dif.z * 180/Math.PI)
calPitch = dif.x
calYaw = dif.y
calRoll = dif.z
calibrationRot = diff
}
document.getElementById("deleteCalibration").onclick = delCalibration
document.getElementById("manualCalibration").onclick = manualCalibration
document.getElementById("calibrate").onclick = calibrate
// indicators from https://github.com/sebmatton/jQuery-Flight-Indicators.git
let options = {
size : 200, // Sets the size in pixels of the indicator (square)
roll : 0, // Roll angle in degrees for an attitude indicator
pitch : 0, // Pitch angle in degrees for an attitude indicator
heading: 0, // Heading angle in degrees for an heading indicator
vario: 0, // Variometer in 1000 feets/min for the variometer indicator
airspeed: 0, // Air speed in knots for an air speed indicator
altitude: 0, // Altitude in feets for an altimeter indicator
pressure: 1000, // Pressure in hPa for an altimeter indicator
showBox : true, // Sets if the outer squared box is visible or not (true or false)
img_directory : 'static/indicators/img/' // The directory where the images are saved to
}
let headingSer = $.flightIndicator('#headingSer', 'heading', options);
let headingTcp = $.flightIndicator('#headingTcp', 'heading', options);
let attitudeSer = $.flightIndicator('#attitudeSer', 'attitude', options);
let attitudeTcp = $.flightIndicator('#attitudeTcp', 'attitude', options);
let airspeed = $.flightIndicator('#airspeed', 'airspeed', options);
let altimeter = $.flightIndicator('#altimeter', 'altimeter', options);
let airspeedLabel = document.getElementById("airspeedLabel")
let altitudeLabel = document.getElementById("altitudeLabel")
// function to set analog indictors from current sensor data
function setIndicatorsTcp(sensordata) {
let q = new THREE.Euler().setFromQuaternion( cube2.quaternion, 'YXZ' ) // XYZ XZY YZX YXZ ZXY ZYX
if (sensordata.Orientation[0] !== 0 && sensordata.Orientation[1] !== 0) {
attitudeTcp.setPitch(q.x * 180 / Math.PI)
attitudeTcp.setRoll(q.z * 180 / Math.PI)
}
let heading = sensordata.HeadDevice
if (heading !== 0) {
headingTcp.setHeading(heading)
}
}
// function to set analog indictors from current sensor data
function setIndicatorsSer(sensordata) {
if (sensordata.Orientation[0] !== 0 && sensordata.Orientation[1] !== 0) {
attitudeSer.setPitch(sensordata.Orientation[0])
attitudeSer.setRoll(sensordata.Orientation[1])
}
let heading = sensordata.HeadMotion
if (heading !== 0) {
headingSer.setHeading(heading)
}
if (sensordata.Speed !== 0) {
airspeed.setAirSpeed(sensordata.Speed * 3.6)
airspeedLabel.innerHTML = `Ref. Speed: ${(sensordata.Speed * 3.6).toFixed(1)} km/h`
}
if (sensordata.Position[2] !== 0) {
altimeter.setAltitude((sensordata.Position[2] * 10).toFixed())
altitudeLabel.innerHTML = `HMSL: ${(sensordata.Position[2]).toFixed(2)} m`
}
}

View File

@ -1,22 +1,30 @@
/**
* This file defines the map in satellite style for the live tracking page, defines and updates its data sources.
*
* @authors Timo Volkmann, Frank Herkommer.
*/
mapboxgl.accessToken = 'pk.eyJ1IjoiZmhlcmtvbW0iLCJhIjoiY2tobm81bXppMGVuNzMyazY3eDU0M2dyaSJ9.qWJrwtv7KitW60pzs6h3Gg';
var map = new mapboxgl.Map({
let map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
style: 'mapbox://styles/mapbox/satellite-v9',
zoom: 0
});
var emptyTCP = {
type: "FeatureCollection",
features: [
{
type: "Feature",
geometry: {
type: "LineString",
coordinates: []
}
}]
}
var emptySERIAL = {
//Empty geoJSON for coordinates from TCP input
let emptyTCP = {
type: "FeatureCollection",
features: [
{
type: "Feature",
geometry: {
type: "LineString",
coordinates: []
}
}]
}
//Empty geoJSON for coordinates from serial input
let emptySERIAL = {
type: "FeatureCollection",
features: [
{
@ -28,15 +36,10 @@ var emptySERIAL = {
}]
}
/**
* Set up the map with its data sources and its different layers.
*/
map.on('load', function () {
// save full coordinate list for later
//var coordinates = data.features[0].geometry.coordinates;
// start by showing just the first coordinate
//data.features[0].geometry.coordinates = [coordinates[0]];
// add it to the map
//map.addSource('trace', { type: 'geojson', data: data });
map.addSource('routeTCP', { 'type': 'geojson', 'data': emptyTCP })
map.addSource('routeSERIAL', { 'type': 'geojson', 'data': emptySERIAL })
@ -49,8 +52,8 @@ map.on('load', function () {
'visibility': 'visible'
},
'paint': {
'line-color': 'blue',
'line-opacity': 0.75,
'line-color': 'rgba(30, 139, 195, 1)',
'line-opacity': 1,
'line-width': 5
}
});
@ -63,28 +66,42 @@ map.on('load', function () {
'visibility': 'visible'
},
'paint': {
'line-color': 'yellow',
'line-opacity': 0.75,
'line-color': 'rgba(214, 69, 65, 1)',
'line-opacity': 1,
'line-width': 5
}
});
map.jumpTo({ 'center': [9.19640999, 49.12283027], 'zoom': 17 });
map.jumpTo({ 'center': [9.19640999, 49.12283027], 'zoom': 16 });
map.setPitch(30);
var stateLegendEl = document.getElementById('state-legend');
let stateLegendEl = document.getElementById('state-legend');
stateLegendEl.style.display = 'block';
})
function updateMapTCP (long, lat) {
emptyTCP.features[0].geometry.coordinates.push([long, lat]);
/**
* Function to push coordinates data coming from a TCP connection
* into the emptyTCP geoJSON object.
* Called every time a message is received over TCP from the server.
*
* @param TCPlong longitude coming in from device connected over TCP port.
* @param TCPlat latitude coming in from device connected over TCP port.
*/
function updateMapTCP (TCPlong, TCPlat) {
emptyTCP.features[0].geometry.coordinates.push([TCPlong, TCPlat]);
map.getSource('routeTCP').setData(emptyTCP);
map.panTo([long, lat]);
}
function updateMapSERIAL (long, lat) {
emptySERIAL.features[0].geometry.coordinates.push([long, lat]);
/**
* Function to push coordinates data coming from a serial connection
* into the emptySERIAL geoJSON object.
* Called every time a message is received over serial connection from the server.
*
* @param SERIALlong longitude coming in from device connected over serial port.
* @param SERIALlat latitude coming in from device connected over serial port.
*/
function updateMapSERIAL (SERIALlong, SERIALlat) {
emptySERIAL.features[0].geometry.coordinates.push([SERIALlong, SERIALlat]);
map.getSource('routeSERIAL').setData(emptySERIAL);
//map.panTo([long, lat]);
}
}

116
static/scripts/mapfull.js Normal file
View File

@ -0,0 +1,116 @@
/**
* This file defines the map in satellite style for the full replay page.
*
* @authors Timo Volkmann, Frank Herkommer.
*/
mapboxgl.accessToken = 'pk.eyJ1IjoiZmhlcmtvbW0iLCJhIjoiY2tobm81bXppMGVuNzMyazY3eDU0M2dyaSJ9.qWJrwtv7KitW60pzs6h3Gg';
let map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/satellite-v9',
zoom: 0
});
/**
* Set up the map with its data sources and its different layers.
*/
map.on('load', function () {
map.addSource('routeTCP', { 'type': 'geojson', 'data': null })
map.addSource('routeSERIAL', { 'type': 'geojson', 'data': null })
map.addLayer({
'id': 'routeTCP',
'type': 'line',
'source': 'routeTCP',
'layout': {
'visibility': 'visible'
},
'paint': {
'line-color': 'rgba(30, 139, 195, 1)',
'line-opacity': 1,
'line-width': 5
}
});
map.addLayer({
'id': 'routeSERIAL',
'type': 'line',
'source': 'routeSERIAL',
'layout': {
'visibility': 'visible'
},
'paint': {
'line-color': 'rgba(214, 69, 65, 1)',
'line-opacity': 1,
'line-width': 5
}
});
map.jumpTo({ 'center': [9.19640999, 49.12283027], 'zoom': 16 });
map.setPitch(30);
let stateLegendEl = document.getElementById('state-legend');
stateLegendEl.style.display = 'block';
})
/**
* Function to add all collected coordinates sent in by the Smartphone to the map.
* @param sensordataList List of all received messages of the tcp source and their sensor data
*/
function updateMapTCPbulk(sensordataList) {
console.log("add TCP data to map")
let geoJsonTCP = {
type: "FeatureCollection",
features: [
{
type: "Feature",
geometry: {
type: "LineString",
coordinates: []
}
}]
}
sensordataList.forEach(sensordata => {
if (sensordata.Position[0] === 0 && sensordata.Position[1] === 0 && sensordata.Position[2] === 0) {
return;
}
let lat = sensordata.Position[0]
let lon = sensordata.Position[1]
geoJsonTCP.features[0].geometry.coordinates.push([lon, lat]);
})
map.getSource('routeTCP').setData(geoJsonTCP);
map.panTo(geoJsonTCP.features[0].geometry.coordinates[geoJsonTCP.features[0].geometry.coordinates.length-1]);
}
/**
* Function to add all collected coordinates sent in by the Ublox to the map.
* @param sensordataList List of all received messages of the serial source and their sensor data
*/
function updateMapSERIALbulk(sensordataList) {
console.log("add SERIAL data to map")
let geoJsonSerial = {
type: "FeatureCollection",
features: [
{
type: "Feature",
geometry: {
type: "LineString",
coordinates: []
}
}]
}
sensordataList.forEach(sensordata => {
if (sensordata.Position[0] === 0 && sensordata.Position[1] === 0 && sensordata.Position[2] === 0) {
return;
}
let lat = sensordata.Position[0]
let lon = sensordata.Position[1]
geoJsonSerial.features[0].geometry.coordinates.push([lon, lat]);
})
map.getSource('routeSERIAL').setData(geoJsonSerial);
map.panTo(geoJsonSerial.features[0].geometry.coordinates[geoJsonSerial.features[0].geometry.coordinates.length-1]);
}

188
static/scripts/refull.js Normal file
View File

@ -0,0 +1,188 @@
/**
* This file contains all the controls for the full replay page.
*
* @authors Timo Volkmann, Frank Herkommer.
*/
// indexes of a list of all collected Serial data where Timestamps matched with the smallest time
//difference so they can be fitted on a chart with a smaller amount of tcp data
let indexes
/**
* function to parse a timestamp to a number
* @param time Timestamp
* @returns composed a number
*/
function composeTimestamp(time){
let composed;
composed = time.slice(11,25).split(':').join("").split('.').join("");
return composed;
}
/**
* function to compare a Timestamp from tcp to all timestamps from serial to find a best match.
* @param num tcp timestamp
* @param arr list of serial timestamps
* @returns {(*|number)[]} array with the index of the closest Serial timestamp match
*/
function findBestTimeMatch(num, arr) {
let mid;
let lo = 0;
let hi = arr.length - 1;
while (hi - lo > 1) {
mid = Math.floor ((lo + hi) / 2);
if (arr[mid] < num) {
lo = mid;
} else {
hi = mid;
}
}
if (num - arr[lo] <= arr[hi] - num) {
return [arr[lo], lo];
}
return [arr[hi], hi];
}
/**
* function that calls find best match function and saves the indexes from a match into a list.
* @param tcpdataList
* @param serialdataList
*/
function findSerialDataIndex(tcpdataList, serialdataList) {
let allSerialTimes = []
serialdataList.forEach(sensordata => {
if (sensordata.Speed !== 0 && sensordata.HAcc !== 0) {
allSpeedsSerial.push(sensordata.Speed)
allAccSerial.push(sensordata.HAcc)
if(sensordata.Position[1] !== 0 && sensordata.Position[0] !== 0){
allSerialCoords.push([sensordata.Position[1], sensordata.Position[0]])
}
let serialTimestamp = composeTimestamp(sensordata.Timestamp)
allSerialTimes.push(serialTimestamp)
}
})
tcpdataList.forEach(sensordata => {
if (sensordata.Speed !== 0 && sensordata.HAcc !== 0) {
let tcpTimestamp = composeTimestamp(sensordata.Timestamp)
let index = findBestTimeMatch(tcpTimestamp, allSerialTimes)[1]
indexes.push(index)
}
})
}
window.addEventListener("load", function(evt) {
//------------------------Buttons------------------------------
let trackings = null;
document.getElementById("messungladen").onclick = function(evt) {
fetch('http://localhost:3011/trackings/', { method: 'GET'}).then(results => {
return results.json()
}).then(r => {
console.log(r)
if (!'data' in r) {
return
}
trackings = r.data
let sel = document.getElementById("meas")
sel.innerHTML = ''
r.data.sort((a,b) => new Date(b.TimeCreated).getTime() - new Date(a.TimeCreated).getTime());
r.data.forEach(tracking => {
console.log(tracking)
let option = document.createElement("option");
option.text = tracking.TimeCreated + " Size: " + tracking.Size
sel.add(option)
})
sel.disabled = false
document.getElementById("replaystarten").disabled = false
})
};
document.getElementById("replaystarten").onclick = function(evt) {
indexes = []
allSpeedsSerial = []
allAccSerial = []
allSerialCoords = []
let sel = document.getElementById("meas")
console.log(trackings[sel.selectedIndex].UUID)
fetch(`http://localhost:3011/trackings/${trackings[sel.selectedIndex].UUID}`, { method: 'GET'}).then(results => {
return results.json()
}).then(r => {
// console.log(r.data.Data)
// console.log(r.data)
if('SOURCE_SERIAL' in r.data.Data && r.data.Data.SOURCE_SERIAL.length > 0 && 'SOURCE_TCP' in r.data.Data && r.data.Data.SOURCE_TCP.length > 0) {
findSerialDataIndex(r.data.Data.SOURCE_TCP, r.data.Data.SOURCE_SERIAL)
}
if ('SOURCE_TCP' in r.data.Data && r.data.Data.SOURCE_TCP.length > 0) {
updateMapTCPbulk(r.data.Data.SOURCE_TCP)
addTCPSpeedData(r.data.Data.SOURCE_TCP)
}
if ('SOURCE_SERIAL' in r.data.Data && r.data.Data.SOURCE_SERIAL.length > 0) {
updateMapSERIALbulk(r.data.Data.SOURCE_SERIAL)
addSerialSpeedData(r.data.Data.SOURCE_SERIAL)
}
addDistances(prepareForDistanceCalc(r.data.Data))
})
}
});
function prepareForDistanceCalc(data) {
if('SOURCE_TCP' in data && data.SOURCE_TCP.length > 0 && 'SOURCE_SERIAL' in data && data.SOURCE_SERIAL.length > 0) {
let sensorPoints = [];
data.SOURCE_TCP.forEach(element => {
if (element.Position[0] !== 0 && element.Position[1] !== 0) {
sensorPoints.push({
differenceMs: Number.MAX_VALUE,
tcp: element,
ser: null,
})
}
})
sensorPoints.forEach((el, index, arr) => {
let tcpTS = Date.parse(el.tcp.Timestamp)
data.SOURCE_SERIAL.forEach(serElement => {
let serTS = Date.parse(serElement.Timestamp)
if (Math.abs(tcpTS - serTS) < el.differenceMs && serElement.Position[0] !== 0 && serElement.Position[1] !== 0) {
el.differenceMs = tcpTS - serTS
arr[index].ser = serElement
}
})
})
// filter differences with more than 50 ms
let newSensorPoints = sensorPoints.filter(el => {
return Math.abs(el.differenceMs) < 50
})
console.log("SENSORPOINTs", newSensorPoints)
return newSensorPoints
} else if ('SOURCE_SERIAL' in data && data.SOURCE_SERIAL.length > 0) {
let sensorPoints = [];
data.SOURCE_SERIAL.forEach(element => {
if (element.Position[0] !== 0 && element.Position[1] !== 0) {
sensorPoints.push({
differenceMs: Number.MAX_VALUE,
tcp: null,
ser: element,
})
}
})
return sensorPoints
} else if ('SOURCE_TCP' in data && data.SOURCE_TCP.length > 0) {
let sensorPoints = [];
data.SOURCE_TCP.forEach(element => {
if (element.Position[0] !== 0 && element.Position[1] !== 0) {
sensorPoints.push({
differenceMs: Number.MAX_VALUE,
tcp: element,
ser: null,
})
}
})
return sensorPoints
}
return null
}

View File

@ -0,0 +1,115 @@
/**
* This file defines a line chart used in the full replay page, showing Ublox speeds and
* smartphone speeds at a same moment in time in km/h, over time.
*
* @authors Timo Volkmann, Frank Herkommer.
*/
//List of all speeds sent in by the Ublox
let allSpeedsSerial = []
//Define the line chart and its properties
let ctx = document.getElementById('speedChart').getContext('2d');
let speedChart = new Chart(ctx, {
type: 'line',
data: {
labels: new Array(),
datasets: [{
label: 'Ublox speed',
backgroundColor: 'rgba(214, 69, 65, 1)',
borderColor: 'rgba(214, 69, 65, 1)',
borderWidth: 1,
fill: false,
pointRadius: 0.5,
lineTension: 0.5,
data: []
},
{
label: 'Smartphone speed',
backgroundColor: 'rgba(30, 139, 195, 1)',
borderColor: 'rgba(30, 139, 195, 1)',
borderWidth: 1,
fill: false,
pointRadius: 0.5,
lineTension: 0.5,
data: []
}]
},
options: {
scales: {
yAxes: [{
ticks: {
min: 0,
max: 250,
stepSize: 25
}
}],
xAxes: [{
type: 'time',
time: {
unit: 'second'
}
}]
},
animation: {
duration: 0
}
}
});
/**
* Function filtering out speed data sent in by the Ublox using only the given indexes calculated in the refull.js file,
* and adding the data to the chart.
* As there is less smartphone data collected, we only want the Ublox speeds that come in at the moment with the
* smallest time difference between the TCP message and the Serial message.
*/
function addSerialSpeedData(sensordataList) {
let speedsSerial = []
let times = []
if(indexes.length > 0){
indexes.forEach(index => {
speedsSerial.push((allSpeedsSerial[index] * 3.6).toFixed(2))
})
speedChart.data.datasets[0].data = speedsSerial;
speedChart.update();
} else {
sensordataList.forEach(sensordata => {
if (sensordata.Speed === 0) {
return;
}
let speed = sensordata.Speed
speedsSerial.push((speed * 3.6).toFixed(2));
let time = sensordata.Timestamp
times.push(time)
})
speedChart.data.labels = times;
speedChart.data.datasets[0].data = speedsSerial;
speedChart.update();
}
}
/**
* Function to extract only the speeds and Timestamps out of the messages received from the smartphone
* and add tem to the chart.
* @param sensordataList List of all received messages of the tcp source and their sensor data.
*/
function addTCPSpeedData(sensordataList) {
let speedsTCP = []
let times = []
sensordataList.forEach(sensordata => {
if (sensordata.Speed === 0) {
return;
}
let speed = sensordata.Speed
speedsTCP.push((speed * 3.6).toFixed(2));
let time = sensordata.Timestamp
times.push(time)
})
speedChart.data.labels = times;
speedChart.data.datasets[1].data = speedsTCP;
speedChart.update();
}

View File

@ -0,0 +1,183 @@
/**
* This file defines a doughnut chart used in the live tracking page, representing a speedometer showing
* Ublox speeds and smartphone speeds in km/h.
*
* @authors Timo Volkmann, Frank Herkommer.
*/
let options1 = {
type: 'doughnut',
data: {
datasets: [
{
label: 'speedTCP',
data: [0,100],
backgroundColor: [
'rgba(30, 139, 195, 1)',
'rgba(191, 191, 191, 1)'
],
borderColor: [
'rgba(255, 255, 255 ,1)',
'rgba(255, 255, 255 ,1)'
],
borderWidth: 0
},
{
label: 'speedSERIAl',
data: [0, 100],
backgroundColor: [
'rgba(214, 69, 65, 1)',
'rgba(191, 191, 191, 1)'
],
borderColor: [
'rgba(255, 255, 255 ,1)',
'rgba(255, 255, 255 ,1)'
],
borderWidth: 0
}
]
},
options: {
rotation: Math.PI,
circumference: Math.PI,
legend: {
display: false,
enabled: false
},
tooltips: {
enabled: false,
display: false
},
cutoutPercentage: 40
}
}
let options2 = {
type: 'doughnut',
data: {
datasets: [
{
label: 'speedMarks',
data: [50,50,50,50],
backgroundColor: [
'rgba(30, 130, 76, 1)',
'rgba(244, 208, 63, 1)',
'rgba(235, 151, 78, 1)',
'rgba(217, 30, 24, 1)'
],
borderColor: [
'rgba(30, 130, 76, 1)',
'rgba(244, 208, 63, 1)',
'rgba(235, 151, 78, 1)',
'rgba(217, 30, 24, 1)'
],
borderWidth: 0
},
{
label: 'transparent',
data: [100],
backgroundColor: [
'rgba(0, 0, 0, 0)'
],
borderColor: [
'rgba(0, 0, 0, 0)'
],
borderWidth: 2
},
{
label: 'transparent1',
data: [100],
backgroundColor: [
'rgba(0, 0, 0, 0)'
],
borderColor: [
'rgba(0, 0, 0, 0)'
],
borderWidth: 2
},
{
label: 'transparent2',
data: [100],
backgroundColor: [
'rgba(0, 0, 0, 0)'
],
borderColor: [
'rgba(0, 0, 0, 0)'
],
borderWidth: 2
},
{
label: 'transparent3',
data: [100],
backgroundColor: [
'rgba(0, 0, 0, 0)'
],
borderColor: [
'rgba(0, 0, 0, 0)'
],
borderWidth: 2
},
{
label: 'speedMarks1',
data: [50,50,50,50],
backgroundColor: [
'rgba(30, 130, 76, 1)',
'rgba(244, 208, 63, 1)',
'rgba(235, 151, 78, 1)',
'rgba(217, 30, 24, 1)'
],
borderColor: [
'rgba(30, 130, 76, 1)',
'rgba(244, 208, 63, 1)',
'rgba(235, 151, 78, 1)',
'rgba(217, 30, 24, 1)'
],
borderWidth: 0
},
]
},
options: {
rotation: Math.PI,
circumference: Math.PI,
legend: {
display: false,
enabled: false
},
tooltips: {
enabled: false,
display: false
},
cutoutPercentage: 65
}
}
//chart showing the actual speed of the two devices.
let ctx1 = document.getElementById('speedometer').getContext('2d');
let mySpeedometer = new Chart(ctx1, options1);
//chart adding the different speed areas colored from green to red.
let ctx2 = document.getElementById('speedometerSpeeds').getContext('2d');
let mySpeedometer2 = new Chart(ctx2, options2);
/**
* Function that adds speed data sent by the Ublox, converted to km/h, to the speedometer.
* Called every time a message is received over serial connection from the server.
* @param speedSERIAL speed of the Ublox in m/s
*/
function addSpeedSerial(speedSERIAL){
let speedSERIALkmh = (speedSERIAL * 3.6)
let speedSERIALpercent = (speedSERIALkmh/250)*100
mySpeedometer.data.datasets[1].data = [speedSERIALpercent, 100-speedSERIALpercent];
document.getElementById("speedSERIAL").innerHTML = `Ublox: ${speedSERIALkmh.toFixed(1)} km/h`
mySpeedometer.update();
}
/**
* Function that adds speed data sent by the Smartphone, converted to km/h, to the speedometer.
* Called every time a message is received over tcp connection from the server.
* @param speedTCP speed of the Smartphone in m/s
*/
function addSpeedTcp(speedTCP){
let speedTCPkmh = (speedTCP * 3.6)
let speedTCPpercent = (speedTCPkmh/250)*100;
mySpeedometer.data.datasets[0].data = [speedTCPpercent, 100-speedTCPpercent];
document.getElementById("speedTCP").innerHTML = `Phone: ${speedTCPkmh.toFixed(1)} km/h`
mySpeedometer.update();
}

View File

@ -1,85 +1,175 @@
const GRAPH_RES = 100;
var dataSmartphone = [];
// Temporary TCP and Serial data used to calculate vincenty distance between two coordinates in realtime.
let tempTCPCoords = null;
let tempSERIALCoords = null;
window.addEventListener("load", function(evt) {
var orientation = [0,0,0];
var multiplier = 180/Math.PI/15
var output = document.getElementById("output");
var input = document.getElementById("input");
var ws;
var print = function(message) {
var d = document.createElement("div");
d.textContent = message;
output.appendChild(d);
};
var print2 = function(message) {
var d = document.createElement("p");
let output = document.getElementById("output");
let checkBoxSmartphone = document.getElementById("checkbox1");
let checkBoxUblox = document.getElementById("checkbox2");
let ws;
const wsOnCloseF = function (evt) {
ws = null;
print2("CLOSED");
let intervalId;
intervalId = setInterval(() => {
console.log("reconnect websocket...")
if (ws !== null && ws.CONNECTING) {
return
}
if (ws !== null && ws.OPEN) {
clearInterval(intervalId)
return
}
ws = new WebSocket("ws://localhost:3011/ws");
ws.onopen = wsOnOpenF
ws.onclose = wsOnCloseF
ws.onmessage = wsOnMessageF
ws.onerror = wsOnErrorF
}, 1000)
}
// function called every time a message is received by the server.
const wsOnMessageF = function (evt) {
let dat = JSON.parse(evt.data)
// If message comes from TCP source call functions to add TCP data != 0 to the charts
if ('SOURCE_TCP' in dat) {
setIndicatorsTcp(dat.SOURCE_TCP)
if(dat.SOURCE_TCP.Orientation[0] !== 0 && dat.SOURCE_TCP.Orientation[1] !== 0 && dat.SOURCE_TCP.Orientation[2] !== 0){
let heading = (dat.SOURCE_TCP.Orientation[2]+90)%360
renderTCP((dat.SOURCE_TCP.Orientation[0]*Math.PI/180),heading*Math.PI/180,-(dat.SOURCE_TCP.Orientation[1]*Math.PI/180))
}
if(dat.SOURCE_TCP.Position[1] !== 0 && dat.SOURCE_TCP.Position[0] !== 0){
document.getElementById("TCPlong").innerHTML = "Smartphone long: " + dat.SOURCE_TCP.Position[1]
document.getElementById("TCPlat").innerHTML = "Smartphone lat: " + dat.SOURCE_TCP.Position[0]
updateMapTCP(dat.SOURCE_TCP.Position[1], dat.SOURCE_TCP.Position[0])
map.panTo([dat.SOURCE_TCP.Position[1], dat.SOURCE_TCP.Position[0]])
tempTCPCoords = dat.SOURCE_TCP
}
if(dat.SOURCE_TCP.Speed !== 0){
addSpeedTcp(dat.SOURCE_TCP.Speed);
}
if(dat.SOURCE_TCP.HeadDevice !== 0){
document.getElementById("compassTCP").innerHTML = " Heading Device: " + dat.SOURCE_TCP.HeadDevice.toFixed(2) + "°"
}
if(dat.SOURCE_TCP.HeadMotion !== 0) {
document.getElementById("compassTCPMot").innerHTML = "Heading Motion: " + dat.SOURCE_TCP.HeadMotion.toFixed(2) + "°"
}
if(dat.SOURCE_TCP.HAcc !== 0 && dat.SOURCE_TCP.VAcc !== 0){
addTCPAccuracy(dat.SOURCE_TCP.HAcc, dat.SOURCE_TCP.VAcc)
document.getElementById("tcpHAcc").innerHTML = "Phone HAcc: " + dat.SOURCE_TCP.HAcc.toFixed(2) + " m"
document.getElementById("tcpVAcc").innerHTML = "Phone VAcc: " + dat.SOURCE_TCP.VAcc.toFixed(2) + " m"
}
}
// If message comes from Serial source call functions to add serial data != 0 to the charts
if ('SOURCE_SERIAL' in dat) {
setIndicatorsSer(dat.SOURCE_SERIAL)
if(dat.SOURCE_SERIAL.Orientation[0] !== 0 && dat.SOURCE_SERIAL.Orientation[2] !== 0){
renderSerial(dat.SOURCE_SERIAL.Orientation[0]*Math.PI/180,-dat.SOURCE_SERIAL.Orientation[2]*Math.PI/180,dat.SOURCE_SERIAL.Orientation[1]*Math.PI/180)
}
if(dat.SOURCE_SERIAL.Position[1] !== 0 && dat.SOURCE_SERIAL.Position[0] !== 0){
document.getElementById("SERIALlong").innerHTML = "Ublox long: " + dat.SOURCE_SERIAL.Position[1]
document.getElementById("SERIALlat").innerHTML = "Ublox lat: " + dat.SOURCE_SERIAL.Position[0]
updateMapSERIAL(dat.SOURCE_SERIAL.Position[1], dat.SOURCE_SERIAL.Position[0])
map.panTo([dat.SOURCE_SERIAL.Position[1], dat.SOURCE_SERIAL.Position[0]])
tempSERIALCoords = dat.SOURCE_SERIAL
if (tempTCPCoords !== null) {
// calculate distance between coordinates from phone and m8u
let tempDist = distVincenty(tempTCPCoords.Position, tempSERIALCoords.Position)
// calculate time difference between currently cached measurements from phone and m8u
let timeDiff = Date.parse(tempTCPCoords.Timestamp) - Date.parse(tempSERIALCoords.Timestamp)
// calculate error estimation: traveled distance in time difference
let distError = Math.abs(dat.SOURCE_SERIAL.Speed / 1000 * timeDiff)
// remove invalid negative values
let distClean = Math.max(tempDist - distError, 0) // set to zero if error greater than diff as this is only an estimation
addDistanceToBarChart(distClean)
document.getElementById("distance").innerHTML = "Distance 2D: " + distClean.toFixed(3) + " m"
if(distClean <= dat.SOURCE_SERIAL.HAcc){
document.getElementById("greenlamp").style.backgroundColor = 'rgba(0, 230, 64, 1)'
document.getElementById("yellow").style.backgroundColor = 'rgb(157,117,25)'
document.getElementById("redlamp").style.backgroundColor = 'rgba(139, 0, 0, 1)'
}
else if(distClean <= tempTCPCoords.HAcc){
document.getElementById("greenlamp").style.backgroundColor = 'rgba(0, 100, 0, 1)'
document.getElementById("yellow").style.backgroundColor = 'rgb(255,199,66)'
document.getElementById("redlamp").style.backgroundColor = 'rgba(139, 0, 0, 1)'
}
else{
document.getElementById("greenlamp").style.backgroundColor = 'rgba(0, 100, 0, 1)'
document.getElementById("yellow").style.backgroundColor = 'rgb(157,117,25)'
document.getElementById("redlamp").style.backgroundColor = 'rgb(255,14,14)'
}
}
tempTCPCoords = null
}
if(dat.SOURCE_SERIAL.Speed !== 0){
addSpeedSerial(dat.SOURCE_SERIAL.Speed);
}
if(dat.SOURCE_SERIAL.HeadDevice !== 0){
document.getElementById("compassSERIAL").innerHTML = "Heading Device: " + dat.SOURCE_SERIAL.HeadDevice.toFixed(2) + "°"
}
if(dat.SOURCE_SERIAL.HeadMotion !== 0) {
document.getElementById("compassSERIALMot").innerHTML = "Heading Motion: " + dat.SOURCE_SERIAL.HeadMotion.toFixed(2) + "°"
}
if(dat.SOURCE_SERIAL.HAcc !== 0 && dat.SOURCE_SERIAL.VAcc !== 0){
addSerialAccuracy(dat.SOURCE_SERIAL.HAcc, dat.SOURCE_SERIAL.VAcc)
document.getElementById("serialHAcc").innerHTML = "Ublox HAcc: " + dat.SOURCE_SERIAL.HAcc.toFixed(3) + " m"
document.getElementById("serialVAcc").innerHTML = "Ublox VAcc: " + dat.SOURCE_SERIAL.VAcc.toFixed(2) + " m"
}
}
}
const wsOnOpenF = function (evt) {
print2("OPEN");
}
const wsOnErrorF = function(evt) {
console.log(evt)
print2("ERROR: " + evt);
}
ws = new WebSocket("ws://localhost:3011/ws");
ws.onopen = wsOnOpenF
ws.onclose = wsOnCloseF
ws.onmessage = wsOnMessageF
ws.onerror = wsOnErrorF
const print2 = function(message) {
let d = document.createElement("p");
d.innerText = message;
oldNode = output.firstChild
output.replaceChild(d, oldNode)
};
// On open send HTTP request corresponding to the chosen Source/s
document.getElementById("open").onclick = function(evt) {
if (ws && ws.OPEN) {
print("Websocket already open")
return false;
if(checkBoxSmartphone.checked && checkBoxUblox.checked){
fetch('http://localhost:3011/trackings?serial=true&tcp=true', { method: 'POST', body: 'some test data'})
.then(results => results.json())
.then(console.log);
}
ws = new WebSocket("ws://localhost:3011/ws");
ws.onopen = function(evt) {
print("OPEN");
else if(!checkBoxSmartphone.checked && checkBoxUblox.checked){
fetch('http://localhost:3011/trackings?serial=true&tcp=false', { method: 'POST', body: 'some test data'})
.then(results => results.json())
.then(console.log);
}
ws.onclose = function(evt) {
ws = null;
print2("CLOSE");
else if(checkBoxSmartphone.checked && !checkBoxUblox.checked){
fetch('http://localhost:3011/trackings?serial=false&tcp=true', { method: 'POST', body: 'some test data'})
.then(results => results.json())
.then(console.log);
}
else if(!checkBoxSmartphone.checked && !checkBoxUblox.checked){
fetch('http://localhost:3011/trackings?serial=false&tcp=false', { method: 'POST', body: 'some test data'})
.then(results => results.json())
.then(console.log);
}
ws.onmessage = function(evt) {
//print2("RESPONSE: " + evt.data);
// let dat = JSON.parse(evt.data)["bmi26x gyroscope"]
// let dat = JSON.parse(evt.data)["lsm6dsm gyroscope"]
//let dat = JSON.parse(evt.data)["lsm6ds3c gyroscope"]
let dat = JSON.parse(evt.data)
dataSmartphone.push(dat)
//console.log(evt.data)
console.log("JSON geparsed onmessage", dat)
//console.log(dat.SOURCE_TCP.Orientation)
document.getElementById("gyroscopeTCP").style.transform = `rotateX(${dat.SOURCE_TCP.Orientation[0]}deg) rotateY(${dat.SOURCE_TCP.Orientation[1]}deg) rotateZ(${dat.SOURCE_TCP.Orientation[2]}deg)`
document.getElementById("gyroscopeSERIAL").style.transform = `rotateX(${dat.SOURCE_SERIAL.Orientation[0]}deg) rotateY(${dat.SOURCE_SERIAL.Orientation[1]}deg) rotateZ(${dat.SOURCE_SERIAL.Orientation[2]}deg)`
document.getElementById("TCPlong").innerHTML = "Smartphone long: " + dat.SOURCE_TCP.Position[1]
document.getElementById("TCPlat").innerHTML = "Smartphone lat: " + dat.SOURCE_TCP.Position[0]
document.getElementById("SERIALlong").innerHTML = "Ublox long: " + dat.SOURCE_SERIAL.Position[1]
document.getElementById("SERIALlat").innerHTML = "Ublox lat: " + dat.SOURCE_SERIAL.Position[0]
document.getElementById("tracking state").innerHTML = "Tracking state: LIVE"
document.getElementById("diffLong").innerHTML = "Differenz long: " + Math.abs(dat.SOURCE_TCP.Position[1] - dat.SOURCE_SERIAL.Position[1])
document.getElementById("diffLat").innerHTML = "Differenz lat: " + Math.abs(dat.SOURCE_TCP.Position[0] - dat.SOURCE_SERIAL.Position[0])
/*
console.log(dat)
orientation[0] += dat[0] * multiplier
orientation[1] += dat[1] * multiplier
orientation[2] += dat[2] * multiplier
// dataset.push(orientation[0])
// while (dataset.length >= 50) {
// dataset.shift();
// }
// addData(orientation[0] / multiplier)
*/
addData(dat.SOURCE_TCP.Orientation[0])
updateMapTCP(dat.SOURCE_TCP.Position[1], dat.SOURCE_TCP.Position[0])
updateMapSERIAL(dat.SOURCE_SERIAL.Position[1], dat.SOURCE_SERIAL.Position[0])
//updateMap(dat.SOURCE_SERIAL.Position[1], dat.SOURCE_SERIAL.Position[0])
checkBoxSmartphone.disabled = true;
checkBoxUblox.disabled = true;
// addData(dat[0])
//document.getElementById("gyroscope").style.transform = `rotateX(${-orientation[0]}deg) rotateY(${orientation[1]}deg) rotateZ(${-orientation[2]}deg) translateZ(50px)`
}
ws.onerror = function(evt) {
print("ERROR: " + evt.data);
}
return false;
};
document.getElementById("send").onclick = function(evt) {
if (!ws) {
return false;
}
print("SEND: " + input.value);
ws.send(input.value);
return false;
};
document.getElementById("close").onclick = function(evt) {
@ -89,4 +179,85 @@ window.addEventListener("load", function(evt) {
ws.close();
return false;
};
});
//------------------------Buttons------------------------------
/*
Provides every Button with the corresponding HTTP request if the websocket is open.
*/
document.getElementById("messungstarten").onclick = function(evt) {
if (ws) {
fetch('http://localhost:3011/trackings/', { method: 'PATCH', body: 'some data'})
.then(results => results.json())
.then(console.log);
document.getElementById("tracking state").innerHTML = "Tracking state: RECORD"
}
return false;
};
document.getElementById("messungbeenden").onclick = function(evt) {
if (ws) {
fetch('http://localhost:3011/trackings/', { method: 'PUT', body: 'some data'})
.then(results => results.json())
.then(console.log);
document.getElementById("tracking state").innerHTML = "Tracking state: LIVE"
}
return false;
};
document.getElementById("allesbeenden").onclick = function(evt) {
if (ws) {
fetch('http://localhost:3011/trackings/', { method: 'DELETE', body: 'some data'})
.then(results => results.json())
.then(console.log);
checkBoxSmartphone.disabled = false;
checkBoxUblox.disabled = false;
document.getElementById("tracking state").innerHTML = "Tracking state: CLOSED"
}
return false;
};
var trackings = null;
document.getElementById("messungladen").onclick = function(evt) {
fetch('http://localhost:3011/trackings/', { method: 'GET'}).then(results => {
return results.json()
}).then(r => {
console.log(r)
if (!'data' in r) {
return
}
trackings = r.data
let sel = document.getElementById("meas")
r.data.sort((a,b) => new Date(b.TimeCreated).getTime() - new Date(a.TimeCreated).getTime());
r.data.forEach(tracking => {
console.log(tracking)
let option = document.createElement("option");
option.text = tracking.TimeCreated + " Size: " + tracking.Size
sel.add(option)
})
sel.disabled = false
document.getElementById("replaystarten").disabled = false
})
};
document.getElementById("replaystarten").onclick = function(evt) {
emptyTCP.features[0].geometry.coordinates = []
emptySERIAL.features[0].geometry.coordinates = []
let sel = document.getElementById("meas")
console.log(trackings[sel.selectedIndex].UUID)
fetch(`http://localhost:3011/trackings/${trackings[sel.selectedIndex].UUID}?replay=true`, { method: 'GET'}).then(results => {
return results.json()
}).then(r => {
console.log(r.data.Data)
})
document.getElementById("tracking state").innerHTML = "Tracking state: REPLAY"
}
document.getElementById("fullReplay").onclick = function(evt) {
window.open('http://localhost:3011/tracking')
}
});

476
static/style.css Normal file
View File

@ -0,0 +1,476 @@
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
.legend {
background-color: #fff;
border-radius: 3px;
bottom: 30px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
padding: 10px;
position: absolute;
right: 10px;
z-index: 1;
}
.legend h4 {
margin: 0 0 10px;
}
.legend div span {
border-radius: 50%;
display: inline-block;
height: 10px;
margin-right: 5px;
width: 10px;
}
body{margin:0; padding:0; font-size:13px; font-family:Georgia, "Times New Roman", Times, serif; color:#919191; background-color:#232323;}
.float-child {
width: 50%;
float: left;
}
.sceneMap {
width: 200px;
height: 50vh;
border: 0px solid #CCC;
/*margin: 20px;*/
perspective: 400px;
}
.sceneMap, #map {
width: 100%;
}
.right-col {
display: flex;
flex-flow: wrap;
width: 34%;
max-width: 460px;
}
.left-col {
display: flex;
flex-flow: wrap;
width: calc(66% - 20px);
padding-right: 20px;
}
.compass-container {
display: flex;
flex-flow: wrap;
}
.outer {
position: relative;
width: 300px;
height: 200px;
margin: 20px;
margin-bottom: 50px;
}
canvas {
position: absolute;
}
.speedMin {
position: absolute;
left: 0%;
transform: translate(-50%, 0);
font-size: 30px;
bottom: 0;
margin-bottom: -50px;
}
.speed14 {
position: absolute;
left: 8%;
transform: translate(-50%, 0);
font-size: 20px;
bottom: 0;
margin-bottom: 90px;
}
.speedMed {
position: absolute;
left: 50%;
transform: translate(-50%, 0);
font-size: 20px;
bottom: 0;
margin-bottom: 140px;
}
.speed34 {
position: absolute;
left: 92%;
transform: translate(-50%, 0);
font-size: 20px;
bottom: 0;
margin-bottom: 90px;
}
.speedMax {
position: absolute;
Left: 100%;
transform: translate(-50%, 0);
font-size: 30px;
bottom: 0;
margin-bottom: -50px;
}
#compass {
position: relative;
width: 200px;
height: 200px;
/*margin: 20px;*/
}
#compass1 {
position: relative;
width: 200px;
height: 200px;
/*margin: 20px;*/
}
.accuracy-container {
display: flex;
flex-flow: wrap;
margin: 20px 0;
}
.accuracy-values {
margin: 28px 20px 0 20px;
}
.led-container {
display: flex;
flex-flow: column;
margin: 20px 0;
}
#bezel {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
border-radius: 50%;
}
#bezelTCP {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
border-radius: 50%;
}
#axis {
position: absolute;
left: 88px;
top: 88px;
width: 10px;
height: 10px;
background: #ffff;
border: 6px solid #666;
border-radius: 50%;
}
#axisTCP {
position: absolute;
left: 88px;
top: 88px;
width: 10px;
height: 10px;
background: #ffff;
border: 6px solid #666;
border-radius: 50%;
}
#needle {
position: absolute;
left: 96px;
width: 4px;
height: 160px;
top: 16px;
}
#needleTCP {
position: absolute;
left: 96px;
width: 4px;
height: 160px;
top: 16px;
}
#needle:after {
content: '';
position: absolute;
top: 0;
left: -1px;
width: 0;
height: 0;
border: 4px solid transparent;
border-bottom: 80px solid #FF3600;
}
#needle:before {
content: '';
position: absolute;
top: 80px;
left: -1px;
width: 0;
height: 0;
border: 4px solid transparent;
border-top: 80px solid #666;
}
#needleTCP:after {
content: '';
position: absolute;
top: 0;
left: -1px;
width: 0;
height: 0;
border: 4px solid transparent;
border-bottom: 80px solid #FF3600;
}
#needleTCP:before {
content: '';
position: absolute;
top: 80px;
left: -1px;
width: 0;
height: 0;
border: 4px solid transparent;
border-top: 80px solid #666;
}
#N {
position: absolute;
top: 0px;
left: 88px;
color: #FF3600;
}
#E {
position: absolute;
right: 5px;
top: 80px;
}
#S {
position: absolute;
bottom: 5px;
left: 88px;
}
#W {
position: absolute;
left: 5px;
top: 80px;
}
#NTCP {
position: absolute;
top: 0px;
left: 88px;
color: #FF3600;
}
#ETCP {
position: absolute;
right: 5px;
top: 80px;
}
#STCP {
position: absolute;
bottom: 5px;
left: 88px;
}
#WTCP {
position: absolute;
left: 5px;
top: 80px;
}
.dir {
font-family: arial, sans-serif;
color: #999;
font-size: 20px;
padding: 5px;
}
.quad {
display: block;
width: 2px;
background: #ddd;
height: 130px;
position: absolute;
top: 30px;
left: 98px;
}
#NWSE {
transform: rotate(45deg);
}
#NESW {
transform: rotate(-45deg);
}
#WE {
transform: rotate(90deg);
}
#NWSETCP {
transform: rotate(45deg);
}
#NESWTCP {
transform: rotate(-45deg);
}
#WETCP {
transform: rotate(90deg);
}
@-webkit-keyframes spin {
100% {
-webkit-transform: rotate(360deg);
}
}
.scene {
width: 200px;
height: 200px;
border: 0px solid #CCC;
margin: 70px;
perspective: 400px;
}
.lamp {
width: 50px;
height: 50px;
background-color: gray;
border-radius: 50%;
}
.lamps {
display: flex;
flex-flow: row;
}
.lampinner{
width: 40px;
height: 40px;
position: relative;
top: 5px;
left: 5px;
border-radius: 50%;
}
.cube {
width: 200px;
height: 200px;
position: relative;
transform-style: preserve-3d;
/*transform: translateZ(-100px);*/
transition: transform 25ms;
}
.cube.show-front { transform:translateZ(-100px) rotateY( 0deg); }
.cube.show-right { transform:translateZ(-100px) rotateY( -90deg); }
.cube.show-back { transform:translateZ(-100px) rotateY(-180deg); }
.cube.show-left { transform:translateZ(-100px) rotateY( 90deg); }
.cube.show-top { transform:translateZ(-100px) rotateX( -90deg); }
.cube.show-bottom { transform:translateZ(-100px) rotateX( 90deg); }
.cube__face {
position: absolute;
width: 200px;
height: 200px;
border: 1px solid white;
line-height: 200px;
font-size: 40px;
/*font-weight: bold;*/
color: white;
text-align: center;
}
.cube__face--front { background: hsla( 0, 100%, 50%, 0.7); }
.cube__face--right { background: hsla( 60, 100%, 50%, 0.7); }
.cube__face--back { background: hsla(120, 100%, 50%, 0.7); }
.cube__face--left { background: hsla(180, 100%, 50%, 0.7); }
.cube__face--top { background: hsla(240, 100%, 50%, 0.7); }
.cube__face--bottom { background: hsla(300, 100%, 50%, 0.7); }
.cube__face--front { transform: rotateY( 0deg) translateZ(100px) translateY(50px); height: 100px; line-height: 100px; }
.cube__face--right { transform: rotateY( 90deg) translateZ(100px) translateY(50px); height: 100px; line-height: 100px; }
.cube__face--back { transform: rotateY(180deg) translateZ(100px) translateY(50px); height: 100px; line-height: 100px; }
.cube__face--left { transform: rotateY(-90deg) translateZ(100px) translateY(50px); height: 100px; line-height: 100px; }
.cube__face--top { transform: rotateX( 90deg) translateZ(50px); }
.cube__face--bottom { transform: rotateX(-90deg) translateZ(50px); }
label { margin-right: 10px; }
.slidecontainer {
width: 100%;
overflow: visible;
padding: 10px 0px;
}
#manCalContainer {
display: none;
}
/*.slider {*/
/* -webkit-appearance: none;*/
/* width: 100%;*/
/* height: 25px;*/
/* background: #d3d3d3;*/
/* outline: none;*/
/* opacity: 0.7;*/
/* -webkit-transition: .2s;*/
/* transition: opacity .2s;*/
/*}*/
.slider:hover {
opacity: 1;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
background: #4CAF50;
cursor: pointer;
border-radius: 50%;
border: none;
}
.slider::-moz-range-thumb {
width: 20px;
height: 20px;
background: #4CAF50;
cursor: pointer;
border-radius: 50%;
border: none;
}
.slider {
-webkit-appearance: none;
width: 100%;
height: 8px;
border-radius: 5px;
background: #d3d3d3;
outline: none;
opacity: 0.7;
-webkit-transition: opacity .15s ease-in-out;
transition: opacity .15s ease-in-out;
}
#viewport {
width: 100%;
height: 360px;
}
.compass-digital {
width: 400px;
}

View File

@ -7,7 +7,6 @@ import (
"github.com/dgraph-io/badger/v2"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
"github.com/tidwall/pretty"
"os"
"path/filepath"
"time"
@ -19,6 +18,7 @@ type badgerStore struct {
sensordatDb *badger.DB
}
// Returns a badgerDB K/V Store instance. Opens database if exist or creates a new one in working directory
func NewRepository(c *core.Configuration) *badgerStore {
dir, _ := os.Getwd()
logrus.Debug(dir)
@ -69,11 +69,11 @@ func (r *badgerStore) Save(tr core.Tracking) error {
}
err = r.sensordatDb.Update(func(txn *badger.Txn) error {
for _, v := range tr.Data {
k := createRecordKey(tr.UUID, v.Source(), v.Servertime)
logrus.Trace(v, " len key ->", len(k))
k := createRecordKey(tr.UUID, v.Source(), v.Timestamp)
//logrus.Trace(v, " len key ->", len(k))
j, err2 := json.Marshal(v)
logrus.Traceln("save record k/v:\n", tr.UUID.String(), v.Servertime.Format(time.RFC3339Nano))
logrus.Traceln(string(pretty.Pretty(j)))
logrus.Traceln("save record k/v:\n", tr.UUID.String(), v.Timestamp.Format(time.RFC3339Nano))
//logrus.Traceln(string(pretty.Pretty(j)))
if err2 != nil {
return err2
}
@ -105,6 +105,7 @@ func (r *badgerStore) Save(tr core.Tracking) error {
return nil
}
// Retrieves all existing Trackings. Only Metadata. If you want actual data of tracking, load a specific one with Load(uuid)
func (r *badgerStore) LoadAll() ([]core.TrackingMetadata, error) {
var result []core.TrackingMetadata
err := r.trackingsDb.View(func(txn *badger.Txn) error {
@ -133,6 +134,7 @@ func (r *badgerStore) LoadAll() ([]core.TrackingMetadata, error) {
return result, nil
}
// Retrieves all data of a tracking from disk
func (r *badgerStore) Load(id uuid.UUID) (*core.Tracking, error) {
logrus.Debugln("try to load from db...", id)
if ok := r.isDbClosed(); ok {
@ -171,12 +173,12 @@ func (r *badgerStore) Load(id uuid.UUID) (*core.Tracking, error) {
item := it.Item()
_, source, recTime := unmarshalDataKey(item.Key())
el := core.SensorData{}
el.Servertime = recTime
el.Timestamp = recTime
el.SetSource(source)
err2 := item.Value(func(val []byte) error {
logrus.Traceln(string(val))
//logrus.Traceln(string(val))
err3 := json.Unmarshal(val, &el)
logrus.Traceln(err3, el)
//logrus.Traceln(err3, el)
return err3
})
if err2 != nil {
@ -191,11 +193,10 @@ func (r *badgerStore) Load(id uuid.UUID) (*core.Tracking, error) {
logrus.Error(err)
}
// implement retrieval of raw data only if needed
return t, nil
}
// helper function to create []byte key from sensordata element
func createRecordKey(uid uuid.UUID, source core.SourceId, timestamp time.Time) []byte {
prefix := []byte(uid.String())
var i string
@ -212,17 +213,17 @@ func createRecordKey(uid uuid.UUID, source core.SourceId, timestamp time.Time) [
logrus.Errorln("unable to create key", err)
}
logrus.Traceln("save as:", string(prefix), string(middle), string(suffix))
//binary.BigEndian.PutUint64(suffix, uint64(timestamp.UnixNano()))
ret := append(prefix, middle...)
return append(ret, suffix...)
}
// helper function to split []byte key back to actual data
func unmarshalDataKey(key []byte) (uuid.UUID, core.SourceId, time.Time) {
logrus.Trace("key len ->", len(key))
prefix := string(key[:36])
suffix := string(key[37:])
middle := string(key[36:37])
logrus.Traceln("load as:", prefix, middle, suffix)
var source core.SourceId
switch middle {
case "1":
@ -238,8 +239,6 @@ func unmarshalDataKey(key []byte) (uuid.UUID, core.SourceId, time.Time) {
if err != nil {
logrus.Errorln("corrupted key", err)
}
logrus.Traceln(uid, timestamp)
//timestamp := time.Unix(0, int64(binary.BigEndian.Uint64(suffix)))
return uid, source, timestamp
}

View File

@ -3,178 +3,185 @@
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.bundle.js"></script>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no"/>
<script src="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" rel="stylesheet" />
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r124/three.js"></script>
<script src="static/scripts/distanceCalc.js"></script>
<script src="static/scripts/websocket.js"></script>
<style>
.legend {
background-color: #fff;
border-radius: 3px;
bottom: 30px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
padding: 10px;
position: absolute;
right: 10px;
z-index: 1;
}
.legend h4 {
margin: 0 0 10px;
}
.legend div span {
border-radius: 50%;
display: inline-block;
height: 10px;
margin-right: 5px;
width: 10px;
}
body{margin:0; padding:0; font-size:13px; font-family:Georgia, "Times New Roman", Times, serif; color:#919191; background-color:#232323;}
.float-child {
width: 45%;
float: left;
padding: 20px;
}
.scene {
width: 200px;
height: 200px;
border: 0px solid #CCC;
margin: 75px;
perspective: 400px;
}
.cube {
width: 200px;
height: 200px;
position: relative;
transform-style: preserve-3d;
/*transform: translateZ(-100px);*/
/*transition: transform 25ms;*/
}
.cube.show-front { transform:/*translateZ(-100px)*/ rotateY( 0deg); }
.cube.show-right { transform:/*translateZ(-100px)*/ rotateY( -90deg); }
.cube.show-back { transform:/*translateZ(-100px)*/ rotateY(-180deg); }
.cube.show-left { transform:/*translateZ(-100px)*/ rotateY( 90deg); }
.cube.show-top { transform:/*translateZ(-100px)*/ rotateX( -90deg); }
.cube.show-bottom { transform:/*translateZ(-100px)*/ rotateX( 90deg); }
.cube__face {
position: absolute;
width: 200px;
height: 200px;
border: 1px solid white;
line-height: 200px;
font-size: 40px;
font-weight: bold;
color: white;
text-align: center;
}
.cube__face--front { background: hsla( 0, 100%, 50%, 0.7); }
.cube__face--right { background: hsla( 60, 100%, 50%, 0.7); }
.cube__face--back { background: hsla(120, 100%, 50%, 0.7); }
.cube__face--left { background: hsla(180, 100%, 50%, 0.7); }
.cube__face--top { background: hsla(240, 100%, 50%, 0.7); }
.cube__face--bottom { background: hsla(300, 100%, 50%, 0.7); }
.cube__face--front { transform: rotateY( 0deg) translateZ(100px); }
.cube__face--right { transform: rotateY( 90deg) translateZ(100px); }
.cube__face--back { transform: rotateY(180deg) translateZ(100px); }
.cube__face--left { transform: rotateY(-90deg) translateZ(100px); }
.cube__face--top { transform: rotateX( 90deg) translateZ(100px); }
.cube__face--bottom { transform: rotateX(-90deg) translateZ(100px); }
label { margin-right: 10px; }
</style>
<link rel="stylesheet" type="text/css" href="static/indicators/css/flightindicators.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="static/indicators/js/jquery.flightindicators.js"></script>
<link rel="stylesheet" href="static/style.css">
</head>
<body>
<table style="font-size: small">
<tr>
<td valign="top" width="50%">
<p>Click "Open" to create a connection to the server.
</p>
<form>
<button id="open">Verbinden</button>
<button id="close">Trennen</button>
<p style="display: none" >
<input id="input" type="text" value="Hello world!">
<button id="send">Send</button>
</p>
</form>
<button id="messung starten">Messung starten</button>
<button id="messung beenden">Messung beenden</button>
<button id="messung speichern">Messung speichern</button>
<button id="messung verwerfen">Messung verwerfen</button>
<button id="messung laden">Messung laden</button></li>
</td>
<td valign="top" width="100%">
<div id="output"></div>
<div class="controls">
<label><input type="checkbox" id="checkbox1" value="smartphone"> TCP</label><br>
<label><input type="checkbox" id="checkbox2" value="ublox"> SERIAL</label><br>
<button id="open">Livetracking starten</button>
<button id="close" style="display: none">Trennen</button>
<button id="allesbeenden" style="margin-right: 16px;">Pipeline stoppen</button>
<button id="messungstarten">Aufnahme starten</button>
<button id="messungbeenden" style="margin-right: 16px;">Aufnahme beenden</button>
<button id="messungladen" style="margin-right: 16px;">Aufnahmen laden</button>
<label>Aufnahmen:
<select name="meas" id="meas" disabled>
</select>
</label>
<button id="replaystarten" style="margin-right: 16px;" disabled>Wiedergabe starten</button>
<button id="fullReplay">TRACKINGANALYSE</button>
</div>
<br>
<label id="tracking state" style="font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif">Tracking
state: </label><br>
<label id="TCPlong" style="font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif">Smartphone
long: </label>
<label id="TCPlat" style="font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif">Smartphone lat: </label>
<label id="SERIALlong" style="font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif">Ublox long: </label>
<label id="SERIALlat" style="font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif">Ublox lat: </label>
</td>
<td valign="top">
<div id="output"><p>CLOSED</p></div>
</td>
</tr>
</table>
<div class="float-container">
<div class="float-child">
<label id="TCPlong" style= "font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif">Smartphone long: </label>
<label id="TCPlat" style= "font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif">Smartphone lat: </label>
<label id="SERIALlong" style= "font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif">Ublox long: </label>
<label id="SERIALlat" style= "font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif">Ublox lat: </label>
<label id="diffLong" style= "font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif">Differenz long: </label>
<label id="diffLat" style= "font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif">Differenz lat: </label>
<div class="scene" style='width: 500px; height: 500px;'>
<div id="map" style='width: 500px; height: 500px;'></div>
<div class="float-child left-col">
<div class="sceneMap">
<div id="map"></div>
<div id="state-legend" class="legend">
<h4>Legende</h4>
<div><span style="background-color: yellow"></span>Ublox</div>
<div><span style="background-color: blue"></span>Smartphone</div>
</div>
</div>
</div>
<div class="float-child">
<div class="scene">
<p style="font-size: large">Smartphone</p>
<div id="gyroscopeTCP" class="cube">
<div class="cube__face cube__face--front">front</div>
<div class="cube__face cube__face--back">back</div>
<div class="cube__face cube__face--right">right</div>
<div class="cube__face cube__face--left">left</div>
<div class="cube__face cube__face--top">top</div>
<div class="cube__face cube__face--bottom">bottom</div>
<div><span style="background-color: rgba(214, 69, 65, 1)"></span>Ublox</div>
<div><span style="background-color: rgba(30, 139, 195, 1)"></span>Smartphone</div>
</div>
</div>
<div class="scene">
<p style="font-size: large">Ublox</p>
<div id="gyroscopeSERIAL" class="cube">
<div class="cube__face cube__face--front">front</div>
<div class="cube__face cube__face--back">back</div>
<div class="cube__face cube__face--right">right</div>
<div class="cube__face cube__face--left">left</div>
<div class="cube__face cube__face--top">top</div>
<div class="cube__face cube__face--bottom">bottom</div>
<div class="outer">
<div>
<label id="speedTCP"
style="color: rgba(30, 139, 195, 1); font: 15px 'Helvetica Neue', Arial, Helvetica, sans-serif">Speed
Smartphone (km/h): </label><br>
<label id="speedSERIAL"
style="color: rgba(214, 69, 65, 1); font: 15px 'Helvetica Neue', Arial, Helvetica, sans-serif">Speed
Ublox
(km/h): </label><br>
</div>
<div>
<canvas id="speedometer" width="320" height="230"></canvas>
<canvas id="speedometerSpeeds" width="320" height="230"></canvas>
<p class="speedMin">0</p>
<p class="speed14">62</p>
<p class="speedMed">125</p>
<p class="speed34">188</p>
<p class="speedMax">250</p>
</div>
</div>
<div class="led-container">
<div class="lamps">
<div class="lamp">
<div class="lampinner" id="greenlamp" style="background-color: darkgreen"></div>
</div>
<div class="lamp">
<div class="lampinner" id="yellow" style="background-color: #9D7519"></div>
</div>
<div class="lamp">
<div class="lampinner" id="redlamp" style="background-color: #8b0000"></div>
</div>
<label style="margin:auto; margin-left: 10px">Smartphone Genauigkeit</label>
</div>
</div>
<div class="accuracy-container">
<div style="width: 500px; height: 150px;">
<canvas id="accuracy" width="500" height="150"></canvas>
</div>
<div class="accuracy-values">
<div class="container" style="width: 150px; height: 70px">
<label id="serialHAcc"
style="color: rgba(214, 69, 65, 1); font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif">Ublox
HAcc: </label><br>
<label id="tcpHAcc"
style="color: rgba(30, 139, 195, 1); font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif">Phone
HAcc: </label><br>
<label id="distance"
style="color: rgba(30, 139, 0, 1); font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif">
Distance: </label>
</div>
<div class="container" style="width: 150px; height: 50px">
<label id="serialVAcc"
style="color: rgba(214, 69, 65, 1); font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif">Ublox
VAcc: </label><br>
<label id="tcpVAcc"
style="color: rgba(30, 139, 195, 1); font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif">Phone
VAcc: </label><br>
</div>
</div>
</div>
</div>
<div class="float-child right-col">
<div class="compass-container">
<label id="airspeedLabel"
style="display:inline-block; width: 200px; margin-right: 0; color: rgba(214, 69, 65, 1); font: 15px 'Helvetica Neue', Arial, Helvetica, sans-serif">Ref. Speed:</label>
<label id="altitudeLabel"
style="color: rgba(214, 69, 65, 1); font: 15px 'Helvetica Neue', Arial, Helvetica, sans-serif">Altitude: </label><br>
<span id="airspeed"></span>
<span id="altimeter"></span>
<div class="compass-digital">
<label id="compassSERIAL"
style="display:inline-block; width: 200px; margin-right: 0; color: rgba(214, 69, 65, 1); font: 15px 'Helvetica Neue', Arial, Helvetica, sans-serif"></label>
<label id="compassTCP"
style="color: rgba(30, 139, 195, 1); font: 15px 'Helvetica Neue', Arial, Helvetica, sans-serif"></label><br>
<label id="compassSERIALMot"
style="display:inline-block; width: 200px; margin-right: 0; color: rgba(214, 69, 65, 1); font: 15px 'Helvetica Neue', Arial, Helvetica, sans-serif"></label>
<label id="compassTCPMot"
style="color: rgba(30, 139, 195, 1); font: 15px 'Helvetica Neue', Arial, Helvetica, sans-serif"></label><br>
</div>
</div>
<span id="headingSer"></span>
<span id="headingTcp"></span>
<span id="attitudeSer"></span>
<span id="attitudeTcp"></span>
<div>
<div id="viewport">
</div>
<label id="quaternionOffset"
style="color: grey; font: 15px 'Helvetica Neue', Arial, Helvetica, sans-serif">Lage Abweichung: </label><br>
<div class="slidecontainer">
<button id="calibrate">Smartphone Ausrichtung kalibrieren</button>
<button id="deleteCalibration">Kalibrierung zurücksetzen</button>
<button id="manualCalibration">Manuelle Kalibrierung</button>
<div id="manCalContainer">
<p><br>Manuelle Kalibrierung</p>
<label>Pitch<input type="range" min="-180" max="180" value="0" class="slider" id="pitchRange"
style="margin: 10px 0px"></label>
<label>Yaw<input type="range" min="-180" max="180" value="0" class="slider" id="yawRange"
style="margin: 10px 0px"></label>
<label>Roll<input type="range" min="-180" max="180" value="0" class="slider" id="rollRange"
style="margin: 10px 0px"></label>
</div>
</div>
<script src="static/scripts/indicators.js"></script>
</div>
</div>
</div>
<script src="static/scripts/map.js"></script>
<script src="static/scripts/speedometer.js"></script>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="static/scripts/map.js"></script>
<div style="width: 600px; height: 400px;">
<canvas id="myChart" width="400" height="200"></canvas>
</div>
<script src="static/scripts/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.4/Chart.min.js"></script>
<script src="static/scripts/accuracy.js"></script>
</body>
</html>
</html>

47
templates/replayFull.html Normal file
View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.bundle.js"></script>
<script src="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" rel="stylesheet" />
<script src="static/scripts/distanceCalc.js"></script>
<script src="static/scripts/refull.js"></script>
<link rel="stylesheet" href="static/replayStyle.css">
</head>
<body>
<div class="controls">
<br>
<button id="messungladen" style="margin-right: 16px;">Aufnahmen laden</button>
<label>Aufnahmen:
<select name="meas" id="meas" disabled>
</select>
</label>
<button id="replaystarten" style="margin-right: 16px;" disabled>Öffnen</button>
</div>
<div class="sceneMap">
<div id="map" style='width: 1000px; height: 500px;'></div>
<div id="state-legend" class="legend">
<h4>Legende</h4>
<div><span style="background-color: rgba(214, 69, 65, 1)"></span>Ublox</div>
<div><span style="background-color: rgba(30, 139, 195, 1)"></span>Smartphone</div>
</div>
</div>
<div style="width: 1000px; height: 300px;">
<canvas id="speedChart" width="1000" height="300"></canvas>
</div>
<div style="width: 1000px; height: 300px;">
<canvas id="accChart" width="1000" height="300"></canvas>
</div>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="static/scripts/mapFull.js"></script>
<script src="static/scripts/speedChart.js"></script>
<script src="static/scripts/accChart.js"></script>
</body>
</html>

View File

@ -1,3 +1,6 @@
// credits to https://github.com/daedaleanai/ublox for basic ubx parsing idea & code
// missing ubx messages added and code modified by Timo Volkmann
// Package ublox provides methods to encode and decode u-Blox 8 / M8 NMEA and UBX messages
// as documented in
// UBX-13003221 - R20 u-blox 8 / u-blox M8 Receiver description Including protocol specification

View File

@ -4,12 +4,6 @@ type Message interface {
ClassID() uint16
}
//type UbxMessage interface {
// Timestamp() (time.Time, error)
// Position() ([3]float64, error)
// Orientation() ([3]float64, error)
//}
type RawMessage struct {
classID uint16
Data []byte

View File

@ -4,52 +4,40 @@ import (
"errors"
"git.timovolkmann.de/gyrogpsc/core"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/template/html"
"github.com/gofiber/websocket/v2"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
)
func CreateServer(s core.Service, sub core.Subscriber, c *core.Configuration) {
func CreateServer(s *core.TrackingService, sub core.Subscriber, c *core.Configuration) {
app := fiber.New(fiber.Config{
Views: fiberTemplateEngine(c),
})
app.Static("/static", "static")
app.Use(logger.New())
app.Static("/static", "./static")
// Application Main Page
app.Get("/", fiberHomeHandler)
app.Get("/tracking", fiberTrackingHandler)
// Websocket
app.Get("/ws", websocket.New(createFiberWebsocketHandler(sub)))
// TODO: Get all SerialPorts
// app.Get("/serialports")
// Tracking persistence controls HTTP JSON RPC API
trackings := app.Group("/trackings")
trackings.Get("/", allTrackingsHandler(s, c)) // Get all trackings Metadata
trackings.Post("/", startPipelineHandler(s, c)) // Initialize new tracking, open websocket and prepare for automatic recording. Toggle ?serial=true and ?tcp=true. Returns trackingId
trackings.Patch("/", startRecordingHandler(s, c)) // Starts recording
trackings.Put("/", stopRecordingHandler(s, c)) // Stops current recording. Returns trackingId if record was successful
trackings.Delete("/", stopAllHandler(s, c)) // Stops websocket connection, pipelines and collectors
trackings.Get("/", allTrackingsHandler(s, c)) // Get all trackings Metadata
trackings.Post("/", startPipelineHandler(s, c)) // Initialize new tracking, open websocket and prepare for automatic recording. Toggle ?serial=true and ?tcp=true. Returns trackingId
trackings.Patch("/", startRecordingHandler(s, c)) // Starts recording
trackings.Put("/", stopRecordingHandler(s, c)) // Stops current recording. Returns trackingId if record was successful
trackings.Delete("/", stopAllHandler(s, c)) // Stops websocket connection, pipelines and collectors
trackings.Get("/:trackingId", LoadTrackingHandler(s, c)) // Gets Tracking Metadata and loads sensorRecords from storage.
trackings.Delete("/:trackingId", stubhander()) // Deletes Tracking from storage
trackings.Post("/current", stubhander()) // Starts Replay.
trackings.Patch("/current", stubhander()) // Pauses Replay.
trackings.Put("/current", stubhander()) // Stops Replay.
logrus.Fatal(app.Listen(c.Webserver.Port))
}
func stubhander() fiber.Handler {
return func(ctx *fiber.Ctx) error {
return nil
}
}
func LoadTrackingHandler(s core.Service, c *core.Configuration) fiber.Handler {
func LoadTrackingHandler(s *core.TrackingService, c *core.Configuration) fiber.Handler {
return func(ctx *fiber.Ctx) error {
trackId := ctx.Params("trackingId")
uid, err := uuid.Parse(trackId)
@ -58,18 +46,30 @@ func LoadTrackingHandler(s core.Service, c *core.Configuration) fiber.Handler {
ctx.Status(404).JSON(err)
return err
}
tracking, err := s.LoadTracking(uid)
var replay bool
if ctx.Query("replay", "false") == "true" {
replay = true
} else {
replay = false
}
tracking, err := s.LoadTracking(uid, replay)
if err != nil {
logrus.Error(err)
ctx.Status(404).JSON(err)
return err
}
prepres := map[string]interface{}{}
prepres["data"] = *tracking
if err != nil {
prepres["error"] = err.Error()
st := struct {
core.TrackingMetadata
Data map[string][]core.SensorData
}{}
st.TrackingMetadata = tracking.TrackingMetadata
st.Data = make(map[string][]core.SensorData)
for _, el := range tracking.Data {
st.Data[string(el.Source())] = append(st.Data[string(el.Source())], el)
}
prepres := make(map[string]interface{})
prepres["data"] = st
err2 := ctx.JSON(prepres)
if err2 != nil {
ctx.Status(500).JSON(err2)
@ -78,18 +78,14 @@ func LoadTrackingHandler(s core.Service, c *core.Configuration) fiber.Handler {
return nil
}
}
func allTrackingsHandler(s core.Service, c *core.Configuration) fiber.Handler {
func allTrackingsHandler(s *core.TrackingService, c *core.Configuration) fiber.Handler {
return func(ctx *fiber.Ctx) error {
trackings, err := s.AllTrackings()
if err != nil {
//ctx.Status(500).JSON(err)
//return err
}
prepres := map[string]interface{}{}
prepres := make(map[string]interface{})
prepres["data"] = trackings
if err != nil {
prepres["error"] = err.Error()
}
err2 := ctx.JSON(prepres)
if err2 != nil {
@ -100,7 +96,7 @@ func allTrackingsHandler(s core.Service, c *core.Configuration) fiber.Handler {
}
}
func startPipelineHandler(s core.Service, c *core.Configuration) fiber.Handler {
func startPipelineHandler(s *core.TrackingService, c *core.Configuration) fiber.Handler {
return func(ctx *fiber.Ctx) error {
var collecs []core.CollectorType
ser := ctx.Query("serial", "true")
@ -114,12 +110,9 @@ func startPipelineHandler(s core.Service, c *core.Configuration) fiber.Handler {
if tcp == "true" {
collecs = append(collecs, core.TCP)
}
res, err := s.StartPipeline(collecs...)
if err != nil {
//ctx.Status(500).JSON(err)
//return err
}
prepres := map[string]interface{}{}
res, err := s.StartLivetracking(collecs...)
prepres := make(map[string]interface{})
prepres["tracking_state"] = res
prepres["data"] = collecs
if err != nil {
@ -138,14 +131,11 @@ func startPipelineHandler(s core.Service, c *core.Configuration) fiber.Handler {
}
}
func startRecordingHandler(s core.Service, c *core.Configuration) fiber.Handler {
func startRecordingHandler(s *core.TrackingService, c *core.Configuration) fiber.Handler {
return func(ctx *fiber.Ctx) error {
rec, err := s.StartRecord()
if err != nil {
//ctx.Status(500).JSON(err)
//return err
}
prepres := map[string]interface{}{}
prepres := make(map[string]interface{})
prepres["tracking_state"] = "RECORD"
prepres["data"] = rec
if err != nil {
@ -161,14 +151,11 @@ func startRecordingHandler(s core.Service, c *core.Configuration) fiber.Handler
}
}
func stopRecordingHandler(s core.Service, c *core.Configuration) fiber.Handler {
func stopRecordingHandler(s *core.TrackingService, c *core.Configuration) fiber.Handler {
return func(ctx *fiber.Ctx) error {
rec, err := s.StopRecord()
if err != nil {
//ctx.Status(500).JSON(err)
//return err
}
prepres := map[string]interface{}{}
prepres := make(map[string]interface{})
prepres["tracking_state"] = "LIVE"
prepres["data"] = rec
if err != nil {
@ -184,14 +171,11 @@ func stopRecordingHandler(s core.Service, c *core.Configuration) fiber.Handler {
}
}
func stopAllHandler(s core.Service, c *core.Configuration) fiber.Handler {
func stopAllHandler(s *core.TrackingService, c *core.Configuration) fiber.Handler {
return func(ctx *fiber.Ctx) error {
rec, err := s.StopAll()
if err != nil {
//ctx.Status(500).JSON(err)
//return err
}
prepres := map[string]interface{}{}
prepres := make(map[string]interface{})
prepres["tracking_state"] = "STOPPED"
prepres["data"] = rec
if err != nil {
@ -224,9 +208,10 @@ func createFiberWebsocketHandler(s core.Subscriber) func(conn *websocket.Conn) {
defer s.Unsubscribe(dispatcherId)
for {
cmsg := <-channel
logrus.Traceln("write to ws:", cmsg)
err := c.WriteMessage(websocket.TextMessage, []byte(cmsg))
if err != nil {
logrus.Info("close websocket connection")
logrus.Infoln("close websocket connection", err)
c.Close()
break
}
@ -268,3 +253,8 @@ func fiberHomeHandler(c *fiber.Ctx) error {
// Render index template
return c.Render("index", "ws://"+c.Hostname()+"/ws")
}
func fiberTrackingHandler(c *fiber.Ctx) error {
// Render index template
return c.Render("replayFull", nil)
}