diff --git a/.gitignore b/.gitignore index e9517ad..7ecb1e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *~ *.swo *.swp +/bin diff --git a/just b/just new file mode 100755 index 0000000..ade6d6a --- /dev/null +++ b/just @@ -0,0 +1,56 @@ +#! /bin/bash + +set -eu +cd `dirname $0` + +NAME="bitvishttp" +VERSION="$(git describe --always --dirty) ($(date --date="@$(git show -s --format='%ct' HEAD)" '+%Y-%m-%d'))" +WORKSPACE="$PWD" +BIN="$WORKSPACE/bin" +LIB="$WORKSPACE/lib" +GOPATH="$LIB/gopath" + +mkdir -p "$BIN" +mkdir -p "$LIB" + +usage() { +cat << EOF +SYNOPSIS + + This is a very handy tool to manage this Go project. + +USAGE + $ $0 install # Install dependencies + $ $0 build # Build a binary + $ $0 run # Run the currently built binary +EOF +} + +case ${1:-} in + "install") + echo "*** Installing dependencies ***" + cat "$WORKSPACE/src/Godeps" | \ + while read dep; do + pkg=`echo $dep | cut -f1 -d' '` + rev=`echo $dep | cut -f2 -d' '` + echo " Installing $pkg" + GOPATH="$GOPATH" go get "$pkg" + pushd "$GOPATH/src/$pkg" > /dev/null; git checkout $rev --quiet; popd > /dev/null + done + ;; + + "build") + echo "*** Building Project ***" + cd "$WORKSPACE/src" + GOPATH="$GOPATH" go build -o "$WORKSPACE/bin/$NAME" + cd "$WORKSPACE" + ;; + + "run") + "$BIN/$NAME" + ;; + + *) + usage + ;; +esac diff --git a/src/image.go b/src/image.go new file mode 100644 index 0000000..044bc05 --- /dev/null +++ b/src/image.go @@ -0,0 +1,93 @@ +package main + +import ( + "image" + "image/color" + "io" + "io/ioutil" + "log" + "net" + "time" +) + +const ( + LedpanelWidth = 120 + LedpanelHeight = 48 +) + +func Listen() (chan image.Image, chan error) { + errs := make(chan error, 1) + out := make(chan image.Image, 1) + out <- bitvisImage{} + go func() { + defer close(errs) + defer close(out) + service, err := net.Listen("tcp", ":1338") + if err != nil { + errs <- err + return + } + for { + conn, err := service.Accept() + if err != nil { + errs <- err + return + } + go func() { + if err := handleConnection(conn, out); err != nil { + log.Printf("Error handling connection: %v", err) + } + }() + } + }() + return out, errs +} + +func handleConnection(conn net.Conn, out chan<- image.Image) error { + for { + if err := conn.SetReadDeadline(time.Now().Add(time.Second * 4)); err != nil { + return err + } + + var boundary [1]byte + if _, err := io.ReadFull(conn, boundary[:]); err != nil { + return err + } + if boundary[0] != ':' { + continue + } + io.CopyN(ioutil.Discard, conn, 2) + + var img bitvisImage + if _, err := io.ReadFull(conn, img[:]); err != nil { + return err + } + select { + case out <- img: + default: + } + } +} + +type bitvisImage [LedpanelWidth * LedpanelHeight / 4]uint8 + +func (img bitvisImage) ColorModel() color.Model { + return color.RGBAModel +} + +func (img bitvisImage) Bounds() image.Rectangle { + return image.Rectangle{ + Min: image.Point{X: 0, Y: 0}, + Max: image.Point{X: LedpanelWidth, Y: LedpanelHeight}, + } +} + +func (img bitvisImage) At(x, y int) color.Color { + return bitvisColor(img[y*LedpanelWidth/4+x/4] >> ((3 - uint(x)%4) * 2)) +} + +type bitvisColor uint8 + +func (c bitvisColor) RGBA() (r, g, b, a uint32) { + return uint32(c&2>>1) * 0xffff, uint32(c&1) * 0xffff, 0, 0xffff +} diff --git a/src/main.go b/src/main.go new file mode 100644 index 0000000..159215c --- /dev/null +++ b/src/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "bytes" + "fmt" + "image/png" + "io" + "log" + "net/http" + "sync" + "time" +) + +func main() { + images, errors := Listen() + go func() { + err := <-errors + log.Fatal(err) + }() + var currentFrame []byte + var currentFrameLock sync.RWMutex + + mux := http.NewServeMux() + mux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { + res.Header().Set("Content-Type", "text/html") + res.Write([]byte(` + + + Bitvis + + + + + + +
+ + + +
+ + + `)) + }) + mux.HandleFunc("/stream.mpng", func(res http.ResponseWriter, req *http.Request) { + res.Header().Set("Content-Type", "multipart/x-mixed-replace; boundary=--pngboundary") + res.WriteHeader(http.StatusOK) + for { + currentFrameLock.RLock() + buf := currentFrame + currentFrameLock.RUnlock() + res.Write([]byte("--pngboundary")) + res.Write([]byte("Content-Type: image/png\n")) + res.Write([]byte(fmt.Sprintf("Content-Length: %d\n\n", len(buf)))) + if _, err := io.Copy(res, bytes.NewReader(buf)); err != nil { + return + } + time.Sleep(time.Millisecond * 2) + } + }) + mux.HandleFunc("/frame.png", func(res http.ResponseWriter, req *http.Request) { + res.Header().Set("Content-Type", "image/png") + currentFrameLock.RLock() + buf := currentFrame + currentFrameLock.RUnlock() + io.Copy(res, bytes.NewReader(buf)) + }) + go func() { + log.Fatal(http.ListenAndServe(":13378", mux)) + }() + + for img := range images { + var buf bytes.Buffer + enc := png.Encoder{CompressionLevel: png.BestSpeed} + enc.Encode(&buf, img) + currentFrameLock.Lock() + currentFrame = buf.Bytes() + currentFrameLock.Unlock() + } +}