stratux/main/managementinterface.go

313 wiersze
9.8 KiB
Go

/*
Copyright (c) 2015-2016 Christopher Young
Distributable under the terms of The "BSD New"" License
that can be found in the LICENSE file, herein included
as part of this header.
managementinterface.go: Web interfaces (JSON and websocket), web server for web interface HTML.
*/
package main
import (
"encoding/hex"
"encoding/json"
"fmt"
"golang.org/x/net/websocket"
"io"
"log"
"net/http"
"strings"
"syscall"
"time"
)
type SettingMessage struct {
Setting string `json:"setting"`
Value bool `json:"state"`
}
// Weather updates channel.
var weatherUpdate *uibroadcaster
var trafficUpdate *uibroadcaster
/*
The /weather websocket starts off by sending the current buffer of weather messages, then sends updates as they are received.
*/
func handleWeatherWS(conn *websocket.Conn) {
// Subscribe the socket to receive updates.
weatherUpdate.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)
}
}
// Works just as weather updates do.
func handleTrafficWS(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)
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)
}
}
func handleStatusWS(conn *websocket.Conn) {
// log.Printf("Web client connected.\n")
timer := time.NewTicker(1 * time.Second)
for {
// The below is not used, but should be if something needs to be streamed from the web client ever in the future.
/* var msg SettingMessage
err := websocket.JSON.Receive(conn, &msg)
if err == io.EOF {
break
} else if err != nil {
log.Printf("handleStatusWS: %s\n", err.Error())
} else {
// Use 'msg'.
}
*/
// Send status.
<-timer.C
update, _ := json.Marshal(&globalStatus)
_, err := conn.Write(update)
if err != nil {
// log.Printf("Web client disconnected.\n")
break
}
}
}
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) {
setNoCache(w)
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "application/json")
statusJSON, _ := json.Marshal(&globalStatus)
fmt.Fprintf(w, "%s\n", statusJSON)
}
// AJAX call - /getSituation. Responds with current situation (lat/lon/gdspeed/track/pitch/roll/heading/etc.)
func handleSituationRequest(w http.ResponseWriter, r *http.Request) {
setNoCache(w)
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "application/json")
situationJSON, _ := json.Marshal(&mySituation)
fmt.Fprintf(w, "%s\n", situationJSON)
}
// AJAX call - /getTowers. Responds with all ADS-B ground towers that have sent messages that we were able to parse, along with its stats.
func handleTowersRequest(w http.ResponseWriter, r *http.Request) {
setNoCache(w)
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "application/json")
towersJSON, _ := json.Marshal(&ADSBTowers)
// for testing purposes, we can return a fixed reply
// towersJSON = []byte(`{"(38.490880,-76.135554)":{"Lat":38.49087953567505,"Lng":-76.13555431365967,"Signal_strength_last_minute":100,"Signal_strength_max":67,"Messages_last_minute":1,"Messages_total":1059},"(38.978698,-76.309276)":{"Lat":38.97869825363159,"Lng":-76.30927562713623,"Signal_strength_last_minute":495,"Signal_strength_max":32,"Messages_last_minute":45,"Messages_total":83},"(39.179285,-76.668413)":{"Lat":39.17928457260132,"Lng":-76.66841268539429,"Signal_strength_last_minute":50,"Signal_strength_max":24,"Messages_last_minute":1,"Messages_total":16},"(39.666309,-74.315300)":{"Lat":39.66630935668945,"Lng":-74.31529998779297,"Signal_strength_last_minute":9884,"Signal_strength_max":35,"Messages_last_minute":4,"Messages_total":134}}`)
fmt.Fprintf(w, "%s\n", towersJSON)
}
// AJAX call - /getSettings. Responds with all stratux.conf data.
func handleSettingsGetRequest(w http.ResponseWriter, r *http.Request) {
setNoCache(w)
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "application/json")
settingsJSON, _ := json.Marshal(&globalSettings)
fmt.Fprintf(w, "%s\n", settingsJSON)
}
// AJAX call - /setSettings. receives via POST command, any/all stratux.conf data.
func handleSettingsSetRequest(w http.ResponseWriter, r *http.Request) {
// define header in support of cross-domain AJAX
setNoCache(w)
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Method", "GET, POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
w.Header().Set("Content-Type", "application/json")
// for an OPTION method request, we return header without processing.
// this insures we are recognized as supporting cross-domain AJAX REST calls
if r.Method == "POST" {
// raw, _ := httputil.DumpRequest(r, true)
// log.Printf("handleSettingsSetRequest:raw: %s\n", raw)
decoder := json.NewDecoder(r.Body)
for {
var msg map[string]interface{} // support arbitrary JSON
err := decoder.Decode(&msg)
if err == io.EOF {
break
} else if err != nil {
log.Printf("handleSettingsSetRequest:error: %s\n", err.Error())
} else {
for key, val := range msg {
// log.Printf("handleSettingsSetRequest:json: testing for key:%s of type %s\n", key, reflect.TypeOf(val))
switch key {
case "UAT_Enabled":
globalSettings.UAT_Enabled = val.(bool)
case "ES_Enabled":
globalSettings.ES_Enabled = val.(bool)
case "GPS_Enabled":
globalSettings.GPS_Enabled = val.(bool)
case "AHRS_Enabled":
globalSettings.AHRS_Enabled = val.(bool)
case "DEBUG":
globalSettings.DEBUG = val.(bool)
case "ReplayLog":
v := val.(bool)
if v != globalSettings.ReplayLog { // Don't mark the files unless there is a change.
globalSettings.ReplayLog = v
replayMark(v)
}
case "PPM":
globalSettings.PPM = int(val.(float64))
case "WatchList":
globalSettings.WatchList = val.(string)
case "OwnshipModeS":
// Expecting a hex string less than 6 characters (24 bits) long.
if len(val.(string)) > 6 { // Too long.
continue
}
// Pad string, must be 6 characters long.
vals := strings.ToUpper(val.(string))
for len(vals) < 6 {
vals = "0" + vals
}
hexn, err := hex.DecodeString(vals)
if err != nil { // Number not valid.
log.Printf("handleSettingsSetRequest:OwnshipModeS: %s\n", err.Error())
continue
}
globalSettings.OwnshipModeS = fmt.Sprintf("%02X%02X%02X", hexn[0], hexn[1], hexn[2])
default:
log.Printf("handleSettingsSetRequest:json: unrecognized key:%s\n", key)
}
}
saveSettings()
}
}
// while it may be redundent, we return the latest settings
settingsJSON, _ := json.Marshal(&globalSettings)
fmt.Fprintf(w, "%s\n", settingsJSON)
}
}
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 setNoCache(w http.ResponseWriter) {
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
}
func defaultServer(w http.ResponseWriter, r *http.Request) {
setNoCache(w)
http.FileServer(http.Dir("/var/www")).ServeHTTP(w, r)
}
func managementInterface() {
weatherUpdate = NewUIBroadcaster()
trafficUpdate = NewUIBroadcaster()
http.HandleFunc("/", defaultServer)
http.Handle("/logs/", http.StripPrefix("/logs/", http.FileServer(http.Dir("/var/log"))))
http.HandleFunc("/status",
func(w http.ResponseWriter, req *http.Request) {
s := websocket.Server{
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{
Handler: websocket.Handler(handleWeatherWS)}
s.ServeHTTP(w, req)
})
http.HandleFunc("/traffic",
func(w http.ResponseWriter, req *http.Request) {
s := websocket.Server{
Handler: websocket.Handler(handleTrafficWS)}
s.ServeHTTP(w, req)
})
http.HandleFunc("/getStatus", handleStatusRequest)
http.HandleFunc("/getSituation", handleSituationRequest)
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)
if err != nil {
log.Printf("managementInterface ListenAndServe: %s\n", err.Error())
}
}