diff --git a/Makefile b/Makefile index 51bbda68..6cf09fdb 100644 --- a/Makefile +++ b/Makefile @@ -9,8 +9,37 @@ all: test: sh -c true +www: + mkdir -p /var/www + mkdir -p /var/www/css + cp web/css/*.css /var/www/css + mkdir -p /var/www/js + cp web/js/*.js /var/www/js + mkdir -p /var/www/img + cp web/img/logo*.png /var/www/img + cp web/img/screen*.png /var/www/img + mkdir -p /var/www/maui + mkdir -p /var/www/maui/js + cp web/maui/js/angular-ui-router.min.js /var/www/maui/js + cp web/maui/js/mobile-angular-ui.min.js /var/www/maui/js + cp web/maui/js/angular.min.js /var/www/maui/js + cp web/maui/js/mobile-angular-ui.gestures.min.js /var/www/maui/js + cp web/maui/js/mobile-angular-ui.core.min.js /var/www/maui/js + mkdir -p /var/www/maui/css + cp web/maui/css/mobile-angular-ui-hover.min.css /var/www/maui/css + cp web/maui/css/mobile-angular-ui-desktop.min.css /var/www/maui/css + cp web/maui/css/mobile-angular-ui-base.min.css /var/www/maui/css + mkdir -p /var/www/maui/fonts + cp web/maui/fonts/fontawesome-webfont.woff /var/www/maui/fonts + mkdir -p /var/www/plates + cp web/plates/*.html /var/www/plates + mkdir -p /var/www/plates/js + cp web/plates/js/*.js /var/www/plates/js + cp web/index.html /var/www + install: cp -f gen_gdl90 /usr/bin/gen_gdl90 chmod 755 /usr/bin/gen_gdl90 + clean: rm -f gen_gdl90 diff --git a/main/managementinterface.go b/main/managementinterface.go index 8332ce21..4343ff08 100644 --- a/main/managementinterface.go +++ b/main/managementinterface.go @@ -43,6 +43,9 @@ func handleWeatherWS(conn *websocket.Conn) { func handleTrafficWS(conn *websocket.Conn) { trafficMutex.Lock() for _, traf := range traffic { + if !traf.Position_valid { // Don't send unless a valid position exists. + continue + } trafficJSON, _ := json.Marshal(&traf) conn.Write(trafficJSON) } diff --git a/main/traffic.go b/main/traffic.go index 3448d590..7d093570 100644 --- a/main/traffic.go +++ b/main/traffic.go @@ -48,6 +48,11 @@ AUXSV: */ +const ( + TRAFFIC_SOURCE_1090ES = 1 + TRAFFIC_SOURCE_UAT = 2 +) + type TrafficInfo struct { Icao_addr uint32 addr_type uint8 @@ -68,7 +73,8 @@ type TrafficInfo struct { Tail string - Last_seen time.Time + Last_seen time.Time + Last_source uint8 } var traffic map[uint32]TrafficInfo @@ -96,7 +102,9 @@ func sendTrafficUpdates() { // Send update to attached client. func registerTrafficUpdate(ti TrafficInfo) { - trafficUpdate <- ti + if ti.Position_valid { // Don't send unless a valid position exists. + trafficUpdate <- ti + } } func makeTrafficReport(ti TrafficInfo) { @@ -326,6 +334,7 @@ func parseDownlinkReport(s string) { //OK. // fmt.Printf("tisb_site_id %d, utc_coupled %t\n", tisb_site_id, utc_coupled) + ti.Last_source = TRAFFIC_SOURCE_UAT ti.Last_seen = time.Now() // Parse tail number, if available. @@ -497,6 +506,7 @@ func esListen() { } // Update "last seen" (any type of message, as long as the ICAO addr can be parsed). + ti.Last_source = TRAFFIC_SOURCE_1090ES ti.Last_seen = time.Now() ti.addr_type = 0 //FIXME: ADS-B with ICAO address. Not recognized by ForeFlight. diff --git a/web/css/main.css b/web/css/main.css index 2a147113..334b763e 100755 --- a/web/css/main.css +++ b/web/css/main.css @@ -1,122 +1,216 @@ .help-page { - font-size: 0.85em; + font-size: 0.85em; } .section_invisible { - display: none; + display: none; } .ahrs_connected { - content: + content: } .ahrs_disconnected {} .separator { - height: 1px; - border-bottom: 1px solid #cccccc; - margin-bottom: 4px; + height: 1px; + border-bottom: 1px solid #cccccc; + margin-bottom: 4px; } +.label_adj { + margin-left: -6px; + margin-right: -6px; +} + +.col-padding-shift-right { + margin-left: -12px; + margin-right: 12px; +} + +.col-padding-shift-left { + margin-left: 12px; + margin-right: -12px; +} + +.feature-icon { + font-size: 44px; + text-align: center; + line-height: 68px; + opacity: 0.8; + width: 72px; + height: 72px; + border: 3px solid; + border-radius: 200px; + padding: 0; + margin-bottom: 20px; +} + +.list-simple { + padding-left: 12px; + list-style: none; +} /* *************************************************************************** everything below this comment represents tweeks to the mobile-angular-uis CSS *************************************************************************** */ -.label_adj { - margin-left: -15px; - margin-right: -15px; - } +@font-face { + font-family: 'FontAwesome'; + src: url('../maui/fonts/fontawesome-webfont.woff?v=4.4.0') format('woff'); + font-weight: normal; + font-style: normal; +} + +.row { + margin-left: -6px; + margin-right: -6px; +} + +.col-xs-1, +.col-sm-1, +.col-md-1, +.col-lg-1, +.col-xs-2, +.col-sm-2, +.col-md-2, +.col-lg-2, +.col-xs-3, +.col-sm-3, +.col-md-3, +.col-lg-3, +.col-xs-4, +.col-sm-4, +.col-md-4, +.col-lg-4, +.col-xs-5, +.col-sm-5, +.col-md-5, +.col-lg-5, +.col-xs-6, +.col-sm-6, +.col-md-6, +.col-lg-6, +.col-xs-7, +.col-sm-7, +.col-md-7, +.col-lg-7, +.col-xs-8, +.col-sm-8, +.col-md-8, +.col-lg-8, +.col-xs-9, +.col-sm-9, +.col-md-9, +.col-lg-9, +.col-xs-10, +.col-sm-10, +.col-md-10, +.col-lg-10, +.col-xs-11, +.col-sm-11, +.col-md-11, +.col-lg-11, +.col-xs-12, +.col-sm-12, +.col-md-12, +.col-lg-12 { + position: relative; + min-height: 1px; + padding-left: 6px; + padding-right: 6px; +} + input[type="number_format"] { - text-align: end; + text-align: end; } .sidebar .scrollable-header, .panel-title { - border-bottom: 1px solid #cccccc; + border-bottom: 1px solid #cccccc; } .app-body, .panel-default, .scrollable-content, .form-group { - background-color: #ffffff; + background-color: #ffffff; } .app-content { - margin-top: 4px; + margin-top: 4px; } .sidebar-header, .app-name { - line-height: 50px; - color: #000; - background-color: #f7f7f7; + line-height: 50px; + color: #000; + background-color: #f7f7f7; } .panel-heading { - padding: 4px 4px 4px 4px; - border-bottom: 1px solid transparent; - border-top-right-radius: 3px; - border-top-left-radius: 3px; - border-bottom: 1px solid #cccccc; - font-weight: bold; + padding: 4px 4px 4px 4px; + border-bottom: 1px solid transparent; + border-top-right-radius: 3px; + border-top-left-radius: 3px; + border-bottom: 1px solid #cccccc; + font-weight: bold; } .panel-group .panel-heading { - border-bottom: inherit; + border-bottom: inherit; } .panel_label { - font-size: 16px; - margin-top: 4px; - display: inline-block; - line-height: 12px; - vertical-align: bottom; - padding-bottom: 2px; + font-size: 16px; + margin-top: 4px; + display: inline-block; + line-height: 12px; + vertical-align: bottom; + padding-bottom: 2px; } .panel-body { - padding: 8px 4px 4px 4px; + padding: 8px 4px 4px 4px; } .control-label { - line-height: 28px; - margin-bottom: 4px; + line-height: 28px; + margin-bottom: 4px; } .switch { - cursor: pointer; - position: relative; - display: block; - width: 52px; - height: 28px; - margin-bottom: 4px; + cursor: pointer; + position: relative; + display: block; + width: 52px; + height: 28px; + margin-bottom: 4px; } .switch .switch-handle { - width: 26px; - height: 28px; + width: 26px; + height: 28px; } .switch.active .switch-handle { - border-color: #007aff; - -webkit-transform: translate(26px, 0); - -ms-transform: translate(26px, 0); - -o-transform: translate(26px, 0); - transform: translate(26px, 0); + border-color: #007aff; + -webkit-transform: translate(26px, 0); + -ms-transform: translate(26px, 0); + -o-transform: translate(26px, 0); + transform: translate(26px, 0); } pre { - display: block; - padding: 0; - margin: 0; - font-size: 13px; - line-height: 1.42857143; - word-break: break-all; - word-wrap: break-word; - color: #333333; - background-color: inherit; - border: 0px; - border-radius: 0px; + display: block; + padding: 0; + margin: 0; + font-size: 13px; + line-height: 1.42857143; + word-break: break-all; + word-wrap: break-word; + color: #333333; + background-color: inherit; + border: 0px; + border-radius: 0px; } \ No newline at end of file diff --git a/web/index.html b/web/index.html index 73753d96..a867e77c 100755 --- a/web/index.html +++ b/web/index.html @@ -2,137 +2,138 @@ - - Stratux - - - - - - - - - + + Stratux + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + - + - - - - + + + + - - - - - - - - - + + + + + + + + - - - - + + + + -
+
- - - -
-
-
-
-
-
-
-
-
+ +
+
+
+
+
+
+
+
+
-
+
\ No newline at end of file diff --git a/web/js/main.js b/web/js/main.js index 0c4160aa..916e439f 100755 --- a/web/js/main.js +++ b/web/js/main.js @@ -12,67 +12,22 @@ var appControllers = angular.module('appControllers', []); app.config(function ($stateProvider, $urlRouterProvider) { - $stateProvider - .state('home', { - url: '/', - templateUrl: 'plates/status.html', - controller: 'StatusCtrl', - reloadOnSearch: false - }) - .state('weather', { - url: '/weather', - templateUrl: 'plates/weather.html', - controller: 'WeatherCtrl', - reloadOnSearch: false - }) - .state('traffic', { - url: '/traffic', - templateUrl: 'plates/traffic.html', - controller: 'TrafficCtrl', - reloadOnSearch: false - }) - .state('gps', { - url: '/gps', - templateUrl: 'plates/gps.html', - controller: 'GPSCtrl', - reloadOnSearch: false - }) - .state('ahrs', { - url: '/ahrs', - templateUrl: 'plates/ahrs.html', - controller: 'AHRSCtrl', - reloadOnSearch: false - }) - .state('logs', { - url: '/logs', - templateUrl: 'plates/logs.html', - controller: 'LogsCtrl', - reloadOnSearch: false - }) - .state('settings', { - url: '/settings', - templateUrl: 'plates/settings.html', - controller: 'SettingsCtrl', - reloadOnSearch: false - }); - - $urlRouterProvider.otherwise('/'); + $stateProvider + .state('home', { url: '/', templateUrl: 'plates/status.html', controller: 'StatusCtrl', reloadOnSearch: false }) + .state('weather', { url: '/weather', templateUrl: 'plates/weather.html', controller: 'WeatherCtrl', reloadOnSearch: false }) + .state('traffic', { url: '/traffic', templateUrl: 'plates/traffic.html', controller: 'TrafficCtrl', reloadOnSearch: false }) + .state('gps', { url: '/gps', templateUrl: 'plates/gps.html', controller: 'GPSCtrl', reloadOnSearch: false }) + .state('logs', { url: '/logs', templateUrl: 'plates/logs.html', controller: 'LogsCtrl', reloadOnSearch: false }) + .state('settings', { url: '/settings', templateUrl: 'plates/settings.html',controller: 'SettingsCtrl', reloadOnSearch: false }); + $urlRouterProvider.otherwise('/'); }); app.run(function ($transform) { - window.$transform = $transform; + window.$transform = $transform; }); -/* -app.config(function ($httpProvider) { - // We need to setup some parameters for http requests - // These three lines are all you need for CORS support - $httpProvider.defaults.useXDomain = true; - // $httpProvider.defaults.withCredentials = true; - delete $httpProvider.defaults.headers.common['X-Requested-With']; -}); -*/ -// For this app we have a MainController for whatever and the nindividual controllers for each page + +// For this app we have a MainController for whatever and individual controllers for each page app.controller('MainCtrl', function ($rootScope, $scope) { - // any logic global logic + // any logic global logic }); \ No newline at end of file diff --git a/web/plates/ahrs.html b/web/plates/ahrs.html deleted file mode 100755 index 9f0ca40b..00000000 --- a/web/plates/ahrs.html +++ /dev/null @@ -1,13 +0,0 @@ -
-
-

AHRS Status

-
-
-
- -
-
- this space reserved to display AHRS status -
-
-
\ No newline at end of file diff --git a/web/plates/gps.html b/web/plates/gps.html index f2707c49..08f37572 100755 --- a/web/plates/gps.html +++ b/web/plates/gps.html @@ -4,10 +4,10 @@
- +
- this space reserved to display GPS status + this space reserved to display GPS/AHRS status
\ No newline at end of file diff --git a/web/plates/help.html b/web/plates/help.html deleted file mode 100755 index f7e4dfc9..00000000 --- a/web/plates/help.html +++ /dev/null @@ -1,21 +0,0 @@ -
-
-
- -
-
-

Help

-
-
-
- -
-
- this space reserved to display context sensitive help -
-
-
- -
-
-
\ No newline at end of file diff --git a/web/plates/js/ahrs.js b/web/plates/js/ahrs.js deleted file mode 100755 index 7b5201f9..00000000 --- a/web/plates/js/ahrs.js +++ /dev/null @@ -1,15 +0,0 @@ -angular.module('appControllers').controller('AHRSCtrl', AHRSCtrl); // get the main module contollers set -AHRSCtrl.$inject = ['$rootScope', '$scope', '$state', '$http']; // Inject my dependencies - -// create our controller function with all necessary logic -function AHRSCtrl($rootScope, $scope, $state, $http) { - - /* - $state.get('weather').onEnter = function () { - }; - $state.get('weather').onExit = function () { - }; - */ - - // Weather Controller tasks go here -}; \ No newline at end of file diff --git a/web/plates/js/settings.js b/web/plates/js/settings.js index 5ce9bb8d..a9feefb5 100755 --- a/web/plates/js/settings.js +++ b/web/plates/js/settings.js @@ -49,7 +49,7 @@ function SettingsCtrl($rootScope, $scope, $state, $http) { $scope.AHRS_Enabled = settings.AHRS_Enabled; $scope.DEBUG = settings.DEBUG; $scope.ReplayLog = settings.ReplayLog; - $scope.PPM = parseInt(settings.PPM); + $scope.PPM = settings.PPM; // $scope.$apply(); }, function (response) { $scope.rawSettings = "error setting settings"; @@ -82,9 +82,9 @@ function SettingsCtrl($rootScope, $scope, $state, $http) { $scope.updateppm = function() { if (($scope.PPM !== undefined) && ($scope.PPM !== null) && $scope.PPM !== settings["PPM"]) { - settings["PPM"] = $scope.PPM; + settings["PPM"] = parseInt($scope.PPM); newsettings = { - "PPM": $scope.PPM + "PPM": parseInt($scope.PPM) }; console.log(angular.toJson(newsettings)); setSettings(angular.toJson(newsettings)); diff --git a/web/plates/js/status.js b/web/plates/js/status.js index a10b832c..287afc3f 100755 --- a/web/plates/js/status.js +++ b/web/plates/js/status.js @@ -11,8 +11,7 @@ function StatusCtrl($rootScope, $scope, $state, $http) { return; // we are getting called once after clicking away from the status page if (($scope.socket === undefined) || ($scope.socket === null)) { - // socket = new WebSocket('ws://' + window.location.hostname + '/control'); - socket = new WebSocket('ws://' + URL_HOST_BASE + '/control'); + socket = new WebSocket('ws://' + URL_HOST_BASE + '/status'); $scope.socket = socket; // store socket in scope for enter/exit usage } diff --git a/web/plates/js/traffic.js b/web/plates/js/traffic.js index 5f061d00..8263aa3a 100755 --- a/web/plates/js/traffic.js +++ b/web/plates/js/traffic.js @@ -1,15 +1,135 @@ angular.module('appControllers').controller('TrafficCtrl', TrafficCtrl); // get the main module contollers set -TrafficCtrl.$inject = ['$rootScope', '$scope', '$state', '$http']; // Inject my dependencies +TrafficCtrl.$inject = ['$rootScope', '$scope', '$state', '$http', '$interval']; // Inject my dependencies // create our controller function with all necessary logic -function TrafficCtrl($rootScope, $scope, $state, $http) { +function TrafficCtrl($rootScope, $scope, $state, $http, $interval) { - /* - $state.get('weather').onEnter = function () { - }; - $state.get('weather').onExit = function () { - }; - */ + $scope.$parent.helppage = 'plates/traffic-help.html'; + $scope.traffic = []; - // Weather Controller tasks go here + function utcTimeString(epoc) { + var time = ""; + var val; + var d = new Date(epoc); + val = d.getUTCHours(); + time += (val < 10 ? "0" + val : "" + val); + val = d.getUTCMinutes(); + time += ":" + (val < 10 ? "0" + val : "" + val); + val = d.getUTCSeconds(); + time += ":" + (val < 10 ? "0" + val : "" + val); + time += "Z"; + return time; + } + + function dmsString(val) { + return [0 | val, + 'd ', + 0 | (val < 0 ? val = -val : val) % 1 * 60, + "' ", + 0 | val * 60 % 1 * 60, + '"'].join(''); + } + + function setAircraft(obj, new_traffic) { + new_traffic.icao_int = obj.Icao_addr; + new_traffic.icao = obj.Icao_addr.toString(16).toUpperCase(); + new_traffic.tail = obj.Tail; + new_traffic.lat = dmsString(obj.Lat); + new_traffic.lon = dmsString(obj.Lng); + var n = Math.round(obj.Alt / 100) * 100; + new_traffic.alt = n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); + new_traffic.heading = Math.round(obj.Track / 10) * 10; + new_traffic.speed = Math.round(obj.Speed / 10) * 10; + new_traffic.vspeed = Math.round(obj.Vvel / 100) * 100 + new_traffic.age = Date.parse(obj.Last_seen); + new_traffic.time = utcTimeString(new_traffic.age); + // return new_aircraft; + } + + function connect($scope) { + if (($scope === undefined) || ($scope === null)) + return; // we are getting called once after clicking away from the status page + + if (($scope.socket === undefined) || ($scope.socket === null)) { + socket = new WebSocket('ws://' + URL_HOST_BASE + '/traffic'); + $scope.socket = socket; // store socket in scope for enter/exit usage + } + + $scope.ConnectState = "Not Receiving"; + + socket.onopen = function (msg) { + $scope.ConnectStyle = "label-success"; + $scope.ConnectState = "Receiving"; + }; + + socket.onclose = function (msg) { + $scope.ConnectStyle = "label-danger"; + $scope.ConnectState = "Not Receiving"; + setTimeout(connect, 1000); + }; + + socket.onerror = function (msg) { + $scope.ConnectStyle = "label-danger"; + $scope.ConnectState = "Problem"; + }; + + socket.onmessage = function (msg) { + console.log('Received traffic update.') + + var aircraft = JSON.parse(msg.data); + if (aircraft.Position_valid) { + $scope.rawTraffic = msg.data; + // we need to use an array so AngularJS can perform sorting; it also means we need to loop to find an aircraft in the traffic set + var found = false; + for (var i = 0, len = $scope.traffic.length; i < len; i++) { + if ($scope.traffic[i].icao_int === aircraft.Icao_addr) { + setAircraft(aircraft, $scope.traffic[i]); + found = true; + break; + } + } + if (!found) { + var new_traffic = {}; + setAircraft(aircraft, new_traffic); + $scope.traffic.unshift(new_traffic); // add to start of array + } + $scope.$apply(); + } + }; + } + + // perform cleanup every 60 seconds + var clearStaleTraffic = $interval(function () { + // remove stail aircraft = anything more than 180 seconds without and update + var dirty = false; + var cutoff = Date.now() - (180 * 1000); + + for (var i = len = $scope.traffic.length; i > 0; i--) { + if ($scope.traffic[i - 1].age < cutoff) { + $scope.traffic.splice(i - 1, 1); + dirty = true; + } + } + if (dirty) { + $scope.$apply(); + } + }, (1000 * 60), 0, false); + + + $state.get('traffic').onEnter = function () { + // everything gets handled correctly by the controller + }; + + $state.get('traffic').onExit = function () { + // disconnect from the socket + if (($scope.socket !== undefined) && ($scope.socket !== null)) { + $scope.socket.close(); + $scope.socket = null; + } + // stop stale traffic cleanup + $interval.cancel(clearStaleTraffic); + }; + + // Traffic Controller tasks + connect($scope); // connect - opens a socket and listens for messages }; \ No newline at end of file diff --git a/web/plates/settings-help.html b/web/plates/settings-help.html index a4bdbb60..d31dc23b 100755 --- a/web/plates/settings-help.html +++ b/web/plates/settings-help.html @@ -6,7 +6,7 @@ Status page.

The Diagnostics section helps with debugging and communicating with the Stratux project contributors via GitHub and the reddit subgroup. -