Add support for circuitry using "real hardware" connected to a Pi.

This commit adds support for using the RaspberryPi's GPIO pins to
detect the state of the lock using a magnetic switch and change the
state of a lock using a relay (well, for now using a "buzzer").
See README.md for the circuit "diagram".

For testing on machines without the GPIO pins, a dummy implementation is
provided that prints status to STDOUT.

Currently, all this is placed in an "internal" package just so that
we can have a separate binary (hwtest) to test out the circuitry.
We might want to get rid of that binary and the "internal" package
altogether - folding the contents into "package main" for "lockd".

Change-Id: Ie8f3d72a8166ce81d80b6771a49591f2f3f17b4f
diff --git a/README.md b/README.md
index 8e86deb..02ba9a6 100644
--- a/README.md
+++ b/README.md
@@ -9,26 +9,71 @@
 Features to add to the Lock server:
 
 1) Auditing: This is one of the strong features of our model, so I'd imagine that we'd want a:
+```
 AuditLog(startTime, endTime time.Time) []AuditLog | error
 type AuditLog struct {
   Blessing string
   Action LockStatus
   Timestamp time.Time
 }
-We'd also have to work out how to control access to this AuditLog. One option is to use caveats - so when "making" a new key one can choose to insert the "noadmin" caveat?
+```
+We'd also have to work out how to control access to this AuditLog. One option
+is to use caveats - so when "making" a new key one can choose to insert the
+"noadmin" caveat?
 
 Features to add to the Lock client:
 
-1) "makekey"
-
-> makekey <lockname> <for>
-makekey creates a key for the specified lock and principal.
-<lockname> is the name of the lock object for which 'key' should be created,
-and <for> is the public key of the principal to which the minted key must
-be bound to.
-
-2) "sendkey" <lockname> <email>
+1) `sendkey <lockname> <email>`
 sendkey sends a key for the specified lock and any principal with the specified
-<email> who is current running "recvkey".
+`<email>` who is currently running a `recvkey` command.
+
+# Circuitry with the RaspberryPi
+
+### Equipment
+- 10KΩ resistor
+- 1KΩ resistor
+- Magnetic switch (normally open circuit - closed when the sensor moves away)
+  (e.g. [P/N 8601 Magnetic Switch](http://www.amazon.com/gp/product/B0009SUF08/ref=oh_aui_detailpage_o03_s00?ie=UTF8&psc=1))
+- For now, an active buzzer to simulate a relay.
+  Will fill in the relay details here once we have that setup.
+- Breadboard, jumper cables, ribbon cable - or whatever is needed to connect to the RPi's GPIO pins
+
+### Circuitry
+
+Apologies for this unconventional, possibly unreadable circuit representation. Putting it down
+so that the author can remember! TODO(ashankar): Clean it up!
+
+The pin number assignments here work both for RaspberryPi Model B/B+ and RaspberryPi2-ModelB.
+
+```
+---(Pin 1)-------/\/\(10KΩ)/\/\---------(COM port of magnetic switch)
+                                  \
+                                   \----/\/\(1KΩ)/\/\---------(Pin 15 = GPIO22)
+                                                          \
+                                                           \----(LED)-----|
+                                                                          |
+                                                                          |
+                                          (N.O. port of magnetic switch)--|
+                                                                          |
+                                                                          |
+                                         (-ve terminal of active buzzer)--|
+                                                                          |
+                                                                          |
+                                                                          |
+                                                           (Pin 6 = GND)--|
+
+---(Pin 11 = GPIO17)-----------(+ terminal of active buzzer)
+```
 
 # Deployment
+
+To build for the RaspberryPi setup with the circuitry mentioned above:
+```
+v23 go get -u github.com/davecheney/gpio
+V23_PROFILE=arm v23 go install v.io/x/lock/lockd
+scp $V23_ROOT/release/projects/physical-lock/go/bin/lockd <rpi_scp_location>
+```
+
+If building without the `arm` profile, there are no physical switches/relays
+and instead a simulated hardware is used that uses the interrupt signal
+(SIGINT) to simulate locking/unlocking externally.
diff --git a/go/src/v.io/x/lock/lockd/internal/hardware.go b/go/src/v.io/x/lock/lockd/internal/hardware.go
new file mode 100644
index 0000000..0c0400c
--- /dev/null
+++ b/go/src/v.io/x/lock/lockd/internal/hardware.go
@@ -0,0 +1,21 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package internal
+
+import "v.io/x/lock"
+
+var hardware Hardware // The single global instance of Hardware, initialized by init()
+
+// Hardware abstracts the interface for physically manipulating the lock.
+type Hardware interface {
+	// Status returns the current state of the lock.
+	Status() lock.LockStatus
+
+	// SetStatus changes the state of the lock to the provided one.
+	SetStatus(s lock.LockStatus) error
+}
+
+// GetHardware returns the singleton instance of Hardware
+func GetHardware() Hardware { return hardware }
diff --git a/go/src/v.io/x/lock/lockd/internal/hardware_rpi.go b/go/src/v.io/x/lock/lockd/internal/hardware_rpi.go
new file mode 100644
index 0000000..825c21b
--- /dev/null
+++ b/go/src/v.io/x/lock/lockd/internal/hardware_rpi.go
@@ -0,0 +1,67 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build arm
+
+package internal
+
+import (
+	"fmt"
+	"sync"
+	"time"
+
+	"github.com/davecheney/gpio"
+	"github.com/davecheney/gpio/rpi"
+
+	"v.io/x/lock"
+)
+
+const toggleWaitTime = 5 * time.Second
+
+type hw struct {
+	relay   gpio.Pin
+	monitor gpio.Pin
+	mu      sync.Mutex // To allow for only one SetStatus invocation at a time.
+}
+
+func init() {
+	relay, err := gpio.OpenPin(rpi.GPIO17, gpio.ModeOutput)
+	if err != nil {
+		panic(err)
+	}
+	relay.Clear()
+
+	monitor, err := gpio.OpenPin(rpi.GPIO22, gpio.ModeInput)
+	if err != nil {
+		relay.Close()
+		panic(err)
+	}
+
+	hardware = &hw{relay: relay, monitor: monitor}
+}
+
+func (hw *hw) Status() lock.LockStatus {
+	if hw.monitor.Get() {
+		return lock.Unlocked
+	}
+	return lock.Locked
+}
+
+func (hw *hw) SetStatus(status lock.LockStatus) error {
+	hw.mu.Lock()
+	defer hw.mu.Unlock()
+	desired := (status == lock.Unlocked)
+	// TODO(ashankar): Change this to work with an actual relay. Currently
+	// simulating the "motor" with an active buzzer.
+	defer hw.relay.Clear()
+	hw.relay.Set()
+	start := time.Now()
+	for hw.monitor.Get() != desired {
+		if d := time.Since(start); d > toggleWaitTime {
+			return fmt.Errorf("lock state unchanged after %v: might be stuck. aborting.", d)
+		}
+		time.Sleep(200 * time.Millisecond)
+	}
+	return nil
+}
diff --git a/go/src/v.io/x/lock/lockd/internal/hardware_simulated.go b/go/src/v.io/x/lock/lockd/internal/hardware_simulated.go
new file mode 100644
index 0000000..5158959
--- /dev/null
+++ b/go/src/v.io/x/lock/lockd/internal/hardware_simulated.go
@@ -0,0 +1,61 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !arm
+
+package internal
+
+import (
+	"fmt"
+	"math/rand"
+	"os"
+	"os/signal"
+	"sync"
+
+	"v.io/x/lock"
+)
+
+type hw struct {
+	mu     sync.Mutex
+	status lock.LockStatus // GUARDED_BY(mu)
+}
+
+func init() {
+	hw := &hw{status: lock.Unlocked}
+	sigch := make(chan os.Signal)
+	signal.Notify(sigch, os.Interrupt)
+	go func() {
+		for range sigch {
+			fmt.Fprintln(os.Stderr, "simulated: externally initiated status change")
+			if hw.Status() == lock.Locked {
+				hw.setStatus(lock.Unlocked)
+				continue
+			}
+			hw.setStatus(lock.Locked)
+		}
+	}()
+	hardware = hw
+	fmt.Fprintln(os.Stderr, "Using simulated hardware. Simulate external locking/unlocking with: kill -SIGINT", os.Getpid())
+}
+
+func (hw *hw) Status() lock.LockStatus {
+	hw.mu.Lock()
+	defer hw.mu.Unlock()
+	return hw.status
+}
+
+func (hw *hw) SetStatus(status lock.LockStatus) error {
+	// Randomly fail with 10% chance, just for fun.
+	if rand.Intn(10) == 1 {
+		return fmt.Errorf("simulated error: lock failed to toggle - check the door")
+	}
+	hw.setStatus(status)
+	return nil
+}
+
+func (hw *hw) setStatus(status lock.LockStatus) {
+	hw.mu.Lock()
+	hw.status = status
+	hw.mu.Unlock()
+}
diff --git a/go/src/v.io/x/lock/lockd/internal/hwtest/main.go b/go/src/v.io/x/lock/lockd/internal/hwtest/main.go
new file mode 100644
index 0000000..85281de
--- /dev/null
+++ b/go/src/v.io/x/lock/lockd/internal/hwtest/main.go
@@ -0,0 +1,50 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Silly binary to test our the Hardware interface implementation without the
+// rest of the lock implementation.
+package main
+
+import (
+	"bufio"
+	"fmt"
+	"os"
+	"strings"
+
+	"v.io/x/lock"
+	"v.io/x/lock/lockd/internal"
+)
+
+func main() {
+	hw := internal.GetHardware()
+	fmt.Println("Commands are 'status', 'lock', 'unlock' or 'quit'")
+	bio := bufio.NewReader(os.Stdin)
+
+	for {
+		fmt.Fprintf(os.Stdout, "> ")
+		os.Stdout.Sync()
+		line, _, err := bio.ReadLine()
+		if err != nil {
+			fmt.Println("ERROR:", err)
+			return
+		}
+		cmd := strings.ToLower(strings.TrimSpace(string(line)))
+		switch {
+		case strings.HasPrefix(cmd, "s"):
+			fmt.Println(hw.Status())
+		case strings.HasPrefix(cmd, "l"):
+			if err := hw.SetStatus(lock.Locked); err != nil {
+				fmt.Println("ERROR:", err)
+			}
+		case strings.HasPrefix(cmd, "u"):
+			if err := hw.SetStatus(lock.Unlocked); err != nil {
+				fmt.Println("ERROR:", err)
+			}
+		case strings.HasPrefix(cmd, "q"), strings.HasPrefix(cmd, "x"):
+			return
+		default:
+			fmt.Printf("ERROR: unrecognized command %q\n", cmd)
+		}
+	}
+}
diff --git a/go/src/v.io/x/lock/lockd/lock.go b/go/src/v.io/x/lock/lockd/lock.go
index 7ad8450..2b56a39 100644
--- a/go/src/v.io/x/lock/lockd/lock.go
+++ b/go/src/v.io/x/lock/lockd/lock.go
@@ -5,62 +5,38 @@
 package main
 
 import (
-	"sync"
-
 	"v.io/v23/context"
 	"v.io/v23/rpc"
 	"v.io/v23/security"
 
 	"v.io/x/lib/vlog"
 	"v.io/x/lock"
+
+	"v.io/x/lock/lockd/internal"
 )
 
 type lockImpl struct {
-	status lock.LockStatus
-	mu     sync.RWMutex
+	hw internal.Hardware
 }
 
 func (l *lockImpl) Lock(ctx *context.T, call rpc.ServerCall) error {
 	remoteBlessingNames, _ := security.RemoteBlessingNames(ctx, call.Security())
 	vlog.Infof("Lock called by %q", remoteBlessingNames)
-
-	defer l.mu.Unlock()
-	l.mu.Lock()
-
-	l.status = lock.Locked
-	// Instruct the hardware to appropriately change state.
-
-	vlog.Info("Updated lock to status: UNLOCKED")
-	return nil
+	return l.hw.SetStatus(lock.Locked)
 }
 
 func (l *lockImpl) Unlock(ctx *context.T, call rpc.ServerCall) error {
 	remoteBlessingNames, _ := security.RemoteBlessingNames(ctx, call.Security())
 	vlog.Infof("Unlock called by %q", remoteBlessingNames)
-
-	defer l.mu.Unlock()
-	l.mu.Lock()
-
-	l.status = lock.Unlocked
-	// Instruct the hardware to appropriately change state.
-
-	vlog.Info("Updated lock to status: UNLOCKED")
-	return nil
+	return l.hw.SetStatus(lock.Unlocked)
 }
 
 func (l *lockImpl) Status(ctx *context.T, call rpc.ServerCall) (lock.LockStatus, error) {
 	remoteBlessingNames, _ := security.RemoteBlessingNames(ctx, call.Security())
 	vlog.Infof("Status called by %q", remoteBlessingNames)
-
-	defer l.mu.RUnlock()
-	l.mu.RLock()
-
-	return l.status, nil
+	return l.hw.Status(), nil
 }
 
 func newLock() lock.LockServerStub {
-	// At the moment we always create the lock object in locked state.
-	// For a real device, the lock would be initialized based on the state
-	// determined by the hardware sensors.
-	return lock.LockServer(&lockImpl{status: lock.Locked})
+	return lock.LockServer(&lockImpl{hw: internal.GetHardware()})
 }