kopia lustrzana https://github.com/cyoung/stratux
commit
a473cca229
|
@ -757,7 +757,7 @@ type WeatherMessage struct {
|
|||
}
|
||||
|
||||
// Send update to connected websockets.
|
||||
func registerADSBTextMessageReceived(msg string) {
|
||||
func registerADSBTextMessageReceived(msg string, uatMsg *uatparse.UATMsg) {
|
||||
x := strings.Split(msg, " ")
|
||||
if len(x) < 5 {
|
||||
return
|
||||
|
@ -777,17 +777,14 @@ func registerADSBTextMessageReceived(msg string) {
|
|||
if x[0] == "PIREP" {
|
||||
globalStatus.UAT_PIREP_total++
|
||||
}
|
||||
|
||||
wm.Type = x[0]
|
||||
wm.Location = x[1]
|
||||
wm.Time = x[2]
|
||||
wm.Data = strings.Join(x[3:], " ")
|
||||
wm.LocaltimeReceived = stratuxClock.Time
|
||||
|
||||
wmJSON, _ := json.Marshal(&wm)
|
||||
|
||||
// Send to weatherUpdate channel for any connected clients.
|
||||
weatherUpdate.Send(wmJSON)
|
||||
weatherUpdate.SendJSON(wm)
|
||||
}
|
||||
|
||||
func UpdateUATStats(ProductID uint32) {
|
||||
|
@ -900,11 +897,12 @@ func parseInput(buf string) ([]byte, uint16) {
|
|||
for _, f := range uatMsg.Frames {
|
||||
thisMsg.Products = append(thisMsg.Products, f.Product_id)
|
||||
UpdateUATStats(f.Product_id)
|
||||
weatherRawUpdate.SendJSON(f)
|
||||
}
|
||||
// Get all of the text reports.
|
||||
textReports, _ := uatMsg.GetTextReports()
|
||||
for _, r := range textReports {
|
||||
registerADSBTextMessageReceived(r)
|
||||
registerADSBTextMessageReceived(r, uatMsg)
|
||||
}
|
||||
thisMsg.uatMsg = uatMsg
|
||||
}
|
||||
|
|
|
@ -35,6 +35,31 @@ type SettingMessage struct {
|
|||
// Weather updates channel.
|
||||
var weatherUpdate *uibroadcaster
|
||||
var trafficUpdate *uibroadcaster
|
||||
var gdl90Update *uibroadcaster
|
||||
|
||||
func handleGDL90WS(conn *websocket.Conn) {
|
||||
// Subscribe the socket to receive updates.
|
||||
gdl90Update.AddSocket(conn)
|
||||
|
||||
// Connection closes when function returns. Since uibroadcast is writing and we don't need to read anything (for now), just keep it busy.
|
||||
for {
|
||||
buf := make([]byte, 1024)
|
||||
_, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if buf[0] != 0 { // Dummy.
|
||||
continue
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// Situation updates channel.
|
||||
var situationUpdate *uibroadcaster
|
||||
|
||||
// Raw weather (UATFrame packet stream) update channel.
|
||||
var weatherRawUpdate *uibroadcaster
|
||||
|
||||
/*
|
||||
The /weather websocket starts off by sending the current buffer of weather messages, then sends updates as they are received.
|
||||
|
@ -57,6 +82,36 @@ func handleWeatherWS(conn *websocket.Conn) {
|
|||
}
|
||||
}
|
||||
|
||||
func handleJsonIo(conn *websocket.Conn) {
|
||||
trafficMutex.Lock()
|
||||
for _, traf := range traffic {
|
||||
if !traf.Position_valid { // Don't send unless a valid position exists.
|
||||
continue
|
||||
}
|
||||
trafficJSON, _ := json.Marshal(&traf)
|
||||
conn.Write(trafficJSON)
|
||||
}
|
||||
// Subscribe the socket to receive updates.
|
||||
trafficUpdate.AddSocket(conn)
|
||||
weatherRawUpdate.AddSocket(conn)
|
||||
situationUpdate.AddSocket(conn)
|
||||
|
||||
trafficMutex.Unlock()
|
||||
|
||||
// Connection closes when function returns. Since uibroadcast is writing and we don't need to read anything (for now), just keep it busy.
|
||||
for {
|
||||
buf := make([]byte, 1024)
|
||||
_, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if buf[0] != 0 { // Dummy.
|
||||
continue
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// Works just as weather updates do.
|
||||
|
||||
func handleTrafficWS(conn *websocket.Conn) {
|
||||
|
@ -484,11 +539,20 @@ func viewLogs(w http.ResponseWriter, r *http.Request) {
|
|||
func managementInterface() {
|
||||
weatherUpdate = NewUIBroadcaster()
|
||||
trafficUpdate = NewUIBroadcaster()
|
||||
situationUpdate = NewUIBroadcaster()
|
||||
weatherRawUpdate = NewUIBroadcaster()
|
||||
gdl90Update = NewUIBroadcaster()
|
||||
|
||||
http.HandleFunc("/", defaultServer)
|
||||
http.Handle("/logs/", http.StripPrefix("/logs/", http.FileServer(http.Dir("/var/log"))))
|
||||
http.HandleFunc("/view_logs/", viewLogs)
|
||||
|
||||
http.HandleFunc("/gdl90",
|
||||
func(w http.ResponseWriter, req *http.Request) {
|
||||
s := websocket.Server{
|
||||
Handler: websocket.Handler(handleGDL90WS)}
|
||||
s.ServeHTTP(w, req)
|
||||
})
|
||||
http.HandleFunc("/status",
|
||||
func(w http.ResponseWriter, req *http.Request) {
|
||||
s := websocket.Server{
|
||||
|
@ -514,6 +578,13 @@ func managementInterface() {
|
|||
s.ServeHTTP(w, req)
|
||||
})
|
||||
|
||||
http.HandleFunc("/jsonio",
|
||||
func(w http.ResponseWriter, req *http.Request) {
|
||||
s := websocket.Server{
|
||||
Handler: websocket.Handler(handleJsonIo)}
|
||||
s.ServeHTTP(w, req)
|
||||
})
|
||||
|
||||
http.HandleFunc("/getStatus", handleStatusRequest)
|
||||
http.HandleFunc("/getSituation", handleSituationRequest)
|
||||
http.HandleFunc("/getTowers", handleTowersRequest)
|
||||
|
|
|
@ -143,6 +143,7 @@ func sendToAllConnectedClients(msg networkMessage) {
|
|||
if (msg.msgType & NETWORK_GDL90_STANDARD) != 0 {
|
||||
// It's a GDL90 message. Send to serial output channel (which may or may not cause something to happen).
|
||||
serialOutputChan <- msg.msg
|
||||
networkGDL90Chan <- msg.msg
|
||||
}
|
||||
|
||||
netMutex.Lock()
|
||||
|
@ -192,6 +193,14 @@ func sendToAllConnectedClients(msg networkMessage) {
|
|||
}
|
||||
|
||||
var serialOutputChan chan []byte
|
||||
var networkGDL90Chan chan []byte
|
||||
|
||||
func networkOutWatcher() {
|
||||
for {
|
||||
ch := <-networkGDL90Chan
|
||||
gdl90Update.SendJSON(ch)
|
||||
}
|
||||
}
|
||||
|
||||
// Monitor serial output channel, send to serial port.
|
||||
func serialOutWatcher() {
|
||||
|
@ -603,6 +612,7 @@ func ffMonitor() {
|
|||
func initNetwork() {
|
||||
messageQueue = make(chan networkMessage, 1024) // Buffered channel, 1024 messages.
|
||||
serialOutputChan = make(chan []byte, 1024) // Buffered channel, 1024 GDL90 messages.
|
||||
networkGDL90Chan = make(chan []byte, 1024)
|
||||
outSockets = make(map[string]networkConnection)
|
||||
pingResponse = make(map[string]time.Time)
|
||||
netMutex = &sync.Mutex{}
|
||||
|
@ -612,4 +622,5 @@ func initNetwork() {
|
|||
go sleepMonitor()
|
||||
go networkStatsCounter()
|
||||
go serialOutWatcher()
|
||||
go networkOutWatcher()
|
||||
}
|
||||
|
|
|
@ -847,6 +847,15 @@ func calculateNACp(accuracy float32) uint8 {
|
|||
return ret
|
||||
}
|
||||
|
||||
/*
|
||||
registerSituationUpdate().
|
||||
Called whenever there is a change in mySituation.
|
||||
*/
|
||||
func registerSituationUpdate() {
|
||||
logSituation()
|
||||
situationUpdate.SendJSON(mySituation)
|
||||
}
|
||||
|
||||
/*
|
||||
processNMEALine parses NMEA-0183 formatted strings against several message types.
|
||||
|
||||
|
@ -863,7 +872,7 @@ func processNMEALine(l string) (sentenceUsed bool) {
|
|||
|
||||
defer func() {
|
||||
if sentenceUsed || globalSettings.DEBUG {
|
||||
logSituation()
|
||||
registerSituationUpdate()
|
||||
}
|
||||
mySituation.mu_GPS.Unlock()
|
||||
}()
|
||||
|
|
|
@ -207,8 +207,7 @@ func sendTrafficUpdates() {
|
|||
traffic[icao] = ti // write the updated ti back to the map
|
||||
//log.Printf("Traffic age of %X is %f seconds\n",icao,ti.Age)
|
||||
if ti.Age > 2 { // if nothing polls an inactive ti, it won't push to the webUI, and its Age won't update.
|
||||
tiJSON, _ := json.Marshal(&ti)
|
||||
trafficUpdate.Send(tiJSON)
|
||||
trafficUpdate.SendJSON(ti)
|
||||
}
|
||||
if ti.Position_valid && ti.Age < 6 { // ... but don't pass stale data to the EFB. TO-DO: Coast old traffic? Need to determine how FF, WingX, etc deal with stale targets.
|
||||
logTraffic(ti) // only add to the SQLite log if it's not stale
|
||||
|
@ -235,8 +234,7 @@ func registerTrafficUpdate(ti TrafficInfo) {
|
|||
return
|
||||
}
|
||||
*/ // Send all traffic to the websocket and let JS sort it out. This will provide user indication of why they see 1000 ES messages and no traffic.
|
||||
tiJSON, _ := json.Marshal(&ti)
|
||||
trafficUpdate.Send(tiJSON)
|
||||
trafficUpdate.SendJSON(ti)
|
||||
}
|
||||
|
||||
func makeTrafficReportMsg(ti TrafficInfo) []byte {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"golang.org/x/net/websocket"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -36,6 +37,11 @@ func (u *uibroadcaster) Send(msg []byte) {
|
|||
u.messages <- msg
|
||||
}
|
||||
|
||||
func (u *uibroadcaster) SendJSON(i interface{}) {
|
||||
j, _ := json.Marshal(&i)
|
||||
u.Send(j)
|
||||
}
|
||||
|
||||
func (u *uibroadcaster) AddSocket(sock *websocket.Conn) {
|
||||
u.sockets_mu.Lock()
|
||||
u.sockets = append(u.sockets, sock)
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
package uatparse
|
||||
|
||||
import ()
|
||||
|
||||
const (
|
||||
BLOCK_WIDTH = float64(48.0 / 60.0)
|
||||
WIDE_BLOCK_WIDTH = float64(96.0 / 60.0)
|
||||
BLOCK_HEIGHT = float64(4.0 / 60.0)
|
||||
BLOCK_THRESHOLD = 405000
|
||||
BLOCKS_PER_RING = 450
|
||||
)
|
||||
|
||||
type NEXRADBlock struct {
|
||||
Radar_Type uint32
|
||||
Scale int
|
||||
LatNorth float64
|
||||
LonWest float64
|
||||
Height float64
|
||||
Width float64
|
||||
Intensity []uint16 // Really only 4-bit values, but using this as a hack for the JSON encoding.
|
||||
}
|
||||
|
||||
func block_location(block_num int, ns_flag bool, scale_factor int) (float64, float64, float64, float64) {
|
||||
var realScale float64
|
||||
if scale_factor == 1 {
|
||||
realScale = float64(5.0)
|
||||
} else if scale_factor == 2 {
|
||||
realScale = float64(9.0)
|
||||
} else {
|
||||
realScale = float64(1.0)
|
||||
}
|
||||
|
||||
if block_num >= BLOCK_THRESHOLD {
|
||||
block_num = block_num & ^1
|
||||
}
|
||||
|
||||
raw_lat := float64(BLOCK_HEIGHT * float64(int(float64(block_num)/float64(BLOCKS_PER_RING))))
|
||||
raw_lon := float64(block_num%BLOCKS_PER_RING) * BLOCK_WIDTH
|
||||
|
||||
var lonSize float64
|
||||
if block_num >= BLOCK_THRESHOLD {
|
||||
lonSize = WIDE_BLOCK_WIDTH * realScale
|
||||
} else {
|
||||
lonSize = BLOCK_WIDTH * realScale
|
||||
}
|
||||
|
||||
latSize := BLOCK_HEIGHT * realScale
|
||||
|
||||
if ns_flag { // Southern hemisphere.
|
||||
raw_lat = 0 - raw_lat
|
||||
} else {
|
||||
raw_lat = raw_lat + BLOCK_HEIGHT
|
||||
}
|
||||
|
||||
if raw_lon > 180.0 {
|
||||
raw_lon = raw_lon - 360.0
|
||||
}
|
||||
|
||||
return raw_lat, raw_lon, latSize, lonSize
|
||||
|
||||
}
|
||||
|
||||
func (f *UATFrame) decodeNexradFrame() {
|
||||
if len(f.FISB_data) < 4 { // Short read.
|
||||
return
|
||||
}
|
||||
|
||||
rle_flag := (uint32(f.FISB_data[0]) & 0x80) != 0
|
||||
ns_flag := (uint32(f.FISB_data[0]) & 0x40) != 0
|
||||
block_num := ((int(f.FISB_data[0]) & 0x0f) << 16) | (int(f.FISB_data[1]) << 8) | (int(f.FISB_data[2]))
|
||||
scale_factor := (int(f.FISB_data[0]) & 0x30) >> 4
|
||||
|
||||
if rle_flag { // Single bin, RLE encoded.
|
||||
lat, lon, h, w := block_location(block_num, ns_flag, scale_factor)
|
||||
var tmp NEXRADBlock
|
||||
tmp.Radar_Type = f.Product_id
|
||||
tmp.Scale = scale_factor
|
||||
tmp.LatNorth = lat
|
||||
tmp.LonWest = lon
|
||||
tmp.Height = h
|
||||
tmp.Width = w
|
||||
tmp.Intensity = make([]uint16, 0)
|
||||
|
||||
intensityData := f.FISB_data[3:]
|
||||
for _, v := range intensityData {
|
||||
intensity := uint16(v) & 0x7
|
||||
runlength := (uint16(v) >> 3) + 1
|
||||
for runlength > 0 {
|
||||
tmp.Intensity = append(tmp.Intensity, intensity)
|
||||
runlength--
|
||||
}
|
||||
}
|
||||
f.NEXRAD = []NEXRADBlock{tmp}
|
||||
} else {
|
||||
var row_start int
|
||||
var row_size int
|
||||
if block_num >= 405000 {
|
||||
row_start = block_num - ((block_num - 405000) % 225)
|
||||
row_size = 225
|
||||
} else {
|
||||
row_start = block_num - (block_num % 450)
|
||||
row_size = 450
|
||||
}
|
||||
|
||||
row_offset := block_num - row_start
|
||||
|
||||
L := int(f.FISB_data[3] & 15)
|
||||
|
||||
if len(f.FISB_data) < L+3 { // Short read.
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < L; i++ {
|
||||
var bb int
|
||||
if i == 0 {
|
||||
bb = (int(f.FISB_data[3]) & 0xF0) | 0x08
|
||||
} else {
|
||||
bb = int(f.FISB_data[i+3])
|
||||
}
|
||||
|
||||
for j := 0; j < 8; j++ {
|
||||
if bb&(1<<uint(j)) != 0 {
|
||||
row_x := (row_offset + 8*i + j - 3) % row_size
|
||||
bn := row_start + row_x
|
||||
lat, lon, h, w := block_location(bn, ns_flag, scale_factor)
|
||||
var tmp NEXRADBlock
|
||||
tmp.Radar_Type = f.Product_id
|
||||
tmp.Scale = scale_factor
|
||||
tmp.LatNorth = lat
|
||||
tmp.LonWest = lon
|
||||
tmp.Height = h
|
||||
tmp.Width = w
|
||||
tmp.Intensity = make([]uint16, 0)
|
||||
for k := 0; k < 128; k++ {
|
||||
z := uint16(0)
|
||||
if f.Product_id == 64 {
|
||||
z = 1
|
||||
}
|
||||
tmp.Intensity = append(tmp.Intensity, z)
|
||||
}
|
||||
f.NEXRAD = append(f.NEXRAD, tmp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -61,6 +61,9 @@ type UATFrame struct {
|
|||
RecordFormat uint8
|
||||
ReportStart string
|
||||
ReportEnd string
|
||||
|
||||
// For NEXRAD.
|
||||
NEXRAD []NEXRADBlock
|
||||
}
|
||||
|
||||
type UATMsg struct {
|
||||
|
@ -500,6 +503,9 @@ func (f *UATFrame) decodeInfoFrame() {
|
|||
case 8, 11, 13:
|
||||
f.decodeAirmet()
|
||||
*/
|
||||
case 63, 64:
|
||||
f.decodeNexradFrame()
|
||||
|
||||
default:
|
||||
fmt.Fprintf(ioutil.Discard, "don't know what to do with product id: %d\n", f.Product_id)
|
||||
}
|
||||
|
|
|
@ -5,22 +5,22 @@
|
|||
.modal {
|
||||
}
|
||||
.vertical-alignment-helper {
|
||||
display:table;
|
||||
height: 100%;
|
||||
width: 75%;
|
||||
display:table;
|
||||
height: 100%;
|
||||
width: 75%;
|
||||
}
|
||||
.vertical-align-center {
|
||||
/* To center vertically */
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
/* To center vertically */
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
/* Bootstrap sets the size of the modal in the modal-dialog class, we need to inherit it */
|
||||
width:inherit;
|
||||
height:inherit;
|
||||
/* To center horizontally */
|
||||
margin: 0 auto;
|
||||
/* Bootstrap sets the size of the modal in the modal-dialog class, we need to inherit it */
|
||||
width:inherit;
|
||||
height:inherit;
|
||||
/* To center horizontally */
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.traffic-page {}
|
||||
|
@ -131,22 +131,44 @@
|
|||
content: "\f1d9";
|
||||
}
|
||||
|
||||
.report_TAF {
|
||||
border-radius: 5px;
|
||||
background-color: cornsilk;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.report_PIREP {
|
||||
border-radius: 5px;
|
||||
background-color: gainsboro;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.report_WINDS {
|
||||
border-radius: 5px;
|
||||
background-color: lavender;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.flight_condition_VFR {
|
||||
border-radius: 5px;
|
||||
background-color: forestgreen;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.flight_condition_MVFR {
|
||||
border-radius: 5px;
|
||||
background-color: blue;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.flight_condition_IFR {
|
||||
border-radius: 5px;
|
||||
background-color: crimson;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.flight_condition_LIFR {
|
||||
border-radius: 5px;
|
||||
background-color: darkorchid;
|
||||
color: white;
|
||||
}
|
||||
|
|
|
@ -6373,50 +6373,62 @@ a.label:focus {
|
|||
|
||||
.label-default {
|
||||
background-color: #777777;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.label-default[href]:focus {
|
||||
background-color: #5e5e5e;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.label-primary {
|
||||
background-color: #007aff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.label-primary[href]:focus {
|
||||
background-color: #0062cc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.label-success {
|
||||
background-color: #4cd964;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.label-success[href]:focus {
|
||||
background-color: #2ac845;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.label-info {
|
||||
background-color: #34aadc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.label-info[href]:focus {
|
||||
background-color: #218ebd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.label-warning {
|
||||
background-color: #ffcc00;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.label-warning[href]:focus {
|
||||
background-color: #cca300;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.label-danger {
|
||||
background-color: #ff3b30;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.label-danger[href]:focus {
|
||||
background-color: #fc0d00;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
|
|
|
@ -29,8 +29,11 @@
|
|||
<div class="separator"></div>
|
||||
<div class="col-sm-12">
|
||||
<span class="col-xs-3"><strong>{{weather.location}}</strong></span>
|
||||
<span ng-class="weather.flight_condition ? 'col-xs-3 label label-success flight_condition_{{weather.flight_condition}}' : 'col-xs-3'">{{weather.type}}</span>
|
||||
<span class="col-xs-6 text-right">{{weather.time}}</span>
|
||||
<span class="col-xs-3" align="center">
|
||||
<div align="center" ng-class="weather.flight_condition ? ' label label-success flight_condition_{{weather.flight_condition}}' : 'label label-success report_{{weather.type}}'">{{weather.type}}</div>
|
||||
</span>
|
||||
<span class="col-xs-4"> </span>
|
||||
<span class="col-xs-2 text-right" style="background-color: {{weather.backcolor}}">{{weather.time}}</span>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<span class="col-xs-10">{{weather.data}}</span>
|
||||
|
@ -62,8 +65,11 @@
|
|||
<div class="separator"></div>
|
||||
<div class="col-sm-12">
|
||||
<span class="col-xs-3"><strong>{{weather.location}}</strong></span>
|
||||
<span ng-class="weather.flight_condition ? 'col-xs-3 label label-success flight_condition_{{weather.flight_condition}}' : 'col-xs-3'">{{weather.type}}</span>
|
||||
<span class="col-xs-6 text-right">{{weather.time}}</span>
|
||||
<span class="col-xs-3" align="center">
|
||||
<div align="center" ng-class="weather.flight_condition ? ' label label-success flight_condition_{{weather.flight_condition}}' : 'label label-success report_{{weather.type}}'">{{weather.type}}</div>
|
||||
</span>
|
||||
<span class="col-xs-4"> </span>
|
||||
<span class="col-xs-2 text-right" style="background-color: {{weather.backcolor}}">{{weather.time}}</span>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<span class="col-xs-10">{{weather.data}}</span>
|
||||
|
|
Ładowanie…
Reference in New Issue