Arquivos
Felix Geisendörfer 8921fbe17a Make MaxAngle configurable
2013-12-21 15:35:33 +01:00

184 linhas
4.2 KiB
Go

// Package http implements the HTTP interface for GoDrone.
//
// It currently handles serving the HTML UI and related assets, as well as
// WebSocket clients.
package http
import (
"code.google.com/p/go.net/websocket"
"encoding/json"
"fmt"
"github.com/felixge/godrone/attitude"
"github.com/felixge/godrone/control"
"github.com/felixge/godrone/drivers/navboard"
"github.com/felixge/godrone/http/fs"
"github.com/felixge/godrone/log"
"net/http"
"sync"
"time"
)
// Config holds the arguments required to create a Handler.
type Config struct {
Control *control.Control
Log log.Interface
Version string
ControlTimeout time.Duration
MaxAngle int
}
// Handler provides a http.Handler.
type Handler struct {
lock sync.Mutex
config Config
websocketHandler http.Handler
fileHandler http.Handler
listeners []chan update
}
type update struct {
NavData navboard.Data
AttitudeData attitude.Attitude
}
type setpoint struct {
attitude.Attitude
Throttle float64
}
// NewHandler returns a new handler.
func NewHandler(c Config) *Handler {
h := &Handler{
config: c,
fileHandler: http.FileServer(fs.Fs),
}
h.websocketHandler = websocket.Handler(h.handleWebsocket)
return h
}
func (h *Handler) handleWebsocket(conn *websocket.Conn) {
var (
log = h.config.Log
ip = conn.Request().RemoteAddr
setCh = make(chan setpoint, 1)
setErrCh = make(chan error, 1)
zero setpoint
)
defer func() {
// prevent drone from flying into outer space if connection gets
// interrupted.
h.config.Control.Set(zero.Attitude, zero.Throttle)
}()
defer conn.Close()
log.Info("New WebSocket connection. ip=%s", ip)
defer log.Info("Closed WebSocket connection. ip=%s", ip)
updateCh := h.sub()
defer h.unsub(updateCh)
go func() {
for {
var s setpoint
if err := conn.SetReadDeadline(time.Now().Add(h.config.ControlTimeout)); err != nil {
setErrCh <- err
return
}
if err := websocket.JSON.Receive(conn, &s); err != nil {
setErrCh <- err
return
}
setCh <- s
}
}()
for {
select {
case u := <-updateCh:
if err := conn.SetWriteDeadline(time.Now().Add(h.config.ControlTimeout)); err != nil {
log.Warn("WebSocket could not set write deadline. err='%s' ip='%s'", err, ip)
return
}
if err := websocket.JSON.Send(conn, u); err != nil {
log.Warn("WebSocket send error. err='%s' ip='%s'", err, ip)
return
}
case s := <-setCh:
h.config.Control.Set(s.Attitude, s.Throttle)
case err := <-setErrCh:
log.Warn("WebSocket receive error. err='%s' ip='%s'", err, ip)
return
}
}
}
func (h *Handler) handleConfig(w http.ResponseWriter, r *http.Request) {
config := map[string]interface{}{
"Version": h.config.Version,
"MaxAngle": h.config.MaxAngle,
}
data, err := json.Marshal(config)
if err != nil {
err = h.config.Log.Error("Could marshal JSON config. err=%s", err)
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "%s", err)
return
}
w.Header().Set("Content-Type", "text/javascript; charset=UTF-8")
fmt.Fprintf(w, "window.Config = %s;", data)
}
// ServeHTTP implements the http.Handler interface. It acts as a router
// dispatching websocket / asset requests.
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" && r.URL.Path == "/ws" {
h.websocketHandler.ServeHTTP(w, r)
return
}
if r.Method == "GET" && r.URL.Path == "/js/config.js" {
h.handleConfig(w, r)
return
}
h.fileHandler.ServeHTTP(w, r)
}
// Update informs all connected clients about new navboard.Data and
// attitude.Data.
func (h *Handler) Update(n navboard.Data, a attitude.Attitude) {
h.pub(update{n, a})
}
func (h *Handler) pub(u update) {
h.lock.Lock()
defer h.lock.Unlock()
for _, ch := range h.listeners {
select {
case ch <- u:
default:
}
}
}
func (h *Handler) sub() chan update {
ch := make(chan update)
h.lock.Lock()
defer h.lock.Unlock()
h.listeners = append(h.listeners, ch)
return ch
}
func (h *Handler) unsub(ch chan update) {
h.lock.Lock()
defer h.lock.Unlock()
for i, chEntry := range h.listeners {
if ch == chEntry {
h.listeners = append(h.listeners[:i], h.listeners[i+1:]...)
return
}
}
panic("failed to unsub")
}