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 + +}