2016-01-19 13:39:46 +00:00
/ *
Copyright ( c ) 2015 - 2016 Christopher Young
2017-04-19 19:57:25 +00:00
Distributable under the terms of The "BSD New" License
2016-01-19 13:39:46 +00:00
that can be found in the LICENSE file , herein included
as part of this header .
gen_gdl90 . go : Input demodulated UAT and 1090 ES information , output GDL90 . Heartbeat ,
ownship , status messages , stats collection .
* /
2015-08-04 05:44:55 +00:00
package main
import (
2015-08-08 23:05:19 +00:00
"bufio"
2015-12-14 01:04:40 +00:00
"compress/gzip"
2015-08-08 23:05:19 +00:00
"encoding/hex"
2015-08-15 17:37:41 +00:00
"encoding/json"
2015-12-17 21:11:17 +00:00
"flag"
2015-09-05 07:51:19 +00:00
"fmt"
2015-09-04 17:49:42 +00:00
"io"
2015-09-01 20:47:48 +00:00
"io/ioutil"
2015-08-17 17:59:03 +00:00
"log"
2016-02-25 05:53:35 +00:00
"math"
2015-08-08 23:05:19 +00:00
"os"
2016-01-19 15:40:40 +00:00
"os/signal"
2017-03-05 23:56:11 +00:00
"path/filepath"
2015-08-15 17:37:41 +00:00
"runtime"
2017-04-05 00:52:22 +00:00
"runtime/pprof"
2015-09-01 20:47:48 +00:00
"strconv"
2015-08-08 23:05:19 +00:00
"strings"
2016-05-24 19:39:11 +00:00
"sync"
2016-01-19 15:40:40 +00:00
"syscall"
2015-08-08 23:05:19 +00:00
"time"
2015-09-26 07:04:39 +00:00
"../uatparse"
2016-04-12 14:38:04 +00:00
humanize "github.com/dustin/go-humanize"
"github.com/ricochet2200/go-disk-usage/du"
2015-08-04 05:44:55 +00:00
)
2016-12-02 15:57:40 +00:00
// https://www.faa.gov/nextgen/programs/adsb/Archival/
// https://www.faa.gov/nextgen/programs/adsb/Archival/media/GDL90_Public_ICD_RevA.PDF
2015-08-04 05:44:55 +00:00
2017-03-28 15:57:32 +00:00
var logDirf string // Directory for all logging
var debugLogf string // Set according to OS config.
2016-05-27 20:27:22 +00:00
var dataLogFilef string // Set according to OS config.
2015-08-04 05:44:55 +00:00
const (
2016-05-27 20:27:22 +00:00
configLocation = "/etc/stratux.conf"
managementAddr = ":80"
2017-03-05 23:56:11 +00:00
logDir = "/var/log/"
debugLogFile = "stratux.log"
dataLogFile = "stratux.sqlite"
2016-05-27 20:27:22 +00:00
//FlightBox: log to /root.
2017-03-05 23:56:11 +00:00
logDir_FB = "/root/"
2015-09-01 20:16:31 +00:00
maxDatagramSize = 8192
2015-09-16 20:08:21 +00:00
maxUserMsgQueueSize = 25000 // About 10MB per port per connected client.
2015-12-17 21:11:17 +00:00
2015-08-08 23:05:19 +00:00
UPLINK_BLOCK_DATA_BITS = 576
UPLINK_BLOCK_BITS = ( UPLINK_BLOCK_DATA_BITS + 160 )
UPLINK_BLOCK_DATA_BYTES = ( UPLINK_BLOCK_DATA_BITS / 8 )
UPLINK_BLOCK_BYTES = ( UPLINK_BLOCK_BITS / 8 )
UPLINK_FRAME_BLOCKS = 6
UPLINK_FRAME_DATA_BITS = ( UPLINK_FRAME_BLOCKS * UPLINK_BLOCK_DATA_BITS )
UPLINK_FRAME_BITS = ( UPLINK_FRAME_BLOCKS * UPLINK_BLOCK_BITS )
UPLINK_FRAME_DATA_BYTES = ( UPLINK_FRAME_DATA_BITS / 8 )
UPLINK_FRAME_BYTES = ( UPLINK_FRAME_BITS / 8 )
2015-08-04 05:44:55 +00:00
// assume 6 byte frames: 2 header bytes, 4 byte payload
// (TIS-B heartbeat with one address, or empty FIS-B APDU)
2015-08-08 23:05:19 +00:00
UPLINK_MAX_INFO_FRAMES = ( 424 / 6 )
2015-08-09 16:10:44 +00:00
2015-08-09 22:51:23 +00:00
MSGTYPE_UPLINK = 0x07
MSGTYPE_BASIC_REPORT = 0x1E
MSGTYPE_LONG_REPORT = 0x1F
2015-08-11 22:27:26 +00:00
2016-03-26 21:12:26 +00:00
MSGCLASS_UAT = 0
MSGCLASS_ES = 1
2015-08-15 00:11:04 +00:00
2015-08-15 17:37:41 +00:00
LON_LAT_RESOLUTION = float32 ( 180.0 / 8388608.0 )
TRACK_RESOLUTION = float32 ( 360.0 / 256.0 )
2016-06-07 13:03:25 +00:00
2016-12-25 19:00:01 +00:00
/ *
GPS_TYPE_NMEA = 0x01
GPS_TYPE_UBX = 0x02
GPS_TYPE_SIRF = 0x03
GPS_TYPE_MEDIATEK = 0x04
GPS_TYPE_FLARM = 0x05
GPS_TYPE_GARMIN = 0x06
* /
GPS_TYPE_UBX8 = 0x08
GPS_TYPE_UBX7 = 0x07
GPS_TYPE_UBX6 = 0x06
GPS_TYPE_PROLIFIC = 0x02
GPS_TYPE_UART = 0x01
GPS_PROTOCOL_NMEA = 0x10
GPS_PROTOCOL_UBX = 0x30
2016-06-07 13:03:25 +00:00
// other GPS types to be defined as needed
2015-08-04 05:44:55 +00:00
)
2016-10-23 16:04:18 +00:00
var logFileHandle * os . File
2016-04-07 08:21:15 +00:00
var usage * du . DiskUsage
2016-02-21 16:40:14 +00:00
var maxSignalStrength int
2015-09-19 03:00:05 +00:00
var stratuxBuild string
2015-09-19 16:37:52 +00:00
var stratuxVersion string
2015-09-19 03:00:05 +00:00
2017-06-11 17:50:34 +00:00
var product_name_map = map [ int ] string {
0 : "METAR" ,
1 : "TAF" ,
2 : "SIGMET" ,
3 : "Conv SIGMET" ,
4 : "AIRMET" ,
5 : "PIREP" ,
6 : "Severe Wx" ,
7 : "Winds Aloft" ,
8 : "NOTAM" , //"NOTAM (Including TFRs) and Service Status";
9 : "D-ATIS" , //"Aerodrome and Airspace – D-ATIS";
10 : "Terminal Wx" , //"Aerodrome and Airspace - TWIP";
11 : "AIRMET" , //"Aerodrome and Airspace - AIRMET";
12 : "SIGMET" , //"Aerodrome and Airspace - SIGMET/Convective SIGMET";
13 : "SUA" , //"Aerodrome and Airspace - SUA Status";
20 : "METAR" , //"METAR and SPECI";
21 : "TAF" , //"TAF and Amended TAF";
22 : "SIGMET" , //"SIGMET";
23 : "Conv SIGMET" , //"Convective SIGMET";
24 : "AIRMET" , //"AIRMET";
25 : "PIREP" , //"PIREP";
26 : "Severe Wx" , //"AWW";
27 : "Winds Aloft" , //"Winds and Temperatures Aloft";
51 : "NEXRAD" , //"National NEXRAD, Type 0 - 4 level";
52 : "NEXRAD" , //"National NEXRAD, Type 1 - 8 level (quasi 6-level VIP)";
53 : "NEXRAD" , //"National NEXRAD, Type 2 - 8 level";
54 : "NEXRAD" , //"National NEXRAD, Type 3 - 16 level";
55 : "NEXRAD" , //"Regional NEXRAD, Type 0 - low dynamic range";
56 : "NEXRAD" , //"Regional NEXRAD, Type 1 - 8 level (quasi 6-level VIP)";
57 : "NEXRAD" , //"Regional NEXRAD, Type 2 - 8 level";
58 : "NEXRAD" , //"Regional NEXRAD, Type 3 - 16 level";
59 : "NEXRAD" , //"Individual NEXRAD, Type 0 - low dynamic range";
60 : "NEXRAD" , //"Individual NEXRAD, Type 1 - 8 level (quasi 6-level VIP)";
61 : "NEXRAD" , //"Individual NEXRAD, Type 2 - 8 level";
62 : "NEXRAD" , //"Individual NEXRAD, Type 3 - 16 level";
63 : "NEXRAD Regional" , //"Global Block Representation - Regional NEXRAD, Type 4 – 8 level";
64 : "NEXRAD CONUS" , //"Global Block Representation - CONUS NEXRAD, Type 4 - 8 level";
81 : "Tops" , //"Radar echo tops graphic, scheme 1: 16-level";
82 : "Tops" , //"Radar echo tops graphic, scheme 2: 8-level";
83 : "Tops" , //"Storm tops and velocity";
101 : "Lightning" , //"Lightning strike type 1 (pixel level)";
102 : "Lightning" , //"Lightning strike type 2 (grid element level)";
151 : "Lightning" , //"Point phenomena, vector format";
201 : "Surface" , //"Surface conditions/winter precipitation graphic";
202 : "Surface" , //"Surface weather systems";
254 : "G-AIRMET" , //"AIRMET, SIGMET: Bitmap encoding";
351 : "Time" , //"System Time";
352 : "Status" , //"Operational Status";
353 : "Status" , //"Ground Station Status";
401 : "Imagery" , //"Generic Raster Scan Data Product APDU Payload Format Type 1";
402 : "Text" ,
403 : "Vector Imagery" , //"Generic Vector Data Product APDU Payload Format Type 1";
404 : "Symbols" ,
405 : "Text" ,
411 : "Text" , //"Generic Textual Data Product APDU Payload Format Type 1";
412 : "Symbols" , //"Generic Symbolic Product APDU Payload Format Type 1";
413 : "Text" , //"Generic Textual Data Product APDU Payload Format Type 2";
}
2015-09-05 17:46:55 +00:00
// CRC16 table generated to use to work with GDL90 messages.
2015-08-04 05:44:55 +00:00
var Crc16Table [ 256 ] uint16
2015-09-05 17:46:55 +00:00
// Current AHRS, pressure altitude, etc.
2015-08-20 20:47:05 +00:00
var mySituation SituationData
2015-08-15 00:11:04 +00:00
2015-12-17 21:11:17 +00:00
type WriteCloser interface {
io . Writer
io . Closer
}
2015-12-17 21:22:05 +00:00
type ReadCloser interface {
io . Reader
io . Closer
}
2015-08-11 22:27:26 +00:00
type msg struct {
2016-02-25 05:53:35 +00:00
MessageClass uint
TimeReceived time . Time
2016-06-10 19:12:32 +00:00
Data string
2016-02-25 05:53:35 +00:00
Products [ ] uint32
Signal_amplitude int
Signal_strength float64
ADSBTowerID string // Index in the 'ADSBTowers' map, if this is a parseable uplink message.
2016-05-31 13:00:02 +00:00
uatMsg * uatparse . UATMsg
2015-08-11 22:27:26 +00:00
}
2015-09-05 17:46:55 +00:00
// Raw inputs.
2015-08-11 22:27:26 +00:00
var MsgLog [ ] msg
2015-09-05 17:46:55 +00:00
// Time gen_gdl90 was started.
2015-08-25 19:55:41 +00:00
var timeStarted time . Time
2015-08-11 22:27:26 +00:00
2015-09-24 21:18:21 +00:00
type ADSBTower struct {
Lat float64
Lng float64
2016-02-25 05:53:35 +00:00
Signal_strength_now float64 // Current RSSI (dB)
Signal_strength_max float64 // all-time peak RSSI (dB) observed for this tower
Energy_last_minute uint64 // Summation of power observed for this tower across all messages last minute
Signal_strength_last_minute float64 // Average RSSI (dB) observed for this tower last minute
2015-09-24 21:18:21 +00:00
Messages_last_minute uint64
}
var ADSBTowers map [ string ] ADSBTower // Running list of all towers seen. (lat,lng) -> ADSBTower
2016-05-24 19:39:11 +00:00
var ADSBTowerMutex * sync . Mutex
2015-09-24 21:18:21 +00:00
2015-08-04 05:44:55 +00:00
// Construct the CRC table. Adapted from FAA ref above.
func crcInit ( ) {
var i uint16
var bitctr uint16
var crc uint16
for i = 0 ; i < 256 ; i ++ {
crc = ( i << 8 )
for bitctr = 0 ; bitctr < 8 ; bitctr ++ {
z := uint16 ( 0 )
if ( crc & 0x8000 ) != 0 {
z = 0x1021
}
crc = ( crc << 1 ) ^ z
}
Crc16Table [ i ] = crc
}
}
// Compute CRC. Adapted from FAA ref above.
func crcCompute ( data [ ] byte ) uint16 {
ret := uint16 ( 0 )
for i := 0 ; i < len ( data ) ; i ++ {
2015-08-08 23:05:19 +00:00
ret = Crc16Table [ ret >> 8 ] ^ ( ret << 8 ) ^ uint16 ( data [ i ] )
2015-08-04 05:44:55 +00:00
}
return ret
}
func prepareMessage ( data [ ] byte ) [ ] byte {
// Compute CRC before modifying the message.
crc := crcCompute ( data )
2015-08-12 04:00:50 +00:00
// Add the two CRC16 bytes before replacing control characters.
data = append ( data , byte ( crc & 0xFF ) )
data = append ( data , byte ( crc >> 8 ) )
tmp := [ ] byte { 0x7E } // Flag start.
2015-08-04 05:44:55 +00:00
// Copy the message over, escaping 0x7E (Flag Byte) and 0x7D (Control-Escape).
for i := 0 ; i < len ( data ) ; i ++ {
mv := data [ i ]
if ( mv == 0x7E ) || ( mv == 0x7D ) {
mv = mv ^ 0x20
tmp = append ( tmp , 0x7D )
}
tmp = append ( tmp , mv )
}
tmp = append ( tmp , 0x7E ) // Flag end.
return tmp
}
2015-08-15 00:11:04 +00:00
func makeLatLng ( v float32 ) [ ] byte {
ret := make ( [ ] byte , 3 )
v = v / LON_LAT_RESOLUTION
wk := int32 ( v )
ret [ 0 ] = byte ( ( wk & 0xFF0000 ) >> 16 )
ret [ 1 ] = byte ( ( wk & 0x00FF00 ) >> 8 )
ret [ 2 ] = byte ( ( wk & 0x0000FF ) )
return ret
}
2016-12-02 15:57:40 +00:00
func isDetectedOwnshipValid ( ) bool {
return stratuxClock . Since ( OwnshipTrafficInfo . Last_seen ) < 10 * time . Second
}
2015-08-15 00:11:04 +00:00
func makeOwnshipReport ( ) bool {
2016-12-02 15:57:40 +00:00
gpsValid := isGPSValid ( )
selfOwnshipValid := isDetectedOwnshipValid ( )
if ! gpsValid && ! selfOwnshipValid {
2015-08-15 00:11:04 +00:00
return false
}
2016-12-02 15:57:40 +00:00
curOwnship := OwnshipTrafficInfo
2015-08-15 00:11:04 +00:00
msg := make ( [ ] byte , 28 )
// See p.16.
msg [ 0 ] = 0x0A // Message type "Ownship".
msg [ 1 ] = 0x01 // Alert status, address type.
2015-10-06 22:31:20 +00:00
code , _ := hex . DecodeString ( globalSettings . OwnshipModeS )
2015-10-10 20:02:09 +00:00
if len ( code ) != 3 {
// Reserved dummy code.
msg [ 2 ] = 0xF0
msg [ 3 ] = 0x00
msg [ 4 ] = 0x00
} else {
msg [ 2 ] = code [ 0 ] // Mode S address.
msg [ 3 ] = code [ 1 ] // Mode S address.
msg [ 4 ] = code [ 2 ] // Mode S address.
}
2015-08-15 00:11:04 +00:00
2016-12-02 15:57:40 +00:00
var tmp [ ] byte
if selfOwnshipValid {
tmp = makeLatLng ( curOwnship . Lat )
msg [ 5 ] = tmp [ 0 ] // Latitude.
msg [ 6 ] = tmp [ 1 ] // Latitude.
msg [ 7 ] = tmp [ 2 ] // Latitude.
tmp = makeLatLng ( curOwnship . Lng )
msg [ 8 ] = tmp [ 0 ] // Longitude.
msg [ 9 ] = tmp [ 1 ] // Longitude.
msg [ 10 ] = tmp [ 2 ] // Longitude.
} else {
2017-03-30 23:20:49 +00:00
tmp = makeLatLng ( mySituation . GPSLatitude )
2016-12-02 15:57:40 +00:00
msg [ 5 ] = tmp [ 0 ] // Latitude.
msg [ 6 ] = tmp [ 1 ] // Latitude.
msg [ 7 ] = tmp [ 2 ] // Latitude.
2017-03-30 23:20:49 +00:00
tmp = makeLatLng ( mySituation . GPSLongitude )
2016-12-02 15:57:40 +00:00
msg [ 8 ] = tmp [ 0 ] // Longitude.
msg [ 9 ] = tmp [ 1 ] // Longitude.
msg [ 10 ] = tmp [ 2 ] // Longitude.
}
2015-08-15 00:11:04 +00:00
2015-08-20 23:47:05 +00:00
// This is **PRESSURE ALTITUDE**
2017-06-09 14:49:25 +00:00
alt := uint16 ( 0xFFF ) // 0xFFF "invalid altitude."
validAltf := false
2015-08-20 23:47:05 +00:00
2015-11-25 03:43:07 +00:00
var altf float64
2016-12-02 15:57:40 +00:00
if selfOwnshipValid {
altf = float64 ( curOwnship . Alt )
2017-06-09 14:49:25 +00:00
validAltf = true
2016-12-02 15:57:40 +00:00
} else if isTempPressValid ( ) {
2017-03-30 23:20:49 +00:00
altf = float64 ( mySituation . BaroPressureAltitude )
2017-06-09 14:54:41 +00:00
validAltf = true
2015-08-20 23:47:05 +00:00
}
2016-12-02 15:57:40 +00:00
2017-06-09 14:49:25 +00:00
if validAltf {
altf = ( altf + 1000 ) / 25
alt = uint16 ( altf ) & 0xFFF // Should fit in 12 bits.
}
2015-08-15 00:11:04 +00:00
msg [ 11 ] = byte ( ( alt & 0xFF0 ) >> 4 ) // Altitude.
msg [ 12 ] = byte ( ( alt & 0x00F ) << 4 )
2016-12-02 15:57:40 +00:00
if selfOwnshipValid || isGPSGroundTrackValid ( ) {
2016-04-07 06:31:50 +00:00
msg [ 12 ] = msg [ 12 ] | 0x09 // "Airborne" + "True Track"
2015-08-15 00:11:04 +00:00
}
2015-09-26 07:04:39 +00:00
2017-03-30 23:20:49 +00:00
msg [ 13 ] = byte ( 0x80 | ( mySituation . GPSNACp & 0x0F ) ) //Set NIC = 8 and use NACp from gps.go.
2015-08-15 00:11:04 +00:00
gdSpeed := uint16 ( 0 ) // 1kt resolution.
2016-12-02 15:57:40 +00:00
if selfOwnshipValid && curOwnship . Speed_valid {
gdSpeed = curOwnship . Speed
} else if isGPSGroundTrackValid ( ) {
2017-03-30 23:20:49 +00:00
gdSpeed = uint16 ( mySituation . GPSGroundSpeed + 0.5 )
2015-08-15 00:11:04 +00:00
}
2015-09-26 07:04:39 +00:00
// gdSpeed should fit in 12 bits.
2015-08-15 00:11:04 +00:00
msg [ 14 ] = byte ( ( gdSpeed & 0xFF0 ) >> 4 )
msg [ 15 ] = byte ( ( gdSpeed & 0x00F ) << 4 )
2015-11-25 03:43:07 +00:00
verticalVelocity := int16 ( 0x800 ) // ft/min. 64 ft/min resolution.
2015-08-15 00:11:04 +00:00
//TODO: 0x800 = no information available.
2015-09-26 07:04:39 +00:00
// verticalVelocity should fit in 12 bits.
2015-08-15 17:37:41 +00:00
msg [ 15 ] = msg [ 15 ] | byte ( ( verticalVelocity & 0x0F00 ) >> 8 )
2015-08-15 00:11:04 +00:00
msg [ 16 ] = byte ( verticalVelocity & 0xFF )
2016-04-07 06:31:50 +00:00
// Track is degrees true, set from GPS true course.
2016-04-07 05:24:12 +00:00
groundTrack := float32 ( 0 )
2016-12-02 15:57:40 +00:00
if selfOwnshipValid {
groundTrack = float32 ( curOwnship . Track )
} else if isGPSGroundTrackValid ( ) {
2017-03-30 23:20:49 +00:00
groundTrack = mySituation . GPSTrueCourse
2015-08-15 00:11:04 +00:00
}
2016-04-07 06:31:50 +00:00
tempTrack := groundTrack + TRACK_RESOLUTION / 2 // offset by half the 8-bit resolution to minimize binning error
for tempTrack > 360 {
tempTrack -= 360
}
for tempTrack < 0 {
tempTrack += 360
}
trk := uint8 ( tempTrack / TRACK_RESOLUTION ) // Resolution is ~1.4 degrees.
//log.Printf("For groundTrack = %.2f°, tempTrack= %.2f, trk = %d (%f°)\n",groundTrack,tempTrack,trk,float32(trk)*TRACK_RESOLUTION)
2015-08-15 00:11:04 +00:00
msg [ 17 ] = byte ( trk )
msg [ 18 ] = 0x01 // "Light (ICAO) < 15,500 lbs"
2016-12-02 15:57:40 +00:00
if selfOwnshipValid {
// Limit tail number to 7 characters.
tail := curOwnship . Tail
if len ( tail ) > 7 {
tail = tail [ : 7 ]
}
// Copy tail number into message.
for i := 0 ; i < len ( tail ) ; i ++ {
msg [ 19 + i ] = tail [ i ]
}
}
2018-03-09 14:49:03 +00:00
myReg := "Stratux" // Default callsign.
// Use icao2reg() results for ownship tail number, if available.
if len ( code ) == 3 {
uintIcao := uint32 ( code [ 0 ] ) << 16 | uint32 ( code [ 1 ] ) << 8 | uint32 ( code [ 2 ] )
regFromIcao , regFromIcaoValid := icao2reg ( uintIcao )
if regFromIcaoValid {
// Valid "decoded" registration. Use this for the reg.
myReg = regFromIcao
}
}
// Truncate registration to 8 characters.
if len ( myReg ) > 8 {
myReg = myReg [ : 8 ]
}
// Write the callsign.
for i := 0 ; i < len ( myReg ) ; i ++ {
msg [ 19 + i ] = myReg [ i ]
}
2015-09-30 17:36:31 +00:00
2015-09-01 20:16:31 +00:00
sendGDL90 ( prepareMessage ( msg ) , false )
2015-08-15 00:11:04 +00:00
return true
}
2015-08-15 03:58:53 +00:00
func makeOwnshipGeometricAltitudeReport ( ) bool {
2015-08-15 03:10:16 +00:00
if ! isGPSValid ( ) {
return false
}
2015-08-15 00:11:04 +00:00
msg := make ( [ ] byte , 5 )
// See p.28.
2017-03-30 23:20:49 +00:00
msg [ 0 ] = 0x0B // Message type "Ownship Geo Alt".
alt := int16 ( mySituation . GPSAltitudeMSL / 5 ) // GPS Altitude, encoded to 16-bit int using 5-foot resolution
msg [ 1 ] = byte ( alt >> 8 ) // Altitude.
msg [ 2 ] = byte ( alt & 0x00FF ) // Altitude.
2015-08-15 00:11:04 +00:00
//TODO: "Figure of Merit". 0x7FFF "Not available".
msg [ 3 ] = 0x00
msg [ 4 ] = 0x0A
2015-09-01 20:16:31 +00:00
sendGDL90 ( prepareMessage ( msg ) , false )
2015-08-15 03:10:16 +00:00
return true
2015-08-15 00:11:04 +00:00
}
2015-12-18 17:28:37 +00:00
/ *
"SX" Stratux GDL90 message .
2016-01-01 04:44:47 +00:00
http : //hiltonsoftware.com/stratux/ for latest version (currently using V104)
2015-12-18 17:28:37 +00:00
* /
2016-01-05 16:09:06 +00:00
func makeStratuxStatus ( ) [ ] byte {
2015-12-18 17:28:37 +00:00
msg := make ( [ ] byte , 29 )
msg [ 0 ] = 'S'
msg [ 1 ] = 'X'
msg [ 2 ] = 1
msg [ 3 ] = 1 // "message version".
2015-12-26 22:17:53 +00:00
// Version code. Messy parsing to fit into four bytes.
2015-12-26 22:34:35 +00:00
thisVers := stratuxVersion [ 1 : ] // Skip first character, should be 'v'.
m_str := thisVers [ 0 : strings . Index ( thisVers , "." ) ] // Major version.
mib_str := thisVers [ strings . Index ( thisVers , "." ) + 1 : ] // Minor and build version.
2015-12-26 22:17:53 +00:00
tp := 0 // Build "type".
mi_str := ""
b_str := ""
if strings . Index ( mib_str , "rc" ) != - 1 {
tp = 3
mi_str = mib_str [ 0 : strings . Index ( mib_str , "rc" ) ]
b_str = mib_str [ strings . Index ( mib_str , "rc" ) + 2 : ]
} else if strings . Index ( mib_str , "r" ) != - 1 {
tp = 2
mi_str = mib_str [ 0 : strings . Index ( mib_str , "r" ) ]
b_str = mib_str [ strings . Index ( mib_str , "r" ) + 1 : ]
} else if strings . Index ( mib_str , "b" ) != - 1 {
tp = 1
mi_str = mib_str [ 0 : strings . Index ( mib_str , "b" ) ]
b_str = mib_str [ strings . Index ( mib_str , "b" ) + 1 : ]
}
// Convert to strings.
m , _ := strconv . Atoi ( m_str )
mi , _ := strconv . Atoi ( mi_str )
b , _ := strconv . Atoi ( b_str )
msg [ 4 ] = byte ( m )
msg [ 5 ] = byte ( mi )
msg [ 6 ] = byte ( tp )
msg [ 7 ] = byte ( b )
2015-12-18 17:28:37 +00:00
//TODO: Hardware revision.
2015-12-18 19:35:42 +00:00
msg [ 8 ] = 0xFF
msg [ 9 ] = 0xFF
msg [ 10 ] = 0xFF
msg [ 11 ] = 0xFF
2015-12-18 17:28:37 +00:00
// Valid and enabled flags.
// Valid/Enabled: GPS portion.
if isGPSValid ( ) {
2017-03-30 23:20:49 +00:00
switch mySituation . GPSFixQuality {
2015-12-18 17:28:37 +00:00
case 1 : // 1 = 3D GPS.
2015-12-20 04:35:44 +00:00
msg [ 13 ] = 1
2015-12-18 17:28:37 +00:00
case 2 : // 2 = DGPS (SBAS /WAAS).
2015-12-20 04:35:44 +00:00
msg [ 13 ] = 2
2015-12-18 17:28:37 +00:00
default : // Zero.
}
}
// Valid/Enabled: AHRS portion.
if isAHRSValid ( ) {
2015-12-20 04:35:44 +00:00
msg [ 13 ] = msg [ 13 ] | ( 1 << 2 )
2015-12-18 17:28:37 +00:00
}
// Valid/Enabled: Pressure altitude portion.
if isTempPressValid ( ) {
2015-12-20 04:35:44 +00:00
msg [ 13 ] = msg [ 13 ] | ( 1 << 3 )
2015-12-18 17:28:37 +00:00
}
// Valid/Enabled: CPU temperature portion.
2017-05-11 04:26:17 +00:00
if isCPUTempValid ( globalStatus . CPUTemp ) {
2015-12-20 04:35:44 +00:00
msg [ 13 ] = msg [ 13 ] | ( 1 << 4 )
2015-12-18 17:28:37 +00:00
}
// Valid/Enabled: UAT portion.
if globalSettings . UAT_Enabled {
2015-12-20 04:35:44 +00:00
msg [ 13 ] = msg [ 13 ] | ( 1 << 5 )
2015-12-18 17:28:37 +00:00
}
// Valid/Enabled: ES portion.
if globalSettings . ES_Enabled {
2015-12-20 04:35:44 +00:00
msg [ 13 ] = msg [ 13 ] | ( 1 << 6 )
2015-12-18 17:28:37 +00:00
}
2016-02-26 11:18:22 +00:00
// Ping provides ES and UAT
if globalSettings . Ping_Enabled {
msg [ 13 ] = msg [ 13 ] | ( 1 << 5 ) | ( 1 << 6 )
}
2015-12-26 21:52:32 +00:00
// Valid/Enabled: GPS Enabled portion.
if globalSettings . GPS_Enabled {
msg [ 13 ] = msg [ 13 ] | ( 1 << 7 )
}
// Valid/Enabled: AHRS Enabled portion.
2017-03-05 22:47:38 +00:00
if globalSettings . IMU_Sensor_Enabled {
2017-01-07 13:05:37 +00:00
msg [ 12 ] = 1 << 0
}
2015-12-26 21:52:32 +00:00
2015-12-18 17:28:37 +00:00
// Valid/Enabled: last bit unused.
// Connected hardware: number of radios.
2015-12-20 04:35:44 +00:00
msg [ 15 ] = msg [ 15 ] | ( byte ( globalStatus . Devices ) & 0x3 )
2017-01-07 13:05:37 +00:00
// Connected hardware: IMU (spec really says just RY835AI, could revise for other IMUs
if globalStatus . IMUConnected {
msg [ 15 ] = msg [ 15 ] | ( 1 << 2 )
}
2015-12-18 17:28:37 +00:00
// Number of GPS satellites locked.
msg [ 16 ] = byte ( globalStatus . GPS_satellites_locked )
2015-12-27 08:47:37 +00:00
// Number of satellites tracked
msg [ 17 ] = byte ( globalStatus . GPS_satellites_tracked )
2015-12-18 17:28:37 +00:00
// Number of UAT traffic targets.
2016-10-05 03:50:44 +00:00
msg [ 18 ] = byte ( ( globalStatus . UAT_traffic_targets_tracking & 0xFF00 ) >> 8 )
msg [ 19 ] = byte ( globalStatus . UAT_traffic_targets_tracking & 0xFF )
2015-12-18 17:28:37 +00:00
// Number of 1090ES traffic targets.
2016-10-05 03:50:44 +00:00
msg [ 20 ] = byte ( ( globalStatus . ES_traffic_targets_tracking & 0xFF00 ) >> 8 )
msg [ 21 ] = byte ( globalStatus . ES_traffic_targets_tracking & 0xFF )
2015-12-18 17:28:37 +00:00
// Number of UAT messages per minute.
msg [ 22 ] = byte ( ( globalStatus . UAT_messages_last_minute & 0xFF00 ) >> 8 )
msg [ 23 ] = byte ( globalStatus . UAT_messages_last_minute & 0xFF )
// Number of 1090ES messages per minute.
msg [ 24 ] = byte ( ( globalStatus . ES_messages_last_minute & 0xFF00 ) >> 8 )
msg [ 25 ] = byte ( globalStatus . ES_messages_last_minute & 0xFF )
2015-12-18 19:35:42 +00:00
// CPU temperature.
2015-12-22 20:40:56 +00:00
v := uint16 ( float32 ( 10.0 ) * globalStatus . CPUTemp )
2015-12-18 17:28:37 +00:00
msg [ 26 ] = byte ( ( v & 0xFF00 ) >> 8 )
2016-01-01 04:44:47 +00:00
msg [ 27 ] = byte ( v & 0xFF )
2015-12-18 17:28:37 +00:00
2016-05-24 19:39:11 +00:00
// Number of ADS-B towers. Map structure is protected by ADSBTowerMutex.
ADSBTowerMutex . Lock ( )
2015-12-18 17:28:37 +00:00
num_towers := uint8 ( len ( ADSBTowers ) )
msg [ 28 ] = byte ( num_towers )
// List of ADS-B towers (lat, lng).
for _ , tower := range ADSBTowers {
tmp := makeLatLng ( float32 ( tower . Lat ) )
msg = append ( msg , tmp [ 0 ] ) // Latitude.
msg = append ( msg , tmp [ 1 ] ) // Latitude.
msg = append ( msg , tmp [ 2 ] ) // Latitude.
tmp = makeLatLng ( float32 ( tower . Lng ) )
msg = append ( msg , tmp [ 0 ] ) // Longitude.
msg = append ( msg , tmp [ 1 ] ) // Longitude.
msg = append ( msg , tmp [ 2 ] ) // Longitude.
}
2016-05-24 19:39:11 +00:00
ADSBTowerMutex . Unlock ( )
2015-12-18 17:28:37 +00:00
return prepareMessage ( msg )
}
2015-10-01 01:06:38 +00:00
/ *
"Stratux" GDL90 message .
Message ID 0xCC .
Byte1 : p p p p p p GPS AHRS
First 6 bytes are protocol version codes .
Protocol 1 : GPS on / off | AHRS on / off .
* /
func makeStratuxHeartbeat ( ) [ ] byte {
msg := make ( [ ] byte , 2 )
msg [ 0 ] = 0xCC // Message type "Stratux".
msg [ 1 ] = 0
if isGPSValid ( ) {
msg [ 1 ] = 0x02
}
if isAHRSValid ( ) {
msg [ 1 ] = msg [ 1 ] | 0x01
}
protocolVers := int8 ( 1 )
2015-10-04 22:33:44 +00:00
msg [ 1 ] = msg [ 1 ] | byte ( protocolVers << 2 )
2015-10-01 01:06:38 +00:00
return prepareMessage ( msg )
}
2018-04-06 19:08:45 +00:00
/ *
ForeFlight "ID Message" .
Sends device information to ForeFlight .
* /
func makeFFIDMessage ( ) [ ] byte {
msg := make ( [ ] byte , 39 )
msg [ 0 ] = 0x65 // Message type "ForeFlight".
msg [ 1 ] = 0 // ID message identifier.
msg [ 2 ] = 1 // Message version.
// Serial number. Set to "invalid" for now.
for i := 3 ; i <= 10 ; i ++ {
msg [ i ] = 0xFF
}
devShortName := "Stratux" // Temporary. Will be populated in the future with other names.
if len ( devShortName ) > 8 {
devShortName = devShortName [ : 8 ] // 8 chars.
}
copy ( msg [ 11 : ] , devShortName )
devLongName := fmt . Sprintf ( "%s-%s" , stratuxVersion , stratuxBuild )
if len ( devLongName ) > 16 {
devLongName = devLongName [ : 16 ] // 16 chars.
}
copy ( msg [ 19 : ] , devLongName )
msg [ 38 ] = 0x01 // Capabilities mask. MSL altitude for Ownship Geometric report.
return prepareMessage ( msg )
}
2015-08-04 05:44:55 +00:00
func makeHeartbeat ( ) [ ] byte {
msg := make ( [ ] byte , 7 )
// See p.10.
msg [ 0 ] = 0x00 // Message type "Heartbeat".
2015-08-20 20:47:05 +00:00
msg [ 1 ] = 0x01 // "UAT Initialized".
if isGPSValid ( ) {
msg [ 1 ] = msg [ 1 ] | 0x80
}
msg [ 1 ] = msg [ 1 ] | 0x10 //FIXME: Addr talkback.
2017-04-19 20:49:35 +00:00
// "Maintenance Req'd". Add flag if there are any current critical system errors.
if len ( globalStatus . Errors ) > 0 {
msg [ 1 ] = msg [ 1 ] | 0x40
}
2015-08-04 05:44:55 +00:00
nowUTC := time . Now ( ) . UTC ( )
// Seconds since 0000Z.
midnightUTC := time . Date ( nowUTC . Year ( ) , nowUTC . Month ( ) , nowUTC . Day ( ) , 0 , 0 , 0 , 0 , time . UTC )
secondsSinceMidnightUTC := uint32 ( nowUTC . Sub ( midnightUTC ) . Seconds ( ) )
2015-08-15 00:11:04 +00:00
msg [ 2 ] = byte ( ( ( secondsSinceMidnightUTC >> 16 ) << 7 ) | 0x1 ) // UTC OK.
2015-08-04 05:44:55 +00:00
msg [ 3 ] = byte ( ( secondsSinceMidnightUTC & 0xFF ) )
msg [ 4 ] = byte ( ( secondsSinceMidnightUTC & 0xFFFF ) >> 8 )
// TODO. Number of uplink messages. See p.12.
// msg[5]
// msg[6]
return prepareMessage ( msg )
}
2015-08-09 16:10:44 +00:00
func relayMessage ( msgtype uint16 , msg [ ] byte ) {
2015-08-08 23:05:19 +00:00
ret := make ( [ ] byte , len ( msg ) + 4 )
2015-08-04 05:44:55 +00:00
// See p.15.
2015-08-09 16:10:44 +00:00
ret [ 0 ] = byte ( msgtype ) // Uplink message ID.
2015-08-09 22:51:23 +00:00
ret [ 1 ] = 0x00 //TODO: Time.
ret [ 2 ] = 0x00 //TODO: Time.
ret [ 3 ] = 0x00 //TODO: Time.
2015-08-04 05:44:55 +00:00
for i := 0 ; i < len ( msg ) ; i ++ {
ret [ i + 4 ] = msg [ i ]
}
2015-09-01 20:16:31 +00:00
sendGDL90 ( prepareMessage ( ret ) , true )
2015-08-04 05:44:55 +00:00
}
2017-04-19 21:04:12 +00:00
func blinkStatusLED ( ) {
timer := time . NewTicker ( 100 * time . Millisecond )
ledON := false
for {
<- timer . C
if ledON {
ioutil . WriteFile ( "/sys/class/leds/led0/brightness" , [ ] byte ( "0\n" ) , 0644 )
} else {
ioutil . WriteFile ( "/sys/class/leds/led0/brightness" , [ ] byte ( "1\n" ) , 0644 )
}
ledON = ! ledON
}
}
2017-10-17 21:30:03 +00:00
2015-08-04 05:44:55 +00:00
func heartBeatSender ( ) {
2015-08-20 20:47:05 +00:00
timer := time . NewTicker ( 1 * time . Second )
2016-02-25 05:53:35 +00:00
timerMessageStats := time . NewTicker ( 2 * time . Second )
2017-04-19 21:04:12 +00:00
ledBlinking := false
2015-08-04 05:44:55 +00:00
for {
2015-09-12 21:28:58 +00:00
select {
case <- timer . C :
2017-04-19 21:04:12 +00:00
// Green LED - always on during normal operation.
// Blinking when there is a critical system error (and Stratux is still running).
if len ( globalStatus . Errors ) == 0 { // Any system errors?
2017-10-17 21:30:03 +00:00
if ! globalStatus . NightMode { // LED is off by default (/boot/config.txt.)
// Turn on green ACT LED on the Pi.
ioutil . WriteFile ( "/sys/class/leds/led0/brightness" , [ ] byte ( "1\n" ) , 0644 )
}
2017-04-19 21:04:12 +00:00
} else if ! ledBlinking {
// This assumes that system errors do not disappear until restart.
go blinkStatusLED ( )
ledBlinking = true
}
2016-09-26 18:34:41 +00:00
2015-09-12 21:28:58 +00:00
sendGDL90 ( makeHeartbeat ( ) , false )
2015-10-01 01:06:38 +00:00
sendGDL90 ( makeStratuxHeartbeat ( ) , false )
2016-01-05 16:09:06 +00:00
sendGDL90 ( makeStratuxStatus ( ) , false )
2018-04-06 19:08:45 +00:00
sendGDL90 ( makeFFIDMessage ( ) , false )
2015-09-12 21:28:58 +00:00
makeOwnshipReport ( )
makeOwnshipGeometricAltitudeReport ( )
2016-02-15 01:27:02 +00:00
2016-02-17 04:19:33 +00:00
// --- debug code: traffic demo ---
// Uncomment and compile to display large number of artificial traffic targets
/ *
2016-02-27 05:36:33 +00:00
numTargets := uint32 ( 36 )
2016-02-17 04:19:33 +00:00
hexCode := uint32 ( 0xFF0000 )
2016-02-15 01:27:02 +00:00
2016-02-17 04:19:33 +00:00
for i := uint32 ( 0 ) ; i < numTargets ; i ++ {
tail := fmt . Sprintf ( "DEMO%d" , i )
alt := float32 ( ( i * 117 % 2000 ) * 25 + 2000 )
hdg := int32 ( ( i * 149 ) % 360 )
2016-02-27 05:36:33 +00:00
spd := float64 ( 50 + ( ( i * 23 ) % 13 ) * 37 )
2016-02-15 01:27:02 +00:00
2016-02-17 04:19:33 +00:00
updateDemoTraffic ( i | hexCode , tail , alt , spd , hdg )
}
2016-02-15 01:27:02 +00:00
* /
2016-02-26 17:08:01 +00:00
2016-02-17 04:19:33 +00:00
// ---end traffic demo code ---
2015-09-12 21:28:58 +00:00
sendTrafficUpdates ( )
updateStatus ( )
case <- timerMessageStats . C :
// Save a bit of CPU by not pruning the message log every 1 second.
updateMessageStats ( )
}
2015-08-04 05:44:55 +00:00
}
}
2015-09-12 21:28:58 +00:00
func updateMessageStats ( ) {
2015-08-11 22:27:26 +00:00
t := make ( [ ] msg , 0 )
m := len ( MsgLog )
UAT_messages_last_minute := uint ( 0 )
ES_messages_last_minute := uint ( 0 )
2015-09-24 21:18:21 +00:00
2016-05-24 19:39:11 +00:00
ADSBTowerMutex . Lock ( )
2016-05-31 13:00:02 +00:00
defer ADSBTowerMutex . Unlock ( )
2015-09-24 21:18:21 +00:00
// Clear out ADSBTowers stats.
for t , tinf := range ADSBTowers {
tinf . Messages_last_minute = 0
2016-02-25 05:53:35 +00:00
tinf . Energy_last_minute = 0
2015-09-24 21:18:21 +00:00
ADSBTowers [ t ] = tinf
}
2015-08-11 22:27:26 +00:00
for i := 0 ; i < m ; i ++ {
2016-01-07 16:42:37 +00:00
if stratuxClock . Since ( MsgLog [ i ] . TimeReceived ) < 1 * time . Minute {
2015-08-11 22:27:26 +00:00
t = append ( t , MsgLog [ i ] )
if MsgLog [ i ] . MessageClass == MSGCLASS_UAT {
UAT_messages_last_minute ++
2015-09-24 21:18:21 +00:00
if len ( MsgLog [ i ] . ADSBTowerID ) > 0 { // Update tower stats.
tid := MsgLog [ i ] . ADSBTowerID
2016-05-31 13:00:02 +00:00
if _ , ok := ADSBTowers [ tid ] ; ! ok { // First time we've seen the tower? Start tracking.
var newTower ADSBTower
newTower . Lat = MsgLog [ i ] . uatMsg . Lat
newTower . Lng = MsgLog [ i ] . uatMsg . Lon
newTower . Signal_strength_max = - 999 // dBmax = 0, so this needs to initialize below scale ( << -48 dB)
ADSBTowers [ tid ] = newTower
}
2015-09-24 21:18:21 +00:00
twr := ADSBTowers [ tid ]
2016-05-31 13:00:02 +00:00
twr . Signal_strength_now = MsgLog [ i ] . Signal_strength
2016-02-25 05:53:35 +00:00
twr . Energy_last_minute += uint64 ( ( MsgLog [ i ] . Signal_amplitude ) * ( MsgLog [ i ] . Signal_amplitude ) )
2015-09-24 21:18:21 +00:00
twr . Messages_last_minute ++
if MsgLog [ i ] . Signal_strength > twr . Signal_strength_max { // Update alltime max signal strength.
twr . Signal_strength_max = MsgLog [ i ] . Signal_strength
}
ADSBTowers [ tid ] = twr
}
2015-08-11 22:27:26 +00:00
} else if MsgLog [ i ] . MessageClass == MSGCLASS_ES {
ES_messages_last_minute ++
}
}
}
MsgLog = t
globalStatus . UAT_messages_last_minute = UAT_messages_last_minute
globalStatus . ES_messages_last_minute = ES_messages_last_minute
2015-08-15 03:58:53 +00:00
2015-08-25 19:31:13 +00:00
// Update "max messages/min" counters.
if globalStatus . UAT_messages_max < UAT_messages_last_minute {
globalStatus . UAT_messages_max = UAT_messages_last_minute
}
if globalStatus . ES_messages_max < ES_messages_last_minute {
globalStatus . ES_messages_max = ES_messages_last_minute
}
2015-09-24 21:18:21 +00:00
// Update average signal strength over last minute for all ADSB towers.
for t , tinf := range ADSBTowers {
2016-07-09 18:01:36 +00:00
if tinf . Messages_last_minute == 0 || tinf . Energy_last_minute == 0 {
2016-07-09 17:59:41 +00:00
tinf . Signal_strength_last_minute = - 999
2015-09-24 21:18:21 +00:00
} else {
2016-02-25 05:53:35 +00:00
tinf . Signal_strength_last_minute = 10 * ( math . Log10 ( float64 ( ( tinf . Energy_last_minute / tinf . Messages_last_minute ) ) ) - 6 )
2015-09-24 21:18:21 +00:00
}
ADSBTowers [ t ] = tinf
}
2015-09-12 21:28:58 +00:00
}
func updateStatus ( ) {
2017-03-30 23:20:49 +00:00
if mySituation . GPSFixQuality == 2 {
2016-07-15 07:36:14 +00:00
globalStatus . GPS_solution = "GPS + SBAS (WAAS)"
2017-03-30 23:20:49 +00:00
} else if mySituation . GPSFixQuality == 1 {
2016-02-05 01:08:12 +00:00
globalStatus . GPS_solution = "3D GPS"
2017-03-30 23:20:49 +00:00
} else if mySituation . GPSFixQuality == 6 {
2016-02-05 01:08:12 +00:00
globalStatus . GPS_solution = "Dead Reckoning"
2017-03-30 23:20:49 +00:00
} else if mySituation . GPSFixQuality == 0 {
2016-02-05 01:08:12 +00:00
globalStatus . GPS_solution = "No Fix"
2016-02-17 03:13:21 +00:00
} else {
globalStatus . GPS_solution = "Unknown"
2016-02-05 01:08:12 +00:00
}
2016-02-10 07:06:52 +00:00
if ! ( globalStatus . GPS_connected ) || ! ( isGPSConnected ( ) ) { // isGPSConnected looks for valid NMEA messages. GPS_connected is set by gpsSerialReader and will immediately fail on disconnected USB devices, or in a few seconds after "blocked" comms on ttyAMA0.
2016-05-08 05:14:20 +00:00
2017-03-30 23:20:49 +00:00
mySituation . muSatellite . Lock ( )
2016-05-08 05:14:20 +00:00
Satellites = make ( map [ string ] SatelliteInfo )
2017-03-30 23:20:49 +00:00
mySituation . muSatellite . Unlock ( )
2016-05-08 05:14:20 +00:00
2017-03-30 23:20:49 +00:00
mySituation . GPSSatellites = 0
mySituation . GPSSatellitesSeen = 0
mySituation . GPSSatellitesTracked = 0
mySituation . GPSFixQuality = 0
2016-02-05 01:08:12 +00:00
globalStatus . GPS_solution = "Disconnected"
globalStatus . GPS_connected = false
}
2017-03-30 23:20:49 +00:00
globalStatus . GPS_satellites_locked = mySituation . GPSSatellites
globalStatus . GPS_satellites_seen = mySituation . GPSSatellitesSeen
globalStatus . GPS_satellites_tracked = mySituation . GPSSatellitesTracked
globalStatus . GPS_position_accuracy = mySituation . GPSHorizontalAccuracy
2015-08-25 19:55:41 +00:00
2015-09-02 20:27:39 +00:00
// Update Uptime value
2016-01-27 05:27:09 +00:00
globalStatus . Uptime = int64 ( stratuxClock . Milliseconds )
2016-02-17 03:13:21 +00:00
globalStatus . UptimeClock = stratuxClock . Time
2016-04-12 14:38:04 +00:00
2016-04-07 08:21:15 +00:00
usage = du . NewDiskUsage ( "/" )
globalStatus . DiskBytesFree = usage . Free ( )
2016-10-23 16:04:18 +00:00
fileInfo , err := logFileHandle . Stat ( )
if err == nil {
globalStatus . Logfile_Size = fileInfo . Size ( )
}
2017-07-03 23:43:51 +00:00
var ahrsLogSize int64
ahrsLogFiles , _ := ioutil . ReadDir ( "/var/log" )
for _ , f := range ahrsLogFiles {
if v , _ := filepath . Match ( "sensors_*.csv" , f . Name ( ) ) ; v {
ahrsLogSize += f . Size ( )
}
}
globalStatus . AHRS_LogFiles_Size = ahrsLogSize
2015-08-11 22:27:26 +00:00
}
2015-09-30 16:50:23 +00:00
type WeatherMessage struct {
Type string
Location string
Time string
Data string
LocaltimeReceived time . Time
}
2015-10-08 00:24:56 +00:00
// Send update to connected websockets.
2016-10-24 04:38:13 +00:00
func registerADSBTextMessageReceived ( msg string , uatMsg * uatparse . UATMsg ) {
2015-09-30 16:50:23 +00:00
x := strings . Split ( msg , " " )
if len ( x ) < 5 {
return
}
var wm WeatherMessage
2016-10-01 15:48:27 +00:00
if ( x [ 0 ] == "METAR" ) || ( x [ 0 ] == "SPECI" ) {
globalStatus . UAT_METAR_total ++
}
if ( x [ 0 ] == "TAF" ) || ( x [ 0 ] == "TAF.AMD" ) {
globalStatus . UAT_TAF_total ++
}
if x [ 0 ] == "WINDS" {
globalStatus . UAT_TAF_total ++
}
if x [ 0 ] == "PIREP" {
globalStatus . UAT_PIREP_total ++
}
2015-09-30 16:50:23 +00:00
wm . Type = x [ 0 ]
wm . Location = x [ 1 ]
wm . Time = x [ 2 ]
wm . Data = strings . Join ( x [ 3 : ] , " " )
2016-01-07 16:37:57 +00:00
wm . LocaltimeReceived = stratuxClock . Time
2015-09-30 16:50:23 +00:00
// Send to weatherUpdate channel for any connected clients.
2016-10-24 19:40:30 +00:00
weatherUpdate . SendJSON ( wm )
2015-09-30 16:50:23 +00:00
}
2016-09-24 23:23:39 +00:00
func UpdateUATStats ( ProductID uint32 ) {
2016-10-01 15:48:27 +00:00
switch ProductID {
case 0 , 20 :
globalStatus . UAT_METAR_total ++
case 1 , 21 :
globalStatus . UAT_TAF_total ++
case 51 , 52 , 53 , 54 , 55 , 56 , 57 , 58 , 59 , 60 , 61 , 62 , 63 , 64 , 81 , 82 , 83 :
globalStatus . UAT_NEXRAD_total ++
// AIRMET and SIGMETS
case 2 , 3 , 4 , 6 , 11 , 12 , 22 , 23 , 24 , 26 , 254 :
globalStatus . UAT_SIGMET_total ++
case 5 , 25 :
globalStatus . UAT_PIREP_total ++
case 8 :
globalStatus . UAT_NOTAM_total ++
case 413 :
// Do nothing in the case since text is recorded elsewhere
return
default :
globalStatus . UAT_OTHER_total ++
}
2015-09-30 16:50:23 +00:00
}
2015-08-09 16:10:44 +00:00
func parseInput ( buf string ) ( [ ] byte , uint16 ) {
2016-03-26 21:19:59 +00:00
//FIXME: We're ignoring all invalid format UAT messages (not sending to datalog).
2015-08-09 22:51:23 +00:00
x := strings . Split ( buf , ";" ) // Discard everything after the first ';'.
2015-08-04 05:44:55 +00:00
s := x [ 0 ]
if len ( s ) == 0 {
2015-08-09 16:10:44 +00:00
return nil , 0
2015-08-04 05:44:55 +00:00
}
2015-08-09 16:10:44 +00:00
msgtype := uint16 ( 0 )
2015-09-05 17:02:06 +00:00
isUplink := false
if s [ 0 ] == '+' {
isUplink = true
}
2015-08-04 05:44:55 +00:00
2015-09-24 21:18:21 +00:00
var thisSignalStrength int
2016-02-24 02:01:35 +00:00
if /*isUplink &&*/ len ( x ) >= 3 {
2015-09-22 13:52:49 +00:00
// See if we can parse out the signal strength.
ss := x [ 2 ]
2016-02-23 07:00:16 +00:00
//log.Printf("x[2] = %s\n",ss)
2015-09-22 13:52:49 +00:00
if strings . HasPrefix ( ss , "ss=" ) {
ssStr := ss [ 3 : ]
if ssInt , err := strconv . Atoi ( ssStr ) ; err == nil {
2015-09-24 21:18:21 +00:00
thisSignalStrength = ssInt
2016-02-24 02:20:38 +00:00
if isUplink && ( ssInt > maxSignalStrength ) { // only look at uplinks; ignore ADS-B and TIS-B/ADS-R messages
2015-09-22 13:52:49 +00:00
maxSignalStrength = ssInt
}
2016-02-23 07:00:16 +00:00
} else {
//log.Printf("Error was %s\n",err.Error())
2015-09-22 13:52:49 +00:00
}
}
}
2016-02-23 05:45:39 +00:00
if s [ 0 ] == '-' {
2016-02-25 05:53:35 +00:00
parseDownlinkReport ( s , int ( thisSignalStrength ) )
2016-02-23 05:45:39 +00:00
}
2016-02-26 17:08:01 +00:00
2015-08-04 05:44:55 +00:00
s = s [ 1 : ]
2015-08-09 22:51:23 +00:00
msglen := len ( s ) / 2
2015-08-04 05:44:55 +00:00
2015-08-08 23:05:19 +00:00
if len ( s ) % 2 != 0 { // Bad format.
2015-08-09 16:10:44 +00:00
return nil , 0
}
2015-09-24 21:18:21 +00:00
if isUplink && msglen == UPLINK_FRAME_DATA_BYTES {
2015-08-09 16:10:44 +00:00
msgtype = MSGTYPE_UPLINK
2016-04-20 08:59:55 +00:00
} else if msglen == 48 {
// With Reed Solomon appended
msgtype = MSGTYPE_LONG_REPORT
2015-08-09 16:10:44 +00:00
} else if msglen == 34 {
msgtype = MSGTYPE_LONG_REPORT
} else if msglen == 18 {
msgtype = MSGTYPE_BASIC_REPORT
} else {
msgtype = 0
2015-08-04 05:44:55 +00:00
}
2015-08-09 16:10:44 +00:00
if msgtype == 0 {
2015-08-17 17:59:03 +00:00
log . Printf ( "UNKNOWN MESSAGE TYPE: %s - msglen=%d\n" , s , msglen )
2015-08-04 05:44:55 +00:00
}
// Now, begin converting the string into a byte array.
frame := make ( [ ] byte , UPLINK_FRAME_DATA_BYTES )
hex . Decode ( frame , [ ] byte ( s ) )
2015-08-11 22:27:26 +00:00
var thisMsg msg
thisMsg . MessageClass = MSGCLASS_UAT
2016-01-07 16:37:57 +00:00
thisMsg . TimeReceived = stratuxClock . Time
2016-06-10 19:12:32 +00:00
thisMsg . Data = buf
2016-02-25 05:53:35 +00:00
thisMsg . Signal_amplitude = thisSignalStrength
2016-07-09 17:59:41 +00:00
if thisSignalStrength > 0 {
thisMsg . Signal_strength = 20 * math . Log10 ( ( float64 ( thisSignalStrength ) ) / 1000 )
} else {
thisMsg . Signal_strength = - 999
}
2015-09-24 21:28:08 +00:00
thisMsg . Products = make ( [ ] uint32 , 0 )
2015-09-24 21:18:21 +00:00
if msgtype == MSGTYPE_UPLINK {
// Parse the UAT message.
uatMsg , err := uatparse . New ( buf )
if err == nil {
uatMsg . DecodeUplink ( )
towerid := fmt . Sprintf ( "(%f,%f)" , uatMsg . Lat , uatMsg . Lon )
thisMsg . ADSBTowerID = towerid
2015-09-24 21:28:08 +00:00
// Get all of the "product ids".
for _ , f := range uatMsg . Frames {
thisMsg . Products = append ( thisMsg . Products , f . Product_id )
2016-10-01 15:48:27 +00:00
UpdateUATStats ( f . Product_id )
2016-10-24 21:28:57 +00:00
weatherRawUpdate . SendJSON ( f )
2015-09-24 21:28:08 +00:00
}
2015-09-30 16:50:23 +00:00
// Get all of the text reports.
textReports , _ := uatMsg . GetTextReports ( )
for _ , r := range textReports {
2016-10-24 04:38:13 +00:00
registerADSBTextMessageReceived ( r , uatMsg )
2015-09-30 16:50:23 +00:00
}
2016-05-31 13:00:02 +00:00
thisMsg . uatMsg = uatMsg
2015-09-24 21:18:21 +00:00
}
}
2015-09-24 21:28:08 +00:00
2015-08-11 22:27:26 +00:00
MsgLog = append ( MsgLog , thisMsg )
2016-03-26 20:49:57 +00:00
logMsg ( thisMsg )
2015-08-11 22:27:26 +00:00
2015-08-09 16:10:44 +00:00
return frame , msgtype
2015-08-04 05:44:55 +00:00
}
2015-09-05 07:51:19 +00:00
func getProductNameFromId ( product_id int ) string {
name , present := product_name_map [ product_id ]
if present {
return name
}
if product_id == 600 || ( product_id >= 2000 && product_id <= 2005 ) {
return "Custom/Test"
}
return fmt . Sprintf ( "Unknown (%d)" , product_id )
}
2015-08-11 22:27:26 +00:00
type settings struct {
2017-07-17 21:18:12 +00:00
UAT_Enabled bool
ES_Enabled bool
Ping_Enabled bool
GPS_Enabled bool
BMP_Sensor_Enabled bool
IMU_Sensor_Enabled bool
NetworkOutputs [ ] networkConnection
SerialOutputs map [ string ] serialConnection
DisplayTrafficSource bool
DEBUG bool
ReplayLog bool
AHRSLog bool
IMUMapping [ 2 ] int // Map from aircraft axis to sensor axis: accelerometer
SensorQuaternion [ 4 ] float64 // Quaternion mapping from sensor frame to aircraft frame
C , D [ 3 ] float64 // IMU Accel, Gyro zero bias
PPM int
OwnshipModeS string
WatchList string
DeveloperMode bool
GLimits string
StaticIps [ ] string
2015-08-11 22:27:26 +00:00
}
type status struct {
2016-05-31 12:29:06 +00:00
Version string
Build string
HardwareBuild string
Devices uint32
Connected_Users uint
DiskBytesFree uint64
UAT_messages_last_minute uint
2016-02-15 01:27:02 +00:00
UAT_messages_max uint
ES_messages_last_minute uint
ES_messages_max uint
2016-10-05 03:50:44 +00:00
UAT_traffic_targets_tracking uint16
ES_traffic_targets_tracking uint16
2016-04-20 08:59:55 +00:00
Ping_connected bool
2016-02-15 01:27:02 +00:00
GPS_satellites_locked uint16
GPS_satellites_seen uint16
GPS_satellites_tracked uint16
2016-07-15 07:36:14 +00:00
GPS_position_accuracy float32
2016-02-15 01:27:02 +00:00
GPS_connected bool
GPS_solution string
2016-06-07 13:03:25 +00:00
GPS_detected_type uint
2016-02-15 01:27:02 +00:00
Uptime int64
2016-02-17 03:13:21 +00:00
UptimeClock time . Time
2016-02-15 01:27:02 +00:00
CPUTemp float32
NetworkDataMessagesSent uint64
NetworkDataMessagesSentNonqueueable uint64
NetworkDataBytesSent uint64
NetworkDataBytesSentNonqueueable uint64
NetworkDataMessagesSentLastSec uint64
NetworkDataMessagesSentNonqueueableLastSec uint64
NetworkDataBytesSentLastSec uint64
NetworkDataBytesSentNonqueueableLastSec uint64
2016-09-24 17:17:46 +00:00
UAT_METAR_total uint32
UAT_TAF_total uint32
UAT_NEXRAD_total uint32
UAT_SIGMET_total uint32
UAT_PIREP_total uint32
UAT_NOTAM_total uint32
UAT_OTHER_total uint32
2016-10-23 16:04:18 +00:00
Errors [ ] string
2017-02-10 06:28:22 +00:00
Logfile_Size int64
2017-07-03 23:43:51 +00:00
AHRS_LogFiles_Size int64
2017-05-13 12:40:19 +00:00
BMPConnected bool
IMUConnected bool
2017-10-17 21:30:03 +00:00
NightMode bool // For turning off LEDs.
2015-08-11 22:27:26 +00:00
}
var globalSettings settings
var globalStatus status
func defaultSettings ( ) {
2015-12-21 02:56:53 +00:00
globalSettings . UAT_Enabled = true
globalSettings . ES_Enabled = true
2016-03-10 17:34:06 +00:00
globalSettings . GPS_Enabled = true
2017-03-05 22:47:38 +00:00
globalSettings . IMU_Sensor_Enabled = true
globalSettings . BMP_Sensor_Enabled = true
2015-09-01 20:16:31 +00:00
//FIXME: Need to change format below.
2016-01-19 14:50:02 +00:00
globalSettings . NetworkOutputs = [ ] networkConnection {
{ Conn : nil , Ip : "" , Port : 4000 , Capability : NETWORK_GDL90_STANDARD | NETWORK_AHRS_GDL90 } ,
2016-01-25 03:19:34 +00:00
// {Conn: nil, Ip: "", Port: 49002, Capability: NETWORK_AHRS_FFSIM},
2016-01-19 14:50:02 +00:00
}
2015-09-04 03:35:26 +00:00
globalSettings . DEBUG = false
2016-05-03 04:40:51 +00:00
globalSettings . DisplayTrafficSource = false
2015-09-05 17:46:55 +00:00
globalSettings . ReplayLog = false //TODO: 'true' for debug builds.
2017-03-05 23:34:25 +00:00
globalSettings . AHRSLog = false
2017-07-06 23:45:01 +00:00
globalSettings . IMUMapping = [ 2 ] int { - 1 , 0 }
2015-10-10 00:07:28 +00:00
globalSettings . OwnshipModeS = "F00000"
2016-10-03 03:17:09 +00:00
globalSettings . DeveloperMode = false
2016-12-25 07:03:56 +00:00
globalSettings . StaticIps = make ( [ ] string , 0 )
2015-08-11 22:27:26 +00:00
}
func readSettings ( ) {
fd , err := os . Open ( configLocation )
if err != nil {
2015-08-17 17:59:03 +00:00
log . Printf ( "can't read settings %s: %s\n" , configLocation , err . Error ( ) )
2015-08-11 22:27:26 +00:00
defaultSettings ( )
return
}
2015-09-26 07:20:16 +00:00
defer fd . Close ( )
2015-08-11 22:27:26 +00:00
buf := make ( [ ] byte , 1024 )
count , err := fd . Read ( buf )
if err != nil {
2015-08-17 17:59:03 +00:00
log . Printf ( "can't read settings %s: %s\n" , configLocation , err . Error ( ) )
2015-08-11 22:27:26 +00:00
defaultSettings ( )
return
}
var newSettings settings
err = json . Unmarshal ( buf [ 0 : count ] , & newSettings )
if err != nil {
2015-08-17 17:59:03 +00:00
log . Printf ( "can't read settings %s: %s\n" , configLocation , err . Error ( ) )
2015-08-11 22:27:26 +00:00
defaultSettings ( )
2015-08-15 17:37:41 +00:00
return
2015-08-11 22:27:26 +00:00
}
globalSettings = newSettings
2015-08-17 17:59:03 +00:00
log . Printf ( "read in settings.\n" )
2015-08-11 22:27:26 +00:00
}
2016-03-10 18:06:14 +00:00
func addSystemError ( err error ) {
2016-03-10 19:41:30 +00:00
globalStatus . Errors = append ( globalStatus . Errors , err . Error ( ) )
2016-03-10 18:06:14 +00:00
}
2018-01-09 14:57:00 +00:00
var systemErrsMutex * sync . Mutex
var systemErrs map [ string ] string
func addSingleSystemErrorf ( ident string , format string , a ... interface { } ) {
systemErrsMutex . Lock ( )
if _ , ok := systemErrs [ ident ] ; ! ok {
// Error hasn't been thrown yet.
systemErrs [ ident ] = fmt . Sprintf ( format , a ... )
globalStatus . Errors = append ( globalStatus . Errors , systemErrs [ ident ] )
log . Printf ( "Added critical system error: %s\n" , systemErrs [ ident ] )
}
// Do nothing on this call if the error has already been thrown.
systemErrsMutex . Unlock ( )
}
2015-08-11 22:27:26 +00:00
func saveSettings ( ) {
2015-08-26 20:26:23 +00:00
fd , err := os . OpenFile ( configLocation , os . O_CREATE | os . O_WRONLY | os . O_TRUNC , os . FileMode ( 0644 ) )
2015-08-11 22:27:26 +00:00
if err != nil {
2018-01-09 15:09:18 +00:00
addSingleSystemErrorf ( "save-settings" , "can't save settings %s: %s" , configLocation , err . Error ( ) )
2015-08-11 22:27:26 +00:00
return
}
2015-09-26 07:20:16 +00:00
defer fd . Close ( )
2015-08-31 17:02:20 +00:00
jsonSettings , _ := json . Marshal ( & globalSettings )
fd . Write ( jsonSettings )
2015-08-17 17:59:03 +00:00
log . Printf ( "wrote settings.\n" )
2015-08-11 22:27:26 +00:00
}
2015-12-17 21:11:17 +00:00
func openReplay ( fn string , compressed bool ) ( WriteCloser , error ) {
2015-12-02 17:18:52 +00:00
fp , err := os . OpenFile ( fn , os . O_CREATE | os . O_WRONLY | os . O_APPEND , 0666 )
2015-09-21 18:20:12 +00:00
if err != nil {
log . Printf ( "Failed to open log file '%s': %s\n" , fn , err . Error ( ) )
2015-12-02 17:18:52 +00:00
return nil , err
2015-09-21 18:20:12 +00:00
}
2015-12-17 21:11:17 +00:00
var ret WriteCloser
if compressed {
ret = gzip . NewWriter ( fp ) //FIXME: Close() on the gzip.Writer will not close the underlying file.
} else {
ret = fp
2015-12-14 01:04:40 +00:00
}
2015-12-17 21:11:17 +00:00
2015-12-14 01:04:40 +00:00
timeFmt := "Mon Jan 2 15:04:05 -0700 MST 2006"
s := fmt . Sprintf ( "START,%s,%s\n" , timeStarted . Format ( timeFmt ) , time . Now ( ) . Format ( timeFmt ) ) // Start time marker.
2015-12-17 21:11:17 +00:00
ret . Write ( [ ] byte ( s ) )
return ret , err
2015-12-14 01:04:40 +00:00
}
2016-12-09 14:22:10 +00:00
/ *
fsWriteTest ( ) .
Makes a temporary file in ' dir ' , checks for error . Deletes the file .
* /
func fsWriteTest ( dir string ) error {
fn := dir + "/.write_test"
err := ioutil . WriteFile ( fn , [ ] byte ( "test\n" ) , 0644 )
if err != nil {
return err
}
err = os . Remove ( fn )
return err
}
2015-09-22 13:52:49 +00:00
func printStats ( ) {
statTimer := time . NewTicker ( 30 * time . Second )
for {
<- statTimer . C
var memstats runtime . MemStats
runtime . ReadMemStats ( & memstats )
2016-01-07 16:08:54 +00:00
log . Printf ( "stats [started: %s]\n" , humanize . RelTime ( time . Time { } , stratuxClock . Time , "ago" , "from now" ) )
2016-04-12 14:59:07 +00:00
log . Printf ( " - Disk bytes used = %s (%.1f %%), Disk bytes free = %s (%.1f %%)\n" , humanize . Bytes ( usage . Used ( ) ) , 100 * usage . Usage ( ) , humanize . Bytes ( usage . Free ( ) ) , 100 * ( 1 - usage . Usage ( ) ) )
2015-09-22 13:52:49 +00:00
log . Printf ( " - CPUTemp=%.02f deg C, MemStats.Alloc=%s, MemStats.Sys=%s, totalNetworkMessagesSent=%s\n" , globalStatus . CPUTemp , humanize . Bytes ( uint64 ( memstats . Alloc ) ) , humanize . Bytes ( uint64 ( memstats . Sys ) ) , humanize . Comma ( int64 ( totalNetworkMessagesSent ) ) )
2016-04-07 08:27:07 +00:00
log . Printf ( " - UAT/min %s/%s [maxSS=%.02f%%], ES/min %s/%s, Total traffic targets tracked=%s" , humanize . Comma ( int64 ( globalStatus . UAT_messages_last_minute ) ) , humanize . Comma ( int64 ( globalStatus . UAT_messages_max ) ) , float64 ( maxSignalStrength ) / 10.0 , humanize . Comma ( int64 ( globalStatus . ES_messages_last_minute ) ) , humanize . Comma ( int64 ( globalStatus . ES_messages_max ) ) , humanize . Comma ( int64 ( len ( seenTraffic ) ) ) )
2016-02-15 01:27:02 +00:00
log . Printf ( " - Network data messages sent: %d total, %d nonqueueable. Network data bytes sent: %d total, %d nonqueueable.\n" , globalStatus . NetworkDataMessagesSent , globalStatus . NetworkDataMessagesSentNonqueueable , globalStatus . NetworkDataBytesSent , globalStatus . NetworkDataBytesSentNonqueueable )
2015-10-07 23:03:24 +00:00
if globalSettings . GPS_Enabled {
2017-03-30 23:20:49 +00:00
log . Printf ( " - Last GPS fix: %s, GPS solution type: %d using %d satellites (%d/%d seen/tracked), NACp: %d, est accuracy %.02f m\n" , stratuxClock . HumanizeTime ( mySituation . GPSLastFixLocalTime ) , mySituation . GPSFixQuality , mySituation . GPSSatellites , mySituation . GPSSatellitesSeen , mySituation . GPSSatellitesTracked , mySituation . GPSNACp , mySituation . GPSHorizontalAccuracy )
log . Printf ( " - GPS vertical velocity: %.02f ft/sec; GPS vertical accuracy: %v m\n" , mySituation . GPSVerticalSpeed , mySituation . GPSVerticalAccuracy )
2015-10-07 23:03:24 +00:00
}
2017-10-11 12:39:09 +00:00
sensorsOutput := make ( [ ] string , 0 )
2017-03-05 22:47:38 +00:00
if globalSettings . IMU_Sensor_Enabled {
2017-10-12 17:21:55 +00:00
sensorsOutput = append ( sensorsOutput , fmt . Sprintf ( "Last IMU read: %s" , stratuxClock . HumanizeTime ( mySituation . AHRSLastAttitudeTime ) ) )
2017-03-05 22:47:38 +00:00
}
if globalSettings . BMP_Sensor_Enabled {
2017-10-12 17:21:55 +00:00
sensorsOutput = append ( sensorsOutput , fmt . Sprintf ( "Last BMP read: %s" , stratuxClock . HumanizeTime ( mySituation . BaroLastMeasurementTime ) ) )
2017-10-11 12:39:09 +00:00
}
if len ( sensorsOutput ) > 0 {
2017-10-12 17:21:55 +00:00
log . Printf ( "- " + strings . Join ( sensorsOutput , ", " ) + "\n" )
2017-01-07 13:05:37 +00:00
}
2016-04-12 14:59:07 +00:00
// Check if we're using more than 95% of the free space. If so, throw a warning (only once).
2018-01-09 15:09:18 +00:00
if usage . Usage ( ) > 0.95 {
addSingleSystemErrorf ( "disk-space" , "Disk bytes used = %s (%.1f %%), Disk bytes free = %s (%.1f %%)" , humanize . Bytes ( usage . Used ( ) ) , 100 * usage . Usage ( ) , humanize . Bytes ( usage . Free ( ) ) , 100 * ( 1 - usage . Usage ( ) ) )
2016-04-12 14:59:07 +00:00
}
2016-03-24 14:51:12 +00:00
logStatus ( )
2015-09-22 13:52:49 +00:00
}
}
2015-12-13 20:19:15 +00:00
var uatReplayDone bool
2015-12-17 21:35:42 +00:00
func uatReplay ( f ReadCloser , replaySpeed uint64 ) {
defer f . Close ( )
2015-12-13 20:19:15 +00:00
rdr := bufio . NewReader ( f )
curTick := int64 ( 0 )
for {
buf , err := rdr . ReadString ( '\n' )
if err != nil {
break
}
linesplit := strings . Split ( buf , "," )
if len ( linesplit ) < 2 { // Blank line or invalid.
continue
}
if linesplit [ 0 ] == "START" { // Reset ticker, new start.
curTick = 0
} else { // If it's not "START", then it's a tick count.
i , err := strconv . ParseInt ( linesplit [ 0 ] , 10 , 64 )
if err != nil {
2015-12-17 21:22:05 +00:00
log . Printf ( "invalid tick: '%s'\n" , linesplit [ 0 ] )
2015-12-13 20:19:15 +00:00
continue
}
thisWait := ( i - curTick ) / int64 ( replaySpeed )
if thisWait >= 120000000000 { // More than 2 minutes wait, skip ahead.
2015-12-17 21:22:05 +00:00
log . Printf ( "UAT skipahead - %d seconds.\n" , thisWait / 1000000000 )
2015-12-13 20:19:15 +00:00
} else {
time . Sleep ( time . Duration ( thisWait ) * time . Nanosecond ) // Just in case the units change.
}
p := strings . Trim ( linesplit [ 1 ] , " ;\r\n" )
buf := fmt . Sprintf ( "%s;\n" , p )
o , msgtype := parseInput ( buf )
if o != nil && msgtype != 0 {
relayMessage ( msgtype , o )
}
curTick = i
}
}
uatReplayDone = true
}
2015-12-17 21:35:42 +00:00
func openReplayFile ( fn string ) ReadCloser {
2015-12-17 21:22:05 +00:00
fp , err := os . Open ( fn )
2015-12-13 20:19:15 +00:00
if err != nil {
2015-12-17 21:22:05 +00:00
log . Printf ( "error opening '%s': %s\n" , fn , err . Error ( ) )
2015-12-13 20:19:15 +00:00
os . Exit ( 1 )
return nil
}
2015-12-17 21:22:05 +00:00
var ret ReadCloser
2015-12-17 21:35:42 +00:00
if strings . HasSuffix ( fn , ".gz" ) { // Open as a compressed replay log, depending on the suffix.
2015-12-17 21:22:05 +00:00
ret , err = gzip . NewReader ( fp )
if err != nil {
log . Printf ( "error opening compressed log '%s': %s\n" , fn , err . Error ( ) )
os . Exit ( 1 )
return nil
}
} else {
ret = fp
}
return ret
2015-12-13 20:19:15 +00:00
}
2016-01-07 16:08:54 +00:00
var stratuxClock * monotonic
2016-01-19 15:40:40 +00:00
var sigs = make ( chan os . Signal , 1 ) // Signal catch channel (shutdown).
// Graceful shutdown.
2016-02-15 22:48:33 +00:00
func gracefulShutdown ( ) {
2016-01-19 15:40:40 +00:00
// Shut down SDRs.
sdrKill ( )
2016-04-20 08:59:55 +00:00
pingKill ( )
2016-05-02 05:03:21 +00:00
2016-03-26 21:15:59 +00:00
// Shut down data logging.
2016-05-02 05:03:21 +00:00
if dataLogStarted {
closeDataLog ( )
}
2016-05-03 04:40:51 +00:00
2017-04-05 00:52:22 +00:00
pprof . StopCPUProfile ( )
2016-05-03 04:40:51 +00:00
//TODO: Any other graceful shutdown functions.
2016-09-26 18:34:41 +00:00
// Turn off green ACT LED on the Pi.
ioutil . WriteFile ( "/sys/class/leds/led0/brightness" , [ ] byte ( "0\n" ) , 0644 )
2016-01-19 15:40:40 +00:00
os . Exit ( 1 )
}
2016-01-07 16:08:54 +00:00
2016-02-15 22:48:33 +00:00
func signalWatcher ( ) {
sig := <- sigs
log . Printf ( "signal caught: %s - shutting down.\n" , sig . String ( ) )
gracefulShutdown ( )
}
2016-10-23 16:04:18 +00:00
func clearDebugLogFile ( ) {
if logFileHandle != nil {
2017-02-10 06:28:22 +00:00
_ , err := logFileHandle . Seek ( 0 , 0 )
2016-10-23 16:04:18 +00:00
if err != nil {
log . Printf ( "Could not seek to the beginning of the logfile\n" )
return
} else {
err2 := logFileHandle . Truncate ( 0 )
if err2 != nil {
log . Printf ( "Could not truncate the logfile\n" )
return
}
log . Printf ( "Logfile truncated\n" )
}
}
}
2015-08-04 05:44:55 +00:00
func main ( ) {
2016-01-19 15:40:40 +00:00
// Catch signals for graceful shutdown.
signal . Notify ( sigs , syscall . SIGINT , syscall . SIGTERM )
go signalWatcher ( )
2016-01-07 16:08:54 +00:00
stratuxClock = NewMonotonic ( ) // Start our "stratux clock".
2015-12-17 21:11:17 +00:00
2016-12-29 12:33:11 +00:00
// Set up mySituation, do it here so logging JSON doesn't panic
2017-03-30 23:20:49 +00:00
mySituation . muGPS = & sync . Mutex { }
mySituation . muGPSPerformance = & sync . Mutex { }
mySituation . muAttitude = & sync . Mutex { }
mySituation . muBaro = & sync . Mutex { }
mySituation . muSatellite = & sync . Mutex { }
2016-12-29 12:33:11 +00:00
2018-01-09 14:57:00 +00:00
// Set up system error tracking.
systemErrsMutex = & sync . Mutex { }
systemErrs = make ( map [ string ] string )
2016-03-10 19:41:30 +00:00
// Set up status.
globalStatus . Version = stratuxVersion
globalStatus . Build = stratuxBuild
globalStatus . Errors = make ( [ ] string , 0 )
2016-05-27 20:27:22 +00:00
//FlightBox: detect via presence of /etc/FlightBox file.
2016-03-10 19:41:30 +00:00
if _ , err := os . Stat ( "/etc/FlightBox" ) ; ! os . IsNotExist ( err ) {
globalStatus . HardwareBuild = "FlightBox"
2017-03-05 23:56:11 +00:00
logDirf = logDir_FB
2016-06-08 00:20:10 +00:00
} else { // if not using the FlightBox config, use "normal" log file locations
2017-03-05 23:56:11 +00:00
logDirf = logDir
2016-03-10 19:41:30 +00:00
}
2017-10-12 16:22:52 +00:00
//Merlin: detect presence of /etc/Merlin file.
if _ , err := os . Stat ( "/etc/Merlin" ) ; ! os . IsNotExist ( err ) {
globalStatus . HardwareBuild = "Merlin"
}
2017-03-05 23:56:11 +00:00
debugLogf = filepath . Join ( logDirf , debugLogFile )
dataLogFilef = filepath . Join ( logDirf , dataLogFile )
2016-06-24 20:48:48 +00:00
//FIXME: All of this should be removed by 08/01/2016.
2016-06-24 20:10:34 +00:00
// Check if Raspbian version is <8.0. Throw a warning if so.
vt , err := ioutil . ReadFile ( "/etc/debian_version" )
if err == nil {
vtS := strings . Trim ( string ( vt ) , "\n" )
vtF , err := strconv . ParseFloat ( vtS , 32 )
2016-06-24 20:48:48 +00:00
if err == nil {
if vtF < 8.0 {
if globalStatus . HardwareBuild == "FlightBox" {
2018-01-09 15:09:18 +00:00
addSingleSystemErrorf ( "deprecated-image" , "You are running an old Stratux image that can't be updated fully and is now deprecated. Visit https://www.openflightsolutions.com/flightbox/image-update-required for further information." )
2016-06-24 20:48:48 +00:00
} else {
2018-01-09 15:09:18 +00:00
addSingleSystemErrorf ( "deprecated-image" , "You are running an old Stratux image that can't be updated fully and is now deprecated. Visit http://stratux.me/ to update using the latest release image." )
2016-06-24 20:48:48 +00:00
}
2016-06-24 20:10:34 +00:00
} else {
2016-06-24 20:48:48 +00:00
// Running Jessie or better. Remove some old init.d files.
// This made its way in here because /etc/init.d/stratux invokes the update script, which can't delete the init.d file.
os . Remove ( "/etc/init.d/stratux" )
os . Remove ( "/etc/rc2.d/S01stratux" )
os . Remove ( "/etc/rc6.d/K01stratux" )
2016-06-24 20:10:34 +00:00
}
}
}
2016-03-10 19:41:30 +00:00
2015-12-17 21:11:17 +00:00
// replayESFilename := flag.String("eslog", "none", "ES Log filename")
2015-12-13 20:19:15 +00:00
replayUATFilename := flag . String ( "uatlog" , "none" , "UAT Log filename" )
replayFlag := flag . Bool ( "replay" , false , "Replay file flag" )
replaySpeed := flag . Int ( "speed" , 1 , "Replay speed multiplier" )
2016-01-08 21:02:57 +00:00
stdinFlag := flag . Bool ( "uatin" , false , "Process UAT messages piped to stdin" )
2015-12-17 21:11:17 +00:00
2017-04-05 00:52:22 +00:00
cpuprofile := flag . String ( "cpuprofile" , "" , "write cpu profile to file" )
2015-12-13 20:19:15 +00:00
flag . Parse ( )
2015-12-17 21:11:17 +00:00
2015-08-25 19:55:41 +00:00
timeStarted = time . Now ( )
2015-08-15 17:38:44 +00:00
runtime . GOMAXPROCS ( runtime . NumCPU ( ) ) // redundant with Go v1.5+ compiler
2015-09-04 17:38:06 +00:00
2017-04-05 00:52:22 +00:00
// Start CPU profile, if requested.
if * cpuprofile != "" {
f , err := os . Create ( * cpuprofile )
if err != nil {
log . Fatal ( err )
}
2017-04-05 00:55:03 +00:00
log . Printf ( "Writing CPU profile to: %s\n" , * cpuprofile )
2017-04-05 00:52:22 +00:00
pprof . StartCPUProfile ( f )
}
2015-09-05 17:46:55 +00:00
// Duplicate log.* output to debugLog.
2016-05-27 20:27:22 +00:00
fp , err := os . OpenFile ( debugLogf , os . O_CREATE | os . O_WRONLY | os . O_APPEND , 0666 )
2015-09-04 17:38:06 +00:00
if err != nil {
2018-01-09 15:09:18 +00:00
addSingleSystemErrorf ( debugLogf , "Failed to open '%s': %s" , debugLogf , err . Error ( ) )
2015-09-26 07:04:39 +00:00
} else {
defer fp . Close ( )
2016-10-23 16:04:18 +00:00
// Keep the logfile handle for later use
logFileHandle = fp
2015-09-26 07:04:39 +00:00
mfp := io . MultiWriter ( fp , os . Stdout )
log . SetOutput ( mfp )
2015-09-04 17:38:06 +00:00
}
2015-09-19 03:00:05 +00:00
log . Printf ( "Stratux %s (%s) starting.\n" , stratuxVersion , stratuxBuild )
2015-12-17 21:11:17 +00:00
2015-09-24 21:18:21 +00:00
ADSBTowers = make ( map [ string ] ADSBTower )
2016-05-24 19:39:11 +00:00
ADSBTowerMutex = & sync . Mutex { }
2015-08-11 22:27:26 +00:00
MsgLog = make ( [ ] msg , 0 )
2015-08-15 03:58:53 +00:00
2017-01-07 13:00:28 +00:00
// Start the management interface.
go managementInterface ( )
2015-08-15 03:58:53 +00:00
crcInit ( ) // Initialize CRC16 table.
2015-12-17 21:11:17 +00:00
2015-12-14 01:04:40 +00:00
sdrInit ( )
2018-04-25 20:08:46 +00:00
initUATRadioSerial ( )
2016-04-20 08:59:55 +00:00
pingInit ( )
2015-08-15 03:58:53 +00:00
initTraffic ( )
2016-03-10 19:41:30 +00:00
// Read settings.
2015-08-15 03:58:53 +00:00
readSettings ( )
2015-12-17 21:35:42 +00:00
// Disable replay logs when replaying - so that messages replay data isn't copied into the logs.
// Override after reading in the settings.
if * replayFlag == true {
log . Printf ( "Replay file %s\n" , * replayUATFilename )
2017-02-10 06:28:22 +00:00
globalSettings . ReplayLog = false
2015-12-17 21:35:42 +00:00
}
2016-10-02 00:59:41 +00:00
if globalSettings . DeveloperMode == true {
log . Printf ( "Developer mode set\n" )
2016-10-19 00:27:14 +00:00
}
2016-04-12 14:38:04 +00:00
//FIXME: Only do this if data logging is enabled.
2016-03-24 13:33:11 +00:00
initDataLog ( )
2015-10-04 22:50:21 +00:00
2016-12-29 21:12:19 +00:00
// Start the AHRS sensor monitoring.
initI2CSensors ( )
2017-01-06 04:01:00 +00:00
// Start the GPS external sensor monitoring.
initGPS ( )
2015-08-04 05:44:55 +00:00
// Start the heartbeat message loop in the background, once per second.
go heartBeatSender ( )
2015-08-20 16:49:23 +00:00
// Initialize the (out) network handler.
initNetwork ( )
2015-09-22 13:52:49 +00:00
// Start printing stats periodically to the logfiles.
go printStats ( )
2015-10-19 12:17:02 +00:00
// Monitor RPi CPU temp.
2017-05-13 14:33:09 +00:00
go cpuTempMonitor ( func ( cpuTemp float32 ) {
2017-05-11 04:26:17 +00:00
globalStatus . CPUTemp = cpuTemp
} )
2016-04-12 14:38:04 +00:00
2015-08-04 05:44:55 +00:00
reader := bufio . NewReader ( os . Stdin )
2015-12-13 20:19:15 +00:00
if * replayFlag == true {
2015-12-17 21:35:42 +00:00
fp := openReplayFile ( * replayUATFilename )
2015-12-17 21:22:05 +00:00
2015-12-13 20:19:15 +00:00
playSpeed := uint64 ( * replaySpeed )
2015-12-17 21:22:05 +00:00
log . Printf ( "Replay speed: %dx\n" , playSpeed )
2015-12-17 21:35:42 +00:00
go uatReplay ( fp , playSpeed )
2015-12-17 21:22:05 +00:00
2015-12-13 20:19:15 +00:00
for {
time . Sleep ( 1 * time . Second )
if uatReplayDone {
2015-12-17 21:11:17 +00:00
//&& esDone {
2015-12-13 20:19:15 +00:00
return
}
2015-09-11 18:48:34 +00:00
}
2015-12-17 21:11:17 +00:00
2016-01-08 21:02:57 +00:00
} else if * stdinFlag == true {
2015-12-13 20:19:15 +00:00
for {
buf , err := reader . ReadString ( '\n' )
if err != nil {
log . Printf ( "lost stdin.\n" )
break
}
o , msgtype := parseInput ( buf )
if o != nil && msgtype != 0 {
relayMessage ( msgtype , o )
}
2015-08-04 05:44:55 +00:00
}
2016-01-08 21:02:57 +00:00
} else {
// wait indefinitely
select { }
2015-08-04 05:44:55 +00:00
}
2015-08-08 23:05:19 +00:00
}