-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.go
156 lines (142 loc) · 3.85 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Command rise implements a simple alarm clock for raspberry pi that can be
// set from a smart phone via a web interface.
package main
import (
"encoding/json"
"flag"
"log"
"net/http"
"os"
"os/exec"
"sync"
"time"
)
// main is in charge
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
// run does all the work
func run() error {
var (
httpAddr = flag.String("http.addr", ":80", "HTTP addr to listen on")
assetsDir = flag.String("assets.dir", "./public", "Directory to serve assets from")
soundCmd = flag.String("sound.cmd", "omxplayer ./public/alarm.mp3", "Command for playing alarm sound")
logLevel = flag.Int("log.level", 1, "0 = off, 1 = normal, 2 = debug")
)
flag.Parse()
LogStart(os.Args, *logLevel)
sound := NewCmdSound(*soundCmd)
sound = LoggedSound(sound, *logLevel)
clock := NewClock(sound)
clock = LoggedClock(clock, *logLevel)
handler := http.FileServer(http.Dir(*assetsDir))
handler = NewAlarmHandler(clock, handler)
handler = LoggedHandler(handler, *logLevel)
return http.ListenAndServe(*httpAddr, handler)
}
// NewClock returns a new Clock that plays the given sound when the alarm goes
// off.
func NewClock(sound Sound) Clock {
return &clock{sound: sound}
}
// Clock defines the capabilities of an alarm clock.
type Clock interface {
// Get returns the Alarm that is currently set.
Get() Alarm
// Set sets the current alarm, returns time to alarm.
Set(Alarm) time.Duration
}
// Alarm holds the settings that make up an alarm.
type Alarm struct {
Hour int `json:"hour"`
Minute int `json:"minute"`
Zone int `json:"zone"`
Enabled bool `json:"enabled"`
}
// clock implements the Clock interface.
type clock struct {
lock sync.Mutex
sound Sound
alarm Alarm
timer *time.Timer
}
// Get is part of the Clock interface.
func (c *clock) Get() Alarm {
c.lock.Lock()
defer c.lock.Unlock()
return c.alarm
}
// Set is part of the clock interface.
func (c *clock) Set(alarm Alarm) time.Duration {
c.lock.Lock()
defer c.lock.Unlock()
// stop active alarm, if any
if c.timer != nil {
c.timer.Stop()
c.timer = nil
}
// determine the time the alarm should go off
now := time.Now()
zone := time.FixedZone("", alarm.Zone)
when := time.Date(now.Year(), now.Month(), now.Day(), alarm.Hour, alarm.Minute, 0, 0, zone)
// if the time is in the past, this means the next day is meant
if when.Before(now) {
when = when.AddDate(0, 0, 1)
}
duration := when.Sub(now)
// start new alarm, if needed
if alarm.Enabled {
c.timer = time.AfterFunc(duration, func() {
for {
c.sound.Play()
}
})
}
// update the alarm
c.alarm = alarm
return duration
}
// NewAlarmHandler returns a new http handler that allows setting/getting the
// alarm of the given clock at /alarm. Calls the next handler for any other
// path.
func NewAlarmHandler(clock Clock, next http.Handler) http.Handler {
const prefix = "/alarm"
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != prefix {
next.ServeHTTP(w, r)
} else {
w.Header().Set("Content-Type", "application/json")
var err error
if r.Method == "PUT" {
var alarm Alarm
if err = json.NewDecoder(r.Body).Decode(&alarm); err == nil {
clock.Set(alarm)
}
}
var response interface{}
if err == nil {
response = clock.Get()
} else {
response = map[string]interface{}{"error": err.Error()}
}
json.NewEncoder(w).Encode(response)
}
})
}
// Sound defines an alarm sound that can be played.
type Sound interface {
// Play plays the alarm sound or returns an error.
Play() error
}
// NewCmdSound returns a sound that is played by invoking the given shell cmd.
func NewCmdSound(cmd string) Sound {
return cmdSound(cmd)
}
// cmdSound implements the Sound interface.
type cmdSound string
// Play is part of the Sound interface.
func (c cmdSound) Play() error {
return exec.Command("/bin/sh", "-c", string(c)).Run()
}