view service/laundry/laundry.go @ 107:08eb981b6cf5

rest of rdf graph output Ignore-this: f9c97bfa068be00462df094bca3165d8
author drewp@bigasterisk.com
date Sun, 01 Sep 2013 00:45:15 -0700
parents 90be7f99696f
children 868e759aeb74
line wrap: on
line source

package main

import (
	"encoding/json"
	"log"
	"net"
	"net/http"
	"strconv"
	"fmt"
	"time"
	"runtime"
	"github.com/mrmorphic/hwio"
	"github.com/stretchr/goweb"
	"github.com/stretchr/goweb/context"
	"bitbucket.org/ww/goraptor"
)

/*
hwio.DebugPinMap() wrote this:

Pin 1: 3.3V,  cap:
Pin 2: 5V,  cap:
Pin 3: SDA,GPIO0  cap:output,input,input_pullup,input_pulldown
Pin 5: SCL,GPIO1  cap:output,input,input_pullup,input_pulldown
Pin 6: GROUND,  cap:
Pin 7: GPIO4  cap:output,input,input_pullup,input_pulldown
Pin 8: TXD,GPIO14  cap:output,input,input_pullup,input_pulldown
Pin 10: RXD,GPIO15  cap:output,input,input_pullup,input_pulldown
Pin 11: GPIO17  cap:output,input,input_pullup,input_pulldown
Pin 12: GPIO18  cap:output,input,input_pullup,input_pulldown
Pin 13: GPIO21  cap:output,input,input_pullup,input_pulldown
Pin 15: GPIO22  cap:output,input,input_pullup,input_pulldown
Pin 16: GPIO23  cap:output,input,input_pullup,input_pulldown
Pin 18: GPIO24  cap:output,input,input_pullup,input_pulldown
Pin 19: MOSI,GPIO10  cap:output,input,input_pullup,input_pulldown
Pin 21: MISO,GPIO9  cap:output,input,input_pullup,input_pulldown
Pin 22: GPIO25  cap:output,input,input_pullup,input_pulldown
Pin 23: SCLK,GPIO11  cap:output,input,input_pullup,input_pulldown
Pin 24: CE0N,GPIO8  cap:output,input,input_pullup,input_pulldown
Pin 26: CE1N,GPIO7  cap:output,input,input_pullup,input_pulldown
*/

type Hardware struct {
	InMotion, InSwitch3, InSwitch1, InSwitch2, OutLed, OutSpeaker, InDoorClosed, OutStrike hwio.Pin
	LastOutLed, LastOutStrike int
}

// hwio.GetPin with a panic instead of an error return
func GetPin(id string) hwio.Pin {
	p, e := hwio.GetPin(id)
	if e != nil {
		panic(e)
	}
	return p
}

func DigitalRead(p hwio.Pin) int {
	v, err := hwio.DigitalRead(p)
	if err != nil {
		panic(err)
	}
	return v
}

func SetupIo() Hardware {
//	return Hardware{}
	pins := Hardware{
		InMotion:		GetPin("GPIO2"), // pi rev2 calls it GPIO2
		InSwitch3:		GetPin("GPIO3"), // pi rev2 calls it GPIO3
		InSwitch1:		GetPin("GPIO4"),
		InSwitch2:		GetPin("GPIO17"),
		OutLed:			GetPin("GPIO27"), // pi rev2 calls it GPIO27
		OutSpeaker:		GetPin("GPIO22"),
		InDoorClosed:	GetPin("GPIO10"),
		OutStrike:      GetPin("GPIO9"),
	}
	
	if err := hwio.PinMode(pins.InMotion,	  hwio.INPUT_PULLUP); err != nil { panic(err) }
	if err := hwio.PinMode(pins.InSwitch1,	  hwio.INPUT_PULLUP); err != nil { panic(err) }
	if err := hwio.PinMode(pins.InSwitch2,	  hwio.INPUT_PULLUP); err != nil { panic(err) }
	if err := hwio.PinMode(pins.InSwitch3,	  hwio.INPUT_PULLUP); err != nil { panic(err) }
	if err := hwio.PinMode(pins.InDoorClosed, hwio.INPUT_PULLUP); err != nil { panic(err) }
	if err := hwio.PinMode(pins.OutLed,       hwio.OUTPUT); err != nil { panic(err) }
	if err := hwio.PinMode(pins.OutSpeaker,	  hwio.OUTPUT); err != nil { panic(err) }
	if err := hwio.PinMode(pins.OutStrike,	  hwio.OUTPUT); err != nil { panic(err) }
	return pins
}

func serializeGowebResponse(
	c context.Context,
	syntaxName string,
	statements chan *goraptor.Statement) error {
	serializer := goraptor.NewSerializer(syntaxName)
	defer serializer.Free()

	str, err := serializer.Serialize(statements, "")
	if err != nil {
		panic(err);
	}
	c.HttpResponseWriter().Header().Set("Content-Type",
		goraptor.SerializerSyntax[syntaxName].MimeType)
	return goweb.Respond.With(c, 200, []byte(str))
}

func namespace(ns string) (func(string) *goraptor.Uri) {
	return func (path string) *goraptor.Uri {
		var u goraptor.Uri = goraptor.Uri(ns + path)
		return &u
	}
}

func literal(v string, datatype *goraptor.Uri) (ret *goraptor.Literal) {
	ret = new(goraptor.Literal)
	ret.Value = v
	if datatype != nil {
		ret.Datatype = string(*datatype)
	}
	return
}

func nowLiteral() *goraptor.Literal {
	XS := namespace("http://www.w3.org/2001/XMLSchema#")
	rfc3999Time, err := time.Now().MarshalJSON()
	if err != nil {
		panic(err)
	}
	return literal(string(rfc3999Time[:]), XS("dateTime"))
}

func twoState(
	graph *goraptor.Uri,
	subject *goraptor.Uri,
	test interface{},
	trueVal interface{}, trueObject *goraptor.Uri,
	falseVal interface{}, falseObject *goraptor.Uri,
) *goraptor.Statement {
	ROOM := namespace("http://projects.bigasterisk.com/room/")
	var motionState goraptor.Term
	if test == trueVal {
		motionState = trueObject
	} else if test == falseVal {
		motionState = falseObject
	} else {
		motionState = literal(fmt.Sprintf("%v", test), nil)
	}
	return &(goraptor.Statement{
		subject, ROOM("state"), motionState, graph})
}

func main() {
	pins := SetupIo()

	goweb.MapStatic("/static", "static")

	// this one needs to fail if the hardware is broken in
	// any way that we can determine, though I'm not sure
	// what that will mean on rpi
	goweb.MapStaticFile("/", "index.html")
	
	goweb.Map("GET", "/status", func(c context.Context) error {
		jsonEncode := json.NewEncoder(c.HttpResponseWriter())
		jsonEncode.Encode(map[string]int{
			"motion": DigitalRead(pins.InMotion),
			"switch1": DigitalRead(pins.InSwitch1),
			"switch2": DigitalRead(pins.InSwitch2),
			"switch3": DigitalRead(pins.InSwitch3),
			"doorClosed": DigitalRead(pins.InDoorClosed),
			"led": pins.LastOutLed,
			"strike": pins.LastOutStrike,
		})
		return nil
	})

	goweb.Map("GET", "/graph", func(c context.Context) error {
		DC := namespace("http://purl.org/dc/terms/")
		ROOM := namespace("http://projects.bigasterisk.com/room/")

		statements := make(chan *goraptor.Statement, 100)

		graph := ROOM("laundrySensors")

		_, thisFile, _, _ := runtime.Caller(0)
		statements <- &(goraptor.Statement{
			graph, DC("creator"), literal(thisFile, nil), graph})
		statements <- &(goraptor.Statement{
			graph, DC("modified"), nowLiteral(), graph})

		statements <- twoState(graph, ROOM("laundryDoorMotion"),
			DigitalRead(pins.InMotion),
			1, ROOM("motion"),
			0, ROOM("noMotion"))

		statements <- twoState(graph, ROOM("laundryDoorOpen"),
			DigitalRead(pins.InDoorClosed),
			1, ROOM("closed"),
			0, ROOM("open"))

		for i, p := range map[string]hwio.Pin{
			"1": pins.InSwitch1,
			"2": pins.InSwitch2,
			"3": pins.InSwitch3} {
			statements <- twoState(
				graph, ROOM("laundryDoorSwitch" + i),
				DigitalRead(p),
				1, ROOM("closed"),
				0, ROOM("open"))
		}

		statements <- twoState(graph, ROOM("laundryDoorLed"),
			pins.LastOutLed, 1, ROOM("on"), 0, ROOM("off"))
		
		statements <- twoState(graph, ROOM("laundryDoorStrike"),
			pins.LastOutLed, 1, ROOM("unlocked"), 0, ROOM("locked"))
		
		close(statements)
		// type should be chosen with accept header. trig is
		// causing segfaults.
		return serializeGowebResponse(c, "nquads", statements)
	})

	goweb.Map("PUT", "/led", func(c context.Context) error {
		body, err := c.RequestBody()
		if err != nil {
			panic(err)
		}
		
		var level int
		if string(body) == "on" {
			level = 1
		} else if string(body) == "off" {
			level = 0
		} else {
			return goweb.Respond.With(c, http.StatusBadRequest,
				[]byte("body must be 'on' or 'off'"))
		}

		hwio.DigitalWrite(pins.OutLed, level)
		pins.LastOutLed = level
		return goweb.Respond.WithStatusText(c, http.StatusAccepted)
	})

	setStrike := func (level int) {
		hwio.DigitalWrite(pins.OutStrike, level)
		pins.LastOutStrike = level
	}
	
	goweb.Map("PUT", "/strike", func(c context.Context) error {
		body, err := c.RequestBody()
		if err != nil {
			panic(err)
		}

		level, err := strconv.Atoi(string(body[:]))
		if err != nil {
			return goweb.Respond.With(c, http.StatusBadRequest,
				[]byte("body must be '0' or '1'"))
		}

		setStrike(level)
		return goweb.Respond.WithStatusText(c, http.StatusAccepted)
	})
	
	goweb.Map(
		"PUT", "/strike/temporaryUnlock",
		func(c context.Context) error {
			type TemporaryUnlockRequest struct {
				Seconds float64
			}

			var req TemporaryUnlockRequest
			err := json.NewDecoder(c.HttpRequest().Body).
				Decode(&req)
			if err != nil {
				panic(err)
			}

			// This is not correctly reentrant. There should be a
			// stack of temporary effects that unpop correctly,
			// and status should show you any running effects.
			setStrike(1)
			go func() {
				time.Sleep(time.Duration(req.Seconds *
					float64(time.Second)))
				setStrike(0)
			}()
			return goweb.Respond.WithStatusText(
				c, http.StatusAccepted)
		})

	goweb.Map("PUT", "/speaker/beep", func(c context.Context) error {
		// queue a beep
		return goweb.Respond.WithStatusText(c, http.StatusAccepted)
	})


	address := ":8081"
	
	s := &http.Server{
		Addr:           address,
		Handler:        goweb.DefaultHttpHandler(),
		ReadTimeout:    10 * time.Second,
		WriteTimeout:   10 * time.Second,
	}

	log.Printf("Listening on port %s", address)
	log.Printf("%s", goweb.DefaultHttpHandler())
	listener, listenErr := net.Listen("tcp", address)
	if listenErr != nil {
		panic(listenErr)
	}
	s.Serve(listener)
}