kopia lustrzana https://github.com/cyoung/stratux
commit
3f6828a600
|
@ -3,3 +3,9 @@ dump978/uat2esnt
|
||||||
dump978/uat2json
|
dump978/uat2json
|
||||||
dump978/uat2text
|
dump978/uat2text
|
||||||
gen_gdl90
|
gen_gdl90
|
||||||
|
|
||||||
|
*.mp4
|
||||||
|
|
||||||
|
*.img
|
||||||
|
|
||||||
|
*.zip
|
||||||
|
|
|
@ -11,7 +11,14 @@ Supported WiFi adapters:
|
||||||
|
|
||||||
Tested RTL-SDR:
|
Tested RTL-SDR:
|
||||||
* NooElec NESDR Mini 2
|
* NooElec NESDR Mini 2
|
||||||
|
* Generic R820T (degraded performance)
|
||||||
|
|
||||||
|
Tested weather/traffic displays:
|
||||||
|
* ForeFlight 7.2 (Jul 13, 2015) - weather, traffic, AHRS.
|
||||||
|
* Naviator - weather, traffic, AHRS.
|
||||||
|
* WingX - weather & traffic.
|
||||||
|
* Avare
|
||||||
|
* iFly 740 - weather & traffic.
|
||||||
|
|
||||||
|
|
||||||
https://www.reddit.com/r/stratux
|
https://www.reddit.com/r/stratux
|
28
gen_gdl90.go
28
gen_gdl90.go
|
@ -15,7 +15,7 @@ import (
|
||||||
// http://www.faa.gov/nextgen/programs/adsb/wsa/media/GDL90_Public_ICD_RevA.PDF
|
// http://www.faa.gov/nextgen/programs/adsb/wsa/media/GDL90_Public_ICD_RevA.PDF
|
||||||
|
|
||||||
const (
|
const (
|
||||||
stratuxVersion = "v0.1"
|
stratuxVersion = "v0.2"
|
||||||
configLocation = "/etc/stratux.conf"
|
configLocation = "/etc/stratux.conf"
|
||||||
managementAddr = "127.0.0.1:9110"
|
managementAddr = "127.0.0.1:9110"
|
||||||
maxDatagramSize = 8192
|
maxDatagramSize = 8192
|
||||||
|
@ -56,6 +56,7 @@ type msg struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var MsgLog []msg
|
var MsgLog []msg
|
||||||
|
var timeStarted time.Time
|
||||||
|
|
||||||
// Construct the CRC table. Adapted from FAA ref above.
|
// Construct the CRC table. Adapted from FAA ref above.
|
||||||
func crcInit() {
|
func crcInit() {
|
||||||
|
@ -299,9 +300,20 @@ func updateStatus() {
|
||||||
globalStatus.UAT_messages_last_minute = UAT_messages_last_minute
|
globalStatus.UAT_messages_last_minute = UAT_messages_last_minute
|
||||||
globalStatus.ES_messages_last_minute = ES_messages_last_minute
|
globalStatus.ES_messages_last_minute = ES_messages_last_minute
|
||||||
|
|
||||||
|
// Update "max messages/min" counters.
|
||||||
|
if globalStatus.UAT_messages_max < UAT_messages_last_minute {
|
||||||
|
globalStatus.UAT_messages_max = UAT_messages_last_minute
|
||||||
|
}
|
||||||
|
if globalStatus.ES_messages_max < ES_messages_last_minute {
|
||||||
|
globalStatus.ES_messages_max = ES_messages_last_minute
|
||||||
|
}
|
||||||
|
|
||||||
if isGPSValid() {
|
if isGPSValid() {
|
||||||
globalStatus.GPS_satellites_locked = mySituation.satellites
|
globalStatus.GPS_satellites_locked = mySituation.satellites
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update Uptime.
|
||||||
|
globalStatus.Uptime = time.Since(timeStarted).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseInput(buf string) ([]byte, uint16) {
|
func parseInput(buf string) ([]byte, uint16) {
|
||||||
|
@ -372,6 +384,7 @@ type status struct {
|
||||||
GPS_satellites_locked uint16
|
GPS_satellites_locked uint16
|
||||||
GPS_connected bool
|
GPS_connected bool
|
||||||
RY835AI_connected bool
|
RY835AI_connected bool
|
||||||
|
Uptime string
|
||||||
}
|
}
|
||||||
|
|
||||||
var globalSettings settings
|
var globalSettings settings
|
||||||
|
@ -428,10 +441,10 @@ func managementInterface() {
|
||||||
|
|
||||||
func defaultSettings() {
|
func defaultSettings() {
|
||||||
globalSettings.UAT_Enabled = true //TODO
|
globalSettings.UAT_Enabled = true //TODO
|
||||||
globalSettings.ES_Enabled = true //TODO
|
globalSettings.ES_Enabled = false //TODO
|
||||||
globalSettings.GPS_Enabled = true //TODO
|
globalSettings.GPS_Enabled = false //TODO
|
||||||
globalSettings.NetworkOutputs = []networkConnection{{nil, "", 4000, NETWORK_GDL90}, {nil, "", 43211, NETWORK_GDL90}, {nil, "", 49002, NETWORK_AHRS}}
|
globalSettings.NetworkOutputs = []networkConnection{{nil, "", 4000, NETWORK_GDL90_STANDARD}, {nil, "", 43211, NETWORK_GDL90_STANDARD | NETWORK_AHRS_GDL90}, {nil, "", 49002, NETWORK_AHRS_FFSIM}}
|
||||||
globalSettings.AHRS_Enabled = true
|
globalSettings.AHRS_Enabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func readSettings() {
|
func readSettings() {
|
||||||
|
@ -473,6 +486,7 @@ func saveSettings() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
timeStarted = time.Now()
|
||||||
runtime.GOMAXPROCS(runtime.NumCPU()) // redundant with Go v1.5+ compiler
|
runtime.GOMAXPROCS(runtime.NumCPU()) // redundant with Go v1.5+ compiler
|
||||||
MsgLog = make([]msg, 0)
|
MsgLog = make([]msg, 0)
|
||||||
|
|
||||||
|
@ -480,9 +494,7 @@ func main() {
|
||||||
initTraffic()
|
initTraffic()
|
||||||
|
|
||||||
globalStatus.Version = stratuxVersion
|
globalStatus.Version = stratuxVersion
|
||||||
globalStatus.Devices = 123 //TODO
|
globalStatus.Devices = 0 //TODO
|
||||||
globalStatus.UAT_messages_last_minute = 567 //TODO
|
|
||||||
globalStatus.ES_messages_last_minute = 981 //TODO
|
|
||||||
|
|
||||||
readSettings()
|
readSettings()
|
||||||
|
|
||||||
|
|
23
network.go
23
network.go
|
@ -1,7 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/exp/inotify"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
@ -29,8 +28,9 @@ var dhcpLeases map[string]string
|
||||||
var netMutex *sync.Mutex
|
var netMutex *sync.Mutex
|
||||||
|
|
||||||
const (
|
const (
|
||||||
NETWORK_GDL90 = 1
|
NETWORK_GDL90_STANDARD = 1
|
||||||
NETWORK_AHRS = 2
|
NETWORK_AHRS_FFSIM = 2
|
||||||
|
NETWORK_AHRS_GDL90 = 4
|
||||||
dhcp_lease_file = "/var/lib/dhcp/dhcpd.leases"
|
dhcp_lease_file = "/var/lib/dhcp/dhcpd.leases"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -134,25 +134,16 @@ func sendMsg(msg []byte, msgType uint8) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendGDL90(msg []byte) {
|
func sendGDL90(msg []byte) {
|
||||||
sendMsg(msg, NETWORK_GDL90)
|
sendMsg(msg, NETWORK_GDL90_STANDARD)
|
||||||
}
|
}
|
||||||
|
|
||||||
func monitorDHCPLeases() {
|
func monitorDHCPLeases() {
|
||||||
watcher, err := inotify.NewWatcher()
|
//TODO: inotify or dhcp event hook.
|
||||||
if err != nil {
|
timer := time.NewTicker(30 * time.Second)
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
err = watcher.AddWatch(dhcp_lease_file, inotify.IN_CLOSE_WRITE)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-watcher.Event:
|
case <-timer.C:
|
||||||
log.Println("file modified, attempting to refresh DHCP")
|
|
||||||
refreshConnectedClients()
|
refreshConnectedClients()
|
||||||
case err := <-watcher.Error:
|
|
||||||
log.Println("error with DHCP file system watcher:", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
51
ry835ai.go
51
ry835ai.go
|
@ -259,6 +259,53 @@ func tempAndPressureReader() {
|
||||||
globalStatus.RY835AI_connected = false
|
globalStatus.RY835AI_connected = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeFFAHRSSimReport() {
|
||||||
|
s := fmt.Sprintf("XATTStratux,%f,%f,%f", mySituation.gyro_heading, mySituation.pitch, mySituation.roll)
|
||||||
|
|
||||||
|
sendMsg([]byte(s), NETWORK_AHRS_FFSIM)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeAHRSGDL90Report() {
|
||||||
|
msg := make([]byte, 16)
|
||||||
|
msg[0] = 0x4c
|
||||||
|
msg[1] = 0x45
|
||||||
|
msg[2] = 0x01
|
||||||
|
msg[3] = 0x00
|
||||||
|
|
||||||
|
pitch := int16(float64(mySituation.pitch) * float64(10.0))
|
||||||
|
roll := int16(float64(mySituation.roll) * float64(10.0))
|
||||||
|
hdg := uint16(float64(mySituation.gyro_heading) * float64(10.0)) //TODO.
|
||||||
|
slip_skid := int16(float64(0) * float64(10.0)) //TODO.
|
||||||
|
yaw_rate := int16(float64(0) * float64(10.0)) //TODO.
|
||||||
|
g := int16(float64(1.0) * float64(10.0)) //TODO.
|
||||||
|
|
||||||
|
// Roll.
|
||||||
|
msg[4] = byte((roll >> 8) & 0xFF)
|
||||||
|
msg[5] = byte(roll & 0xFF)
|
||||||
|
|
||||||
|
// Pitch.
|
||||||
|
msg[6] = byte((pitch >> 8) & 0xFF)
|
||||||
|
msg[7] = byte(pitch & 0xFF)
|
||||||
|
|
||||||
|
// Heading.
|
||||||
|
msg[8] = byte((hdg >> 8) & 0xFF)
|
||||||
|
msg[9] = byte(hdg & 0xFF)
|
||||||
|
|
||||||
|
// Slip/skid.
|
||||||
|
msg[10] = byte((slip_skid >> 8) & 0xFF)
|
||||||
|
msg[11] = byte(slip_skid & 0xFF)
|
||||||
|
|
||||||
|
// Yaw rate.
|
||||||
|
msg[12] = byte((yaw_rate >> 8) & 0xFF)
|
||||||
|
msg[13] = byte(yaw_rate & 0xFF)
|
||||||
|
|
||||||
|
// "G".
|
||||||
|
msg[14] = byte((g >> 8) & 0xFF)
|
||||||
|
msg[15] = byte(g & 0xFF)
|
||||||
|
|
||||||
|
sendMsg(prepareMessage(msg), NETWORK_AHRS_GDL90)
|
||||||
|
}
|
||||||
|
|
||||||
func attitudeReaderSender() {
|
func attitudeReaderSender() {
|
||||||
timer := time.NewTicker(100 * time.Millisecond) // ~10Hz update.
|
timer := time.NewTicker(100 * time.Millisecond) // ~10Hz update.
|
||||||
for globalStatus.RY835AI_connected && globalSettings.AHRS_Enabled {
|
for globalStatus.RY835AI_connected && globalSettings.AHRS_Enabled {
|
||||||
|
@ -281,9 +328,9 @@ func attitudeReaderSender() {
|
||||||
|
|
||||||
// Send, if valid.
|
// Send, if valid.
|
||||||
// if isGPSGroundTrackValid(), etc.
|
// if isGPSGroundTrackValid(), etc.
|
||||||
s := fmt.Sprintf("XATTStratux,%f,%f,%f", mySituation.gyro_heading, mySituation.pitch, mySituation.roll)
|
|
||||||
|
|
||||||
sendMsg([]byte(s), NETWORK_AHRS)
|
makeFFAHRSSimReport()
|
||||||
|
makeAHRSGDL90Report()
|
||||||
|
|
||||||
mySituation.mu_Attitude.Unlock()
|
mySituation.mu_Attitude.Unlock()
|
||||||
}
|
}
|
||||||
|
|
|
@ -360,6 +360,15 @@ func esListen() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log the message after we've determined that it at least meets some requirements on the fields.
|
||||||
|
var thisMsg msg
|
||||||
|
thisMsg.MessageClass = MSGCLASS_ES
|
||||||
|
thisMsg.TimeReceived = time.Now()
|
||||||
|
thisMsg.Data = []byte(buf)
|
||||||
|
MsgLog = append(MsgLog, thisMsg)
|
||||||
|
|
||||||
|
// Begin to parse the message.
|
||||||
icaoDec := uint32(icaoDecf)
|
icaoDec := uint32(icaoDecf)
|
||||||
trafficMutex.Lock()
|
trafficMutex.Lock()
|
||||||
// Retrieve previous information on this ICAO code.
|
// Retrieve previous information on this ICAO code.
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function _str_send($sock, $str) {
|
||||||
|
return socket_send($sock, $str, strlen($str), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_json($tp) {
|
||||||
|
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
|
||||||
|
if (!socket_connect($sock, '127.0.0.1', '9110')) {
|
||||||
|
throw new Exception("couldn't connect");
|
||||||
|
}
|
||||||
|
|
||||||
|
_str_send($sock, $tp);
|
||||||
|
|
||||||
|
$buf = "";
|
||||||
|
socket_recv($sock, $buf, 1024, 0);
|
||||||
|
|
||||||
|
$x = json_decode($buf, true);
|
||||||
|
|
||||||
|
socket_close($sock);
|
||||||
|
|
||||||
|
return $x;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_settings() {
|
||||||
|
return get_json("SETTINGS\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_status() {
|
||||||
|
return get_json("STATUS\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_settings($to_set) {
|
||||||
|
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
|
||||||
|
if (!socket_connect($sock, '127.0.0.1', '9110')) {
|
||||||
|
print "couldn't connect\n";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$buf = json_encode($to_set);
|
||||||
|
_str_send($sock, $buf . "\n");
|
||||||
|
_str_send($sock, "QUIT\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
$current_settings = get_settings();
|
||||||
|
|
||||||
|
// Copy over old settings to the new ones, such that if there is a field that doesn't change it gets sent over again.
|
||||||
|
if (isset($_SERVER['REQUEST_METHOD']) && ($_SERVER['REQUEST_METHOD'] == 'POST')) {
|
||||||
|
$new_settings = $current_settings;
|
||||||
|
foreach ($_POST as $k => $v) {
|
||||||
|
if ($v === "true") $v = true;
|
||||||
|
else if ($v === "false") $v = false;
|
||||||
|
$new_settings[$k] = $v; //FIXME.
|
||||||
|
}
|
||||||
|
set_settings($new_settings);
|
||||||
|
$current_settings = get_settings();
|
||||||
|
}
|
||||||
|
|
||||||
|
$current_status = get_status();
|
||||||
|
|
||||||
|
$p = array_merge($current_settings, $current_status);
|
||||||
|
|
||||||
|
print json_encode($p) . "\n";
|
||||||
|
?>
|
|
@ -0,0 +1,70 @@
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
Stratux
|
||||||
|
</title>
|
||||||
|
<script src="js/jquery-2.1.4.min.js" type="text/javascript"></script>
|
||||||
|
<script src="js/jquery.form.min.js" type="text/javascript"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('input[name=UAT_Enabled]').change(function(){
|
||||||
|
$('#settings').ajaxSubmit({url: 'control.php', type: 'post'})
|
||||||
|
});
|
||||||
|
$('input[name=ES_Enabled]').change(function(){
|
||||||
|
$('#settings').ajaxSubmit({url: 'control.php', type: 'post'})
|
||||||
|
});
|
||||||
|
$('input[name=GPS_Enabled]').change(function(){
|
||||||
|
$('#settings').ajaxSubmit({url: 'control.php', type: 'post'})
|
||||||
|
});
|
||||||
|
$('input[name=AHRS_Enabled]').change(function(){
|
||||||
|
$('#settings').ajaxSubmit({url: 'control.php', type: 'post'})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
(function worker() {
|
||||||
|
$.ajax({
|
||||||
|
url: 'control.php',
|
||||||
|
success: function(data) {
|
||||||
|
obj = $.parseJSON(data);
|
||||||
|
$.each(obj, function(k, v) {
|
||||||
|
// Radio values.
|
||||||
|
if ((k == "UAT_Enabled") || (k == "ES_Enabled") || (k == "GPS_Enabled") || (k == "AHRS_Enabled")) {
|
||||||
|
$('[name=' + k + ']').val([v.toString()]);
|
||||||
|
}
|
||||||
|
$('#' + k).text(v);
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
complete: function() {
|
||||||
|
// Schedule the next request when the current one is complete.
|
||||||
|
setTimeout(worker, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
Status:<br>
|
||||||
|
RTL-SDR devices: <span id="Devices">1</span><br>
|
||||||
|
Clients connected: <span id="Connected_Users">2</span><br>
|
||||||
|
Current firmware: <span id="Version">v0.1</span><br>
|
||||||
|
UAT msgs: <span id="UAT_messages_last_minute"></span> / <span id="UAT_messages_max"></span><br>
|
||||||
|
1090ES msgs: <span id="ES_messages_last_minute"></span> / <span id="ES_messages_max"></span><br>
|
||||||
|
GPS satellites: <span id="GPS_satellites_locked"></span><br>
|
||||||
|
AHRS: <span id="RY835AI_connected"></span><br>
|
||||||
|
Uptime: <span id="Uptime"></span><br>
|
||||||
|
<br>
|
||||||
|
<form id="settings">
|
||||||
|
Set:<br>
|
||||||
|
978MHz <input type="radio" name="UAT_Enabled" value="true">On
|
||||||
|
<input type="radio" name="UAT_Enabled" value="false">Off<br>
|
||||||
|
1090MHz <input type="radio" name="ES_Enabled" value="true">On
|
||||||
|
<input type="radio" name="ES_Enabled" value="false">Off<br>
|
||||||
|
GPS <input type="radio" name="GPS_Enabled" value="true">On
|
||||||
|
<input type="radio" name="GPS_Enabled" value="false">Off<br>
|
||||||
|
AHRS <input type="radio" name="AHRS_Enabled" value="true">On
|
||||||
|
<input type="radio" name="AHRS_Enabled" value="false">Off<br>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Ładowanie…
Reference in New Issue