kopia lustrzana https://github.com/cyoung/stratux
Merge remote-tracking branch 'origin/master' into powersave
# Conflicts: # main/network.gopull/166/head
commit
268bc227c9
5
Makefile
5
Makefile
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
```
|
|
@ -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 $<
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
Ładowanie…
Reference in New Issue