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