-
Notifications
You must be signed in to change notification settings - Fork 22
/
hueMotionSensor.lua
197 lines (183 loc) · 6.43 KB
/
hueMotionSensor.lua
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
-- This is a bunch of code to use a Philips Hue Motion Sensor as a trigger for doing work
local hueBridge = {}
hueBridge.ip = nil
hueBridge.username = hs.settings.get("hueBridgeUsername") -- This will store the username the Hue bridge gave us
hueBridge.sensorID = hs.settings.get("hueBridgeSensorID")
hueBridge.apiURLBase = nil
hueBridge.apiURLUser = nil
hueBridge.authTimer = nil
hueBridge.pollingTimer = nil
hueBridge.pollingBeginTimer = nil
hueBridge.pollingInterval = 2
hueBridge.defaultHeaders = {}
hueBridge.defaultHeaders["Accept"] = "application/json"
hueBridge.sensorChooser = nil
hueBridge.isGettingIP = false
hueBridge.isGettingUsername = false
hueBridge.isGettingSensors = false
hueBridge.isPollingSensor = false
hueBridge.userCallback = nil
function hueBridge:debug(msg)
print(string.format("DEBUG: hueMotionSensor: %s", msg))
end
function hueBridge:init()
if not self.userCallback then
print("ERROR: No userCallback has been set")
return self
end
self:isReadyForPolling()
return self
end
function hueBridge:start()
print("Starting hueMotionSensor polling")
if not self.userCallback then
print("ERROR: No userCallback has been set")
return self
end
self.pollingBeginTimer = hs.timer.waitUntil(function() return self:isReadyForPolling() end, function() self:pollingStart() end, 5)
return self
end
function hueBridge:stop()
print("Stopping hueMotionSensor polling")
if self.pollingBeginTimer then
self.pollingBeginTimer:stop()
end
if self.authTimer then
self.authTimer:stop()
end
if self.pollingTimer then
self.pollingTimer:stop()
end
if self.sensorChooser then
self.sensorChooser:hide()
end
return self
end
function hueBridge:updateURLs()
if (self.ip and not self.apiURLBase) then
self.apiURLBase = string.format("http://%s/api", self.ip)
end
if (self.apiURLBase and self.username and not self.apiURLUser) then
self.apiURLUser = string.format("%s/%s", self.apiURLBase, self.username)
end
return self
end
function hueBridge:isReadyForPolling()
if not self.ip then
self:getIP()
return false
end
if not self.username then
self:getAuth()
return false
end
if not self.sensorID then
self:getSensor()
return false
end
self:debug("Sensor is ready for polling.")
return true
end
function hueBridge:getIP()
if self.isGettingIP then
return self
end
self.isGettingIP = true
hs.http.asyncGet("https://www.meethue.com/api/nupnp", nil, function(code, body, headers)
self.isGettingIP = false
-- print(string.format("Debug: getIP() callback, %d, %s, %s", code, body, hs.inspect(headers)))
-- FIXME: Handle error codes
if code == 200 then
rawJSON = hs.json.decode(body)[1]
self.ip = rawJSON["internalipaddress"]
self:updateURLs()
self:debug("Bridge discovered at: "..self.ip)
end
end)
return self
end
function hueBridge:getAuth()
if self.isGettingUsername then
return self
end
self.isGettingUsername = true
hs.http.asyncPost(self.apiURLBase, '{"devicetype":"Hammerspoon#hammerspoon hammerspoon"}', self.defaultHeaders, function(code, body, headers)
self.isGettingUsername = false
-- print(string.format("Debug: getAuth() callback, %d, %s, %s", code, body, hs.inspect(headers)))
-- FIXME: Handle error codes
if code == 200 then
rawJSON = hs.json.decode(body)[1]
if rawJSON["error"] and rawJSON["error"]["type"] == 101 then
-- FIXME: Don't spam the user, create a notification, track its lifecycle properly
hs.notify.show("Hammerspoon", "Hue Bridge authentication", "Please press the button on your Hue bridge")
return
end
if rawJSON["success"] ~= nil then
self.username = rawJSON["success"]["username"]
hs.settings.set("hueBridgeUsername", self.username)
self:updateURLs()
self:debug("Created username: "..self.username)
end
end
end)
return self
end
function hueBridge:getSensor()
if self.isGettingSensors then
return self
end
self.isGettingSensors = true
hs.http.asyncGet(string.format("%s/sensors", self.apiURLUser), self.defaultHeaders, function(code, body, headers)
self.isGettingSensors = false
-- print(string.format("Debug: getSensor() callback, %d, %s, %s", code, body, hs.inspect(headers)))
-- FIXME: Handle error codes
if code == 200 then
rawJSON = hs.json.decode(body)
sensors = {}
for id,data in pairs(rawJSON) do
if (data["type"] and data["type"] == "ZLLPresence") then
self:debug(string.format("Found sensor: %d (%s)", id, data["name"]))
table.insert(sensors, {id=id,data=data})
end
end
if #sensors == 1 then
self.sensorID = sensors[1]["id"]
hs.settings.set("hueBridgeSensorID", self.sensorID)
self:debug("Found Hue Motion Sensor: "..self.sensorID)
elseif #sensors > 1 then
-- FIXME: Implement a chooser here
else
-- We found no sensors
hs.notify.show("Hammerspoon", "Hue Motion Detection", "No compatible sensors found. Terminating")
self:stop()
end
end
end)
return self
end
function hueBridge:pollingStart()
self.pollingTimer = hs.timer.new(2, function() self:doPoll() end)
self.pollingTimer:start()
return self
end
function hueBridge:doPoll()
print("polling...")
if self.isPollingSensor then
return self
end
self.isPollingSensor = true
hs.http.asyncGet(string.format("%s/sensors/%s", self.apiURLUser, self.sensorID), self.defaultHeaders, function(code, body, headers)
self.isPollingSensor = false
-- print(string.format("Debug: doPoll() callback, %d, %s, %s", code, body, hs.inspect(headers)))
-- FIXME: Handle error codes
if code == 200 then
rawJSON = hs.json.decode(body)
print("sensor: "..hs.inspect(rawJSON))
if rawJSON["state"] then
self.userCallback(rawJSON["state"]["presence"])
end
end
end)
return self
end
return hueBridge