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"
|
ifeq "$(CIRCLECI)" "true"
|
||||||
BUILDINFO=
|
BUILDINFO=
|
||||||
else
|
else
|
||||||
BUILDINFO=-ldflags "-X main.stratuxVersion=`git describe --tags --abbrev=0` -X main.stratuxBuild=`git log -n 1 --pretty=%H`"
|
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
|
endif
|
||||||
|
|
||||||
all:
|
all:
|
||||||
|
@ -10,8 +12,9 @@ all:
|
||||||
go get -t -d -v ./...
|
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
|
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:
|
test:
|
||||||
sh -c true
|
make -C test
|
||||||
|
|
||||||
www:
|
www:
|
||||||
mkdir -p /var/www
|
mkdir -p /var/www
|
||||||
|
|
|
@ -10,20 +10,20 @@ Use Pi 2 and ForeFlight 7.3.1 (1792) (Sep 18, 2015).
|
||||||
|
|
||||||
Supported WiFi adapters:
|
Supported WiFi adapters:
|
||||||
* Edimax EW-7811Un
|
* Edimax EW-7811Un
|
||||||
* TP-LINK TL-WN725N
|
|
||||||
|
|
||||||
Tested RTL-SDR:
|
Tested RTL-SDR:
|
||||||
|
* NooElec NESDR Nano 2 (best)
|
||||||
* NooElec NESDR Mini 2
|
* NooElec NESDR Mini 2
|
||||||
* Generic R820T (degraded performance)
|
* Generic R820T (degraded performance)
|
||||||
|
|
||||||
Apps with stratux recognition/support:
|
Apps with stratux recognition/support:
|
||||||
* Seattle Avionics FlyQ EFB 2.1.1+.
|
* Seattle Avionics FlyQ EFB 2.1.1+.
|
||||||
* AvNav EFB 2.0.0+.
|
* AvNav EFB 2.0.0+.
|
||||||
|
* Naviator.
|
||||||
|
* WingX Pro7 8.6.2+
|
||||||
|
|
||||||
Tested weather/traffic displays:
|
Tested weather/traffic displays:
|
||||||
* ForeFlight 7+ - weather, traffic, AHRS.
|
* ForeFlight 7+ - weather, traffic, AHRS.
|
||||||
* Naviator - weather, traffic, AHRS.
|
|
||||||
* WingX - weather & traffic.
|
|
||||||
* Avare
|
* Avare
|
||||||
* iFly 740 - weather & traffic.
|
* iFly 740 - weather & traffic.
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,7 @@ apt-get install -y git cmake libusb-1.0-0.dev build-essential
|
||||||
|
|
||||||
cd /root
|
cd /root
|
||||||
rm -rf rtl-sdr
|
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
|
cd rtl-sdr
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
|
@ -131,7 +131,8 @@ echo " - Stratux"
|
||||||
|
|
||||||
scp_in_to_qemu /root/spindle/gen_gdl90 /tmp/gen_gdl90
|
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
|
cd /root
|
||||||
|
|
||||||
apt-get install -y mercurial
|
apt-get install -y mercurial
|
||||||
|
|
|
@ -2,8 +2,10 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"compress/gzip"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -23,15 +25,12 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
configLocation = "/etc/stratux.conf"
|
configLocation = "/etc/stratux.conf"
|
||||||
|
indexFilename = "/var/log/stratux/LOGINDEX"
|
||||||
managementAddr = ":80"
|
managementAddr = ":80"
|
||||||
debugLog = "/var/log/stratux.log"
|
debugLog = "/var/log/stratux.log"
|
||||||
maxDatagramSize = 8192
|
maxDatagramSize = 8192
|
||||||
maxUserMsgQueueSize = 25000 // About 10MB per port per connected client.
|
maxUserMsgQueueSize = 25000 // About 10MB per port per connected client.
|
||||||
uatReplayLog = "/var/log/stratux-uat.log"
|
logDirectory = "/var/log/stratux"
|
||||||
esReplayLog = "/var/log/stratux-es.log"
|
|
||||||
gpsReplayLog = "/var/log/stratux-gps.log"
|
|
||||||
ahrsReplayLog = "/var/log/stratux-ahrs.log"
|
|
||||||
dump1090ReplayLog = "/var/log/stratux-dump1090.log"
|
|
||||||
|
|
||||||
UPLINK_BLOCK_DATA_BITS = 576
|
UPLINK_BLOCK_DATA_BITS = 576
|
||||||
UPLINK_BLOCK_BITS = (UPLINK_BLOCK_DATA_BITS + 160)
|
UPLINK_BLOCK_BITS = (UPLINK_BLOCK_DATA_BITS + 160)
|
||||||
|
@ -62,6 +61,12 @@ const (
|
||||||
TRACK_RESOLUTION = float32(360.0 / 256.0)
|
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 stratuxBuild string
|
||||||
var stratuxVersion string
|
var stratuxVersion string
|
||||||
|
|
||||||
|
@ -71,12 +76,24 @@ var Crc16Table [256]uint16
|
||||||
// Current AHRS, pressure altitude, etc.
|
// Current AHRS, pressure altitude, etc.
|
||||||
var mySituation SituationData
|
var mySituation SituationData
|
||||||
|
|
||||||
|
type WriteCloser interface {
|
||||||
|
io.Writer
|
||||||
|
io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReadCloser interface {
|
||||||
|
io.Reader
|
||||||
|
io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
// File handles for replay logging.
|
// File handles for replay logging.
|
||||||
var uatReplayfp *os.File
|
var uatReplayWriter WriteCloser
|
||||||
var esReplayfp *os.File
|
var esReplayWriter WriteCloser
|
||||||
var gpsReplayfp *os.File
|
var gpsReplayWriter WriteCloser
|
||||||
var ahrsReplayfp *os.File
|
var ahrsReplayWriter WriteCloser
|
||||||
var dump1090Replayfp *os.File
|
var dump1090ReplayWriter WriteCloser
|
||||||
|
|
||||||
|
var developerMode bool
|
||||||
|
|
||||||
type msg struct {
|
type msg struct {
|
||||||
MessageClass uint
|
MessageClass uint
|
||||||
|
@ -105,6 +122,49 @@ type ADSBTower struct {
|
||||||
|
|
||||||
var ADSBTowers map[string]ADSBTower // Running list of all towers seen. (lat,lng) -> ADSBTower
|
var ADSBTowers map[string]ADSBTower // Running list of all towers seen. (lat,lng) -> ADSBTower
|
||||||
|
|
||||||
|
func constructFilenames() {
|
||||||
|
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.
|
// Construct the CRC table. Adapted from FAA ref above.
|
||||||
func crcInit() {
|
func crcInit() {
|
||||||
var i uint16
|
var i uint16
|
||||||
|
@ -207,14 +267,16 @@ func makeOwnshipReport() bool {
|
||||||
// alt := uint16(0xFFF) // 0xFFF "invalid altitude."
|
// alt := uint16(0xFFF) // 0xFFF "invalid altitude."
|
||||||
|
|
||||||
var alt uint16
|
var alt uint16
|
||||||
if isTempPressValid() {
|
var altf float64
|
||||||
alt = uint16(mySituation.Pressure_alt)
|
|
||||||
} else {
|
|
||||||
alt = uint16(mySituation.Alt) //FIXME: This should not be here.
|
|
||||||
}
|
|
||||||
alt = (alt + 1000) / 25
|
|
||||||
|
|
||||||
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[11] = byte((alt & 0xFF0) >> 4) // Altitude.
|
||||||
msg[12] = byte((alt & 0x00F) << 4)
|
msg[12] = byte((alt & 0x00F) << 4)
|
||||||
|
@ -233,7 +295,7 @@ func makeOwnshipReport() bool {
|
||||||
msg[14] = byte((gdSpeed & 0xFF0) >> 4)
|
msg[14] = byte((gdSpeed & 0xFF0) >> 4)
|
||||||
msg[15] = byte((gdSpeed & 0x00F) << 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.
|
//TODO: 0x800 = no information available.
|
||||||
// verticalVelocity should fit in 12 bits.
|
// verticalVelocity should fit in 12 bits.
|
||||||
msg[15] = msg[15] | byte((verticalVelocity&0x0F00)>>8)
|
msg[15] = msg[15] | byte((verticalVelocity&0x0F00)>>8)
|
||||||
|
@ -284,6 +346,175 @@ func makeOwnshipGeometricAltitudeReport() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
"SX" Stratux GDL90 message.
|
||||||
|
http://hiltonsoftware.com/stratux/StratuxStatusMessage-V01.pdf
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
func makeSXHeartbeat() []byte {
|
||||||
|
msg := make([]byte, 29)
|
||||||
|
msg[0] = 'S'
|
||||||
|
msg[1] = 'X'
|
||||||
|
msg[2] = 1
|
||||||
|
|
||||||
|
msg[3] = 1 // "message version".
|
||||||
|
|
||||||
|
// 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.
|
"Stratux" GDL90 message.
|
||||||
|
@ -360,6 +591,7 @@ func heartBeatSender() {
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
sendGDL90(makeHeartbeat(), false)
|
sendGDL90(makeHeartbeat(), false)
|
||||||
sendGDL90(makeStratuxHeartbeat(), false)
|
sendGDL90(makeStratuxHeartbeat(), false)
|
||||||
|
sendGDL90(makeSXHeartbeat(), false)
|
||||||
// sendGDL90(makeTrafficReport())
|
// sendGDL90(makeTrafficReport())
|
||||||
makeOwnshipReport()
|
makeOwnshipReport()
|
||||||
makeOwnshipGeometricAltitudeReport()
|
makeOwnshipGeometricAltitudeReport()
|
||||||
|
@ -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.
|
cpuTempMonitor() reads the RPi board temperature every second and updates it in globalStatus.
|
||||||
This is broken out into its own function (run as its own goroutine) because the RPi temperature
|
This is broken out into its own function (run as its own goroutine) because the RPi temperature
|
||||||
|
@ -466,6 +703,13 @@ func cpuTempMonitor() {
|
||||||
func updateStatus() {
|
func updateStatus() {
|
||||||
if isGPSValid() {
|
if isGPSValid() {
|
||||||
globalStatus.GPS_satellites_locked = mySituation.Satellites
|
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
|
// Update Uptime value
|
||||||
|
@ -497,19 +741,21 @@ func replayLog(msg string, msgclass int) {
|
||||||
if len(msg) == 0 { // Blank message.
|
if len(msg) == 0 { // Blank message.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var fp *os.File
|
var fp WriteCloser
|
||||||
|
|
||||||
switch msgclass {
|
switch msgclass {
|
||||||
case MSGCLASS_UAT:
|
case MSGCLASS_UAT:
|
||||||
fp = uatReplayfp
|
fp = uatReplayWriter
|
||||||
case MSGCLASS_ES:
|
case MSGCLASS_ES:
|
||||||
fp = esReplayfp
|
fp = esReplayWriter
|
||||||
case MSGCLASS_GPS:
|
case MSGCLASS_GPS:
|
||||||
fp = gpsReplayfp
|
fp = gpsReplayWriter
|
||||||
case MSGCLASS_AHRS:
|
case MSGCLASS_AHRS:
|
||||||
fp = ahrsReplayfp
|
fp = ahrsReplayWriter
|
||||||
case MSGCLASS_DUMP1090:
|
case MSGCLASS_DUMP1090:
|
||||||
fp = dump1090Replayfp
|
fp = dump1090ReplayWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
if fp != nil {
|
if fp != nil {
|
||||||
s := makeReplayLogEntry(msg)
|
s := makeReplayLogEntry(msg)
|
||||||
fp.Write([]byte(s))
|
fp.Write([]byte(s))
|
||||||
|
@ -740,6 +986,7 @@ type status struct {
|
||||||
ES_messages_max uint
|
ES_messages_max uint
|
||||||
GPS_satellites_locked uint16
|
GPS_satellites_locked uint16
|
||||||
GPS_connected bool
|
GPS_connected bool
|
||||||
|
GPS_solution string
|
||||||
RY835AI_connected bool
|
RY835AI_connected bool
|
||||||
Uptime int64
|
Uptime int64
|
||||||
CPUTemp float32
|
CPUTemp float32
|
||||||
|
@ -749,9 +996,9 @@ var globalSettings settings
|
||||||
var globalStatus status
|
var globalStatus status
|
||||||
|
|
||||||
func defaultSettings() {
|
func defaultSettings() {
|
||||||
globalSettings.UAT_Enabled = true //TODO
|
globalSettings.UAT_Enabled = true
|
||||||
globalSettings.ES_Enabled = false //TODO
|
globalSettings.ES_Enabled = true
|
||||||
globalSettings.GPS_Enabled = false //TODO
|
globalSettings.GPS_Enabled = false
|
||||||
//FIXME: Need to change format below.
|
//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.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
|
globalSettings.AHRS_Enabled = false
|
||||||
|
@ -806,36 +1053,47 @@ func replayMark(active bool) {
|
||||||
t = fmt.Sprintf("UNPAUSE,%d\n", time.Since(timeStarted).Nanoseconds())
|
t = fmt.Sprintf("UNPAUSE,%d\n", time.Since(timeStarted).Nanoseconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
if uatReplayfp != nil {
|
if uatReplayWriter != nil {
|
||||||
uatReplayfp.Write([]byte(t))
|
uatReplayWriter.Write([]byte(t))
|
||||||
}
|
}
|
||||||
|
|
||||||
if esReplayfp != nil {
|
if esReplayWriter != nil {
|
||||||
esReplayfp.Write([]byte(t))
|
esReplayWriter.Write([]byte(t))
|
||||||
}
|
}
|
||||||
|
|
||||||
if gpsReplayfp != nil {
|
if gpsReplayWriter != nil {
|
||||||
gpsReplayfp.Write([]byte(t))
|
gpsReplayWriter.Write([]byte(t))
|
||||||
}
|
}
|
||||||
|
|
||||||
if ahrsReplayfp != nil {
|
if ahrsReplayWriter != nil {
|
||||||
ahrsReplayfp.Write([]byte(t))
|
ahrsReplayWriter.Write([]byte(t))
|
||||||
}
|
}
|
||||||
|
|
||||||
if dump1090Replayfp != nil {
|
if dump1090ReplayWriter != nil {
|
||||||
dump1090Replayfp.Write([]byte(t))
|
dump1090ReplayWriter.Write([]byte(t))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func openReplay(fn string) (*os.File, error) {
|
func openReplay(fn string, compressed bool) (WriteCloser, error) {
|
||||||
ret, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
fp, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to open log file '%s': %s\n", fn, err.Error())
|
log.Printf("Failed to open log file '%s': %s\n", fn, err.Error())
|
||||||
} else {
|
return nil, err
|
||||||
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.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
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() {
|
func main() {
|
||||||
|
|
||||||
|
// replayESFilename := flag.String("eslog", "none", "ES Log filename")
|
||||||
|
replayUATFilename := flag.String("uatlog", "none", "UAT Log filename")
|
||||||
|
develFlag := flag.Bool("developer", false, "Developer mode")
|
||||||
|
replayFlag := flag.Bool("replay", false, "Replay file flag")
|
||||||
|
replaySpeed := flag.Int("speed", 1, "Replay speed multiplier")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
timeStarted = time.Now()
|
timeStarted = time.Now()
|
||||||
runtime.GOMAXPROCS(runtime.NumCPU()) // redundant with Go v1.5+ compiler
|
runtime.GOMAXPROCS(runtime.NumCPU()) // redundant with Go v1.5+ compiler
|
||||||
|
|
||||||
|
if *develFlag == true {
|
||||||
|
log.Printf("Developer mode flag true!\n")
|
||||||
|
developerMode = true
|
||||||
|
}
|
||||||
|
|
||||||
// Duplicate log.* output to debugLog.
|
// Duplicate log.* output to debugLog.
|
||||||
fp, err := os.OpenFile(debugLog, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
fp, err := os.OpenFile(debugLog, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -869,11 +1208,13 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Stratux %s (%s) starting.\n", stratuxVersion, stratuxBuild)
|
log.Printf("Stratux %s (%s) starting.\n", stratuxVersion, stratuxBuild)
|
||||||
|
constructFilenames()
|
||||||
|
|
||||||
ADSBTowers = make(map[string]ADSBTower)
|
ADSBTowers = make(map[string]ADSBTower)
|
||||||
MsgLog = make([]msg, 0)
|
MsgLog = make([]msg, 0)
|
||||||
|
|
||||||
crcInit() // Initialize CRC16 table.
|
crcInit() // Initialize CRC16 table.
|
||||||
|
|
||||||
sdrInit()
|
sdrInit()
|
||||||
initTraffic()
|
initTraffic()
|
||||||
|
|
||||||
|
@ -881,42 +1222,48 @@ func main() {
|
||||||
|
|
||||||
readSettings()
|
readSettings()
|
||||||
|
|
||||||
|
// Disable replay logs when replaying - so that messages replay data isn't copied into the logs.
|
||||||
|
// Override after reading in the settings.
|
||||||
|
if *replayFlag == true {
|
||||||
|
log.Printf("Replay file %s\n", *replayUATFilename)
|
||||||
|
globalSettings.ReplayLog = true
|
||||||
|
}
|
||||||
|
|
||||||
// Set up the replay logs. Keep these files open in any case, even if replay logging is disabled.
|
// Set up the replay logs. Keep these files open in any case, even if replay logging is disabled.
|
||||||
|
|
||||||
// UAT replay log.
|
if uatwt, err := openReplay(uatReplayLog, !developerMode); err != nil {
|
||||||
if uatfp, err := openReplay(uatReplayLog); err != nil {
|
|
||||||
globalSettings.ReplayLog = false
|
globalSettings.ReplayLog = false
|
||||||
} else {
|
} else {
|
||||||
uatReplayfp = uatfp
|
uatReplayWriter = uatwt
|
||||||
defer uatReplayfp.Close()
|
defer uatReplayWriter.Close()
|
||||||
}
|
}
|
||||||
// 1090ES replay log.
|
// 1090ES replay log.
|
||||||
if esfp, err := openReplay(esReplayLog); err != nil {
|
if eswt, err := openReplay(esReplayLog, !developerMode); err != nil {
|
||||||
globalSettings.ReplayLog = false
|
globalSettings.ReplayLog = false
|
||||||
} else {
|
} else {
|
||||||
esReplayfp = esfp
|
esReplayWriter = eswt
|
||||||
defer esReplayfp.Close()
|
defer esReplayWriter.Close()
|
||||||
}
|
}
|
||||||
// GPS replay log.
|
// GPS replay log.
|
||||||
if gpsfp, err := openReplay(gpsReplayLog); err != nil {
|
if gpswt, err := openReplay(gpsReplayLog, !developerMode); err != nil {
|
||||||
globalSettings.ReplayLog = false
|
globalSettings.ReplayLog = false
|
||||||
} else {
|
} else {
|
||||||
gpsReplayfp = gpsfp
|
gpsReplayWriter = gpswt
|
||||||
defer gpsReplayfp.Close()
|
defer gpsReplayWriter.Close()
|
||||||
}
|
}
|
||||||
// AHRS replay log.
|
// AHRS replay log.
|
||||||
if ahrsfp, err := openReplay(ahrsReplayLog); err != nil {
|
if ahrswt, err := openReplay(ahrsReplayLog, !developerMode); err != nil {
|
||||||
globalSettings.ReplayLog = false
|
globalSettings.ReplayLog = false
|
||||||
} else {
|
} else {
|
||||||
ahrsReplayfp = ahrsfp
|
ahrsReplayWriter = ahrswt
|
||||||
defer ahrsReplayfp.Close()
|
defer ahrsReplayWriter.Close()
|
||||||
}
|
}
|
||||||
// Dump1090 replay log.
|
// Dump1090 replay log.
|
||||||
if dump1090fp, err := openReplay(dump1090ReplayLog); err != nil {
|
if dump1090wt, err := openReplay(dump1090ReplayLog, !developerMode); err != nil {
|
||||||
globalSettings.ReplayLog = false
|
globalSettings.ReplayLog = false
|
||||||
} else {
|
} else {
|
||||||
dump1090Replayfp = dump1090fp
|
dump1090ReplayWriter = dump1090wt
|
||||||
defer dump1090Replayfp.Close()
|
defer dump1090ReplayWriter.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the files (whether we're logging or not).
|
// Mark the files (whether we're logging or not).
|
||||||
|
@ -940,16 +1287,32 @@ func main() {
|
||||||
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
|
||||||
for {
|
if *replayFlag == true {
|
||||||
buf, err := reader.ReadString('\n')
|
fp := openReplayFile(*replayUATFilename)
|
||||||
if err != nil {
|
|
||||||
log.Printf("lost stdin.\n")
|
playSpeed := uint64(*replaySpeed)
|
||||||
break
|
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 {
|
} else {
|
||||||
relayMessage(msgtype, o)
|
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"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SettingMessage struct {
|
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
|
// 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
|
// 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) {
|
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)
|
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() {
|
func managementInterface() {
|
||||||
weatherUpdate = NewUIBroadcaster()
|
weatherUpdate = NewUIBroadcaster()
|
||||||
trafficUpdate = NewUIBroadcaster()
|
trafficUpdate = NewUIBroadcaster()
|
||||||
|
@ -225,6 +253,12 @@ func managementInterface() {
|
||||||
Handler: websocket.Handler(handleStatusWS)}
|
Handler: websocket.Handler(handleStatusWS)}
|
||||||
s.ServeHTTP(w, req)
|
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",
|
http.HandleFunc("/weather",
|
||||||
func(w http.ResponseWriter, req *http.Request) {
|
func(w http.ResponseWriter, req *http.Request) {
|
||||||
s := websocket.Server{
|
s := websocket.Server{
|
||||||
|
@ -243,6 +277,8 @@ func managementInterface() {
|
||||||
http.HandleFunc("/getTowers", handleTowersRequest)
|
http.HandleFunc("/getTowers", handleTowersRequest)
|
||||||
http.HandleFunc("/getSettings", handleSettingsGetRequest)
|
http.HandleFunc("/getSettings", handleSettingsGetRequest)
|
||||||
http.HandleFunc("/setSettings", handleSettingsSetRequest)
|
http.HandleFunc("/setSettings", handleSettingsSetRequest)
|
||||||
|
http.HandleFunc("/shutdown", handleShutdownRequest)
|
||||||
|
http.HandleFunc("/reboot", handleRebootRequest)
|
||||||
|
|
||||||
err := http.ListenAndServe(managementAddr, nil)
|
err := http.ListenAndServe(managementAddr, nil)
|
||||||
|
|
||||||
|
|
|
@ -186,9 +186,13 @@ func refreshConnectedClients() {
|
||||||
|
|
||||||
func messageQueueSender() {
|
func messageQueueSender() {
|
||||||
secondTimer := time.NewTicker(5 * time.Second)
|
secondTimer := time.NewTicker(5 * time.Second)
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
queueTimer := time.NewTicker(1 * time.Second)
|
queueTimer := time.NewTicker(1 * time.Second)
|
||||||
// queueTimer := time.NewTicker(400 * time.Microsecond) // 2500 msg/sec
|
// 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.
|
var lastQueueTimeChange time.Time // Reevaluate send frequency every 5 seconds.
|
||||||
for {
|
for {
|
||||||
|
@ -210,6 +214,7 @@ func messageQueueSender() {
|
||||||
outSockets[k] = tmpConn
|
outSockets[k] = tmpConn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
<<<<<<< HEAD
|
||||||
if time.Since(lastQueueTimeChange) >= 5 * time.Second {
|
if time.Since(lastQueueTimeChange) >= 5 * time.Second {
|
||||||
var pd float64
|
var pd float64
|
||||||
if averageSendableQueueSize > 0.0 && len(outSockets) > 0 {
|
if averageSendableQueueSize > 0.0 && len(outSockets) > 0 {
|
||||||
|
@ -220,6 +225,19 @@ func messageQueueSender() {
|
||||||
}
|
}
|
||||||
queueTimer.Stop()
|
queueTimer.Stop()
|
||||||
queueTimer = time.NewTicker(time.Duration(pd * 1000000000.0) * time.Nanosecond)
|
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()
|
lastQueueTimeChange = time.Now()
|
||||||
}
|
}
|
||||||
netMutex.Unlock()
|
netMutex.Unlock()
|
||||||
|
|
|
@ -256,11 +256,11 @@ func (e *ES) writeID() error {
|
||||||
func (u *UAT) shutdown() {
|
func (u *UAT) shutdown() {
|
||||||
log.Println("Entered UAT shutdown() ...")
|
log.Println("Entered UAT shutdown() ...")
|
||||||
close(uat_shutdown) // signal to 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() ...")
|
log.Println("UAT shutdown(): calling uat_wg.Wait() ...")
|
||||||
uat_wg.Wait() // Wait for the goroutine to shutdown
|
uat_wg.Wait() // Wait for the goroutine to shutdown
|
||||||
log.Println("UAT shutdown(): uat_wg.Wait() returned...")
|
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() {
|
func (e *ES) shutdown() {
|
||||||
|
|
|
@ -65,7 +65,7 @@ type TrafficInfo struct {
|
||||||
|
|
||||||
Position_valid bool
|
Position_valid bool
|
||||||
|
|
||||||
Alt uint32
|
Alt int32
|
||||||
|
|
||||||
Track uint16
|
Track uint16
|
||||||
Speed uint16
|
Speed uint16
|
||||||
|
@ -138,12 +138,24 @@ func makeTrafficReport(ti TrafficInfo) {
|
||||||
msg[9] = tmp[1] // Longitude.
|
msg[9] = tmp[1] // Longitude.
|
||||||
msg[10] = tmp[2] // Longitude.
|
msg[10] = tmp[2] // Longitude.
|
||||||
|
|
||||||
//Altitude: OK
|
// Altitude: OK
|
||||||
//TODO: 0xFFF "invalid altitude."
|
// GDL 90 Data Interface Specification examples:
|
||||||
alt := uint16(ti.Alt)
|
// where 1,000 foot offset and 25 foot resolution (1,000 / 25 = 40)
|
||||||
alt = (alt + 1000) / 25
|
// -1,000 feet 0x000
|
||||||
alt = alt & 0xFFF // Should fit in 12 bits.
|
// 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[11] = byte((alt & 0xFF0) >> 4) // Altitude.
|
||||||
msg[12] = byte((alt & 0x00F) << 4)
|
msg[12] = byte((alt & 0x00F) << 4)
|
||||||
|
|
||||||
|
@ -237,9 +249,9 @@ func parseDownlinkReport(s string) {
|
||||||
ti.Lng = lng
|
ti.Lng = lng
|
||||||
ti.Position_valid = position_valid
|
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_geo := false // Barometric if not geometric.
|
||||||
alt := uint32(0)
|
alt := int32(0)
|
||||||
if raw_alt != 0 {
|
if raw_alt != 0 {
|
||||||
// alt_geo = (uint8(frame[9]) & 1) != 0
|
// alt_geo = (uint8(frame[9]) & 1) != 0
|
||||||
alt = ((raw_alt - 1) * 25) - 1000
|
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)
|
//log.Printf("icao=%s, icaoDec=%d, alt=%s, lat=%s, lng=%s\n", icao, icaoDec, alt, lat, lng)
|
||||||
if valid_change {
|
if valid_change {
|
||||||
ti.Alt = uint32(altFloat)
|
ti.Alt = int32(altFloat)
|
||||||
ti.Lat = float32(latFloat)
|
ti.Lat = float32(latFloat)
|
||||||
ti.Lng = float32(lngFloat)
|
ti.Lng = float32(lngFloat)
|
||||||
ti.Position_valid = true
|
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.
|
network and a DHCP lease is issued to the device, the IP of the DHCP lease is added to the list of clients receiving GDL90 messages.
|
||||||
|
|
||||||
|
|
||||||
The GDL90 is "standard" with the exception of two non-standard GDL90-style messages: 0xCC (stratux heartbeat) and 0x4C (AHRS report).
|
The GDL90 is "standard" with the exception of three non-standard GDL90-style messages: `0xCC` (stratux heartbeat), `0x5358` (another stratux heartbeat), and `0x4C` (AHRS report).
|
||||||
|
|
||||||
|
|
||||||
### How to recognize stratux
|
### How to recognize stratux
|
||||||
|
|
||||||
In order of preference:
|
In order of preference:
|
||||||
|
|
||||||
1. Look for 0xCC heartbeat message. This is sent at the same time as the GDL90 heartbeat (0x00) message.
|
1. Look for `0xCC` (or `0x5358`) GDL90 heartbeat message. This is sent at the same time as the GDL90 heartbeat (0x00) message.
|
||||||
2. Look for Wi-Fi network that **starts with** "stratux".
|
2. Look for Wi-Fi network that **starts with** "stratux".
|
||||||
3. Detect 192.168.10.0/24 Wi-Fi connection, verify stratux status with JSON response from ws://192.168.10.1/status.
|
3. Detect 192.168.10.0/24 Wi-Fi connection, verify stratux status with JSON response from ws://192.168.10.1/status.
|
||||||
|
|
||||||
|
@ -52,4 +51,130 @@ Traffic information roughly follows this path:
|
||||||
|
|
||||||
When traffic information is being received both from UAT and 1090ES sources, it is not uncommon to see a flip/flop in tail numbers on targets.
|
When traffic information is being received both from UAT and 1090ES sources, it is not uncommon to see a flip/flop in tail numbers on targets.
|
||||||
Some 1090ES transponders will send the actual registration number of the aircraft, which then becomes a TIS-B target whose tail number may be
|
Some 1090ES transponders will send the actual registration number of the aircraft, which then becomes a TIS-B target whose tail number may be
|
||||||
a squawk code.
|
a squawk code.
|
||||||
|
|
||||||
|
|
||||||
|
### Additional data available to EFBs
|
||||||
|
|
||||||
|
Stratux makes available a webserver to retrieve statistics which may be useful to EFBs:
|
||||||
|
|
||||||
|
* `http://192.168.10.1/getTowers` - a list of ADS-B towers received with attached message receipt and signal level statistics. Example output:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"(28.845592,-96.920400)": {
|
||||||
|
"Lat": 28.845591545105,
|
||||||
|
"Lng": -96.920399665833,
|
||||||
|
"Signal_strength_last_minute": 55,
|
||||||
|
"Signal_strength_max": 69,
|
||||||
|
"Messages_last_minute": 97,
|
||||||
|
"Messages_total": 196
|
||||||
|
},
|
||||||
|
"(29.266505,-98.309097)": {
|
||||||
|
"Lat": 29.266505241394,
|
||||||
|
"Lng": -98.309097290039,
|
||||||
|
"Signal_strength_last_minute": 78,
|
||||||
|
"Signal_strength_max": 78,
|
||||||
|
"Messages_last_minute": 1,
|
||||||
|
"Messages_total": 3
|
||||||
|
},
|
||||||
|
"(29.702547,-96.900787)": {
|
||||||
|
"Lat": 29.702546596527,
|
||||||
|
"Lng": -96.900787353516,
|
||||||
|
"Signal_strength_last_minute": 87,
|
||||||
|
"Signal_strength_max": 119,
|
||||||
|
"Messages_last_minute": 94,
|
||||||
|
"Messages_total": 203
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* `http://192.168.10.1/getStatus` - device status and statistics. Example output (commented JSON):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Version": "v0.5b1", // Software version.
|
||||||
|
"Devices": 0, // Number of radios connected.
|
||||||
|
"Connected_Users": 1, // Number of WiFi devices connected.
|
||||||
|
"UAT_messages_last_minute": 0, // UAT messages received in last minute.
|
||||||
|
"UAT_messages_max": 17949, // Max UAT messages received in a minute (since last reboot).
|
||||||
|
"ES_messages_last_minute": 0, // 1090ES messages received in last minute.
|
||||||
|
"ES_messages_max": 0, // Max 1090ES messages received in a minute (since last reboot).
|
||||||
|
"GPS_satellites_locked": 0, // Number of GPS satellites used in last GPS lock.
|
||||||
|
"GPS_connected": true, // GPS unit connected and functioning.
|
||||||
|
"GPS_solution": "", // "DGPS (WAAS)", "3D GPS", "N/A", or "" when GPS not connected/enabled.
|
||||||
|
"RY835AI_connected": false, // GPS/AHRS unit - use only for debugging (this will be removed).
|
||||||
|
"Uptime": 227068, // Device uptime (in milliseconds).
|
||||||
|
"CPUTemp": 42.236 // CPU temperature (in ºC).
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* `http://192.168.10.1/getSettings` - get device settings. Example output:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"UAT_Enabled": true,
|
||||||
|
"ES_Enabled": false,
|
||||||
|
"GPS_Enabled": true,
|
||||||
|
"NetworkOutputs": [
|
||||||
|
{
|
||||||
|
"Conn": null,
|
||||||
|
"Ip": "",
|
||||||
|
"Port": 4000,
|
||||||
|
"Capability": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Conn": null,
|
||||||
|
"Ip": "",
|
||||||
|
"Port": 49002,
|
||||||
|
"Capability": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"AHRS_Enabled": false,
|
||||||
|
"DEBUG": false,
|
||||||
|
"ReplayLog": true,
|
||||||
|
"PPM": 0,
|
||||||
|
"OwnshipModeS": "F00000",
|
||||||
|
"WatchList": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
* `http://192.168.10.1/setSettings` - set device settings. Use an HTTP POST of JSON content in the format given above - posting only the fields containing the settings to be modified.
|
||||||
|
|
||||||
|
* `http://192.168.10.1/getSituation` - get GPS/AHRS information. Example output:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Lat": 39.108533,
|
||||||
|
"Lng": -76.770862,
|
||||||
|
"Satellites": 7,
|
||||||
|
"Accuracy": 5.88,
|
||||||
|
"NACp": 10,
|
||||||
|
"Alt": 170.10767,
|
||||||
|
"LastFixLocalTime": "2015-12-18T23:47:06.015563066Z",
|
||||||
|
"TrueCourse": 0,
|
||||||
|
"GroundSpeed": 0,
|
||||||
|
"LastGroundTrackTime": "0001-01-01T00:00:00Z",
|
||||||
|
"Temp": 6553,
|
||||||
|
"Pressure_alt": 231.27980834234,
|
||||||
|
"Pitch": -0.006116937627108,
|
||||||
|
"Roll": -0.026442866350631,
|
||||||
|
"Gyro_heading": 45.844213419776,
|
||||||
|
"LastAttitudeTime": "2015-12-18T23:47:06.774039623Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
* `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_last_minute = status.ES_messages_last_minute;
|
||||||
$scope.ES_messages_max = status.ES_messages_max;
|
$scope.ES_messages_max = status.ES_messages_max;
|
||||||
$scope.GPS_satellites_locked = status.GPS_satellites_locked;
|
$scope.GPS_satellites_locked = status.GPS_satellites_locked;
|
||||||
|
$scope.GPS_solution = status.GPS_solution;
|
||||||
$scope.RY835AI_connected = status.RY835AI_connected;
|
$scope.RY835AI_connected = status.RY835AI_connected;
|
||||||
|
|
||||||
var uptime = status.Uptime;
|
var uptime = status.Uptime;
|
||||||
|
@ -130,4 +131,4 @@ function StatusCtrl($rootScope, $scope, $state, $http, $interval) {
|
||||||
// Status Controller tasks
|
// Status Controller tasks
|
||||||
setHardwareVisibility();
|
setHardwareVisibility();
|
||||||
connect($scope); // connect - opens a socket and listens for messages
|
connect($scope); // connect - opens a socket and listens for messages
|
||||||
};
|
};
|
||||||
|
|
|
@ -56,6 +56,10 @@
|
||||||
<label class="col-xs-6">UAT Towers:</label>
|
<label class="col-xs-6">UAT Towers:</label>
|
||||||
<span class="col-xs-6">{{UAT_Towers}}</span>
|
<span class="col-xs-6">{{UAT_Towers}}</span>
|
||||||
</div>
|
</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}">
|
<div class="row" ng-class="{'section_invisible': !visible_gps}">
|
||||||
<label class="col-xs-6">GPS satellites:</label>
|
<label class="col-xs-6">GPS satellites:</label>
|
||||||
<span class="col-xs-6">{{GPS_satellites_locked}}</span>
|
<span class="col-xs-6">{{GPS_satellites_locked}}</span>
|
||||||
|
@ -81,4 +85,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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