maslow's hierarchy of needs
After self-actualization, a little known fact is that the next, tiniest part of the pyramid is the ability find your cell phone before you leave. The normal solution is to have someone call it for you- sound being a part of the hierarchy of senses- but if you’re alone, trying to leave, and you can’t find the thing, what then?
I bought this little board for almost nothing, got a trial account at Twilio, and hacked up a little program.
Calling Twilio was most of the work-
type TwilioPlay struct {
Name xml.Name `xml:"Response"`
Say string `xml:",omitempty"`
Play string `xml:",omitempty"`
}
type TwilioResponse struct {
Sid *string `json:"sid"`
DateCreated *string `json:"date_created"`
DateUpdated *string `json:"date_updated"`
DateSent *interface{} `json:"date_sent"`
AccountSid *string `json:"account_sid"`
To *string `json:"to"`
From *string `json:"from"`
Body *string `json:"body"`
Status string `json:"status"`
Flags *[]string `json:"flags"`
APIVersion *string `json:"api_version"`
Price *interface{} `json:"price"`
URI *string `json:"uri"`
// Optional exception params
Message *string `json:"message"`
MoreInfo *string `json:"more_info"`
Code *int `json:"code"`
}
func Call(toNumber, fromNumber, sid, token, twimlURL string) {
u := url.URL{
Scheme: "https",
Host: "api.twilio.com",
Path: path.Join("2010-04-01/Accounts/", sid, "/Calls.json"),
}
q := u.Query()
q.Set("To", toNumber)
q.Set("From", fromNumber)
q.Set("Url", twimlURL)
r, err := http.NewRequest("POST", u.String(), strings.NewReader(q.Encode()))
if err != nil {
panic(err)
}
r.SetBasicAuth(sid, token)
r.Header.Add("Accept", "application/json")
r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := (&http.Client{}).Do(r)
if err != nil {
panic(err)
}
var tresp TwilioResponse
body, err := ioutil.ReadAll(resp.Body)
err = json.Unmarshal(body, &tresp)
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
// fine
} else {
panic(fmt.Sprintf(*tresp.Message))
}
}
Possibly not best practice, but my theory here is to just crash the program
when things go wrong, and let systemd restart it. So there’s no real error
handling anywhere, just unconditional panic
s.
To trigger the thing, I hooked up a button to the board’s GPIO pin 18. linux-sunxi.org has good documentation, but as someone without much experience with the conventions of little single board computers, it took me a while to figure out how to map their GPIO table to the pins on the board. The trick is there is a little ▶ character next to pin 1- count from there left to right, top to bottom.
The GPIO filesystem
is very unixy, and made it really easy to test that the wiring was working.
Hold the button down, cat /sys/class/gpio/gpio18/value
. Let go of the button,
cat /sys/class/gpio/gpio18/value
. I spent some time wandering around looking
for a Go library, and had some bad luck,
so I went ahead and wrote my own. The documentation mentions that
If the pin can be configured as interrupt-generating interrupt
and if it has been configured to generate interrupts (see the
description of "edge"), you can poll(2) on that file and
poll(2) will return whenever the interrupt was triggered. If
you use poll(2), set the events POLLPRI and POLLERR. If you
use select(2), set the file descriptor in exceptfds. After
poll(2) returns, either lseek(2) to the beginning of the sysfs
file and read the new value or close the file and re-open it
to read the value.
So I went ahead and did that-
func poller(pin string, values chan []byte) {
var fds []unix.PollFd = make([]unix.PollFd, 1)
fd, err := os.Open(pin)
fds[0] = unix.PollFd{Fd: int32(fd.Fd()), Events: unix.POLLPRI | unix.POLLERR}
for {
if err != nil {
panic(err)
} // everything is busted
n, err := unix.Poll(fds, 10000)
if err != nil {
panic(err)
} // everything is definitely busted
if n == 1 {
contents, err := ioutil.ReadAll(fd)
if err != nil {
panic(err)
} else {
values <- contents
}
_, err = fd.Seek(0, 0)
if err != nil {
panic(err)
}
}
}
}
Throwing it all together-
func main() {
var pin string = "/sys/class/gpio/gpio18/value"
throttle := time.Tick(time.Second * 30)
values := make(chan []byte)
go poller(pin, values)
for {
edge := <-values
// we're doing pulldown
if bytes.Compare(edge, []byte{'0', '\n'}) == 0 {
Call(call_number, from_number, os.Getenv("SID"), os.Getenv("TOKEN"), twimlURL)
<-throttle
}
}
}
I needed the goroutine and channel for the initial implementation, but since I
switched to poll(2)
, I guess I could just have one loop.
systemd, as always, makes the whole “I would like a supervised process” problem trivial-
[Unit]
Description=Monitor a GPIO port, and call a phone when it goes low
[Service]
Environment=SID='{{sid}}' TOKEN='{{token}}'
# Turn on the GPIO port
ExecStartPre=/bin/echo "18" > /sys/class/gpio/export
# Set high/low behavior
ExecStartPre=/bin/echo both > /sys/class/gpio/gpio18/edge
Exec=/usr/local/bin/findphone
Restart=always
[Install]
WantedBy=multi-user.target
Hopefully this is helpful to someone somehow.