84 Commits

Author SHA1 Message Date
c659a98587 Prevent multiple close of channel
Some checks reported errors
Pyxis/orion/pipeline/head Something is wrong with the build of this commit
LaCanne/orion/pipeline/head Something is wrong with the build of this commit
2021-01-26 18:01:06 +01:00
e7bec24f0f Use ReachView 2.24.X broadcast API
Some checks failed
Pyxis/orion/pipeline/head There was a failure building this commit
2020-11-26 17:00:13 +01:00
35536d9047 Reboot module after configuration 2020-11-26 16:59:17 +01:00
825873b698 Fix rover configuration
Some checks failed
Pyxis/orion/pipeline/head There was a failure building this commit
2020-11-23 16:27:02 +01:00
e3cc7ab2b1 Add IsAlive() method on client 2020-11-23 16:23:03 +01:00
420c3164d6 Fix lint task 2020-11-23 11:17:00 +01:00
e06aa5129a Upgrade to ReachView v2.24.0 2020-11-23 11:15:30 +01:00
b8a07953dd Add new command to do a basic JSON dump of a Reachview module configuration
Some checks reported errors
Pyxis/orion/develop Something is wrong with the build of this commit
2019-08-18 20:32:19 +02:00
98e33af1e5 Migrate to latest Reachview new API evolution
Tested with Reachview v2.18.1
2019-08-18 20:30:35 +02:00
c7d4215be8 Add release target to generate compiled version archive 2019-03-27 18:50:35 +01:00
8c6c0e12b2 Fix module reboot in updater and reachview mode 2019-02-26 20:39:03 +01:00
e63d7b6f97 Mutualise RebootNow() method on Client 2019-02-12 11:12:50 +01:00
a1a417b05b Fix typo 2019-02-12 10:19:24 +01:00
030bb818e1 Add base manual position update example 2018-12-06 16:33:04 +01:00
668a33a1a1 Fix reach services discovery 2018-12-06 16:31:23 +01:00
4da7f14564 Add method to stream modules status 2018-10-16 19:07:35 +02:00
187d53fd29 Fix Reach services discovery
- Use github.com/grandcat/zeroconf lib instead of github.com/oleksandr/bonjour
2018-10-16 15:23:16 +02:00
9bd20331ef Add service discovery helper
Use MDNS-SD to discover ReachRS services on the local network
2018-10-09 14:05:38 +02:00
fd8657a495 Disable golang modules when executing linters 2018-09-26 16:02:43 +02:00
599c4cf1d5 Merge branch 'feature/reachview-apply-conf' into develop 2018-09-26 15:57:01 +02:00
dc4dd7a9fb Lint: disable lll 2018-09-26 15:56:46 +02:00
854052bc84 Example: simple cli for automatic configuration of rover/base modules 2018-09-26 15:56:06 +02:00
e4bb33e895 CI: only lint modified files 2018-09-26 12:08:29 +02:00
9367e5e79e Use context.Context to provide timeout detection to websocket calls 2018-09-26 12:05:55 +02:00
1dcd03e455 Use pointers to create partial configuration updates 2018-09-25 17:28:12 +02:00
70ddfe49ed Merge branch 'develop' of https://forge.cadoles.com/Pyxis/orion into develop 2018-09-21 17:00:59 +02:00
364dfecb31 Cleaning code to please the linter
Added feature for #145
Fix bug #145
2018-09-21 17:00:11 +02:00
cf58d86eb0 Remove gometalinter install script 2018-09-21 16:56:32 +02:00
f769ca382e Merge branch 'feature/reachview-api' into develop 2018-09-21 16:07:56 +02:00
1a04caafdf Implements full configuration model 2018-09-21 16:07:38 +02:00
6e9df8d386 Refactor reach package
- Rename reach package to emlid
- Create generic Reach websocket client
- Add 2 new subpackages 'updater' and 'reachview' to provides specific
API
2018-09-21 16:07:38 +02:00
77a779aebe Provides a pre-commit git hook to lint Go files 2018-09-21 16:07:38 +02:00
2c7aad91c6 Use golangci-lint instead of gometalinter 2018-09-21 16:07:38 +02:00
f67724dc3a Add another checkpoint to ReachView.Configuration() test 2018-09-21 16:07:38 +02:00
e3672cc6d7 Add basic Configuration() method 2018-09-21 16:07:38 +02:00
ff13693f69 Add ReachView client 2018-09-21 16:07:38 +02:00
ba3d85f48b Create generic method to implements the request/response pattern 2018-09-21 16:07:38 +02:00
a44e40eea2 Merge branch 'feature/uciClient' into develop 2018-09-21 16:05:16 +02:00
17487dd7a6 Spliting complex function parseWifiCells 2018-09-21 15:42:48 +02:00
b34ccd3dee Improving network_test.go ...
This way we don't need a real wifi card
2018-09-21 14:36:06 +02:00
1dbb62c490 Add /proc/net/wireless sample 2018-09-21 14:33:17 +02:00
ec26c74ae8 Spliting "complex" methods to please go linter ! 2018-09-21 13:42:43 +02:00
de0255fd03 Cleaning lint issue : openwrt/wifi.go:39:⚠️ declaration of res shadows declaration at openwrt/wifi.go:31 (vetshadow) 2018-09-21 13:42:43 +02:00
3f36175f1a Cleaning test data 2018-09-21 13:42:43 +02:00
b680e256b9 Updating executor to remove old util.go
Adding new Network interaction "class"
2018-09-21 13:42:43 +02:00
735bc8d19e Adding DHCP Client and new Wifi stack 2018-09-21 13:42:43 +02:00
e1f0c68630 Object oriented UCI + Executor interface 2018-09-21 13:42:43 +02:00
b8f0a554c1 Adding begin of openwrt manipulation module 2018-09-21 13:42:43 +02:00
1aa77c90ab Add Jenkinsfile for continuous integration 2018-09-21 10:13:08 +02:00
3c8f85dcff Lint: sort struct attributes to reduce memory usage 2018-09-20 15:46:32 +02:00
3adfc2a5bb Remove redundant return 2018-09-20 15:45:52 +02:00
425d139b1c Fix error event unbinding 2018-09-20 15:44:34 +02:00
a968004d4e Lint: sort struct attributes to reduce memory usage 2018-09-20 15:43:34 +02:00
5f6dcd2253 Rename ConnectToWifiNetwork() to JoinWifiNetwork() for brevety and clarity 2018-09-20 12:08:20 +02:00
13267b2d5a Rename TimeSyncStatus() to TimeSynced() for API clarity 2018-09-20 11:36:48 +02:00
6534c51fe4 Rename SavedWifiNetworks() to WifiNetworks() for consistency 2018-09-20 11:33:51 +02:00
5008dfbd56 Add Updater CLI example 2018-09-20 11:22:03 +02:00
964e1d713e Allow RebootNow() to wait for disconnection 2018-09-20 11:20:35 +02:00
f24f697a3d Allow ConnectToWifiNetwork() to wait for disconnection 2018-09-20 10:59:39 +02:00
52bac70174 Merge branch 'feature/reach-updater-client' into develop 2018-09-19 17:47:49 +02:00
ee3113ed80 Add package documentation and usage example of UpdaterClient 2018-09-19 17:45:38 +02:00
582e362aae Add WEP wifi network security 2018-09-19 17:25:11 +02:00
403e14c320 Rename OPKGUpdate() to Update() 2018-09-19 17:24:50 +02:00
2d0c3d9de9 Fix flag message to run updater integration tests 2018-09-19 17:24:17 +02:00
998f718354 Add doc command to Makefile 2018-09-19 17:23:28 +02:00
dd0a466bd3 Namespaces updaters API in its own client 2018-09-19 17:13:45 +02:00
48db0458c0 Add OPKGUpdate() API 2018-09-19 16:30:37 +02:00
fc4e50bf08 Use internal logger to display results info 2018-09-19 15:54:11 +02:00
1e16779c52 Add ReachViewVersion() API 2018-09-19 15:53:40 +02:00
055ee0000d Add ReceiverUpradeAvailable() API 2018-09-19 15:46:15 +02:00
1bf205347e Add TimeSyncStatus() API 2018-09-19 15:35:56 +02:00
23aee0fb68 Add flag to execute integration tests 2018-09-19 15:29:44 +02:00
505d462352 Remove Probe() method 2018-09-19 13:04:56 +02:00
a2d1c675d7 WiFi networks management API 2018-09-19 13:00:08 +02:00
e7831091ad Restrict linting domain 2018-09-19 10:33:50 +02:00
55e74355bf Update forge.cadoles.com/Pyxis/golang-socketio dep 2018-09-19 10:26:55 +02:00
68371cb7c7 Add reach.Option comments 2018-09-19 10:02:09 +02:00
cd27332c4f Base de client ReachRS fonctionnelle 2018-09-19 10:02:09 +02:00
267017bdc1 Adding begin of Golang setupKit implementation 2018-09-18 16:32:58 +02:00
2421071da6 Adding bash script to connect a wlan interface to a wifi network
usage :

./setupKit.sh wlan1 reach my_secret_wpa_key

first parameter is the wifi interface
second is the SSID prefix, I use reach for  "reachxx:xx"
last is the WPA KEY to connect to network
2018-09-18 15:53:45 +02:00
4489f22ac0 Adding experimentation shell scripts ! 2018-09-18 14:36:45 +02:00
29d5a48440 Upating License 2018-09-18 09:03:49 +02:00
3d693f0632 Add Docker Compose basic environment 2018-09-17 17:51:14 +02:00
9b11769a70 Initial commit 2018-09-17 14:17:54 +02:00
83 changed files with 5771 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/vendor
/bin
/.env
/release
*.log

59
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,59 @@
pipeline {
agent {
docker { image 'golang:1.11' }
}
stages {
stage('Prepare environment') {
steps {
script {
sh 'make install-devtools'
sh 'make vendor'
}
}
}
stage('Run unit tests') {
steps {
script {
sh 'make test'
}
}
}
stage('Run lint') {
steps {
script {
try {
sh 'make LINT_ARGS="--new-from-rev=HEAD~" lint'
} catch(ex) {
currentBuild.result = "UNSTABLE"
}
}
}
}
}
post {
always {
script {
if (currentBuild.currentResult != 'SUCCESS') {
emailext (
subject: "${currentBuild.currentResult} - Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'",
body: """
Voir les étapes du job: ${env.BUILD_URL}flowGraphTable
Projet: ${env.GIT_URL}
Branche: ${env.GIT_BRANCH}
Commit: ${env.GIT_COMMIT}
""",
recipientProviders: [developers(), requestor()],
)
}
}
}
}
}

46
Makefile Normal file
View File

@ -0,0 +1,46 @@
LINT_ARGS ?= ./...
export GO111MODULE := on
build: bin/server
bin/server:
CGO_ENABLED=0 go build -mod=vendor -v -o ./bin/server ./cmd/server
watch:
modd
test: tidy
GO111MODULE=off go clean -testcache
go test -mod=vendor -v ./...
lint:
go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.32.2
go run github.com/golangci/golangci-lint/cmd/golangci-lint run -e '.*/pkg/mod' -e ".*/go/src" --enable-all --disable lll $(LINT_ARGS)
tidy:
go mod tidy
vendor: tidy
go mod vendor
install-devtools: vendor
# Install modd
GO111MODULE=off go get -u github.com/cortesi/modd/cmd/modd
# Install golangci-lint
GO111MODULE=off go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
clean:
rm -rf ./bin ./release
go clean -i -x -r -modcache
doc:
@echo "Open your browser to http://localhost:6060/pkg/forge.cadoles.com/Pyxis/orion/ to see the documentation"
@godoc -http=:6060
release:
scripts/release
spy-websocket:
echo > websocket.log && sudo tshark -Y websocket.payload -Tfields -e text | tee -a websocket.log
.PHONY: test clean generate vendor install-devtools lint watch tidy doc release

11
README.md Normal file
View File

@ -0,0 +1,11 @@
# Orion
Récepteur GNSS/RTK pour les topographes et chefs de chantier
## Documentation
[Voir la documentation](./doc)
## License
`GNU GPL v3` http://dachary.org/loic/gpl-french.pdf

58
cmd/broadcast/main.go Normal file
View File

@ -0,0 +1,58 @@
package main
import (
"context"
"flag"
"log"
"time"
"forge.cadoles.com/Pyxis/orion/emlid"
"forge.cadoles.com/Pyxis/orion/emlid/reachview"
"github.com/davecgh/go-spew/spew"
)
var (
host = "192.168.42.1"
)
func init() {
flag.StringVar(&host, "host", host, "ReachRS module host")
}
func main() {
flag.Parse()
c := connect()
defer c.Close()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
events, err := c.Broadcast(ctx)
if err != nil {
log.Fatal(err)
}
for e := range events {
log.Printf("received event: %s", spew.Sdump(e))
}
log.Println("done")
}
func connect() *reachview.Client {
c := reachview.NewClient(
emlid.WithEndpoint(host, 80),
)
log.Printf("connecting to module '%s'", host)
if err := c.Connect(); err != nil {
log.Fatal(err)
}
log.Println("connected")
return c
}

64
cmd/configdump/main.go Normal file
View File

@ -0,0 +1,64 @@
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
"time"
"forge.cadoles.com/Pyxis/orion/emlid"
"forge.cadoles.com/Pyxis/orion/emlid/reachview"
)
var (
host = "192.168.42.1"
)
func init() {
flag.StringVar(&host, "host", host, "ReachRS module host")
}
func main() {
flag.Parse()
c := connect()
defer c.Close()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
config, err := c.Configuration(ctx)
if err != nil {
log.Fatal(err)
}
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
log.Println("done")
}
func connect() *reachview.Client {
c := reachview.NewClient(
emlid.WithEndpoint(host, 80),
)
log.Printf("connecting to module '%s'", host)
if err := c.Connect(); err != nil {
log.Fatal(err)
}
log.Println("connected")
return c
}

View File

@ -0,0 +1,16 @@
# Example: Configurator
A simple example of a ReachView client that can:
- Configure a ReachRS module as "base" or "rover"
## Usage
1. Boot your ReachRS module in "ReachView" mode
2. Launch the example:
```shell
go run cmd/configurator/main.go \
-mode 'rover|base'\
-host '<DEVICE_IP_ADDRESS'\
```

245
cmd/configurator/main.go Normal file
View File

@ -0,0 +1,245 @@
package main
import (
"context"
"flag"
"fmt"
"log"
"strings"
"time"
"forge.cadoles.com/Pyxis/orion/emlid"
"forge.cadoles.com/Pyxis/orion/emlid/reachview"
"github.com/pkg/errors"
)
const (
modeRover = "rover"
modeBase = "base"
)
var (
mode = modeRover
host = "192.168.42.1"
)
func init() {
modes := []string{modeRover, modeBase}
flag.StringVar(
&mode, "mode", mode,
fmt.Sprintf("The configuration mode. Available: %v", strings.Join(modes, ", ")),
)
flag.StringVar(&host, "host", host, "ReachRS module host")
}
func main() {
flag.Parse()
switch mode {
case modeRover:
configureRover()
case modeBase:
configureBase()
default:
log.Fatalf("unknown mode '%s'", mode)
}
log.Println("done")
}
func connect() *reachview.Client {
c := reachview.NewClient(
emlid.WithEndpoint(host, 80),
)
log.Printf("connecting to module '%s'", host)
if err := c.Connect(); err != nil {
log.Fatal(err)
}
log.Println("connected")
return c
}
func configureRover() {
c := connect()
defer c.Close()
resetConfiguration(c)
config := getCommonConfiguration()
config.RTKSettings.GPSARMode = reachview.GPSARModeFixAndHold
config.RTKSettings.GLONASSARMode = reachview.On
config.RTKSettings.PositionningMode = reachview.PositionningModeKinematic
config.RTKSettings.UpdateRate = reachview.Float(5)
config.CorrectionInput = &reachview.CorrectionInput{
Input2: &reachview.Input2{
Input: reachview.Input{
Enabled: reachview.True,
Format: reachview.IOFormatRTCM3,
Type: reachview.IOTypeLoRa,
Path: reachview.String("lora"),
},
SendPositionToBase: reachview.Off,
},
}
config.BaseMode.Output = &reachview.Output{
Enabled: reachview.False,
}
log.Println("configuring module as rover")
applyConfiguration(c, config)
log.Println("rebooting module")
if err := c.Reboot(context.Background(), true); err != nil {
log.Fatal(errors.Wrap(err, "could not reboot module"))
}
}
func configureBase() {
c := connect()
defer c.Close()
resetConfiguration(c)
log.Println("configuring module as base")
config := getCommonConfiguration()
config.RTKSettings.UpdateRate = reachview.Float(1)
config.BaseMode.Output = &reachview.Output{
Enabled: reachview.True,
Format: reachview.IOFormatRTCM3,
Type: reachview.IOTypeLoRa,
}
config.BaseMode.BaseCoordinates = &reachview.BaseCoordinates{
Accumulation: reachview.String("1"),
AntennaOffset: &reachview.AntennaOffset{
East: reachview.String("0"),
North: reachview.String("0"),
Up: reachview.String("2.20"),
},
Mode: reachview.BaseCoordinatesModeManual,
Format: reachview.BaseCoordinatesFormatLLH,
Coordinates: []*string{
reachview.String("0"),
reachview.String("0"),
reachview.String("0"),
},
}
applyConfiguration(c, config)
log.Println("rebooting module")
if err := c.Reboot(context.Background(), true); err != nil {
log.Fatal(errors.Wrap(err, "could not reboot module"))
}
}
func applyConfiguration(c *reachview.Client, config *reachview.Configuration) {
ctx, applyConfCancel := context.WithTimeout(context.Background(), 60*time.Second)
defer applyConfCancel()
result, _, err := c.ApplyConfiguration(ctx, config)
if err != nil {
log.Fatal(err)
}
if result != reachview.ConfigurationApplySuccess {
log.Fatal("configuration update failed !")
}
}
func resetConfiguration(c *reachview.Client) {
log.Println("resetting module configuration")
ctx, resetCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer resetCancel()
if err := c.ResetConfiguration(ctx); err != nil {
log.Fatal(err)
}
}
func getCommonConfiguration() *reachview.Configuration {
return &reachview.Configuration{
RTKSettings: &reachview.RTKSettings{
PositioningSystems: &reachview.PositionningSystems{
GPS: reachview.True,
GLONASS: reachview.True,
Galileo: reachview.True,
SBAS: reachview.True,
QZSS: reachview.True,
Compass: reachview.False,
},
MaxHorizontalAcceleration: reachview.String("1"),
GPSARMode: reachview.String("fix-and-hold"),
SNRMask: reachview.String("35"),
GLONASSARMode: reachview.String("off"),
UpdateRate: reachview.Float(5),
ElevationMaskAngle: reachview.String("15"),
PositionningMode: reachview.String("kinematic"),
MaxVerticalAcceleration: reachview.String("1"),
},
LoRa: &reachview.LoRa{
AirRate: reachview.String("9.11"),
Frequency: reachview.Float(868000),
OutputPower: reachview.String("20"),
},
PositionOutput: &reachview.PositionOutput{
Output1: &reachview.Output{
Enabled: reachview.False,
},
Output2: &reachview.Output{
Enabled: reachview.False,
},
},
BaseMode: &reachview.BaseMode{
RTCM3Messages: &reachview.RTCM3Messages{
Type1002: &reachview.RTCMMessageType{
Enabled: reachview.True,
Frequency: reachview.String("0.1"),
},
Type1006: &reachview.RTCMMessageType{
Enabled: reachview.True,
Frequency: reachview.String("0.1"),
},
Type1010: &reachview.RTCMMessageType{
Enabled: reachview.True,
Frequency: reachview.String("0.5"),
},
Type1097: &reachview.RTCMMessageType{
Enabled: reachview.True,
Frequency: reachview.String("0.5"),
},
Type1008: &reachview.RTCMMessageType{
Enabled: reachview.False,
},
Type1019: &reachview.RTCMMessageType{
Enabled: reachview.False,
},
Type1020: &reachview.RTCMMessageType{
Enabled: reachview.False,
},
Type1107: &reachview.RTCMMessageType{
Enabled: reachview.False,
},
Type1117: &reachview.RTCMMessageType{
Enabled: reachview.False,
},
Type1127: &reachview.RTCMMessageType{
Enabled: reachview.False,
},
},
},
}
}

41
cmd/discovery/main.go Normal file
View File

@ -0,0 +1,41 @@
package main
import (
"context"
"flag"
"log"
"time"
"forge.cadoles.com/Pyxis/orion/emlid"
)
var (
timeout = 10 * time.Second
)
func init() {
flag.DurationVar(&timeout, "timeout", timeout, "mDNS scan timeout")
}
func main() {
flag.Parse()
log.Println("Searching for ReachRS services on network...")
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
services, err := emlid.Discover(ctx)
if err != nil {
log.Fatal(err)
}
for _, s := range services {
log.Println("Found:")
log.Printf(" Name: '%s'", s.Name)
log.Printf(" Hosts: %s", s.AddrV4)
log.Printf(" Port: '%d'", s.Port)
}
}

24
cmd/server/config.go Normal file
View File

@ -0,0 +1,24 @@
package main
import (
"github.com/caarlos0/env"
)
type config struct {
HTTPHost string `env:"ORION_HTTP_HOST"`
HTTPPort string `env:"ORION_HTTP_PORT"`
}
func overwriteFromEnv(conf *config) error {
if err := env.Parse(conf); err != nil {
return err
}
return nil
}
func newDefaultConfig() *config {
return &config{
HTTPHost: "0.0.0.0",
HTTPPort: "8888",
}
}

36
cmd/server/main.go Normal file
View File

@ -0,0 +1,36 @@
package main
import (
"fmt"
"log"
"net/http"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
)
var (
conf = newDefaultConfig()
)
func main() {
if err := overwriteFromEnv(conf); err != nil {
log.Fatal(err)
}
r := chi.NewRouter()
r.Use(middleware.Recoverer)
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
hostStr := fmt.Sprintf("%s:%s", conf.HTTPHost, conf.HTTPPort)
log.Printf("listening on http://%s", hostStr)
if err := http.ListenAndServe(hostStr, r); err != nil {
log.Fatal(err)
}
}

27
cmd/updater/README.md Normal file
View File

@ -0,0 +1,27 @@
# Example: Updater
A simple example of an updater client wich can:
- Configure a ReachRS module to use the given WiFi network
- Check for updates then reboot to "ReachView" mode
## Usage
1. Boot your ReachRS module in "Updater" mode. You can see a documentation on how to reset your device [here](https://forge.cadoles.com/Pyxis/orion/wiki/FlashIt).
2. Launch the example in `configure-wifi` phase and provides WiFi network informations.
```shell
go run cmd/updater/main.go \
-phase 'configure-wifi'\
-host '<DEVICE_IP_ADDRESS'\
-ssid '<WIFI_SSID>'\
-password '<WIFI_PASSWORD>'\
-security '<WIFI_SECURITY>'
```
3. The device will switch to the provided WiFi network, as you should do.
4. Launch the example in `update-then-reboot` phase.
```shell
go run cmd/updater/main.go \
-phase 'update-and-reboot'\
-host '<DEVICE_IP_ADDRESS'
```

171
cmd/updater/main.go Normal file
View File

@ -0,0 +1,171 @@
package main
import (
"context"
"flag"
"fmt"
"log"
"strings"
"time"
"forge.cadoles.com/Pyxis/orion/emlid"
"forge.cadoles.com/Pyxis/orion/emlid/updater"
)
const (
phaseConfigureWifi = "configure-wifi"
phaseUpdateThenReboot = "update-then-reboot"
)
var (
phase = phaseConfigureWifi
host = "192.168.42.1"
ssid = ""
security = string(updater.SecurityWPAPSK)
password = ""
)
func init() {
phases := []string{phaseConfigureWifi, phaseUpdateThenReboot}
flag.StringVar(
&phase, "phase", phase,
fmt.Sprintf("The configuration phase. Available: %v", strings.Join(phases, ", ")),
)
flag.StringVar(&host, "host", host, "ReachRS module host")
flag.StringVar(&ssid, "ssid", ssid, "The WiFi SSID to connect the module to")
flag.StringVar(&security, "security", security, "The WiFi security algorithm")
flag.StringVar(&password, "password", password, "The WiFi password")
}
func main() {
flag.Parse()
switch phase {
case phaseConfigureWifi:
configureWifi()
case phaseUpdateThenReboot:
updateThenReboot()
default:
log.Fatalf("unknown phase '%s'", phase)
}
log.Println("done")
}
func connect() *updater.Client {
c := updater.NewClient(
emlid.WithEndpoint(host, 80),
)
log.Printf("connecting to module '%s'", host)
if err := c.Connect(); err != nil {
log.Fatal(err)
}
log.Println("connected")
return c
}
func configureWifi() {
if ssid == "" {
log.Fatal("you must provide a WiFi SSID with the -ssid flag")
}
c := connect()
defer c.Close()
ctx := context.Background()
log.Println("checking module status")
ctx, testResultsCancel := context.WithTimeout(ctx, 5*time.Second)
defer testResultsCancel()
results, err := c.TestResults(ctx)
if err != nil {
log.Fatal(err)
}
log.Printf("device: '%s'", results.Device)
log.Printf("lora activated ? %v", results.Lora)
log.Printf("mpu activated ? %v", results.MPU)
log.Printf("stc activated ? %v", results.STC)
log.Printf("ublox activated ? %v", results.UBlox)
log.Printf("adding wifi network '%s'", ssid)
ctx, addWifiCancel := context.WithTimeout(ctx, 5*time.Second)
defer addWifiCancel()
done, err := c.AddWifiNetwork(ctx, ssid, updater.WifiSecurity(security), password)
if err != nil {
log.Fatal(err)
}
if !done {
log.Fatal("couldnt add wifi network")
}
log.Println("connecting module to wifi network")
ctx, joinWifiCancel := context.WithTimeout(ctx, 5*time.Second)
defer joinWifiCancel()
if err := c.JoinWifiNetwork(ctx, ssid, true); err != nil {
log.Fatal(err)
}
log.Printf("you can now switch to the wifi network and start phase '%s'", phaseUpdateThenReboot)
}
func updateThenReboot() {
c := connect()
defer c.Close()
ctx := context.Background()
log.Println("checking time sync")
ctx, timeSyncedCancel := context.WithTimeout(ctx, 5*time.Second)
defer timeSyncedCancel()
synced, err := c.TimeSynced(ctx)
if err != nil {
log.Fatal(err)
}
log.Printf("time synced ? %v", synced)
log.Println("checking reachview version")
ctx, reachviewVersionCancel := context.WithTimeout(ctx, 5*time.Second)
defer reachviewVersionCancel()
version, _, err := c.ReachViewVersion(ctx)
if err != nil {
log.Fatal(err)
}
log.Printf("reachview version ? '%s'", version)
log.Println("checking for update")
ctx, updateCancel := context.WithTimeout(ctx, 5*time.Second)
defer updateCancel()
status, err := c.Update(ctx)
if err != nil {
log.Fatal(err)
}
log.Printf("is update running ? %v", status.Active)
log.Printf("is update locked ? %v", status.Locked)
log.Printf("last update state ? '%s'", status.State)
if status.Active {
log.Fatal("cannot reboot while an update is active")
}
log.Println("rebooting device")
ctx, rebootCancel := context.WithTimeout(ctx, 5*time.Second)
defer rebootCancel()
if err := c.RebootNow(ctx, true); err != nil {
log.Fatal(err)
}
}

View File

@ -0,0 +1,40 @@
FROM alpine:3.7 AS downloader
ARG HTTP_PROXY=""
ARG HTTPS_PROXY=""
ARG http_proxy=""
ARG https_proxy=""
ARG OPENWRT_VERSION=18.06.1
RUN apk add --no-cache wget tar
RUN wget -O openwrt.tar.gz https://downloads.openwrt.org/releases/${OPENWRT_VERSION}/targets/x86/generic/openwrt-${OPENWRT_VERSION}-x86-generic-generic-rootfs.tar.gz\
&& mkdir /openwrt \
&& tar -C /openwrt -xzf openwrt.tar.gz
FROM scratch
COPY --from=downloader /openwrt /
RUN mkdir -p /var/lock \
&& mkdir -p /var/run
USER root
# Install and enable Luci
RUN opkg update\
&& opkg install luci luci-mod-rpc\
&& /etc/init.d/uhttpd enable
RUN rm /lib/preinit/* &&\
echo > /lib/preinit/00_empty_dummy_script &&\
/etc/init.d/cron disable &&\
/etc/init.d/gpio_switch disable &&\
/etc/init.d/led disable &&\
/etc/init.d/dropbear disable &&\
/etc/init.d/network disable &&\
/etc/init.d/odhcpd disable &&\
/etc/init.d/sysctl disable &&\
/etc/init.d/sysfixtime disable &&\
/etc/init.d/sysntpd disable
CMD /sbin/init

4
doc/README.md Normal file
View File

@ -0,0 +1,4 @@
# Documentation
- [Préparer son environnement de développement](./prepa-dev.md)
- [Utilisation de l'environnement Compose](./compose.md)

22
doc/compose.md Normal file
View File

@ -0,0 +1,22 @@
# Utilisation de l'environnement Compose
Un environnement docker-compose est mis à disposition des développeurs afin de faciliter le processus de développement.
Cet environnement contient:
- Une installation d'OpenWRT basique
## Lancer l'environnement
Il vous faudra [Docker](https://docs.docker.com/install/) et [Compose](https://docs.docker.com/compose/overview/) sur votre machine.
Dans le répertoire du projet Orion, faites
```
docker-compose up
```
Les services suivants seront alors exposés sur votre machine:
- http://localhost:8080/ - Accès à l'interface LuCi du service OpenWRT

14
doc/debug-websocket.md Normal file
View File

@ -0,0 +1,14 @@
# Débugger la communication websocket avec ReachView
## Intercepter les connexions sur le module ReachRS
Il est possible d'intercepter et logger les communications sur la connexion websocket de ReachView.
Pour ce faire, se connecter en SSH sur le module ReachRS et lancer la commande:
```
tcpdump -nl -w - -i wlan0 -c 500 port 80|strings
```
## Intercepter les connexions sortantes depuis la machine de test

24
doc/prepa-dev.md Normal file
View File

@ -0,0 +1,24 @@
# Préparer son environnement de développement
### Dépendances
- [Go >= 1.11](https://golang.org/dl/)
### Procédure
1. Installer les outils de développement. Cette opération va notamment générer les bindings Qt pour l'application `notebook`. **Cette étape peut durer un certain temps**.
```shell
make install-devtools
```
2. Lancer le serveur
```shell
make watch
```
3. (Optionnel mais recommandé) Installer le hook Git `pre-commit`
```shell
rm -f .git/hooks/pre-commit.sample
ln -s "$PWD/misc/git-hooks/pre-commit" .git/hooks/pre-commit
```

12
docker-compose.yml Normal file
View File

@ -0,0 +1,12 @@
version: '2.2'
services:
openwrt:
build:
context: ./containers/openwrt
args:
HTTP_PROXY: ${HTTP_PROXY}
HTTPS_PROXY: ${HTTPS_PROXY}
http_proxy: ${http_proxy}
https_proxy: ${https_proxy}
ports:
- 8080:80

View File

@ -0,0 +1,17 @@
package emlid
const (
// EventBrowserConnected is emitted after the initial connection to the
// ReachView endpoint
eventBrowserConnected = "browser connected"
)
// sendBrowserConnected notifies the ReachView endpoint
// of a new connection.
// See misc/reachview/update_main.js line 297
func (c *Client) sendBrowserConnected() error {
payload := map[string]string{
"data": "I'm connected",
}
return c.Emit(eventBrowserConnected, payload)
}

175
emlid/client.go Normal file
View File

@ -0,0 +1,175 @@
package emlid
import (
"context"
"sync"
gosocketio "forge.cadoles.com/Pyxis/golang-socketio"
"forge.cadoles.com/Pyxis/golang-socketio/transport"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
)
// Client is a ReachRS websocket API client
type Client struct {
opts *Options
conn *gosocketio.Client
}
// EventHandler is an event handler
type EventHandler func(data interface{})
// Connect connects the client to the ReachView endpoint
// This method is not safe to call by different coroutines
func (c *Client) Connect() error {
var err error
var wg sync.WaitGroup
wg.Add(1)
transport := &transport.WebsocketTransport{
PingInterval: c.opts.PingInterval,
PingTimeout: c.opts.PingTimeout,
ReceiveTimeout: c.opts.ReceiveTimeout,
SendTimeout: c.opts.SendTimeout,
BufferSize: c.opts.BufferSize,
}
c.Logf("connecting to '%s'", c.opts.Endpoint)
conn, err := gosocketio.Dial(c.opts.Endpoint, transport)
if err != nil {
return errors.Wrap(err, "error while connecting to endpoint")
}
c.conn = conn
err = conn.On(gosocketio.OnConnection, func(h *gosocketio.Channel) {
conn.Off(gosocketio.OnError)
c.Logf("connected with sid '%s'", h.Id())
err = c.sendBrowserConnected()
wg.Done()
})
if err != nil {
return errors.Wrap(err, "error while attaching to connection event")
}
err = conn.On(gosocketio.OnError, func(h *gosocketio.Channel) {
c.Logf("error")
err = errors.Errorf("an unknown error occured")
c.conn = nil
wg.Done()
})
if err != nil {
return errors.Wrap(err, "error while attaching to error event")
}
wg.Wait()
return err
}
// Close closes the current connection to the ReachView endpoint
func (c *Client) Close() {
if c.conn == nil {
return
}
c.conn.Close()
c.conn = nil
}
func (c *Client) IsAlive() bool {
if c.conn == nil {
return false
}
return c.conn.IsAlive()
}
// Emit a new event with the given data
func (c *Client) Emit(event string, data interface{}) error {
// enc := json.NewEncoder(os.Stdout)
// enc.SetIndent("", " ")
// enc.Encode(data)
c.Logf("emit '%s' event", event)
if err := c.conn.Emit(event, data); err != nil {
return errors.Wrapf(err, "error while emitting '%s' event", event)
}
c.Logf("'%s' event sent", event)
return nil
}
// On binds and event handler to the given event
func (c *Client) On(event string, handler interface{}) error {
return c.conn.On(event, handler)
}
// Off remove the handler bound to the given event
func (c *Client) Off(event string) {
c.conn.Off(event)
}
// ReqResp emits an event with the given data and waits for a response
func (c *Client) ReqResp(ctx context.Context,
requestEvent string, requestData interface{},
responseEvent string, res interface{}) error {
var err error
var wg sync.WaitGroup
var once sync.Once
done := func() {
c.conn.Off(responseEvent)
wg.Done()
}
wg.Add(1)
go func() {
<-ctx.Done()
err = ctx.Err()
once.Do(done)
}()
err = c.conn.On(responseEvent, func(_ *gosocketio.Channel, data interface{}) {
if res != nil {
// enc := json.NewEncoder(os.Stdout)
// enc.SetIndent("", " ")
// enc.Encode(data)
err = mapstructure.WeakDecode(data, res)
}
once.Do(done)
})
if err != nil {
return errors.Wrapf(err, "error while binding to '%s' event", responseEvent)
}
if err = c.Emit(requestEvent, requestData); err != nil {
return errors.Wrapf(err, "error while emitting event '%s'", requestEvent)
}
wg.Wait()
return err
}
// Logf logs the given message with the configured logger
func (c *Client) Logf(format string, args ...interface{}) {
if c.opts.Logger == nil {
return
}
c.opts.Logger.Printf(format, args...)
}
// NewClient returns a new ReachRS websocket client
func NewClient(opts ...OptionFunc) *Client {
options := DefaultOptions()
for _, o := range opts {
o(options)
}
return &Client{
opts: options,
}
}

17
emlid/common/client.go Normal file
View File

@ -0,0 +1,17 @@
package common
import (
"forge.cadoles.com/Pyxis/orion/emlid"
)
// Client is a ReachRS Updater client
type Client struct {
*emlid.Client
}
// NewClient returns a new ReachRS ReachView client
func NewClient(opts ...emlid.OptionFunc) *Client {
return &Client{
Client: emlid.NewClient(opts...),
}
}

View File

@ -0,0 +1,51 @@
package common
import (
"context"
"github.com/blang/semver/v4"
"github.com/pkg/errors"
)
type VersionHandlerFunc func(ctx context.Context, version string, stable bool) error
type VersionHandler struct {
Range string
Handler VersionHandlerFunc
}
func NewVersionHandler(versionRange string, h VersionHandlerFunc) *VersionHandler {
return &VersionHandler{
Range: versionRange,
Handler: h,
}
}
func (c *Client) HandleVersion(ctx context.Context, handlers ...*VersionHandler) error {
rawVersion, stable, err := c.ReachViewVersion(ctx)
if err != nil {
return errors.Wrap(err, "could not retrieve reachview version")
}
version, err := semver.ParseTolerant(rawVersion)
if err != nil {
return errors.Wrap(err, "could not parse reachview version")
}
for _, h := range handlers {
versionRange, err := semver.ParseRange(h.Range)
if err != nil {
return errors.Wrap(err, "could not parse handler version range")
}
if !versionRange(version) {
continue
}
if err := h.Handler(ctx, rawVersion, stable); err != nil {
return errors.WithStack(err)
}
}
return nil
}

View File

@ -0,0 +1,26 @@
package common
import (
"context"
"strings"
)
const (
eventGetReachViewVersion = "get reachview version"
eventReachViewVersionResults = "current reachview version"
)
type reachViewVersion struct {
Version string `json:"version"`
Stable bool `json:"bool"`
}
// ReachViewVersion returns the ReachRS module ReachView version
func (c *Client) ReachViewVersion(ctx context.Context) (string, bool, error) {
res := &reachViewVersion{}
if err := c.ReqResp(ctx, eventGetReachViewVersion, nil, eventReachViewVersionResults, res); err != nil {
return "", false, err
}
return strings.TrimSpace(res.Version), res.Stable, nil
}

View File

@ -0,0 +1,39 @@
package common
import (
"context"
"testing"
"time"
"forge.cadoles.com/Pyxis/orion/emlid"
)
func TestClientReachViewVersion(t *testing.T) {
if !*runCommonIntegrationTests {
t.Skip("To run this test, use: go test -updater-integration")
}
client := NewClient(
emlid.WithStandardLogger(),
emlid.WithEndpoint(*reachHost, 80),
)
if err := client.Connect(); err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
version, _, err := client.ReachViewVersion(ctx)
if err != nil {
t.Error(err)
}
if version == "" {
t.Error("version should not be empty")
}
defer client.Close()
}

13
emlid/common/util_test.go Normal file
View File

@ -0,0 +1,13 @@
package common
import "flag"
var runCommonIntegrationTests = flag.Bool(
"common-integration", false,
"Run the 'Common' integration tests (in addition to the unit tests)",
)
var reachHost = flag.String(
"reach-host", "192.168.42.1",
"The Reach module host to use in integration tests",
)

57
emlid/discovery.go Normal file
View File

@ -0,0 +1,57 @@
package emlid
import (
"context"
"net"
"sync"
"github.com/grandcat/zeroconf"
)
// Service is a ReachRS service discovered via MDNS-SD
type Service struct {
Name string
AddrV4 *net.IP
Port int
}
// Discover tries to discover ReachRS services on the local network via mDNS-SD
func Discover(ctx context.Context) ([]Service, error) {
var wg sync.WaitGroup
wg.Add(1)
resolver, err := zeroconf.NewResolver()
if err != nil {
return nil, err
}
services := make([]Service, 0)
entries := make(chan *zeroconf.ServiceEntry)
go func() {
for e := range entries {
var addr *net.IP
if len(e.AddrIPv4) > 0 {
addr = &e.AddrIPv4[0]
}
srv := Service{
Name: e.Instance,
AddrV4: addr,
Port: e.Port,
}
services = append(services, srv)
}
wg.Done()
}()
if err = resolver.Browse(ctx, "_reach._tcp", ".local", entries); err != nil {
return nil, err
}
wg.Wait()
return services, nil
}

33
emlid/discovery_test.go Normal file
View File

@ -0,0 +1,33 @@
package emlid
import (
"context"
"testing"
"time"
)
func TestDiscovery(t *testing.T) {
if !*runDiscoveryIntegrationTests {
t.Skip("To run this test, use: go test -discovery-integration")
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
services, err := Discover(ctx)
if err != nil {
t.Fatal(err)
}
if g, e := len(services), 1; g != e {
t.Fatalf("len(services): got '%d', expected '%d'", g, e)
}
s := services[0]
if g, e := s.Name, "reach"; g != e {
t.Errorf("services[0].Name: got '%s', expected '%s'", g, e)
}
}

109
emlid/option.go Normal file
View File

@ -0,0 +1,109 @@
package emlid
import (
"log"
"os"
"time"
"forge.cadoles.com/Pyxis/golang-socketio"
)
// Logger is a logger implementation for the ReachRS client
type Logger interface {
Printf(format string, args ...interface{})
}
// Options are ReachRS client configuration options
type Options struct {
Endpoint string
PingInterval time.Duration
PingTimeout time.Duration
ReceiveTimeout time.Duration
SendTimeout time.Duration
BufferSize int
Logger Logger
}
// OptionFunc is an option configuration for the ReachRS client
type OptionFunc func(opts *Options)
// WithEndpoint configures the client to target the given host and port
func WithEndpoint(host string, port int) OptionFunc {
return func(opts *Options) {
opts.Endpoint = gosocketio.GetUrl(host, port, false)
}
}
// WithService configures the client to target the given ReachRS service
func WithService(service Service) OptionFunc {
return func(opts *Options) {
opts.Endpoint = gosocketio.GetUrl(service.AddrV4.String(), service.Port, false)
}
}
// WithPingInterval configures the client to use the given ping interval
func WithPingInterval(interval time.Duration) OptionFunc {
return func(opts *Options) {
opts.PingInterval = interval
}
}
// WithPingTimeout configures the client to use the given ping timeout
func WithPingTimeout(timeout time.Duration) OptionFunc {
return func(opts *Options) {
opts.PingTimeout = timeout
}
}
// WithReceiveTimeout configures the client to use the given receive timeout
func WithReceiveTimeout(timeout time.Duration) OptionFunc {
return func(opts *Options) {
opts.ReceiveTimeout = timeout
}
}
// WithSendTimeout configures the client to use the given send timeout
func WithSendTimeout(timeout time.Duration) OptionFunc {
return func(opts *Options) {
opts.SendTimeout = timeout
}
}
// WithBufferSize configures the client to use the given buffer size
func WithBufferSize(size int) OptionFunc {
return func(opts *Options) {
opts.BufferSize = size
}
}
// WithLogger configures the client to use the given logger
func WithLogger(logger Logger) OptionFunc {
return func(opts *Options) {
opts.Logger = logger
}
}
// WithStandardLogger configures the client to use the given standard logger
func WithStandardLogger() OptionFunc {
return func(opts *Options) {
logger := log.New(os.Stdout, "[reachrs] ", log.LstdFlags)
opts.Logger = logger
}
}
// DefaultOptions returns the default values for the client options
func DefaultOptions() *Options {
opts := &Options{}
defaults := []OptionFunc{
WithEndpoint("192.168.42.1", 80),
WithPingInterval(30 * time.Second),
WithPingTimeout(60 * time.Second),
WithReceiveTimeout(60 * time.Second),
WithSendTimeout(60 * time.Second),
WithBufferSize(1024 * 32),
}
for _, o := range defaults {
o(opts)
}
return opts
}

4
emlid/reach.go Normal file
View File

@ -0,0 +1,4 @@
// Package emlid is a package to configure EMLID ReachRS modules in Go.
//
// It aims to provide a simple interface to common ReachRS modules management operations.
package emlid

View File

@ -0,0 +1,60 @@
package reachview
import (
"context"
"sync"
gosocketio "forge.cadoles.com/Pyxis/golang-socketio"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
)
const (
eventBroadcast = "broadcast"
)
const (
BroadcastRoverStatus = "rover_status"
BroadcastBaseStatus = "base_status"
BroadcastRTKStatus = "rtk_status"
BroadcastObservations = "observations"
BroadcastTimeMarks = "time_marks"
BroadcastBatteryStatus = "battery_status"
)
// Broadcast is a broadcasted message containing modules status informations.
type Broadcast struct {
Name string `mapstructure:"name"`
Payload map[string]interface{} `mapstructure:"payload"`
}
// Broadcast listens for broadcast messages.
func (c *Client) Broadcast(ctx context.Context) (chan Broadcast, error) {
out := make(chan Broadcast)
closer := new(sync.Once)
handler := func(_ *gosocketio.Channel, data interface{}) {
res := Broadcast{}
if err := mapstructure.WeakDecode(data, &res); err != nil {
c.Logf("error while decoding broadcast message: %s", errors.WithStack(err))
}
select {
case <-ctx.Done():
closer.Do(func() {
c.Off(eventBroadcast)
close(out)
})
return
default:
out <- res
}
}
if err := c.On(eventBroadcast, handler); err != nil {
return nil, errors.WithStack(err)
}
return out, nil
}

View File

@ -0,0 +1,37 @@
package reachview
import (
"context"
"testing"
"time"
"forge.cadoles.com/Pyxis/orion/emlid"
"github.com/davecgh/go-spew/spew"
)
func TestReachViewStatusBroadcast(t *testing.T) {
if !*runReachViewIntegrationTests {
t.Skip("To run this test, use: go test -reachview-integration")
}
client := NewClient(
emlid.WithStandardLogger(),
emlid.WithEndpoint(*reachHost, 80),
)
if err := client.Connect(); err != nil {
t.Fatal(err)
}
defer client.Close()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
broadcastChan, err := client.Broadcast(ctx)
if err != nil {
t.Fatal(err)
}
for b := range broadcastChan {
spew.Dump(b)
}
}

18
emlid/reachview/client.go Normal file
View File

@ -0,0 +1,18 @@
package reachview
import (
"forge.cadoles.com/Pyxis/orion/emlid"
"forge.cadoles.com/Pyxis/orion/emlid/common"
)
// Client is a ReachRS Updater client
type Client struct {
*common.Client
}
// NewClient returns a new ReachRS ReachView client
func NewClient(opts ...emlid.OptionFunc) *Client {
return &Client{
Client: common.NewClient(opts...),
}
}

View File

@ -0,0 +1,79 @@
package reachview
import (
"context"
"forge.cadoles.com/Pyxis/orion/emlid/common"
"github.com/pkg/errors"
)
const (
eventGetConfiguration = "get configuration"
eventCurrentConfiguration = "current configuration"
eventApplyConfiguration = "apply configuration"
eventConfigurationApplied = "configuration applied"
eventResetConfiguration = "reset configuration to default"
eventSettingsResetToDefault = "settings reset to default"
)
// Configuration fetches and return the current configuration of the ReachRS module
func (r *Client) Configuration(ctx context.Context) (*Configuration, error) {
configuration := &Configuration{}
if err := r.ReqResp(ctx, eventGetConfiguration, nil, eventCurrentConfiguration, configuration); err != nil {
return nil, err
}
return configuration, nil
}
type configurationApplied struct {
Configuration *Configuration `mapstructure:"configuration,omitempty"`
Result string `mapstructure:"result"`
Constraints *Constraints `mapstructure:"Constraints,omitempty"`
}
const (
// ConfigurationApplySuccess -
ConfigurationApplySuccess = "success"
// ConfigurationApplyFailed -
ConfigurationApplyFailed = "failed"
)
// ApplyConfiguration applies the given configuration
func (r *Client) ApplyConfiguration(ctx context.Context, config *Configuration) (string, *Configuration, error) {
res := &configurationApplied{}
if err := r.ReqResp(ctx, eventApplyConfiguration, config, eventConfigurationApplied, res); err != nil {
return ConfigurationApplyFailed, nil, err
}
return res.Result, res.Configuration, nil
}
// ResetConfiguration resets the module configuration to factory defaults
func (r *Client) ResetConfiguration(ctx context.Context) error {
err := r.HandleVersion(
ctx,
common.NewVersionHandler("< 2.24.0", func(ctx context.Context, version string, stable bool) error {
res := &configurationApplied{}
if err := r.ReqResp(ctx, eventResetConfiguration, nil, eventConfigurationApplied, res); err != nil {
return errors.WithStack(err)
}
if res.Result != ConfigurationApplySuccess {
return errors.New(res.Result)
}
return nil
}),
common.NewVersionHandler(">= 2.24.0", func(ctx context.Context, version string, stable bool) error {
if err := r.ReqResp(ctx, eventResetConfiguration, nil, eventSettingsResetToDefault, nil); err != nil {
return errors.WithStack(err)
}
return nil
}),
)
if err != nil {
return errors.WithStack(err)
}
return nil
}

View File

@ -0,0 +1,243 @@
package reachview
var (
// On -
On = String("on")
// Off -
Off = String("off")
// True -
True = Bool(true)
// False -
False = Bool(false)
// GPSARModeFixAndHold -
GPSARModeFixAndHold = String("fix-and-hold")
// GPSARModeContinuous -
GPSARModeContinuous = String("continuous")
// PositionningModeKinematic -
PositionningModeKinematic = String("kinematic")
// PositionningModeSingle -
PositionningModeSingle = String("single")
// PositionningModeStatic -
PositionningModeStatic = String("static")
// IOFormatRTCM3 -
IOFormatRTCM3 = String("rtcm3")
// IOTypeLoRa -
IOTypeLoRa = String("lora")
// BaseCoordinatesModeManual -
BaseCoordinatesModeManual = String("manual")
// BaseCoordinatesModeAverageFix -
BaseCoordinatesModeAverageFix = String("fix-and-hold")
// BaseCoordinatesModeAverageSingle -
BaseCoordinatesModeAverageSingle = String("single-and-hold")
// BaseCoordinatesModeAverageFloat -
BaseCoordinatesModeAverageFloat = String("float-and-hold")
// BaseCoordinatesFormatLLH -
BaseCoordinatesFormatLLH = String("llh")
// BaseCoordinatesFormatXYZ -
BaseCoordinatesFormatXYZ = String("xyz")
)
// String returns an string pointer
// This is a helper to constructs partials configations objects
// for the ApplyConfiguration() method
func String(v string) *string {
return &v
}
// Int returns an int pointer
// This is a helper to constructs partials configations objects
// for the ApplyConfiguration() method
func Int(v int) *int {
return &v
}
// Bool returns a bool pointer
// This is a helper to constructs partials configations objects
// for the ApplyConfiguration() method
func Bool(v bool) *bool {
return &v
}
// Float returns a float64 pointer
// This is a helper to constructs partials configations objects
// for the ApplyConfiguration() method
func Float(v float64) *float64 {
return &v
}
// Configuration -
type Configuration struct {
RTKSettings *RTKSettings `mapstructure:"rtk settings,omitempty" json:"rtk settings,omitempty"`
CorrectionInput *CorrectionInput `mapstructure:"correction input,omitempty" json:"correction input,omitempty"`
PositionOutput *PositionOutput `mapstructure:"position output,omitempty" json:"position output,omitempty"`
BaseMode *BaseMode `mapstructure:"base mode,omitempty" json:"base mode,omitempty"`
Logging *Logging `mapstructure:"logging,omitempty" json:"logging,omitempty"`
Bluetooth *Bluetooth `mapstructure:"bluetooth,omitempty" json:"bluetooth,omitempty"`
LoRa *LoRa `mapstructure:"lora,omitempty" json:"lora,omitempty"`
Constraints *Constraints `mapstructure:"constraints,omitempty" json:"constraints,omitempty"`
}
// RTKSettings -
type RTKSettings struct {
GLONASSARMode *string `mapstructure:"glonass ar mode,omitempty" json:"glonass ar mode,omitempty"`
UpdateRate *float64 `mapstructure:"update rate,omitempty" json:"update rate,omitempty"`
ElevationMaskAngle *string `mapstructure:"elevation mask angle,omitempty" json:"elevation mask angle,omitempty"`
MaxHorizontalAcceleration *string `mapstructure:"max horizontal acceleration,omitempty" json:"max horizontal acceleration,omitempty"`
SNRMask *string `mapstructure:"snr mask,omitempty" json:"snr mask,omitempty"`
GPSARMode *string `mapstructure:"gps ar mode,omitempty" json:"gps ar mode,omitempty"`
PositionningMode *string `mapstructure:"positioning mode,omitempty" json:"positioning mode,omitempty"`
PositioningSystems *PositionningSystems `mapstructure:"positioning systems,omitempty" json:"positioning systems,omitempty"`
MaxVerticalAcceleration *string `mapstructure:"max vertical acceleration,omitempty" json:"max vertical acceleration,omitempty"`
}
// PositionningSystems -
type PositionningSystems struct {
GLONASS *bool `mapstructure:"glonass,omitempty" json:"glonass,omitempty"`
SBAS *bool `mapstructure:"sbas,omitempty" json:"sbas,omitempty"`
QZS *bool `mapstructure:"qzs,omitempty" json:"qzs,omitempty"`
QZSS *bool `mapstructure:"qzss,omitempty" json:"qzss,omitempty"`
Compass *bool `mapstructure:"compass,omitempty" json:"compass,omitempty"`
Galileo *bool `mapstructure:"galileo,omitempty" json:"galileo,omitempty"`
GPS *bool `mapstructure:"gps,omitempty" json:"gps,omitempty"`
}
// CorrectionInput -
type CorrectionInput struct {
Input2 *Input2 `mapstructure:"input2,omitempty" json:"input2,omitempty"`
Input3 *Input3 `mapstructure:"input3,omitempty" json:"input3,omitempty"`
}
// Input -
type Input struct {
Path *string `mapstructure:"path,omitempty" json:"path,omitempty"`
Type *string `mapstructure:"type,omitempty" json:"type,omitempty"`
Enabled *bool `mapstructure:"enabled,omitempty" json:"enabled,omitempty"`
Format *string `mapstructure:"format,omitempty" json:"format,omitempty"`
}
// Input2 -
type Input2 struct {
Input `mapstructure:",squash"`
SendPositionToBase *string `mapstructure:"send position to base,omitempty" json:"send position to base,omitempty"`
}
// Input3 -
type Input3 struct {
Input `mapstructure:",squash"`
}
// PositionOutput -
type PositionOutput struct {
Output1 *Output `mapstructure:"output1,omitempty" json:"output1,omitempty"`
Output2 *Output `mapstructure:"output2,omitempty" json:"output2,omitempty"`
Output3 *Output `mapstructure:"output3,omitempty" json:"output3,omitempty"`
Output4 *Output `mapstructure:"output4,omitempty" json:"output4,omitempty"`
}
// Output -
type Output struct {
Path *string `mapstructure:"path,omitempty" json:"path,omitempty"`
Type *string `mapstructure:"type,omitempty" json:"type,omitempty"`
Enabled *bool `mapstructure:"enabled,omitempty" json:"enabled,omitempty"`
Format *string `mapstructure:"format,omitempty" json:"format,omitempty"`
}
// BaseMode -
type BaseMode struct {
Output *Output `mapstructure:"output,omitempty" json:"output,omitempty"`
BaseCoordinates *BaseCoordinates `mapstructure:"base coordinates,omitempty" json:"base coordinates,omitempty"`
RTCM3Messages *RTCM3Messages `mapstructure:"rtcm3 messages,omitempty" json:"rtcm3 messages,omitempty"`
}
// BaseCoordinates -
type BaseCoordinates struct {
Format *string `mapstructure:"format,omitempty" json:"format,omitempty"`
AntennaOffset *AntennaOffset `mapstructure:"antenna offset,omitempty" json:"antenna offset,omitempty"`
Accumulation *string `mapstructure:"accumulation,omitempty" json:"accumulation,omitempty"`
Coordinates []*string `mapstructure:"coordinates,omitempty" json:"coordinates,omitempty"`
Mode *string `mapstructure:"mode,omitempty" json:"mode,omitempty"`
}
// AntennaOffset -
type AntennaOffset struct {
East *string `mapstructure:"east,omitempty" json:"east,omitempty"`
North *string `mapstructure:"north,omitempty" json:"north,omitempty"`
Up *string `mapstructure:"up,omitempty" json:"up,omitempty"`
}
// RTCM3Messages -
type RTCM3Messages struct {
// GPS L1 code and phase and ambiguities and carrier-to-noise ratio
Type1002 *RTCMMessageType `mapstructure:"1002,omitemtpy" json:"1002,omitemtpy"`
// Station coordinates XYZ for antenna reference point and antenna height.
Type1006 *RTCMMessageType `mapstructure:"1006,omitemtpy" json:"1006,omitemtpy"`
// Antenna serial number.
Type1008 *RTCMMessageType `mapstructure:"1008,omitemtpy" json:"1008,omitemtpy"`
// GLONASS L1 code and phase and ambiguities and carrier-to-noise ratio.
Type1010 *RTCMMessageType `mapstructure:"1010,omitemtpy" json:"1010,omitemtpy"`
// GPS ephemeris.
Type1019 *RTCMMessageType `mapstructure:"1019,omitemtpy" json:"1019,omitemtpy"`
// GLONASS ephemeris.
Type1020 *RTCMMessageType `mapstructure:"1020,omitemtpy" json:"1020,omitemtpy"`
// The type 7 Multiple Signal Message format for Europes Galileo system
Type1097 *RTCMMessageType `mapstructure:"1097,omitemtpy" json:"1097,omitemtpy"`
// Full SBAS pseudo-ranges, carrier phases, Doppler and signal strength (high resolution)
Type1107 *RTCMMessageType `mapstructure:"1107,omitemtpy" json:"1107,omitemtpy"`
// Full QZSS pseudo-ranges, carrier phases, Doppler and signal strength (high resolution)
Type1117 *RTCMMessageType `mapstructure:"1117,omitemtpy" json:"1117,omitemtpy"`
// Full BeiDou pseudo-ranges, carrier phases, Doppler and signal strength (high resolution)
Type1127 *RTCMMessageType `mapstructure:"1127,omitemtpy" json:"1127,omitemtpy"`
}
// RTCMMessageType -
type RTCMMessageType struct {
Frequency *string `mapstructure:"frequency,omitempty" json:"frequency,omitempty"`
Enabled *bool `mapstructure:"enabled,omitempty" json:"enabled,omitempty"`
}
// Logging -
type Logging struct {
Correction *LoggingService `mapstructure:"correction,omitempty" json:"correction,omitempty"`
Interval *int `mapstructure:"interval,omitempty" json:"interval,omitempty"`
Solution *LoggingService `mapstructure:"solution,omitempty" json:"solution,omitempty"`
Raw *LoggingService `mapstructure:"raw,omitempty" json:"raw,omitempty"`
Base *LoggingService `mapstructure:"base,omitempty" json:"base,omitempty"`
Overwrite *bool `mapstructure:"overwrite,omitempty" json:"overwrite,omitempty"`
}
// LoggingService -
type LoggingService struct {
Started *bool `mapstructure:"started,omitempty" json:"started,omitempty"`
Version *string `mapstructure:"version,omitempty" json:"version,omitempty"`
Format *string `mapstructure:"format,omitempty" json:"format,omitempty"`
}
// Bluetooth -
type Bluetooth struct {
Enabled *bool `mapstructure:"enabled,omitempty" json:"enabled,omitempty"`
Discoverable *bool `mapstructure:"discoverable,omitempty" json:"discoverable,omitempty"`
Pin *int `mapstructure:"pin,omitempty" json:"pin,omitempty"`
}
// LoRa -
type LoRa struct {
AirRate *string `mapstructure:"air rate,omitempty" json:"air rate,omitempty"`
Frequency *float64 `mapstructure:"frequency,omitempty" json:"frequency,omitempty"`
OutputPower *string `mapstructure:"output power,omitempty" json:"output power,omitempty"`
}
// Constraints -
type Constraints struct {
LoRa *LoRaConstraints `mapstructure:"lora,omitempty" json:"lora,omitempty"`
}
// LoRaConstraints -
type LoRaConstraints struct {
Frequency [][]int `mapstructure:"frequency,omitempty" json:"frequency,omitempty"`
}
// LoRaFrequencyRange -
type LoRaFrequencyRange struct {
Min *int `mapstructure:"min,omitempty" json:"min,omitempty"`
Max *int `mapstructure:"max,omitempty" json:"max,omitempty"`
}

View File

@ -0,0 +1,91 @@
package reachview
import (
"context"
"testing"
"time"
"forge.cadoles.com/Pyxis/orion/emlid"
)
func TestReachViewConfiguration(t *testing.T) {
if !*runReachViewIntegrationTests {
t.Skip("To run this test, use: go test -reachview-integration")
}
client := NewClient(
emlid.WithStandardLogger(),
emlid.WithEndpoint(*reachHost, 80),
)
if err := client.Connect(); err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
config, err := client.Configuration(ctx)
if err != nil {
t.Error(err)
}
if config == nil {
t.Fatal("config should not be nil")
}
if config.RTKSettings == nil {
t.Fatal("config.RTKSettings should not be nil")
}
defer client.Close()
}
func TestReachViewApplyConfiguration(t *testing.T) {
if !*runReachViewIntegrationTests {
t.Skip("To run this test, use: go test -reachview-integration")
}
client := NewClient(
emlid.WithStandardLogger(),
emlid.WithEndpoint(*reachHost, 80),
)
if err := client.Connect(); err != nil {
t.Fatal(err)
}
ctx, configurationCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer configurationCancel()
config, err := client.Configuration(ctx)
if err != nil {
t.Error(err)
}
config.RTKSettings.PositionningMode = String("single")
ctx, applyConfCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer applyConfCancel()
result, config, err := client.ApplyConfiguration(ctx, config)
if err != nil {
t.Error(err)
}
if config == nil {
t.Fatal("config should not be nil")
}
if config.RTKSettings == nil {
t.Fatal("config.RTKSettings should not be nil")
}
if g, e := result, ConfigurationApplySuccess; g != e {
t.Errorf("result: got '%s', expected '%s'", g, e)
}
defer client.Close()
}

56
emlid/reachview/reboot.go Normal file
View File

@ -0,0 +1,56 @@
package reachview
import (
"context"
"sync"
"forge.cadoles.com/Pyxis/golang-socketio"
"github.com/pkg/errors"
)
const (
eventReboot = "reboot"
)
// Reboot asks the ReachRS module to reboot
func (c *Client) Reboot(ctx context.Context, waitDisconnect bool) error {
var err error
var wg sync.WaitGroup
if waitDisconnect {
var once sync.Once
done := func() {
c.Off(gosocketio.OnDisconnection)
wg.Done()
}
wg.Add(1)
go func() {
<-ctx.Done()
err = ctx.Err()
once.Do(done)
}()
err = c.On(gosocketio.OnDisconnection, func(h *gosocketio.Channel) {
once.Do(done)
})
if err != nil {
return errors.Wrapf(err, "error while binding to '%s' event", gosocketio.OnDisconnection)
}
}
if err = c.Emit(eventReboot, nil); err != nil {
return err
}
if waitDisconnect {
wg.Wait()
}
return err
}

View File

@ -0,0 +1,33 @@
package reachview
import (
"context"
"testing"
"time"
"forge.cadoles.com/Pyxis/orion/emlid"
)
func TestClientReboot(t *testing.T) {
if !*runRebootTest {
t.Skip("To run this test, use: go test -reboot-test")
}
client := NewClient(
emlid.WithStandardLogger(),
emlid.WithEndpoint(*reachHost, 80),
)
if err := client.Connect(); err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := client.Reboot(ctx, true); err != nil {
t.Error(err)
}
defer client.Close()
}

10
emlid/reachview/rtk.go Normal file
View File

@ -0,0 +1,10 @@
package reachview
const (
eventRestartRTKLib = "restart rtklib"
)
// RestartRTKLib asks the ReachRS module to restart the RTKlib
func (c *Client) RestartRTKLib() error {
return c.Emit(eventRestartRTKLib, nil)
}

View File

@ -0,0 +1,18 @@
package reachview
import "flag"
var runReachViewIntegrationTests = flag.Bool(
"reachview-integration", false,
"Run the 'ReachView' integration tests (in addition to the unit tests)",
)
var runRebootTest = flag.Bool(
"reboot-test", false,
"Run the updater 'Reboot' test (in addition to the unit tests)",
)
var reachHost = flag.String(
"reach-host", "192.168.42.1",
"The Reach module host to use in integration tests",
)

18
emlid/updater/client.go Normal file
View File

@ -0,0 +1,18 @@
// Package updater provides an API to communicate with the ReachRS modules "Updater" application
package updater
import (
"forge.cadoles.com/Pyxis/orion/emlid"
"forge.cadoles.com/Pyxis/orion/emlid/common"
)
// Client is a ReachRS Updater client
type Client struct {
*common.Client
}
// NewClient returns a new ReachRS Updater client
func NewClient(opts ...emlid.OptionFunc) *Client {
client := common.NewClient(opts...)
return &Client{client}
}

View File

@ -0,0 +1,39 @@
package updater
import (
"context"
"log"
"time"
"forge.cadoles.com/Pyxis/orion/emlid"
)
func Example_usage() {
// Create a new Updater client instance
updater := NewClient(
emlid.WithEndpoint("192.168.42.1", 80), // Define the module endpoint
)
// Connect to the ReachRS Updater endpoint
if err := updater.Connect(); err != nil {
log.Fatal(err)
}
// We create a context for the API call with a 10 second delay
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Retrieve the Wifi networks
networks, err := updater.WifiNetworks(ctx)
if err != nil {
log.Fatal(err)
}
// Do something with network
for _, n := range networks {
log.Printf("Save WiFi network: SSID: '%s', Security: '%s'", n.SSID, n.Security)
}
// Dont forget to close the connection when you are done
defer updater.Close()
}

View File

@ -0,0 +1,56 @@
package updater
import (
"context"
"sync"
"forge.cadoles.com/Pyxis/golang-socketio"
"github.com/pkg/errors"
)
const (
eventReboot = "reboot now"
)
// RebootNow asks the ReachRS module to reboot now
func (c *Client) RebootNow(ctx context.Context, waitDisconnect bool) error {
var err error
var wg sync.WaitGroup
if waitDisconnect {
var once sync.Once
done := func() {
c.Off(gosocketio.OnDisconnection)
wg.Done()
}
wg.Add(1)
go func() {
<-ctx.Done()
err = ctx.Err()
once.Do(done)
}()
err = c.On(gosocketio.OnDisconnection, func(h *gosocketio.Channel) {
once.Do(done)
})
if err != nil {
return errors.Wrapf(err, "error while binding to '%s' event", gosocketio.OnDisconnection)
}
}
if err = c.Emit(eventReboot, nil); err != nil {
return err
}
if waitDisconnect {
wg.Wait()
}
return err
}

View File

@ -0,0 +1,33 @@
package updater
import (
"context"
"testing"
"time"
"forge.cadoles.com/Pyxis/orion/emlid"
)
func TestClientRebootNow(t *testing.T) {
if !*runRebootTest {
t.Skip("To run this test, use: go test -reboot-test")
}
client := NewClient(
emlid.WithStandardLogger(),
emlid.WithEndpoint(*reachHost, 80),
)
if err := client.Connect(); err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := client.RebootNow(ctx, true); err != nil {
t.Error(err)
}
defer client.Close()
}

View File

@ -0,0 +1,23 @@
package updater
import "context"
const (
eventIsReceiverUpgradeAvailable = "is receiver upgrade available"
eventReceiverUpgradeAvailable = "receiver upgrade available"
)
type receiverUpgreAvailable struct {
Available bool `json:"available"`
Running bool `json:"running"`
}
// ReceiverUpgradeAvailable checks if an upgrade is avaialable/running for the ReachRS module
func (c *Client) ReceiverUpgradeAvailable(ctx context.Context) (bool, bool, error) {
res := &receiverUpgreAvailable{}
if err := c.ReqResp(ctx, eventIsReceiverUpgradeAvailable, nil, eventReceiverUpgradeAvailable, res); err != nil {
return false, false, err
}
c.Logf("receiver upgrade result: available: %v, running: %v", res.Available, res.Running)
return res.Available, res.Running, nil
}

View File

@ -0,0 +1,34 @@
package updater
import (
"context"
"testing"
"time"
"forge.cadoles.com/Pyxis/orion/emlid"
)
func TestClientReceiverUpgradeAvailable(t *testing.T) {
if !*runUpdaterIntegrationTests {
t.Skip("To run this test, use: go test -updater-integration")
}
client := NewClient(
emlid.WithStandardLogger(),
emlid.WithEndpoint(*reachHost, 80),
)
if err := client.Connect(); err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, _, err := client.ReceiverUpgradeAvailable(ctx)
if err != nil {
t.Error(err)
}
defer client.Close()
}

View File

@ -0,0 +1,27 @@
package updater
import "context"
const (
eventGetTestResults = "get test results"
eventTestResults = "test results"
)
// TestResults are the ReachRS module's test results
//
type TestResults struct {
Device string `json:"device"`
Lora bool `json:"lora"`
MPU bool `json:"mpu"`
STC bool `json:"stc"`
UBlox bool `json:"u-blox"`
}
// TestResults returns the ReachRS module tests results
func (c *Client) TestResults(ctx context.Context) (*TestResults, error) {
res := &TestResults{}
if err := c.ReqResp(ctx, eventGetTestResults, nil, eventTestResults, res); err != nil {
return nil, err
}
return res, nil
}

View File

@ -0,0 +1,38 @@
package updater
import (
"context"
"testing"
"time"
"forge.cadoles.com/Pyxis/orion/emlid"
)
func TestClientTestResults(t *testing.T) {
if !*runUpdaterIntegrationTests {
t.Skip("To run this test, use: go test -updater-integration")
}
client := NewClient(
emlid.WithStandardLogger(),
emlid.WithEndpoint(*reachHost, 80),
)
if err := client.Connect(); err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
results, err := client.TestResults(ctx)
if err != nil {
t.Error(err)
}
if g, e := results.Device, "ReachRS"; g != e {
t.Errorf("results.Device: got '%s', expected '%s'", g, e)
}
defer client.Close()
}

View File

@ -0,0 +1,23 @@
package updater
import "context"
const (
eventGetTimeSyncStatus = "get time sync status"
eventTimeSyncResults = "time sync status"
)
type timeSyncStatus struct {
Status bool `json:"status"`
}
// TimeSynced returns the ReachRS module time synchronization status.
// A true response means that the module has synchronized its clock.
func (c *Client) TimeSynced(ctx context.Context) (bool, error) {
res := &timeSyncStatus{}
if err := c.ReqResp(ctx, eventGetTimeSyncStatus, nil, eventTimeSyncResults, res); err != nil {
return false, err
}
c.Logf("time sync result: %v", res.Status)
return res.Status, nil
}

View File

@ -0,0 +1,34 @@
package updater
import (
"context"
"testing"
"time"
"forge.cadoles.com/Pyxis/orion/emlid"
)
func TestClientTimeSync(t *testing.T) {
if !*runUpdaterIntegrationTests {
t.Skip("To run this test, use: go test -updater-integration")
}
client := NewClient(
emlid.WithStandardLogger(),
emlid.WithEndpoint(*reachHost, 80),
)
if err := client.Connect(); err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err := client.TimeSynced(ctx)
if err != nil {
t.Error(err)
}
defer client.Close()
}

28
emlid/updater/update.go Normal file
View File

@ -0,0 +1,28 @@
package updater
import "context"
const (
eventUpdate = "update"
eventOPKGUpdateResult = "opkg update result"
)
// UpdateStatus embeds informations about update status
type UpdateStatus struct {
Active bool `json:"active"`
Locked bool `json:"locked"`
State string `json:"state"`
}
// Update asks the ReachRS module to start an OPKG update
func (c *Client) Update(ctx context.Context) (*UpdateStatus, error) {
res := &UpdateStatus{}
if err := c.ReqResp(ctx, eventUpdate, nil, eventOPKGUpdateResult, res); err != nil {
return nil, err
}
c.Logf(
"opkg update result: active: %v, state: %v, locked: %v",
res.Active, res.State, res.Locked,
)
return res, nil
}

View File

@ -0,0 +1,34 @@
package updater
import (
"context"
"testing"
"time"
"forge.cadoles.com/Pyxis/orion/emlid"
)
func TestClientOPKGUpdate(t *testing.T) {
if !*runUpdaterIntegrationTests {
t.Skip("To run this test, use: go test -updater-integration")
}
client := NewClient(
emlid.WithStandardLogger(),
emlid.WithEndpoint(*reachHost, 80),
)
if err := client.Connect(); err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err := client.Update(ctx)
if err != nil {
t.Error(err)
}
defer client.Close()
}

View File

@ -0,0 +1,23 @@
package updater
import "flag"
var runUpdaterIntegrationTests = flag.Bool(
"updater-integration", false,
"Run the 'Updater' integration tests (in addition to the unit tests)",
)
var runJoinNetworkTest = flag.Bool(
"updater-join-network-test", false,
"Run the updater 'JoinWiFiNetwork' test (in addition to the unit tests)",
)
var runRebootTest = flag.Bool(
"reboot-test", false,
"Run the updater 'Reboot' test (in addition to the unit tests)",
)
var reachHost = flag.String(
"reach-host", "192.168.42.1",
"The Reach module host to use in integration tests",
)

View File

@ -0,0 +1,116 @@
package updater
import (
"context"
"sync"
"forge.cadoles.com/Pyxis/golang-socketio"
"github.com/pkg/errors"
)
const (
eventGetSavedWifiNetworks = "get saved wifi networks"
eventSavedWifiNetworkResults = "wifi saved networks results"
eventAddWifiNetwork = "add new network"
eventAddWifiNetworkResults = "add network results"
eventRemoveWifiNetwork = "remove network"
eventRemoveWifiNetworkResults = "remove network results"
eventConnectToNetwork = "connect to network"
)
// WifiSecurity is a WiFi network security algorithm
type WifiSecurity string
const (
// SecurityWEP WEP wifi network
SecurityWEP WifiSecurity = "wep"
// SecurityWPAPSK WPA(2)-PSK wifi network
SecurityWPAPSK WifiSecurity = "wpa-psk"
// SecurityOpen Open wifi network
SecurityOpen WifiSecurity = "open"
)
// WifiNetwork is a ReachRS module wifi network
type WifiNetwork struct {
SSID string `json:"ssid"`
Password string `json:"password"`
Security WifiSecurity `json:"security"`
Identity string `json:"identity"`
Visible bool `json:"is_visible"`
Connected bool `json:"is_connected"`
Added bool `json:"is_added"`
}
// WifiNetworks returns the ReachRS module wifi networks
func (c *Client) WifiNetworks(ctx context.Context) ([]WifiNetwork, error) {
res := make([]WifiNetwork, 0)
if err := c.ReqResp(ctx, eventGetSavedWifiNetworks, nil, eventSavedWifiNetworkResults, &res); err != nil {
return nil, err
}
return res, nil
}
// AddWifiNetwork asks the ReachRS module to save the given wifi network informations
func (c *Client) AddWifiNetwork(ctx context.Context, ssid string, security WifiSecurity, password string) (bool, error) {
res := false
network := &WifiNetwork{
SSID: ssid,
Security: security,
Password: password,
}
if err := c.ReqResp(ctx, eventAddWifiNetwork, network, eventAddWifiNetworkResults, &res); err != nil {
return false, err
}
return res, nil
}
// RemoveWifiNetwork asks the ReachRS module to remove the given WiFi network
func (c *Client) RemoveWifiNetwork(ctx context.Context, ssid string) (bool, error) {
res := false
if err := c.ReqResp(ctx, eventRemoveWifiNetwork, ssid, eventRemoveWifiNetworkResults, &res); err != nil {
return false, err
}
return res, nil
}
// JoinWifiNetwork asks the ReachRS module to join the given WiFi network
func (c *Client) JoinWifiNetwork(ctx context.Context, ssid string, waitDisconnect bool) error {
var err error
var wg sync.WaitGroup
if waitDisconnect {
var once sync.Once
done := func() {
c.Off(gosocketio.OnDisconnection)
wg.Done()
}
wg.Add(1)
go func() {
<-ctx.Done()
err = ctx.Err()
once.Do(done)
}()
err = c.On(gosocketio.OnDisconnection, func(h *gosocketio.Channel) {
once.Do(done)
})
if err != nil {
return errors.Wrapf(err, "error while binding to '%s' event", gosocketio.OnDisconnection)
}
}
if err := c.Emit(eventConnectToNetwork, ssid); err != nil {
return errors.Wrapf(err, "error while emitting '%s' event", eventConnectToNetwork)
}
if waitDisconnect {
wg.Wait()
}
return nil
}

View File

@ -0,0 +1,145 @@
package updater
import (
"context"
"fmt"
"math/rand"
"testing"
"time"
"forge.cadoles.com/Pyxis/orion/emlid"
)
func TestClientSavedWiFiNetworks(t *testing.T) {
if !*runUpdaterIntegrationTests {
t.Skip("To run this test, use: go test -updater-integration")
}
client := NewClient(
emlid.WithStandardLogger(),
emlid.WithEndpoint(*reachHost, 80),
)
if err := client.Connect(); err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err := client.WifiNetworks(ctx)
if err != nil {
t.Error(err)
}
defer client.Close()
}
func TestClientCRUDWiFiNetwork(t *testing.T) {
if !*runUpdaterIntegrationTests {
t.Skip("To run this test, use: go test -updater-integration")
}
client := NewClient(
emlid.WithStandardLogger(),
emlid.WithEndpoint(*reachHost, 80),
)
if err := client.Connect(); err != nil {
t.Fatal(err)
}
ssid := fmt.Sprintf("wifi_test_%d", rand.Uint32())
ctx := context.Background()
addWifiContext, addWifiCancel := context.WithTimeout(ctx, 5*time.Second)
defer addWifiCancel()
done, err := client.AddWifiNetwork(addWifiContext, ssid, SecurityOpen, "")
if err != nil {
t.Error(err)
}
if g, e := done, true; g != e {
t.Errorf("AddWifiNetwork() -> done: got '%v', expected '%v'", g, e)
}
wifiContext, wifiCancel := context.WithTimeout(ctx, 5*time.Second)
defer wifiCancel()
networks, err := client.WifiNetworks(wifiContext)
if err != nil {
t.Error(err)
}
found := false
for _, n := range networks {
if n.SSID == ssid {
found = true
break
}
}
if g, e := found, true; g != e {
t.Errorf("wifi network '%s' should exists", ssid)
}
removeWifiContext, removeWifiCancel := context.WithTimeout(ctx, 5*time.Second)
defer removeWifiCancel()
done, err = client.RemoveWifiNetwork(removeWifiContext, ssid)
if err != nil {
t.Error(err)
}
if g, e := done, true; g != e {
t.Errorf("RemoveWifiNetwork() -> done: got '%v', expected '%v'", g, e)
}
defer client.Close()
}
func TestClientWifiNetworkJoin(t *testing.T) {
if !*runUpdaterIntegrationTests {
t.Skip("To run this test, use: go test -updater-integration")
}
if !*runJoinNetworkTest {
t.Skip("To run this test, use: go test -updater-join-network-test")
}
client := NewClient(
emlid.WithStandardLogger(),
emlid.WithEndpoint(*reachHost, 80),
)
if err := client.Connect(); err != nil {
t.Fatal(err)
}
ssid := fmt.Sprintf("wifi_test_%d", rand.Uint32())
ctx := context.Background()
addWifiContext, addWifiCancel := context.WithTimeout(ctx, 5*time.Second)
defer addWifiCancel()
done, err := client.AddWifiNetwork(addWifiContext, ssid, SecurityOpen, "")
if err != nil {
t.Error(err)
}
if g, e := done, true; g != e {
t.Errorf("AddWifiNetwork() -> done: got '%v', expected '%v'", g, e)
}
joinWifiContext, joinWifiCancel := context.WithTimeout(ctx, 5*time.Second)
defer joinWifiCancel()
if err := client.JoinWifiNetwork(joinWifiContext, ssid, true); err != nil {
t.Error(err)
}
time.Sleep(5 * time.Second)
defer client.Close()
}

13
emlid/util_test.go Normal file
View File

@ -0,0 +1,13 @@
package emlid
import "flag"
var runDiscoveryIntegrationTests = flag.Bool(
"discovery-integration", false,
"Run the 'Discovery' integration tests (in addition to the unit tests)",
)
var reachHost = flag.String(
"reach-host", "192.168.42.1",
"The Reach module host to use in integration tests",
)

93
example/base-pos/main.go Normal file
View File

@ -0,0 +1,93 @@
package main
import (
"context"
"flag"
"log"
"time"
"forge.cadoles.com/Pyxis/orion/emlid"
"forge.cadoles.com/Pyxis/orion/emlid/reachview"
)
var (
host = "192.168.42.1"
)
func init() {
flag.StringVar(&host, "host", host, "ReachRS module host")
}
func main() {
flag.Parse()
c := connect()
defer c.Close()
log.Println("updating base manual position")
// 42["apply configuration",{"base mode":{"base coordinates":{"coordinates":["0","0","0"],"antenna offset":{"up":"0","east":"0","north":"0"},"format":"llh","mode":"fix-and-hold","accumulation":"2"}}}]
config := &reachview.Configuration{
BaseMode: &reachview.BaseMode{
BaseCoordinates: &reachview.BaseCoordinates{
Coordinates: []*string{
reachview.String("2"),
reachview.String("0"),
reachview.String("0"),
},
AntennaOffset: &reachview.AntennaOffset{
East: reachview.String("0"),
North: reachview.String("0"),
Up: reachview.String("2"),
},
Format: reachview.BaseCoordinatesFormatLLH,
Mode: reachview.String("manual"),
},
},
}
applyConfiguration(c, config)
log.Println("done")
}
func connect() *reachview.Client {
c := reachview.NewClient(
emlid.WithEndpoint(host, 80),
)
log.Printf("connecting to module '%s'", host)
if err := c.Connect(); err != nil {
log.Fatal(err)
}
log.Println("connected")
return c
}
func applyConfiguration(c *reachview.Client, config *reachview.Configuration) {
ctx, applyConfCancel := context.WithTimeout(context.Background(), 20*time.Second)
defer applyConfCancel()
result, _, err := c.ApplyConfiguration(ctx, config)
if err != nil {
log.Fatal(err)
}
if result != reachview.ConfigurationApplySuccess {
log.Fatal("configuration update failed !")
}
log.Println("restarting rtklib")
if err := c.RestartRTKLib(); err != nil {
log.Fatal(err)
}
}

19
go.mod Normal file
View File

@ -0,0 +1,19 @@
module forge.cadoles.com/Pyxis/orion
go 1.15
require (
forge.cadoles.com/Pyxis/golang-socketio v0.0.0-20180919100209-bb857ced6b95
github.com/blang/semver/v4 v4.0.0
github.com/caarlos0/env v3.5.0+incompatible
github.com/cenkalti/backoff v2.0.0+incompatible // indirect
github.com/davecgh/go-spew v1.1.1
github.com/go-chi/chi v3.3.3+incompatible
github.com/golangci/golangci-lint v1.32.2 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/grandcat/zeroconf v0.0.0-20180329153754-df75bb3ccae1
github.com/mitchellh/mapstructure v1.1.2
github.com/pkg/errors v0.9.1
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 // indirect
)

637
go.sum Normal file
View File

@ -0,0 +1,637 @@
4d63.com/gochecknoglobals v0.0.0-20201008074935-acfc0b28355a h1:wFEQiK85fRsEVF0CRrPAos5LoAryUsIX1kPW/WrIqFw=
4d63.com/gochecknoglobals v0.0.0-20201008074935-acfc0b28355a/go.mod h1:wfdC5ZjKSPr7CybKEcgJhUOgeAQW1+7WcyK8OvUilfo=
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=
forge.cadoles.com/Pyxis/golang-socketio v0.0.0-20180919100209-bb857ced6b95 h1:o3G5+9RjczCK1xAYFaRMknk1kY9Ule6PNfiW6N6hEpg=
forge.cadoles.com/Pyxis/golang-socketio v0.0.0-20180919100209-bb857ced6b95/go.mod h1:I6kYOFWNkFlNeQLI7ZqfTRz4NdPHZxX0Bzizmzgchs0=
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/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5 h1:XTrzB+F8+SpRmbhAH8HLxhiiG6nYNwaBZjrFps1oWEk=
github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQee8Us=
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/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
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/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/blang/semver v1.1.0 h1:ol1rO7QQB5uy7umSNV7VAmLugfLRD+17sYJujRNYPhg=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/bombsimon/wsl/v3 v3.1.0 h1:E5SRssoBgtVFPcYWUOFJEcgaySgdtTNYzsSKDOY7ss8=
github.com/bombsimon/wsl/v3 v3.1.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc=
github.com/caarlos0/env v3.4.0+incompatible h1:FRwBdvENjLHZoUbFnULnFss9wKtcapdaM35DfxiTjeM=
github.com/caarlos0/env v3.4.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y=
github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs=
github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y=
github.com/cenkalti/backoff v2.0.0+incompatible h1:5IIPUHhlnUZbcHQsQou5k1Tn58nJkeJL9U+ig5CHJbY=
github.com/cenkalti/backoff v2.0.0+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
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.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
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/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/daixiang0/gci v0.2.4 h1:BUCKk5nlK2m+kRIsoj+wb/5hazHvHeZieBKWd9Afa8Q=
github.com/daixiang0/gci v0.2.4/go.mod h1:+AV8KmHTGxxwp/pY84TLQfFKp2vuKXXJVzF3kD/hfR4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denis-tingajkin/go-header v0.3.1 h1:ymEpSiFjeItCy1FOP+x0M2KdCELdEAHUsNa8F+hHc6w=
github.com/denis-tingajkin/go-header v0.3.1/go.mod h1:sq/2IxMhaZX+RRcgHfCRx/m0M5na0fBt4/CRe7Lrji0=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
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/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
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-chi/chi v3.3.3+incompatible h1:KHkmBEMNkwKuK4FdQL7N2wOeB9jnIx7jR5wsuSBEFI8=
github.com/go-chi/chi v3.3.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-critic/go-critic v0.5.2 h1:3RJdgf6u4NZUumoP8nzbqiiNT8e1tC2Oc7jlgqre/IA=
github.com/go-critic/go-critic v0.5.2/go.mod h1:cc0+HvdE3lFpqLecgqMaJcvWWH77sLdBp+wLGPM1Yyo=
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-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 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g=
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8=
github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ=
github.com/go-toolsmith/astequal v1.0.0 h1:4zxD8j3JRFNyLN46lodQuqz3xdKSrur7U/sr0SDS/gQ=
github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
github.com/go-toolsmith/astfmt v1.0.0 h1:A0vDDXt+vsvLEdbMFJAUBI/uTbRw1ffOPnxsILnFL6k=
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 v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg=
github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI=
github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc=
github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4=
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 h1:8xdsa1+FSIH/RhEkgnD1j2CJOy5mNllW1Q9tRiYwvlk=
github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo=
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY=
github.com/gofrs/flock v0.8.0/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/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0=
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 h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM=
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 h1:YYWNAGTKWhKpcLLt7aSj/odlKrSrelQwlovBpDuf19w=
github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0=
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 h1:9kfjN3AdxcbsZBf8NjltjWihK2QfBBBZuv91cMFfDHw=
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8=
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3 h1:pe9JHs3cHHDQgOFXJJdYkK6fLz2PWyYtP4hthoCMvs8=
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 h1:pXTK/gkVNs7Zyy7WKgLXmpQ5bHTrq5GDsp8R9Qs67g0=
github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU=
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks=
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
github.com/golangci/golangci-lint v1.32.2 h1:CgIeFWTLJ3Nt1w/WU1RO351j/CjN6LIVjppbJfI9nMk=
github.com/golangci/golangci-lint v1.32.2/go.mod h1:ydr+IqtIVyAh72L16aK0bNdNg/YGa+AEgdbKj9MluzI=
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc h1:gLLhTLMk2/SutryVJ6D4VZCU3CUqr8YloG7FPIBWFpI=
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU=
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA=
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA=
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770 h1:EL/O5HGrF7Jaq0yNhBLucz9hTuRzj2LdwGBOaENgxIk=
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA=
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21 h1:leSNB7iYzLYSSx3J/s5sVf4Drkc68W2wm4Ixh/mr0us=
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI=
github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0 h1:HVfrLniijszjS1aiNg8JbBMO2+E1WIQ+j/gL4SQqGPg=
github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4=
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys=
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.1/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/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.3.1/go.mod h1:R3ogXq2B9rTbXoSHJ1HyUVAZ3poOJHpd9nQmyGZsfvQ=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/gostaticanalysis/analysisutil v0.1.0 h1:E4c8Y1EQURbBEAHoXc/jBTK7Np14ArT8NPUiSFOl9yc=
github.com/gostaticanalysis/analysisutil v0.1.0/go.mod h1:dMhHRU9KTiDcuLGdy87/2gTR8WruwYZrKdRq9m1O6uw=
github.com/gostaticanalysis/comment v1.3.0 h1:wTVgynbFu8/nz6SGgywA0TcyIoAVsYc7ai/Zp5xNGlw=
github.com/gostaticanalysis/comment v1.3.0/go.mod h1:xMicKDx7XRXYdVwY9f9wQpDJVnqWxw9wCauCMKp+IBI=
github.com/grandcat/zeroconf v0.0.0-20180329153754-df75bb3ccae1 h1:VSELJSxQlpi1bz4ZwT+93hPpzNLRcgytLr77iVRJpcE=
github.com/grandcat/zeroconf v0.0.0-20180329153754-df75bb3ccae1/go.mod h1:YjKB0WsLXlMkO9p+wGTCoPIDGRJH0mz7E526PxkQVxI=
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 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a h1:GmsqmapfzSJkm28dhRoHz2tLRbJmqhU86IPgBtN3mmk=
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 h1:jNYPNLe3d8smommaoQlK7LOA5ESyUJJ+Wf79ZtA7Vp4=
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/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 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
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/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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kyoh86/exportloopref v0.1.7 h1:u+iHuTbkbTS2D/JP7fCuZDo/t3rBVGo3Hf58Rc+lQVY=
github.com/kyoh86/exportloopref v0.1.7/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.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/maratori/testpackage v1.0.1 h1:QtJ5ZjqapShm0w5DosRjg0PRlSdAdlx+W6cCKoALdbQ=
github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU=
github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb h1:RHba4YImhrUVQDHUCe2BNSOz4tVy2yGyXhvYDvxGgeE=
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.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/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 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
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/mbilski/exhaustivestruct v1.1.0 h1:4ykwscnAFeHJruT+EY3M3vdeP8uXMh0VV2E61iR7XD8=
github.com/mbilski/exhaustivestruct v1.1.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc=
github.com/miekg/dns v1.0.12 h1:814rTNaw7Q7pGncpSEDT06YS8rdGmpUEnKgpQzctJsk=
github.com/miekg/dns v1.0.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
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/moricho/tparallel v0.2.1 h1:95FytivzT6rYzdJLdtfn6m1bfFJylOJK41+lgv/EHf4=
github.com/moricho/tparallel v0.2.1/go.mod h1:fXEIZxG2vdfl0ZF8b42f5a78EhjjD5mX8qUplsoSU4k=
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 h1:+yOViDGhg8ygGrmII72nV9B/zGxY188TYpfolntsaPw=
github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c=
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E=
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nishanths/exhaustive v0.1.0 h1:kVlMw8h2LHPMGUVqUj6230oQjjTMFjwcZrnkhXzFfl8=
github.com/nishanths/exhaustive v0.1.0/go.mod h1:S1j9110vxV1ECdCudXRkeMnFQ/DQk9ajLT0Uf2MYZQQ=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
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 h1:CdDQnGF8Nq9ocOS/xlSptM1N3BbrA6/kmaep5ggwaIA=
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
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/polyfloyd/go-errorlint v0.0.0-20201006195004-351e25ade6e3 h1:Amgs0nbayPhBNGh1qPqqr2e7B2qNAcBgRjnBH/lmn8k=
github.com/polyfloyd/go-errorlint v0.0.0-20201006195004-351e25ade6e3/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw=
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.2.0 h1:UOVMyH2EKkxIfzrULvA9n/tO+HtEhqD9mrLSWMr5FwU=
github.com/quasilyte/go-ruleguard v0.2.0/go.mod h1:2RT/tf0Ce0UDj5y243iWKosQogJd8+1G3Rs2fxmlYnw=
github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95 h1:L8QM9bvf68pVdQ3bCFZMDmnt9yqcMBro1pC7F+IPYMY=
github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=
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/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryancurrah/gomodguard v1.1.0 h1:DWbye9KyMgytn8uYpuHkwf0RHqAYO6Ay/D0TbCpPtVU=
github.com/ryancurrah/gomodguard v1.1.0/go.mod h1:4O8tr7hBODaGE6VIhfJDHcwzh5GUccKSJBU0UMXJFVM=
github.com/ryanrolds/sqlclosecheck v0.3.0 h1:AZx+Bixh8zdUBxUA1NxbxVAS78vTPq4rCb8OUZI9xFw=
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/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/securego/gosec/v2 v2.5.0 h1:kjfXLeKdk98gBe2+eYRFMpC4+mxmQQtbidpiiOQ69Qc=
github.com/securego/gosec/v2 v2.5.0/go.mod h1:L/CDXVntIff5ypVHIkqPXbtRpJiNCh6c6Amn68jXDjo=
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU=
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs=
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/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
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 h1:VC1Qhl6Oxx9vvWo3UDgrGXYCeKCe3Wbw7qAWL6FrmTY=
github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI=
github.com/sourcegraph/go-diff v0.6.1 h1:hmA1LzxW0n1c3Q4YbrFgg4P99GSnebYa3x8gr0HZqLQ=
github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/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 v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
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.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/ssgreg/nlreturn/v2 v2.1.0 h1:6/s4Rc49L6Uo6RLjhWZGBpWWjfzk2yrf1nIW8m4wgVA=
github.com/ssgreg/nlreturn/v2 v2.1.0/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
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 h1:Xr9gkxfOP0KQWXKNqmwe8vEeSUiUj4Rlee9CMVX2ZUQ=
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM=
github.com/tetafro/godot v0.4.9 h1:dSOiuasshpevY73eeI3+zaqFnXSBKJ3mvxbyhh54VRo=
github.com/tetafro/godot v0.4.9/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0=
github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e h1:RumXZ56IrCj4CL+g1b9OL/oH0QnsF976bC8xQFYUD5Q=
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/tomarrell/wrapcheck v0.0.0-20200807122107-df9e8bcb914d h1:3EZyvNUMsGD1QA8cu0STNn1L7I77rvhf2IhOcHYQhSw=
github.com/tomarrell/wrapcheck v0.0.0-20200807122107-df9e8bcb914d/go.mod h1:yiFB6fFoV7saXirUGfuK+cPtUh4NX/Hf5y2WC2lehu0=
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa h1:RC4maTWLKKwb7p1cnoygsbKIgNlJqSYBeAFON3Ar8As=
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iLA=
github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
github.com/ultraware/whitespace v0.0.4 h1:If7Va4cM03mpgrNH9k49/VOicWpGoG70XPBFFODYDsg=
github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
github.com/uudashr/gocognit v1.0.1 h1:MoG2fZ0b/Eo7NXoIwCVFLG5JED3qgQz5/NEE+rOsjPs=
github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA=
github.com/valyala/quicktemplate v1.6.3/go.mod h1:fwPzK2fHuYEODzJ9pkw0ipCPNHZ2tD5KW4lOuSdPKzY=
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/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
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-20181001203147-e3636079e1a4 h1:Vk3wNqEZwyGyei9yq5ekj7frek2u7HUfffJ1/opblzc=
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o=
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
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 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
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-20180911220305-26e67e76b6c3 h1:czFLhve3vsQetD6JOJ8NZZvGQIXlnN3/yXxbT6/awxI=
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/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-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
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-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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
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 h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20181005133103-4497e2df6f9e h1:EfdBzeKbFSvOjoIqSZcfS8wp0FBLokGBEs9lz1OtSg0=
golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e/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-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-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
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-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 h1:bNEHhJCnrwMKNMmOx3yAynp5vs5/gRy+XWFtZFu7NBM=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
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 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
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-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-20190307163923-6a08e3108db3/go.mod h1:25r3+/G6/xytQM8iWZKq3Hn0kr0rgFKPUNVEL/dr3z4=
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-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-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-20191029190741-b9c20aec41a5/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-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-20200410194907-79a7a3126eef/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-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200624225443-88f3c62a19ff/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-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200731060945-b5fad4ed8dd6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200831203904-5a2aa26beb65/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201001104356-43ebab892c4c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201011145850-ed2f50202694/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201013201025-64a9e34f3752 h1:2ntEwh02rqo2jSsrYmp4yKHHjh0CbXP3ZtSUetSB+q8=
golang.org/x/tools v0.0.0-20201013201025-64a9e34f3752/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
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=
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.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/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/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/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 h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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.6 h1:W18jzjh8mfPez+AwGLxmOImucz/IFjpNlrKVnaj2YVc=
honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY=
mvdan.cc/gofumpt v0.0.0-20200802201014-ab5a8192947d h1:t8TAw9WgTLghti7RYkpPmqk4JtQ3+wcP5GgZqgWeWLQ=
mvdan.cc/gofumpt v0.0.0-20200802201014-ab5a8192947d/go.mod h1:bzrjFmaD6+xqohD3KYP0H2FEuxknnBmyyOxdhLdaIws=
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I=
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo=
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7 h1:kAREL6MPwpsk1/PQPFD3Eg7WAQR5mPTWZJaBiG5LDbY=
mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7/go.mod h1:HGC5lll35J70Y5v7vCGb9oLhHoScFwkHDJm/05RdSTc=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

17
misc/git-hooks/pre-commit Executable file
View File

@ -0,0 +1,17 @@
#!/bin/bash
set -eo pipefail
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
CHANGESET=$(git diff --cached --name-only --diff-filter=ACM)
function lint_go_files {
echo "Linting modified Go files..."
( cd "$DIR/../.." && make LINT_ARGS="--new-from-rev=HEAD~ ./..." lint )
}
function main {
lint_go_files
}
main

View File

@ -0,0 +1,823 @@
/**
* ReachView code is placed under the GPL license.
* Written by Egor Fedorov (egor.fedorov@emlid.com) and Danil Kramorov (danil.kramorov@emlid.com)
* Copyright (c) 2015-2018, Emlid Limited
* All rights reserved.
*
* If you are interested in using ReachView code as a part of a
* closed source project, please contact Emlid Limited (info@emlid.com).
*
* This file is part of ReachView.
*
* ReachView is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ReachView is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ReachView. If not, see <http://www.gnu.org/licenses/>.
*/
$(document).ready(function () {
requirejs(['socket', 'bootstrap_select'], function (io, bootstrap_select) {
var lostConnectionNoty;
var addConnectionNoty;
var removeConnetctionNoty;
var emptyRequiredModal = false;
var incorrectSymbolsNumber = false;
var incorrectSymbols = false;
var syncInterval;
var new_network = {};
var to_append = '';
var testPass = false;
var wifiPass = false;
var timePass = false;
var receiverPass = false;
var updatePass = false;
var currentVersion = false;
var availableReceiverVersion = false;
var availableVersion = false;
var opkgResult = false;
var receiverLocked = false;
var device = '';
var preventLostConnectionNoty = false;
var disconnect_msg = 'Lost connection with Reach. Please check your network, then try refreshing the page.';
$('.bootstrap-select').selectpicker();
$('.styled, .multiselect-container input').uniform({ radioClass: 'choice' });
$(document).on('click', '#create_new_network', function () {
$(this).parents('.modal-content').find('.required_field:visible').each(function () {
if ($.trim($(this).val()) === '') {
emptyRequiredModal = true;
return false;
}
});
if ($('#new_network_pass:visible').length !== 0) {
if ($('#new_network_pass').val().length < 8) {
incorrectSymbolsNumber = true;
} else {
incorrectSymbolsNumber = false;
}
} else {
incorrectSymbolsNumber = false;
}
if (emptyRequiredModal) {
$(this).parents('.modal-content').find('.required_field:visible')
.filter(function () { return $.trim($(this).val()) === ''; }).css('border', '1px solid red');
$(this).parents('.modal-content').find('.required_field')
.filter(function () { return $.trim($(this).val()) !== ''; }).css('border', '1px solid #ddd');
emptyRequiredModal = false;
} else {
$(this).parents('.modal-content').find('.required_field').css('border', '1px solid #ddd');
if (incorrectSymbols) {
$('.modal_add_warning').text('Incorrect symbols. Use only a-z, 1-9 characters.');
} else if (incorrectSymbolsNumber) {
$('.modal_add_warning').text('Password should contain at least 8 characters');
} else {
new_network['ssid'] = $('#new_network_name').val();
new_network['password'] = $('#new_network_pass').val();
new_network['security'] = $('#security_select').val();
new_network['identity'] = $('#new_network_identity').val();
$('#modal_network').modal('hide');
socket.emit('add new network', new_network);
}
}
});
$(document).on('change', '#security_select', function () {
$('#new_network_identity').removeClass('required_field');
$('#new_network_identity').parents('.form-group').css('display', 'none');
$('#new_network_pass').addClass('required_field');
$('#new_network_pass').parents('.form-group').css('display', 'block');
$('#uniform-show_pass').parents('.form-group').css('display', 'block');
if ($(this).val() === 'open') {
$('#new_network_pass').removeClass('required_field');
$('#new_network_pass').parents('.form-group').css('display', 'none');
$('#uniform-show_pass').parents('.form-group').css('display', 'none');
} else if ($(this).val() == 'wpaeap') {
$('#new_network_identity').addClass('required_field');
$('#new_network_identity').parents('.form-group').css('display', 'block');
}
});
$('#modal_network').on('show.bs.modal', function () {
$('#new_network_name').val('');
$('#new_network_pass').val('');
$('#new_network_pass').attr('type', 'password');
$('#new_network_identity').val('');
$('#security_select').val('wpa-psk');
$('#security_select').selectpicker('refresh');
$('#show_pass').attr('checked', false);
$('#show_pass').parent().removeClass('checked');
$('.modal_add_warning').text('');
$('#security_select').change();
$('#modal_network input').css('border', '1px solid #ddd');
});
$(document).on('change', '#show_pass', function () {
if ($(this).is(':checked')) {
$('#new_network_pass').attr('type', 'text');
} else {
$('#new_network_pass').attr('type', 'password');
}
});
$(document).on('click', '#added_wi-fi li', function () {
if (!$(this).hasClass('connected_wi-fi_network')) {
$('#modal_saved_connect .network_title').text($(this).find('.wi-fi_title').text());
$('#modal_saved_connect .network_mac').text('Security: ' + $(this).find('.wi-fi_security').val());
$('#modal_saved_connect').modal('show');
}
return false;
});
$(document).on('click', '#connected_wi-fi li', function () {
return false;
});
$(document).on('click', '#connect_network', function () {
var ssid_to_connect = $('#modal_saved_connect .network_title').text();
disconnect_msg = 'Reach is connecting to another network. Switch to ' + ssid_to_connect + ' to continue.';
socket.emit('connect to network', ssid_to_connect);
$('#modal_saved_connect').modal('hide');
return false;
});
$(document).on('click', '#forget_network', function () {
var ssid_to_remove = $('#modal_saved_connect .network_title').text();
socket.emit('remove network', ssid_to_remove);
$('#modal_saved_connect').modal('hide');
return false;
});
$('.update_reachview').on('click', function () {
if (!$(this).hasClass('disabled')) {
receiverPass = true;
socket.emit('upgrade reachview');
$('.current_version').text('Updating...');
$('.update_status').html('<i class="icon-spinner2 spinner text-warning"></i>');
$('.row_try_skip').css('display', 'none');
$('.skip_update_btn').css('display', 'none');
$('.update_reachview').addClass('disabled');
}
return false;
});
$('.skip_update, .skip_update_btn').on('click', function () {
receiverPass = true;
socket.emit('skip update');
$('.update_status').html('<i class="icon-checkmark3 text-success"></i>');
$('.update_anchor:not(.collapsed)').click();
$('.row_try_skip').css('display', 'none');
$('.skip_update_btn').css('display', 'none');
updatePass = true;
if (updatePass && receiverPass && timePass && wifiPass && testPass) {
$('.to_app').removeClass('disabled');
} else {
$('.to_app').addClass('disabled');
}
return false;
});
$('.try_again').on('click', function () {
receiverPass = true;
$('.row_try_skip').css('display', 'none');
currentVersion = false;
socket.emit('get reachview version');
$('.current_version').text('Getting current version...');
$('.update_status').html('<i class="icon-spinner2 spinner text-warning"></i>');
return false;
});
$('.to_app').on('click', function () {
if (!$(this).hasClass('disabled')) {
disconnect_msg = 'Reach is rebooting and has been disconnected from this network.';
noty({
width: 200,
text: 'Reboot will start in 3...',
type: 'information',
dismissQueue: true,
timeout: 3000,
closeWith: false,
layout: 'topRight',
callback: {
onClose: function () {
if (device === 'ReachM+' || device === 'ReachRS+') {
preventLostConnectionNoty = true;
$('#modal_mender_guide').modal({ backdrop: 'static', keyboard: false });
}
socket.emit('reboot now');
}
}
});
setTimeout(function () { $('.noty_text').text('Reboot will start in 2...'); }, 1000);
setTimeout(function () { $('.noty_text').text('Reboot will start in 1...'); }, 2000);
}
return false;
});
// SocketIO namespace:
socket = io();
socket.on('add network results', function (msg) {
if (msg) {
socket.emit('get saved wifi networks');
} else {
addConnectionNoty = noty({
width: 200,
text: 'Failed to add a new Wi-Fi connection',
type: 'error',
dismissQueue: true,
timeout: 4000,
closeWith: ['click'],
layout: 'topRight'
});
}
});
socket.on('remove network results', function (msg) {
if (msg) {
socket.emit('get saved wifi networks');
} else {
removeConnetctionNoty = noty({
width: 200,
text: 'Failed to remove network',
type: 'error',
dismissQueue: true,
timeout: 4000,
closeWith: ['click'],
layout: 'topRight'
});
}
});
// say hello on connect
socket.on('connect', function () {
socket.emit('browser connected', {data: 'I\'m connected'});
if (typeof lostConnectionNoty !== 'undefined') {
lostConnectionNoty.close();
}
});
socket.on('reconnect', function () {
$('.disconnect_overlay').fadeOut();
$('body').css('position', 'relative');
$(window).resize();
});
socket.on('disconnect', function () {
if (preventLostConnectionNoty) {
return;
}
$('.disconnect_overlay').fadeIn();
$('body').css('position', 'fixed');
lostConnectionNoty = noty({
width: 200,
text: disconnect_msg,
type: 'error',
dismissQueue: true,
timeout: false,
closeWith: false,
layout: 'topRight',
callback: {
onClose: function () {
$('.disconnect_overlay').fadeOut();
$('body').css('position', 'relative');
noty({
width: 200,
text: 'Reach reconnected!',
type: 'success',
dismissQueue: true,
closeWith: false,
timeout: 3000,
layout: 'topRight'
});
}
}
});
});
socket.on('connect_error', function () {
console.clear();
console.warn('Lost connection with sockets');
});
socket.emit('get test results');
socket.on('reachview upgrade status', function (msg) {
if (!receiverPass) {
return;
}
$('.row_try_skip').css('display', 'none');
$('.skip_update_btn').css('display', 'none');
if (!$('.update_status i').hasClass('icon-spinner2')) {
$('.update_status').html('<i class="icon-spinner2 spinner text-warning"></i>');
}
$('.available_version').css('display', 'none');
$('.update_reachview').addClass('disabled');
$('.coords_progress').css('display', 'block');
if (msg['active']) {
$('.current_version').text(msg['state'] + ' ' + msg['package'] + ' (' + msg['version'] + ')');
}
if (msg['state'] === 'Downloading') {
$('.coords_progress .progress-bar').removeClass('progress-bar-striped active');
$('.coords_progress .progress-bar').css('width', msg['percentage'] + '%');
if (parseInt(msg['percentage']) > 30) {
$('.coords_progress .progress-bar span').text(msg['percentage'] + '%');
} else {
$('.coords_progress .progress-bar span').text('');
}
} else if (msg['state'] === 'Installing') {
$('.coords_progress .progress-bar').css('width', '100%');
$('.coords_progress .progress-bar span').text('');
$('.coords_progress .progress-bar').addClass('progress-bar-striped active');
} else if (msg['state'] === 'Finished') {
$('.coords_progress').css('display', 'none');
currentVersion = false;
socket.emit('get reachview version');
$('.update_status').html('<i class="icon-checkmark3 text-success"></i>');
$('.update_reachview').css('display', 'none');
$('.available_version').css('display', 'none');
updatePass = true;
if (updatePass && receiverPass && timePass && wifiPass && testPass) {
$('.to_app').removeClass('disabled');
} else {
$('.to_app').addClass('disabled');
}
} else if (msg['state'] === 'Failed') {
$('.coords_progress').css('display', 'none');
$('.update_status').html('<i class="icon-cross2 text-danger-400"></i>');
$('.coords_progress .progress-bar').css('width', '0%');
$('.coords_progress .progress-bar span').text('');
if (msg['locked']) {
$('.current_version').html('Update system is used by another process. Please try again later.');
} else {
$('.current_version').html('Failed to perform update.');
}
$('.skip_update_btn').css('display', 'inline-block');
$('.update_reachview').css('display', 'block');
$('.update_reachview').removeClass('disabled');
$('.update_anchor.collapsed').click();
}
});
socket.on('wifi saved networks results', function (msg) {
var connectedNetwork = false;
var to_append = '';
msg.forEach(function (key, value) {
var ssid = (key['ssid'] !== '') ? key['ssid'] : 'Unknown';
if (key['is_connected']) {
to_append += '<li class="media connected_wi-fi_network">';
$('#current_network').text(ssid);
} else {
to_append += '<li class="media">';
}
to_append += '<a href="#" class="media-link"><div class="media-body">';
to_append += '<div class="media-heading text-semibold wi-fi_title">' + ssid + '</div>';
if (key['is_connected']) {
to_append += '<span class="text-muted">Connected (' + key['ip'] + ')</span>';
connectedNetwork = true;
} else {
to_append += '<span class="text-muted">Saved</span>';
}
to_append += '<input type="hidden" class="wi-fi_mac" value="' + key['mac_address'] + '"><input type="hidden" class="wi-fi_security" value="' + key['security'] + '"></div>';
if (key['is_connected']) {
to_append += '<div class="media-right media-middle text-nowrap"><span class="text-muted wi-fi_remove"><i class="icon-link text-size-base"></i></span></div>';
}
to_append += '</a></li>';
});
$('#added_wi-fi').html(to_append);
$('#connected_wi-fi').html('');
$('#connected_wi-fi').append($('.connected_wi-fi_network'));
if (connectedNetwork) {
$('.wifi_status').html('<i class="icon-checkmark3 text-success"></i>');
socket.emit('get time sync status');
$('.overlay.sync_overlay').fadeOut();
$('.sync_status').html('<i class="icon-spinner2 spinner text-warning"></i>');
syncInterval = setInterval(function () { socket.emit('get time sync status'); }, 1000);
wifiPass = true;
} else {
$('.wifi_status').html('<i class="icon-circle-small text-danger-400"></i>');
$('.wi-fi_anchor.collapsed').click();
wifiPass = false;
}
});
socket.on('time sync status', function (msg) {
if (timePass) {
return;
}
if (msg['status']) {
$('.sync_status').html('<i class="icon-checkmark3 text-success"></i>');
$('.time_sync_warning').text('Time was synchronized!');
// Receiver update
availableReceiverVersion = false;
socket.emit('is receiver upgrade available');
$('.receiver_status').html('<i class="icon-spinner2 spinner text-warning"></i>');
$('#receiver_upgrade_msg').text('Checking for receiver updates...');
$('.receiver_overlay').fadeOut();
// Reach update
// currentVersion = false;
// socket.emit('get reachview version');
// $('.current_version').text('Getting current version...');
// $('.overlay.update_overlay').fadeOut();
// $('.update_status').html('<i class="icon-spinner2 spinner text-warning"></i>');
clearInterval(syncInterval);
timePass = true;
} else {
$('.time_sync_warning').text('Check your internet connection or connect antenna.');
timePass = false;
}
});
socket.on('receiver upgrade available', function (msg) {
if (availableReceiverVersion || receiverLocked) {
return;
}
if (msg['running']) {
$('#receiver_upgrade_msg').text('Updating receiver...');
$('.receiver_status').html('<i class="icon-spinner2 spinner text-warning"></i>');
$('.update_receiver').css('display', 'inline-block');
$('.update_receiver').addClass('disabled');
$('.skip_receiver_update').css('display', 'none');
$('.receiver_progress').css('display', 'block');
$('.receiver_anchor.collapsed').click();
return;
}
if (msg['available']) {
$('.receiver_status').html('<i class="text-warning icon-circle-small"></i>');
$('#receiver_upgrade_msg').text('Receiver upgrade available');
$('.update_receiver').css('display', 'inline-block');
$('.skip_receiver_update').css('display', 'inline-block');
$('.receiver_anchor.collapsed').click();
} else {
$('.receiver_status').html('<i class="icon-checkmark3 text-success"></i>');
$('#receiver_upgrade_msg').text('No upgrades available for receiver');
$('.skip_receiver_update').trigger('click', [true]);
}
availableReceiverVersion = true;
});
$('.update_receiver').on('click', function () {
if (!$(this).hasClass('disabled')) {
receiverPass = false;
socket.emit('upgrade receiver');
$('#receiver_upgrade_msg').text('Updating receiver...');
$('.receiver_status').html('<i class="icon-spinner2 spinner text-warning"></i>');
$('.update_receiver').addClass('disabled');
$('.skip_receiver_update').css('display', 'none');
$('.receiver_progress').css('display', 'block');
}
return false;
});
$('.skip_receiver_update, .receiver_skip_locked').on('click', function (event, fakeClick) {
if (!$(this).hasClass('disabled')) {
$(this).css('display', 'none');
$('.receiver_status').html('<i class="icon-checkmark3 text-success"></i>');
if (!fakeClick) {
$('.receiver_anchor:not(.collapsed)').click();
}
if (!availableVersion && !updatePass) {
currentVersion = false;
socket.emit('get reachview version');
$('.current_version').text('Getting current version...');
$('.overlay.update_overlay').fadeOut();
$('.update_status').html('<i class="icon-spinner2 spinner text-warning"></i>');
}
receiverLocked = false;
receiverPass = true;
}
return false;
});
$('.receiver_try_again').on('click', function () {
receiverPass = false;
availableReceiverVersion = false;
socket.emit('is receiver upgrade available');
$('.receiver_status').html('<i class="icon-spinner2 spinner text-warning"></i>');
$('#receiver_upgrade_msg').text('Checking for receiver updates...');
$('.receiver_try_skip').css('display', 'none');
receiverLocked = false;
return false;
});
socket.on('receiver upgrade result', function (msg) {
if (receiverPass || receiverLocked) {
return;
}
if (msg) {
$('.update_receiver').css('display', 'none');
$('#receiver_upgrade_msg').text('Receiver updated successfully');
$('.receiver_status').html('<i class="icon-checkmark3 text-success"></i>');
$('.skip_receiver_update').trigger('click', [true]);
} else {
$('#receiver_upgrade_msg').text('Failed to perform receiver update');
$('.receiver_status').html('<i class="text-warning icon-circle-small"></i>');
$('.update_receiver').css('display', 'inline-block');
$('.skip_receiver_update').css('display', 'inline-block');
$('.receiver_anchor.collapsed').click();
$('.update_receiver').removeClass('disabled');
}
$('.receiver_progress').css('display', 'none');
});
socket.on('opkg update result', function (msg) {
if (opkgResult) {
return;
}
if (msg['state'] === 'Failed') {
$('.update_status').html('<i class="icon-cross2 text-danger-400"></i>');
$('.update_anchor.collapsed').click();
if (msg['locked']) {
$('.current_version').html('Update system is used by another process. Please try again later.');
} else {
$('.current_version').html('Update server unreachable. Check your Internet connection or try again later.');
}
$('.available_version').css('display', 'none');
$('.row_try_skip').css('display', 'block');
$('.update_reachview').css('display', 'none');
$('.update_anchor.collapsed').click();
}
else if (msg['state'] === 'Finished') {
opkgResult = true;
availableVersion = false;
socket.emit('is reachview upgrade available');
}
});
socket.on('update system locked', function () {
if (receiverPass) {
$('.update_anchor.collapsed').click();
$('.update_status').html('<i class="icon-cross2 text-danger-400"></i>');
$('.current_version').text('Update system is used by another process. Please try again later.');
$('.available_version').css('display', 'none');
$('.row_try_skip').css('display', 'block');
} else {
$('.receiver_anchor.collapsed').click();
$('.receiver_status').html('<i class="icon-cross2 text-danger-400"></i>');
$('#receiver_upgrade_msg').text('Update system is used by another process. Please try again later.');
$('.update_receiver').css('display', 'none');
$('.skip_receiver_update').css('display', 'none');
$('.receiver_try_skip').css('display', 'block');
receiverLocked = true;
}
});
socket.on('current reachview version', function (msg) {
if (currentVersion || !receiverPass) {
return;
}
var version = (msg['version'] != null)
? 'Current ReachView version: ' + msg['version']
: 'Could not retrieve ReachView version.';
$('.current_version').text(version);
currentVersion = true;
opkgResult = false;
socket.emit('update');
$('.available_version').css('display', 'block');
$('.available_version').text('Checking for updates...');
});
socket.on('reachview upgrade version', function (msg) {
if (availableVersion || !receiverPass) {
return;
}
if (!msg['upgrade available']) {
$('.update_status').html('<i class="icon-checkmark3 text-success"></i>');
$('.update_reachview').css('display', 'none');
$('.available_version').css('display', 'none');
updatePass = true;
if (updatePass && receiverPass && timePass && wifiPass && testPass) {
$('.to_app').removeClass('disabled');
} else {
$('.to_app').addClass('disabled');
}
} else {
$('.available_version').css('display', 'block');
$('.available_version').text('Available version: ' + msg['available version']);
$('.update_status').html('<i class="text-warning icon-circle-small"></i>');
$('.update_reachview').css('display', 'inline-block');
$('.update_anchor.collapsed').click();
}
availableVersion = true;
});
socket.on('test results', function (msg) {
$('.tests_status').css('display', 'none');
device = msg['device'];
if (msg['device'] === 'Reach' || msg['device'] === 'ReachM+') {
$('.ltc_test, .stc_test, .lora_test').parent().css('display', 'none');
}
if (!msg['ltc']) {
$('.tests_status').html('<i class="icon-cross2 text-danger-400"></i>');
$('.test_warning').text('Test 3 failed');
$('.test_warning').slideDown();
testPass = true;
}
if (!msg['stc']) {
$('.tests_status').html('<i class="icon-cross2 text-danger-400"></i>');
$('.test_warning').text('Test 4 failed');
$('.test_warning').slideDown();
testPass = true;
}
if (!msg['lora']) {
$('.tests_status').html('<i class="icon-cross2 text-danger-400"></i>');
$('.test_warning').text('Test 5 failed');
$('.test_warning').slideDown();
testPass = true;
}
if (!msg['mpu']) {
$('.tests_status').html('<i class="icon-cross2 text-danger-400"></i>');
$('.test_warning').text('Test 1 failed');
$('.test_warning').slideDown();
testPass = true;
}
if (!msg['u-blox']) {
$('.tests_status').html('<i class="icon-cross2 text-danger-400"></i>');
$('.test_warning').text('Test 2 failed');
$('.test_warning').slideDown();
testPass = false;
}
if (msg['device'] === 'Reach' || msg['device'] === 'ReachM+') {
if (msg['u-blox'] && msg['mpu']) {
$('.tests_status').html('<i class="icon-checkmark3 text-success"></i>');
testPass = true;
}
} else {
if (msg['u-blox'] && msg['mpu'] && msg['lora'] && msg['ltc'] && msg['stc']) {
$('.tests_status').html('<i class="icon-checkmark3 text-success"></i>');
testPass = true;
}
}
$('.tests_status').fadeIn();
if (msg['mpu']) {
$('.mpu_test').html('<i class="icon-checkmark3 text-success"></i>');
} else {
$('.mpu_test').html('<i class="icon-cross2 text-danger-400"></i>');
}
if (msg['u-blox']) {
$('.u-blox_test').html('<i class="icon-checkmark3 text-success"></i>');
} else {
$('.u-blox_test').html('<i class="icon-cross2 text-danger-400"></i>');
}
if (msg['lora']) {
$('.lora_test').html('<i class="icon-checkmark3 text-success"></i>');
} else {
$('.lora_test').html('<i class="icon-cross2 text-danger-400"></i>');
}
if (msg['stc']) {
$('.stc_test').html('<i class="icon-checkmark3 text-success"></i>');
} else {
$('.stc_test').html('<i class="icon-cross2 text-danger-400"></i>');
}
if (msg['ltc']) {
$('.ltc_test').html('<i class="icon-checkmark3 text-success"></i>');
} else {
$('.ltc_test').html('<i class="icon-cross2 text-danger-400"></i>');
}
socket.emit('get saved wifi networks');
});
});
});

17
modd.conf Normal file
View File

@ -0,0 +1,17 @@
**/*.go
!**/*_test.go
modd.conf
.env
Makefile {
prep: make build
daemon: [ -e .env ] && . .env; ./bin/server
}
**/*.go
modd.conf
Makefile {
prep: make lint LINT_ARGS=--fast
prep: make test
}

38
openwrt/dhcp_client.go Normal file
View File

@ -0,0 +1,38 @@
package openwrt
// DhcpClient represents a dhcp client ... :)
type DhcpClient struct {
exec Executor
iface string
}
// NewDhcpClient return an UCI instance to interact with UCI
func NewDhcpClient(netIface string) *DhcpClient {
exec := &localExecutor{}
iface := netIface
return &DhcpClient{exec, iface}
}
// NewDhcpClientWithExecutor return an UCI instance to interact with UCI
func NewDhcpClientWithExecutor(netIface string, exe Executor) *DhcpClient {
exec := exe
iface := netIface
return &DhcpClient{exec, iface}
}
// NewDhcpClient return an UCI instance to interact with UCI
//func NewDhcpClient(netIface string, exe Executor) *DhcpClient {
// var exec Executor
// if exe == nil {
// exec = &localExecutor{}
// } else {
// exec = exe
// }
// iface := netIface
// return &DhcpClient{exec, iface}
//}
// AskForIP runs a dhclient ip request with udhcpc
func (dc *DhcpClient) AskForIP() *CommandResult {
return dc.exec.Run("udhcpc", "-i", dc.iface)
}

View File

@ -0,0 +1,12 @@
package openwrt
import "testing"
func TestDhcpClientAskForIP(t *testing.T) {
uexec := createMockExecutor("", "", 0)
dhc := NewDhcpClientWithExecutor("wlan1", uexec)
res := dhc.AskForIP()
if res.ReturnCode != 0 {
t.Error("Error in DHCP Client !!")
}
}

67
openwrt/executor.go Normal file
View File

@ -0,0 +1,67 @@
package openwrt
import (
"bytes"
"fmt"
"log"
"os/exec"
"syscall"
)
// Executor interface to describe command runners signature
type Executor interface {
Run(command string, params ...string) *CommandResult
}
// CommandResult contain all information about a command execution, stdout, stderr
type CommandResult struct {
Stdout string
Stderr string
ReturnCode int
}
type localExecutor struct{}
func (e *localExecutor) Run(command string, params ...string) *CommandResult {
var out bytes.Buffer
var stderr bytes.Buffer
var exitCode int
defaultFailedCode := 255
exe := exec.Command(command, params...)
exe.Stdout = &out
exe.Stderr = &stderr
err := exe.Run()
if err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
ws := exitError.Sys().(syscall.WaitStatus)
exitCode = ws.ExitStatus()
} else {
// This will happen (in OSX) if `name` is not available in $PATH,
// in this situation, exit code could not be get, and stderr will be
// empty string very likely, so we use the default fail code, and format err
// to string and set to stderr
log.Printf("Could not get exit code for failed program: %v, %v", command, params)
exitCode = defaultFailedCode
}
fmt.Println(fmt.Sprint(err) + ": " + stderr.String())
log.Fatal(err)
}
if err != nil {
// try to get the exit code
} else {
// success, exitCode should be 0 if go is ok
ws := exe.ProcessState.Sys().(syscall.WaitStatus)
exitCode = ws.ExitStatus()
}
return &CommandResult{
Stdout: out.String(),
Stderr: stderr.String(),
ReturnCode: exitCode,
}
}

18
openwrt/executor_test.go Normal file
View File

@ -0,0 +1,18 @@
package openwrt
import (
"testing"
)
func TestRun(t *testing.T) {
exec := &localExecutor{}
res := exec.Run("uname", "-a")
if g, e := res.ReturnCode, 0; g != e {
t.Errorf("Run command failed ! Got bad return code [%d], [%d} is expected\n", g, e)
}
// res = exec.Run("noCommandWithThisNameExists", "-a")
// if g, e := res.ReturnCode, 127; g != e {
// t.Errorf("Run command failed ! Got bad return code [%d], [%d} is expected\n", g, e)
// }
}

77
openwrt/network.go Normal file
View File

@ -0,0 +1,77 @@
package openwrt
import (
"fmt"
"io/ioutil"
"net"
"strings"
)
// Network provides a representation of network
type Network struct {
exec Executor
}
// NewNetwork return an UCI instance to interact with UCI
func NewNetwork() *Network {
exec := &localExecutor{}
return &Network{exec}
}
// NewNetworkWithExecutor return an UCI instance to interact with UCI
func NewNetworkWithExecutor(exe Executor) *Network {
exec := exe
return &Network{exec}
}
// ListInterfaces list all available interfaces on a system using "ip" command
func (n *Network) ListInterfaces() []net.Interface {
var result []net.Interface
ifaces, err := net.Interfaces()
if err != nil {
fmt.Print(fmt.Errorf("error listing network interfacess: %+v", err.Error()))
return nil
}
result = append(result, ifaces...)
return result
}
// ListWirelessInterfaces list all wifi cards
// you need to provide the wireless file or "" to use
// Linux default one "/proc/net/wireless"
func (n *Network) ListWirelessInterfaces(wifiFile string) []net.Interface {
var result []net.Interface
var ifaceNames []string
if wifiFile == "" {
wifiFile = "/proc/net/wireless"
}
wifiFileContent, err := ioutil.ReadFile(wifiFile)
check(err)
index := 0
for _, line := range strings.Split(string(wifiFileContent), "\n") {
if index < 2 {
index++
continue
} else {
name := strings.Split(line, ":")[0]
ifaceNames = append(ifaceNames, name)
}
}
ifaces, err := net.Interfaces()
if err != nil {
fmt.Print(fmt.Errorf("error listing network interfaces : %+v", err.Error()))
return nil
}
for _, i := range ifaces {
for _, name := range ifaceNames {
if name == i.Name {
result = append(result, i)
}
}
}
return result
}

22
openwrt/network_test.go Normal file
View File

@ -0,0 +1,22 @@
package openwrt
import (
"fmt"
"testing"
)
func TestNetworkListInterfaces(t *testing.T) {
net := NewNetwork()
iface := net.ListInterfaces()
for _, ife := range iface {
fmt.Printf("%s\n", ife.Name)
}
}
func TestListWirelessInterfaces(t *testing.T) {
net := NewNetwork()
res := net.ListWirelessInterfaces("./testdata/proc_net_wireless.txt")
for _, el := range res {
fmt.Printf("%s\n", el.Name)
}
}

35
openwrt/test.go Normal file
View File

@ -0,0 +1,35 @@
package openwrt
import (
"log"
"strings"
)
func check(e error) {
if e != nil {
panic(e)
}
}
func createMockExecutor(stdout string, stderr string, returnCode int) Executor {
return &mockExecutor{
stdout: stdout,
stderr: stderr,
returnCode: returnCode,
}
}
type mockExecutor struct {
stdout string
stderr string
returnCode int
}
func (e *mockExecutor) Run(command string, params ...string) *CommandResult {
log.Printf("executing '%s %s'", command, strings.Join(params, " "))
return &CommandResult{
Stderr: e.stderr,
Stdout: e.stdout,
ReturnCode: e.returnCode,
}
}

View File

@ -0,0 +1,4 @@
Inter-| sta-| Quality | Discarded packets | Missed | WE
face | tus | link level noise | nwid crypt frag retry misc | beacon | 22
wlan1: 0000 0 0 0 0 0 0 0 0 0
wlan0: 0000 0 0 0 0 0 0 0 0 0

View File

@ -0,0 +1,17 @@
Cell 40 - Address: 68:A3:78:6E:D9:24
ESSID: "PyxisWifi"
Mode: Master Channel: 3
Signal: -90 dBm Quality: 20/70
Encryption: none
Cell 41 - Address: B0:39:56:92:59:E2
ESSID: "NET17"
Mode: Master Channel: 4
Signal: -88 dBm Quality: 22/70
Encryption: WPA2 PSK (CCMP)
Cell 42 - Address: 0C:F4:D5:16:AA:18
ESSID: "DIJON-METROPOLE-WIFI"
Mode: Master Channel: 13
Signal: -90 dBm Quality: 20/70
Encryption: none

View File

@ -0,0 +1,293 @@
Cell 01 - Address: 0C:8D:DB:C4:A0:34
ESSID: "pfPauvres"
Mode: Master Channel: 11
Signal: -50 dBm Quality: 60/70
Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP)
Cell 02 - Address: 40:5A:9B:ED:BA:F0
ESSID: "Cadoles"
Mode: Master Channel: 6
Signal: -36 dBm Quality: 70/70
Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP)
Cell 03 - Address: A0:04:60:B2:8A:C8
ESSID: "Cadoles Formations (N)"
Mode: Master Channel: 13
Signal: -32 dBm Quality: 70/70
Encryption: WPA2 PSK (CCMP)
Cell 04 - Address: B0:39:56:D8:38:ED
ESSID: "Frate Dijon EXT"
Mode: Master Channel: 11
Signal: -57 dBm Quality: 53/70
Encryption: WPA2 PSK (CCMP)
Cell 05 - Address: 00:A6:CA:10:DF:00
ESSID: unknown
Mode: Master Channel: 6
Signal: -80 dBm Quality: 30/70
Encryption: WPA2 802.1X (CCMP)
Cell 06 - Address: AC:84:C9:2F:59:6E
ESSID: "Livebox-596a"
Mode: Master Channel: 1
Signal: -62 dBm Quality: 48/70
Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP)
Cell 07 - Address: 00:A6:CA:10:DF:01
ESSID: "EFF-Mobility"
Mode: Master Channel: 6
Signal: -74 dBm Quality: 36/70
Encryption: WPA2 PSK (CCMP)
Cell 08 - Address: A0:1B:29:BE:98:26
ESSID: "Livebox-9822"
Mode: Master Channel: 6
Signal: -81 dBm Quality: 29/70
Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP)
Cell 09 - Address: 7C:26:64:66:CC:44
ESSID: "Livebox-32c8"
Mode: Master Channel: 1
Signal: -74 dBm Quality: 36/70
Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP)
Cell 10 - Address: 00:A6:CA:10:DF:02
ESSID: "Keo-HotSpot"
Mode: Master Channel: 6
Signal: -79 dBm Quality: 31/70
Encryption: none
Cell 11 - Address: 7E:26:64:66:CC:44
ESSID: "orange"
Mode: Master Channel: 1
Signal: -73 dBm Quality: 37/70
Encryption: none
Cell 12 - Address: 3C:52:82:FC:5E:21
ESSID: "DIRECT-20-HP DeskJet 3630 series"
Mode: Master Channel: 6
Signal: -78 dBm Quality: 32/70
Encryption: WPA2 PSK (CCMP)
Cell 13 - Address: E4:9E:12:8B:EF:73
ESSID: "Freebox-8BEF72"
Mode: Master Channel: 9
Signal: -79 dBm Quality: 31/70
Encryption: WPA2 PSK (CCMP)
Cell 14 - Address: 40:4A:03:05:D2:68
ESSID: "ZyXEL"
Mode: Master Channel: 11
Signal: -71 dBm Quality: 39/70
Encryption: WPA2 PSK (CCMP)
Cell 15 - Address: 5C:C3:07:7E:39:D4
ESSID: "pfP Xa"
Mode: Master Channel: 1
Signal: -65 dBm Quality: 45/70
Encryption: WPA2 PSK (CCMP)
Cell 16 - Address: AC:84:C9:1D:C6:7C
ESSID: "Frate Djon"
Mode: Master Channel: 1
Signal: -79 dBm Quality: 31/70
Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP)
Cell 17 - Address: 00:17:33:9F:4D:80
ESSID: "NEUF_4D7C"
Mode: Master Channel: 11
Signal: -83 dBm Quality: 27/70
Encryption: WPA PSK (TKIP, CCMP)
Cell 18 - Address: A2:17:33:9F:4D:81
ESSID: "SFR WiFi FON"
Mode: Master Channel: 11
Signal: -85 dBm Quality: 25/70
Encryption: none
Cell 19 - Address: BC:F6:85:FE:6D:46
ESSID: "Dlink"
Mode: Master Channel: 12
Signal: -70 dBm Quality: 40/70
Encryption: WPA2 PSK (CCMP)
Cell 20 - Address: 30:7C:B2:D1:0B:0D
ESSID: "Livebox-0b09"
Mode: Master Channel: 11
Signal: -81 dBm Quality: 29/70
Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP)
Cell 21 - Address: A2:17:33:9F:4D:83
ESSID: "SFR WiFi Mobile"
Mode: Master Channel: 11
Signal: -85 dBm Quality: 25/70
Encryption: WPA2 802.1X (CCMP)
Cell 22 - Address: 90:4D:4A:F7:B9:70
ESSID: "Livebox-B970"
Mode: Master Channel: 11
Signal: -84 dBm Quality: 26/70
Encryption: WPA2 PSK (CCMP)
Cell 23 - Address: 90:4D:4A:F7:B9:71
ESSID: "orange"
Mode: Master Channel: 11
Signal: -89 dBm Quality: 21/70
Encryption: none
Cell 24 - Address: 00:22:6B:86:5B:71
ESSID: "linksys"
Mode: Master Channel: 11
Signal: -86 dBm Quality: 24/70
Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP)
Cell 25 - Address: 68:A3:78:6E:D9:25
ESSID: "FreeWifi_secure"
Mode: Master Channel: 3
Signal: -86 dBm Quality: 24/70
Encryption: WPA2 802.1X (TKIP, CCMP)
Cell 26 - Address: 6C:38:A1:62:1B:28
ESSID: "Bbox-1B7889A9"
Mode: Master Channel: 1
Signal: -90 dBm Quality: 20/70
Encryption: mixed WPA/WPA2 PSK (CCMP)
Cell 27 - Address: 78:81:02:5E:B7:14
ESSID: "Livebox-B714"
Mode: Master Channel: 6
Signal: -86 dBm Quality: 24/70
Encryption: WPA2 PSK (CCMP)
Cell 28 - Address: F4:CA:E5:98:3B:DC
ESSID: "Freebox-5D2400"
Mode: Master Channel: 11
Signal: -84 dBm Quality: 26/70
Encryption: WPA PSK (CCMP)
Cell 29 - Address: 8C:DC:D4:93:69:17
ESSID: "HP-Print-17-Photosmart 5520"
Mode: Master Channel: 11
Signal: -87 dBm Quality: 23/70
Encryption: none
Cell 30 - Address: 44:CE:7D:20:5C:A4
ESSID: "SFR_5CA0"
Mode: Master Channel: 6
Signal: -86 dBm Quality: 24/70
Encryption: WPA PSK (TKIP, CCMP)
Cell 31 - Address: F4:CA:E5:98:3B:DE
ESSID: "FreeWifi_secure"
Mode: Master Channel: 11
Signal: -72 dBm Quality: 38/70
Encryption: WPA2 802.1X (TKIP, CCMP)
Cell 32 - Address: 70:0B:01:C0:B3:E0
ESSID: "Livebox-B3E0"
Mode: Master Channel: 11
Signal: -80 dBm Quality: 30/70
Encryption: WPA2 PSK (CCMP)
Cell 33 - Address: D2:CE:7D:20:5C:A7
ESSID: "SFR WiFi Mobile"
Mode: Master Channel: 6
Signal: -85 dBm Quality: 25/70
Encryption: WPA2 802.1X (CCMP)
Cell 34 - Address: 68:A3:78:0D:B6:51
ESSID: "Freebox-0DB650"
Mode: Master Channel: 1
Signal: -92 dBm Quality: 18/70
Encryption: WPA2 PSK (CCMP)
Cell 35 - Address: F8:AB:05:1D:6A:E0
ESSID: "Bbox-8CE43C68"
Mode: Master Channel: 6
Signal: -88 dBm Quality: 22/70
Encryption: mixed WPA/WPA2 PSK (CCMP)
Cell 36 - Address: F4:CA:E5:98:3B:DD
ESSID: "FreeWifi"
Mode: Master Channel: 11
Signal: -87 dBm Quality: 23/70
Encryption: none
Cell 37 - Address: 14:0C:76:79:C0:D9
ESSID: "freebox_ZFSFUA"
Mode: Master Channel: 4
Signal: -88 dBm Quality: 22/70
Encryption: WPA PSK (TKIP, CCMP)
Cell 38 - Address: 68:15:90:36:63:60
ESSID: "Livebox-6360"
Mode: Master Channel: 1
Signal: -81 dBm Quality: 29/70
Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP)
Cell 39 - Address: 64:7C:34:29:2B:7C
ESSID: "Bbox-D646CB51"
Mode: Master Channel: 1
Signal: -90 dBm Quality: 20/70
Encryption: mixed WPA/WPA2 PSK (CCMP)
Cell 40 - Address: 68:A3:78:6E:D9:24
ESSID: "FreeWifi"
Mode: Master Channel: 3
Signal: -90 dBm Quality: 20/70
Encryption: none
Cell 41 - Address: B0:39:56:92:59:E2
ESSID: "NETGEAR17"
Mode: Master Channel: 4
Signal: -88 dBm Quality: 22/70
Encryption: WPA2 PSK (CCMP)
Cell 42 - Address: 0C:F4:D5:16:AA:18
ESSID: "DIJON-METROPOLE-WIFI"
Mode: Master Channel: 13
Signal: -90 dBm Quality: 20/70
Encryption: none
Cell 43 - Address: D2:CE:7D:20:5C:A5
ESSID: "SFR WiFi FON"
Mode: Master Channel: 6
Signal: -81 dBm Quality: 29/70
Encryption: none
Cell 44 - Address: 34:27:92:42:CD:72
ESSID: "Freebox-42CD71"
Mode: Master Channel: 8
Signal: -88 dBm Quality: 22/70
Encryption: WPA2 PSK (CCMP)
Cell 45 - Address: 72:5D:51:78:4C:87
ESSID: "SFR WiFi FON"
Mode: Master Channel: 11
Signal: -87 dBm Quality: 23/70
Encryption: none
Cell 46 - Address: 68:A3:78:6E:D9:23
ESSID: "Freebox-6ED922"
Mode: Master Channel: 3
Signal: -76 dBm Quality: 34/70
Encryption: WPA2 PSK (CCMP)
Cell 47 - Address: 00:19:70:4F:DE:F2
ESSID: "Livebox-45cc"
Mode: Master Channel: 6
Signal: -78 dBm Quality: 32/70
Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP)
Cell 48 - Address: AC:84:C9:CC:AE:90
ESSID: "Livebox-AE90"
Mode: Master Channel: 11
Signal: -81 dBm Quality: 29/70
Encryption: WPA2 PSK (CCMP)
Cell 49 - Address: 00:07:7D:89:81:B0
ESSID: "orange"
Mode: Master Channel: 6
Signal: -85 dBm Quality: 25/70
Encryption: none

72
openwrt/uci.go Normal file
View File

@ -0,0 +1,72 @@
package openwrt
import (
"fmt"
"time"
)
// Action is the result of an UCI action output and return code
type Action struct {
*CommandResult
}
// UCI "Object"
type UCI struct {
exec Executor
}
// NewUCI return an UCI instance to interact with UCI
func NewUCI() *UCI {
exec := &localExecutor{}
return &UCI{exec}
}
// NewUCIWithExecutor returns a UCI Instance an gives you the ability to provide
// a different command executor than the default one.
func NewUCIWithExecutor(exec Executor) *UCI {
return &UCI{exec}
}
// uciRun, private method to run the UCI command
func (u *UCI) uciRun(uciAction string, param string) *Action {
cmd := "uci"
res := u.exec.Run(cmd, uciAction, param)
return &Action{res}
}
// Add add an entry to UCI configuration, specify the Module and the value
func (u *UCI) Add(module string, name string) *Action {
commandRes := u.exec.Run("uci add", module, name)
return &Action{commandRes}
}
// Delete delete an entry from UCI configuration specify the entry name
func (u *UCI) Delete(entry string) *Action {
return u.uciRun("delete", entry)
}
// Set set a value ton an UCI configuration entry
func (u *UCI) Set(entry string, value string) *Action {
return u.uciRun("set", fmt.Sprintf("%s=%s", entry, value))
}
// Commit the recent actions to UCI
func (u *UCI) Commit() *Action {
return u.uciRun("commit", "")
}
// Reload reload uci configuration
func (u *UCI) Reload() *Action {
cmdResult := u.exec.Run("reload_config")
time.Sleep(5 * time.Second)
return &Action{cmdResult}
}
// AddWireless Create a new Wireless entry in UCI configuration
func (u *UCI) AddWireless(name string) *Action {
res := u.Add("wireless", name)
return res
}

100
openwrt/uci_test.go Normal file
View File

@ -0,0 +1,100 @@
package openwrt
import (
"fmt"
"testing"
)
func TestUCIAdd(t *testing.T) {
exec := createMockExecutor("", "", 0)
uci := NewUCIWithExecutor(exec)
res := uci.Add("wireless", "test")
if res.ReturnCode != 0 {
t.Error("Bad Return Code !")
}
if res.Stdout != "" {
fmt.Printf("[%s] - ", res.Stdout)
t.Error("Stdout is not empty ...")
}
if res.Stderr != "" {
fmt.Printf("[%s] - ", res.Stdout)
t.Error("Stderr is not empty ...")
}
}
func TestUCIAddFailed(t *testing.T) {
exec := createMockExecutor("", "BigError", 3)
uci := NewUCIWithExecutor(exec)
res := uci.Add("wireless", "test")
if res.ReturnCode != 3 {
t.Error("Bad Return Code !")
}
}
func TestUCIDelete(t *testing.T) {
exec := createMockExecutor("", "", 0)
uci := NewUCIWithExecutor(exec)
res := uci.Delete("wireless.@wifi-iface[1]")
if res.ReturnCode != 0 {
t.Error("Bad Return Code !")
}
if res.Stdout != "" {
fmt.Printf("[%s] - ", res.Stdout)
t.Error("Stdout is not empty ...")
}
if res.Stderr != "" {
fmt.Printf("[%s] - ", res.Stdout)
t.Error("Stderr is not empty ...")
}
}
func TestUCISet(t *testing.T) {
exec := createMockExecutor("", "", 0)
uci := NewUCIWithExecutor(exec)
res := uci.Set("wireless.@wifi-iface[1].network", "OrionNetwork")
if res.ReturnCode != 0 {
t.Error("Bad Return Code !")
}
if res.Stdout != "" {
fmt.Printf("[%s] - ", res.Stdout)
t.Error("Stdout is not empty ...")
}
if res.Stderr != "" {
fmt.Printf("[%s] - ", res.Stdout)
t.Error("Stderr is not empty ...")
}
}
func TestUCICommit(t *testing.T) {
exec := createMockExecutor("", "", 0)
uci := NewUCIWithExecutor(exec)
res := uci.Commit()
if res.ReturnCode != 0 {
t.Error("Bad Return Code !")
}
if res.Stdout != "" {
fmt.Printf("[%s] - ", res.Stdout)
t.Error("Stdout is not empty ...")
}
if res.Stderr != "" {
fmt.Printf("[%s] - ", res.Stdout)
t.Error("Stderr is not empty ...")
}
}
func TestUCIReload(t *testing.T) {
exec := createMockExecutor("", "", 0)
uci := NewUCIWithExecutor(exec)
res := uci.Reload()
if res.ReturnCode != 0 {
t.Error("Bad Return Code !")
}
if res.Stdout != "" {
fmt.Printf("[%s] - ", res.Stdout)
t.Error("Stdout is not empty ...")
}
if res.Stderr != "" {
fmt.Printf("[%s] - ", res.Stdout)
t.Error("Stderr is not empty ...")
}
}

91
openwrt/wifi.go Normal file
View File

@ -0,0 +1,91 @@
package openwrt
import (
"log"
"strings"
"time"
)
// Wifi gives access to al OpenWRT Wifi operations
type Wifi struct {
exec Executor
iface string
Cells []*WifiCell
}
// NewWifi return an UCI instance to interact with UCI
func NewWifi(wIface string) *Wifi {
exec := &localExecutor{}
iface := wIface
return &Wifi{exec, iface, nil}
}
// NewWifiWithExecutor returns a Wifi Instance an gives you the ability to provide
// a different command executor than the default one.
func NewWifiWithExecutor(exec Executor, wIface string) *Wifi {
return &Wifi{exec, wIface, nil}
}
func (w *Wifi) getEncryption(line string) string {
enc := "unkn"
if strings.Contains(line, "WPA2 PSK") {
enc = "psk"
} else if strings.Contains(line, "none") {
enc = "none"
}
return enc
}
func (w *Wifi) parseWifiCells(stdout string) int {
new := false
mac, ssid, enc := "", "", ""
for _, line := range strings.Split(strings.TrimSuffix(stdout, "\n"), "\n") {
if strings.HasPrefix(line, "Cell") && new == false {
new = true
mac = strings.Split(line, " ")[4]
}
if strings.Contains(line, "ESSID:") {
ssid = strings.Split(line, " ")[1]
ssid = strings.Trim(ssid, "\"")
}
if strings.Contains(line, "Encryption:") {
enc = w.getEncryption(line)
}
if len(mac) > 0 && len(ssid) > 0 && len(enc) > 0 {
cell := NewWifiCell(ssid, mac, enc)
w.Cells = append(w.Cells, cell)
ssid, mac, enc = "", "", ""
new = false
}
}
return 0
}
// GetWifiCells retrieves all available wifi cells for a card !
func (w *Wifi) GetWifiCells() int {
res := w.exec.Run("iwinfo", w.iface, "scan")
if res.ReturnCode != 0 {
log.Fatal(res.Stderr)
return res.ReturnCode
}
for res.Stdout == "Scanning not possible" {
time.Sleep(time.Second)
res = w.exec.Run("iwinfo", w.iface, "scan")
if res.ReturnCode != 0 {
log.Fatal(res.Stderr)
return res.ReturnCode
}
}
return w.parseWifiCells(res.Stdout)
}
// GetCell retreives an WifiCell by SSID provided in parameter
func (w *Wifi) GetCell(ssid string) *WifiCell {
for _, v := range w.Cells {
if v.Ssid == ssid {
return v
}
}
return nil
}

93
openwrt/wifi_cell.go Normal file
View File

@ -0,0 +1,93 @@
package openwrt
import "time"
// WifiCell reprensents wifi network Cell
type WifiCell struct {
Ssid string
MacAdress string
Encryption string
}
// NewWifiCell returns a new WifiCell object
func NewWifiCell(ssid string, mac string, encrypt string) *WifiCell {
return &WifiCell{
Ssid: ssid,
MacAdress: mac,
Encryption: encrypt,
}
}
func (cell *WifiCell) uciWifiConfigure(uci *UCI, secret string) *Action {
setRes := uci.Set("wireless.@wifi-iface[1].network", "PyxisNetwork")
if setRes.ReturnCode != 0 {
return setRes
}
setRes = uci.Set("wireless.@wifi-iface[1].ssid", cell.Ssid)
if setRes.ReturnCode != 0 {
return setRes
}
setRes = uci.Set("wireless.@wifi-iface[1].encryption", cell.Encryption)
if setRes.ReturnCode != 0 {
return setRes
}
setRes = uci.Set("wireless.@wifi-iface[1].device", "radio1")
if setRes.ReturnCode != 0 {
return setRes
}
setRes = uci.Set("wireless.@wifi-iface[1].mode", "sta")
if setRes.ReturnCode != 0 {
return setRes
}
setRes = uci.Set("wireless.@wifi-iface[1].bssid", cell.MacAdress)
if setRes.ReturnCode != 0 {
return setRes
}
setRes = uci.Set("wireless.@wifi-iface[1].key", secret)
if setRes.ReturnCode != 0 {
return setRes
}
return &Action{
&CommandResult{
Stdout: "",
Stderr: "",
ReturnCode: 0,
},
}
}
// Connect to wifi Cell
func (cell *WifiCell) Connect(uci *UCI, secret string) *Action {
delRes := uci.Delete("wireless.@wifi-iface[1]")
if delRes.ReturnCode != 0 {
return delRes
}
addRes := uci.AddWireless("wifi-iface")
if addRes.ReturnCode != 0 {
return addRes
}
setRes := cell.uciWifiConfigure(uci, secret)
if setRes.ReturnCode != 0 {
return setRes
}
setRes = uci.Commit()
if setRes.ReturnCode != 0 {
return setRes
}
setRes = uci.Reload()
if setRes.ReturnCode != 0 {
return setRes
}
time.Sleep(20 * time.Second)
return &Action{
&CommandResult{
Stdout: "",
Stderr: "",
ReturnCode: 0,
},
}
}

21
openwrt/wifi_cell_test.go Normal file
View File

@ -0,0 +1,21 @@
package openwrt
import "testing"
func TestWifiCellConnection(t *testing.T) {
uexec := createMockExecutor("", "", 0)
uci := NewUCIWithExecutor(uexec)
cellList := `Cell 40 - Address: 68:A3:78:6E:D9:24
ESSID: "PyxisWifi"
Mode: Master Channel: 3
Signal: -90 dBm Quality: 20/70
Encryption: WPA2 PSK (CCMP)`
exec := createMockExecutor(cellList, "", 0)
wifi := NewWifiWithExecutor(exec, "wlan1")
_ = wifi.GetWifiCells()
cell := wifi.GetCell("PyxisWifi")
cell.Connect(uci, "secret")
}

71
openwrt/wifi_test.go Normal file
View File

@ -0,0 +1,71 @@
package openwrt
import (
"fmt"
"io/ioutil"
"testing"
)
// Test GestWifiCells method with 3 Cells
func TestGetWifiCells(t *testing.T) {
cellList, err := ioutil.ReadFile("testdata/wifi_cells_output_3.txt")
if err != nil {
t.Fatal(err)
}
exec := createMockExecutor(string(cellList), "", 0)
wifi := NewWifiWithExecutor(exec, "wlan1")
_ = wifi.GetWifiCells()
if len(wifi.Cells) != 3 {
fmt.Printf("Size of wifi.Cells is %d and not 3 !!!\n", len(wifi.Cells))
t.Error("Cell list is empty ... This can not append !! Fix your code Dummy !")
}
if g, e := wifi.Cells[0].Ssid, "PyxisWifi"; g != e {
t.Errorf("The first Cell have a bad SSID !\n %s is expected and we have %s", e, g)
}
if g, e := wifi.Cells[0].MacAdress, "68:A3:78:6E:D9:24"; g != e {
t.Errorf("The first Cell have a bad MAC !\n %s is expected and we have %s", e, g)
}
if g, e := wifi.Cells[0].Encryption, "none"; g != e {
t.Errorf("The first Cell have a bad Encryption!\n %s is expected and we have %s", e, g)
}
if g, e := wifi.Cells[1].Encryption, "psk"; g != e {
t.Errorf("The second Cell have a bad Encryption!\n %s is expected and we have %s", e, g)
}
if g, e := wifi.Cells[2].MacAdress, "0C:F4:D5:16:AA:18"; g != e {
t.Errorf("The last Cell have a bad MAC !\n %s is expected and we have %s", e, g)
}
}
// Test GestWifiCells method with empty list
func TestGetWifiCellsEmpty(t *testing.T) {
exec := createMockExecutor("", "", 0)
wifi := NewWifiWithExecutor(exec, "wlan1")
_ = wifi.GetWifiCells()
if len(wifi.Cells) != 0 {
fmt.Printf("Size of wifi.Cells is %d and not 0 !!!\n", len(wifi.Cells))
t.Error("Cell list is empty ... This can not append !! Fix your code Dummy !")
}
}
// Test GestWifiCells method with 3 Cells
func TestGetWifiCellsLarge(t *testing.T) {
cellList, err := ioutil.ReadFile("testdata/wifi_cells_output_large.txt")
if err != nil {
t.Fatal(err)
}
exec := createMockExecutor(string(cellList), "", 0)
wifi := NewWifiWithExecutor(exec, "wlan1")
_ = wifi.GetWifiCells()
if len(wifi.Cells) != 49 {
fmt.Printf("Size of wifi.Cells is %d and not 49 !!!\n", len(wifi.Cells))
t.Error("Cell list is empty ... This can not append !! Fix your code Dummy !")
}
}

View File

@ -0,0 +1,90 @@
#!/bin/bash
function rpcLogin() {
local IP=$1
local PORT=$2
local USER="$3"
local PASS="$4"
local RES=""
local cmd="curl"
local url="http://${IP}:${PORT}/cgi-bin/luci/rpc/auth"
local opt="--silent --data"
local data="{ \"id\": 1, \"method\": \"login\", \"params\": [ \"$USER\", \"$PASS\" ] }"
res=$(${cmd} ${url} ${opt} "${data}")
echo ${res} | jq -Ma ".result"
}
function iwList()
{
local IP=$1
local PORT=$2
local IFACE=$3
local TOKEN="$4"
local cmd="curl"
local url="http://${IP}:${PORT}/cgi-bin/luci/rpc/sys"
local opt="--silent --cookie sysauth=${TOKEN} --data"
local data="{ \"method\": \"wifi.getiwinfo\", \"params\": [ \"${IFACE}\" ] }"
res=$(${cmd} ${url} ${opt} "${data}")
echo ${res} |jq
}
function netDeviceInfo()
{
local IP=$1
local PORT=$2
local TOKEN="$3"
local cmd="curl"
local url="http://${IP}:${PORT}/cgi-bin/luci/rpc/sys"
local opt="--silent --cookie sysauth=${TOKEN} --data"
local data="{ \"method\": \"net.deviceinfo\", \"params\": [ \"wlan0\" ]}"
res=$(${cmd} ${url} ${opt} "${data}")
echo ${res} |jq
}
function netDevices()
{
local IP=$1
local PORT=$2
local TOKEN="$3"
local cmd="curl"
local url="http://${IP}:${PORT}/cgi-bin/luci/rpc/sys"
local opt="--silent --cookie sysauth=${TOKEN} --data"
local data="{ \"method\": \"net.devices\", \"params\": [] }"
res=$(${cmd} ${url} ${opt} "${data}")
echo ${res} |jq
}
function arpTable()
{
local IP=$1
local PORT=$2
local TOKEN="$3"
local cmd="curl"
local url="http://${IP}:${PORT}/cgi-bin/luci/rpc/sys"
local opt="--silent --cookie sysauth=${TOKEN} --data"
local data="{ \"method\": \"net.arptable\", \"params\": [] }"
res=$(${cmd} ${url} ${opt} "${data}")
echo ${res} |jq
}
#FIXME USER PASS
authToken=$(rpcLogin 192.168.1.1 8080 root 'XXXXXX EDITE MOI')
#netDevices 192.168.1.1 8080 ${authToken}
#netDeviceInfo 192.168.1.1 8080 ${authToken}
#iwList 192.168.1.1 8080 wlan0 ${authToken}
#iwList 192.168.1.1 8080 wlan1 ${authToken}
adoles:

View File

@ -0,0 +1,38 @@
package main
import (
"bytes"
"fmt"
"log"
"os/exec"
)
func scanWifi(iface string) string {
command := "iwinfo"
opt := "scan"
var out bytes.Buffer
var stderr bytes.Buffer
fmt.Printf("Running %s command\n", command)
exe := exec.Command(command, iface, opt)
exe.Stdout = &out
exe.Stderr = &stderr
err := exe.Run()
if err != nil {
fmt.Println(fmt.Sprint(err) + ": " + stderr.String())
log.Fatal(err)
}
return out.String()
}
func getCellInfo(iface string, ssidPrefix string) {
cells := scanWifi(iface)
fmt.Printf("%s\n", cells)
}
func main() {
getCellInfo("wlan1", "Base1")
}

View File

@ -0,0 +1,73 @@
#!/bin/bash
#
# Try to setup Kit !
#
function scanWifi() {
local iface=${1}
iwinfo ${iface} scan
return ${?}
}
function getWifiCell()
{
local iface=${1}
local ssidPrefix=${2}
res=$(scanWifi ${iface} | grep -B 1 "ESSID: \"${ssidPrefix}")
if [[ "${res}" == "Scanning not possible" ]] || [[ "${res}" == "" ]]
then
for try in $(seq 0 10)
do
sleep 5
res=$(scanWifi ${iface} | grep -B 1 "ESSID: \"${ssidPrefix}")
if [[ "${res}" == "Scanning not possible" ]] || [[ "${res}" == "" ]]
then
continue
else
echo ${res}
return 0
fi
done
echo "Error scanning wifi networks !"
return 2
else
echo ${res}
return 0
fi
}
function connectWifi() {
local iface=${1}
local ssidPrefix=${2}
local wpa=${3}
local cell=$(getWifiCell ${iface} ${ssidPrefix})
if [[ $? -ne 0 ]]
then
echo "Error on wifi scan !"
exit 12
fi
local ssid=$(echo ${cell} | awk '{print $7}' | sed -e 's/"//g')
local cellMAC=$(echo ${cell} | awk '{print $5}' | sed -e 's/"//g"')
set -x
uci delete wireless.@wifi-iface[1]
uci add wireless wifi-iface
uci set wireless.@wifi-iface[1].network="EmlidReach"
uci set wireless.@wifi-iface[1].ssid="${ssid}"
uci set wireless.@wifi-iface[1].encryption="psk2"
uci set wireless.@wifi-iface[1].device="radio1"
uci set wireless.@wifi-iface[1].mode="sta"
uci set wireless.@wifi-iface[1].bssid="${cellMAC}"
uci set wireless.@wifi-iface[1].key="${wpa}"
uci commit
reload_config
sleep 20
set +x
udhcpc -i ${iface}
}
# interface SSID_PREFIX Network_KEY
connectWifi "$1" "$2" "$3"

View File

@ -0,0 +1,26 @@
#!/bin/bash
function scanWifi()
{
local ifcace=$1
iwconfig ${iface} scan
function connectBoard()
{
uci delete wireless.@wifi-iface[1]
uci add wireless wifi-iface
uci set wireless.@wifi-iface[1].network="EmlidReach"
uci set wireless.@wifi-iface[1].ssid="Base1:2a:03"
uci set wireless.@wifi-iface[1].encryption="psk2"
uci set wireless.@wifi-iface[1].device="radio1"
uci set wireless.@wifi-iface[1].mode="sta"
uci set wireless.@wifi-iface[1].bssid="FC:DB:B3:7E:2A:03"
uci set wireless.@wifi-iface[1].key="basepyxis"
uci commit
reload_config
sleep 15
udhcpc -i wlan1
}
cells=$(scanWifi wlan1)

118
scripts/release Executable file
View File

@ -0,0 +1,118 @@
#!/bin/bash
set -eo pipefail
export GO111MODULE=on
OS_TARGETS=(windows linux)
ARCH_TARGETS=(amd64)
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
function build {
local name=$1
local srcdir=$2
local os=$3
local arch=$4
local dirname="$name-$os-$arch"
local destdir="$DIR/../release/$dirname"
local ext=
[ "$os" == "windows" ] && ext=.exe
rm -rf "$destdir"
mkdir -p "$destdir"
echo "building $dirname..."
CGO_ENABLED=0 GOOS="$os" GOARCH="$arch" go build \
-v \
-mod=vendor \
-ldflags="-s -w" \
-gcflags=-trimpath="${PWD}" \
-asmflags=-trimpath="${PWD}" \
-o "$destdir/$name$ext" \
"$srcdir"
if [ ! -z "$(which upx)" ]; then
upx --best "$destdir/$name$ext"
fi
}
function copy {
local name=$1
local os=$2
local arch=$3
local src=$4
local dest=$5
local dirname="$name-$os-$arch"
local destdir="$DIR/../release/$dirname"
echo "copying '$src' to '$destdir/$dest'..."
mkdir -p "$(dirname $destdir/$dest)"
cp -rfL $src "$destdir/$dest"
}
function compress {
local name=$1
local os=$2
local arch=$3
local dirname="$name-$os-$arch"
local destdir="$DIR/../release/$dirname"
echo "compressing $dirname..."
tar -czf "$destdir.tar.gz" -C "$destdir/../" "$dirname"
}
function release_updater {
local os=$1
local arch=$2
build 'updater' "$DIR/../cmd/updater" $os $arch
compress 'updater' $os $arch
}
function release_discovery {
local os=$1
local arch=$2
build 'discovery' "$DIR/../cmd/discovery" $os $arch
compress 'discovery' $os $arch
}
function release_configurator {
local os=$1
local arch=$2
build 'configurator' "$DIR/../cmd/configurator" $os $arch
compress 'configurator' $os $arch
}
function main {
for os in ${OS_TARGETS[@]}; do
for arch in ${ARCH_TARGETS[@]}; do
release_configurator $os $arch
release_updater $os $arch
release_discovery $os $arch
done
done
}
main