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 .
traffic . go : Target management , UAT downlink message processing , 1090 ES source input , GDL90 traffic reports .
* /
2015-08-15 03:10:16 +00:00
package main
import (
2015-08-22 01:00:43 +00:00
"bufio"
2015-08-15 03:10:16 +00:00
"encoding/hex"
2015-10-08 00:24:56 +00:00
"encoding/json"
2015-08-15 03:10:16 +00:00
"math"
2015-08-22 01:00:43 +00:00
"net"
"strconv"
"strings"
2015-08-20 17:06:40 +00:00
"sync"
2015-08-15 03:10:16 +00:00
"time"
)
//-0b2b48fe3aef1f88621a0856110a31c01105c4e6c4e6c40a9a820300000000000000;rs=7;
/ *
HDR :
MDB Type : 1
Address : 2 B48FE ( TIS - B track file address )
SV :
NIC : 6
Latitude : + 41.4380
Longitude : - 84.1056
Altitude : 2300 ft ( barometric )
N / S velocity : - 65 kt
E / W velocity : - 98 kt
Track : 236
Speed : 117 kt
Vertical rate : 0 ft / min ( from barometric altitude )
UTC coupling : no
TIS - B site ID : 1
MS :
Emitter category : No information
Callsign : unavailable
Emergency status : No emergency
UAT version : 2
SIL : 2
Transmit MSO : 38
NACp : 8
NACv : 1
NICbaro : 0
2015-08-20 17:06:40 +00:00
Capabilities :
Active modes :
2015-08-15 03:10:16 +00:00
Target track type : true heading
AUXSV :
Sec . altitude : unavailable
* /
2015-10-01 20:10:35 +00:00
const (
TRAFFIC_SOURCE_1090ES = 1
TRAFFIC_SOURCE_UAT = 2
)
2015-08-15 03:10:16 +00:00
type TrafficInfo struct {
2015-09-24 20:26:51 +00:00
Icao_addr uint32
2015-11-06 03:02:58 +00:00
OnGround bool
2015-08-23 02:45:54 +00:00
addr_type uint8
emitter_category uint8
2015-08-15 03:10:16 +00:00
2015-09-24 20:26:51 +00:00
Lat float32
Lng float32
2015-08-15 03:10:16 +00:00
2015-09-24 20:26:51 +00:00
Position_valid bool
2015-08-15 03:10:16 +00:00
2015-12-02 01:11:33 +00:00
Alt int32
2015-08-15 03:10:16 +00:00
2015-09-24 20:26:51 +00:00
Track uint16
Speed uint16
Speed_valid bool
2015-08-15 03:10:16 +00:00
2015-09-24 20:26:51 +00:00
Vvel int16
2015-08-15 03:10:16 +00:00
2015-09-24 20:26:51 +00:00
Tail string
2015-08-15 03:10:16 +00:00
2015-10-01 20:10:35 +00:00
Last_seen time . Time
Last_source uint8
2015-08-15 03:10:16 +00:00
}
2015-08-15 03:58:53 +00:00
var traffic map [ uint32 ] TrafficInfo
2015-08-20 17:06:40 +00:00
var trafficMutex * sync . Mutex
2015-09-22 13:52:49 +00:00
var seenTraffic map [ uint32 ] bool // Historical list of all ICAO addresses seen.
2015-08-15 03:58:53 +00:00
func cleanupOldEntries ( ) {
for icao_addr , ti := range traffic {
2016-01-07 16:42:30 +00:00
if stratuxClock . Since ( ti . Last_seen ) > 60 * time . Second { //FIXME: 60 seconds with no update on this address - stop displaying.
2015-08-15 03:58:53 +00:00
delete ( traffic , icao_addr )
}
}
}
func sendTrafficUpdates ( ) {
2015-08-20 17:06:40 +00:00
trafficMutex . Lock ( )
defer trafficMutex . Unlock ( )
2015-08-15 03:58:53 +00:00
cleanupOldEntries ( )
2016-02-15 01:27:02 +00:00
var msg [ ] byte
for _ , ti := range traffic { // TO-DO: Limit number of aircraft in traffic message. ForeFlight chokes at ~1000-2000 messages depending on iDevice RAM. Practical limit likely around 500-1000 aircraft
2015-09-24 20:26:51 +00:00
if ti . Position_valid {
2016-02-15 01:27:02 +00:00
msg = append ( msg , makeTrafficReportMsg ( ti ) ... )
2015-08-23 03:56:08 +00:00
}
2015-08-15 03:58:53 +00:00
}
2016-02-15 01:27:02 +00:00
if len ( msg ) > 0 {
sendGDL90 ( msg , false )
}
2015-08-15 03:58:53 +00:00
}
2015-09-30 17:14:48 +00:00
// Send update to attached client.
func registerTrafficUpdate ( ti TrafficInfo ) {
2015-10-08 00:24:56 +00:00
if ! ti . Position_valid { // Don't send unless a valid position exists.
return
2015-09-30 22:52:52 +00:00
}
2015-10-08 00:24:56 +00:00
tiJSON , _ := json . Marshal ( & ti )
trafficUpdate . Send ( tiJSON )
2015-09-30 17:14:48 +00:00
}
2016-02-15 01:27:02 +00:00
func makeTrafficReportMsg ( ti TrafficInfo ) [ ] byte {
2015-08-15 03:10:16 +00:00
msg := make ( [ ] byte , 28 )
// See p.16.
msg [ 0 ] = 0x14 // Message type "Traffic Report".
msg [ 1 ] = 0x10 | ti . addr_type // Alert status, address type.
// ICAO Address.
2015-09-24 20:26:51 +00:00
msg [ 2 ] = byte ( ( ti . Icao_addr & 0x00FF0000 ) >> 16 )
msg [ 3 ] = byte ( ( ti . Icao_addr & 0x0000FF00 ) >> 8 )
msg [ 4 ] = byte ( ( ti . Icao_addr & 0x000000FF ) )
2015-08-15 03:10:16 +00:00
2015-09-24 20:26:51 +00:00
lat := float32 ( ti . Lat )
2015-08-15 03:10:16 +00:00
tmp := makeLatLng ( lat )
msg [ 5 ] = tmp [ 0 ] // Latitude.
msg [ 6 ] = tmp [ 1 ] // Latitude.
msg [ 7 ] = tmp [ 2 ] // Latitude.
2015-09-24 20:26:51 +00:00
lng := float32 ( ti . Lng )
2015-08-15 03:10:16 +00:00
tmp = makeLatLng ( lng )
2015-08-20 17:06:40 +00:00
msg [ 8 ] = tmp [ 0 ] // Longitude.
msg [ 9 ] = tmp [ 1 ] // Longitude.
2015-08-15 03:10:16 +00:00
msg [ 10 ] = tmp [ 2 ] // Longitude.
2015-12-20 21:34:36 +00:00
// Altitude: OK
// GDL 90 Data Interface Specification examples:
// where 1,000 foot offset and 25 foot resolution (1,000 / 25 = 40)
// -1,000 feet 0x000
// 0 feet 0x028
// +1000 feet 0x050
// +101,350 feet 0xFFE
// Invalid or unavailable 0xFFF
//
2015-12-21 00:20:38 +00:00
// Algo example at: https://play.golang.org/p/VXCckSdsvT
2015-12-20 21:34:36 +00:00
//
2015-12-20 22:16:52 +00:00
var alt int16
2015-12-20 21:34:36 +00:00
if ti . Alt < - 1000 || ti . Alt > 101350 {
2015-12-20 22:16:52 +00:00
alt = 0x0FFF
2015-12-20 21:34:36 +00:00
} else {
2015-12-20 22:16:52 +00:00
// output guaranteed to be between 0x0000 and 0x0FFE
alt = int16 ( ( ti . Alt / 25 ) + 40 )
2015-12-20 21:34:36 +00:00
}
2015-08-15 03:10:16 +00:00
msg [ 11 ] = byte ( ( alt & 0xFF0 ) >> 4 ) // Altitude.
msg [ 12 ] = byte ( ( alt & 0x00F ) << 4 )
2015-11-06 03:02:58 +00:00
msg [ 12 ] = byte ( ( ( alt & 0x00F ) << 4 ) | 0x3 ) // True heading.
if ! ti . OnGround {
msg [ 12 ] = msg [ 12 ] | 0x08 // Airborne.
}
2015-08-15 03:10:16 +00:00
msg [ 13 ] = 0x11 //FIXME.
// Horizontal velocity (speed).
2015-09-24 20:26:51 +00:00
msg [ 14 ] = byte ( ( ti . Speed & 0x0FF0 ) >> 4 )
msg [ 15 ] = byte ( ( ti . Speed & 0x000F ) << 4 )
2015-08-15 03:10:16 +00:00
// Vertical velocity.
2015-09-24 20:26:51 +00:00
vvel := ti . Vvel / 64 // 64fpm resolution.
2015-08-20 17:06:40 +00:00
msg [ 15 ] = msg [ 15 ] | byte ( ( vvel & 0x0F00 ) >> 8 )
2015-08-15 03:10:16 +00:00
msg [ 16 ] = byte ( vvel & 0x00FF )
// Track.
2015-09-24 20:26:51 +00:00
trk := uint8 ( float32 ( ti . Track ) / TRACK_RESOLUTION ) // Resolution is ~1.4 degrees.
2015-08-15 03:10:16 +00:00
msg [ 17 ] = byte ( trk )
2015-08-23 02:45:54 +00:00
msg [ 18 ] = ti . emitter_category
2015-08-15 03:10:16 +00:00
2015-08-22 01:49:38 +00:00
// msg[19] to msg[26] are "call sign" (tail).
2015-09-24 20:26:51 +00:00
for i := 0 ; i < len ( ti . Tail ) && i < 8 ; i ++ {
c := byte ( ti . Tail [ i ] )
2015-08-23 02:45:54 +00:00
if c != 20 && ! ( ( c >= 48 ) && ( c <= 57 ) ) && ! ( ( c >= 65 ) && ( c <= 90 ) ) && c != 'e' && c != 'u' { // See p.24, FAA ref.
2015-08-22 01:49:38 +00:00
c = byte ( 20 )
}
2015-08-23 02:45:54 +00:00
msg [ 19 + i ] = c
2015-08-22 01:49:38 +00:00
}
2016-02-15 01:27:02 +00:00
return prepareMessage ( msg )
2015-08-15 03:10:16 +00:00
}
func parseDownlinkReport ( s string ) {
var ti TrafficInfo
s = s [ 1 : ]
frame := make ( [ ] byte , len ( s ) / 2 )
hex . Decode ( frame , [ ] byte ( s ) )
// Header.
2015-08-23 02:45:54 +00:00
msg_type := ( uint8 ( frame [ 0 ] ) >> 3 ) & 0x1f
// Extract emitter category.
if msg_type == 1 || msg_type == 3 {
v := ( uint16 ( frame [ 17 ] ) << 8 ) | ( uint16 ( frame [ 18 ] ) )
ti . emitter_category = uint8 ( ( v / 1600 ) % 40 )
}
2015-08-22 01:00:43 +00:00
icao_addr := ( uint32 ( frame [ 1 ] ) << 16 ) | ( uint32 ( frame [ 2 ] ) << 8 ) | uint32 ( frame [ 3 ] )
trafficMutex . Lock ( )
defer trafficMutex . Unlock ( )
if curTi , ok := traffic [ icao_addr ] ; ok { // Retrieve the current entry, as it may contain some useful information like "tail" from 1090ES.
ti = curTi
}
2015-09-24 20:26:51 +00:00
ti . Icao_addr = icao_addr
2015-08-22 01:00:43 +00:00
2015-08-15 03:10:16 +00:00
ti . addr_type = uint8 ( frame [ 0 ] ) & 0x07
// OK.
2015-09-24 20:26:51 +00:00
// fmt.Printf("%d, %d, %06X\n", msg_type, ti.addr_type, ti.Icao_addr)
2015-08-15 03:10:16 +00:00
nic := uint8 ( frame [ 11 ] ) & 15 //TODO: Meaning?
raw_lat := ( uint32 ( frame [ 4 ] ) << 15 ) | ( uint32 ( frame [ 5 ] ) << 7 ) | ( uint32 ( frame [ 6 ] ) >> 1 )
raw_lon := ( ( uint32 ( frame [ 6 ] ) & 0x01 ) << 23 ) | ( uint32 ( frame [ 7 ] ) << 15 ) | ( uint32 ( frame [ 8 ] ) << 7 ) | ( uint32 ( frame [ 9 ] ) >> 1 )
lat := float32 ( 0.0 )
lng := float32 ( 0.0 )
position_valid := false
if nic != 0 || raw_lat != 0 || raw_lon != 0 {
position_valid = true
lat = float32 ( raw_lat ) * 360.0 / 16777216.0
if lat > 90 {
lat = lat - 180
}
lng = float32 ( raw_lon ) * 360.0 / 16777216.0
if lng > 180 {
lng = lng - 360
}
}
2015-09-24 20:26:51 +00:00
ti . Lat = lat
ti . Lng = lng
ti . Position_valid = position_valid
2015-08-15 03:10:16 +00:00
2015-12-02 01:11:33 +00:00
raw_alt := ( int32 ( frame [ 10 ] ) << 4 ) | ( ( int32 ( frame [ 11 ] ) & 0xf0 ) >> 4 )
2015-08-20 17:06:40 +00:00
// alt_geo := false // Barometric if not geometric.
2015-12-02 01:11:33 +00:00
alt := int32 ( 0 )
2015-08-15 03:10:16 +00:00
if raw_alt != 0 {
2015-08-20 17:06:40 +00:00
// alt_geo = (uint8(frame[9]) & 1) != 0
2015-08-15 03:10:16 +00:00
alt = ( ( raw_alt - 1 ) * 25 ) - 1000
}
2015-09-24 20:26:51 +00:00
ti . Alt = alt
2015-08-15 03:10:16 +00:00
//OK.
2015-08-20 17:06:40 +00:00
// fmt.Printf("%d, %t, %f, %f, %t, %d\n", nic, position_valid, lat, lng, alt_geo, alt)
2015-08-15 03:10:16 +00:00
airground_state := ( uint8 ( frame [ 12 ] ) >> 6 ) & 0x03
//OK.
2015-08-20 17:06:40 +00:00
// fmt.Printf("%d\n", airground_state)
2015-08-15 03:10:16 +00:00
ns_vel := int16 ( 0 )
ew_vel := int16 ( 0 )
track := uint16 ( 0 )
speed_valid := false
speed := uint16 ( 0 )
vvel := int16 ( 0 )
2015-08-20 17:06:40 +00:00
// vvel_geo := false
2015-08-15 03:10:16 +00:00
if airground_state == 0 || airground_state == 1 { // Subsonic. Supersonic.
// N/S velocity.
ns_vel_valid := false
ew_vel_valid := false
raw_ns := ( ( int16 ( frame [ 12 ] ) & 0x1f ) << 6 ) | ( ( int16 ( frame [ 13 ] ) & 0xfc ) >> 2 )
if ( raw_ns & 0x3ff ) != 0 {
ns_vel_valid = true
2015-08-20 17:06:40 +00:00
ns_vel = ( ( raw_ns & 0x3ff ) - 1 )
if ( raw_ns & 0x400 ) != 0 {
ns_vel = 0 - ns_vel
2015-08-15 03:10:16 +00:00
}
2015-08-20 17:06:40 +00:00
if airground_state == 1 { // Supersonic.
ns_vel = ns_vel * 4
2015-08-15 03:10:16 +00:00
}
2015-08-20 17:06:40 +00:00
}
2015-08-15 03:10:16 +00:00
// E/W velocity.
raw_ew := ( ( int16 ( frame [ 13 ] ) & 0x03 ) << 9 ) | ( int16 ( frame [ 14 ] ) << 1 ) | ( ( int16 ( frame [ 15 ] & 0x80 ) ) >> 7 )
if ( raw_ew & 0x3ff ) != 0 {
ew_vel_valid = true
ew_vel = ( raw_ew & 0x3ff ) - 1
if ( raw_ew & 0x400 ) != 0 {
ew_vel = 0 - ew_vel
}
2015-08-23 17:22:49 +00:00
if airground_state == 1 { // Supersonic.
2015-08-15 03:10:16 +00:00
ew_vel = ew_vel * 4
}
}
if ns_vel_valid && ew_vel_valid {
if ns_vel != 0 && ew_vel != 0 {
//TODO: Track type
2015-09-11 16:29:42 +00:00
track = uint16 ( ( 360 + 90 - ( int16 ( math . Atan2 ( float64 ( ns_vel ) , float64 ( ew_vel ) ) * 180 / math . Pi ) ) ) % 360 )
2015-08-15 03:10:16 +00:00
}
speed_valid = true
speed = uint16 ( math . Sqrt ( float64 ( ( ns_vel * ns_vel ) + ( ew_vel * ew_vel ) ) ) )
}
// Vertical velocity.
raw_vvel := ( ( int16 ( frame [ 15 ] ) & 0x7f ) << 4 ) | ( ( int16 ( frame [ 16 ] ) & 0xf0 ) >> 4 )
if ( raw_vvel & 0x1ff ) != 0 {
2015-08-20 17:06:40 +00:00
// vvel_geo = (raw_vvel & 0x400) == 0
2015-08-15 03:10:16 +00:00
vvel = ( ( raw_vvel & 0x1ff ) - 1 ) * 64
if ( raw_vvel & 0x200 ) != 0 {
vvel = 0 - vvel
}
}
} else if airground_state == 2 { // Ground vehicle.
2015-11-06 03:02:58 +00:00
ti . OnGround = true
2015-08-23 02:36:02 +00:00
raw_gs := ( ( uint16 ( frame [ 12 ] ) & 0x1f ) << 6 ) | ( ( uint16 ( frame [ 13 ] ) & 0xfc ) >> 2 )
if raw_gs != 0 {
speed_valid = true
speed = ( ( raw_gs & 0x3ff ) - 1 )
}
raw_track := ( ( uint16 ( frame [ 13 ] ) & 0x03 ) << 9 ) | ( uint16 ( frame [ 14 ] ) << 1 ) | ( ( uint16 ( frame [ 15 ] ) & 0x80 ) >> 7 )
//tt := ((raw_track & 0x0600) >> 9)
//FIXME: tt == 1 TT_TRACK. tt == 2 TT_MAG_HEADING. tt == 3 TT_TRUE_HEADING.
track = uint16 ( ( raw_track & 0x1ff ) * 360 / 512 )
// Dimensions of vehicle - skip.
2015-08-15 03:10:16 +00:00
}
2015-09-24 20:26:51 +00:00
ti . Track = track
ti . Speed = speed
ti . Vvel = vvel
ti . Speed_valid = speed_valid
2015-08-15 03:10:16 +00:00
//OK.
2015-08-20 17:06:40 +00:00
// fmt.Printf("ns_vel %d, ew_vel %d, track %d, speed_valid %t, speed %d, vvel_geo %t, vvel %d\n", ns_vel, ew_vel, track, speed_valid, speed, vvel_geo, vvel)
2015-08-15 03:10:16 +00:00
2015-08-20 17:06:40 +00:00
/ *
utc_coupled := false
tisb_site_id := uint8 ( 0 )
2015-08-15 03:10:16 +00:00
2015-08-20 17:06:40 +00:00
if ( uint8 ( frame [ 0 ] ) & 7 ) == 2 || ( uint8 ( frame [ 0 ] ) & 7 ) == 3 { //TODO: Meaning?
tisb_site_id = uint8 ( frame [ 16 ] ) & 0x0f
} else {
utc_coupled = ( uint8 ( frame [ 16 ] ) & 0x08 ) != 0
}
* /
2015-08-15 03:10:16 +00:00
//OK.
2015-08-20 17:06:40 +00:00
// fmt.Printf("tisb_site_id %d, utc_coupled %t\n", tisb_site_id, utc_coupled)
2015-08-15 03:10:16 +00:00
2015-10-01 20:10:35 +00:00
ti . Last_source = TRAFFIC_SOURCE_UAT
2016-01-07 16:42:30 +00:00
ti . Last_seen = stratuxClock . Time
2015-08-15 03:10:16 +00:00
2015-09-04 03:28:47 +00:00
// Parse tail number, if available.
if msg_type == 1 || msg_type == 3 { // Need "MS" portion of message.
base40_alphabet := string ( "0123456789ABCDEFGHIJKLMNOPQRTSUVWXYZ .." )
tail := ""
v := ( uint16 ( frame [ 17 ] ) << 8 ) | uint16 ( frame [ 18 ] )
tail += string ( base40_alphabet [ ( v / 40 ) % 40 ] )
tail += string ( base40_alphabet [ v % 40 ] )
v = ( uint16 ( frame [ 19 ] ) << 8 ) | uint16 ( frame [ 20 ] )
tail += string ( base40_alphabet [ ( v / 1600 ) % 40 ] )
tail += string ( base40_alphabet [ ( v / 40 ) % 40 ] )
tail += string ( base40_alphabet [ v % 40 ] )
v = ( uint16 ( frame [ 21 ] ) << 8 ) | uint16 ( frame [ 22 ] )
tail += string ( base40_alphabet [ ( v / 1600 ) % 40 ] )
tail += string ( base40_alphabet [ ( v / 40 ) % 40 ] )
tail += string ( base40_alphabet [ v % 40 ] )
tail = strings . Trim ( tail , " " )
2015-09-24 20:26:51 +00:00
ti . Tail = tail
2015-09-04 03:28:47 +00:00
}
2015-09-04 03:35:26 +00:00
if globalSettings . DEBUG {
// This is a hack to show the source of the traffic in ForeFlight.
2015-09-24 20:26:51 +00:00
if len ( ti . Tail ) == 0 || ( len ( ti . Tail ) != 0 && len ( ti . Tail ) < 8 && ti . Tail [ 0 ] != 'U' ) {
ti . Tail = "u" + ti . Tail
2015-09-04 03:35:26 +00:00
}
2015-08-22 01:49:38 +00:00
}
2015-09-24 20:26:51 +00:00
traffic [ ti . Icao_addr ] = ti
2015-09-30 17:14:48 +00:00
registerTrafficUpdate ( ti )
2015-09-24 20:26:51 +00:00
seenTraffic [ ti . Icao_addr ] = true // Mark as seen.
2015-08-20 17:06:40 +00:00
}
2015-08-22 01:00:43 +00:00
func esListen ( ) {
for {
if ! globalSettings . ES_Enabled {
time . Sleep ( 1 * time . Second ) // Don't do much unless ES is actually enabled.
continue
}
dump1090Addr := "127.0.0.1:30003"
inConn , err := net . Dial ( "tcp" , dump1090Addr )
if err != nil { // Local connection failed.
time . Sleep ( 1 * time . Second )
continue
}
rdr := bufio . NewReader ( inConn )
2015-08-31 22:21:08 +00:00
for globalSettings . ES_Enabled {
2015-08-22 01:00:43 +00:00
buf , err := rdr . ReadString ( '\n' )
if err != nil { // Must have disconnected?
break
}
buf = strings . Trim ( buf , "\r\n" )
//log.Printf("%s\n", buf)
2015-09-05 17:46:55 +00:00
replayLog ( buf , MSGCLASS_ES ) // Log the raw message.
2015-08-22 01:00:43 +00:00
x := strings . Split ( buf , "," )
if len ( x ) < 22 {
continue
}
icao := x [ 4 ]
icaoDecf , err := strconv . ParseInt ( icao , 16 , 32 )
if err != nil {
continue
}
2015-08-25 19:31:13 +00:00
// Log the message after we've determined that it at least meets some requirements on the fields.
var thisMsg msg
thisMsg . MessageClass = MSGCLASS_ES
2016-01-07 16:37:57 +00:00
thisMsg . TimeReceived = stratuxClock . Time
2015-08-25 19:31:13 +00:00
thisMsg . Data = [ ] byte ( buf )
MsgLog = append ( MsgLog , thisMsg )
// Begin to parse the message.
2015-08-22 01:00:43 +00:00
icaoDec := uint32 ( icaoDecf )
trafficMutex . Lock ( )
// Retrieve previous information on this ICAO code.
var ti TrafficInfo
if val , ok := traffic [ icaoDec ] ; ok {
ti = val
}
2015-08-20 17:06:40 +00:00
2015-09-24 20:26:51 +00:00
ti . Icao_addr = icaoDec
2015-08-22 01:00:43 +00:00
//FIXME: Some stale information will be renewed.
valid_change := true
2016-01-03 18:46:26 +00:00
if x [ 1 ] == "3" { // ES airborne position message. DF17 BDS 0,5.
2015-08-22 01:00:43 +00:00
//MSG,3,111,11111,AC2BB7,111111,2015/07/28,03:59:12.363,2015/07/28,03:59:12.353,,5550,,,42.35847,-83.42212,,,,,,0
//MSG,3,111,11111,A5D007,111111, , , , ,,35000,,,42.47454,-82.57433,,,0,0,0,0
alt := x [ 11 ]
lat := x [ 14 ]
lng := x [ 15 ]
if len ( alt ) == 0 || len ( lat ) == 0 || len ( lng ) == 0 { //FIXME.
valid_change = false
}
altFloat , err := strconv . ParseFloat ( alt , 32 )
if err != nil {
2015-09-22 13:52:49 +00:00
// log.Printf("err parsing alt (%s): %s\n", alt, err.Error())
2015-08-22 01:00:43 +00:00
valid_change = false
}
latFloat , err := strconv . ParseFloat ( lat , 32 )
if err != nil {
2015-09-22 13:52:49 +00:00
// log.Printf("err parsing lat (%s): %s\n", lat, err.Error())
2015-08-22 01:00:43 +00:00
valid_change = false
}
lngFloat , err := strconv . ParseFloat ( lng , 32 )
if err != nil {
2015-09-22 13:52:49 +00:00
// log.Printf("err parsing lng (%s): %s\n", lng, err.Error())
2015-08-22 01:00:43 +00:00
valid_change = false
}
//log.Printf("icao=%s, icaoDec=%d, alt=%s, lat=%s, lng=%s\n", icao, icaoDec, alt, lat, lng)
if valid_change {
2015-12-02 01:11:33 +00:00
ti . Alt = int32 ( altFloat )
2015-09-24 20:26:51 +00:00
ti . Lat = float32 ( latFloat )
ti . Lng = float32 ( lngFloat )
ti . Position_valid = true
2015-08-22 01:00:43 +00:00
}
}
2016-01-03 18:46:26 +00:00
if x [ 1 ] == "4" { // ES airborne velocity message. DF17 BDS 0,9.
2015-08-22 01:00:43 +00:00
// MSG,4,111,11111,A3B557,111111,2015/07/28,06:13:36.417,2015/07/28,06:13:36.398,,,414,278,,,-64,,,,,0
2016-01-04 03:54:21 +00:00
// MSG,4,111,11111,ABE287,111111,2016/01/03,19:44:43.440,2016/01/03,19:44:43.401,,,469,88,,,0,,,,,0
2015-08-22 01:00:43 +00:00
speed := x [ 12 ]
track := x [ 13 ]
vvel := x [ 16 ]
if len ( speed ) == 0 || len ( track ) == 0 || len ( vvel ) == 0 {
valid_change = false
}
speedFloat , err := strconv . ParseFloat ( speed , 32 )
if err != nil {
2015-09-22 13:52:49 +00:00
// log.Printf("err parsing speed (%s): %s\n", speed, err.Error())
2015-08-22 01:00:43 +00:00
valid_change = false
}
trackFloat , err := strconv . ParseFloat ( track , 32 )
if err != nil {
2015-09-22 13:52:49 +00:00
// log.Printf("err parsing track (%s): %s\n", track, err.Error())
2015-08-22 01:00:43 +00:00
valid_change = false
}
vvelFloat , err := strconv . ParseFloat ( vvel , 32 )
if err != nil {
2015-09-22 13:52:49 +00:00
// log.Printf("err parsing vvel (%s): %s\n", vvel, err.Error())
2015-08-22 01:00:43 +00:00
valid_change = false
}
//log.Printf("icao=%s, icaoDec=%d, vel=%s, hdg=%s, vr=%s\n", icao, icaoDec, vel, hdg, vr)
if valid_change {
2015-09-24 20:26:51 +00:00
ti . Speed = uint16 ( speedFloat )
ti . Track = uint16 ( trackFloat )
ti . Vvel = int16 ( vvelFloat )
ti . Speed_valid = true
2015-08-22 01:00:43 +00:00
}
}
2016-01-03 18:46:26 +00:00
if x [ 1 ] == "1" { // ES identification and category. DF17 BDS 0,8.
2015-08-22 01:00:43 +00:00
// MSG,1,,,%02X%02X%02X,,,,,,%s,,,,,,,,0,0,0,0
tail := x [ 10 ]
2015-08-20 17:06:40 +00:00
2015-08-22 01:00:43 +00:00
if len ( tail ) == 0 {
valid_change = false
}
2015-08-20 17:06:40 +00:00
2015-08-22 01:00:43 +00:00
if valid_change {
2015-09-24 20:26:51 +00:00
ti . Tail = tail
2015-08-22 01:00:43 +00:00
}
}
2016-01-03 18:52:33 +00:00
if x [ 1 ] == "5" { // Surveillance alt message. DF4, DF20.
// MSG,5,,,%02X%02X%02X,,,,,,,%d,,,,,,,%d,%d,%d,%d
// MSG,5,111,11111,AB5F1B,111111,2016/01/03,04:43:52.028,2016/01/03,04:43:52.006,,13050,,,,,,,0,,0,0
alt := x [ 11 ]
altFloat , err := strconv . ParseFloat ( alt , 32 )
if len ( alt ) != 0 && err == nil {
ti . Alt = int32 ( altFloat )
}
}
2015-08-20 17:06:40 +00:00
2015-08-22 01:00:43 +00:00
// Update "last seen" (any type of message, as long as the ICAO addr can be parsed).
2015-10-01 20:10:35 +00:00
ti . Last_source = TRAFFIC_SOURCE_1090ES
2016-01-07 16:42:30 +00:00
ti . Last_seen = stratuxClock . Time
2015-08-22 01:00:43 +00:00
2015-08-23 02:45:54 +00:00
ti . addr_type = 0 //FIXME: ADS-B with ICAO address. Not recognized by ForeFlight.
ti . emitter_category = 0x01 //FIXME. "Light"
2015-08-22 01:49:38 +00:00
// This is a hack to show the source of the traffic in ForeFlight.
2015-09-24 20:26:51 +00:00
ti . Tail = strings . Trim ( ti . Tail , " " )
2015-09-04 03:35:26 +00:00
if globalSettings . DEBUG {
2015-09-24 20:26:51 +00:00
if len ( ti . Tail ) == 0 || ( len ( ti . Tail ) != 0 && len ( ti . Tail ) < 8 && ti . Tail [ 0 ] != 'E' ) {
ti . Tail = "e" + ti . Tail
2015-09-04 03:35:26 +00:00
}
2015-08-22 01:49:38 +00:00
}
2015-08-22 01:00:43 +00:00
traffic [ icaoDec ] = ti // Update information on this ICAO code.
2015-09-30 17:14:48 +00:00
registerTrafficUpdate ( ti )
2015-09-22 13:52:49 +00:00
seenTraffic [ icaoDec ] = true // Mark as seen.
2015-08-22 01:00:43 +00:00
trafficMutex . Unlock ( )
}
}
}
2016-02-15 01:27:02 +00:00
/ *
updateDemoTraffic creates / updates a simulated traffic target for demonstration / debugging
purpose . Target will circle clockwise around the current GPS position ( if valid ) or around
KOSH , once every five minutes .
Inputs are ICAO 24 - bit hex code , tail number ( 8 chars max ) , relative altitude in feet ,
groundspeed in knots , and bearing offset from 0 deg initial position .
* /
func updateDemoTraffic ( icao uint32 , tail string , relAlt float32 , gs float64 , offset float64 ) {
var ti TrafficInfo
hdg := float64 ( ( stratuxClock . Milliseconds / 1000 ) % 360 ) + offset
// gs := float64(220) // knots
radius := gs * 0.1 / ( 2 * math . Pi )
x := radius * math . Cos ( hdg * math . Pi / 180.0 )
y := radius * math . Sin ( hdg * math . Pi / 180.0 )
// default traffic location is Oshkosh if GPS not detected
lat := 43.99
lng := - 88.56
if isGPSValid ( ) {
lat = float64 ( mySituation . Lat )
lng = float64 ( mySituation . Lng )
}
traffRelLat := y / 60
traffRelLng := - x / ( 60 * math . Cos ( lat * math . Pi / 180.0 ) )
ti . Icao_addr = icao
ti . OnGround = false
ti . addr_type = 0
ti . emitter_category = 1
ti . Lat = float32 ( lat + traffRelLat )
ti . Lng = float32 ( lng + traffRelLng )
ti . Position_valid = true
ti . Alt = int32 ( mySituation . Alt + relAlt )
ti . Track = uint16 ( hdg )
ti . Speed = uint16 ( gs )
ti . Speed_valid = true
ti . Vvel = 0
ti . Tail = tail // "DEMO1234"
ti . Last_seen = stratuxClock . Time
ti . Last_source = 1
// now insert this into the traffic map...
trafficMutex . Lock ( )
defer trafficMutex . Unlock ( )
traffic [ ti . Icao_addr ] = ti
registerTrafficUpdate ( ti )
seenTraffic [ ti . Icao_addr ] = true
}
2015-08-22 01:00:43 +00:00
func initTraffic ( ) {
traffic = make ( map [ uint32 ] TrafficInfo )
2015-09-22 23:51:37 +00:00
seenTraffic = make ( map [ uint32 ] bool )
2015-08-22 01:00:43 +00:00
trafficMutex = & sync . Mutex { }
go esListen ( )
}