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"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -30,7 +31,7 @@ const (
|
|||
maxDatagramSize = 8192
|
||||
maxUserMsgQueueSize = 25000 // About 10MB per port per connected client.
|
||||
logDirectory = "/var/log/stratux"
|
||||
|
||||
|
||||
UPLINK_BLOCK_DATA_BITS = 576
|
||||
UPLINK_BLOCK_BITS = (UPLINK_BLOCK_DATA_BITS + 160)
|
||||
UPLINK_BLOCK_DATA_BYTES = (UPLINK_BLOCK_DATA_BITS / 8)
|
||||
|
@ -60,11 +61,11 @@ const (
|
|||
TRACK_RESOLUTION = float32(360.0 / 256.0)
|
||||
)
|
||||
|
||||
var uatReplayLog string
|
||||
var esReplayLog string
|
||||
var gpsReplayLog string
|
||||
var ahrsReplayLog string
|
||||
var dump1090ReplayLog string
|
||||
var uatReplayLog string
|
||||
var esReplayLog string
|
||||
var gpsReplayLog string
|
||||
var ahrsReplayLog string
|
||||
var dump1090ReplayLog string
|
||||
|
||||
var stratuxBuild string
|
||||
var stratuxVersion string
|
||||
|
@ -75,12 +76,24 @@ var Crc16Table [256]uint16
|
|||
// Current AHRS, pressure altitude, etc.
|
||||
var mySituation SituationData
|
||||
|
||||
type WriteCloser interface {
|
||||
io.Writer
|
||||
io.Closer
|
||||
}
|
||||
|
||||
type ReadCloser interface {
|
||||
io.Reader
|
||||
io.Closer
|
||||
}
|
||||
|
||||
// File handles for replay logging.
|
||||
var uatReplayWriter *gzip.Writer
|
||||
var esReplayWriter *gzip.Writer
|
||||
var gpsReplayWriter *gzip.Writer
|
||||
var ahrsReplayWriter *gzip.Writer
|
||||
var dump1090ReplayWriter *gzip.Writer
|
||||
var uatReplayWriter WriteCloser
|
||||
var esReplayWriter WriteCloser
|
||||
var gpsReplayWriter WriteCloser
|
||||
var ahrsReplayWriter WriteCloser
|
||||
var dump1090ReplayWriter WriteCloser
|
||||
|
||||
var developerMode bool
|
||||
|
||||
type msg struct {
|
||||
MessageClass uint
|
||||
|
@ -107,47 +120,49 @@ type ADSBTower struct {
|
|||
Messages_total uint64
|
||||
}
|
||||
|
||||
|
||||
var ADSBTowers map[string]ADSBTower // Running list of all towers seen. (lat,lng) -> ADSBTower
|
||||
|
||||
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
|
||||
|
||||
|
||||
// First, create the log file directory if it does not exist
|
||||
os.Mkdir(logDirectory,0644)
|
||||
|
||||
os.Mkdir(logDirectory, 0644)
|
||||
|
||||
f, err := os.Open(indexFilename)
|
||||
if err != nil {
|
||||
log.Printf("Unable to open index file %s using index of 0\n", indexFilename)
|
||||
fileIndexNumber=0
|
||||
fileIndexNumber = 0
|
||||
} else {
|
||||
_, err := fmt.Fscanf(f,"%d\n",&fileIndexNumber)
|
||||
if err != nil {
|
||||
log.Printf("Unable to read index file %s using index of 0\n", indexFilename)
|
||||
}
|
||||
f.Close()
|
||||
fileIndexNumber++
|
||||
_, err := fmt.Fscanf(f, "%d\n", &fileIndexNumber)
|
||||
if err != nil {
|
||||
log.Printf("Unable to read index file %s using index of 0\n", indexFilename)
|
||||
}
|
||||
f.Close()
|
||||
fileIndexNumber++
|
||||
}
|
||||
fo, err := os.Create(indexFilename)
|
||||
if err != nil {
|
||||
log.Printf("Error creating index file %s\n", indexFilename)
|
||||
}
|
||||
_, err2 := fmt.Fprintf(fo,"%d\n",fileIndexNumber)
|
||||
_, err2 := fmt.Fprintf(fo, "%d\n", fileIndexNumber)
|
||||
if err2 != nil {
|
||||
log.Printf("Error writing to index file %s\n", indexFilename)
|
||||
}
|
||||
fo.Sync()
|
||||
fo.Close()
|
||||
uatReplayLog = fmt.Sprintf("%s/%04d-uat.log.gz",logDirectory,fileIndexNumber)
|
||||
esReplayLog = fmt.Sprintf("%s/%04d-es.log.gz",logDirectory,fileIndexNumber)
|
||||
gpsReplayLog = fmt.Sprintf("%s/%04d-gps.log.gz",logDirectory,fileIndexNumber)
|
||||
ahrsReplayLog = fmt.Sprintf("%s/%04d-ahrs.log.gz",logDirectory,fileIndexNumber)
|
||||
dump1090ReplayLog = fmt.Sprintf("%s/%04d-dump1090.log.gz",logDirectory,fileIndexNumber)
|
||||
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)
|
||||
esReplayLog = fmt.Sprintf("%s/%04d-es.log.gz", logDirectory, fileIndexNumber)
|
||||
gpsReplayLog = fmt.Sprintf("%s/%04d-gps.log.gz", logDirectory, fileIndexNumber)
|
||||
ahrsReplayLog = fmt.Sprintf("%s/%04d-ahrs.log.gz", logDirectory, fileIndexNumber)
|
||||
dump1090ReplayLog = fmt.Sprintf("%s/%04d-dump1090.log.gz", logDirectory, fileIndexNumber)
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the CRC table. Adapted from FAA ref above.
|
||||
|
@ -331,6 +346,138 @@ func makeOwnshipGeometricAltitudeReport() bool {
|
|||
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.
|
||||
|
@ -407,6 +554,7 @@ func heartBeatSender() {
|
|||
case <-timer.C:
|
||||
sendGDL90(makeHeartbeat(), false)
|
||||
sendGDL90(makeStratuxHeartbeat(), false)
|
||||
sendGDL90(makeSXHeartbeat(), false)
|
||||
// sendGDL90(makeTrafficReport())
|
||||
makeOwnshipReport()
|
||||
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.
|
||||
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.
|
||||
return
|
||||
}
|
||||
var wt *gzip.Writer
|
||||
var fp WriteCloser
|
||||
|
||||
switch msgclass {
|
||||
case MSGCLASS_UAT:
|
||||
wt = uatReplayWriter
|
||||
fp = uatReplayWriter
|
||||
case MSGCLASS_ES:
|
||||
wt = esReplayWriter
|
||||
fp = esReplayWriter
|
||||
case MSGCLASS_GPS:
|
||||
wt = gpsReplayWriter
|
||||
fp = gpsReplayWriter
|
||||
case MSGCLASS_AHRS:
|
||||
wt = ahrsReplayWriter
|
||||
fp = ahrsReplayWriter
|
||||
case MSGCLASS_DUMP1090:
|
||||
wt = dump1090ReplayWriter
|
||||
fp = dump1090ReplayWriter
|
||||
}
|
||||
if wt != nil {
|
||||
|
||||
if fp != nil {
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
|
@ -891,11 +1046,18 @@ func openReplay(fn string) (*gzip.Writer, error) {
|
|||
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"
|
||||
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() {
|
||||
|
@ -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() {
|
||||
|
||||
// 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()
|
||||
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.
|
||||
fp, err := os.OpenFile(debugLog, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
|
@ -929,11 +1172,12 @@ func main() {
|
|||
|
||||
log.Printf("Stratux %s (%s) starting.\n", stratuxVersion, stratuxBuild)
|
||||
constructFilenames()
|
||||
|
||||
|
||||
ADSBTowers = make(map[string]ADSBTower)
|
||||
MsgLog = make([]msg, 0)
|
||||
|
||||
crcInit() // Initialize CRC16 table.
|
||||
|
||||
sdrInit()
|
||||
initTraffic()
|
||||
|
||||
|
@ -941,38 +1185,44 @@ func main() {
|
|||
|
||||
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.
|
||||
|
||||
// UAT replay log.
|
||||
if uatwt, err := openReplay(uatReplayLog); err != nil {
|
||||
if uatwt, err := openReplay(uatReplayLog, !developerMode); err != nil {
|
||||
globalSettings.ReplayLog = false
|
||||
} else {
|
||||
uatReplayWriter = uatwt
|
||||
defer uatReplayWriter.Close()
|
||||
}
|
||||
// 1090ES replay log.
|
||||
if eswt, err := openReplay(esReplayLog); err != nil {
|
||||
if eswt, err := openReplay(esReplayLog, !developerMode); err != nil {
|
||||
globalSettings.ReplayLog = false
|
||||
} else {
|
||||
esReplayWriter = eswt
|
||||
defer esReplayWriter.Close()
|
||||
}
|
||||
// GPS replay log.
|
||||
if gpswt, err := openReplay(gpsReplayLog); err != nil {
|
||||
if gpswt, err := openReplay(gpsReplayLog, !developerMode); err != nil {
|
||||
globalSettings.ReplayLog = false
|
||||
} else {
|
||||
gpsReplayWriter = gpswt
|
||||
defer gpsReplayWriter.Close()
|
||||
}
|
||||
// AHRS replay log.
|
||||
if ahrswt, err := openReplay(ahrsReplayLog); err != nil {
|
||||
if ahrswt, err := openReplay(ahrsReplayLog, !developerMode); err != nil {
|
||||
globalSettings.ReplayLog = false
|
||||
} else {
|
||||
ahrsReplayWriter = ahrswt
|
||||
defer ahrsReplayWriter.Close()
|
||||
}
|
||||
// Dump1090 replay log.
|
||||
if dump1090wt, err := openReplay(dump1090ReplayLog); err != nil {
|
||||
if dump1090wt, err := openReplay(dump1090ReplayLog, !developerMode); err != nil {
|
||||
globalSettings.ReplayLog = false
|
||||
} else {
|
||||
dump1090ReplayWriter = dump1090wt
|
||||
|
@ -1000,16 +1250,32 @@ func main() {
|
|||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
for {
|
||||
buf, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Printf("lost stdin.\n")
|
||||
break
|
||||
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
|
||||
}
|
||||
}
|
||||
o, msgtype := parseInput(buf)
|
||||
if o != nil && msgtype != 0 {
|
||||
relayMessage(msgtype, o)
|
||||
|
||||
} else {
|
||||
for {
|
||||
buf, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Printf("lost stdin.\n")
|
||||
break
|
||||
}
|
||||
o, msgtype := parseInput(buf)
|
||||
if o != nil && msgtype != 0 {
|
||||
relayMessage(msgtype, o)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
||||
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
|
||||
|
||||
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".
|
||||
3. Detect 192.168.10.0/24 Wi-Fi connection, verify stratux status with JSON response from ws://192.168.10.1/status.
|
||||
|
||||
|
@ -52,4 +51,114 @@ 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.
|
||||
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