Merge branch 'master' into rework

pull/13/head^2
Joseph Poirier 2015-08-15 12:30:46 -05:00
commit 83dcc24787
6 zmienionych plików z 676 dodań i 20 usunięć

26
LICENSE 100644
Wyświetl plik

@ -0,0 +1,26 @@
Copyright (c) 2015 Christopher Young.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Joseph D Poirier nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

4
Makefile 100644
Wyświetl plik

@ -0,0 +1,4 @@
all:
GOARCH=6 go-linux-arm build gen_gdl90.go traffic.go ry835ai.go
clean:
rm -f gen_gdl90

Wyświetl plik

@ -15,7 +15,7 @@ import (
const (
stratuxVersion = "v0.1"
configLocation = "stratux.conf"
configLocation = "/etc/stratux.conf"
ipadAddr = "192.168.10.255:4000" // Port 4000 for FreeFlight RANGR.
maxDatagramSize = 8192
UPLINK_BLOCK_DATA_BITS = 576
@ -39,11 +39,16 @@ const (
MSGCLASS_UAT = 0
MSGCLASS_ES = 1
LON_LAT_RESOLUTION = float32(180.0 / 8388608.0)
TRACK_RESOLUTION = float32(360.0 / 256.0)
)
var Crc16Table [256]uint16
var outConn *net.UDPConn
var myGPS GPSData
type msg struct {
MessageClass uint
TimeReceived time.Time
@ -103,18 +108,143 @@ func prepareMessage(data []byte) []byte {
return tmp
}
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 isGPSValid() bool {
return time.Since(myGPS.lastFixLocalTime).Seconds() < 15
}
func isGPSGroundTrackValid() bool {
return time.Since(myGPS.lastGroundTrackTime).Seconds() < 15
}
//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.
tmp := makeLatLng(myGPS.lat)
msg[5] = tmp[0] // Latitude.
msg[6] = tmp[1] // Latitude.
msg[7] = tmp[2] // Latitude.
tmp = makeLatLng(myGPS.lng)
msg[8] = tmp[0] // Longitude.
msg[9] = tmp[1] // Longitude.
msg[10] = tmp[2] // Longitude.
//TODO: 0xFFF "invalid altitude."
//FIXME: This is **PRESSURE ALTITUDE**
alt := uint16(myGPS.alt)
alt = (alt + 1000)/25
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() {
gdSpeed = myGPS.groundSpeed
}
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.
msg[15] = msg[15] | byte((verticalVelocity & 0x0F00) >> 8)
msg[16] = byte(verticalVelocity & 0xFF)
// Showing magnetic (corrected) on ForeFlight. Needs to be True Heading.
groundTrack := uint16(0)
if isGPSGroundTrackValid() {
groundTrack = myGPS.trueCourse
}
trk := uint8(float32(groundTrack) / TRACK_RESOLUTION) // Resolution is ~1.4 degrees.
msg[17] = byte(trk)
msg[18] = 0x01 // "Light (ICAO) < 15,500 lbs"
outConn.Write(prepareMessage(msg))
return true
}
//TODO
func makeOwnshipGeometricAltitudeReport() bool {
if !isGPSValid() {
return false
}
msg := make([]byte, 5)
// See p.28.
msg[0] = 0x0B // Message type "Ownship Geo Alt".
alt := int16(myGPS.alt)
alt = alt/5
msg[1] = byte(alt >> 8) // Altitude.
msg[2] = byte(alt & 0x00FF) // Altitude.
//TODO: "Figure of Merit". 0x7FFF "Not available".
msg[3] = 0x00
msg[4] = 0x0A
outConn.Write(prepareMessage(msg))
return true
}
func makeInitializationMessage() []byte {
msg := make([]byte, 3)
// See p.13.
msg[0] = 0x02 // Message type "Initialization".
msg[1] = 0x00 //TODO
msg[2] = 0x00 //TODO
return prepareMessage(msg)
}
func makeHeartbeat() []byte {
msg := make([]byte, 7)
// See p.10.
msg[0] = 0x00 // Message type "Heartbeat".
msg[1] = 0x01 // "UAT Initialized".
msg[1] = 0x01 // "UAT Initialized". //FIXME
msg[1] = 0x91 //FIXME: GPS valid. Addr talkback.
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())
msg[2] = byte((secondsSinceMidnightUTC >> 16) << 7)
msg[2] = byte(((secondsSinceMidnightUTC >> 16) << 7) | 0x1) // UTC OK.
msg[3] = byte((secondsSinceMidnightUTC & 0xFF))
msg[4] = byte((secondsSinceMidnightUTC & 0xFFFF) >> 8)
@ -143,6 +273,12 @@ func relayMessage(msgtype uint16, msg []byte) {
func heartBeatSender() {
for {
outConn.Write(makeHeartbeat())
// outConn.Write(makeTrafficReport())
makeOwnshipReport()
makeOwnshipGeometricAltitudeReport()
outConn.Write(makeInitializationMessage())
sendTrafficUpdates()
updateStatus()
time.Sleep(1 * time.Second)
}
}
@ -165,8 +301,13 @@ func updateStatus() {
MsgLog = t
globalStatus.UAT_messages_last_minute = UAT_messages_last_minute
globalStatus.ES_messages_last_minute = ES_messages_last_minute
if isGPSValid() {
globalStatus.GPS_satellites_locked = myGPS.satellites
}
}
func parseInput(buf string) ([]byte, uint16) {
x := strings.Split(buf, ";") // Discard everything after the first ';'.
if len(x) == 0 {
@ -178,6 +319,10 @@ func parseInput(buf string) ([]byte, uint16) {
}
msgtype := uint16(0)
if s[0] == '-' {
parseDownlinkReport(s)
}
s = s[1:]
msglen := len(s) / 2
@ -208,7 +353,6 @@ func parseInput(buf string) ([]byte, uint16) {
thisMsg.TimeReceived = time.Now()
thisMsg.Data = frame
MsgLog = append(MsgLog, thisMsg)
updateStatus()
return frame, msgtype
}
@ -216,6 +360,7 @@ func parseInput(buf string) ([]byte, uint16) {
type settings struct {
UAT_Enabled bool
ES_Enabled bool
GPS_Enabled bool
}
type status struct {
@ -225,7 +370,7 @@ type status struct {
UAT_messages_max uint
ES_messages_last_minute uint
ES_messages_max uint
GPS_satellites_locked uint
GPS_satellites_locked uint16
}
var globalSettings settings
@ -257,6 +402,9 @@ func handleManagementConnection(conn net.Conn) {
fmt.Printf("%s - error: %s\n", s, err.Error())
} else {
fmt.Printf("new settings: %s\n", s)
if !globalSettings.GPS_Enabled && newSettings.GPS_Enabled { // GPS was enabled, restart the reader thread.
go gpsReader()
}
globalSettings = newSettings
saveSettings()
}
@ -284,6 +432,7 @@ func managementInterface() {
func defaultSettings() {
globalSettings.UAT_Enabled = true //TODO
globalSettings.ES_Enabled = false //TODO
globalSettings.GPS_Enabled = false //TODO
}
func readSettings() {
@ -326,6 +475,10 @@ func saveSettings() {
func main() {
MsgLog = make([]msg, 0)
crcInit() // Initialize CRC16 table.
initTraffic()
globalStatus.Version = stratuxVersion
globalStatus.Devices = 123 //TODO
globalStatus.UAT_messages_last_minute = 567 //TODO
@ -333,7 +486,9 @@ func main() {
readSettings()
crcInit() // Initialize CRC16 table.
if globalSettings.GPS_Enabled {
go gpsReader()
}
// Open UDP port to send out the messages.
addr, err := net.ResolveUDPAddr("udp", ipadAddr)

Wyświetl plik

@ -67,10 +67,6 @@ echo blacklist rtl2832 >>/etc/modprobe.d/rtl-sdr-blacklist.conf
echo "# prevent power down of wireless when idle" >>/etc/modprobe.d/8192cu.conf
echo "options 8192cu rtw_power_mgnt=0 rtw_enusbss=0" >>/etc/modprobe.d/8192cu.conf
#usb hub power
echo "max_usb_current=1" >>/boot/config.txt
EOF
echo "*** STRATUX COMPILE/PACKAGE INSTALL ***"
@ -103,14 +99,12 @@ cp dump1090 /usr/bin/
EOF
echo " - Stratux"
scp_in_to_qemu /root/spindle/gen_gdl90 /tmp/gen_gdl90
ssh_in_to_qemu chroot /mnt sh -l -ex - <<\EOF
apt-get install -y golang-go
cd /root
mkdir /root/go
export GOPATH=/root/go
rm -rf stratux
git clone https://github.com/cyoung/stratux
cd stratux
@ -118,12 +112,9 @@ cd dump978
make
cp dump978 /usr/bin/
cd ..
go build gen_gdl90.go
go get github.com/sevlyar/go-daemon
go build 1090es_relay.go
cp gen_gdl90 /usr/bin/
mv /tmp/gen_gdl90 /usr/bin/gen_gdl90
chmod +x /usr/bin/gen_gdl90
cp start_uat.sh /usr/bin/start_uat
cp init.d-stratux /etc/init.d/stratux
cp start_stratux.sh /usr/sbin/stratux
@ -132,7 +123,15 @@ chmod 755 /usr/sbin/stratux
chmod 755 /etc/init.d/stratux
ln -s /etc/init.d/stratux /etc/rc2.d/S01stratux
ln -s /etc/init.d/stratux /etc/rc6.d/K01stratux
echo -n '{"UAT_Enabled":false,"ES_Enabled":true,"GPS_Enabled":true}' >/etc/stratux.conf
update-rc.d stratux enable
#usb hub power
echo "max_usb_current=1" >>/boot/config.txt
EOF
echo "**** END STRATUX SETUP *****"

186
ry835ai.go 100644
Wyświetl plik

@ -0,0 +1,186 @@
package main
import (
"fmt"
"github.com/tarm/serial"
"time"
"strings"
"strconv"
)
type GPSData struct {
lastFixSinceMidnightUTC uint32
lat float32
lng float32
quality uint8
satellites uint16
accuracy float32 // Meters.
alt float32 // Feet.
alt_accuracy float32
lastFixLocalTime time.Time
trueCourse uint16
groundSpeed uint16
lastGroundTrackTime time.Time
}
var serialConfig *serial.Config
var serialPort *serial.Port
func initGPSSerialReader() bool {
serialConfig = &serial.Config{Name: "/dev/ttyACM0", Baud: 9600}
p, err := serial.OpenPort(serialConfig)
if err != nil {
fmt.Printf("serial port err: %s\n", err.Error())
return false
}
serialPort = p
return true
}
func processNMEALine(l string) bool {
x := strings.Split(l, ",")
if x[0] == "$GNVTG" { // Ground track information.
if len(x) < 10 {
return false
}
trueCourse := uint16(0)
if len(x[1]) > 0 {
tc, err := strconv.ParseFloat(x[1], 32)
if err != nil {
return false
}
trueCourse = uint16(tc)
} else {
// No movement.
myGPS.trueCourse = 0
myGPS.groundSpeed = 0
myGPS.lastGroundTrackTime = time.Time{}
return true
}
groundSpeed, err := strconv.ParseFloat(x[5], 32) // Knots.
if err != nil {
return false
}
myGPS.trueCourse = uint16(trueCourse)
myGPS.groundSpeed = uint16(groundSpeed)
myGPS.lastGroundTrackTime = time.Now()
} else if x[0] == "$GNGGA" { // GPS fix.
if len(x) < 15 {
return false
}
var fix GPSData
fix = myGPS
// Timestamp.
if len(x[1]) < 9 {
return false
}
hr, err1 := strconv.Atoi(x[1][0:2])
min, err2 := strconv.Atoi(x[1][2:4])
sec, err3 := strconv.Atoi(x[1][4:6])
if err1 != nil || err2 != nil || err3 != nil {
return false
}
fix.lastFixSinceMidnightUTC = uint32((hr * 60 * 60) + (min * 60) + sec)
// Latitude.
if len(x[2]) < 10 {
return false
}
hr, err1 = strconv.Atoi(x[2][0:2])
minf, err2 := strconv.ParseFloat(x[2][2:10], 32)
if err1 != nil || err2 != nil {
return false
}
fix.lat = float32(hr) + float32(minf / 60.0)
if x[3] == "S" { // South = negative.
fix.lat = -fix.lat
}
// Longitude.
if len(x[4]) < 11 {
return false
}
hr, err1 = strconv.Atoi(x[4][0:3])
minf, err2 = strconv.ParseFloat(x[4][3:11], 32)
if err1 != nil || err2 != nil {
return false
}
fix.lng = float32(hr) + float32(minf / 60.0)
if x[5] == "W" { // West = negative.
fix.lng = -fix.lng
}
// Quality indicator.
q, err1 := strconv.Atoi(x[6])
if err1 != nil {
return false
}
fix.quality = uint8(q)
// Satellites.
sat, err1 := strconv.Atoi(x[7])
if err1 != nil {
return false
}
fix.satellites = uint16(sat)
// Accuracy.
hdop, err1 := strconv.ParseFloat(x[8], 32)
if err1 != nil {
return false
}
fix.accuracy = float32(hdop * 5.0) //FIXME: 5 meters ~ 1.0 HDOP?
// Altitude.
alt, err1 := strconv.ParseFloat(x[9], 32)
if err1 != nil {
return false
}
fix.alt = float32(alt * 3.28084) // Covnert to feet.
//TODO: Altitude accuracy.
fix.alt_accuracy = 0
// Timestamp.
fix.lastFixLocalTime = time.Now()
myGPS = fix
}
return true
}
func gpsSerialReader() {
defer serialPort.Close()
for {
if !globalSettings.GPS_Enabled { // GPS was turned off. Shut down.
break
}
buf := make([]byte, 1024)
n, err := serialPort.Read(buf)
if err != nil {
fmt.Printf("gps unit read error: %s\n", err.Error())
return
}
s := string(buf[:n])
x := strings.Split(s, "\n")
for _, l := range x {
processNMEALine(l)
}
}
}
func gpsReader() {
if initGPSSerialReader() {
gpsSerialReader()
} else {
globalSettings.GPS_Enabled = false
}
}

286
traffic.go 100644
Wyświetl plik

@ -0,0 +1,286 @@
package main
import (
"encoding/hex"
"math"
"time"
)
//-0b2b48fe3aef1f88621a0856110a31c01105c4e6c4e6c40a9a820300000000000000;rs=7;
/*
HDR:
MDB Type: 1
Address: 2B48FE (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
Capabilities:
Active modes:
Target track type: true heading
AUXSV:
Sec. altitude: unavailable
*/
type TrafficInfo struct {
icao_addr uint32
addr_type uint8
lat float32
lng float32
position_valid bool
alt uint32
track uint16
speed uint16
speed_valid bool
vvel int16
tail string
last_seen time.Time
}
var traffic map[uint32]TrafficInfo
func cleanupOldEntries() {
for icao_addr, ti := range traffic {
if time.Since(ti.last_seen).Seconds() > float64(60) { //FIXME: 60 seconds with no update on this address - stop displaying.
delete(traffic, icao_addr)
}
}
}
func sendTrafficUpdates() {
cleanupOldEntries()
for _, ti := range traffic {
makeTrafficReport(ti)
}
}
func initTraffic() {
traffic = make(map[uint32]TrafficInfo)
}
func makeTrafficReport(ti TrafficInfo) {
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.
msg[2] = byte((ti.icao_addr & 0x00FF0000) >> 16)
msg[3] = byte((ti.icao_addr & 0x0000FF00) >> 8)
msg[4] = byte((ti.icao_addr & 0x000000FF))
lat := float32(ti.lat)
tmp := makeLatLng(lat)
msg[5] = tmp[0] // Latitude.
msg[6] = tmp[1] // Latitude.
msg[7] = tmp[2] // Latitude.
lng := float32(ti.lng)
tmp = makeLatLng(lng)
msg[8] = tmp[0] // Longitude.
msg[9] = tmp[1] // Longitude.
msg[10] = tmp[2] // Longitude.
//Altitude: OK
//TODO: 0xFFF "invalid altitude."
alt := uint16(ti.alt)
alt = (alt + 1000)/25
alt = alt & 0xFFF // Should fit in 12 bits.
msg[11] = byte((alt & 0xFF0) >> 4) // Altitude.
msg[12] = byte((alt & 0x00F) << 4)
msg[12] = byte(((alt & 0x00F) << 4) | 0x8) //FIXME. "Airborne".
msg[13] = 0x11 //FIXME.
// Horizontal velocity (speed).
msg[14] = byte((ti.speed & 0x0FF0) >> 4)
msg[15] = byte((ti.speed & 0x000F) << 4)
// Vertical velocity.
vvel := ti.vvel / 64 // 64fpm resolution.
msg[15] = msg[15] | byte((vvel & 0x0F00) >> 8)
msg[16] = byte(vvel & 0x00FF)
// Track.
trk := uint8(float32(ti.track) / TRACK_RESOLUTION) // Resolution is ~1.4 degrees.
msg[17] = byte(trk)
msg[18] = 0x01 // "light"
outConn.Write(prepareMessage(msg))
}
func parseDownlinkReport(s string) {
var ti TrafficInfo
s = s[1:]
frame := make([]byte, len(s)/2)
hex.Decode(frame, []byte(s))
// Header.
// msg_type := (uint8(frame[0]) >> 3) & 0x1f
ti.addr_type = uint8(frame[0]) & 0x07
ti.icao_addr = (uint32(frame[1]) << 16) | (uint32(frame[2]) << 8) | uint32(frame[3])
// OK.
// fmt.Printf("%d, %d, %06X\n", msg_type, ti.addr_type, ti.icao_addr)
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
}
}
ti.lat = lat
ti.lng = lng
ti.position_valid = position_valid
raw_alt := (uint32(frame[10]) << 4) | ((uint32(frame[11]) & 0xf0) >> 4)
// alt_geo := false // Barometric if not geometric.
alt := uint32(0)
if raw_alt != 0 {
// alt_geo = (uint8(frame[9]) & 1) != 0
alt = ((raw_alt - 1) * 25) - 1000
}
ti.alt = alt
//OK.
// fmt.Printf("%d, %t, %f, %f, %t, %d\n", nic, position_valid, lat, lng, alt_geo, alt)
airground_state := (uint8(frame[12]) >> 6) & 0x03
//OK.
// fmt.Printf("%d\n", airground_state)
ns_vel := int16(0)
ew_vel := int16(0)
track := uint16(0)
speed_valid := false
speed := uint16(0)
vvel := int16(0)
// vvel_geo := false
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
ns_vel = ((raw_ns & 0x3ff) - 1)
if (raw_ns & 0x400) != 0 {
ns_vel = 0 - ns_vel
}
if airground_state == 1 { // Supersonic.
ns_vel = ns_vel * 4
}
}
// 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
}
if airground_state == 1 { // Supersonic
ew_vel = ew_vel * 4
}
}
if ns_vel_valid && ew_vel_valid {
if ns_vel != 0 && ew_vel != 0 {
//TODO: Track type
track = (360 + 90 - uint16(math.Atan2(float64(ns_vel), float64(ew_vel)) * 180 / math.Pi)) % 360
}
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 {
// vvel_geo = (raw_vvel & 0x400) == 0
vvel = ((raw_vvel & 0x1ff) - 1) * 64
if (raw_vvel & 0x200) != 0 {
vvel = 0 - vvel
}
}
} else if airground_state == 2 { // Ground vehicle.
//TODO.
return
}
ti.track = track
ti.speed = speed
ti.vvel = vvel
ti.speed_valid = speed_valid
//OK.
// 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)
/*
utc_coupled := false
tisb_site_id := uint8(0)
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
}
*/
//OK.
// fmt.Printf("tisb_site_id %d, utc_coupled %t\n", tisb_site_id, utc_coupled)
ti.last_seen = time.Now()
traffic[ti.icao_addr] = ti
}