/
device.go
130 lines (100 loc) · 2.46 KB
/
device.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
package atgain60
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"sync"
"time"
"go.uber.org/zap"
"golang.org/x/time/rate"
)
// Amp represents an Atlona 60 watt amplifier
type Amp struct {
Username string
Password string
Address string
Log *zap.Logger
RequestDelay time.Duration
loginMu sync.Mutex
once sync.Once
limiter *rate.Limiter
}
func (a *Amp) init() {
a.limiter = rate.NewLimiter(rate.Every(a.RequestDelay), 1)
}
func (a *Amp) r() string {
return fmt.Sprintf("%v", rand.Float64())
}
func (a *Amp) login(ctx context.Context) error {
url := fmt.Sprintf("http://%s/action=compare&701=%s&702=%s&r=%s", a.Address, a.Username, a.Password, a.r())
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return fmt.Errorf("unable to build request: %w", err)
}
client := &http.Client{
Transport: &transport{},
}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("unable to do request: %w", err)
}
defer resp.Body.Close()
var login struct {
Login string `json:"Login"`
}
if err := json.NewDecoder(resp.Body).Decode(&login); err != nil {
return fmt.Errorf("unable to decode response: %w", err)
}
if login.Login != "True" {
return fmt.Errorf("unexpected login result: %s", login.Login)
}
return nil
}
func (a *Amp) doReq(req *http.Request) ([]byte, error) {
a.once.Do(a.init)
if err := a.limiter.Wait(req.Context()); err != nil {
return nil, fmt.Errorf("unable to wait for ratelimit: %w", err)
}
// probably not the best solution...
a.loginMu.Lock()
defer a.loginMu.Unlock()
login := false
for {
if login {
a.Log.Info("Logging in to amp")
if err := a.login(req.Context()); err != nil {
return nil, err
}
a.Log.Info("Successfully logged in; retrying previous request")
}
client := &http.Client{
Transport: &transport{},
}
a.Log.Debug("Doing request", zap.String("url", req.URL.String()))
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("unable to do request: %w", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("unable to read body")
}
a.Log.Debug("Response", zap.ByteString("body", body))
if login {
return body, nil
}
// see if we are unauthorized
var auth struct {
LoggedIn string `json:"909"`
}
_ = json.Unmarshal(body, &auth)
if auth.LoggedIn == "" {
return body, nil
}
login = true
}
}