Merge remote-tracking branch 'origin/master' into powersave

# Conflicts:
#	main/network.go
pull/166/head
Christopher Young 2015-12-26 17:18:57 -05:00
commit 268bc227c9
13 zmienionych plików z 743 dodań i 92 usunięć

Wyświetl plik

@ -1,7 +1,9 @@
ifeq "$(CIRCLECI)" "true"
BUILDINFO=
else
BUILDINFO=-ldflags "-X main.stratuxVersion=`git describe --tags --abbrev=0` -X main.stratuxBuild=`git log -n 1 --pretty=%H`"
$(if $(GOROOT),,$(error GOROOT is not set!))
endif
all:
@ -10,8 +12,9 @@ all:
go get -t -d -v ./...
go build $(BUILDINFO) main/gen_gdl90.go main/traffic.go main/ry835ai.go main/network.go main/managementinterface.go main/sdr.go main/uibroadcast.go
.PHONY: test
test:
sh -c true
make -C test
www:
mkdir -p /var/www

Wyświetl plik

@ -10,20 +10,20 @@ Use Pi 2 and ForeFlight 7.3.1 (1792) (Sep 18, 2015).
Supported WiFi adapters:
* Edimax EW-7811Un
* TP-LINK TL-WN725N
Tested RTL-SDR:
* NooElec NESDR Nano 2 (best)
* NooElec NESDR Mini 2
* Generic R820T (degraded performance)
Apps with stratux recognition/support:
* Seattle Avionics FlyQ EFB 2.1.1+.
* AvNav EFB 2.0.0+.
* Naviator.
* WingX Pro7 8.6.2+
Tested weather/traffic displays:
* ForeFlight 7+ - weather, traffic, AHRS.
* Naviator - weather, traffic, AHRS.
* WingX - weather & traffic.
* Avare
* iFly 740 - weather & traffic.

Wyświetl plik

@ -107,7 +107,7 @@ apt-get install -y git cmake libusb-1.0-0.dev build-essential
cd /root
rm -rf rtl-sdr
git clone git://git.osmocom.org/rtl-sdr.git
git clone https://github.com/jpoirier/librtlsdr rtl-sdr
cd rtl-sdr
mkdir build
cd build
@ -131,7 +131,8 @@ echo " - Stratux"
scp_in_to_qemu /root/spindle/gen_gdl90 /tmp/gen_gdl90
ssh_in_to_qemu chroot /mnt sh -l -ex - <<\EOF
ssh_in_to_qemu chroot /mnt bash -l -ex - <<\EOF
source /root/.bashrc
cd /root
apt-get install -y mercurial

Wyświetl plik

@ -2,8 +2,10 @@ package main
import (
"bufio"
"compress/gzip"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
@ -23,15 +25,12 @@ import (
const (
configLocation = "/etc/stratux.conf"
indexFilename = "/var/log/stratux/LOGINDEX"
managementAddr = ":80"
debugLog = "/var/log/stratux.log"
maxDatagramSize = 8192
maxUserMsgQueueSize = 25000 // About 10MB per port per connected client.
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"
logDirectory = "/var/log/stratux"
UPLINK_BLOCK_DATA_BITS = 576
UPLINK_BLOCK_BITS = (UPLINK_BLOCK_DATA_BITS + 160)
@ -62,6 +61,12 @@ const (
TRACK_RESOLUTION = float32(360.0 / 256.0)
)
var uatReplayLog string
var esReplayLog string
var gpsReplayLog string
var ahrsReplayLog string
var dump1090ReplayLog string
var stratuxBuild string
var stratuxVersion string
@ -71,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 uatReplayfp *os.File
var esReplayfp *os.File
var gpsReplayfp *os.File
var ahrsReplayfp *os.File
var dump1090Replayfp *os.File
var uatReplayWriter WriteCloser
var esReplayWriter WriteCloser
var gpsReplayWriter WriteCloser
var ahrsReplayWriter WriteCloser
var dump1090ReplayWriter WriteCloser
var developerMode bool
type msg struct {
MessageClass uint
@ -105,6 +122,49 @@ type ADSBTower struct {
var ADSBTowers map[string]ADSBTower // Running list of all towers seen. (lat,lng) -> ADSBTower
func constructFilenames() {
var fileIndexNumber uint
// First, create the log file directory if it does not exist
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
} 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++
}
fo, err := os.Create(indexFilename)
if err != nil {
log.Printf("Error creating index file %s\n", indexFilename)
}
_, err2 := fmt.Fprintf(fo, "%d\n", fileIndexNumber)
if err2 != nil {
log.Printf("Error writing to index file %s\n", indexFilename)
}
fo.Sync()
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)
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.
func crcInit() {
var i uint16
@ -207,14 +267,16 @@ func makeOwnshipReport() bool {
// alt := uint16(0xFFF) // 0xFFF "invalid altitude."
var alt uint16
if isTempPressValid() {
alt = uint16(mySituation.Pressure_alt)
} else {
alt = uint16(mySituation.Alt) //FIXME: This should not be here.
}
alt = (alt + 1000) / 25
var altf float64
alt = alt & 0xFFF // Should fit in 12 bits.
if isTempPressValid() {
altf = float64(mySituation.Pressure_alt)
} else {
altf = float64(mySituation.Alt) //FIXME: Pass GPS altitude if PA not available. **WORKAROUND FOR FF**
}
altf = (altf + 1000) / 25
alt = uint16(altf) & 0xFFF // Should fit in 12 bits.
msg[11] = byte((alt & 0xFF0) >> 4) // Altitude.
msg[12] = byte((alt & 0x00F) << 4)
@ -233,7 +295,7 @@ func makeOwnshipReport() bool {
msg[14] = byte((gdSpeed & 0xFF0) >> 4)
msg[15] = byte((gdSpeed & 0x00F) << 4)
verticalVelocity := int16(1000 / 64) // ft/min. 64 ft/min resolution.
verticalVelocity := int16(0x800) // ft/min. 64 ft/min resolution.
//TODO: 0x800 = no information available.
// verticalVelocity should fit in 12 bits.
msg[15] = msg[15] | byte((verticalVelocity&0x0F00)>>8)
@ -284,6 +346,175 @@ 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".
// Version code. Messy parsing to fit into four bytes.
//FIXME: This is why we can't have nice things.
v := stratuxVersion[1:] // Skip first character, should be 'v'.
m_str := v[0:strings.Index(v, ".")] // Major version.
mib_str := v[strings.Index(v, ".")+1:] // Minor and build version.
tp := 0 // Build "type".
mi_str := ""
b_str := ""
if strings.Index(mib_str, "rc") != -1 {
tp = 3
mi_str = mib_str[0:strings.Index(mib_str, "rc")]
b_str = mib_str[strings.Index(mib_str, "rc")+2:]
} else if strings.Index(mib_str, "r") != -1 {
tp = 2
mi_str = mib_str[0:strings.Index(mib_str, "r")]
b_str = mib_str[strings.Index(mib_str, "r")+1:]
} else if strings.Index(mib_str, "b") != -1 {
tp = 1
mi_str = mib_str[0:strings.Index(mib_str, "b")]
b_str = mib_str[strings.Index(mib_str, "b")+1:]
}
// Convert to strings.
m, _ := strconv.Atoi(m_str)
mi, _ := strconv.Atoi(mi_str)
b, _ := strconv.Atoi(b_str)
msg[4] = byte(m)
msg[5] = byte(mi)
msg[6] = byte(tp)
msg[7] = byte(b)
//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[13] = 1
case 2: // 2 = DGPS (SBAS /WAAS).
msg[13] = 2
default: // Zero.
}
}
// Valid/Enabled: AHRS portion.
if isAHRSValid() {
msg[13] = msg[13] | (1 << 2)
}
// Valid/Enabled: Pressure altitude portion.
if isTempPressValid() {
msg[13] = msg[13] | (1 << 3)
}
// Valid/Enabled: CPU temperature portion.
if isCPUTempValid() {
msg[13] = msg[13] | (1 << 4)
}
// Valid/Enabled: UAT portion.
if globalSettings.UAT_Enabled {
msg[13] = msg[13] | (1 << 5)
}
// Valid/Enabled: ES portion.
if globalSettings.ES_Enabled {
msg[13] = msg[13] | (1 << 6)
}
// Valid/Enabled: GPS Enabled portion.
if globalSettings.GPS_Enabled {
msg[13] = msg[13] | (1 << 7)
}
// Valid/Enabled: AHRS Enabled portion.
if globalSettings.AHRS_Enabled {
msg[12] = 1 << 0
}
// Valid/Enabled: last bit unused.
// Connected hardware: number of radios.
msg[15] = msg[15] | (byte(globalStatus.Devices) & 0x3)
// Connected hardware: RY835AI.
if globalStatus.RY835AI_connected {
msg[15] = msg[15] | (1 << 2)
}
// 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)
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.
@ -360,6 +591,7 @@ func heartBeatSender() {
case <-timer.C:
sendGDL90(makeHeartbeat(), false)
sendGDL90(makeStratuxHeartbeat(), false)
sendGDL90(makeSXHeartbeat(), false)
// sendGDL90(makeTrafficReport())
makeOwnshipReport()
makeOwnshipGeometricAltitudeReport()
@ -434,6 +666,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
@ -466,6 +703,13 @@ func cpuTempMonitor() {
func updateStatus() {
if isGPSValid() {
globalStatus.GPS_satellites_locked = mySituation.Satellites
if mySituation.quality == 2 {
globalStatus.GPS_solution = "DGPS (WAAS)"
} else if mySituation.quality == 1 {
globalStatus.GPS_solution = "3D GPS"
} else {
globalStatus.GPS_solution = "N/A"
}
}
// Update Uptime value
@ -497,19 +741,21 @@ func replayLog(msg string, msgclass int) {
if len(msg) == 0 { // Blank message.
return
}
var fp *os.File
var fp WriteCloser
switch msgclass {
case MSGCLASS_UAT:
fp = uatReplayfp
fp = uatReplayWriter
case MSGCLASS_ES:
fp = esReplayfp
fp = esReplayWriter
case MSGCLASS_GPS:
fp = gpsReplayfp
fp = gpsReplayWriter
case MSGCLASS_AHRS:
fp = ahrsReplayfp
fp = ahrsReplayWriter
case MSGCLASS_DUMP1090:
fp = dump1090Replayfp
fp = dump1090ReplayWriter
}
if fp != nil {
s := makeReplayLogEntry(msg)
fp.Write([]byte(s))
@ -740,6 +986,7 @@ type status struct {
ES_messages_max uint
GPS_satellites_locked uint16
GPS_connected bool
GPS_solution string
RY835AI_connected bool
Uptime int64
CPUTemp float32
@ -749,9 +996,9 @@ var globalSettings settings
var globalStatus status
func defaultSettings() {
globalSettings.UAT_Enabled = true //TODO
globalSettings.ES_Enabled = false //TODO
globalSettings.GPS_Enabled = false //TODO
globalSettings.UAT_Enabled = true
globalSettings.ES_Enabled = true
globalSettings.GPS_Enabled = false
//FIXME: Need to change format below.
globalSettings.NetworkOutputs = []networkConnection{{nil, "", 4000, NETWORK_GDL90_STANDARD | NETWORK_AHRS_GDL90, nil, time.Time{}, time.Time{}, 0}, {nil, "", 49002, NETWORK_AHRS_FFSIM, nil, time.Time{}, time.Time{}, 0}}
globalSettings.AHRS_Enabled = false
@ -806,36 +1053,47 @@ func replayMark(active bool) {
t = fmt.Sprintf("UNPAUSE,%d\n", time.Since(timeStarted).Nanoseconds())
}
if uatReplayfp != nil {
uatReplayfp.Write([]byte(t))
if uatReplayWriter != nil {
uatReplayWriter.Write([]byte(t))
}
if esReplayfp != nil {
esReplayfp.Write([]byte(t))
if esReplayWriter != nil {
esReplayWriter.Write([]byte(t))
}
if gpsReplayfp != nil {
gpsReplayfp.Write([]byte(t))
if gpsReplayWriter != nil {
gpsReplayWriter.Write([]byte(t))
}
if ahrsReplayfp != nil {
ahrsReplayfp.Write([]byte(t))
if ahrsReplayWriter != nil {
ahrsReplayWriter.Write([]byte(t))
}
if dump1090Replayfp != nil {
dump1090Replayfp.Write([]byte(t))
if dump1090ReplayWriter != nil {
dump1090ReplayWriter.Write([]byte(t))
}
}
func openReplay(fn string) (*os.File, error) {
ret, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
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 {
log.Printf("Failed to open log file '%s': %s\n", fn, err.Error())
} else {
timeFmt := "Mon Jan 2 15:04:05 -0700 MST 2006"
fmt.Fprintf(ret, "START,%s,%s\n", timeStarted.Format(timeFmt), time.Now().Format(timeFmt)) // Start time marker.
return nil, err
}
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.
ret.Write([]byte(s))
return ret, err
}
@ -854,10 +1112,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 {
@ -869,11 +1208,13 @@ 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()
@ -881,42 +1222,48 @@ 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 uatfp, err := openReplay(uatReplayLog); err != nil {
if uatwt, err := openReplay(uatReplayLog, !developerMode); err != nil {
globalSettings.ReplayLog = false
} else {
uatReplayfp = uatfp
defer uatReplayfp.Close()
uatReplayWriter = uatwt
defer uatReplayWriter.Close()
}
// 1090ES replay log.
if esfp, err := openReplay(esReplayLog); err != nil {
if eswt, err := openReplay(esReplayLog, !developerMode); err != nil {
globalSettings.ReplayLog = false
} else {
esReplayfp = esfp
defer esReplayfp.Close()
esReplayWriter = eswt
defer esReplayWriter.Close()
}
// GPS replay log.
if gpsfp, err := openReplay(gpsReplayLog); err != nil {
if gpswt, err := openReplay(gpsReplayLog, !developerMode); err != nil {
globalSettings.ReplayLog = false
} else {
gpsReplayfp = gpsfp
defer gpsReplayfp.Close()
gpsReplayWriter = gpswt
defer gpsReplayWriter.Close()
}
// AHRS replay log.
if ahrsfp, err := openReplay(ahrsReplayLog); err != nil {
if ahrswt, err := openReplay(ahrsReplayLog, !developerMode); err != nil {
globalSettings.ReplayLog = false
} else {
ahrsReplayfp = ahrsfp
defer ahrsReplayfp.Close()
ahrsReplayWriter = ahrswt
defer ahrsReplayWriter.Close()
}
// Dump1090 replay log.
if dump1090fp, err := openReplay(dump1090ReplayLog); err != nil {
if dump1090wt, err := openReplay(dump1090ReplayLog, !developerMode); err != nil {
globalSettings.ReplayLog = false
} else {
dump1090Replayfp = dump1090fp
defer dump1090Replayfp.Close()
dump1090ReplayWriter = dump1090wt
defer dump1090ReplayWriter.Close()
}
// Mark the files (whether we're logging or not).
@ -940,16 +1287,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)
}
}
}
}

Wyświetl plik

@ -10,6 +10,7 @@ import (
"log"
"net/http"
"time"
"syscall"
)
type SettingMessage struct {
@ -101,6 +102,21 @@ func handleStatusWS(conn *websocket.Conn) {
}
}
func handleSituationWS(conn *websocket.Conn) {
timer := time.NewTicker(100 * time.Millisecond)
for {
<-timer.C
situationJSON, _ := json.Marshal(&mySituation)
_, err := conn.Write(situationJSON)
if err != nil {
break
}
}
}
// AJAX call - /getStatus. Responds with current global status
// a webservice call for the same data available on the websocket but when only a single update is needed
func handleStatusRequest(w http.ResponseWriter, r *http.Request) {
@ -203,7 +219,7 @@ func handleSettingsSetRequest(w http.ResponseWriter, r *http.Request) {
log.Printf("handleSettingsSetRequest:json: unrecognized key:%s\n", key)
}
}
saveSettings()
saveSettings()
}
}
@ -213,6 +229,18 @@ func handleSettingsSetRequest(w http.ResponseWriter, r *http.Request) {
}
}
func handleShutdownRequest(w http.ResponseWriter, r *http.Request) {
syscall.Sync()
syscall.Reboot(syscall.LINUX_REBOOT_CMD_POWER_OFF)
}
func handleRebootRequest(w http.ResponseWriter, r *http.Request) {
syscall.Sync()
syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART)
}
func managementInterface() {
weatherUpdate = NewUIBroadcaster()
trafficUpdate = NewUIBroadcaster()
@ -225,6 +253,12 @@ func managementInterface() {
Handler: websocket.Handler(handleStatusWS)}
s.ServeHTTP(w, req)
})
http.HandleFunc("/situation",
func(w http.ResponseWriter, req *http.Request) {
s := websocket.Server{
Handler: websocket.Handler(handleSituationWS)}
s.ServeHTTP(w, req)
})
http.HandleFunc("/weather",
func(w http.ResponseWriter, req *http.Request) {
s := websocket.Server{
@ -243,6 +277,8 @@ func managementInterface() {
http.HandleFunc("/getTowers", handleTowersRequest)
http.HandleFunc("/getSettings", handleSettingsGetRequest)
http.HandleFunc("/setSettings", handleSettingsSetRequest)
http.HandleFunc("/shutdown", handleShutdownRequest)
http.HandleFunc("/reboot", handleRebootRequest)
err := http.ListenAndServe(managementAddr, nil)

Wyświetl plik

@ -186,9 +186,13 @@ func refreshConnectedClients() {
func messageQueueSender() {
secondTimer := time.NewTicker(5 * time.Second)
<<<<<<< HEAD
queueTimer := time.NewTicker(1 * time.Second)
// queueTimer := time.NewTicker(400 * time.Microsecond) // 2500 msg/sec
=======
queueTimer := time.NewTicker(100 * time.Millisecond)
>>>>>>> origin/master
var lastQueueTimeChange time.Time // Reevaluate send frequency every 5 seconds.
for {
@ -210,6 +214,7 @@ func messageQueueSender() {
outSockets[k] = tmpConn
}
}
<<<<<<< HEAD
if time.Since(lastQueueTimeChange) >= 5 * time.Second {
var pd float64
if averageSendableQueueSize > 0.0 && len(outSockets) > 0 {
@ -220,6 +225,19 @@ func messageQueueSender() {
}
queueTimer.Stop()
queueTimer = time.NewTicker(time.Duration(pd * 1000000000.0) * time.Nanosecond)
=======
if time.Since(lastQueueTimeChange) >= 5*time.Second {
var pd float64
if averageSendableQueueSize > 0.0 && len(outSockets) > 0 {
averageSendableQueueSize = averageSendableQueueSize / float64(len(outSockets)) // It's a total, not an average, up until this point.
pd = math.Max(float64(1.0/2500.0), float64(1.0/(4.0*averageSendableQueueSize))) // Say 250ms is enough to get through the whole queue.
} else {
pd = float64(1.0 / 0.1) // 100ms.
}
queueTimer.Stop()
queueTimer = time.NewTicker(time.Duration(pd*1000000000.0) * time.Nanosecond)
>>>>>>> origin/master
lastQueueTimeChange = time.Now()
}
netMutex.Unlock()

Wyświetl plik

@ -256,11 +256,11 @@ func (e *ES) writeID() error {
func (u *UAT) shutdown() {
log.Println("Entered UAT shutdown() ...")
close(uat_shutdown) // signal to shutdown
log.Println("UAT shutdown(): closing device ...")
u.dev.Close() // preempt the blocking ReadSync call
log.Println("UAT shutdown(): calling uat_wg.Wait() ...")
uat_wg.Wait() // Wait for the goroutine to shutdown
log.Println("UAT shutdown(): uat_wg.Wait() returned...")
log.Println("UAT shutdown(): closing device ...")
u.dev.Close() // preempt the blocking ReadSync call
}
func (e *ES) shutdown() {

Wyświetl plik

@ -65,7 +65,7 @@ type TrafficInfo struct {
Position_valid bool
Alt uint32
Alt int32
Track uint16
Speed uint16
@ -138,12 +138,24 @@ func makeTrafficReport(ti TrafficInfo) {
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.
// 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
//
// Algo example at: https://play.golang.org/p/VXCckSdsvT
//
var alt int16
if ti.Alt < -1000 || ti.Alt > 101350 {
alt = 0x0FFF
} else {
// output guaranteed to be between 0x0000 and 0x0FFE
alt = int16((ti.Alt / 25) + 40)
}
msg[11] = byte((alt & 0xFF0) >> 4) // Altitude.
msg[12] = byte((alt & 0x00F) << 4)
@ -237,9 +249,9 @@ func parseDownlinkReport(s string) {
ti.Lng = lng
ti.Position_valid = position_valid
raw_alt := (uint32(frame[10]) << 4) | ((uint32(frame[11]) & 0xf0) >> 4)
raw_alt := (int32(frame[10]) << 4) | ((int32(frame[11]) & 0xf0) >> 4)
// alt_geo := false // Barometric if not geometric.
alt := uint32(0)
alt := int32(0)
if raw_alt != 0 {
// alt_geo = (uint8(frame[9]) & 1) != 0
alt = ((raw_alt - 1) * 25) - 1000
@ -460,7 +472,7 @@ func esListen() {
//log.Printf("icao=%s, icaoDec=%d, alt=%s, lat=%s, lng=%s\n", icao, icaoDec, alt, lat, lng)
if valid_change {
ti.Alt = uint32(altFloat)
ti.Alt = int32(altFloat)
ti.Lat = float32(latFloat)
ti.Lng = float32(lngFloat)
ti.Position_valid = true

Wyświetl plik

@ -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.
@ -53,3 +52,129 @@ 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.
### 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"
}
```
* `ws://192.168.10.1/traffic` - traffic stream. On initial connect, all currently tracked traffic targets are dumped. Updates are streamed as they are received. Example output:
Initial connect:
```json
{"Icao_addr":2837120,"OnGround":false,"Lat":42.19293,"Lng":-83.92148,"Position_valid":true,"Alt":3400,"Track":9,"Speed":92,"Speed_valid":true,"Vvel":0,"Tail":"","Last_seen":"2015-12-22T21:29:22.241048727Z","Last_source":2}
{"Icao_addr":2836155,"OnGround":false,"Lat":42.122932,"Lng":-84.17615,"Position_valid":true,"Alt":2800,"Track":158,"Speed":105,"Speed_valid":true,"Vvel":0,"Tail":"","Last_seen":"2015-12-22T21:29:22.241543881Z","Last_source":2}
```
Subsequent update (2837120 = 2B4A80 reports a newer position, altitude increased from 2,800' to 3,400'):
```json
{"Icao_addr":2837120,"OnGround":false,"Lat":42.193336,"Lng":-83.92136,"Position_valid":true,"Alt":3400,"Track":9,"Speed":92,"Speed_valid":true,"Vvel":0,"Tail":"","Last_seen":"2015-12-22T21:29:22.252914555Z","Last_source":2}
```

14
test/Makefile 100644
Wyświetl plik

@ -0,0 +1,14 @@
ifeq "$(CIRCLECI)" "true"
#
else
$(if $(GOROOT),,$(error GOROOT is not set!))
endif
SRCS = $(wildcard *.go)
DEST = $(patsubst %.go,%,$(SRCS))
all: $(DEST)
%: %.go
go build $<

Wyświetl plik

@ -0,0 +1,72 @@
package main
import (
"fmt"
"github.com/kidoman/embd"
_ "github.com/kidoman/embd/host/all"
"github.com/kidoman/embd/sensor/bmp180"
)
var i2cbus embd.I2CBus
var myBMP180 *bmp180.BMP180
func readBMP180() (float64, float64, error) { // ºCelsius, Meters
temp, err := myBMP180.Temperature()
if err != nil {
return temp, 0.0, err
}
altitude, err := myBMP180.Altitude()
altitude = float64(1/0.3048) * altitude // Convert meters to feet.
if err != nil {
return temp, altitude, err
}
return temp, altitude, nil
}
func initBMP180() error {
myBMP180 = bmp180.New(i2cbus) //TODO: error checking.
return nil
}
func initI2C() error {
i2cbus = embd.NewI2CBus(1) //TODO: error checking.
return nil
}
// Unused at the moment. 5 second update, since read functions in bmp180 are slow.
func tempAndPressureReader() {
// Read temperature and pressure altitude.
temp, alt, err_bmp180 := readBMP180()
// Process.
if err_bmp180 != nil {
fmt.Printf("readBMP180(): %s\n", err_bmp180.Error())
} else {
fmt.Printf("Temp %f Alt %f\n",temp,alt)
}
}
func initAHRS() error {
if err := initI2C(); err != nil { // I2C bus.
return err
}
if err := initBMP180(); err != nil { // I2C temperature and pressure altitude.
i2cbus.Close()
return err
}
go tempAndPressureReader()
return nil
}
func main() {
fmt.Printf("Hello world!\n")
initAHRS()
for {
tempAndPressureReader()
}
}

Wyświetl plik

@ -49,6 +49,7 @@ function StatusCtrl($rootScope, $scope, $state, $http, $interval) {
$scope.ES_messages_last_minute = status.ES_messages_last_minute;
$scope.ES_messages_max = status.ES_messages_max;
$scope.GPS_satellites_locked = status.GPS_satellites_locked;
$scope.GPS_solution = status.GPS_solution;
$scope.RY835AI_connected = status.RY835AI_connected;
var uptime = status.Uptime;

Wyświetl plik

@ -56,6 +56,10 @@
<label class="col-xs-6">UAT Towers:</label>
<span class="col-xs-6">{{UAT_Towers}}</span>
</div>
<div class="row" ng-class="{'section_invisible': !visible_gps}">
<label class="col-xs-6">GPS solution:</label>
<span class="col-xs-6">{{GPS_solution}}</span>
</div>
<div class="row" ng-class="{'section_invisible': !visible_gps}">
<label class="col-xs-6">GPS satellites:</label>
<span class="col-xs-6">{{GPS_satellites_locked}}</span>
@ -81,4 +85,6 @@
</div>
</div>
</div>
<a class="btn btn-warning" href="/reboot">REBOOT</a>
<a class="btn btn-danger" href="/shutdown">SHUTDOWN</a>
</div>