kopia lustrzana https://github.com/cyoung/stratux
Merge pull request #6 from cyoung/master
Pull latest cyoung commits including SX heartbeatpull/169/head
commit
1e9d3ae747
|
@ -5,6 +5,7 @@ import (
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -75,12 +76,24 @@ var Crc16Table [256]uint16
|
||||||
// Current AHRS, pressure altitude, etc.
|
// Current AHRS, pressure altitude, etc.
|
||||||
var mySituation SituationData
|
var mySituation SituationData
|
||||||
|
|
||||||
|
type WriteCloser interface {
|
||||||
|
io.Writer
|
||||||
|
io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReadCloser interface {
|
||||||
|
io.Reader
|
||||||
|
io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
// File handles for replay logging.
|
// File handles for replay logging.
|
||||||
var uatReplayWriter *gzip.Writer
|
var uatReplayWriter WriteCloser
|
||||||
var esReplayWriter *gzip.Writer
|
var esReplayWriter WriteCloser
|
||||||
var gpsReplayWriter *gzip.Writer
|
var gpsReplayWriter WriteCloser
|
||||||
var ahrsReplayWriter *gzip.Writer
|
var ahrsReplayWriter WriteCloser
|
||||||
var dump1090ReplayWriter *gzip.Writer
|
var dump1090ReplayWriter WriteCloser
|
||||||
|
|
||||||
|
var developerMode bool
|
||||||
|
|
||||||
type msg struct {
|
type msg struct {
|
||||||
MessageClass uint
|
MessageClass uint
|
||||||
|
@ -107,15 +120,9 @@ type ADSBTower struct {
|
||||||
Messages_total uint64
|
Messages_total uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var ADSBTowers map[string]ADSBTower // Running list of all towers seen. (lat,lng) -> ADSBTower
|
var ADSBTowers map[string]ADSBTower // Running list of all towers seen. (lat,lng) -> ADSBTower
|
||||||
|
|
||||||
func constructFilenames() {
|
func constructFilenames() {
|
||||||
// uatReplayLog = "/var/log/stratux-uat.log"
|
|
||||||
// esReplayLog = "/var/log/stratux-es.log"
|
|
||||||
// gpsReplayLog = "/var/log/stratux-gps.log"
|
|
||||||
// ahrsReplayLog = "/var/log/stratux-ahrs.log"
|
|
||||||
// dump1090ReplayLog = "/var/log/stratux-dump1090.log"
|
|
||||||
var fileIndexNumber uint
|
var fileIndexNumber uint
|
||||||
|
|
||||||
// First, create the log file directory if it does not exist
|
// First, create the log file directory if it does not exist
|
||||||
|
@ -143,12 +150,20 @@ func constructFilenames() {
|
||||||
}
|
}
|
||||||
fo.Sync()
|
fo.Sync()
|
||||||
fo.Close()
|
fo.Close()
|
||||||
|
if developerMode == true {
|
||||||
|
uatReplayLog = fmt.Sprintf("%s/%04d-uat.log", logDirectory, fileIndexNumber)
|
||||||
|
esReplayLog = fmt.Sprintf("%s/%04d-es.log", logDirectory, fileIndexNumber)
|
||||||
|
gpsReplayLog = fmt.Sprintf("%s/%04d-gps.log", logDirectory, fileIndexNumber)
|
||||||
|
ahrsReplayLog = fmt.Sprintf("%s/%04d-ahrs.log", logDirectory, fileIndexNumber)
|
||||||
|
dump1090ReplayLog = fmt.Sprintf("%s/%04d-dump1090.log", logDirectory, fileIndexNumber)
|
||||||
|
} else {
|
||||||
uatReplayLog = fmt.Sprintf("%s/%04d-uat.log.gz", logDirectory, fileIndexNumber)
|
uatReplayLog = fmt.Sprintf("%s/%04d-uat.log.gz", logDirectory, fileIndexNumber)
|
||||||
esReplayLog = fmt.Sprintf("%s/%04d-es.log.gz", logDirectory, fileIndexNumber)
|
esReplayLog = fmt.Sprintf("%s/%04d-es.log.gz", logDirectory, fileIndexNumber)
|
||||||
gpsReplayLog = fmt.Sprintf("%s/%04d-gps.log.gz", logDirectory, fileIndexNumber)
|
gpsReplayLog = fmt.Sprintf("%s/%04d-gps.log.gz", logDirectory, fileIndexNumber)
|
||||||
ahrsReplayLog = fmt.Sprintf("%s/%04d-ahrs.log.gz", logDirectory, fileIndexNumber)
|
ahrsReplayLog = fmt.Sprintf("%s/%04d-ahrs.log.gz", logDirectory, fileIndexNumber)
|
||||||
dump1090ReplayLog = fmt.Sprintf("%s/%04d-dump1090.log.gz", logDirectory, fileIndexNumber)
|
dump1090ReplayLog = fmt.Sprintf("%s/%04d-dump1090.log.gz", logDirectory, fileIndexNumber)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Construct the CRC table. Adapted from FAA ref above.
|
// Construct the CRC table. Adapted from FAA ref above.
|
||||||
func crcInit() {
|
func crcInit() {
|
||||||
|
@ -331,6 +346,138 @@ func makeOwnshipGeometricAltitudeReport() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
"SX" Stratux GDL90 message.
|
||||||
|
http://hiltonsoftware.com/stratux/StratuxStatusMessage-V01.pdf
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
func makeSXHeartbeat() []byte {
|
||||||
|
msg := make([]byte, 29)
|
||||||
|
msg[0] = 'S'
|
||||||
|
msg[1] = 'X'
|
||||||
|
msg[2] = 1
|
||||||
|
|
||||||
|
msg[3] = 1 // "message version".
|
||||||
|
|
||||||
|
// Firmware version. First 4 bytes of build.
|
||||||
|
msg[4] = byte(stratuxBuild[0])
|
||||||
|
msg[5] = byte(stratuxBuild[1])
|
||||||
|
msg[6] = byte(stratuxBuild[2])
|
||||||
|
msg[7] = byte(stratuxBuild[3])
|
||||||
|
|
||||||
|
//TODO: Hardware revision.
|
||||||
|
msg[8] = 0xFF
|
||||||
|
msg[9] = 0xFF
|
||||||
|
msg[10] = 0xFF
|
||||||
|
msg[11] = 0xFF
|
||||||
|
|
||||||
|
// Valid and enabled flags.
|
||||||
|
// Valid/Enabled: GPS portion.
|
||||||
|
if isGPSValid() {
|
||||||
|
switch mySituation.quality {
|
||||||
|
case 1: // 1 = 3D GPS.
|
||||||
|
msg[12] = msg[12] | (1 << 6)
|
||||||
|
case 2: // 2 = DGPS (SBAS /WAAS).
|
||||||
|
msg[12] = msg[12] | (1 << 7)
|
||||||
|
default: // Zero.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid/Enabled: AHRS portion.
|
||||||
|
if isAHRSValid() {
|
||||||
|
msg[12] = msg[12] | (1 << 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid/Enabled: Pressure altitude portion.
|
||||||
|
if isTempPressValid() {
|
||||||
|
msg[12] = msg[12] | (1 << 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid/Enabled: CPU temperature portion.
|
||||||
|
if isCPUTempValid() {
|
||||||
|
msg[12] = msg[12] | (1 << 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid/Enabled: UAT portion.
|
||||||
|
if globalSettings.UAT_Enabled {
|
||||||
|
msg[12] = msg[12] | (1 << 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid/Enabled: ES portion.
|
||||||
|
if globalSettings.ES_Enabled {
|
||||||
|
msg[12] = msg[12] | (1 << 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid/Enabled: last bit unused.
|
||||||
|
|
||||||
|
// Connected hardware: number of radios.
|
||||||
|
msg[14] = msg[14] | (byte(globalStatus.Devices) << 6)
|
||||||
|
// Connected hardware: RY835AI.
|
||||||
|
if globalStatus.RY835AI_connected {
|
||||||
|
msg[14] = msg[14] | (1 << 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number of GPS satellites locked.
|
||||||
|
msg[16] = byte(globalStatus.GPS_satellites_locked)
|
||||||
|
|
||||||
|
//FIXME: Number of satellites connected. ??
|
||||||
|
msg[17] = 0xFF
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
// CPU temperature.
|
||||||
|
v := uint16(float32(10.0)*globalStatus.CPUTemp) + 32768
|
||||||
|
|
||||||
|
msg[26] = byte((v & 0xFF00) >> 8)
|
||||||
|
msg[27] = byte(v * 0xFF)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
"Stratux" GDL90 message.
|
"Stratux" GDL90 message.
|
||||||
|
@ -407,6 +554,7 @@ func heartBeatSender() {
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
sendGDL90(makeHeartbeat(), false)
|
sendGDL90(makeHeartbeat(), false)
|
||||||
sendGDL90(makeStratuxHeartbeat(), false)
|
sendGDL90(makeStratuxHeartbeat(), false)
|
||||||
|
sendGDL90(makeSXHeartbeat(), false)
|
||||||
// sendGDL90(makeTrafficReport())
|
// sendGDL90(makeTrafficReport())
|
||||||
makeOwnshipReport()
|
makeOwnshipReport()
|
||||||
makeOwnshipGeometricAltitudeReport()
|
makeOwnshipGeometricAltitudeReport()
|
||||||
|
@ -481,6 +629,11 @@ func updateMessageStats() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if CPU temperature is valid. Assume <= 0 is invalid.
|
||||||
|
func isCPUTempValid() bool {
|
||||||
|
return globalStatus.CPUTemp <= 0
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
cpuTempMonitor() reads the RPi board temperature every second and updates it in globalStatus.
|
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
|
This is broken out into its own function (run as its own goroutine) because the RPi temperature
|
||||||
|
@ -551,22 +704,24 @@ func replayLog(msg string, msgclass int) {
|
||||||
if len(msg) == 0 { // Blank message.
|
if len(msg) == 0 { // Blank message.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var wt *gzip.Writer
|
var fp WriteCloser
|
||||||
|
|
||||||
switch msgclass {
|
switch msgclass {
|
||||||
case MSGCLASS_UAT:
|
case MSGCLASS_UAT:
|
||||||
wt = uatReplayWriter
|
fp = uatReplayWriter
|
||||||
case MSGCLASS_ES:
|
case MSGCLASS_ES:
|
||||||
wt = esReplayWriter
|
fp = esReplayWriter
|
||||||
case MSGCLASS_GPS:
|
case MSGCLASS_GPS:
|
||||||
wt = gpsReplayWriter
|
fp = gpsReplayWriter
|
||||||
case MSGCLASS_AHRS:
|
case MSGCLASS_AHRS:
|
||||||
wt = ahrsReplayWriter
|
fp = ahrsReplayWriter
|
||||||
case MSGCLASS_DUMP1090:
|
case MSGCLASS_DUMP1090:
|
||||||
wt = dump1090ReplayWriter
|
fp = dump1090ReplayWriter
|
||||||
}
|
}
|
||||||
if wt != nil {
|
|
||||||
|
if fp != nil {
|
||||||
s := makeReplayLogEntry(msg)
|
s := makeReplayLogEntry(msg)
|
||||||
wt.Write([]byte(s))
|
fp.Write([]byte(s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -883,7 +1038,7 @@ func replayMark(active bool) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func openReplay(fn string) (*gzip.Writer, error) {
|
func openReplay(fn string, compressed bool) (WriteCloser, error) {
|
||||||
fp, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
fp, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -891,11 +1046,18 @@ func openReplay(fn string) (*gzip.Writer, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
gzw := gzip.NewWriter(fp) //FIXME: Close() on the gzip.Writer will not close the underlying file.
|
var ret WriteCloser
|
||||||
|
if compressed {
|
||||||
|
ret = gzip.NewWriter(fp) //FIXME: Close() on the gzip.Writer will not close the underlying file.
|
||||||
|
} else {
|
||||||
|
ret = fp
|
||||||
|
}
|
||||||
|
|
||||||
timeFmt := "Mon Jan 2 15:04:05 -0700 MST 2006"
|
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.
|
s := fmt.Sprintf("START,%s,%s\n", timeStarted.Format(timeFmt), time.Now().Format(timeFmt)) // Start time marker.
|
||||||
gzw.Write([]byte(s))
|
|
||||||
return gzw, err
|
ret.Write([]byte(s))
|
||||||
|
return ret, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func printStats() {
|
func printStats() {
|
||||||
|
@ -913,10 +1075,91 @@ func printStats() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var uatReplayDone bool
|
||||||
|
|
||||||
|
func uatReplay(f ReadCloser, replaySpeed uint64) {
|
||||||
|
defer f.Close()
|
||||||
|
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 {
|
||||||
|
log.Printf("invalid tick: '%s'\n", linesplit[0])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
thisWait := (i - curTick) / int64(replaySpeed)
|
||||||
|
|
||||||
|
if thisWait >= 120000000000 { // More than 2 minutes wait, skip ahead.
|
||||||
|
log.Printf("UAT skipahead - %d seconds.\n", thisWait/1000000000)
|
||||||
|
} else {
|
||||||
|
time.Sleep(time.Duration(thisWait) * time.Nanosecond) // Just in case the units change.
|
||||||
|
}
|
||||||
|
|
||||||
|
p := strings.Trim(linesplit[1], " ;\r\n")
|
||||||
|
log.Printf("%s;\n", p)
|
||||||
|
buf := fmt.Sprintf("%s;\n", p)
|
||||||
|
o, msgtype := parseInput(buf)
|
||||||
|
if o != nil && msgtype != 0 {
|
||||||
|
relayMessage(msgtype, o)
|
||||||
|
}
|
||||||
|
curTick = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uatReplayDone = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func openReplayFile(fn string) ReadCloser {
|
||||||
|
fp, err := os.Open(fn)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error opening '%s': %s\n", fn, err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret ReadCloser
|
||||||
|
if strings.HasSuffix(fn, ".gz") { // Open as a compressed replay log, depending on the suffix.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
|
// replayESFilename := flag.String("eslog", "none", "ES Log filename")
|
||||||
|
replayUATFilename := flag.String("uatlog", "none", "UAT Log filename")
|
||||||
|
develFlag := flag.Bool("developer", false, "Developer mode")
|
||||||
|
replayFlag := flag.Bool("replay", false, "Replay file flag")
|
||||||
|
replaySpeed := flag.Int("speed", 1, "Replay speed multiplier")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
timeStarted = time.Now()
|
timeStarted = time.Now()
|
||||||
runtime.GOMAXPROCS(runtime.NumCPU()) // redundant with Go v1.5+ compiler
|
runtime.GOMAXPROCS(runtime.NumCPU()) // redundant with Go v1.5+ compiler
|
||||||
|
|
||||||
|
if *develFlag == true {
|
||||||
|
log.Printf("Developer mode flag true!\n")
|
||||||
|
developerMode = true
|
||||||
|
}
|
||||||
|
|
||||||
// Duplicate log.* output to debugLog.
|
// Duplicate log.* output to debugLog.
|
||||||
fp, err := os.OpenFile(debugLog, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
fp, err := os.OpenFile(debugLog, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -934,6 +1177,7 @@ func main() {
|
||||||
MsgLog = make([]msg, 0)
|
MsgLog = make([]msg, 0)
|
||||||
|
|
||||||
crcInit() // Initialize CRC16 table.
|
crcInit() // Initialize CRC16 table.
|
||||||
|
|
||||||
sdrInit()
|
sdrInit()
|
||||||
initTraffic()
|
initTraffic()
|
||||||
|
|
||||||
|
@ -941,38 +1185,44 @@ func main() {
|
||||||
|
|
||||||
readSettings()
|
readSettings()
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
// Set up the replay logs. Keep these files open in any case, even if replay logging is disabled.
|
// Set up the replay logs. Keep these files open in any case, even if replay logging is disabled.
|
||||||
|
|
||||||
// UAT replay log.
|
if uatwt, err := openReplay(uatReplayLog, !developerMode); err != nil {
|
||||||
if uatwt, err := openReplay(uatReplayLog); err != nil {
|
|
||||||
globalSettings.ReplayLog = false
|
globalSettings.ReplayLog = false
|
||||||
} else {
|
} else {
|
||||||
uatReplayWriter = uatwt
|
uatReplayWriter = uatwt
|
||||||
defer uatReplayWriter.Close()
|
defer uatReplayWriter.Close()
|
||||||
}
|
}
|
||||||
// 1090ES replay log.
|
// 1090ES replay log.
|
||||||
if eswt, err := openReplay(esReplayLog); err != nil {
|
if eswt, err := openReplay(esReplayLog, !developerMode); err != nil {
|
||||||
globalSettings.ReplayLog = false
|
globalSettings.ReplayLog = false
|
||||||
} else {
|
} else {
|
||||||
esReplayWriter = eswt
|
esReplayWriter = eswt
|
||||||
defer esReplayWriter.Close()
|
defer esReplayWriter.Close()
|
||||||
}
|
}
|
||||||
// GPS replay log.
|
// GPS replay log.
|
||||||
if gpswt, err := openReplay(gpsReplayLog); err != nil {
|
if gpswt, err := openReplay(gpsReplayLog, !developerMode); err != nil {
|
||||||
globalSettings.ReplayLog = false
|
globalSettings.ReplayLog = false
|
||||||
} else {
|
} else {
|
||||||
gpsReplayWriter = gpswt
|
gpsReplayWriter = gpswt
|
||||||
defer gpsReplayWriter.Close()
|
defer gpsReplayWriter.Close()
|
||||||
}
|
}
|
||||||
// AHRS replay log.
|
// AHRS replay log.
|
||||||
if ahrswt, err := openReplay(ahrsReplayLog); err != nil {
|
if ahrswt, err := openReplay(ahrsReplayLog, !developerMode); err != nil {
|
||||||
globalSettings.ReplayLog = false
|
globalSettings.ReplayLog = false
|
||||||
} else {
|
} else {
|
||||||
ahrsReplayWriter = ahrswt
|
ahrsReplayWriter = ahrswt
|
||||||
defer ahrsReplayWriter.Close()
|
defer ahrsReplayWriter.Close()
|
||||||
}
|
}
|
||||||
// Dump1090 replay log.
|
// Dump1090 replay log.
|
||||||
if dump1090wt, err := openReplay(dump1090ReplayLog); err != nil {
|
if dump1090wt, err := openReplay(dump1090ReplayLog, !developerMode); err != nil {
|
||||||
globalSettings.ReplayLog = false
|
globalSettings.ReplayLog = false
|
||||||
} else {
|
} else {
|
||||||
dump1090ReplayWriter = dump1090wt
|
dump1090ReplayWriter = dump1090wt
|
||||||
|
@ -1000,6 +1250,22 @@ func main() {
|
||||||
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
|
||||||
|
if *replayFlag == true {
|
||||||
|
fp := openReplayFile(*replayUATFilename)
|
||||||
|
|
||||||
|
playSpeed := uint64(*replaySpeed)
|
||||||
|
log.Printf("Replay speed: %dx\n", playSpeed)
|
||||||
|
go uatReplay(fp, playSpeed)
|
||||||
|
|
||||||
|
for {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
if uatReplayDone {
|
||||||
|
//&& esDone {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
for {
|
for {
|
||||||
buf, err := reader.ReadString('\n')
|
buf, err := reader.ReadString('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1011,5 +1277,5 @@ func main() {
|
||||||
relayMessage(msgtype, o)
|
relayMessage(msgtype, o)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,13 @@ Stratux uses GDL90 protocol over port 4000 UDP. All messages are sent as **unica
|
||||||
network and a DHCP lease is issued to the device, the IP of the DHCP lease is added to the list of clients receiving GDL90 messages.
|
network and a DHCP lease is issued to the device, the IP of the DHCP lease is added to the list of clients receiving GDL90 messages.
|
||||||
|
|
||||||
|
|
||||||
The GDL90 is "standard" with the exception of two non-standard GDL90-style messages: 0xCC (stratux heartbeat) and 0x4C (AHRS report).
|
The GDL90 is "standard" with the exception of three non-standard GDL90-style messages: `0xCC` (stratux heartbeat), `0x5358` (another stratux heartbeat), and `0x4C` (AHRS report).
|
||||||
|
|
||||||
|
|
||||||
### How to recognize stratux
|
### How to recognize stratux
|
||||||
|
|
||||||
In order of preference:
|
In order of preference:
|
||||||
|
|
||||||
1. Look for 0xCC heartbeat message. This is sent at the same time as the GDL90 heartbeat (0x00) message.
|
1. Look for `0xCC` (or `0x5358`) GDL90 heartbeat message. This is sent at the same time as the GDL90 heartbeat (0x00) message.
|
||||||
2. Look for Wi-Fi network that **starts with** "stratux".
|
2. Look for Wi-Fi network that **starts with** "stratux".
|
||||||
3. Detect 192.168.10.0/24 Wi-Fi connection, verify stratux status with JSON response from ws://192.168.10.1/status.
|
3. Detect 192.168.10.0/24 Wi-Fi connection, verify stratux status with JSON response from ws://192.168.10.1/status.
|
||||||
|
|
||||||
|
@ -53,3 +52,113 @@ Traffic information roughly follows this path:
|
||||||
When traffic information is being received both from UAT and 1090ES sources, it is not uncommon to see a flip/flop in tail numbers on targets.
|
When traffic information is being received both from UAT and 1090ES sources, it is not uncommon to see a flip/flop in tail numbers on targets.
|
||||||
Some 1090ES transponders will send the actual registration number of the aircraft, which then becomes a TIS-B target whose tail number may be
|
Some 1090ES transponders will send the actual registration number of the aircraft, which then becomes a TIS-B target whose tail number may be
|
||||||
a squawk code.
|
a squawk code.
|
||||||
|
|
||||||
|
|
||||||
|
### Additional data available to EFBs
|
||||||
|
|
||||||
|
Stratux makes available a webserver to retrieve statistics which may be useful to EFBs:
|
||||||
|
|
||||||
|
* `http://192.168.10.1/getTowers` - a list of ADS-B towers received with attached message receipt and signal level statistics. Example output:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"(28.845592,-96.920400)": {
|
||||||
|
"Lat": 28.845591545105,
|
||||||
|
"Lng": -96.920399665833,
|
||||||
|
"Signal_strength_last_minute": 55,
|
||||||
|
"Signal_strength_max": 69,
|
||||||
|
"Messages_last_minute": 97,
|
||||||
|
"Messages_total": 196
|
||||||
|
},
|
||||||
|
"(29.266505,-98.309097)": {
|
||||||
|
"Lat": 29.266505241394,
|
||||||
|
"Lng": -98.309097290039,
|
||||||
|
"Signal_strength_last_minute": 78,
|
||||||
|
"Signal_strength_max": 78,
|
||||||
|
"Messages_last_minute": 1,
|
||||||
|
"Messages_total": 3
|
||||||
|
},
|
||||||
|
"(29.702547,-96.900787)": {
|
||||||
|
"Lat": 29.702546596527,
|
||||||
|
"Lng": -96.900787353516,
|
||||||
|
"Signal_strength_last_minute": 87,
|
||||||
|
"Signal_strength_max": 119,
|
||||||
|
"Messages_last_minute": 94,
|
||||||
|
"Messages_total": 203
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* `http://192.168.10.1/getStatus` - device status and statistics. Example output (commented JSON):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Version": "v0.5b1", // Software version.
|
||||||
|
"Devices": 0, // Number of radios connected.
|
||||||
|
"Connected_Users": 1, // Number of WiFi devices connected.
|
||||||
|
"UAT_messages_last_minute": 0, // UAT messages received in last minute.
|
||||||
|
"UAT_messages_max": 17949, // Max UAT messages received in a minute (since last reboot).
|
||||||
|
"ES_messages_last_minute": 0, // 1090ES messages received in last minute.
|
||||||
|
"ES_messages_max": 0, // Max 1090ES messages received in a minute (since last reboot).
|
||||||
|
"GPS_satellites_locked": 0, // Number of GPS satellites used in last GPS lock.
|
||||||
|
"GPS_connected": true, // GPS unit connected and functioning.
|
||||||
|
"GPS_solution": "", // "DGPS (WAAS)", "3D GPS", "N/A", or "" when GPS not connected/enabled.
|
||||||
|
"RY835AI_connected": false, // GPS/AHRS unit - use only for debugging (this will be removed).
|
||||||
|
"Uptime": 227068, // Device uptime (in milliseconds).
|
||||||
|
"CPUTemp": 42.236 // CPU temperature (in ºC).
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* http://192.168.10.1/getSettings` - get device settings. Example output:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"UAT_Enabled": true,
|
||||||
|
"ES_Enabled": false,
|
||||||
|
"GPS_Enabled": true,
|
||||||
|
"NetworkOutputs": [
|
||||||
|
{
|
||||||
|
"Conn": null,
|
||||||
|
"Ip": "",
|
||||||
|
"Port": 4000,
|
||||||
|
"Capability": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Conn": null,
|
||||||
|
"Ip": "",
|
||||||
|
"Port": 49002,
|
||||||
|
"Capability": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"AHRS_Enabled": false,
|
||||||
|
"DEBUG": false,
|
||||||
|
"ReplayLog": true,
|
||||||
|
"PPM": 0,
|
||||||
|
"OwnshipModeS": "F00000",
|
||||||
|
"WatchList": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
* `http://192.168.10.1/setSettings` - set device settings. Use an HTTP POST of JSON content in the format given above - posting only the fields containing the settings to be modified.
|
||||||
|
|
||||||
|
* `http://192.168.10.1/getSituation` - get GPS/AHRS information. Example output:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Lat": 39.108533,
|
||||||
|
"Lng": -76.770862,
|
||||||
|
"Satellites": 7,
|
||||||
|
"Accuracy": 5.88,
|
||||||
|
"NACp": 10,
|
||||||
|
"Alt": 170.10767,
|
||||||
|
"LastFixLocalTime": "2015-12-18T23:47:06.015563066Z",
|
||||||
|
"TrueCourse": 0,
|
||||||
|
"GroundSpeed": 0,
|
||||||
|
"LastGroundTrackTime": "0001-01-01T00:00:00Z",
|
||||||
|
"Temp": 6553,
|
||||||
|
"Pressure_alt": 231.27980834234,
|
||||||
|
"Pitch": -0.006116937627108,
|
||||||
|
"Roll": -0.026442866350631,
|
||||||
|
"Gyro_heading": 45.844213419776,
|
||||||
|
"LastAttitudeTime": "2015-12-18T23:47:06.774039623Z"
|
||||||
|
}
|
||||||
|
```
|
Ładowanie…
Reference in New Issue