diff --git a/Makefile b/Makefile
index 5e6ea9a..59d6447 100644
--- a/Makefile
+++ b/Makefile
@@ -2,21 +2,25 @@ export GO111MODULE := on
build: bin/server
-bin/server: vendor
- CGO_ENABLED=0 go build -v -o ./bin/server ./cmd/server
+bin/server:
+ CGO_ENABLED=0 go build -mod=vendor -v -o ./bin/server ./cmd/server
watch:
modd
-test:
- go test -v ./...
+test: tidy
+ GO111MODULE=off go clean -testcache
+ go test -mod=vendor -v ./...
lint:
PATH=$(PATH):./bin/gometalinter gometalinter -e '.*/pkg/mod' --vendor ./...
-vendor:
+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
@@ -27,4 +31,4 @@ clean:
rm -rf ./bin
go clean -i -x -r -modcache
-.PHONY: test clean generate vendor install-devtools lint watch
\ No newline at end of file
+.PHONY: test clean generate vendor install-devtools lint watch tidy
\ No newline at end of file
diff --git a/doc/debug-websocket.md b/doc/debug-websocket.md
new file mode 100644
index 0000000..8d5dd6a
--- /dev/null
+++ b/doc/debug-websocket.md
@@ -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
\ No newline at end of file
diff --git a/go.mod b/go.mod
index dacfb13..ab2acf8 100644
--- a/go.mod
+++ b/go.mod
@@ -1,9 +1,12 @@
module forge.cadoles.com/Pyxis/orion
require (
+ forge.cadoles.com/Pyxis/golang-socketio v0.0.0-20180918160219-85050e6154f1
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/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
diff --git a/go.sum b/go.sum
index ee69d7d..50e8f67 100644
--- a/go.sum
+++ b/go.sum
@@ -1,9 +1,15 @@
+forge.cadoles.com/Pyxis/golang-socketio v0.0.0-20180918160219-85050e6154f1 h1:T/50zBZ+rLp9q7ntXpkKkiLBp5h8J5BnrHO6dbZUNqo=
+forge.cadoles.com/Pyxis/golang-socketio v0.0.0-20180918160219-85050e6154f1/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/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=
diff --git a/misc/reachview/updater_main.js b/misc/reachview/updater_main.js
new file mode 100644
index 0000000..b079e28
--- /dev/null
+++ b/misc/reachview/updater_main.js
@@ -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 .
+ */
+
+$(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('');
+ $('.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('');
+ $('.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('');
+
+ 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('');
+ }
+
+ $('.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('');
+ $('.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('');
+
+ $('.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 += '
';
+ $('#current_network').text(ssid);
+ } else {
+ to_append += '';
+ }
+
+ to_append += '';
+ to_append += '
' + ssid + '
';
+
+ if (key['is_connected']) {
+ to_append += '
Connected (' + key['ip'] + ')';
+ connectedNetwork = true;
+ } else {
+ to_append += '
Saved';
+ }
+
+ to_append += '
';
+
+ if (key['is_connected']) {
+ to_append += '
';
+ }
+
+ to_append += '';
+ });
+
+ $('#added_wi-fi').html(to_append);
+ $('#connected_wi-fi').html('');
+ $('#connected_wi-fi').append($('.connected_wi-fi_network'));
+
+ if (connectedNetwork) {
+ $('.wifi_status').html('');
+
+ socket.emit('get time sync status');
+
+ $('.overlay.sync_overlay').fadeOut();
+ $('.sync_status').html('');
+
+ syncInterval = setInterval(function () { socket.emit('get time sync status'); }, 1000);
+
+ wifiPass = true;
+ } else {
+ $('.wifi_status').html('');
+ $('.wi-fi_anchor.collapsed').click();
+
+ wifiPass = false;
+ }
+ });
+
+ socket.on('time sync status', function (msg) {
+ if (timePass) {
+ return;
+ }
+
+ if (msg['status']) {
+ $('.sync_status').html('');
+ $('.time_sync_warning').text('Time was synchronized!');
+
+ // Receiver update
+ availableReceiverVersion = false;
+
+ socket.emit('is receiver upgrade available');
+
+ $('.receiver_status').html('');
+ $('#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('');
+
+ 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('');
+ $('.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('');
+ $('#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('');
+ $('#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('');
+ $('.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('');
+
+ 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('');
+ }
+
+ 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('');
+ $('#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('');
+
+ $('.skip_receiver_update').trigger('click', [true]);
+ } else {
+ $('#receiver_upgrade_msg').text('Failed to perform receiver update');
+ $('.receiver_status').html('');
+ $('.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('');
+ $('.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('');
+
+ $('.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('');
+
+ $('#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('');
+ $('.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('');
+ $('.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('');
+ $('.test_warning').text('Test 3 failed');
+ $('.test_warning').slideDown();
+
+ testPass = true;
+ }
+
+ if (!msg['stc']) {
+ $('.tests_status').html('');
+ $('.test_warning').text('Test 4 failed');
+ $('.test_warning').slideDown();
+
+ testPass = true;
+ }
+
+ if (!msg['lora']) {
+ $('.tests_status').html('');
+ $('.test_warning').text('Test 5 failed');
+ $('.test_warning').slideDown();
+
+ testPass = true;
+ }
+
+ if (!msg['mpu']) {
+ $('.tests_status').html('');
+ $('.test_warning').text('Test 1 failed');
+ $('.test_warning').slideDown();
+
+ testPass = true;
+ }
+
+ if (!msg['u-blox']) {
+ $('.tests_status').html('');
+ $('.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('');
+ testPass = true;
+ }
+ } else {
+ if (msg['u-blox'] && msg['mpu'] && msg['lora'] && msg['ltc'] && msg['stc']) {
+ $('.tests_status').html('');
+ testPass = true;
+ }
+ }
+
+ $('.tests_status').fadeIn();
+
+ if (msg['mpu']) {
+ $('.mpu_test').html('');
+ } else {
+ $('.mpu_test').html('');
+ }
+
+ if (msg['u-blox']) {
+ $('.u-blox_test').html('');
+ } else {
+ $('.u-blox_test').html('');
+ }
+
+ if (msg['lora']) {
+ $('.lora_test').html('');
+ } else {
+ $('.lora_test').html('');
+ }
+
+ if (msg['stc']) {
+ $('.stc_test').html('');
+ } else {
+ $('.stc_test').html('');
+ }
+
+ if (msg['ltc']) {
+ $('.ltc_test').html('');
+ } else {
+ $('.ltc_test').html('');
+ }
+
+ socket.emit('get saved wifi networks');
+ });
+ });
+});
diff --git a/modd.conf b/modd.conf
index 018b708..fba3af3 100644
--- a/modd.conf
+++ b/modd.conf
@@ -1,4 +1,5 @@
**/*.go
+!**/*_test.go
modd.conf
.env
Makefile {
diff --git a/reach/browser_connected.go b/reach/browser_connected.go
new file mode 100644
index 0000000..a859cbb
--- /dev/null
+++ b/reach/browser_connected.go
@@ -0,0 +1,26 @@
+package reach
+
+import (
+ "github.com/pkg/errors"
+)
+
+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",
+ }
+ c.logf("sending '%s' event", EventBrowserConnected)
+ if err := c.conn.Emit(EventBrowserConnected, payload); err != nil {
+ return errors.Wrapf(err, "error while emitting '%s' event", EventBrowserConnected)
+ }
+ c.logf("'%s' event sent", EventBrowserConnected)
+ return nil
+}
diff --git a/reach/client.go b/reach/client.go
new file mode 100644
index 0000000..257cb5b
--- /dev/null
+++ b/reach/client.go
@@ -0,0 +1,99 @@
+package reach
+
+import (
+ "sync"
+ "time"
+
+ "forge.cadoles.com/Pyxis/golang-socketio"
+ "forge.cadoles.com/Pyxis/golang-socketio/transport"
+ "github.com/pkg/errors"
+)
+
+type handshake struct {
+ PingInterval time.Duration `json:"pingInterval"`
+}
+
+// Client is a ReachView Websocket API client
+type Client struct {
+ opts *Options
+ conn *gosocketio.Client
+}
+
+// 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) {
+ 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()
+
+ conn.On(gosocketio.OnError, nil)
+
+ 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
+ return
+}
+
+func (c *Client) logf(format string, args ...interface{}) {
+ if c.opts.Logger == nil {
+ return
+ }
+ c.opts.Logger.Printf(format, args...)
+}
+
+// NewClient returns a new ReachView Websocket API client
+func NewClient(opts ...OptionFunc) *Client {
+ options := DefaultOptions()
+ for _, o := range opts {
+ o(options)
+ }
+ return &Client{
+ opts: options,
+ }
+}
diff --git a/reach/client_test.go b/reach/client_test.go
new file mode 100644
index 0000000..658b98e
--- /dev/null
+++ b/reach/client_test.go
@@ -0,0 +1,27 @@
+package reach
+
+import (
+ "testing"
+)
+
+func TestClient(t *testing.T) {
+
+ client := NewClient(
+ WithStandardLogger(),
+ )
+ if err := client.Connect(); err != nil {
+ t.Fatal(err)
+ }
+
+ results, err := client.TestResults()
+ 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()
+
+}
diff --git a/reach/option.go b/reach/option.go
new file mode 100644
index 0000000..516cf45
--- /dev/null
+++ b/reach/option.go
@@ -0,0 +1,91 @@
+package reach
+
+import (
+ "log"
+ "os"
+ "time"
+
+ "forge.cadoles.com/Pyxis/golang-socketio"
+)
+
+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
+}
+
+type OptionFunc func(opts *Options)
+
+func WithEndpoint(host string, port int) OptionFunc {
+ return func(opts *Options) {
+ opts.Endpoint = gosocketio.GetUrl(host, port, false)
+ }
+}
+
+func WithPingInterval(interval time.Duration) OptionFunc {
+ return func(opts *Options) {
+ opts.PingInterval = interval
+ }
+}
+
+func WithPingTimeout(timeout time.Duration) OptionFunc {
+ return func(opts *Options) {
+ opts.PingTimeout = timeout
+ }
+}
+
+func WithReceiveTimeout(timeout time.Duration) OptionFunc {
+ return func(opts *Options) {
+ opts.ReceiveTimeout = timeout
+ }
+}
+
+func WithSendTimeout(timeout time.Duration) OptionFunc {
+ return func(opts *Options) {
+ opts.SendTimeout = timeout
+ }
+}
+
+func WithBufferSize(size int) OptionFunc {
+ return func(opts *Options) {
+ opts.BufferSize = size
+ }
+}
+
+func WithLogger(logger Logger) OptionFunc {
+ return func(opts *Options) {
+ opts.Logger = logger
+ }
+}
+
+func WithStandardLogger() OptionFunc {
+ return func(opts *Options) {
+ logger := log.New(os.Stdout, "[reachrs] ", log.LstdFlags)
+ opts.Logger = logger
+ }
+}
+
+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
+}
diff --git a/reach/probe.go b/reach/probe.go
new file mode 100644
index 0000000..802b9f3
--- /dev/null
+++ b/reach/probe.go
@@ -0,0 +1,37 @@
+package reach
+
+import (
+ "sync"
+
+ "forge.cadoles.com/Pyxis/golang-socketio"
+ "github.com/pkg/errors"
+)
+
+const (
+ // EventProbe -
+ EventProbe = "probe"
+)
+
+// Probe -
+func (c *Client) Probe() error {
+
+ var wg sync.WaitGroup
+
+ wg.Add(1)
+
+ c.conn.On(EventProbe, func(h *gosocketio.Channel) {
+ wg.Done()
+ c.conn.On(EventProbe, nil)
+ })
+
+ c.logf("sending '%s' event", EventProbe)
+ if err := c.conn.Emit(EventProbe, nil); err != nil {
+ return errors.Wrapf(err, "error while emitting '%s' event", EventProbe)
+ }
+ c.logf("'%s' event sent", EventProbe)
+
+ wg.Wait()
+
+ return nil
+
+}
diff --git a/reach/test_results.go b/reach/test_results.go
new file mode 100644
index 0000000..2ceb7bb
--- /dev/null
+++ b/reach/test_results.go
@@ -0,0 +1,51 @@
+package reach
+
+import (
+ "sync"
+
+ "forge.cadoles.com/Pyxis/golang-socketio"
+ "github.com/pkg/errors"
+)
+
+const (
+ // EventGetTestResults is a request for the ReachRS module's test results
+ EventGetTestResults = "get test results"
+ // EventTestResults is the response of the EventGetTestResults request
+ EventTestResults = "test results"
+)
+
+// TestResults are the ReachRS module's test results
+type TestResults struct {
+ UBlox bool `json:"u-blox"`
+ STC bool `json:"stc"`
+ MPU bool `json:"mpu"`
+ Device string `json:"device"`
+ Lora bool `json:"lora"`
+}
+
+// TestResults returns the ReachRS module tests results
+func (c *Client) TestResults() (*TestResults, error) {
+
+ var err error
+ var results *TestResults
+ var wg sync.WaitGroup
+
+ wg.Add(1)
+
+ c.conn.On(EventTestResults, func(h *gosocketio.Channel, res *TestResults) {
+ results = res
+ c.conn.On(EventTestResults, nil)
+ wg.Done()
+ })
+
+ c.logf("sending '%s' event", EventGetTestResults)
+ if err = c.conn.Emit(EventGetTestResults, nil); err != nil {
+ return nil, errors.Wrapf(err, "error while emitting '%s' event", EventGetTestResults)
+ }
+ c.logf("'%s' event sent", EventGetTestResults)
+
+ wg.Wait()
+
+ return results, err
+
+}