-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
158 lines (133 loc) · 3.49 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
157
158
package main
import (
"bytes"
"compress/gzip"
"context"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"strconv"
"time"
)
func main() {
var dev bool
var bundle bool
flag.BoolVar(&dev, "dev", false, "serve index.html from disk")
flag.BoolVar(&bundle, "bundle", false, "re-generate index.html.go")
flag.Parse()
if bundle {
makeIndexHTML()
return
}
http.HandleFunc("/favicon.ico", http.NotFound)
http.HandleFunc("/switch/", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
w.WriteHeader(405)
return
}
port, _ := strconv.Atoi(r.URL.Path[8:])
if port < 1 || port > 4 {
http.Error(w, "Unparsable or invalid port", 422)
return
}
ctx, cancel := context.WithTimeout(r.Context(), 1*time.Second)
defer cancel()
switch err := changePort(ctx, port); err {
case nil:
w.WriteHeader(200)
case context.Canceled:
http.Error(w, err.Error(), 504)
default:
http.Error(w, err.Error(), 500)
}
})
fs := http.FileServer(http.Dir("."))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Println(r.URL.Path)
log.Println(r.Context().Deadline())
if dev {
fs.ServeHTTP(w, r)
} else {
w.Header().Set("content-type", "text/html; charset=utf-8")
w.Header().Set("content-encoding", "gzip")
w.Write(indexHTML)
}
})
srv := &http.Server{
Addr: ":http",
ReadTimeout: 1 * time.Second,
ReadHeaderTimeout: 1 * time.Second,
WriteTimeout: 1 * time.Second,
}
if port := os.Getenv("PORT"); port != "" {
srv.Addr = ":" + port
}
log.Println("Starting server on", srv.Addr)
log.Fatal(srv.ListenAndServe())
}
// sem ensures that we don't attempt to send mulitiple concurrent IR signals.
var sem = make(chan struct{}, 1)
// changePort changes the active input port on the HDMI switch. The context
// must have a deadline.
func changePort(ctx context.Context, port int) error {
select {
case sem <- struct{}{}:
defer func() { <-sem }()
case <-ctx.Done():
log.Println("timeout waiting for lock")
return ctx.Err()
}
// Non-empirical tests show the switch takes a few milliseconds to change
// the port. Bail if there's not enough time left.
dl, _ := ctx.Deadline()
if d := time.Until(dl); d < 100*time.Millisecond {
log.Println("not enough time left after grabbing lock:", d)
return context.Canceled // not true, but simplifies error handling in caller
}
var cmd uint8
switch port {
case 1:
cmd = 0xAA // Button1: 1010 1010b (transmission order)
case 2:
cmd = 0xA8 // Button2: 1010 1000b (transmission order)
case 3:
cmd = 0xBA // Button3: 1011 1010b (transmission order)
case 4:
cmd = 0x38 // Button4: 0011 1000b (transmission order)
}
data := uint32(0x00ff0000) // address is always zero
data |= uint32(cmd) << 8
data |= uint32(^cmd)
log.Println("Switching to port", port)
return exec.CommandContext(ctx, "irsling", fmt.Sprintf("%032b", data)).Run()
}
func makeIndexHTML() {
b, err := ioutil.ReadFile("index.html")
if err != nil {
log.Fatal(err)
}
buf := &bytes.Buffer{}
gw := gzip.NewWriter(buf)
if _, err = gw.Write(b); err != nil {
log.Fatal(err)
}
if err := gw.Close(); err != nil {
log.Fatal(err)
}
f, err := os.Create("index.html.go")
if err != nil {
log.Fatal(err)
}
defer f.Close()
fmt.Fprintln(f, "package main")
fmt.Fprintln(f, "")
fmt.Fprintln(f, "// Generated from index.html. Do not edit.")
fmt.Fprintln(f, "")
fmt.Fprintf(f, "var indexHTML = %#v\n", buf.Bytes())
fmt.Println("index.html.go re-generated. Don't forget to run `go build`.")
return
}