2016-01-19 13:39:46 +00:00
/ *
Copyright ( c ) 2015 - 2016 Christopher Young
Distributable under the terms of The "BSD New" " License
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"
2015-08-15 17:37:41 +00:00
"runtime"
2015-09-01 20:47:48 +00:00
"strconv"
2015-08-08 23:05:19 +00:00
"strings"
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
2015-09-26 07:09:49 +00:00
humanize "github.com/dustin/go-humanize"
2015-09-26 07:04:39 +00:00
"../uatparse"
2015-08-04 05:44:55 +00:00
)
// http://www.faa.gov/nextgen/programs/adsb/wsa/media/GDL90_Public_ICD_RevA.PDF
const (
2015-09-01 20:16:31 +00:00
configLocation = "/etc/stratux.conf"
2015-12-09 04:58:42 +00:00
indexFilename = "/var/log/stratux/LOGINDEX"
2016-03-08 02:33:28 +00:00
managementAddr = ":80"
2015-09-04 20:44:37 +00:00
debugLog = "/var/log/stratux.log"
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-09 04:58:42 +00:00
logDirectory = "/var/log/stratux"
2016-03-24 13:33:11 +00:00
dataLogFile = "/var/log/stratux.sqlite"
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 )
2015-08-04 05:44:55 +00:00
)
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
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-12-14 01:04:40 +00:00
var developerMode bool
2015-09-05 17:46:55 +00:00
2015-08-11 22:27:26 +00:00
type msg struct {
2016-02-25 05:53:35 +00:00
MessageClass uint
TimeReceived time . Time
Data [ ] byte
Products [ ] uint32
Signal_amplitude int
Signal_strength float64
ADSBTowerID string // Index in the 'ADSBTowers' map, if this is a parseable uplink message.
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
Messages_total uint64
}
var ADSBTowers map [ string ] ADSBTower // Running list of all towers seen. (lat,lng) -> ADSBTower
2015-12-09 04:58:42 +00:00
func constructFilenames ( ) {
var fileIndexNumber uint
2015-12-17 21:11:17 +00:00
2015-12-09 04:58:42 +00:00
// First, create the log file directory if it does not exist
2016-03-08 00:49:46 +00:00
os . Mkdir ( logDirectory , 0755 )
2015-12-17 21:11:17 +00:00
2015-12-09 04:58:42 +00:00
f , err := os . Open ( indexFilename )
if err != nil {
log . Printf ( "Unable to open index file %s using index of 0\n" , indexFilename )
2015-12-17 21:11:17 +00:00
fileIndexNumber = 0
2015-12-09 04:58:42 +00:00
} else {
2015-12-17 21:11:17 +00:00
_ , err := fmt . Fscanf ( f , "%d\n" , & fileIndexNumber )
if err != nil {
log . Printf ( "Unable to read index file %s using index of 0\n" , indexFilename )
}
f . Close ( )
fileIndexNumber ++
2015-12-09 04:58:42 +00:00
}
fo , err := os . Create ( indexFilename )
if err != nil {
log . Printf ( "Error creating index file %s\n" , indexFilename )
}
2015-12-17 21:11:17 +00:00
_ , err2 := fmt . Fprintf ( fo , "%d\n" , fileIndexNumber )
2015-12-09 04:58:42 +00:00
if err2 != nil {
log . Printf ( "Error writing to index file %s\n" , indexFilename )
}
fo . Sync ( )
fo . Close ( )
}
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
}
func makeOwnshipReport ( ) bool {
if ! isGPSValid ( ) {
return false
}
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
2015-09-24 20:26:51 +00:00
tmp := makeLatLng ( mySituation . Lat )
2015-08-15 00:11:04 +00:00
msg [ 5 ] = tmp [ 0 ] // Latitude.
msg [ 6 ] = tmp [ 1 ] // Latitude.
msg [ 7 ] = tmp [ 2 ] // Latitude.
2015-09-24 20:26:51 +00:00
tmp = makeLatLng ( mySituation . Lng )
2015-08-15 17:37:41 +00:00
msg [ 8 ] = tmp [ 0 ] // Longitude.
msg [ 9 ] = tmp [ 1 ] // Longitude.
2015-08-15 00:11:04 +00:00
msg [ 10 ] = tmp [ 2 ] // Longitude.
2015-08-20 23:47:05 +00:00
// This is **PRESSURE ALTITUDE**
2015-09-24 11:46:38 +00:00
//FIXME: Temporarily removing "invalid altitude" when pressure altitude not available - using GPS altitude instead.
2015-09-24 20:26:51 +00:00
// alt := uint16(0xFFF) // 0xFFF "invalid altitude."
2015-08-20 23:47:05 +00:00
2015-09-24 11:46:38 +00:00
var alt uint16
2015-11-25 03:43:07 +00:00
var altf float64
2015-08-20 23:47:05 +00:00
if isTempPressValid ( ) {
2015-11-25 03:43:07 +00:00
altf = float64 ( mySituation . Pressure_alt )
2015-09-24 11:46:38 +00:00
} else {
2015-12-02 00:32:20 +00:00
altf = float64 ( mySituation . Alt ) //FIXME: Pass GPS altitude if PA not available. **WORKAROUND FOR FF**
2015-08-20 23:47:05 +00:00
}
2015-11-25 03:43:07 +00:00
altf = ( altf + 1000 ) / 25
2015-09-24 11:46:38 +00:00
2015-11-25 03:43:07 +00:00
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 )
if isGPSGroundTrackValid ( ) {
2015-09-26 07:04:39 +00:00
msg [ 12 ] = msg [ 12 ] | 0x0B // "Airborne" + "True Heading"
2015-08-15 00:11:04 +00:00
}
2015-09-26 07:04:39 +00:00
2015-09-30 17:36:31 +00:00
msg [ 13 ] = byte ( 0x80 | ( mySituation . NACp & 0x0F ) ) //Set NIC = 8 and use NACp from ry835ai.go.
2015-08-15 00:11:04 +00:00
gdSpeed := uint16 ( 0 ) // 1kt resolution.
if isGPSGroundTrackValid ( ) {
2015-09-24 20:26:51 +00:00
gdSpeed = mySituation . GroundSpeed
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 )
// Showing magnetic (corrected) on ForeFlight. Needs to be True Heading.
groundTrack := uint16 ( 0 )
if isGPSGroundTrackValid ( ) {
2015-09-24 20:26:51 +00:00
groundTrack = mySituation . TrueCourse
2015-08-15 00:11:04 +00:00
}
trk := uint8 ( float32 ( groundTrack ) / TRACK_RESOLUTION ) // Resolution is ~1.4 degrees.
msg [ 17 ] = byte ( trk )
msg [ 18 ] = 0x01 // "Light (ICAO) < 15,500 lbs"
2015-09-30 17:36:31 +00:00
// Create callsign "Stratux".
msg [ 19 ] = 0x53
msg [ 20 ] = 0x74
msg [ 21 ] = 0x72
msg [ 22 ] = 0x61
msg [ 23 ] = 0x74
msg [ 24 ] = 0x75
msg [ 25 ] = 0x78
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.
2015-08-20 20:47:05 +00:00
msg [ 0 ] = 0x0B // Message type "Ownship Geo Alt".
2015-09-24 20:26:51 +00:00
alt := int16 ( mySituation . Alt ) // GPS Altitude.
2015-08-15 17:37:41 +00:00
alt = alt / 5
msg [ 1 ] = byte ( alt >> 8 ) // Altitude.
2015-08-15 00:11:04 +00:00
msg [ 2 ] = byte ( alt & 0x00FF ) // Altitude.
//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 ( ) {
2016-03-24 04:26:56 +00:00
switch mySituation . Quality {
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.
if isCPUTempValid ( ) {
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
}
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.
if globalSettings . AHRS_Enabled {
msg [ 12 ] = 1 << 0
}
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 )
2015-12-18 17:28:37 +00:00
// Connected hardware: RY835AI.
if globalStatus . RY835AI_connected {
2015-12-22 22:18:14 +00:00
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
// Summarize number of UAT and 1090ES traffic targets for reports that follow.
var uat_traffic_targets uint16
var es_traffic_targets uint16
for _ , traf := range traffic {
switch traf . Last_source {
case TRAFFIC_SOURCE_1090ES :
es_traffic_targets ++
case TRAFFIC_SOURCE_UAT :
uat_traffic_targets ++
}
}
// Number of UAT traffic targets.
msg [ 18 ] = byte ( ( uat_traffic_targets & 0xFF00 ) >> 8 )
msg [ 19 ] = byte ( uat_traffic_targets & 0xFF )
// Number of 1090ES traffic targets.
msg [ 20 ] = byte ( ( es_traffic_targets & 0xFF00 ) >> 8 )
msg [ 21 ] = byte ( es_traffic_targets & 0xFF )
// 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
// Number of ADS-B towers.
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.
}
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 )
}
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.
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
}
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 )
2015-08-04 05:44:55 +00:00
for {
2015-09-12 21:28:58 +00:00
select {
case <- timer . C :
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 )
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-05 07:51:19 +00:00
products_last_minute := make ( map [ string ] uint32 )
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:28:08 +00:00
for _ , p := range MsgLog [ i ] . Products {
products_last_minute [ getProductNameFromId ( int ( p ) ) ] ++
}
2015-09-24 21:18:21 +00:00
if len ( MsgLog [ i ] . ADSBTowerID ) > 0 { // Update tower stats.
tid := MsgLog [ i ] . ADSBTowerID
twr := ADSBTowers [ tid ]
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
2016-03-24 04:29:08 +00:00
globalStatus . UAT_products_last_minute = products_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 {
if tinf . Messages_last_minute == 0 {
2016-02-25 05:53:35 +00:00
tinf . Signal_strength_last_minute = - 99
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
}
2015-12-18 17:28:37 +00:00
// Check if CPU temperature is valid. Assume <= 0 is invalid.
func isCPUTempValid ( ) bool {
2015-12-22 22:17:02 +00:00
return globalStatus . CPUTemp > 0
2015-12-18 17:28:37 +00:00
}
2015-10-19 12:17:02 +00:00
/ *
cpuTempMonitor ( ) reads the RPi board temperature every second and updates it in globalStatus .
This is broken out into its own function ( run as its own goroutine ) because the RPi temperature
monitor code is buggy , and often times reading this file hangs quite some time .
* /
func cpuTempMonitor ( ) {
timer := time . NewTicker ( 1 * time . Second )
for {
<- timer . C
// Update CPUTemp.
globalStatus . CPUTemp = float32 ( - 99.0 ) // Default value - in case code below hangs.
temp , err := ioutil . ReadFile ( "/sys/class/thermal/thermal_zone0/temp" )
tempStr := strings . Trim ( string ( temp ) , "\n" )
if err == nil {
tInt , err := strconv . Atoi ( tempStr )
if err == nil {
2015-11-06 14:47:25 +00:00
if tInt > 1000 {
globalStatus . CPUTemp = float32 ( tInt ) / float32 ( 1000.0 )
} else {
globalStatus . CPUTemp = float32 ( tInt ) // case where Temp is returned as simple integer
}
2015-10-19 12:17:02 +00:00
}
2015-11-07 03:57:59 +00:00
}
2015-10-19 12:17:02 +00:00
}
}
2015-09-12 21:28:58 +00:00
func updateStatus ( ) {
2016-03-24 04:26:56 +00:00
if mySituation . Quality == 2 {
2016-02-05 01:08:12 +00:00
globalStatus . GPS_solution = "DGPS (SBAS / WAAS)"
2016-03-24 04:26:56 +00:00
} else if mySituation . Quality == 1 {
2016-02-05 01:08:12 +00:00
globalStatus . GPS_solution = "3D GPS"
2016-03-24 04:26:56 +00:00
} else if mySituation . Quality == 6 {
2016-02-05 01:08:12 +00:00
globalStatus . GPS_solution = "Dead Reckoning"
2016-03-24 04:26:56 +00:00
} else if mySituation . Quality == 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-02-05 01:08:12 +00:00
mySituation . Satellites = 0
mySituation . SatellitesSeen = 0
mySituation . SatellitesTracked = 0
2016-03-24 04:26:56 +00:00
mySituation . Quality = 0
2016-02-05 01:08:12 +00:00
globalStatus . GPS_solution = "Disconnected"
globalStatus . GPS_connected = false
}
2015-12-28 06:56:33 +00:00
globalStatus . GPS_satellites_locked = mySituation . Satellites
globalStatus . GPS_satellites_seen = mySituation . SatellitesSeen
globalStatus . GPS_satellites_tracked = mySituation . SatellitesTracked
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
globalStatus . Clock = time . Now ( )
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.
2015-09-30 16:50:23 +00:00
func registerADSBTextMessageReceived ( msg string ) {
x := strings . Split ( msg , " " )
if len ( x ) < 5 {
return
}
var wm WeatherMessage
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
2015-10-08 00:24:56 +00:00
wmJSON , _ := json . Marshal ( & wm )
2015-09-30 16:50:23 +00:00
// Send to weatherUpdate channel for any connected clients.
2015-10-08 00:24:56 +00:00
weatherUpdate . Send ( wmJSON )
2015-09-30 16:50:23 +00:00
}
2015-08-09 16:10:44 +00:00
func parseInput ( buf string ) ( [ ] byte , uint16 ) {
2015-09-05 17:46:55 +00:00
replayLog ( buf , MSGCLASS_UAT ) // Log the raw message.
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
} 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
2015-08-11 22:27:26 +00:00
thisMsg . Data = frame
2016-02-25 05:53:35 +00:00
thisMsg . Signal_amplitude = thisSignalStrength
2016-02-27 08:35:17 +00:00
thisMsg . Signal_strength = 20 * math . Log10 ( ( float64 ( thisSignalStrength ) ) / 1000 )
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
if _ , ok := ADSBTowers [ towerid ] ; ! ok { // First time we've seen the tower. Start tracking.
var newTower ADSBTower
newTower . Lat = uatMsg . Lat
newTower . Lng = uatMsg . Lon
2016-02-25 05:53:35 +00:00
newTower . Signal_strength_now = thisMsg . Signal_strength
newTower . Signal_strength_max = - 999 // dBmax = 0, so this needs to initialize below scale ( << -48 dB)
2015-09-24 21:18:21 +00:00
ADSBTowers [ towerid ] = newTower
}
twr := ADSBTowers [ towerid ]
twr . Messages_total ++
2016-02-25 05:53:35 +00:00
twr . Signal_strength_now = thisMsg . Signal_strength
2015-09-24 21:18:21 +00:00
ADSBTowers [ towerid ] = twr
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 )
}
2015-09-30 16:50:23 +00:00
// Get all of the text reports.
textReports , _ := uatMsg . GetTextReports ( )
for _ , r := range textReports {
registerADSBTextMessageReceived ( r )
}
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
var product_name_map = map [ int ] string {
2015-09-05 16:48:25 +00:00
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 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 {
2015-08-20 16:54:46 +00:00
UAT_Enabled bool
ES_Enabled bool
GPS_Enabled bool
2015-08-20 20:47:05 +00:00
NetworkOutputs [ ] networkConnection
AHRS_Enabled bool
2015-09-04 03:35:26 +00:00
DEBUG bool
2015-10-26 00:58:21 +00:00
ReplayLog bool
2015-09-19 18:19:57 +00:00
PPM int
2015-10-06 22:31:20 +00:00
OwnshipModeS string
2015-10-04 17:50:51 +00:00
WatchList string
2015-08-11 22:27:26 +00:00
}
type status struct {
2016-02-15 01:27:02 +00:00
Version string
2016-02-29 03:29:40 +00:00
Build string
2016-03-10 18:09:25 +00:00
HardwareBuild string
2016-02-15 01:27:02 +00:00
Devices uint32
Connected_Users uint
UAT_messages_last_minute uint
2016-03-24 04:29:08 +00:00
UAT_products_last_minute map [ string ] uint32
2016-02-15 01:27:02 +00:00
UAT_messages_max uint
ES_messages_last_minute uint
ES_messages_max uint
GPS_satellites_locked uint16
GPS_satellites_seen uint16
GPS_satellites_tracked uint16
GPS_connected bool
GPS_solution string
RY835AI_connected bool
Uptime int64
2016-02-17 03:13:21 +00:00
Clock time . Time
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-03-10 19:41:30 +00:00
Errors [ ] string
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
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-08-25 03:15:37 +00:00
globalSettings . AHRS_Enabled = false
2015-09-04 03:35:26 +00:00
globalSettings . DEBUG = false
2015-09-05 17:46:55 +00:00
globalSettings . ReplayLog = false //TODO: 'true' for debug builds.
2015-10-10 00:07:28 +00:00
globalSettings . OwnshipModeS = "F00000"
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
}
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 {
2016-03-10 18:06:14 +00:00
err_ret := fmt . Errorf ( "can't save settings %s: %s" , configLocation , err . Error ( ) )
addSystemError ( err_ret )
log . Printf ( "%s\n" , err_ret . 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
}
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" ) )
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 ) ) )
2015-10-07 23:03:24 +00:00
log . Printf ( " - UAT/min %s/%s [maxSS=%.02f%%], ES/min %s/%s\n, 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 {
2016-03-24 04:26:56 +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 . LastFixLocalTime ) , mySituation . Quality , mySituation . Satellites , mySituation . SatellitesSeen , mySituation . SatellitesTracked , mySituation . NACp , mySituation . Accuracy )
2015-12-27 08:47:37 +00:00
log . Printf ( " - GPS vertical velocity: %.02f ft/sec; GPS vertical accuracy: %v m\n" , mySituation . GPSVertVel , mySituation . AccuracyVert )
2015-10-07 23:03:24 +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 ( )
//TODO: Any other graceful shutdown functions.
2016-02-15 22:48:33 +00:00
closeReplayLogs ( )
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 ( )
}
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-03-10 19:41:30 +00:00
// Set up status.
globalStatus . Version = stratuxVersion
globalStatus . Build = stratuxBuild
globalStatus . Errors = make ( [ ] string , 0 )
if _ , err := os . Stat ( "/etc/FlightBox" ) ; ! os . IsNotExist ( err ) {
globalStatus . HardwareBuild = "FlightBox"
}
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" )
2015-12-14 01:04:40 +00:00
develFlag := flag . Bool ( "developer" , false , "Developer mode" )
2015-12-13 20:19:15 +00:00
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
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
2015-12-14 01:04:40 +00:00
if * develFlag == true {
2015-12-17 21:11:17 +00:00
log . Printf ( "Developer mode flag true!\n" )
developerMode = true
2015-12-14 01:04:40 +00:00
}
2015-12-17 21:11:17 +00:00
2015-09-05 17:46:55 +00:00
// Duplicate log.* output to debugLog.
2015-09-04 17:38:06 +00:00
fp , err := os . OpenFile ( debugLog , os . O_CREATE | os . O_WRONLY | os . O_APPEND , 0666 )
if err != nil {
2016-03-10 18:06:14 +00:00
err_log := fmt . Errorf ( "Failed to open '%s': %s" , debugLog , err . Error ( ) )
addSystemError ( err_log )
log . Printf ( "%s\n" , err_log . Error ( ) )
2015-09-26 07:04:39 +00:00
} else {
defer fp . Close ( )
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-09 04:58:42 +00:00
constructFilenames ( )
2015-12-17 21:11:17 +00:00
2015-09-24 21:18:21 +00:00
ADSBTowers = make ( map [ string ] ADSBTower )
2015-08-11 22:27:26 +00:00
MsgLog = make ( [ ] msg , 0 )
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 ( )
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 )
globalSettings . ReplayLog = true
}
2016-03-24 13:33:11 +00:00
//FIXME: Only do this if data logging is enabled.
initDataLog ( )
2015-10-04 22:50:21 +00:00
2015-08-20 20:47:05 +00:00
initRY835AI ( )
2015-08-15 00:11:04 +00:00
2015-08-04 05:44:55 +00:00
// Start the heartbeat message loop in the background, once per second.
go heartBeatSender ( )
2015-08-11 22:27:26 +00:00
// Start the management interface.
go managementInterface ( )
2015-08-04 05:44:55 +00:00
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.
go cpuTempMonitor ( )
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
}