From 52ee1e50831083a846835a3d53e60fa610675bbf Mon Sep 17 00:00:00 2001 From: Bel LaPointe Date: Sun, 9 Apr 2023 11:25:51 -0600 Subject: [PATCH] mvp control via browser --- go.mod | 1 + go.sum | 2 + src/device/input/raw/public/root.html | 99 +++++++++++++++++++++ src/device/input/raw/raw.go | 5 ++ src/device/input/raw/raw_test.go | 1 + src/device/input/raw/udp.go | 5 -- src/device/input/raw/ws.go | 120 ++++++++++++++++++++++++++ 7 files changed, 228 insertions(+), 5 deletions(-) create mode 100644 src/device/input/raw/public/root.html create mode 100644 src/device/input/raw/ws.go diff --git a/go.mod b/go.mod index dc9e36a..58180e8 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( require ( github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/hajimehoshi/oto v0.7.1 // indirect github.com/micmonay/keybd_event v1.1.1 // indirect github.com/pkg/errors v0.9.1 // indirect diff --git a/go.sum b/go.sum index 5927eae..02f0ba5 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38r github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE= github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= github.com/hajimehoshi/oto v0.7.1 h1:I7maFPz5MBCwiutOrz++DLdbr4rTzBsbBuV2VpgU9kk= diff --git a/src/device/input/raw/public/root.html b/src/device/input/raw/public/root.html new file mode 100644 index 0000000..ee23bcf --- /dev/null +++ b/src/device/input/raw/public/root.html @@ -0,0 +1,99 @@ + + +
+ +
+ +
+

WHO AM I

+
+ +
+

CONTROLS

+
+
+ + + + +
+
+ + + +
+
+ + + + +
+
+
+
+
+ + + diff --git a/src/device/input/raw/raw.go b/src/device/input/raw/raw.go index bbf1f6f..f9630cd 100644 --- a/src/device/input/raw/raw.go +++ b/src/device/input/raw/raw.go @@ -9,6 +9,8 @@ import ( var ( FlagRawKeyboard = os.Getenv("RAW_KEYBOARD") == "true" FlagRawUDP = os.Getenv("RAW_UDP") + FlagRawWS = os.Getenv("RAW_WS") + FlagDebug = os.Getenv("DEBUG") != "" FlagRawRandomWeightFile = os.Getenv("RAW_RANDOM_WEIGHT_FILE") ) @@ -21,6 +23,9 @@ func New(ctx context.Context) Raw { if FlagRawKeyboard { return NewKeyboard() } + if port, _ := strconv.Atoi(FlagRawWS); port != 0 { + return NewWS(ctx, port) + } if port, _ := strconv.Atoi(FlagRawUDP); port != 0 { return NewUDP(ctx, port) } diff --git a/src/device/input/raw/raw_test.go b/src/device/input/raw/raw_test.go index 1e3036d..3eb8e37 100644 --- a/src/device/input/raw/raw_test.go +++ b/src/device/input/raw/raw_test.go @@ -6,4 +6,5 @@ func TestRaw(t *testing.T) { var _ Raw = &Random{} var _ Raw = UDP{} var _ Raw = Keyboard{} + var _ Raw = WS{} } diff --git a/src/device/input/raw/udp.go b/src/device/input/raw/udp.go index 05b2329..45dd19d 100644 --- a/src/device/input/raw/udp.go +++ b/src/device/input/raw/udp.go @@ -4,14 +4,9 @@ import ( "context" "log" "net" - "os" "strconv" ) -var ( - FlagDebug = os.Getenv("DEBUG") == "true" -) - type UDP struct { conn net.PacketConn c chan []byte diff --git a/src/device/input/raw/ws.go b/src/device/input/raw/ws.go new file mode 100644 index 0000000..e5887ae --- /dev/null +++ b/src/device/input/raw/ws.go @@ -0,0 +1,120 @@ +package raw + +import ( + "context" + _ "embed" + "fmt" + "log" + "net/http" + "os" + + "github.com/gorilla/websocket" +) + +type WS struct { + ctx context.Context + can context.CancelFunc + ch chan []byte +} + +func NewWS(ctx context.Context, port int) WS { + ctx, can := context.WithCancel(ctx) + ws := WS{ctx: ctx, can: can, ch: make(chan []byte, 256)} + go ws.listen(port) + return ws +} + +func (ws WS) Read() []byte { + select { + case v := <-ws.ch: + return v + case <-ws.ctx.Done(): + return nil + } +} + +func (ws WS) Close() { + ws.can() +} + +func (ws WS) listen(port int) { + server := &http.Server{ + Addr: fmt.Sprintf(":%d", port), + Handler: ws, + } + go func() { + if err := server.ListenAndServe(); err != nil && ws.ctx.Err() == nil { + panic(err) + } + }() + log.Println("WS on", port) + <-ws.ctx.Done() + server.Close() +} + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, +} + +func (ws WS) ServeHTTP(w http.ResponseWriter, r *http.Request) { + r = r.WithContext(ws.ctx) + if err := ws.serveHTTP(w, r); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + } +} + +func (ws WS) serveHTTP(w http.ResponseWriter, r *http.Request) error { + switch r.URL.Path { + case "/": + return ws.serveIndex(w, r) + case "/api/ws": + return ws.serveWS(w, r) + } + http.NotFound(w, r) + return nil +} + +//go:embed public/root.html +var rootHTML string + +func (ws WS) serveIndex(w http.ResponseWriter, r *http.Request) error { + v := rootHTML + if FlagDebug { + b, _ := os.ReadFile("src/device/input/raw/public/root.html") + v = string(b) + } + w.Write([]byte(v)) + return nil +} + +func (ws WS) serveWS(w http.ResponseWriter, r *http.Request) error { + if err := ws._serveWS(w, r); err != nil { + log.Println("_serveWS:", err) + } + return nil +} + +func (ws WS) _serveWS(w http.ResponseWriter, r *http.Request) error { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + return err + } + defer conn.Close() + + for ws.ctx.Err() == nil { + msgType, p, err := conn.ReadMessage() + if err != nil { + if websocket.IsCloseError(err) || websocket.IsUnexpectedCloseError(err) { + return nil + } + return err + } + if msgType == websocket.TextMessage { + log.Println(string(p)) + ws.ch <- p + } + } + return nil +}