view service/laundry/laundry.go @ 109:868e759aeb74

strings for all hardware values. rewrite rdf graph output. gofmt Ignore-this: 9344993fdda111d34191c11e3cda17ec
author drewp@bigasterisk.com
date Sun, 01 Sep 2013 01:35:36 -0700
parents 08eb981b6cf5
children 94e26e1e0668
line wrap: on
line source

package main

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

/*
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                                                              string
}

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

func (h *Hardware) GetMotion() string {
	if DigitalRead(h.InMotion) == 0 {
		return "motion"
	} else {
		return "noMotion"
	}
}

func (h *Hardware) GetDoor() string {
	if DigitalRead(h.InDoorClosed) == 1 {
		return "closed"
	} else {
		return "open"
	}
}

func (h *Hardware) GetSwitch(which string) string {
	var level int
	switch which {
	case "1":
		level = DigitalRead(h.InSwitch1)
	case "2":
		level = DigitalRead(h.InSwitch2)
	case "3":
		level = DigitalRead(h.InSwitch3)
	}
	if level == 0 {
		return "closed"
	} else {
		return "open"
	}
}

func (h *Hardware) GetLed() string {
	return h.LastOutLed
}

func (h *Hardware) GetStrike() string {
	return h.LastOutStrike
}

func (h *Hardware) SetLed(state string) {
	switch state {
	case "on":
		hwio.DigitalWrite(h.OutLed, 1)
	case "off":
		hwio.DigitalWrite(h.OutLed, 0)
	default:
		panic(errors.New("unknown state"))
	}
	h.LastOutLed = state
}

func (h *Hardware) SetStrike(state string) {
	switch state {
	case "unlocked":
		hwio.DigitalWrite(h.OutStrike, 1)
	case "locked":
		hwio.DigitalWrite(h.OutStrike, 0)
	default:
		panic(errors.New("unknown state"))
	}
	h.LastOutStrike = state
}

// 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 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_PULLDOWN); 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)
	}
	pins.SetLed("off")
	pins.SetStrike("locked")
	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 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]interface{}{
			"motion":     pins.GetMotion(),
			"switch1":    pins.GetSwitch("1"),
			"switch2":    pins.GetSwitch("2"),
			"switch3":    pins.GetSwitch("3"),
			"doorClosed": pins.GetDoor(),
			"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("laundryDoor")

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

		for subj, state := range map[*goraptor.Uri]*goraptor.Uri{
			ROOM("laundryDoorMotion"):  ROOM(pins.GetMotion()),
			ROOM("laundryDoorOpen"):    ROOM(pins.GetDoor()),
			ROOM("laundryDoorSwitch1"): ROOM(pins.GetSwitch("1")),
			ROOM("laundryDoorSwitch2"): ROOM(pins.GetSwitch("2")),
			ROOM("laundryDoorSwitch3"): ROOM(pins.GetSwitch("3")),
			ROOM("laundryDoorLed"):     ROOM(pins.GetLed()),
			ROOM("laundryDoorStrike"):  ROOM(pins.GetStrike()),
		} {
			statements <- &(goraptor.Statement{subj, ROOM("state"), state, graph})
		}

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

		pins.SetLed(string(body))
		return goweb.Respond.WithStatusText(c, http.StatusAccepted)
	})

	goweb.Map("PUT", "/strike", func(c context.Context) error {
		body, err := c.RequestBody()
		if err != nil {
			panic(err)
		}

		pins.SetStrike(string(body))
		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.
			pins.SetStrike("unlocked")
			go func() {
				time.Sleep(time.Duration(req.Seconds *
					float64(time.Second)))
				pins.SetStrike("locked")
			}()
			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)
}