Compare commits
68 Commits
master
...
feature/re
Author | SHA1 | Date | |
---|---|---|---|
624d20046d | |||
c831660f62 | |||
fd8657a495 | |||
599c4cf1d5 | |||
dc4dd7a9fb | |||
854052bc84 | |||
e4bb33e895 | |||
9367e5e79e | |||
1dcd03e455 | |||
70ddfe49ed | |||
364dfecb31 | |||
cf58d86eb0 | |||
f769ca382e | |||
1a04caafdf | |||
6e9df8d386 | |||
77a779aebe | |||
2c7aad91c6 | |||
f67724dc3a | |||
e3672cc6d7 | |||
ff13693f69 | |||
ba3d85f48b | |||
a44e40eea2 | |||
17487dd7a6 | |||
b34ccd3dee | |||
1dbb62c490 | |||
ec26c74ae8 | |||
de0255fd03 | |||
3f36175f1a | |||
b680e256b9 | |||
735bc8d19e | |||
e1f0c68630 | |||
b8f0a554c1 | |||
1aa77c90ab | |||
3c8f85dcff | |||
3adfc2a5bb | |||
425d139b1c | |||
a968004d4e | |||
5f6dcd2253 | |||
13267b2d5a | |||
6534c51fe4 | |||
5008dfbd56 | |||
964e1d713e | |||
f24f697a3d | |||
52bac70174 | |||
ee3113ed80 | |||
582e362aae | |||
403e14c320 | |||
2d0c3d9de9 | |||
998f718354 | |||
dd0a466bd3 | |||
48db0458c0 | |||
fc4e50bf08 | |||
1e16779c52 | |||
055ee0000d | |||
1bf205347e | |||
23aee0fb68 | |||
505d462352 | |||
a2d1c675d7 | |||
e7831091ad | |||
55e74355bf | |||
68371cb7c7 | |||
cd27332c4f | |||
267017bdc1 | |||
2421071da6 | |||
4489f22ac0 | |||
29d5a48440 | |||
3d693f0632 | |||
9b11769a70 |
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/vendor
|
||||
/bin
|
||||
/.env
|
59
Jenkinsfile
vendored
Normal file
59
Jenkinsfile
vendored
Normal 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()],
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
39
Makefile
Normal file
39
Makefile
Normal file
@ -0,0 +1,39 @@
|
||||
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:
|
||||
@GO111MODULE=off 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
|
||||
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
|
||||
|
||||
.PHONY: test clean generate vendor install-devtools lint watch tidy doc
|
11
README.md
Normal file
11
README.md
Normal 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
|
24
cmd/server/config.go
Normal file
24
cmd/server/config.go
Normal 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
36
cmd/server/main.go
Normal 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)
|
||||
}
|
||||
|
||||
}
|
40
containers/openwrt/Dockerfile
Normal file
40
containers/openwrt/Dockerfile
Normal 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
4
doc/README.md
Normal 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
22
doc/compose.md
Normal 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
14
doc/debug-websocket.md
Normal 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
24
doc/prepa-dev.md
Normal 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
12
docker-compose.yml
Normal 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
|
17
emlid/browser_connected.go
Normal file
17
emlid/browser_connected.go
Normal 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)
|
||||
}
|
158
emlid/client.go
Normal file
158
emlid/client.go
Normal file
@ -0,0 +1,158 @@
|
||||
package emlid
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"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
|
||||
}
|
||||
|
||||
// Emit a new event with the given data
|
||||
func (c *Client) Emit(event string, data interface{}) error {
|
||||
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{}) {
|
||||
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,
|
||||
}
|
||||
}
|
102
emlid/option.go
Normal file
102
emlid/option.go
Normal file
@ -0,0 +1,102 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
4
emlid/reach.go
Normal 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
|
14
emlid/reachview/client.go
Normal file
14
emlid/reachview/client.go
Normal file
@ -0,0 +1,14 @@
|
||||
package reachview
|
||||
|
||||
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 {
|
||||
client := emlid.NewClient(opts...)
|
||||
return &Client{client}
|
||||
}
|
51
emlid/reachview/configuration.go
Normal file
51
emlid/reachview/configuration.go
Normal file
@ -0,0 +1,51 @@
|
||||
package reachview
|
||||
|
||||
import "context"
|
||||
|
||||
const (
|
||||
eventGetConfiguration = "get configuration"
|
||||
eventCurrentConfiguration = "current configuration"
|
||||
eventApplyConfiguration = "apply configuration"
|
||||
eventConfigurationApplied = "configuration applied"
|
||||
eventResetConfiguration = "reset configuration 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) (string, *Configuration, error) {
|
||||
res := &configurationApplied{}
|
||||
if err := r.ReqResp(ctx, eventResetConfiguration, nil, eventConfigurationApplied, res); err != nil {
|
||||
return ConfigurationApplyFailed, nil, err
|
||||
}
|
||||
return res.Result, res.Configuration, nil
|
||||
}
|
243
emlid/reachview/configuration_model.go
Normal file
243
emlid/reachview/configuration_model.go
Normal 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 *string `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 Europe’s 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 *LoRaFrequencyRange `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"`
|
||||
}
|
91
emlid/reachview/configuration_test.go
Normal file
91
emlid/reachview/configuration_test.go
Normal 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(), 10*time.Second)
|
||||
defer applyConfCancel()
|
||||
|
||||
result, config, err := client.ApplyConfiguration(ctx, config)
|
||||
if err != nil {
|
||||
t.Fatal(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()
|
||||
|
||||
}
|
58
emlid/reachview/project.go
Normal file
58
emlid/reachview/project.go
Normal file
@ -0,0 +1,58 @@
|
||||
package reachview
|
||||
|
||||
import "context"
|
||||
|
||||
const (
|
||||
eventCreateProject = "create project"
|
||||
eventProjectCreated = "project created"
|
||||
)
|
||||
|
||||
type ProjectID string
|
||||
|
||||
type Project struct {
|
||||
Name string `json:"name"`
|
||||
Author string `json:"author"`
|
||||
Comment string `json:"comment"`
|
||||
Projection string `json:"projection"`
|
||||
Length int `json:"length"`
|
||||
Requirements *ProjectRequirements `json:"requirements"`
|
||||
AntennaHeight string `json:"antenna height"`
|
||||
}
|
||||
|
||||
type ProjectRequirements struct {
|
||||
Fix *ProjectRequirementsEntry `json:"FIX"`
|
||||
Float *ProjectRequirementsEntry `json:"FLOAT"`
|
||||
Single *ProjectRequirementsEntry `json:"SINGLE"`
|
||||
}
|
||||
|
||||
type ProjectRequirementsEntry struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
DOP string `json:"DOP"`
|
||||
Time int `json:"time"`
|
||||
LateralRMS float64 `json:"lateral rms"`
|
||||
}
|
||||
|
||||
func (c *Client) CreateProject(ctx context.Context, project *Project) (ProjectID, error) {
|
||||
var projectID ProjectID
|
||||
err := c.ReqResp(ctx, eventCreateProject, project, eventProjectCreated, &projectID)
|
||||
if err != nil {
|
||||
return projectID, err
|
||||
}
|
||||
return projectID, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListProjects() {
|
||||
|
||||
}
|
||||
|
||||
func (c *Client) OpenProject(id ProjectID) {
|
||||
|
||||
}
|
||||
|
||||
func (c *Client) DeleteProject() {
|
||||
|
||||
}
|
||||
|
||||
func (c *Client) CloseProject() {
|
||||
|
||||
}
|
44
emlid/reachview/project_test.go
Normal file
44
emlid/reachview/project_test.go
Normal file
@ -0,0 +1,44 @@
|
||||
package reachview
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/Pyxis/orion/emlid"
|
||||
)
|
||||
|
||||
func TestReachViewProjectCRUD(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, cancelCreateProject := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancelCreateProject()
|
||||
|
||||
project := &Project{
|
||||
Name: fmt.Sprintf("Test-%d", time.Now().Unix()),
|
||||
}
|
||||
|
||||
projectID, err := client.CreateProject(ctx, project)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if projectID == "" {
|
||||
t.Errorf("projectID should not be empty")
|
||||
}
|
||||
|
||||
defer client.Close()
|
||||
|
||||
}
|
10
emlid/reachview/rtk.go
Normal file
10
emlid/reachview/rtk.go
Normal 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)
|
||||
}
|
13
emlid/reachview/util_test.go
Normal file
13
emlid/reachview/util_test.go
Normal file
@ -0,0 +1,13 @@
|
||||
package reachview
|
||||
|
||||
import "flag"
|
||||
|
||||
var runReachViewIntegrationTests = flag.Bool(
|
||||
"reachview-integration", false,
|
||||
"Run the 'ReachView' 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",
|
||||
)
|
15
emlid/updater/client.go
Normal file
15
emlid/updater/client.go
Normal file
@ -0,0 +1,15 @@
|
||||
// Package updater provides an API to communicate with the ReachRS modules "Updater" application
|
||||
package updater
|
||||
|
||||
import "forge.cadoles.com/Pyxis/orion/emlid"
|
||||
|
||||
// Client is a ReachRS Updater client
|
||||
type Client struct {
|
||||
*emlid.Client
|
||||
}
|
||||
|
||||
// NewClient returns a new ReachRS Updater client
|
||||
func NewClient(opts ...emlid.OptionFunc) *Client {
|
||||
client := emlid.NewClient(opts...)
|
||||
return &Client{client}
|
||||
}
|
39
emlid/updater/example_test.go
Normal file
39
emlid/updater/example_test.go
Normal 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()
|
||||
}
|
21
emlid/updater/reachview_version.go
Normal file
21
emlid/updater/reachview_version.go
Normal file
@ -0,0 +1,21 @@
|
||||
package updater
|
||||
|
||||
import "context"
|
||||
|
||||
const (
|
||||
eventGetReachViewVersion = "get reachview version"
|
||||
eventReachViewVersionResults = "current reachview version"
|
||||
)
|
||||
|
||||
type reachViewVersion struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// ReachViewVersion returns the ReachRS module ReachView version
|
||||
func (c *Client) ReachViewVersion(ctx context.Context) (string, error) {
|
||||
res := &reachViewVersion{}
|
||||
if err := c.ReqResp(ctx, eventGetReachViewVersion, nil, eventReachViewVersionResults, res); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return res.Version, nil
|
||||
}
|
37
emlid/updater/reachview_version_test.go
Normal file
37
emlid/updater/reachview_version_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/Pyxis/orion/emlid"
|
||||
)
|
||||
|
||||
func TestClientReachViewVersion(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()
|
||||
version, err := client.ReachViewVersion(ctx)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if version == "" {
|
||||
t.Error("version should not be empty")
|
||||
}
|
||||
|
||||
defer client.Close()
|
||||
|
||||
}
|
56
emlid/updater/reboot_now.go
Normal file
56
emlid/updater/reboot_now.go
Normal 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
|
||||
|
||||
}
|
37
emlid/updater/reboot_now_test.go
Normal file
37
emlid/updater/reboot_now_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/Pyxis/orion/emlid"
|
||||
)
|
||||
|
||||
func TestClientRebootNow(t *testing.T) {
|
||||
|
||||
if !*runUpdaterIntegrationTests {
|
||||
t.Skip("To run this test, use: go test -updater-integration")
|
||||
}
|
||||
|
||||
if !*runRebootTest {
|
||||
t.Skip("To run this test, use: go test -updater-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()
|
||||
|
||||
}
|
23
emlid/updater/receiver_upgrade.go
Normal file
23
emlid/updater/receiver_upgrade.go
Normal 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
|
||||
}
|
34
emlid/updater/receiver_upgrade_test.go
Normal file
34
emlid/updater/receiver_upgrade_test.go
Normal 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()
|
||||
|
||||
}
|
27
emlid/updater/test_results.go
Normal file
27
emlid/updater/test_results.go
Normal 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
|
||||
}
|
38
emlid/updater/test_results_test.go
Normal file
38
emlid/updater/test_results_test.go
Normal 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()
|
||||
|
||||
}
|
23
emlid/updater/time_sync.go
Normal file
23
emlid/updater/time_sync.go
Normal 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
|
||||
}
|
34
emlid/updater/time_sync_test.go
Normal file
34
emlid/updater/time_sync_test.go
Normal 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
28
emlid/updater/update.go
Normal 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
|
||||
}
|
34
emlid/updater/update_test.go
Normal file
34
emlid/updater/update_test.go
Normal 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()
|
||||
|
||||
}
|
23
emlid/updater/util_test.go
Normal file
23
emlid/updater/util_test.go
Normal 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 runRebootTest = flag.Bool(
|
||||
"updater-reboot-test", false,
|
||||
"Run the updater 'Reboot' test (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 reachHost = flag.String(
|
||||
"reach-host", "192.168.42.1",
|
||||
"The Reach module host to use in integration tests",
|
||||
)
|
116
emlid/updater/wifi_networks.go
Normal file
116
emlid/updater/wifi_networks.go
Normal 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
|
||||
}
|
145
emlid/updater/wifi_networks_test.go
Normal file
145
emlid/updater/wifi_networks_test.go
Normal 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()
|
||||
|
||||
}
|
16
example/reachview/README.md
Normal file
16
example/reachview/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Example: ReachView
|
||||
|
||||
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 example/reachview/main.go \
|
||||
-mode 'rover|base'\
|
||||
-host '<DEVICE_IP_ADDRESS'\
|
||||
```
|
228
example/reachview/main.go
Normal file
228
example/reachview/main.go
Normal file
@ -0,0 +1,228 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/Pyxis/orion/emlid"
|
||||
"forge.cadoles.com/Pyxis/orion/emlid/reachview"
|
||||
)
|
||||
|
||||
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.String("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,
|
||||
},
|
||||
}
|
||||
|
||||
log.Println("configuring module as rover")
|
||||
|
||||
applyConfiguration(c, config)
|
||||
|
||||
}
|
||||
|
||||
func configureBase() {
|
||||
|
||||
c := connect()
|
||||
defer c.Close()
|
||||
|
||||
resetConfiguration(c)
|
||||
|
||||
log.Println("configuring module as base")
|
||||
|
||||
config := getCommonConfiguration()
|
||||
config.RTKSettings.UpdateRate = reachview.String("1")
|
||||
config.BaseMode = &reachview.BaseMode{
|
||||
Output: &reachview.Output{
|
||||
Enabled: reachview.True,
|
||||
Format: reachview.IOFormatRTCM3,
|
||||
Type: reachview.IOTypeLoRa,
|
||||
},
|
||||
BaseCoordinates: &reachview.BaseCoordinates{
|
||||
Accumulation: reachview.String("1"),
|
||||
AntennaOffset: &reachview.AntennaOffset{
|
||||
Up: reachview.String("2.20"),
|
||||
},
|
||||
Mode: reachview.BaseCoordinatesModeAverageSingle,
|
||||
Format: reachview.BaseCoordinatesFormatLLH,
|
||||
},
|
||||
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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
applyConfiguration(c, config)
|
||||
|
||||
}
|
||||
|
||||
func applyConfiguration(c *reachview.Client, config *reachview.Configuration) {
|
||||
|
||||
ctx, applyConfCancel := context.WithTimeout(context.Background(), 10*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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func resetConfiguration(c *reachview.Client) {
|
||||
|
||||
log.Println("resetting module configuration")
|
||||
ctx, resetCancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer resetCancel()
|
||||
result, _, err := c.ResetConfiguration(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if result != reachview.ConfigurationApplySuccess {
|
||||
log.Fatal("configuration reset failed !")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
UpdateRate: reachview.String("5"),
|
||||
},
|
||||
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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
27
example/updater/README.md
Normal file
27
example/updater/README.md
Normal 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 example/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 example/updater/main.go \
|
||||
-phase 'update-and-reboot'\
|
||||
-host '<DEVICE_IP_ADDRESS'
|
||||
```
|
171
example/updater/main.go
Normal file
171
example/updater/main.go
Normal 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)
|
||||
}
|
||||
|
||||
}
|
15
go.mod
Normal file
15
go.mod
Normal file
@ -0,0 +1,15 @@
|
||||
module forge.cadoles.com/Pyxis/orion
|
||||
|
||||
require (
|
||||
forge.cadoles.com/Pyxis/golang-socketio v0.0.0-20180919100209-bb857ced6b95
|
||||
github.com/caarlos0/env v3.3.0+incompatible
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-chi/chi v3.3.3+incompatible
|
||||
github.com/gorilla/websocket v1.4.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.0.0
|
||||
github.com/pkg/errors v0.8.0
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/testify v1.2.2 // indirect
|
||||
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3 // indirect
|
||||
golang.org/x/text v0.3.0 // indirect
|
||||
)
|
22
go.sum
Normal file
22
go.sum
Normal file
@ -0,0 +1,22 @@
|
||||
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/caarlos0/env v3.3.0+incompatible h1:jCfY0ilpzC2FFViyZyDKCxKybDESTwaR+ebh8zm6AOE=
|
||||
github.com/caarlos0/env v3.3.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y=
|
||||
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/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/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I=
|
||||
github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
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/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
17
misc/git-hooks/pre-commit
Executable file
17
misc/git-hooks/pre-commit
Executable 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
|
823
misc/reachview/updater_main.js
Normal file
823
misc/reachview/updater_main.js
Normal 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
17
modd.conf
Normal 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
38
openwrt/dhcp_client.go
Normal 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)
|
||||
}
|
12
openwrt/dhcp_client_test.go
Normal file
12
openwrt/dhcp_client_test.go
Normal 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
67
openwrt/executor.go
Normal 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
18
openwrt/executor_test.go
Normal 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
77
openwrt/network.go
Normal 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
22
openwrt/network_test.go
Normal 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
35
openwrt/test.go
Normal 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,
|
||||
}
|
||||
}
|
4
openwrt/testdata/proc_net_wireless.txt
vendored
Normal file
4
openwrt/testdata/proc_net_wireless.txt
vendored
Normal 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
|
17
openwrt/testdata/wifi_cells_output_3.txt
vendored
Normal file
17
openwrt/testdata/wifi_cells_output_3.txt
vendored
Normal 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
|
293
openwrt/testdata/wifi_cells_output_large.txt
vendored
Normal file
293
openwrt/testdata/wifi_cells_output_large.txt
vendored
Normal 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
72
openwrt/uci.go
Normal 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
100
openwrt/uci_test.go
Normal 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
91
openwrt/wifi.go
Normal 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
93
openwrt/wifi_cell.go
Normal 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
21
openwrt/wifi_cell_test.go
Normal 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
71
openwrt/wifi_test.go
Normal 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 !")
|
||||
}
|
||||
}
|
90
scripts/experiment/luciRpc.sh
Normal file
90
scripts/experiment/luciRpc.sh
Normal 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:
|
||||
|
38
scripts/experiment/setupKit.go
Normal file
38
scripts/experiment/setupKit.go
Normal 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")
|
||||
}
|
73
scripts/experiment/setupKit.sh
Normal file
73
scripts/experiment/setupKit.sh
Normal 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"
|
||||
|
26
scripts/experiment/uciCli.sh
Normal file
26
scripts/experiment/uciCli.sh
Normal 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)
|
Reference in New Issue
Block a user