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()) } }