2015-08-04 05:44:55 +00:00
package main
import (
2015-08-08 23:05:19 +00:00
"bufio"
"encoding/hex"
2015-08-15 17:37:41 +00:00
"encoding/json"
2015-09-05 07:51:19 +00:00
"fmt"
2015-09-23 20:21:32 +00:00
"github.com/dustin/go-humanize"
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"
2015-08-08 23:05:19 +00:00
"os"
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"
"time"
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"
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-09-05 17:46:55 +00:00
uatReplayLog = "/var/log/stratux-uat.log"
esReplayLog = "/var/log/stratux-es.log"
2015-09-21 18:20:12 +00:00
gpsReplayLog = "/var/log/stratux-gps.log"
2015-09-23 20:21:32 +00:00
ahrsReplayLog = "/var/log/stratux-ahrs.log"
2015-09-01 20:16:31 +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
2015-09-23 20:21:32 +00:00
MSGCLASS_UAT = 0
MSGCLASS_ES = 1
MSGCLASS_GPS = 3
MSGCLASS_AHRS = 4
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
)
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-09-05 17:46:55 +00:00
// File handles for replay logging.
var uatReplayfp * os . File
var esReplayfp * os . File
2015-09-21 18:20:12 +00:00
var gpsReplayfp * os . File
2015-09-23 20:21:32 +00:00
var ahrsReplayfp * os . File
2015-09-05 17:46:55 +00:00
2015-08-11 22:27:26 +00:00
type msg struct {
2015-08-15 17:37:41 +00:00
MessageClass uint
TimeReceived time . Time
Data [ ] byte
2015-09-05 07:51:19 +00:00
Product uint32
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-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
}
//TODO
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.
msg [ 2 ] = 1 // Address.
msg [ 3 ] = 1 // Address.
msg [ 4 ] = 1 // Address.
2015-08-20 20:47:05 +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-08-20 20:47:05 +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**
alt := uint16 ( 0xFFF ) // 0xFFF "invalid altitude."
if isTempPressValid ( ) {
alt = uint16 ( mySituation . pressure_alt )
2015-09-24 03:12:48 +00:00
alt = ( alt + 1000 ) / 25
2015-08-20 23:47:05 +00:00
}
2015-08-15 00:11:04 +00:00
alt = alt & 0xFFF // Should fit in 12 bits.
msg [ 11 ] = byte ( ( alt & 0xFF0 ) >> 4 ) // Altitude.
msg [ 12 ] = byte ( ( alt & 0x00F ) << 4 )
if isGPSGroundTrackValid ( ) {
msg [ 12 ] = byte ( ( ( alt & 0x00F ) << 4 ) | 0xB ) // "Airborne" + "True Heading"
} else {
msg [ 12 ] = byte ( ( alt & 0x00F ) << 4 )
}
msg [ 13 ] = 0xBB // NIC and NACp.
gdSpeed := uint16 ( 0 ) // 1kt resolution.
if isGPSGroundTrackValid ( ) {
2015-08-20 20:47:05 +00:00
gdSpeed = mySituation . groundSpeed
2015-08-15 00:11:04 +00:00
}
gdSpeed = gdSpeed & 0x0FFF // Should fit in 12 bits.
msg [ 14 ] = byte ( ( gdSpeed & 0xFF0 ) >> 4 )
msg [ 15 ] = byte ( ( gdSpeed & 0x00F ) << 4 )
verticalVelocity := int16 ( 1000 / 64 ) // ft/min. 64 ft/min resolution.
//TODO: 0x800 = no information available.
verticalVelocity = verticalVelocity & 0x0FFF // 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-08-20 20:47:05 +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-01 20:16:31 +00:00
sendGDL90 ( prepareMessage ( msg ) , false )
2015-08-15 00:11:04 +00:00
return true
}
//TODO
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-08-20 23:47:05 +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-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 )
2015-09-12 21:28:58 +00:00
timerMessageStats := time . NewTicker ( 5 * 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 )
// sendGDL90(makeTrafficReport())
makeOwnshipReport ( )
makeOwnshipGeometricAltitudeReport ( )
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-08-11 22:27:26 +00:00
for i := 0 ; i < m ; i ++ {
if time . Now ( ) . Sub ( MsgLog [ i ] . TimeReceived ) . Minutes ( ) < 1 {
t = append ( t , MsgLog [ i ] )
if MsgLog [ i ] . MessageClass == MSGCLASS_UAT {
UAT_messages_last_minute ++
2015-09-05 07:51:19 +00:00
products_last_minute [ getProductNameFromId ( int ( MsgLog [ i ] . Product ) ) ] ++
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-09-05 07:51:19 +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-12 21:28:58 +00:00
}
func updateStatus ( ) {
2015-08-15 03:58:53 +00:00
if isGPSValid ( ) {
2015-08-20 20:47:05 +00:00
globalStatus . GPS_satellites_locked = mySituation . satellites
2015-08-15 03:58:53 +00:00
}
2015-08-25 19:55:41 +00:00
2015-09-02 20:27:39 +00:00
// Update Uptime value
2015-09-04 17:53:18 +00:00
globalStatus . Uptime = time . Since ( timeStarted ) . Nanoseconds ( ) / 1000000
2015-09-01 20:47:48 +00:00
// Update CPUTemp.
temp , err := ioutil . ReadFile ( "/sys/class/thermal/thermal_zone0/temp" )
tempStr := strings . Trim ( string ( temp ) , "\n" )
globalStatus . CPUTemp = float32 ( - 99.0 )
if err == nil {
tInt , err := strconv . Atoi ( tempStr )
if err == nil {
globalStatus . CPUTemp = float32 ( tInt ) / float32 ( 1000.0 )
}
}
2015-08-11 22:27:26 +00:00
}
2015-09-05 17:46:55 +00:00
func replayLog ( msg string , msgclass int ) {
if ! globalSettings . ReplayLog { // Logging disabled.
return
}
2015-09-11 14:50:33 +00:00
msg = strings . Trim ( msg , " \r\n" )
if len ( msg ) == 0 { // Blank message.
return
}
2015-09-23 20:21:32 +00:00
var fp * os . File
switch msgclass {
case MSGCLASS_UAT :
fp = uatReplayfp
case MSGCLASS_ES :
fp = esReplayfp
case MSGCLASS_GPS :
fp = gpsReplayfp
case MSGCLASS_AHRS :
fp = ahrsReplayfp
}
if fp != nil {
fmt . Fprintf ( fp , "%d,%s\n" , time . Since ( timeStarted ) . Nanoseconds ( ) , msg )
2015-09-05 17:46:55 +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
if len ( x ) == 0 {
2015-08-09 16:10:44 +00:00
return nil , 0
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-08-15 03:10:16 +00:00
if s [ 0 ] == '-' {
parseDownlinkReport ( s )
}
2015-09-22 13:52:49 +00:00
if isUplink && len ( x ) >= 3 {
// See if we can parse out the signal strength.
ss := x [ 2 ]
if strings . HasPrefix ( ss , "ss=" ) {
ssStr := ss [ 3 : ]
if ssInt , err := strconv . Atoi ( ssStr ) ; err == nil {
if ssInt > maxSignalStrength {
maxSignalStrength = ssInt
}
}
}
}
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
}
if msglen == UPLINK_FRAME_DATA_BYTES {
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
thisMsg . TimeReceived = time . Now ( )
thisMsg . Data = frame
2015-09-05 17:02:06 +00:00
thisMsg . Product = 9999
if isUplink && msgtype == MSGTYPE_UPLINK && len ( x ) > 11 { //FIXME: Need to pull out FIS-B frames from within the uplink packet.
thisMsg . Product = ( ( uint32 ( frame [ 10 ] ) & 0x1f ) << 6 ) | ( uint32 ( frame [ 11 ] ) >> 2 )
}
2015-08-11 22:27:26 +00:00
MsgLog = append ( MsgLog , thisMsg )
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-09-05 17:46:55 +00:00
ReplayLog bool // Startup only option. Cannot be changed during runtime.
2015-09-19 18:19:57 +00:00
PPM int
2015-08-11 22:27:26 +00:00
}
type status struct {
2015-08-15 17:37:41 +00:00
Version string
Devices uint
2015-08-22 01:00:43 +00:00
Connected_Users uint
2015-08-15 17:37:41 +00:00
UAT_messages_last_minute uint
2015-09-05 07:51:19 +00:00
UAT_products_last_minute map [ string ] uint32
2015-08-15 17:37:41 +00:00
UAT_messages_max uint
ES_messages_last_minute uint
ES_messages_max uint
GPS_satellites_locked uint16
2015-08-20 20:47:05 +00:00
GPS_connected bool
RY835AI_connected bool
2015-09-04 03:50:34 +00:00
Uptime int64
2015-09-01 20:47:48 +00:00
CPUTemp float32
2015-08-11 22:27:26 +00:00
}
var globalSettings settings
var globalStatus status
func defaultSettings ( ) {
2015-08-25 23:27:28 +00:00
globalSettings . UAT_Enabled = true //TODO
2015-08-25 03:15:37 +00:00
globalSettings . ES_Enabled = false //TODO
globalSettings . GPS_Enabled = false //TODO
2015-09-01 20:16:31 +00:00
//FIXME: Need to change format below.
2015-09-22 13:52:49 +00:00
globalSettings . NetworkOutputs = [ ] networkConnection { { nil , "" , 4000 , NETWORK_GDL90_STANDARD , nil , time . Time { } , time . Time { } , 0 } , { nil , "" , 43211 , NETWORK_GDL90_STANDARD | NETWORK_AHRS_GDL90 , nil , time . Time { } , time . Time { } , 0 } , { nil , "" , 49002 , NETWORK_AHRS_FFSIM , nil , time . Time { } , time . Time { } , 0 } }
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-08-11 22:27:26 +00:00
}
func readSettings ( ) {
fd , err := os . Open ( configLocation )
defer fd . Close ( )
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
}
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
}
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
defer fd . Close ( )
if err != nil {
2015-08-17 17:59:03 +00:00
log . Printf ( "can't save settings %s: %s\n" , configLocation , err . Error ( ) )
2015-08-11 22:27:26 +00:00
return
}
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-09-21 18:20:12 +00:00
func openReplay ( fn string ) ( * os . File , error ) {
ret , err := os . OpenFile ( fn , os . O_CREATE | os . O_WRONLY | os . O_APPEND , 0666 )
if err != nil {
log . Printf ( "Failed to open log file '%s': %s\n" , fn , err . Error ( ) )
} else {
fmt . Fprintf ( ret , "START,%s\n" , timeStarted . Format ( "Mon Jan 2 15:04:05 -0700 MST 2006" ) ) // Start time marker.
}
return ret , 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 )
log . Printf ( "stats [up since: %s]\n" , humanize . Time ( timeStarted ) )
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-09-23 20:21:32 +00:00
log . Printf ( " - UAT/min %s/%s [maxSS=%.02f%%], ES/min %s/%s\n" , 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 ) ) )
2015-09-22 13:52:49 +00:00
log . Printf ( " - Total traffic targets tracked=%s, last GPS fix: %s\n" , humanize . Comma ( int64 ( len ( seenTraffic ) ) ) , humanize . Time ( mySituation . lastFixLocalTime ) )
}
}
2015-08-04 05:44:55 +00:00
func main ( ) {
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-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 )
defer fp . Close ( )
if err != nil {
log . Printf ( "Failed to open log file '%s': %s\n" , debugLog , err . Error ( ) )
}
mfp := io . MultiWriter ( fp , os . Stdout )
log . SetOutput ( mfp )
2015-09-19 03:00:05 +00:00
log . Printf ( "Stratux %s (%s) starting.\n" , stratuxVersion , stratuxBuild )
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-09-11 00:43:32 +00:00
sdrInit ( )
2015-08-15 03:58:53 +00:00
initTraffic ( )
2015-08-11 22:27:26 +00:00
globalStatus . Version = stratuxVersion
2015-08-15 03:58:53 +00:00
readSettings ( )
2015-09-05 17:46:55 +00:00
// Log inputs.
if globalSettings . ReplayLog {
2015-09-21 18:20:12 +00:00
// UAT replay log.
if uatfp , err := openReplay ( uatReplayLog ) ; err != nil {
2015-09-05 17:46:55 +00:00
globalSettings . ReplayLog = false
} else {
uatReplayfp = uatfp
defer uatReplayfp . Close ( )
}
2015-09-21 18:20:12 +00:00
// 1090ES replay log.
if esfp , err := openReplay ( esReplayLog ) ; err != nil {
2015-09-05 17:46:55 +00:00
globalSettings . ReplayLog = false
} else {
esReplayfp = esfp
defer esReplayfp . Close ( )
}
2015-09-21 18:20:12 +00:00
// GPS replay log.
if gpsfp , err := openReplay ( gpsReplayLog ) ; err != nil {
globalSettings . ReplayLog = false
} else {
gpsReplayfp = gpsfp
defer gpsReplayfp . Close ( )
}
2015-09-23 20:21:32 +00:00
// AHRS replay log.
if ahrsfp , err := openReplay ( ahrsReplayLog ) ; err != nil {
globalSettings . ReplayLog = false
} else {
ahrsReplayfp = ahrsfp
defer ahrsReplayfp . Close ( )
}
2015-09-05 17:46:55 +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-08-04 05:44:55 +00:00
reader := bufio . NewReader ( os . Stdin )
for {
2015-09-11 18:48:34 +00:00
buf , err := reader . ReadString ( '\n' )
if err != nil {
log . Printf ( "lost stdin.\n" )
break
}
2015-08-09 16:10:44 +00:00
o , msgtype := parseInput ( buf )
if o != nil && msgtype != 0 {
relayMessage ( msgtype , o )
2015-08-04 05:44:55 +00:00
}
}
2015-08-08 23:05:19 +00:00
}