Compare commits

...

156 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
5a3a9feca8 finished refactoring 2020-12-18 01:29:25 +01:00
b32cc0edeb [WIP] all-new pipeline wired up 2020-12-17 22:34:46 +01:00
c80090de1c [WIP] refactoring to pure push pipeline 2020-12-17 18:05:02 +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
7a5cb35c80 api operations idempotent 2020-12-15 14:34:14 +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
2a0b14e03d bugfixes everywhere 2020-12-15 00:34:19 +01:00
26014b0c34 Merge branch 'develop' into frank-dev2
# Conflicts:
#	cmd/server/server.go
#	templates/index.html
2020-12-14 10:38:06 +01:00
40b06dea7b display long lat data in index.html 2020-12-14 10:30:47 +01:00
145642c6e9 fixed wrong param type in http handler 2020-12-13 03:38:58 +01:00
f223bbec8f get all trackings 2020-12-13 03:23:18 +01:00
40216f11b3 wired webhandler up 2020-12-13 02:52:19 +01:00
391020ec47 some improvements in logging and naming/structuring 2020-12-12 16:34:35 +01:00
1ea6822202 implemented automatic persistence for trackings 2020-12-12 03:26:10 +01:00
09791727a4 [WIP] eliminated data races & formatted files 2020-12-12 02:29:22 +01:00
a4a739c64b [WIP] started with db implementation 2020-12-11 20:42:19 +01:00
c3f6a9ff5b [WIP] bugfixes 2020-12-11 18:38:39 +01:00
ae9b08e0c4 [WIP] repository and service encapsulation 2020-12-11 17:34:31 +01:00
01d5feed7b [WIP] save point 2020-12-11 01:18:05 +01:00
a81535758c add legent to the map 2020-12-10 16:47:52 +01:00
53545013e0 add second cube to html and change layout 2020-12-10 16:46:16 +01:00
2a9c3a1153 [WIP] add functionality 2020-12-10 13:52:21 +01:00
dfde29ed10 added synchronization check for invalid timestamps 2020-12-10 13:51:48 +01:00
d16dd61863 changed serial read behavior 2020-12-10 13:20:20 +01:00
b03d7fcfa2 small changes in sensordata logic 2020-12-10 09:43:44 +01:00
f5f98ced5b renamed format.go to sensordata.go 2020-12-10 09:27:17 +01:00
79325d9209 removed workspace.xml 2020-12-09 12:44:21 +01:00
afa5141af6 Merge branch 'frank-dev2' into develop 2020-12-09 12:32:54 +01:00
21a20727f6 added gitignore 2020-12-09 11:27:39 +01:00
63a6db0b57 Merge branch 'develop' into timo
# Conflicts:
#	.idea/workspace.xml
#	cmd/server/server.go
#	core/collectors.go
#	core/format.go
#	core/http.go
#	core/pipeline.go
#	go.mod
2020-12-09 11:21:30 +01:00
c307f3a300 adds ublox data to the map 2020-12-09 11:15:35 +01:00
074e4f49cc read sensordata and update the map 2020-12-08 22:35:51 +01:00
580ac21ca0 adds map to the index file 2020-12-08 13:58:47 +01:00
6b5a98a2a8 get static folder from frank-dev 2020-12-08 13:30:12 +01:00
6dcfbd012f merge 2020-12-08 13:00:38 +01:00
d8781945d4 changes in workspace 2020-12-08 12:45:22 +01:00
a55dce3820 first synchronizer implementation 2020-12-07 15:10:28 +01:00
eae4bc2e67 changed ubx height reference and optimized logging 2020-12-06 22:26:49 +01:00
64c7f9b644 Remove ignored files 2020-12-03 15:15:09 +01:00
bcdf7ddbf7 cleaned up develop and merged with refactoring 2020-12-03 15:09:33 +01:00
e33761bc46 fixed errors 2020-12-03 13:07:53 +01:00
cefb0153b2 refactor and implemented data pipeline 2020-12-02 19:41:41 +01:00
05946c3230 moved dispatcher, preparing for sensor data consolidation 2020-11-25 21:20:21 +01:00
83 changed files with 5887 additions and 1010 deletions

165
.gitignore vendored Normal file
View File

@ -0,0 +1,165 @@
# Created by https://www.toptal.com/developers/gitignore/api/macos,go,intellij
# Edit at https://www.toptal.com/developers/gitignore?templates=macos,go,intellij
### Go ###
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
### Go Patch ###
/vendor/
/Godeps/
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Intellij Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
# Sonarlint plugin
# https://plugins.jetbrains.com/plugin/7973-sonarlint
.idea/**/sonarlint/
# SonarQube Plugin
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
.idea/**/sonarIssues.xml
# Markdown Navigator plugin
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
.idea/**/markdown-navigator.xml
.idea/**/markdown-navigator-enh.xml
.idea/**/markdown-navigator/
# Cache file creation bug
# See https://youtrack.jetbrains.com/issue/JBR-2257
.idea/$CACHE_FILE$
# CodeStream plugin
# https://plugins.jetbrains.com/plugin/12206-codestream
.idea/codestream.xml
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# End of https://www.toptal.com/developers/gitignore/api/macos,go,intellij
# CUSTOM
.env
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

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

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>

6
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out"/>
</component>
</project>

2
.idea/vcs.xml generated
View File

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

378
.idea/workspace.xml generated
View File

@ -1,378 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="BranchesTreeState">
<expand>
<path>
<item name="ROOT" type="e8cecc67:BranchNodeDescriptor" />
<item name="LOCAL_ROOT" type="e8cecc67:BranchNodeDescriptor" />
</path>
<path>
<item name="ROOT" type="e8cecc67:BranchNodeDescriptor" />
<item name="LOCAL_ROOT" type="e8cecc67:BranchNodeDescriptor" />
<item name="GROUP_NODE:sensor" type="e8cecc67:BranchNodeDescriptor" />
</path>
</expand>
<select>
<path>
<item name="ROOT" type="e8cecc67:BranchNodeDescriptor" />
<item name="LOCAL_ROOT" type="e8cecc67:BranchNodeDescriptor" />
<item name="GROUP_NODE:sensor" type="e8cecc67:BranchNodeDescriptor" />
<item name="BRANCH:sensor/relative" type="e8cecc67:BranchNodeDescriptor" />
</path>
</select>
</component>
<component name="ChangeListManager">
<list default="true" id="99c957e4-aa42-481d-843d-3fbc901e0f79" name="Default Changelist" comment="">
<change afterPath="$PROJECT_DIR$/cmd/serial_only/serial_only.go" afterDir="false" />
<change afterPath="$PROJECT_DIR$/serial_ubx/serial.go" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ublox/decode.go" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ublox/messages.go" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ublox/strings_navpvt.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/go.mod" beforeDir="false" afterPath="$PROJECT_DIR$/go.mod" afterDir="false" />
<change beforePath="$PROJECT_DIR$/go.sum" beforeDir="false" afterPath="$PROJECT_DIR$/go.sum" afterDir="false" />
<change beforePath="$PROJECT_DIR$/hyperimu.json" beforeDir="false" afterPath="$PROJECT_DIR$/hyperimu.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/.gitattributes" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/.gitignore" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/CONTRIBUTING.md" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/ISSUE_TEMPLATE.md" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/LICENSE.md" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/README.md" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/Theory.md" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Dead Reckoning/Example1_calibrateSensor/Example1_calibrateSensor.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Dead Reckoning/Example2_getIMUData/Example2_getIMUData.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Dead Reckoning/Example3_getSensorStatus/Example3_getSensorStatus.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Dead Reckoning/Example4_vehicleDynamics/Example4_vehicleDynamics.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example10_AltitudeMSL/Example10_AltitudeMSL.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example11_ResetModule/Example1_FactoryDefaultviaI2C/Example1_FactoryDefaultviaI2C.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example11_ResetModule/Example2_FactoryDefaultsviaSerial/Example2_FactoryDefaultsviaSerial.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example12_UseUart/Example12_UseUart.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example13_PVT/Example1_AutoPVT/Example1_AutoPVT.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example13_PVT/Example2_AutoPVT_ExplicitUpdate/Example2_AutoPVT_ExplicitUpdate.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example13_PVT/Example3_AssumeAutoPVTviaUart/Example3_AssumeAutoPVTviaUart.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example14_DebugOutput/Example14_DebugOutput.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example15_GetDateTime/Example15_GetDateTime.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example16_Nanosecond_MaxOutput/Example16_Nanosecond_MaxOutput.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example16_PartialSecond_MaxOutput/Example16_PartialSecond_MaxOutput.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example17_Geofence/Example17_Geofence.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example18_PowerSaveMode/Example18_PowerSaveMode.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example19_DynamicModel/Example19_DynamicModel.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example1_BasicNMEARead/Example1_BasicNMEARead.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example20_SendCustomCommand/Example20_SendCustomCommand.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example21_ModuleInfo/Example21_ModuleInfo.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example22_PowerOff/Example22_PowerOff.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example2_NMEAParsing/Example2_NMEAParsing.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example3_GetPosition/Example3_GetPosition.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example4_FixType/Example4_FixType.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example5_SpeedHeadingPrecision/Example5_SpeedHeadingPrecision.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example6_EnableNMEASentences/Example6_EnableNMEASentences.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example7_OutputRate/Example7_OutputRate.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example8_GetProtocolVersion/Example8_GetProtocolVersion.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Example9_ChangeI2CAddress/Example9_ChangeI2CAddress.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/NEO-M8P-2/Example1_EnableRTCM/Example1_EnableRTCM.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/NEO-M8P-2/Example2_StartRTCMBase/Example2_StartRTCMBase.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/NEO-M8P-2/Example3_BaseWithLCD/Example3_BaseWithLCD.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/Example1_GetPositionAndTime_Series_6_7.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/SparkFun_Ublox_Arduino_Library_Series_6_7.cpp" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/SparkFun_Ublox_Arduino_Library_Series_6_7.h" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/ZED-F9P/Example10_GetHighPrecisionPositionAndAccuracy/Example10_GetHighPrecisionPositionAndAccuracy.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/ZED-F9P/Example11_GetHighPrecisionPositionUsingDouble/Example11_GetHighPrecisionPositionUsingDouble.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/ZED-F9P/Example11_autoHPPOSLLH/Example11_autoHPPOSLLH.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/ZED-F9P/Example11_setStaticPosition/Example11_setStaticPosition.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/ZED-F9P/Example1_GetPositionAccuracy/Example1_GetPositionAccuracy.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/ZED-F9P/Example2_ValConfigurationMethod/Example2_ValConfigurationMethod.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/ZED-F9P/Example3_StartRTCMBase/Example3_StartRTCMBase.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/ZED-F9P/Example4_BaseWithLCD/Example4_BaseWithLCD.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/ZED-F9P/Example5_RelativePositioningInformation/Example5_RelativePositioningInformation.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/ZED-F9P/Example6_GetVal/Example6_GetVal.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/ZED-F9P/Example7_SetVal/Example7_SetVal.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/ZED-F9P/Example8_GetSetPortSettings/Example8_GetSetPortSettings.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/examples/ZED-F9P/Example9_multiSetVal/Example9_multiSetVal.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/keywords.txt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/library.properties" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/src/SparkFun_Ublox_Arduino_Library.cpp" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/src/SparkFun_Ublox_Arduino_Library.h" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/SparkFun_Ublox_Arduino_Library/src/u-blox_config_keys.h" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/serial/serial.go" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/server.go" beforeDir="false" afterPath="$PROJECT_DIR$/cmd/server/server.go" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Go Application" />
<option value="Go File" />
</list>
</option>
</component>
<component name="GOROOT" path="/usr/local/Cellar/go/1.15.2/libexec" />
<component name="Git.Settings">
<option name="RECENT_BRANCH_BY_REPOSITORY">
<map>
<entry key="$PROJECT_DIR$" value="master" />
</map>
</option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectId" id="1jq0r5aMzeueG8kqyThltUQyqCa" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">
<property name="DefaultGoTemplateProperty" value="Go File" />
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="configurable.Global.GOPATH.is.expanded" value="true" />
<property name="configurable.Module.GOPATH.is.expanded" value="true" />
<property name="configurable.Project.GOPATH.is.expanded" value="true" />
<property name="go.import.settings.migrated" value="true" />
<property name="go.sdk.automatically.set" value="true" />
<property name="go.tried.to.enable.integration.vgo.integrator" value="true" />
<property name="last_opened_file_path" value="$USER_HOME$" />
<property name="settings.editor.selected.configurable" value="preferences.pluginManager" />
</component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/cmd/server" />
<recent name="$PROJECT_DIR$/serial_ubx" />
<recent name="$PROJECT_DIR$" />
<recent name="$PROJECT_DIR$/ublox" />
<recent name="$PROJECT_DIR$/serial" />
</key>
</component>
<component name="RunManager" selected="Go Build.go build git.timovolkmann.de/gyrogpsc/cmd/serial_only">
<configuration name="go build git.timovolkmann.de/gyrogpsc/cmd/serial_only" type="GoApplicationRunConfiguration" factoryName="Go Application" temporary="true" nameIsGenerated="true">
<module name="gyrogpsc" />
<working_directory value="$PROJECT_DIR$" />
<kind value="PACKAGE" />
<filePath value="$PROJECT_DIR$/cmd/serial_only/serial_only.go" />
<package value="git.timovolkmann.de/gyrogpsc/cmd/serial_only" />
<directory value="$PROJECT_DIR$" />
<method v="2" />
</configuration>
<configuration name="go build git.timovolkmann.de/gyrogpsc/serial" type="GoApplicationRunConfiguration" factoryName="Go Application" temporary="true" nameIsGenerated="true">
<module name="gyrogpsc" />
<working_directory value="$PROJECT_DIR$" />
<kind value="PACKAGE" />
<filePath value="$PROJECT_DIR$/serial/serial.go" />
<package value="git.timovolkmann.de/gyrogpsc/serial" />
<directory value="$PROJECT_DIR$" />
<method v="2" />
</configuration>
<configuration name="go build server.go" type="GoApplicationRunConfiguration" factoryName="Go Application" temporary="true" nameIsGenerated="true">
<module name="gyrogpsc" />
<working_directory value="$PROJECT_DIR$" />
<kind value="FILE" />
<filePath value="$PROJECT_DIR$/server.go" />
<directory value="$PROJECT_DIR$" />
<method v="2" />
</configuration>
<recent_temporary>
<list>
<item itemvalue="Go Build.go build git.timovolkmann.de/gyrogpsc/cmd/serial_only" />
<item itemvalue="Go Build.go build git.timovolkmann.de/gyrogpsc/serial" />
<item itemvalue="Go Build.go build server.go" />
<item itemvalue="Go Build.go build server.go" />
<item itemvalue="Go Build.go build git.timovolkmann.de/gyrogpsc/serial" />
</list>
</recent_temporary>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="99c957e4-aa42-481d-843d-3fbc901e0f79" name="Default Changelist" comment="" />
<created>1604516509922</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1604516509922</updated>
</task>
<task id="LOCAL-00001" summary="initial working version">
<created>1604747925521</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1604747925521</updated>
</task>
<task id="LOCAL-00002" summary="added dispatcher.go">
<created>1604747940729</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1604747940729</updated>
</task>
<task id="LOCAL-00003" summary="3d orientation visualization html/css">
<created>1604768327573</created>
<option name="number" value="00003" />
<option name="presentableId" value="LOCAL-00003" />
<option name="project" value="LOCAL" />
<updated>1604768327573</updated>
</task>
<task id="LOCAL-00004" summary="switched to relative sensor data (gyroscope)">
<created>1604777752387</created>
<option name="number" value="00004" />
<option name="presentableId" value="LOCAL-00004" />
<option name="project" value="LOCAL" />
<updated>1604777752387</updated>
</task>
<task id="LOCAL-00005" summary="changed orientation">
<created>1605444355479</created>
<option name="number" value="00005" />
<option name="presentableId" value="LOCAL-00005" />
<option name="project" value="LOCAL" />
<updated>1605444355479</updated>
</task>
<option name="localTasksCounter" value="6" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State>
<option name="FILTERS">
<map>
<entry key="branch">
<value>
<list>
<option value="sensor/relative" />
</list>
</value>
</entry>
</map>
</option>
</State>
</value>
</entry>
</map>
</option>
<option name="oldMeFiltersMigrated" value="true" />
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="initial working version" />
<MESSAGE value="added dispatcher.go" />
<MESSAGE value="3d orientation visualization html/css" />
<MESSAGE value="switched to relative sensor data (gyroscope)" />
<MESSAGE value="changed orientation" />
<option name="LAST_COMMIT_MESSAGE" value="changed orientation" />
</component>
<component name="VgoProject">
<integration-enabled>true</integration-enabled>
</component>
<component name="WindowStateProjectService">
<state x="640" y="377" key="#Go_Modules" timestamp="1604584628285">
<screen x="0" y="23" width="1680" height="1027" />
</state>
<state x="640" y="377" key="#Go_Modules/0.23.1680.1027/0.-1417.2560.1417@0.23.1680.1027" timestamp="1604584628285" />
<state x="603" y="247" key="#xdebugger.evaluate" timestamp="1604750409547">
<screen x="0" y="23" width="1680" height="1027" />
</state>
<state x="603" y="247" key="#xdebugger.evaluate/0.23.1680.1027/0.-1417.2560.1417@0.23.1680.1027" timestamp="1604750409547" />
<state x="100" y="123" width="1480" height="827" key="DiffContextDialog" timestamp="1604517986662">
<screen x="0" y="23" width="1680" height="1027" />
</state>
<state x="100" y="123" width="1480" height="827" key="DiffContextDialog/0.23.1680.1027/0.-1417.2560.1417@0.23.1680.1027" timestamp="1604517986662" />
<state x="739" y="405" key="EnvironmentVariablesDialog" timestamp="1604585759907">
<screen x="0" y="23" width="1680" height="1027" />
</state>
<state x="739" y="405" key="EnvironmentVariablesDialog/0.23.1680.1027/0.-1417.2560.1417@0.23.1680.1027" timestamp="1604585759907" />
<state width="1638" height="346" key="GridCell.Tab.-1.bottom" timestamp="1604750445987">
<screen x="0" y="23" width="1680" height="1027" />
</state>
<state width="1638" height="346" key="GridCell.Tab.-1.bottom/0.23.1680.1027/0.-1417.2560.1417@0.23.1680.1027" timestamp="1604750445987" />
<state width="1638" height="346" key="GridCell.Tab.-1.center" timestamp="1604750445987">
<screen x="0" y="23" width="1680" height="1027" />
</state>
<state width="1638" height="346" key="GridCell.Tab.-1.center/0.23.1680.1027/0.-1417.2560.1417@0.23.1680.1027" timestamp="1604750445987" />
<state width="1638" height="346" key="GridCell.Tab.-1.left" timestamp="1604750445987">
<screen x="0" y="23" width="1680" height="1027" />
</state>
<state width="1638" height="346" key="GridCell.Tab.-1.left/0.23.1680.1027/0.-1417.2560.1417@0.23.1680.1027" timestamp="1604750445987" />
<state width="1638" height="346" key="GridCell.Tab.-1.right" timestamp="1604750445987">
<screen x="0" y="23" width="1680" height="1027" />
</state>
<state width="1638" height="346" key="GridCell.Tab.-1.right/0.23.1680.1027/0.-1417.2560.1417@0.23.1680.1027" timestamp="1604750445987" />
<state width="1878" height="249" key="GridCell.Tab.0.bottom" timestamp="1606326600270">
<screen x="0" y="23" width="1920" height="1177" />
</state>
<state width="1638" height="273" key="GridCell.Tab.0.bottom/0.-1417.2560.1417/0.23.1680.1027@0.-1417.2560.1417" timestamp="1604948042531" />
<state width="1638" height="273" key="GridCell.Tab.0.bottom/0.23.1680.1027/0.-1417.2560.1417@0.-1417.2560.1417" timestamp="1604913985770" />
<state width="1638" height="273" key="GridCell.Tab.0.bottom/0.23.1680.1027/0.-1417.2560.1417@0.23.1680.1027" timestamp="1604760252890" />
<state width="1638" height="273" key="GridCell.Tab.0.bottom/0.23.1680.1027@0.23.1680.1027" timestamp="1604933111805" />
<state width="1638" height="273" key="GridCell.Tab.0.bottom/0.23.1920.1116@0.23.1920.1116" timestamp="1605380018474" />
<state width="1878" height="249" key="GridCell.Tab.0.bottom/0.23.1920.1177/0.-1417.2560.1417@0.23.1920.1177" timestamp="1606326600270" />
<state width="1638" height="273" key="GridCell.Tab.0.bottom/0.23.1920.1177@0.23.1920.1177" timestamp="1605533225921" />
<state width="1878" height="249" key="GridCell.Tab.0.center" timestamp="1606326600270">
<screen x="0" y="23" width="1920" height="1177" />
</state>
<state width="1638" height="273" key="GridCell.Tab.0.center/0.-1417.2560.1417/0.23.1680.1027@0.-1417.2560.1417" timestamp="1604948042530" />
<state width="1638" height="273" key="GridCell.Tab.0.center/0.23.1680.1027/0.-1417.2560.1417@0.-1417.2560.1417" timestamp="1604913985769" />
<state width="1638" height="273" key="GridCell.Tab.0.center/0.23.1680.1027/0.-1417.2560.1417@0.23.1680.1027" timestamp="1604760252890" />
<state width="1638" height="273" key="GridCell.Tab.0.center/0.23.1680.1027@0.23.1680.1027" timestamp="1604933111805" />
<state width="1638" height="273" key="GridCell.Tab.0.center/0.23.1920.1116@0.23.1920.1116" timestamp="1605380018473" />
<state width="1878" height="249" key="GridCell.Tab.0.center/0.23.1920.1177/0.-1417.2560.1417@0.23.1920.1177" timestamp="1606326600270" />
<state width="1638" height="273" key="GridCell.Tab.0.center/0.23.1920.1177@0.23.1920.1177" timestamp="1605533225920" />
<state width="1878" height="249" key="GridCell.Tab.0.left" timestamp="1606326600269">
<screen x="0" y="23" width="1920" height="1177" />
</state>
<state width="1638" height="273" key="GridCell.Tab.0.left/0.-1417.2560.1417/0.23.1680.1027@0.-1417.2560.1417" timestamp="1604948042530" />
<state width="1638" height="273" key="GridCell.Tab.0.left/0.23.1680.1027/0.-1417.2560.1417@0.-1417.2560.1417" timestamp="1604913985768" />
<state width="1638" height="273" key="GridCell.Tab.0.left/0.23.1680.1027/0.-1417.2560.1417@0.23.1680.1027" timestamp="1604760252889" />
<state width="1638" height="273" key="GridCell.Tab.0.left/0.23.1680.1027@0.23.1680.1027" timestamp="1604933111805" />
<state width="1638" height="273" key="GridCell.Tab.0.left/0.23.1920.1116@0.23.1920.1116" timestamp="1605380018472" />
<state width="1878" height="249" key="GridCell.Tab.0.left/0.23.1920.1177/0.-1417.2560.1417@0.23.1920.1177" timestamp="1606326600269" />
<state width="1638" height="273" key="GridCell.Tab.0.left/0.23.1920.1177@0.23.1920.1177" timestamp="1605533225919" />
<state width="1878" height="249" key="GridCell.Tab.0.right" timestamp="1606326600270">
<screen x="0" y="23" width="1920" height="1177" />
</state>
<state width="1638" height="273" key="GridCell.Tab.0.right/0.-1417.2560.1417/0.23.1680.1027@0.-1417.2560.1417" timestamp="1604948042531" />
<state width="1638" height="273" key="GridCell.Tab.0.right/0.23.1680.1027/0.-1417.2560.1417@0.-1417.2560.1417" timestamp="1604913985770" />
<state width="1638" height="273" key="GridCell.Tab.0.right/0.23.1680.1027/0.-1417.2560.1417@0.23.1680.1027" timestamp="1604760252890" />
<state width="1638" height="273" key="GridCell.Tab.0.right/0.23.1680.1027@0.23.1680.1027" timestamp="1604933111805" />
<state width="1638" height="273" key="GridCell.Tab.0.right/0.23.1920.1116@0.23.1920.1116" timestamp="1605380018473" />
<state width="1878" height="249" key="GridCell.Tab.0.right/0.23.1920.1177/0.-1417.2560.1417@0.23.1920.1177" timestamp="1606326600270" />
<state width="1638" height="273" key="GridCell.Tab.0.right/0.23.1920.1177@0.23.1920.1177" timestamp="1605533225920" />
<state width="1638" height="346" key="GridCell.Tab.1.bottom" timestamp="1604750442086">
<screen x="0" y="23" width="1680" height="1027" />
</state>
<state width="1638" height="346" key="GridCell.Tab.1.bottom/0.23.1680.1027/0.-1417.2560.1417@0.23.1680.1027" timestamp="1604750442086" />
<state width="1638" height="346" key="GridCell.Tab.1.center" timestamp="1604750442085">
<screen x="0" y="23" width="1680" height="1027" />
</state>
<state width="1638" height="346" key="GridCell.Tab.1.center/0.23.1680.1027/0.-1417.2560.1417@0.23.1680.1027" timestamp="1604750442085" />
<state width="1638" height="346" key="GridCell.Tab.1.left" timestamp="1604750442085">
<screen x="0" y="23" width="1680" height="1027" />
</state>
<state width="1638" height="346" key="GridCell.Tab.1.left/0.23.1680.1027/0.-1417.2560.1417@0.23.1680.1027" timestamp="1604750442085" />
<state width="1638" height="346" key="GridCell.Tab.1.right" timestamp="1604750442085">
<screen x="0" y="23" width="1680" height="1027" />
</state>
<state width="1638" height="346" key="GridCell.Tab.1.right/0.23.1680.1027/0.-1417.2560.1417@0.23.1680.1027" timestamp="1604750442085" />
<state x="204" y="155" width="1501" height="766" key="SettingsEditor" timestamp="1605609479939">
<screen x="0" y="23" width="1920" height="1177" />
</state>
<state x="179" y="138" width="1501" height="766" key="SettingsEditor/0.23.1680.1027/0.-1417.2560.1417@0.23.1680.1027" timestamp="1604757277174" />
<state x="204" y="155" key="SettingsEditor/0.23.1920.1177/0.-1417.2560.1417@0.23.1920.1177" timestamp="1605609479939" />
<state width="840" height="513" key="XDebugger.FullValuePopup" timestamp="1604750505120">
<screen x="0" y="23" width="1680" height="1027" />
</state>
<state width="840" height="513" key="XDebugger.FullValuePopup/0.23.1680.1027/0.-1417.2560.1417@0.23.1680.1027" timestamp="1604750505120" />
<state x="505" y="250" width="670" height="676" key="search.everywhere.popup" timestamp="1604757918385">
<screen x="0" y="23" width="1680" height="1027" />
</state>
<state x="505" y="250" width="670" height="676" key="search.everywhere.popup/0.23.1680.1027/0.-1417.2560.1417@0.23.1680.1027" timestamp="1604757918385" />
<state x="596" y="393" key="vcs.readOnlyHandler.ReadOnlyStatusDialog" timestamp="1605566567276">
<screen x="0" y="23" width="1920" height="1177" />
</state>
<state x="596" y="393" key="vcs.readOnlyHandler.ReadOnlyStatusDialog/0.23.1920.1177/0.-1417.2560.1417@0.23.1920.1177" timestamp="1605566567276" />
</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.

72
cmd/replay/replay.go Normal file
View File

@ -0,0 +1,72 @@
/**
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"
)
func main() {
go func() {
logrus.Println(http.ListenAndServe("localhost:6060", nil))
}()
conf := configurationFromFile()
logrus.Debug(conf)
repo := storage.NewRepository(conf)
disp := core.NewDispatcher()
service := core.NewTrackingService(conf, repo, disp)
go func() {
// 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)
}()
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,14 +0,0 @@
package main
import (
"git.timovolkmann.de/gyrogpsc/serial_ubx"
"log"
)
func main() {
r, err := serial_ubx.Setup("/dev/tty.usbmodem14201")
if err != nil {
log.Fatal(err)
}
r.Printloop()
}

View File

@ -1,25 +1,30 @@
package main
import (
"git.timovolkmann.de/gyrogpsc/dispatcher"
gnet "git.timovolkmann.de/gyrogpsc/net"
)
const (
TCP_PORT = ":3010"
HTTP_PORT = ":3011"
SERIAL_PORT = "/dev/tty.usbmodem14201"
"git.timovolkmann.de/gyrogpsc/core"
"git.timovolkmann.de/gyrogpsc/storage"
"git.timovolkmann.de/gyrogpsc/web"
"github.com/sirupsen/logrus"
"net/http"
_ "net/http/pprof"
)
func main() {
d := dispatcher.New()
collectRoutines(d)
gnet.NewHttpServer(d, HTTP_PORT)
}
// launch profiling server in goroutine to debug performance issues
go func() {
logrus.Println(http.ListenAndServe("localhost:6060", nil))
}()
func collectRoutines(d *dispatcher.Dispatcher) {
// collectRoutines Serial UBX Sensor Data
go gnet.SerialUbxCollector(d, SERIAL_PORT)
// collectRoutines TCP JSON Sensor Data
go gnet.TcpJsonCollector(d, TCP_PORT)
// load configuration from file
conf := core.ConfigurationFromFile()
// initialize persistence layer
repo := storage.NewRepository(conf)
// initialize message distribution layer
disp := core.NewDispatcher()
// initialize core logic service and inject
service := core.NewTrackingService(conf, repo, disp)
// launch webserver
web.CreateServer(service, disp, conf)
}

View File

@ -1,25 +0,0 @@
package main
import (
"git.timovolkmann.de/gyrogpsc/dispatcher"
gnet "git.timovolkmann.de/gyrogpsc/net"
)
const (
TCP_PORT = ":3010"
HTTP_PORT = ":3011"
SERIAL_PORT = "/dev/tty.usbmodem14201"
)
func main() {
d := dispatcher.New()
collectRoutines(d)
gnet.NewHttpServer(d, HTTP_PORT)
}
func collectRoutines(d *dispatcher.Dispatcher) {
// collectRoutines Serial UBX Sensor Data
go gnet.SerialUbxCollector(d, SERIAL_PORT)
// collectRoutines TCP JSON Sensor Data
go gnet.TcpJsonCollector(d, TCP_PORT)
}

235
core/collectors.go Normal file
View File

@ -0,0 +1,235 @@
package core
import (
"fmt"
"git.timovolkmann.de/gyrogpsc/ublox"
"github.com/sirupsen/logrus"
"go.bug.st/serial"
"net"
"os"
"time"
)
type Collector interface {
Collect()
Stop()
OutChannel() chan interface{}
}
type CollectorType string
const (
SERIAL CollectorType = "SERIAL_COLLECTOR"
TCP CollectorType = "TCP_COLLECTOR"
)
var tcpSingleton *tcpCollector
func NewCollector(config *Configuration, typ CollectorType) Collector {
var coll Collector
switch typ {
case SERIAL:
coll = newSerial(config)
case TCP:
if tcpSingleton == nil {
tcpSingleton = newTcp(config)
}
coll = tcpSingleton
default:
panic("selected collector type not implemented")
}
return coll
}
type serialCollector struct {
out chan interface{}
config *Configuration
}
func (s *serialCollector) OutChannel() chan interface{} {
return s.out
}
func (s *serialCollector) Collect() {
go func(ch chan interface{}) {
logrus.Println("start serial collector")
mode := &serial.Mode{
BaudRate: 115200,
}
port, err := serial.Open(s.config.Collectors.SerialCollectorPort, mode)
if err != nil {
logrus.Warn("can't open serial port:", err)
//if e, ok := err.(serial.PortError); ok && e.Code() == serial.PortBusy {
for i := 3; i < 20; i = i + 2 {
logrus.Warnf("try again in -> %vms", i*i)
time.Sleep(time.Millisecond * time.Duration(i*i))
port, err = serial.Open(s.config.Collectors.SerialCollectorPort, mode)
if err == nil {
break
}
}
if err != nil {
logrus.Errorln(err)
}
//}
}
decoder := ublox.NewDecoder(port)
defer func() {
if r := recover(); r != nil {
logrus.Infoln("stopped collecting channel: serial")
}
logrus.Infoln("close serial port")
port.Close()
}()
maxSkip := 0
loop:
for {
//logrus.Trace("serial collector running")
meas, err2 := decoder.Decode()
if err2 != nil {
if err2.Error() == "NMEA not implemented" {
continue
}
logrus.Println("serial read err:", err2)
break
}
select {
case ch <- meas:
maxSkip = 0
default:
logrus.Traceln("skip collecting serial messages")
if maxSkip >= 10 {
break loop
}
maxSkip++
}
}
logrus.Println("serial collector stopped")
}(s.out)
}
func (s *serialCollector) Stop() {
defer func() {
if recover() != nil {
logrus.Debugln("channel already closed")
}
}()
loop:
for {
select {
case <-s.out:
logrus.Debugln("waiting")
default:
logrus.Debugln("breaking")
break loop
}
}
close(s.out)
s.out = make(chan interface{}, 16)
}
func newSerial(config *Configuration) *serialCollector {
return &serialCollector{
out: make(chan interface{}, 16),
config: config,
}
}
type tcpCollector struct {
out chan interface{}
config *Configuration
}
func (t *tcpCollector) OutChannel() chan interface{} {
return t.out
}
func (t *tcpCollector) Collect() {
logrus.Info("starting tcp collector")
}
func (t *tcpCollector) Stop() {
defer func() {
if recover() != nil {
logrus.Debugln("channel already closed")
}
}()
loop:
for {
select {
case <-t.out:
logrus.Debugln("waiting")
default:
logrus.Debugln("breaking")
break loop
}
}
close(t.out)
t.out = make(chan interface{}, 16)
}
func newTcp(config *Configuration) *tcpCollector {
//func newTcp(proc Processor, config *Configuration) *tcpCollector {
logrus.Println("listen for tcp connections on port", config.Collectors.TcpCollectorPort)
listener, err := net.Listen("tcp", config.Collectors.TcpCollectorPort)
if err != nil {
fmt.Println("Error listening:", err)
//os.Exit(1)
}
c := &tcpCollector{
out: make(chan interface{}, 16),
config: config,
}
go func() {
for {
// Listen for an incoming connection.
conn, err := listener.Accept()
if err != nil {
logrus.Errorln("Error accepting: ", err.Error())
os.Exit(1)
}
logrus.Infoln("new incoming tcp connection...")
// Handle connections in a new goroutine.
go c.connectionHandler(conn)
}
}()
return c
}
// handles incoming tcp connections.
func (t *tcpCollector) connectionHandler(conn net.Conn) {
defer func() {
if r := recover(); r != nil {
logrus.Infoln("stopped collecting tcp: panic")
}
logrus.Infoln("close connection")
conn.Close()
}()
buf := make([]byte, 2048)
skipped := 0
//loop:
for {
// Read the incoming connection into the buffer.
n, err := conn.Read(buf)
if err != nil {
fmt.Println("TCP error - reading from connection:", n, err.Error())
break
}
select {
case t.out <- buf:
skipped = 0
default:
logrus.Traceln("skip collecting tcp messages")
//if skipped >= 10 {
// break loop
//}
skipped++
}
}
}

52
core/config.go Normal file
View File

@ -0,0 +1,52 @@
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"`
SerialCollectorPort string `mapstructure:"portserial"`
} `mapstructure:"Collectors"`
Webserver struct {
Port string `mapstructure:"port"`
} `mapstructure:"webserver"`
Pipeline struct {
PublishIntervalMs int `mapstructure:"publishintervalms"`
SyncUpdateIntervalMs int `mapstructure:"syncupdateintervalms"`
} `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
}

266
core/datamodel.go Normal file
View File

@ -0,0 +1,266 @@
package core
import (
"errors"
"git.timovolkmann.de/gyrogpsc/ublox"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"math"
"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
}
func newTracking() Tracking {
return Tracking{
TrackingMetadata: TrackingMetadata{
UUID: uuid.New(),
},
Data: []SensorData{},
}
}
func (s *Tracking) isEmpty() bool {
if len(s.Data) != s.Size {
logrus.Errorln("data inconsistent...", len(s.Data), s.Size)
}
return len(s.Data) == 0
}
// enumerate sources
type SourceId string
const (
SOURCE_TCP SourceId = "SOURCE_TCP"
SOURCE_SERIAL SourceId = "SOURCE_SERIAL"
)
// internal unified representation for smartphone and ublox sensor data
type SensorData struct {
//MsgClass string
//FixType string
itow uint32
source SourceId
latency int
Servertime time.Time
Timestamp time.Time
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"`
}
func (s *SensorData) Source() SourceId {
return s.source
}
func (s *SensorData) SetSource(si SourceId) {
s.source = si
}
func (s SensorData) isSameEpoch(n SensorData) bool {
if n.itow == 0 {
return false
}
return s.itow == n.itow
}
// Consolidates two sensordata elements if they are in the same epoch
func (s SensorData) ConsolidateEpochsOnly(n SensorData) SensorData {
s.checkSources(&n)
if s.isSameEpoch(n) {
null := SensorData{}
if n.Timestamp == null.Timestamp {
n.Timestamp = s.Timestamp
}
if n.Position == null.Position {
n.Position = s.Position
}
if n.Orientation == null.Orientation {
n.Orientation = s.Orientation
}
}
return n
}
// Consolidates two sensordata elements but ignores timestamps
func (s SensorData) ConsolidateExTime(n SensorData) SensorData {
s.checkSources(&n)
null := SensorData{}
if n.Position == null.Position {
n.Position = s.Position
}
if n.Orientation == null.Orientation {
n.Orientation = s.Orientation
}
return n
}
func (s *SensorData) checkSources(n *SensorData) {
if (s.source != n.source && *s != SensorData{}) {
logrus.Println(s)
logrus.Println(n)
logrus.Fatalln("Do not consolidate SensorData from different Sources")
}
}
var (
errNotImplemented = errors.New("message not implemented")
errRawMessage = errors.New("raw message")
)
var lastTimeOffsetIphone int64
var lastTimeOffsetUblox int64
func ConvertUbxSensorData(msg interface{}) (*SensorData, error) {
sd := &SensorData{
//Servertime: time.Now().UTC(),
source: SOURCE_SERIAL,
}
switch v := msg.(type) {
case *ublox.NavPvt:
//logrus.Println("NAV-PVT")
sd.itow = v.ITOW_ms
sd.Timestamp = time.Date(int(v.Year_y), time.Month(v.Month_month), int(v.Day_d), int(v.Hour_h), int(v.Min_min), int(v.Sec_s), int(v.Nano_ns), time.UTC)
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
sd.Timestamp = time.Date(int(v.Year_y), time.Month(v.Month_month), int(v.Day_d), int(v.Hour_h), int(v.Min_min), int(v.Sec_s), int(v.Nano_ns), time.UTC)
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
sd.Orientation[0] = float64(v.Pitch_deg) * 1e-5
sd.Orientation[1] = float64(v.Roll_deg) * 1e-5
sd.Orientation[2] = float64(v.Heading_deg) * 1e-5
case *ublox.RawMessage:
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
}
func ConvertByteSensorData(jsonData []byte) (*SensorData, error) {
if gjson.Get(string(jsonData), "os").String() == "hyperimu" {
return convertAndroidHyperImu(jsonData)
}
return convertIPhoneSensorLog(jsonData)
}
func convertIPhoneSensorLog(jsonData []byte) (*SensorData, error) {
timestamp := gjson.Get(string(jsonData), "locationTimestamp_since1970").Float()
lat := gjson.Get(string(jsonData), "locationLatitude").Float()
lon := gjson.Get(string(jsonData), "locationLongitude").Float()
alt := gjson.Get(string(jsonData), "locationAltitude").Float()
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()
lastTimeOffsetIphone = time.Now().UnixNano() - ts.UnixNano()
} else {
ts = time.Now().UTC().Add(time.Duration(lastTimeOffsetIphone))
}
//if ts == time.Date()
sd := &SensorData{
//Servertime: time.Now().UTC(),
source: SOURCE_TCP,
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")
}
return sd, nil
}
func convertAndroidHyperImu(jsonData []byte) (*SensorData, error) {
timestamp := gjson.Get(string(jsonData), "Timestamp").Int()
lat := gjson.Get(string(jsonData), "GPS.0").Float()
lon := gjson.Get(string(jsonData), "GPS.1").Float()
alt := gjson.Get(string(jsonData), "GPS.2").Float()
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(),
source: SOURCE_TCP,
Timestamp: time.Unix(0, timestamp*int64(time.Millisecond)).UTC(),
Position: [3]float64{lat, lon, alt},
Orientation: [3]float64{pitch, roll, yaw},
}
if (*sd == SensorData{}) {
return nil, errors.New("android hyperimu: convert empty")
}
return sd, nil
}

82
core/dispatcher.go Normal file
View File

@ -0,0 +1,82 @@
package core
import (
"errors"
"github.com/sirupsen/logrus"
"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),
counter: 0,
sem: semaphore.NewWeighted(1),
}
}
// 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 you want to turn on and can get semaphore then return success
return true
} else if !s && !ok {
// 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)
return true
}
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
}
logrus.Tracef("publishing to %v listeners\n", len(d.listeners))
for _, ch := range d.listeners {
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++
rec := make(chan string)
d.listeners[key] = rec
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 {
return errors.New("no subscription with id")
}
delete(d.listeners, id)
close(receiver)
return nil
}

28
core/interfaces.go Normal file
View File

@ -0,0 +1,28 @@
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
}
// 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)
Load(id uuid.UUID) (*Tracking, error)
}

146
core/pipeline_record.go Normal file
View File

@ -0,0 +1,146 @@
package core
import (
"encoding/json"
ext "github.com/reugn/go-streams/extension"
"github.com/reugn/go-streams/flow"
"github.com/sirupsen/logrus"
"github.com/tidwall/pretty"
"time"
)
type pipelineRecord struct{}
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, 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(flowStore).Via(dataSanitizer).Via(flowJson).To(sinkPub)
return &pipelineRecord{}
}
func storeFunc(s Tracker) flow.MapFunc {
return func(i interface{}) interface{} {
var sd *SensorData
if v, ok := i.(*SensorData); ok {
sd = v
} else {
panic("unexpected data struct")
}
if (*sd == SensorData{} || sd == nil) {
logrus.Info("empty data")
} else {
sd.Servertime = time.Now().UTC()
s.Put(*sd)
}
logrus.Debugf("%-14v %-40s %-40s %v %v", sd.Source(), sd.Timestamp.Format(time.RFC3339Nano), sd.Servertime.Format(time.RFC3339Nano), sd.Position, sd.Orientation)
//runtime.Gosched()
return sd
}
}
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{} {
logrus.Traceln("transform TCP data...")
var returnSlice []interface{}
if b, ok := i.([]byte); ok {
sd, err := ConvertByteSensorData(b)
if err != nil {
logrus.Errorln("error converting byte message:", err)
}
if sd != nil {
return append(returnSlice, sd)
}
}
logrus.Errorln("wrong data type. expected []byte data")
return nil
}
func transformSerFunc(i interface{}) []interface{} {
logrus.Traceln("transform SERIAL data...")
var returnSlice []interface{}
sd, err := ConvertUbxSensorData(i)
if err != nil {
logrus.Errorln("error converting ubx message:", err)
return nil
}
if sd == nil {
return nil
}
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
}
func newPublishSink(p Publisher) *publishSink {
sink := &publishSink{make(chan interface{}), p}
sink.init()
return sink
}
func (ps *publishSink) In() chan<- interface{} {
return ps.in
}
func (ps *publishSink) init() {
go func() {
logrus.Trace("publish sink running")
for elem := range ps.in {
if v, ok := elem.(string); ok {
ps.p.Publish(v)
} else {
logrus.Debugln("not publishing. wrong format", elem)
//reflect.TypeOf(elem)
}
}
logrus.Trace("publish sink stopped")
}()
}

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()
}

279
core/service.go Normal file
View File

@ -0,0 +1,279 @@
package core
import (
"context"
"errors"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
"golang.org/x/sync/semaphore"
"sync"
"time"
)
// indicates state of the TrackingService
type OpMode uint8
const (
STOPPED OpMode = iota
LIVE
RECORDING
REPLAY
)
// 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
}
type Recorder interface {
SetRecording(s bool) (ok bool)
IsRecording() bool
}
// 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
}
// constructor
func NewTrackingService(c *Configuration, s Storer, p Publisher) *TrackingService {
t := &Tracking{}
ts := &TrackingService{
tracking: t,
opMode: STOPPED,
collectors: nil,
recSem: semaphore.NewWeighted(1),
mu: &sync.RWMutex{},
config: c,
store: s,
publisher: p,
}
// first call to to open tcp port. makes app ready to accept connections
NewCollector(c, TCP)
return ts
}
// caches sensordata while recording
func (t *TrackingService) Put(data SensorData) {
if !t.IsRecording() {
return
}
t.mu.Lock()
t.tracking.Data = append(t.tracking.Data, data)
t.tracking.Size++
logrus.Traceln("raw data points: len->", len(t.tracking.Data))
t.mu.Unlock()
}
// 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
} else if !s && !okay {
// if i want to turn off and cant get semaphore, i can safely turn off by releasing semaphore and return success
t.recSem.Release(1)
return true
}
return false
}
func (t *TrackingService) IsRecording() bool {
if t.recSem.TryAcquire(1) {
t.recSem.Release(1)
return false
}
return true
}
// 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)
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 {
c := NewCollector(t.config, col)
t.collectors = append(t.collectors, c)
if col == TCP {
tcp = c.OutChannel()
}
if col == SERIAL {
ser = c.OutChannel()
}
c.Collect()
}
t.safelyReplaceTracking(newTracking())
t.tracking.Collectors = cols
// finally create pipeline
NewRecordPipeline(t.publisher, t, tcp, ser)
t.publisher.SetStreaming(true)
logrus.Debugln("current State:", t.opMode)
return "LIVE", nil
}
// 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
}
// starts recording and returns state
func (t *TrackingService) StartRecord() (string, error) {
logrus.Info("SERVICE: START RECORD")
if t.opMode != LIVE {
if t.opMode == RECORDING {
txt := "trackingservice: already recording"
logrus.Warn(txt)
return "record already running since: " + t.tracking.TimeCreated.String(), errors.New(txt)
} else {
txt := "trackingservice: start collector pipeline to record data"
logrus.Warn(txt)
return "record already running since: " + t.tracking.TimeCreated.String(), errors.New(txt)
}
}
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
}
// 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"
logrus.Info(txt)
return nil, errors.New(txt)
}
t.SetRecording(false)
err := t.store.Save(*t.tracking)
if err != nil {
logrus.Error(err)
}
t.opMode = LIVE
tm := t.tracking.TrackingMetadata
t.safelyReplaceTracking(newTracking())
t.tracking.Collectors = tm.Collectors
logrus.Debugln("current State:", t.opMode)
return &tm, err
}
// 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
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
}
// 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) {
panic("implement me")
}
// 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

@ -1,46 +0,0 @@
package dispatcher
import (
"errors"
"fmt"
)
type Dispatcher struct {
listeners map[int16]chan string
counter int16
}
func New() *Dispatcher {
fmt.Println("new dispatcher")
return &Dispatcher{
listeners: make(map[int16]chan string),
counter: 0,
}
}
func (d *Dispatcher) Publish(message string) {
fmt.Println("publish to listeners", len(d.listeners))
for _, ch := range d.listeners {
ch <- message
}
}
func (d *Dispatcher) Subscribe() (id int16, receiver <-chan string) {
fmt.Println("subscribe")
key := d.counter
d.counter++
rec := make(chan string)
d.listeners[key] = rec
return key, rec
}
func (d *Dispatcher) Unsubscribe(id int16) error {
fmt.Println("unsubscribe")
receiver, ok := d.listeners[id]
if !ok {
return errors.New("no subscription with id")
}
delete(d.listeners, id)
close(receiver)
return nil
}

27
example_config.yml Normal file
View File

@ -0,0 +1,27 @@
# !!! copy this file and rename it to gpsconfig.yml !!!
# !!! file must be located in working directory !!!
# server configurations
webserver:
port: ":3011"
# collector configurations
collectors:
porttcp: ":3010"
portserial: "/dev/tty.usbmodem14201"
debuglevel: "INFO"
#// ErrorLevel level. Logs. Used for errors that should definitely be noted.
#// Commonly used for hooks to send errors to an error tracking service.
#ErrorLevel
#// WarnLevel level. Non-critical entries that deserve eyes.
#WarnLevel
#// InfoLevel level. General operational entries about what's going on inside the
#// application.
#InfoLevel
#// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
#DebugLevel

25
go.mod
View File

@ -3,11 +3,24 @@ module git.timovolkmann.de/gyrogpsc
go 1.15
require (
github.com/daedaleanai/ublox v0.0.0-20201103121443-9befa131d32d
github.com/gorilla/websocket v1.4.2
github.com/sparkfun/SparkFun_Ublox_Arduino_Library v1.8.7 // indirect
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/golang/protobuf v1.4.2 // indirect
github.com/google/go-cmp v0.5.2 // indirect
github.com/google/uuid v1.1.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
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf // indirect
golang.org/x/tools v0.0.0-20201121010211-780cb80bd7fb // indirect
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
)

589
go.sum
View File

@ -1,50 +1,605 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
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/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/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=
github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
github.com/Joker/hpp v0.0.0-20180418125244-6893e659854a/go.mod h1:MzD2WMdSxvbHw5fM/OXOFily/lipJWRc9C1px0Mt0ZE=
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/Joker/jade v1.0.0/go.mod h1:efZIdO0py/LtcJRSa/j2WEklMSAw84WV0zZVMxNToB8=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
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/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/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/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/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/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/bombsimon/wsl/v3 v3.1.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc=
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/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=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
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/daedaleanai/ublox v0.0.0-20201103121443-9befa131d32d h1:WbFmX8L79E02PgDJYWINhWvceaMGUzgmrwdE5CuUBBk=
github.com/daedaleanai/ublox v0.0.0-20201103121443-9befa131d32d/go.mod h1:pfcwlN8XUYXVYAkPU2LrFZnXIS4EvpZaXh+qRKCN9Sg=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
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=
github.com/denis-tingajkin/go-header v0.3.1/go.mod h1:sq/2IxMhaZX+RRcgHfCRx/m0M5na0fBt4/CRe7Lrji0=
github.com/dgraph-io/badger/v2 v2.2007.2 h1:EjjK0KqwaFMlPin1ajhP943VPENHJdEz1KLIegjaI3k=
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/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/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
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/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=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
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-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-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=
github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ=
github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg=
github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw=
github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU=
github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk=
github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI=
github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks=
github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc=
github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=
github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
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/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=
github.com/gofiber/fiber/v2 v2.2.4/go.mod h1:Aso7/M+EQOinVkWp4LUYjdlTpKTBoCk2Qo4djnMsyHE=
github.com/gofiber/template v1.6.6 h1:mZ0sMRomxndpzVOSez6jKPaMO9UlUDfmBfHFmkFnb/A=
github.com/gofiber/template v1.6.6/go.mod h1:WmzkzTh6QWwrGysWFU8bwNsmHmqRbxHmWZcApIT1ORo=
github.com/gofiber/websocket/v2 v2.0.2 h1:UA/6NpyG+vmPGlvJvW8MJPJpRFuS7abinZ5HbLuV8u0=
github.com/gofiber/websocket/v2 v2.0.2/go.mod h1:7VBnzEVRK0K0eTIVc5GbXPF1JWUFnllY0X4cRtG2v78=
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/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=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
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/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=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4=
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk=
github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0=
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8=
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o=
github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU=
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
github.com/golangci/golangci-lint v1.28.3/go.mod h1:JlLqleIwwgLVJtjKtrB37OKp3LGLrUhEx9tWY4VKWSY=
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU=
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA=
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI=
github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4=
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
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/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/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/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=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
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/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
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/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=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
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.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=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
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/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/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/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
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/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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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=
github.com/kyoh86/exportloopref v0.1.4/go.mod h1:h1rDl2Kdj97+Kwh4gdz3ujE7XHmH51Q0lUiZ1z4NLj8=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU=
github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
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/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/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=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
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-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
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/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/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/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
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/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sparkfun/SparkFun_Ublox_Arduino_Library v1.8.7 h1:T112CHmp+v1bh0W7sp49tZe8SUbw3viSGRTVLziZxfc=
github.com/sparkfun/SparkFun_Ublox_Arduino_Library v1.8.7/go.mod h1:FVoZAzJFrR5D6P8qd2rgpJAV/qF5oODtIT9YjVV+xzY=
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_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/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/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/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/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=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryancurrah/gomodguard v1.1.0/go.mod h1:4O8tr7hBODaGE6VIhfJDHcwzh5GUccKSJBU0UMXJFVM=
github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/savsgio/gotils v0.0.0-20200608150037-a5f6f5aef16c h1:2nF5+FZ4/qp7pZVL7fR6DEaSTzuDmNaFTyqp92/hwF8=
github.com/savsgio/gotils v0.0.0-20200608150037-a5f6f5aef16c/go.mod h1:TWNAOTaVzGOXq8RbEvHnhzA/A2sLZzgn0m6URjnukY8=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/securego/gosec/v2 v2.3.0/go.mod h1:UzeVyUXbxukhLeHKV3VVqo7HdoQR9MrRfFmZYotn8ME=
github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
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.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=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI=
github.com/sourcegraph/go-diff v0.5.3/go.mod h1:v9JDtjCE4HHHCZGId75rg8gkKKa98RVjBcBGsVmMmak=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
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.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/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=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
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/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
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/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=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM=
github.com/tetafro/godot v0.4.2/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0=
github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc=
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.12.0/go.mod h1:229t1eWu9UXTPmoUkbpN/fctKPBY4IJoFXQnxHGXy6E=
github.com/valyala/fasthttp v1.14.0/go.mod h1:ol1PCaL0dX20wC0htZ7sYCsvCYmrouYra0zHzaclZhE=
github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA=
github.com/valyala/fasthttp v1.17.0 h1:P8/koH4aSnJ4xbd0cUUFEGQs3jQqIxoDDyRQrUiAkqg=
github.com/valyala/fasthttp v1.17.0/go.mod h1:jjraHZVbKOXftJfsOYoAjaeygpj5hr8ermTRJNroD7A=
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/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/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=
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=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
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=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
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-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
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/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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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-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=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-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-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-20200602225109-6fdc65e7d980/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-20201107080550-4d91cf3a1aaf h1:kt3wY1Lu5MJAnKTfoMR52Cu4gwvna4VTzNOiT8tY73s=
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/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/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=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
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-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=
golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
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-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=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201121010211-780cb80bd7fb h1:z5+u0pkAUPUWd3taoTialQ2JAMo4Wo1Z3L25U4ZV9r0=
golang.org/x/tools v0.0.0-20201121010211-780cb80bd7fb/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200321224714-0d839f3cf2ed/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200331202046-9d5940d49312/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200428185508-e9a00ec82136/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
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/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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/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/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=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
mvdan.cc/gofumpt v0.0.0-20200513141252-abc0db2c416a/go.mod h1:4q/PlrZKQLU5MowSvCKM3U4xJUPtJ8vKWx7vsWFJ3MI=
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

View File

@ -1,230 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.bundle.js"></script>
<script>
const GRAPH_RES = 100;
var dataSmartphone = [];
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");
d.innerText = message;
oldNode = output.firstChild
output.replaceChild(d, oldNode)
};
document.getElementById("open").onclick = function(evt) {
if (ws && ws.OPEN) {
print("Websocket already open")
return false;
}
ws = new WebSocket("{{.}}");
ws.onopen = function(evt) {
print("OPEN");
}
ws.onclose = function(evt) {
ws = null;
print2("CLOSE");
}
ws.onmessage = function(evt) {
//print2("RESPONSE: " + evt.data);
dataSmartphone.push(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)
//console.log(dat)
//console.log(dat.orientation)
document.getElementById("gyroscope").style.transform = `rotateX(${-((dat.orientation[1]+90)%360)}deg) rotateY(${dat.orientation[0]}deg) rotateZ(${-dat.orientation[2]}deg)`
/*
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.orientation[0])
// 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) {
if (!ws) {
return false;
}
ws.close();
return false;
};
});
</script>
<style>
* { box-sizing: border-box; }
body {
font-family: sans-serif;
background: #000;
color: #fff;
}
.scene {
width: 200px;
height: 200px;
border: 0px solid #CCC;
margin: 160px;
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>
</head>
<body>
<table style="font-size: x-small">
<tr>
<td valign="top" width="50%">
<p>Click "Open" to create a connection to the server.
</p>
<form>
<button id="open">Open</button>
<button id="close">Close</button>
<p style="display: none" >
<input id="input" type="text" value="Hello world!">
<button id="send">Send</button>
</p>
</form>
<div class="scene">
<div id="gyroscope" 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>
</div>
<div style="width: 600px; height: 400px;">
<canvas id="myChart" width="400" height="200"></canvas>
</div>
<script>
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();
};
</script>
</td>
<td valign="top" width="50%">
<div id="output"></div>
</td>
</tr>
</table>
</body>
</html>

View File

@ -1,132 +0,0 @@
package net
import (
"encoding/json"
"fmt"
"git.timovolkmann.de/gyrogpsc/dispatcher"
"git.timovolkmann.de/gyrogpsc/serial_ubx"
"github.com/gorilla/websocket"
"github.com/tidwall/pretty"
"html/template"
"log"
"net"
"net/http"
"os"
)
func echo(d *dispatcher.Dispatcher) func(w http.ResponseWriter, r *http.Request) {
var upgrader = websocket.Upgrader{} // use default options
return func(w http.ResponseWriter, r *http.Request) {
fmt.Println("upgrading to ws")
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
//defer c.Close()
go func() {
for {
if _, _, err := c.NextReader(); err != nil {
c.Close()
break
}
}
}()
dispatcherId, channel := d.Subscribe()
defer d.Unsubscribe(dispatcherId)
for {
log.Println("")
//if err != nil {
// log.Println("read:", err)
// break
//}
cmsg := <-channel
err = c.WriteMessage(websocket.TextMessage, []byte(cmsg))
if err != nil {
log.Println("write:", err)
break
}
}
}
}
func home(w http.ResponseWriter, r *http.Request) {
//var homeTemplate = template.Must(template.New("").ParseFiles("index.html"))
tpl, err := template.ParseFiles("index.html")
if err != nil {
log.Fatalln(err)
}
err = tpl.Execute(w, "ws://"+r.Host+"/echo")
if err != nil {
log.Fatalln(err)
}
}
func NewHttpServer(d *dispatcher.Dispatcher, httpPort string) {
http.HandleFunc("/echo", echo(d))
http.HandleFunc("/", home)
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("."))))
log.Fatal(http.ListenAndServe(httpPort, nil))
}
func SerialUbxCollector(d *dispatcher.Dispatcher, serialPort string) {
r, err := serial_ubx.Setup(serialPort)
if err != nil {
log.Fatalln(err)
}
for {
meas, err := r.NextMeasurement()
if err != nil {
continue
}
fmt.Println(meas)
measjson, err := json.Marshal(meas)
d.Publish(string(measjson))
}
}
func TcpJsonCollector(d *dispatcher.Dispatcher, tcpPort string) {
listener, err := net.Listen("tcp", tcpPort)
if err != nil {
fmt.Println("Error listening:", err.Error())
os.Exit(1)
}
// Close the listener when the application closes.
defer listener.Close()
for {
// Listen for an incoming connection.
conn, err := listener.Accept()
if err != nil {
fmt.Println("Error accepting: ", err.Error())
os.Exit(1)
}
// Handle connections in a new goroutine.
go handleTcpJsonSensorData(conn, d)
}
}
// Handles incoming requests.
func handleTcpJsonSensorData(conn net.Conn, d *dispatcher.Dispatcher) {
defer conn.Close()
// Make a buffer to hold incoming data.
for {
buf := make([]byte, 2048)
// Read the incoming connection into the buffer.
_, err := conn.Read(buf)
if err != nil {
fmt.Println("Error reading:", err.Error())
break
}
json := pretty.Pretty(buf)
fmt.Println(string(json))
d.Publish(string(json))
// Send a response back to person contacting us.
//conn.Write([]byte("success"))
// Close the connection when you're done with it.
}
}

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.

View File

@ -1,124 +0,0 @@
package serial_ubx
import (
"errors"
"fmt"
"git.timovolkmann.de/gyrogpsc/ublox"
"go.bug.st/serial"
"log"
"time"
)
type Measurement struct {
//Timestamp int64 `json:"timestamp"`
Timestamp time.Time `json:"timestamp"`
Position [3]int32 `json:"position"` // Latitude, Longitude, Height
Orientation [3]int32 `json:"orientation"` // Pitch, Roll, Heading
}
type ubxReceiver struct {
decoder *ublox.Decoder
currentMeas Measurement
}
func Setup(portname string) (*ubxReceiver, error) {
mode := &serial.Mode{
BaudRate: 115200,
}
port, err := serial.Open(portname, mode)
if err != nil {
return nil, err
}
return &ubxReceiver{
decoder: ublox.NewDecoder(port),
}, nil
}
func (u *ubxReceiver) Next() (ublox.Message, error) {
return u.decoder.Decode()
}
var (
errNotImplemented = errors.New("message not implemented")
)
// TODO: additional callback with adjustable timing
func (u *ubxReceiver) NextMeasurement() (*Measurement, error) {
msg, err := u.decoder.Decode()
if err != nil {
return nil, err
}
//t := time.Time{}
switch v := msg.(type) {
case *ublox.NavPvt:
t, err := time.Parse(time.RFC3339Nano, formatTime(v.Year_y, v.Month_month, v.Day_d, v.Hour_h, v.Min_min, v.Sec_s, v.Nano_ns))
if err != nil {
log.Println(err)
}
//u.currentMeas.Timestamp = t.UnixNano()
u.currentMeas.Timestamp = t
u.currentMeas.Position[0] = v.Lat_dege7
u.currentMeas.Position[1] = v.Lon_dege7
u.currentMeas.Position[2] = v.Height_mm
fmt.Printf("%T %v\n", *v, *v)
case *ublox.HnrPvt:
t, err := time.Parse(time.RFC3339Nano, formatTime(v.Year_y, v.Month_month, v.Day_d, v.Hour_h, v.Min_min, v.Sec_s, v.Nano_ns))
if err != nil {
log.Println(err)
}
u.currentMeas.Timestamp = t
u.currentMeas.Position[0] = v.Lat_dege7
u.currentMeas.Position[1] = v.Lon_dege7
u.currentMeas.Position[2] = v.Height_mm
fmt.Printf("%T %v\n", *v, *v)
case *ublox.NavAtt:
u.currentMeas.Orientation[0] = v.Pitch_deg
u.currentMeas.Orientation[1] = v.Roll_deg
u.currentMeas.Orientation[2] = v.Heading_deg
fmt.Printf("%T %v\n", *v, *v)
//case *ublox.RawMessage:
// //fmt.Printf("%T %v\n\n", *v, *v)
default:
return nil, errNotImplemented
}
m := u.currentMeas
return &m, nil
}
func formatTime(Year_y uint16, Month_month byte, Day_d byte, Hour_h byte, Min_min byte, Sec_s byte, Nano_ns int32) string {
//Nano_ns *= 1e+3
if Nano_ns < 0 {
Nano_ns += int32(time.Second)
if Sec_s > 0 {
Sec_s--
} else if Min_min > 0 {
Sec_s = 59
Min_min--
} else if Hour_h > 0 {
Sec_s = 59
Min_min = 59
Hour_h--
} else if Day_d > 1 {
Sec_s = 59
Min_min = 59
Hour_h = 23
Day_d--
} // TODO: more cases for exact behavior! good for now...
}
//fmt.Printf("%04d-%02d-%02dT%02d:%02d:%02d.%09dZ00:00\n", Year_y, Month_month, Day_d, Hour_h, Min_min, Sec_s, Nano_ns )
return fmt.Sprintf("%04d-%02d-%02dT%02d:%02d:%02d.%09dZ", Year_y, Month_month, Day_d, Hour_h, Min_min, Sec_s, Nano_ns)
}
func (u *ubxReceiver) Printloop() {
for {
meas, err := u.NextMeasurement()
if err != nil {
continue
}
fmt.Println(meas)
}
}

View File

@ -63,4 +63,4 @@
-0.006618400104343891
],
"tmd3702_proximity proximity sensor": [5, 0, 0]
}
}

1
static/indicators Submodule

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

869
static/iphone.json Normal file
View File

@ -0,0 +1,869 @@
[
{
"accelerometerAccelerationX" : "0.037598",
"accelerometerAccelerationY" : "-0.007950",
"accelerometerAccelerationZ" : "-1.003952",
"accelerometerTimestamp_sinceReboot" : "77700.804899",
"avAudioRecorderAveragePower" : "-34.990307",
"avAudioRecorderPeakPower" : "-30.574509",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"gyroRotationX" : "0.008506",
"gyroRotationY" : "-0.076335",
"gyroRotationZ" : "0.000154",
"gyroTimestamp_sinceReboot" : "77700.794635",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"locationHeadingAccuracy" : "14.876174",
"locationHeadingTimestamp_since1970" : "1607291137.949005",
"locationHeadingX" : "-36.616150",
"locationHeadingY" : "-2.895218",
"locationHeadingZ" : "-25.625000",
"locationMagneticHeading" : "85.920738",
"locationTrueHeading" : "89.001564",
"loggingTime" : "2020-12-06 22:45:37.964 +0100",
"logSampleNr" : "765",
"magnetometerTimestamp_sinceReboot" : "77700.802641",
"magnetometerX" : "187.223969",
"magnetometerY" : "188.439392",
"magnetometerZ" : "-710.010864"
},
{
"accelerometerAccelerationX" : "0.036438",
"accelerometerAccelerationY" : "-0.008789",
"accelerometerAccelerationZ" : "-1.003464",
"accelerometerTimestamp_sinceReboot" : "77700.821012",
"avAudioRecorderAveragePower" : "-35.768673",
"avAudioRecorderPeakPower" : "-30.574509",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"locationHeadingAccuracy" : "14.876176",
"locationHeadingTimestamp_since1970" : "1607291137.968956",
"locationHeadingX" : "-36.500809",
"locationHeadingY" : "-2.983276",
"locationHeadingZ" : "-25.554626",
"locationMagneticHeading" : "85.920799",
"locationTrueHeading" : "89.001625",
"loggingTime" : "2020-12-06 22:45:37.989 +0100",
"logSampleNr" : "766",
"motionAttitudeReferenceFrame" : "XTrueNorthZVertical",
"motionGravityX" : "0.034999",
"motionGravityY" : "-0.009051",
"motionGravityZ" : "-0.999346",
"motionMagneticFieldCalibrationAccuracy" : "2.000000",
"motionMagneticFieldX" : "-36.500809",
"motionMagneticFieldY" : "-2.983276",
"motionMagneticFieldZ" : "-25.554626",
"motionPitch" : "0.009051",
"motionQuaternionW" : "-0.008792",
"motionQuaternionX" : "-0.017541",
"motionQuaternionY" : "0.004372",
"motionQuaternionZ" : "0.999798",
"motionRoll" : "0.035007",
"motionRotationRateX" : "0.002088",
"motionRotationRateY" : "-0.004357",
"motionRotationRateZ" : "-0.000906",
"motionTimestamp_sinceReboot" : "77700.804614",
"motionUserAccelerationX" : "0.002599",
"motionUserAccelerationY" : "0.001101",
"motionUserAccelerationZ" : "-0.004606",
"motionYaw" : "-3.124165"
},
{
"accelerometerAccelerationX" : "0.033234",
"accelerometerAccelerationY" : "-0.008926",
"accelerometerAccelerationZ" : "-1.002014",
"accelerometerTimestamp_sinceReboot" : "77700.837125",
"avAudioRecorderAveragePower" : "-35.768673",
"avAudioRecorderPeakPower" : "-30.574509",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"gyroRotationX" : "0.008186",
"gyroRotationY" : "-0.076672",
"gyroRotationZ" : "0.000862",
"gyroTimestamp_sinceReboot" : "77700.824603",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"locationHeadingAccuracy" : "14.876178",
"locationHeadingTimestamp_since1970" : "1607291137.989421",
"locationHeadingX" : "-36.537552",
"locationHeadingY" : "-2.889069",
"locationHeadingZ" : "-25.606628",
"locationMagneticHeading" : "85.920822",
"locationTrueHeading" : "89.001648",
"loggingTime" : "2020-12-06 22:45:37.996 +0100",
"logSampleNr" : "767"
},
{
"accelerometerAccelerationX" : "0.034439",
"accelerometerAccelerationY" : "-0.009415",
"accelerometerAccelerationZ" : "-1.006668",
"accelerometerTimestamp_sinceReboot" : "77700.853239",
"avAudioRecorderAveragePower" : "-31.989109",
"avAudioRecorderPeakPower" : "-30.574509",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"locationHeadingAccuracy" : "14.876178",
"locationHeadingTimestamp_since1970" : "1607291138.008792",
"locationHeadingX" : "-36.428513",
"locationHeadingY" : "-2.911438",
"locationHeadingZ" : "-25.462769",
"locationMagneticHeading" : "85.921715",
"locationTrueHeading" : "89.002541",
"loggingTime" : "2020-12-06 22:45:38.012 +0100",
"logSampleNr" : "768",
"motionAttitudeReferenceFrame" : "XTrueNorthZVertical",
"motionGravityX" : "0.034996",
"motionGravityY" : "-0.009039",
"motionGravityZ" : "-0.999347",
"motionMagneticFieldCalibrationAccuracy" : "2.000000",
"motionMagneticFieldX" : "-36.537552",
"motionMagneticFieldY" : "-2.889069",
"motionMagneticFieldZ" : "-25.606628",
"motionPitch" : "0.009039",
"motionQuaternionW" : "-0.008788",
"motionQuaternionX" : "-0.017540",
"motionQuaternionY" : "0.004366",
"motionQuaternionZ" : "0.999798",
"motionRoll" : "0.035005",
"motionRotationRateX" : "0.000459",
"motionRotationRateY" : "-0.000712",
"motionRotationRateZ" : "-0.001314",
"motionTimestamp_sinceReboot" : "77700.834613",
"motionUserAccelerationX" : "-0.000923",
"motionUserAccelerationY" : "0.000250",
"motionUserAccelerationZ" : "-0.003110",
"motionYaw" : "-3.124172"
},
{
"accelerometerAccelerationX" : "0.035385",
"accelerometerAccelerationY" : "-0.012100",
"accelerometerAccelerationZ" : "-1.011292",
"accelerometerTimestamp_sinceReboot" : "77700.869321",
"avAudioRecorderAveragePower" : "-31.288141",
"avAudioRecorderPeakPower" : "-30.269579",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"gyroRotationX" : "0.010996",
"gyroRotationY" : "-0.081939",
"gyroRotationZ" : "0.002051",
"gyroTimestamp_sinceReboot" : "77700.854571",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"loggingTime" : "2020-12-06 22:45:38.029 +0100",
"logSampleNr" : "769",
"magnetometerTimestamp_sinceReboot" : "77700.860777",
"magnetometerX" : "187.028076",
"magnetometerY" : "188.409332",
"magnetometerZ" : "-709.159546"
},
{
"accelerometerAccelerationX" : "0.035950",
"accelerometerAccelerationY" : "-0.011780",
"accelerometerAccelerationZ" : "-1.006134",
"accelerometerTimestamp_sinceReboot" : "77700.885435",
"avAudioRecorderAveragePower" : "-32.153595",
"avAudioRecorderPeakPower" : "-29.939217",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"locationHeadingAccuracy" : "14.876180",
"locationHeadingTimestamp_since1970" : "1607291138.029312",
"locationHeadingX" : "-36.654816",
"locationHeadingY" : "-3.019150",
"locationHeadingZ" : "-24.858765",
"locationMagneticHeading" : "85.921791",
"locationTrueHeading" : "89.002617",
"loggingTime" : "2020-12-06 22:45:38.047 +0100",
"logSampleNr" : "770",
"motionAttitudeReferenceFrame" : "XTrueNorthZVertical",
"motionGravityX" : "0.034864",
"motionGravityY" : "-0.009091",
"motionGravityZ" : "-0.999351",
"motionMagneticFieldCalibrationAccuracy" : "2.000000",
"motionMagneticFieldX" : "-36.654816",
"motionMagneticFieldY" : "-3.019150",
"motionMagneticFieldZ" : "-24.858765",
"motionPitch" : "0.009091",
"motionQuaternionW" : "-0.008783",
"motionQuaternionX" : "-0.017474",
"motionQuaternionY" : "0.004393",
"motionQuaternionZ" : "0.999799",
"motionRoll" : "0.034872",
"motionRotationRateX" : "0.000922",
"motionRotationRateY" : "-0.000872",
"motionRotationRateZ" : "-0.000288",
"motionTimestamp_sinceReboot" : "77700.864581",
"motionUserAccelerationX" : "0.001284",
"motionUserAccelerationY" : "-0.001514",
"motionUserAccelerationZ" : "-0.011895",
"motionYaw" : "-3.124182"
},
{
"accelerometerAccelerationX" : "0.035431",
"accelerometerAccelerationY" : "-0.008698",
"accelerometerAccelerationZ" : "-1.001877",
"accelerometerTimestamp_sinceReboot" : "77700.901548",
"avAudioRecorderAveragePower" : "-32.153595",
"avAudioRecorderPeakPower" : "-29.939217",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"gyroRotationX" : "0.005874",
"gyroRotationY" : "-0.076437",
"gyroRotationZ" : "0.000249",
"gyroTimestamp_sinceReboot" : "77700.884570",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"locationAltitude" : "187.182343",
"locationCourse" : "-1.000000",
"locationFloor" : "-9999",
"locationHeadingAccuracy" : "14.876182",
"locationHeadingTimestamp_since1970" : "1607291138.049074",
"locationHeadingX" : "-36.686508",
"locationHeadingY" : "-3.035675",
"locationHeadingZ" : "-24.890198",
"locationHorizontalAccuracy" : "30.000000",
"locationLatitude" : "49.066370",
"locationLongitude" : "9.135756",
"locationMagneticHeading" : "85.922256",
"locationSpeed" : "0.000000",
"locationTimestamp_since1970" : "1607291137.995236",
"locationTrueHeading" : "89.003082",
"locationVerticalAccuracy" : "4.000000",
"loggingTime" : "2020-12-06 22:45:38.061 +0100",
"logSampleNr" : "771",
"motionAttitudeReferenceFrame" : "XTrueNorthZVertical",
"motionGravityX" : "0.034944",
"motionGravityY" : "-0.009061",
"motionGravityZ" : "-0.999348",
"motionMagneticFieldCalibrationAccuracy" : "2.000000",
"motionMagneticFieldX" : "-36.686508",
"motionMagneticFieldY" : "-3.035675",
"motionMagneticFieldZ" : "-24.890198",
"motionPitch" : "0.009061",
"motionQuaternionW" : "-0.008780",
"motionQuaternionX" : "-0.017514",
"motionQuaternionY" : "0.004377",
"motionQuaternionZ" : "0.999799",
"motionRoll" : "0.034952",
"motionRotationRateX" : "-0.001064",
"motionRotationRateY" : "-0.001599",
"motionRotationRateZ" : "0.000173",
"motionTimestamp_sinceReboot" : "77700.894549",
"motionUserAccelerationX" : "-0.000566",
"motionUserAccelerationY" : "-0.000552",
"motionUserAccelerationZ" : "-0.007701",
"motionYaw" : "-3.124187"
},
{
"accelerometerAccelerationX" : "0.033325",
"accelerometerAccelerationY" : "-0.009155",
"accelerometerAccelerationZ" : "-1.004761",
"accelerometerTimestamp_sinceReboot" : "77700.917661",
"avAudioRecorderAveragePower" : "-32.501255",
"avAudioRecorderPeakPower" : "-29.852339",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"locationHeadingAccuracy" : "14.876183",
"locationHeadingTimestamp_since1970" : "1607291138.068919",
"locationHeadingX" : "-36.387695",
"locationHeadingY" : "-2.955109",
"locationHeadingZ" : "-25.351074",
"locationMagneticHeading" : "85.922218",
"locationTrueHeading" : "89.003044",
"loggingTime" : "2020-12-06 22:45:38.077 +0100",
"logSampleNr" : "772"
},
{
"accelerometerAccelerationX" : "0.035431",
"accelerometerAccelerationY" : "-0.008286",
"accelerometerAccelerationZ" : "-1.005096",
"accelerometerTimestamp_sinceReboot" : "77700.933775",
"avAudioRecorderAveragePower" : "-33.457474",
"avAudioRecorderPeakPower" : "-29.852339",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"gyroRotationX" : "0.004643",
"gyroRotationY" : "-0.075107",
"gyroRotationZ" : "0.001784",
"gyroTimestamp_sinceReboot" : "77700.914538",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"locationHeadingAccuracy" : "14.876183",
"locationHeadingTimestamp_since1970" : "1607291138.088690",
"locationHeadingX" : "-36.463043",
"locationHeadingY" : "-2.722488",
"locationHeadingZ" : "-25.380981",
"locationMagneticHeading" : "85.921844",
"locationTrueHeading" : "89.002670",
"loggingTime" : "2020-12-06 22:45:38.094 +0100",
"logSampleNr" : "773",
"magnetometerTimestamp_sinceReboot" : "77700.918913",
"magnetometerX" : "187.101776",
"magnetometerY" : "188.768005",
"magnetometerZ" : "-709.771484",
"motionAttitudeReferenceFrame" : "XTrueNorthZVertical",
"motionGravityX" : "0.034961",
"motionGravityY" : "-0.009001",
"motionGravityZ" : "-0.999348",
"motionMagneticFieldCalibrationAccuracy" : "2.000000",
"motionMagneticFieldX" : "-36.463043",
"motionMagneticFieldY" : "-2.722488",
"motionMagneticFieldZ" : "-25.380981",
"motionPitch" : "0.009001",
"motionQuaternionW" : "-0.008782",
"motionQuaternionX" : "-0.017522",
"motionQuaternionY" : "0.004348",
"motionQuaternionZ" : "0.999798",
"motionRoll" : "0.034970",
"motionRotationRateX" : "-0.003020",
"motionRotationRateY" : "0.004743",
"motionRotationRateZ" : "0.000997",
"motionTimestamp_sinceReboot" : "77700.924548",
"motionUserAccelerationX" : "0.001217",
"motionUserAccelerationY" : "-0.000520",
"motionUserAccelerationZ" : "-0.003139",
"motionYaw" : "-3.124183"
},
{
"accelerometerAccelerationX" : "0.035904",
"accelerometerAccelerationY" : "-0.009094",
"accelerometerAccelerationZ" : "-1.001312",
"accelerometerTimestamp_sinceReboot" : "77700.949857",
"avAudioRecorderAveragePower" : "-33.457474",
"avAudioRecorderPeakPower" : "-29.852339",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"gyroRotationX" : "0.006907",
"gyroRotationY" : "-0.079628",
"gyroRotationZ" : "0.000950",
"gyroTimestamp_sinceReboot" : "77700.944507",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"loggingTime" : "2020-12-06 22:45:38.109 +0100",
"logSampleNr" : "774"
},
{
"accelerometerAccelerationX" : "0.036209",
"accelerometerAccelerationY" : "-0.010406",
"accelerometerAccelerationZ" : "-1.005539",
"accelerometerTimestamp_sinceReboot" : "77700.965971",
"avAudioRecorderAveragePower" : "-33.588783",
"avAudioRecorderPeakPower" : "-29.852339",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"locationHeadingAccuracy" : "14.876184",
"locationHeadingTimestamp_since1970" : "1607291138.108779",
"locationHeadingX" : "-36.467972",
"locationHeadingY" : "-2.907425",
"locationHeadingZ" : "-25.277100",
"locationMagneticHeading" : "85.922852",
"locationTrueHeading" : "89.003677",
"loggingTime" : "2020-12-06 22:45:38.125 +0100",
"logSampleNr" : "775",
"motionAttitudeReferenceFrame" : "XTrueNorthZVertical",
"motionGravityX" : "0.034963",
"motionGravityY" : "-0.009011",
"motionGravityZ" : "-0.999348",
"motionMagneticFieldCalibrationAccuracy" : "2.000000",
"motionMagneticFieldX" : "-36.392593",
"motionMagneticFieldY" : "-3.084808",
"motionMagneticFieldZ" : "-25.575867",
"motionPitch" : "0.009012",
"motionQuaternionW" : "-0.008775",
"motionQuaternionX" : "-0.017523",
"motionQuaternionY" : "0.004353",
"motionQuaternionZ" : "0.999799",
"motionRoll" : "0.034972",
"motionRotationRateX" : "-0.000240",
"motionRotationRateY" : "-0.000722",
"motionRotationRateZ" : "0.001070",
"motionTimestamp_sinceReboot" : "77700.954516",
"motionUserAccelerationX" : "0.000376",
"motionUserAccelerationY" : "-0.001273",
"motionUserAccelerationZ" : "-0.004177",
"motionYaw" : "-3.124197"
},
{
"accelerometerAccelerationX" : "0.037720",
"accelerometerAccelerationY" : "-0.010132",
"accelerometerAccelerationZ" : "-1.008987",
"accelerometerTimestamp_sinceReboot" : "77700.982084",
"avAudioRecorderAveragePower" : "-34.231152",
"avAudioRecorderPeakPower" : "-29.852339",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"gyroRotationX" : "0.004129",
"gyroRotationY" : "-0.077557",
"gyroRotationZ" : "-0.000793",
"gyroTimestamp_sinceReboot" : "77700.974505",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"locationHeadingAccuracy" : "14.876186",
"locationHeadingTimestamp_since1970" : "1607291138.128763",
"locationHeadingX" : "-36.392593",
"locationHeadingY" : "-3.084808",
"locationHeadingZ" : "-25.575867",
"locationMagneticHeading" : "85.922737",
"locationTrueHeading" : "89.003563",
"loggingTime" : "2020-12-06 22:45:38.141 +0100",
"logSampleNr" : "776",
"magnetometerTimestamp_sinceReboot" : "77700.977079",
"magnetometerX" : "186.895142",
"magnetometerY" : "188.252991",
"magnetometerZ" : "-710.194702"
},
{
"accelerometerAccelerationX" : "0.036057",
"accelerometerAccelerationY" : "-0.008194",
"accelerometerAccelerationZ" : "-1.002792",
"accelerometerTimestamp_sinceReboot" : "77700.998197",
"avAudioRecorderAveragePower" : "-34.738995",
"avAudioRecorderPeakPower" : "-29.852339",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"locationHeadingAccuracy" : "14.876188",
"locationHeadingTimestamp_since1970" : "1607291138.148931",
"locationHeadingX" : "-36.706497",
"locationHeadingY" : "-3.118759",
"locationHeadingZ" : "-25.621033",
"locationMagneticHeading" : "85.923042",
"locationTrueHeading" : "89.003868",
"loggingTime" : "2020-12-06 22:45:38.168 +0100",
"logSampleNr" : "777",
"motionAttitudeReferenceFrame" : "XTrueNorthZVertical",
"motionGravityX" : "0.034977",
"motionGravityY" : "-0.009047",
"motionGravityZ" : "-0.999347",
"motionMagneticFieldCalibrationAccuracy" : "2.000000",
"motionMagneticFieldX" : "-36.706497",
"motionMagneticFieldY" : "-3.118759",
"motionMagneticFieldZ" : "-25.621033",
"motionPitch" : "0.009047",
"motionQuaternionW" : "-0.008772",
"motionQuaternionX" : "-0.017530",
"motionQuaternionY" : "0.004370",
"motionQuaternionZ" : "0.999798",
"motionRoll" : "0.034985",
"motionRotationRateX" : "0.000625",
"motionRotationRateY" : "-0.000442",
"motionRotationRateZ" : "0.000817",
"motionTimestamp_sinceReboot" : "77700.984485",
"motionUserAccelerationX" : "0.001827",
"motionUserAccelerationY" : "-0.000368",
"motionUserAccelerationZ" : "-0.007046",
"motionYaw" : "-3.124204"
},
{
"accelerometerAccelerationX" : "0.034225",
"accelerometerAccelerationY" : "-0.008728",
"accelerometerAccelerationZ" : "-1.000931",
"accelerometerTimestamp_sinceReboot" : "77701.014310",
"avAudioRecorderAveragePower" : "-34.738995",
"avAudioRecorderPeakPower" : "-29.852339",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"gyroRotationX" : "0.003781",
"gyroRotationY" : "-0.076643",
"gyroRotationZ" : "0.000894",
"gyroTimestamp_sinceReboot" : "77701.004474",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"locationHeadingAccuracy" : "14.876190",
"locationHeadingTimestamp_since1970" : "1607291138.168952",
"locationHeadingX" : "-36.616852",
"locationHeadingY" : "-2.912506",
"locationHeadingZ" : "-25.239990",
"locationMagneticHeading" : "85.923080",
"locationTrueHeading" : "89.003906",
"loggingTime" : "2020-12-06 22:45:38.173 +0100",
"logSampleNr" : "778"
},
{
"accelerometerAccelerationX" : "0.033676",
"accelerometerAccelerationY" : "-0.007309",
"accelerometerAccelerationZ" : "-1.001099",
"accelerometerTimestamp_sinceReboot" : "77701.030424",
"avAudioRecorderAveragePower" : "-35.218269",
"avAudioRecorderPeakPower" : "-29.852339",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"loggingTime" : "2020-12-06 22:45:38.189 +0100",
"logSampleNr" : "779",
"motionAttitudeReferenceFrame" : "XTrueNorthZVertical",
"motionGravityX" : "0.035004",
"motionGravityY" : "-0.008995",
"motionGravityZ" : "-0.999347",
"motionMagneticFieldCalibrationAccuracy" : "2.000000",
"motionMagneticFieldX" : "-36.715759",
"motionMagneticFieldY" : "-2.925949",
"motionMagneticFieldZ" : "-25.069214",
"motionPitch" : "0.008995",
"motionQuaternionW" : "-0.008769",
"motionQuaternionX" : "-0.017544",
"motionQuaternionY" : "0.004344",
"motionQuaternionZ" : "0.999798",
"motionRoll" : "0.035013",
"motionRotationRateX" : "-0.003867",
"motionRotationRateY" : "0.001772",
"motionRotationRateZ" : "0.000104",
"motionTimestamp_sinceReboot" : "77701.014453",
"motionUserAccelerationX" : "-0.000825",
"motionUserAccelerationY" : "0.000175",
"motionUserAccelerationZ" : "-0.000272",
"motionYaw" : "-3.124209"
},
{
"accelerometerAccelerationX" : "0.037369",
"accelerometerAccelerationY" : "-0.007599",
"accelerometerAccelerationZ" : "-1.002563",
"accelerometerTimestamp_sinceReboot" : "77701.046537",
"avAudioRecorderAveragePower" : "-35.218269",
"avAudioRecorderPeakPower" : "-29.852339",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"gyroRotationX" : "0.006077",
"gyroRotationY" : "-0.077056",
"gyroRotationZ" : "0.001649",
"gyroTimestamp_sinceReboot" : "77701.034442",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"locationHeadingAccuracy" : "14.876190",
"locationHeadingTimestamp_since1970" : "1607291138.188685",
"locationHeadingX" : "-36.715759",
"locationHeadingY" : "-2.925949",
"locationHeadingZ" : "-25.069214",
"locationMagneticHeading" : "85.923744",
"locationTrueHeading" : "89.004570",
"loggingTime" : "2020-12-06 22:45:38.206 +0100",
"logSampleNr" : "780",
"magnetometerTimestamp_sinceReboot" : "77701.035215",
"magnetometerX" : "187.234955",
"magnetometerY" : "188.355042",
"magnetometerZ" : "-709.823547"
},
{
"accelerometerAccelerationX" : "0.035263",
"accelerometerAccelerationY" : "-0.011368",
"accelerometerAccelerationZ" : "-1.005341",
"accelerometerTimestamp_sinceReboot" : "77701.062620",
"avAudioRecorderAveragePower" : "-35.783520",
"avAudioRecorderPeakPower" : "-29.852339",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"locationHeadingAccuracy" : "14.876191",
"locationHeadingTimestamp_since1970" : "1607291138.208948",
"locationHeadingX" : "-36.388031",
"locationHeadingY" : "-3.009003",
"locationHeadingZ" : "-25.307068",
"locationMagneticHeading" : "85.924210",
"locationTrueHeading" : "89.005035",
"loggingTime" : "2020-12-06 22:45:38.228 +0100",
"logSampleNr" : "781",
"motionAttitudeReferenceFrame" : "XTrueNorthZVertical",
"motionGravityX" : "0.035031",
"motionGravityY" : "-0.009005",
"motionGravityZ" : "-0.999346",
"motionMagneticFieldCalibrationAccuracy" : "2.000000",
"motionMagneticFieldX" : "-36.388031",
"motionMagneticFieldY" : "-3.009003",
"motionMagneticFieldZ" : "-25.307068",
"motionPitch" : "0.009005",
"motionQuaternionW" : "-0.008762",
"motionQuaternionX" : "-0.017557",
"motionQuaternionY" : "0.004350",
"motionQuaternionZ" : "0.999798",
"motionRoll" : "0.035040",
"motionRotationRateX" : "-0.002688",
"motionRotationRateY" : "0.001089",
"motionRotationRateZ" : "0.000186",
"motionTimestamp_sinceReboot" : "77701.044452",
"motionUserAccelerationX" : "0.002337",
"motionUserAccelerationY" : "0.001406",
"motionUserAccelerationZ" : "-0.003218",
"motionYaw" : "-3.124224"
},
{
"accelerometerAccelerationX" : "0.033936",
"accelerometerAccelerationY" : "-0.008270",
"accelerometerAccelerationZ" : "-1.008591",
"accelerometerTimestamp_sinceReboot" : "77701.078733",
"avAudioRecorderAveragePower" : "-35.894341",
"avAudioRecorderPeakPower" : "-29.852339",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"gyroRotationX" : "0.006742",
"gyroRotationY" : "-0.078071",
"gyroRotationZ" : "0.000371",
"gyroTimestamp_sinceReboot" : "77701.064441",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"locationHeadingAccuracy" : "14.876193",
"locationHeadingTimestamp_since1970" : "1607291138.229093",
"locationHeadingX" : "-36.477371",
"locationHeadingY" : "-3.081924",
"locationHeadingZ" : "-25.343933",
"locationMagneticHeading" : "85.924126",
"locationTrueHeading" : "89.004951",
"loggingTime" : "2020-12-06 22:45:38.238 +0100",
"logSampleNr" : "782"
},
{
"accelerometerAccelerationX" : "0.034683",
"accelerometerAccelerationY" : "-0.010590",
"accelerometerAccelerationZ" : "-1.008743",
"accelerometerTimestamp_sinceReboot" : "77701.094846",
"avAudioRecorderAveragePower" : "-36.771626",
"avAudioRecorderPeakPower" : "-29.852339",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"locationHeadingAccuracy" : "14.876195",
"locationHeadingTimestamp_since1970" : "1607291138.248836",
"locationHeadingX" : "-36.430481",
"locationHeadingY" : "-2.969254",
"locationHeadingZ" : "-25.259521",
"locationMagneticHeading" : "85.924248",
"locationTrueHeading" : "89.005074",
"loggingTime" : "2020-12-06 22:45:38.254 +0100",
"logSampleNr" : "783",
"magnetometerTimestamp_sinceReboot" : "77701.093351",
"magnetometerX" : "186.871170",
"magnetometerY" : "188.533890",
"magnetometerZ" : "-709.817078",
"motionAttitudeReferenceFrame" : "XTrueNorthZVertical",
"motionGravityX" : "0.035061",
"motionGravityY" : "-0.009031",
"motionGravityZ" : "-0.999344",
"motionMagneticFieldCalibrationAccuracy" : "2.000000",
"motionMagneticFieldX" : "-36.430481",
"motionMagneticFieldY" : "-2.969254",
"motionMagneticFieldZ" : "-25.259521",
"motionPitch" : "0.009031",
"motionQuaternionW" : "-0.008761",
"motionQuaternionX" : "-0.017572",
"motionQuaternionY" : "0.004362",
"motionQuaternionZ" : "0.999798",
"motionRoll" : "0.035070",
"motionRotationRateX" : "0.002348",
"motionRotationRateY" : "0.000247",
"motionRotationRateZ" : "0.000621",
"motionTimestamp_sinceReboot" : "77701.074420",
"motionUserAccelerationX" : "-0.000393",
"motionUserAccelerationY" : "-0.000308",
"motionUserAccelerationZ" : "-0.008239",
"motionYaw" : "-3.124226"
},
{
"accelerometerAccelerationX" : "0.033768",
"accelerometerAccelerationY" : "-0.008774",
"accelerometerAccelerationZ" : "-1.005692",
"accelerometerTimestamp_sinceReboot" : "77701.110960",
"avAudioRecorderAveragePower" : "-36.771626",
"avAudioRecorderPeakPower" : "-29.852339",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"gyroRotationX" : "0.002831",
"gyroRotationY" : "-0.076265",
"gyroRotationZ" : "0.001715",
"gyroTimestamp_sinceReboot" : "77701.094409",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"locationHeadingAccuracy" : "14.876197",
"locationHeadingTimestamp_since1970" : "1607291138.268665",
"locationHeadingX" : "-36.766403",
"locationHeadingY" : "-2.885361",
"locationHeadingZ" : "-25.336365",
"locationMagneticHeading" : "85.924141",
"locationTrueHeading" : "89.004967",
"loggingTime" : "2020-12-06 22:45:38.270 +0100",
"logSampleNr" : "784",
"motionAttitudeReferenceFrame" : "XTrueNorthZVertical",
"motionGravityX" : "0.035022",
"motionGravityY" : "-0.008936",
"motionGravityZ" : "-0.999347",
"motionMagneticFieldCalibrationAccuracy" : "2.000000",
"motionMagneticFieldX" : "-36.766403",
"motionMagneticFieldY" : "-2.885361",
"motionMagneticFieldZ" : "-25.336365",
"motionPitch" : "0.008936",
"motionQuaternionW" : "-0.008761",
"motionQuaternionX" : "-0.017552",
"motionQuaternionY" : "0.004315",
"motionQuaternionZ" : "0.999798",
"motionRoll" : "0.035031",
"motionRotationRateX" : "-0.001589",
"motionRotationRateY" : "-0.001877",
"motionRotationRateZ" : "-0.000561",
"motionTimestamp_sinceReboot" : "77701.104388",
"motionUserAccelerationX" : "0.000745",
"motionUserAccelerationY" : "0.000620",
"motionUserAccelerationZ" : "-0.007642",
"motionYaw" : "-3.124223"
},
{
"accelerometerAccelerationX" : "0.035889",
"accelerometerAccelerationY" : "-0.009491",
"accelerometerAccelerationZ" : "-1.000381",
"accelerometerTimestamp_sinceReboot" : "77701.127073",
"avAudioRecorderAveragePower" : "-37.050854",
"avAudioRecorderPeakPower" : "-29.852339",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"loggingTime" : "2020-12-06 22:45:38.290 +0100",
"logSampleNr" : "785"
},
{
"accelerometerAccelerationX" : "0.035278",
"accelerometerAccelerationY" : "-0.008865",
"accelerometerAccelerationZ" : "-1.004211",
"accelerometerTimestamp_sinceReboot" : "77701.143186",
"avAudioRecorderAveragePower" : "-37.257305",
"avAudioRecorderPeakPower" : "-29.852339",
"batteryLevel" : "0.660000",
"batteryState" : "1",
"deviceID" : "my_iOS_device",
"deviceOrientation" : "5",
"gyroRotationX" : "0.001609",
"gyroRotationY" : "-0.076717",
"gyroRotationZ" : "0.001673",
"gyroTimestamp_sinceReboot" : "77701.124377",
"identifierForVendor" : "1B07EE58-F14E-4E47-ABF8-7E919F119960",
"IP_en0" : "10.41.8.38",
"IP_pdp_ip0" : "100.78.225.228",
"label" : "0",
"locationHeadingAccuracy" : "14.876198",
"locationHeadingTimestamp_since1970" : "1607291138.288638",
"locationHeadingX" : "-36.884933",
"locationHeadingY" : "-3.087875",
"locationHeadingZ" : "-25.284058",
"locationMagneticHeading" : "85.924454",
"locationTrueHeading" : "89.005280",
"loggingTime" : "2020-12-06 22:45:38.302 +0100",
"logSampleNr" : "786",
"motionAttitudeReferenceFrame" : "XTrueNorthZVertical",
"motionGravityX" : "0.035028",
"motionGravityY" : "-0.008846",
"motionGravityZ" : "-0.999347",
"motionMagneticFieldCalibrationAccuracy" : "2.000000",
"motionMagneticFieldX" : "-36.644531",
"motionMagneticFieldY" : "-2.917130",
"motionMagneticFieldZ" : "-25.302368",
"motionPitch" : "0.008846",
"motionQuaternionW" : "-0.008759",
"motionQuaternionX" : "-0.017555",
"motionQuaternionY" : "0.004270",
"motionQuaternionZ" : "0.999798",
"motionRoll" : "0.035037",
"motionRotationRateX" : "-0.003860",
"motionRotationRateY" : "-0.000956",
"motionRotationRateZ" : "-0.001445",
"motionTimestamp_sinceReboot" : "77701.134387",
"motionUserAccelerationX" : "0.002051",
"motionUserAccelerationY" : "0.000881",
"motionUserAccelerationZ" : "-0.002896",
"motionYaw" : "-3.124227"
}
]

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

@ -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

@ -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`
}
}

107
static/scripts/map.js Normal file
View File

@ -0,0 +1,107 @@
/**
* 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';
let map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/satellite-v9',
zoom: 0
});
//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: [
{
type: "Feature",
geometry: {
type: "LineString",
coordinates: []
}
}]
}
/**
* Set up the map with its data sources and its different layers.
*/
map.on('load', function () {
map.addSource('routeTCP', { 'type': 'geojson', 'data': emptyTCP })
map.addSource('routeSERIAL', { 'type': 'geojson', 'data': emptySERIAL })
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 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);
}
/**
* 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);
}

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();
}

263
static/scripts/websocket.js Normal file
View File

@ -0,0 +1,263 @@
// 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) {
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(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);
}
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);
}
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);
}
document.getElementById("tracking state").innerHTML = "Tracking state: LIVE"
checkBoxSmartphone.disabled = true;
checkBoxUblox.disabled = true;
return false;
};
document.getElementById("close").onclick = function(evt) {
if (!ws) {
return false;
}
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;
}

244
storage/kvstore.go Normal file
View File

@ -0,0 +1,244 @@
package storage
import (
"encoding/json"
"errors"
"git.timovolkmann.de/gyrogpsc/core"
"github.com/dgraph-io/badger/v2"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
"os"
"path/filepath"
"time"
)
// Must implement Storer
type badgerStore struct {
trackingsDb *badger.DB
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)
if _, err := os.Stat(filepath.Join(dir, "_db")); os.IsNotExist(err) {
os.Mkdir(filepath.Join(dir, "_db"), os.ModePerm)
}
bs := &badgerStore{}
err := bs.openDBs()
if err != nil {
logrus.Error(err)
}
return bs
}
func (r *badgerStore) openDBs() error {
var err error
r.trackingsDb, err = badger.Open(badger.DefaultOptions("_db/trackings"))
if err != nil {
return err
}
r.sensordatDb, err = badger.Open(badger.DefaultOptions("_db/sensor"))
return err
}
func (r *badgerStore) isDbClosed() bool {
return r.trackingsDb.IsClosed() || r.sensordatDb.IsClosed()
}
func (r *badgerStore) Save(tr core.Tracking) error {
if ok := r.isDbClosed(); ok {
logrus.Error("unable to write to database. database closed!")
err := r.openDBs()
if err != nil {
return err
}
//return badger.ErrDBClosed
}
uid, err := tr.UUID.MarshalText()
if err != nil {
logrus.Error(err, tr)
}
logrus.Infoln("save tracking:", tr.TimeCreated.Format(time.RFC3339Nano))
meta, err := json.Marshal(tr.TrackingMetadata)
if err != nil {
logrus.Error(err, tr)
return err
}
err = r.sensordatDb.Update(func(txn *badger.Txn) error {
for _, v := range tr.Data {
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.Timestamp.Format(time.RFC3339Nano))
//logrus.Traceln(string(pretty.Pretty(j)))
if err2 != nil {
return err2
}
err2 = txn.Set(k, j)
if err2 != nil {
logrus.Warn(err2)
}
}
return nil
})
if err != nil {
logrus.Error(err, tr)
}
err = r.trackingsDb.Update(func(txn *badger.Txn) error {
logrus.Debugln("save tracking metadata k/v:\n", string(uid), string(meta))
err := txn.Set(uid, meta)
return err
})
if err != nil {
logrus.Error(err, tr)
return err
}
dr := 0.5
_ = r.trackingsDb.RunValueLogGC(dr)
_ = r.sensordatDb.RunValueLogGC(dr)
logrus.Info("sucessfully saved tracking")
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 {
opts := badger.DefaultIteratorOptions
it := txn.NewIterator(opts)
defer it.Close()
for it.Rewind(); it.Valid(); it.Next() {
item := it.Item()
el := core.TrackingMetadata{}
err2 := item.Value(func(val []byte) error {
logrus.Debugln(string(val))
err3 := json.Unmarshal(val, &el)
return err3
})
if err2 != nil {
logrus.Warn(err2)
}
result = append(result, el)
}
return nil
})
if err != nil {
return nil, err
}
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 {
logrus.Error("unable to read from database. database closed!")
err := r.openDBs()
if err != nil {
return nil, err
}
}
t := &core.Tracking{
TrackingMetadata: core.TrackingMetadata{},
//Records: []core.recordPair{},
//Data: nil,
}
err := r.trackingsDb.View(func(txn *badger.Txn) error {
item, err2 := txn.Get([]byte(id.String()))
if err2 != nil {
return err2
}
err2 = item.Value(func(val []byte) error {
err3 := json.Unmarshal(val, &t.TrackingMetadata)
return err3
})
return err2
})
if err != nil {
logrus.Error(err)
}
err = r.sensordatDb.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.Prefix = []byte(id.String())
it := txn.NewIterator(opts)
defer it.Close()
for it.Rewind(); it.Valid(); it.Next() {
item := it.Item()
_, source, recTime := unmarshalDataKey(item.Key())
el := core.SensorData{}
el.Timestamp = recTime
el.SetSource(source)
err2 := item.Value(func(val []byte) error {
//logrus.Traceln(string(val))
err3 := json.Unmarshal(val, &el)
//logrus.Traceln(err3, el)
return err3
})
if err2 != nil {
logrus.Warn(err2)
}
t.Data = append(t.Data, el)
}
return nil
})
if err != nil {
logrus.Error(err)
}
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
switch source {
case core.SOURCE_TCP:
i = "1"
case core.SOURCE_SERIAL:
i = "2"
}
middle := []byte(i)
suffix := []byte(timestamp.Format(time.RFC3339Nano))
if timestamp.IsZero() {
err := errors.New("zero value detected")
logrus.Errorln("unable to create key", err)
}
logrus.Traceln("save as:", string(prefix), string(middle), string(suffix))
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) {
prefix := string(key[:36])
suffix := string(key[37:])
middle := string(key[36:37])
var source core.SourceId
switch middle {
case "1":
source = core.SOURCE_TCP
case "2":
source = core.SOURCE_SERIAL
}
uid, err := uuid.Parse(prefix)
if err != nil {
logrus.Errorln("corrupted key", err)
}
timestamp, err := time.Parse(time.RFC3339Nano, suffix)
if err != nil {
logrus.Errorln("corrupted key", err)
}
return uid, source, timestamp
}

187
templates/index.html Normal file
View File

@ -0,0 +1,187 @@
<!DOCTYPE html>
<html>
<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"/>
<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="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>
<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="100%">
<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 left-col">
<div class="sceneMap">
<div id="map"></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 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="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.4/Chart.min.js"></script>
<script src="static/scripts/accuracy.js"></script>
</body>
</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
@ -14,17 +17,17 @@ import (
"io"
)
// A Decoder scans an io stream into UBX (0xB5-0x62 separated) or NMEA ("$xxx,,,,*FF\r\n") frames.
// A decoder scans an io stream into UBX (0xB5-0x62 separated) or NMEA ("$xxx,,,,*FF\r\n") frames.
// If you have an unmixed stream of NMEA-only data you can use nmea.Decode() on bufio.Scanner.Bytes() directly.
type Decoder struct {
type decoder struct {
s *bufio.Scanner
}
// NewDecoder creates a new bufio Scanner with a splitfunc that can handle both UBX and NMEA frames.
func NewDecoder(r io.Reader) *Decoder {
func NewDecoder(r io.Reader) *decoder {
d := bufio.NewScanner(r)
d.Split(splitFunc)
return &Decoder{s: d}
return &decoder{s: d}
}
// Assume we're either at the start of an NMEA sentence or at the start of a UBX message
@ -74,8 +77,8 @@ func splitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) {
return 1 + i1, nil, nil
}
// Decode reads on NMEA or UBX frame and calls DecodeUbx accordingly to parse the message. Skips NMEA.
func (d *Decoder) Decode() (msg Message, err error) {
// Decode reads on NMEA or UBX frame and calls decodeUbx accordingly to parse the message, while skipping NMEA.
func (d *decoder) Decode() (msg interface{}, err error) {
if !d.s.Scan() {
if err = d.s.Err(); err == nil {
err = io.EOF
@ -85,11 +88,10 @@ func (d *Decoder) Decode() (msg Message, err error) {
switch d.s.Bytes()[0] {
case '$':
//fmt.Println("NMEA message: skipping!")
return nil, err
return nil, errors.New("NMEA not implemented")
//return nmea.Decode(d.s.Bytes())
case 0xB5:
return DecodeUbx(d.s.Bytes())
return decodeUbx(d.s.Bytes())
}
panic("impossible frame")
}
@ -99,7 +101,7 @@ var (
errInvalidChkSum = errors.New("invalid UBX checksum")
)
func DecodeUbx(frame []byte) (msg Message, err error) {
func decodeUbx(frame []byte) (msg Message, err error) {
buf := bytes.NewReader(frame)
@ -149,7 +151,7 @@ func DecodeUbx(frame []byte) (msg Message, err error) {
if msg != nil {
err = binary.Read(buf, binary.LittleEndian, msg)
} else {
msg = &RawMessage{ClassID: header.ClassID, Data: append([]byte(nil), frame[6:len(frame)-2]...)}
msg = &RawMessage{classID: header.ClassID, Data: append([]byte(nil), frame[6:len(frame)-2]...)}
}
//fmt.Println(msg)

View File

@ -1,15 +1,15 @@
package ublox
type Message interface {
classID() uint16
ClassID() uint16
}
type RawMessage struct {
ClassID uint16
classID uint16
Data []byte
}
func (msg *RawMessage) classID() uint16 { return msg.ClassID }
func (msg *RawMessage) ClassID() uint16 { return msg.classID }
type NavPvt struct {
ITOW_ms uint32 // - GPS time of week of the navigation epoch. See the description of iTOW for details.
@ -47,7 +47,7 @@ type NavPvt struct {
MagAcc_deg2e uint16 // 1e-2 Magnetic declination accuracy. Only supported in ADR 4.10 and later.
}
func (NavPvt) classID() uint16 { return 0x0701 }
func (NavPvt) ClassID() uint16 { return 0x0701 }
type HnrPvt struct {
ITOW_ms uint32 // - GPS time of week of the navigation epoch. See the description of iTOW for details.
@ -77,7 +77,7 @@ type HnrPvt struct {
Reserved1 [4]byte // - Reserved
}
func (HnrPvt) classID() uint16 { return 0x0028 }
func (HnrPvt) ClassID() uint16 { return 0x0028 }
type NavAtt struct {
ITOW_ms uint32 // - GPS time of week of the navigation epoch. See the description of iTOW for details.
@ -91,7 +91,7 @@ type NavAtt struct {
AccHeading_deg uint32
}
func (NavAtt) classID() uint16 { return 0x0501 }
func (NavAtt) ClassID() uint16 { return 0x0501 }
//go:generate stringer -output=strings_navpvt.go -trimprefix NavPVT -type=NavPVTFixType,NavPVTValid,NavPVTFlags,NavPVTFlags2,NavPVTFlags3

260
web/http.go Normal file
View File

@ -0,0 +1,260 @@
package web
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.TrackingService, sub core.Subscriber, c *core.Configuration) {
app := fiber.New(fiber.Config{
Views: fiberTemplateEngine(c),
})
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)))
// 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("/:trackingId", LoadTrackingHandler(s, c)) // Gets Tracking Metadata and loads sensorRecords from storage.
logrus.Fatal(app.Listen(c.Webserver.Port))
}
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)
if err != nil {
logrus.Error(err)
ctx.Status(404).JSON(err)
return err
}
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
}
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)
return err2
}
return nil
}
}
func allTrackingsHandler(s *core.TrackingService, c *core.Configuration) fiber.Handler {
return func(ctx *fiber.Ctx) error {
trackings, err := s.AllTrackings()
prepres := make(map[string]interface{})
prepres["data"] = trackings
if err != nil {
prepres["error"] = err.Error()
}
err2 := ctx.JSON(prepres)
if err2 != nil {
ctx.Status(500).JSON(err2)
return err2
}
return nil
}
}
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")
if ser == "true" {
collecs = append(collecs, core.SERIAL)
} else if ser != "false" && ser != "" {
collecs = append(collecs, core.CollectorType(ser)) // TODO: allow passing serial port as url parameter
}
tcp := ctx.Query("tcp", "true")
logrus.Debugln("query values: serial/tcp", ser, tcp)
if tcp == "true" {
collecs = append(collecs, core.TCP)
}
res, err := s.StartLivetracking(collecs...)
prepres := make(map[string]interface{})
prepres["tracking_state"] = res
prepres["data"] = collecs
if err != nil {
prepres["error"] = err.Error()
}
if len(collecs) == 0 {
e := errors.New("attention! no collectors running. start a new pipeline")
prepres["error"] = e.Error()
}
err2 := ctx.JSON(prepres)
if err2 != nil {
ctx.Status(500).JSON(err2)
return err2
}
return nil
}
}
func startRecordingHandler(s *core.TrackingService, c *core.Configuration) fiber.Handler {
return func(ctx *fiber.Ctx) error {
rec, err := s.StartRecord()
prepres := make(map[string]interface{})
prepres["tracking_state"] = "RECORD"
prepres["data"] = rec
if err != nil {
prepres["error"] = err.Error()
}
err2 := ctx.JSON(prepres)
if err2 != nil {
ctx.Status(500).JSON(err2)
return err2
}
return nil
}
}
func stopRecordingHandler(s *core.TrackingService, c *core.Configuration) fiber.Handler {
return func(ctx *fiber.Ctx) error {
rec, err := s.StopRecord()
prepres := make(map[string]interface{})
prepres["tracking_state"] = "LIVE"
prepres["data"] = rec
if err != nil {
prepres["error"] = err.Error()
}
err2 := ctx.JSON(prepres)
if err2 != nil {
ctx.Status(500).JSON(err2)
return err2
}
return nil
}
}
func stopAllHandler(s *core.TrackingService, c *core.Configuration) fiber.Handler {
return func(ctx *fiber.Ctx) error {
rec, err := s.StopAll()
prepres := make(map[string]interface{})
prepres["tracking_state"] = "STOPPED"
prepres["data"] = rec
if err != nil {
prepres["error"] = err.Error()
}
err2 := ctx.JSON(prepres)
if err2 != nil {
ctx.Status(500).JSON(err2)
return err2
}
return nil
}
}
func createFiberWebsocketHandler(s core.Subscriber) func(conn *websocket.Conn) {
return func(c *websocket.Conn) {
logrus.Info("new websocket client")
// Handle and discard inbound messages
go func() {
for {
if _, _, err := c.NextReader(); err != nil {
c.Close()
break
}
}
}()
dispatcherId, channel := s.Subscribe()
defer s.Unsubscribe(dispatcherId)
for {
cmsg := <-channel
logrus.Traceln("write to ws:", cmsg)
err := c.WriteMessage(websocket.TextMessage, []byte(cmsg))
if err != nil {
logrus.Infoln("close websocket connection", err)
c.Close()
break
}
}
}
}
func fiberTemplateEngine(c *core.Configuration) *html.Engine {
// Create a new engine by passing the template folder
// and template extension using <engine>.New(dir, ext string)
engine := html.New("./templates", ".html")
// We also support the http.FileSystem interface
// See examples below to load templates from embedded files
// engine := html.NewFileSystem(http.Dir("./views"), ".html")
// Reload the templates on each render, good for development
//engine.Reload(strings.ToLower(c.Debuglevel) == "debug") // Optional. Default: false
engine.Reload(true) // Optional. Default: false
// Debug will print each template that is parsed, good for debugging
//engine.Debug(strings.ToLower(c.Debuglevel) == "debug") // Optional. Default: false
engine.Debug(true) // Optional. Default: false
// Layout defines the variable name that is used to yield templates within layouts
//engine.Layout("embed") // Optional. Default: "embed"
// Delims sets the action delimiters to the specified strings
//engine.Delims("{{", "}}") // Optional. Default: engine delimiters
// AddFunc adds a function to the template's global function map.
//engine.AddFunc("greet", func(name string) string {
// return "Hello, " + name + "!"
//})
return engine
}
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)
}