lightwaverf 0.3.3 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/lightwaverf +10 -2
- data/lib/lightwaverf.rb +644 -146
- metadata +40 -7
data/bin/lightwaverf
CHANGED
@@ -9,12 +9,20 @@ case ARGV[0]
|
|
9
9
|
puts LightWaveRF.new.configure
|
10
10
|
when 'sequence'
|
11
11
|
puts LightWaveRF.new.sequence ARGV[1], ARGV[2]
|
12
|
+
when 'mood'
|
13
|
+
puts LightWaveRF.new.mood ARGV[1], ARGV[2], ARGV[3]
|
14
|
+
when 'learnmood'
|
15
|
+
puts LightWaveRF.new.learnmood ARGV[1], ARGV[2], ARGV[3]
|
12
16
|
when 'energy'
|
13
17
|
puts LightWaveRF.new.energy ARGV[1], ARGV[2], ARGV[3]
|
18
|
+
when 'update_timers'
|
19
|
+
puts LightWaveRF.new.update_timers ARGV[1], ARGV[2], ARGV[3]
|
14
20
|
when 'timer'
|
15
|
-
puts LightWaveRF.new.
|
21
|
+
puts LightWaveRF.new.run_timers ARGV[1], ARGV[2]
|
22
|
+
when 'run_timers'
|
23
|
+
puts LightWaveRF.new.run_timers ARGV[1], ARGV[2]
|
16
24
|
when 'update'
|
17
|
-
puts LightWaveRF.new.update_config ARGV[1], ARGV[2]
|
25
|
+
puts LightWaveRF.new.update_config ARGV[1], ARGV[2]
|
18
26
|
else
|
19
27
|
LightWaveRF.new.send ARGV[0], ARGV[1], ARGV[2], ARGV[3]
|
20
28
|
end
|
data/lib/lightwaverf.rb
CHANGED
@@ -1,17 +1,43 @@
|
|
1
|
+
# TODO:
|
2
|
+
# All day events without times - need to fix regex
|
3
|
+
# Make regex better
|
4
|
+
# Get rid of references in yaml cache file - use dup more? Or does it not matter?
|
5
|
+
# Cope with events that start and end in the same run?
|
6
|
+
# Add info about states to timer log
|
7
|
+
# Consider adding a 'random' time shift modifier to make holiday security lights more 'realistic'
|
8
|
+
# Build / update cron job automatically
|
9
|
+
|
10
|
+
|
1
11
|
require 'yaml'
|
2
12
|
require 'socket'
|
13
|
+
require 'net/http'
|
14
|
+
require 'uri'
|
15
|
+
require 'net/https'
|
16
|
+
require 'json'
|
17
|
+
require 'rexml/document'
|
18
|
+
require 'time'
|
19
|
+
require 'date'
|
3
20
|
include Socket::Constants
|
4
21
|
|
5
22
|
class LightWaveRF
|
6
23
|
|
7
24
|
@config_file = nil
|
8
25
|
@log_file = nil
|
26
|
+
@log_timer_file = nil
|
9
27
|
@config = nil
|
28
|
+
@timers = nil
|
10
29
|
|
11
30
|
# Display usage info
|
12
|
-
def usage
|
31
|
+
def usage room = nil
|
13
32
|
rooms = self.class.get_rooms self.get_config
|
14
|
-
'usage: lightwaverf ' + rooms.values.first['name'] + ' ' + rooms.values.first['device'].keys.first.to_s + ' on # where "' + rooms.keys.first + '" is a room in ' + self.get_config_file
|
33
|
+
config = 'usage: lightwaverf ' + rooms.values.first['name'] + ' ' + rooms.values.first['device'].keys.first.to_s + ' on # where "' + rooms.keys.first + '" is a room in ' + self.get_config_file
|
34
|
+
if room and rooms[room]
|
35
|
+
config += "\ntry: lightwaverf " + rooms[room]['name'] + ' all on'
|
36
|
+
rooms[room]['device'].each do | device |
|
37
|
+
config += "\ntry: lightwaverf " + rooms[room]['name'] + ' ' + device.first.to_s + ' on'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
config
|
15
41
|
end
|
16
42
|
|
17
43
|
# Display help
|
@@ -30,11 +56,11 @@ class LightWaveRF
|
|
30
56
|
# debug: (Boolean
|
31
57
|
def configure debug = false
|
32
58
|
config = self.get_config
|
33
|
-
puts 'What is the ip address of your wifi link? (' + self.get_config['host'] + '). Enter a blank line to broadcast UDP commands.'
|
34
|
-
host = STDIN.gets.chomp
|
35
|
-
if ! host.to_s.empty?
|
36
|
-
|
37
|
-
end
|
59
|
+
# puts 'What is the ip address of your wifi link? (' + self.get_config['host'] + '). Enter a blank line to broadcast UDP commands.'
|
60
|
+
# host = STDIN.gets.chomp
|
61
|
+
# if ! host.to_s.empty?
|
62
|
+
# config['host'] = host
|
63
|
+
# end
|
38
64
|
puts 'What is the address of your google calendar? (' + self.get_config['calendar'] + '). Optional!'
|
39
65
|
calendar = STDIN.gets.chomp
|
40
66
|
if ! calendar.to_s.empty?
|
@@ -47,9 +73,7 @@ class LightWaveRF
|
|
47
73
|
parts = device.split ' '
|
48
74
|
if !parts[0].to_s.empty? and !parts[1].to_s.empty?
|
49
75
|
new_room = parts.shift
|
50
|
-
|
51
|
-
config['room'] = [ ]
|
52
|
-
end
|
76
|
+
config['room'] ||= [ ]
|
53
77
|
found = false
|
54
78
|
config['room'].each do | room |
|
55
79
|
if room['name'] == new_room
|
@@ -59,7 +83,7 @@ class LightWaveRF
|
|
59
83
|
debug and ( p 'so now room is ' + room.to_s )
|
60
84
|
end
|
61
85
|
if ! found
|
62
|
-
config['room'].push 'name' => new_room, 'device' => parts
|
86
|
+
config['room'].push 'name' => new_room, 'device' => parts, 'mood' => nil
|
63
87
|
end
|
64
88
|
debug and ( p 'added ' + parts.to_s + ' to ' + new_room )
|
65
89
|
end
|
@@ -84,9 +108,64 @@ class LightWaveRF
|
|
84
108
|
@log_file || File.expand_path('~') + '/lightwaverf.log'
|
85
109
|
end
|
86
110
|
|
111
|
+
# Timer log file getter
|
112
|
+
def get_timer_log_file
|
113
|
+
@timer_log_file || File.expand_path('~') + '/lightwaverf-timer.log'
|
114
|
+
end
|
115
|
+
|
116
|
+
# Timer logger
|
117
|
+
def log_timer_event type, room = nil, device = nil, state = nil, result = false
|
118
|
+
# create log message
|
119
|
+
message = nil
|
120
|
+
case type
|
121
|
+
when 'update'
|
122
|
+
message = '### Updated timer cache'
|
123
|
+
when 'run'
|
124
|
+
message = '*** Ran timers'
|
125
|
+
when 'sequence'
|
126
|
+
message = 'Ran sequence: ' + state
|
127
|
+
when 'mood'
|
128
|
+
message = 'Set mood: ' + mood + ' in room ' + room
|
129
|
+
when 'device'
|
130
|
+
message = 'Set device: ' + device + ' in room ' + room + ' to state ' + state
|
131
|
+
end
|
132
|
+
unless message.nil?
|
133
|
+
File.open( self.get_timer_log_file, 'a' ) do | f |
|
134
|
+
f.write("\n" + Time.now.to_s + ' - ' + message + ' - ' + ( result ? 'SUCCESS!' : 'FAILED!' ))
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Timer cache file getter
|
140
|
+
def get_timer_cache_file
|
141
|
+
@log_file || File.expand_path('~') + '/lightwaverf-timer-cache.yml'
|
142
|
+
end
|
143
|
+
|
144
|
+
# Get timer cache file, create it if needed
|
145
|
+
def get_timer_cache
|
146
|
+
p 'getting timer cache...'
|
147
|
+
if ! @timers
|
148
|
+
p 'no timers!'
|
149
|
+
if ! File.exists? self.get_timer_cache_file
|
150
|
+
p 'no file!'
|
151
|
+
self.update_timers
|
152
|
+
p 'updated...'
|
153
|
+
end
|
154
|
+
@timers = YAML.load_file self.get_timer_cache_file
|
155
|
+
p 'timers now ' + @timers.to_s
|
156
|
+
end
|
157
|
+
p 'returning, timers now ' + @timers.to_s
|
158
|
+
@timers
|
159
|
+
end
|
160
|
+
|
161
|
+
# Store the timer cache
|
162
|
+
def put_timer_cache timers = { 'events' => [ ] }
|
163
|
+
File.open( self.get_timer_cache_file, 'w' ) do | handle |
|
164
|
+
handle.write YAML.dump( timers )
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
87
168
|
def put_config config = { 'room' => [ { 'name' => 'our', 'device' => [ 'light', 'lights' ] } ] }
|
88
|
-
puts 'put_config got ' + config.to_s
|
89
|
-
puts 'so writing ' + YAML.dump( config )
|
90
169
|
File.open( self.get_config_file, 'w' ) do | handle |
|
91
170
|
handle.write YAML.dump( config )
|
92
171
|
end
|
@@ -117,99 +196,122 @@ class LightWaveRF
|
|
117
196
|
# Credits:
|
118
197
|
# wonko - http://lightwaverfcommunity.org.uk/forums/topic/querying-configuration-information-from-the-lightwaverf-website/
|
119
198
|
def update_config email = nil, pin = nil, debug = false
|
120
|
-
|
199
|
+
|
121
200
|
# Login to LightWaveRF Host server
|
122
|
-
require 'net/http'
|
123
|
-
require 'uri'
|
124
201
|
uri = URI.parse 'https://lightwaverfhost.co.uk/manager/index.php'
|
125
202
|
http = Net::HTTP.new uri.host, uri.port
|
126
203
|
if uri.scheme == 'https'
|
127
|
-
require 'net/https'
|
128
204
|
http.use_ssl = true
|
129
205
|
end
|
130
206
|
data = 'pin=' + pin + '&email=' + email
|
131
|
-
headers = { 'Content-Type'=> 'application/x-www-form-urlencoded
|
207
|
+
headers = { 'Content-Type'=> 'application/x-www-form-urlencoded' }
|
132
208
|
resp, data = http.post uri.request_uri, data, headers
|
133
|
-
|
209
|
+
|
134
210
|
if resp and resp.body
|
135
|
-
|
136
|
-
# var gDeviceNames = [""]
|
137
|
-
# var gDeviceStatus = [""]
|
138
|
-
# var gRoomNames = [""]
|
139
|
-
# var gRoomStatus = [""]
|
140
|
-
# http://rubular.com/r/UH0H4b4afF
|
141
|
-
variables = Hash.new
|
142
|
-
resp.body.scan(/var (gDeviceNames|gDeviceStatus|gRoomNames|gRoomStatus)\s*=\s*([^;]*)/).each do |variable|
|
143
|
-
variables[variable[0]] = variable[1].scan(/"([^"]*)\"/)
|
144
|
-
end
|
145
|
-
debug and (p '[Info - LightWaveRF Gem] Javascript variables ' + variables.to_s)
|
146
|
-
|
147
|
-
rooms = [ ]
|
148
|
-
# Rooms - gRoomNames is a collection of 8 values, or room names
|
149
|
-
variables['gRoomNames'].each_with_index do |(roomName), roomIndex|
|
150
|
-
# Room Status - gRoomStatus is a collection of 8 values indicating the status of the corresponding room in gRoomNames
|
151
|
-
# A: Active
|
152
|
-
# I: Inactive
|
153
|
-
if variables['gRoomStatus'] and variables['gRoomStatus'][roomIndex] and variables['gRoomStatus'][roomIndex][0] == 'A'
|
154
|
-
# Devices - gDeviceNames is a collection of 80 values, structured in blocks of ten values for each room:
|
155
|
-
# Devices 1 - 6, Mood 1 - 3, All Off
|
156
|
-
roomDevices = Array.new
|
157
|
-
deviceNamesIndexStart = roomIndex*10
|
158
|
-
variables['gDeviceNames'][(deviceNamesIndexStart)..(deviceNamesIndexStart+5)].each_with_index do |(deviceName), deviceIndex|
|
159
|
-
# Device Status - gDeviceStatus is a collection of 80 values which indicate the status/type of the corresponding device in gDeviceNames
|
160
|
-
# O: On/Off Switch
|
161
|
-
# D: Dimmer
|
162
|
-
# R: Radiator(s)
|
163
|
-
# P: Open/Close
|
164
|
-
# I: Inactive (i.e. not configured)
|
165
|
-
# m: Mood (inactive)
|
166
|
-
# M: Mood (active)
|
167
|
-
# o: All Off
|
168
|
-
deviceStatusIndex = roomIndex*10+deviceIndex
|
169
|
-
if variables['gDeviceStatus'] and variables['gDeviceStatus'][deviceStatusIndex] and variables['gDeviceStatus'][deviceStatusIndex][0] != 'I'
|
170
|
-
roomDevices << deviceName
|
171
|
-
end
|
172
|
-
end
|
173
|
-
# Create a hash of the active room and active devices and add to rooms array
|
174
|
-
if roomName and roomDevices and roomDevices.any?
|
175
|
-
rooms << {'name'=>roomName,'device'=>roomDevices}
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
211
|
+
rooms = self.get_rooms_from resp.body, debug
|
180
212
|
# Update 'room' element in LightWaveRF Gem config file
|
181
213
|
# config['room'] is an array of hashes containing the room name and device names
|
182
214
|
# in the format { 'name' => 'Room Name', 'device' => ['Device 1', Device 2'] }
|
183
|
-
if rooms
|
215
|
+
if rooms.any?
|
184
216
|
config = self.get_config
|
185
217
|
config['room'] = rooms
|
186
|
-
|
187
|
-
|
188
|
-
end
|
189
|
-
debug and (p '[Info - LightWaveRF Gem] Updated config with ' + rooms.size.to_s + ' room(s): ' + rooms.to_s)
|
218
|
+
self.put_config config
|
219
|
+
debug and ( p '[Info - LightWaveRF Gem] Updated config with ' + rooms.size.to_s + ' room(s): ' + rooms.to_s )
|
190
220
|
else
|
191
|
-
debug and (p '[Info - LightWaveRF Gem] Unable to update config: No active rooms or devices found')
|
221
|
+
debug and ( p '[Info - LightWaveRF Gem] Unable to update config: No active rooms or devices found' )
|
192
222
|
end
|
193
223
|
else
|
194
|
-
debug and (p '[Info - LightWaveRF Gem] Unable to update config: No response from Host server')
|
224
|
+
debug and ( p '[Info - LightWaveRF Gem] Unable to update config: No response from Host server' )
|
195
225
|
end
|
196
226
|
self.get_config
|
197
227
|
end
|
198
228
|
|
229
|
+
def get_rooms_from body = '', debug = nil
|
230
|
+
variables = self.get_variables_from body, debug
|
231
|
+
rooms = [ ]
|
232
|
+
# Rooms - gRoomNames is a collection of 8 values, or room names
|
233
|
+
debug and ( puts variables['gRoomStatus'].inspect )
|
234
|
+
variables['gRoomNames'].each_with_index do | roomName, roomIndex |
|
235
|
+
# Room Status - gRoomStatus is a collection of 8 values indicating the status of the corresponding room in gRoomNames
|
236
|
+
# A: Active
|
237
|
+
# I: Inactive
|
238
|
+
if variables['gRoomStatus'] and variables['gRoomStatus'][roomIndex] and variables['gRoomStatus'][roomIndex][0] == 'A'
|
239
|
+
debug and ( puts variables['gRoomStatus'][roomIndex].inspect )
|
240
|
+
# Devices - gDeviceNames is a collection of 80 values, structured in blocks of ten values for each room:
|
241
|
+
# Devices 1 - 6, Mood 1 - 3, All Off
|
242
|
+
roomDevices = [ ]
|
243
|
+
deviceNamesIndexStart = roomIndex * 10
|
244
|
+
variables['gDeviceNames'][(deviceNamesIndexStart)..(deviceNamesIndexStart+5)].each_with_index do | deviceName, deviceIndex |
|
245
|
+
# Device Status - gDeviceStatus is a collection of 80 values which indicate the status/type of the corresponding device in gDeviceNames
|
246
|
+
# O: On/Off Switch
|
247
|
+
# D: Dimmer
|
248
|
+
# R: Radiator(s)
|
249
|
+
# P: Open/Close
|
250
|
+
# I: Inactive (i.e. not configured)
|
251
|
+
# m: Mood (inactive)
|
252
|
+
# M: Mood (active)
|
253
|
+
# o: All Off
|
254
|
+
deviceStatusIndex = roomIndex * 10 + deviceIndex
|
255
|
+
if variables['gDeviceStatus'] and variables['gDeviceStatus'][deviceStatusIndex] and variables['gDeviceStatus'][deviceStatusIndex][0] != 'I'
|
256
|
+
roomDevices << deviceName
|
257
|
+
end
|
258
|
+
end
|
259
|
+
# Create a hash of the active room and active devices and add to rooms array
|
260
|
+
if roomName and roomDevices and roomDevices.any?
|
261
|
+
rooms << { 'name' => roomName, 'device' => roomDevices }
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
rooms
|
266
|
+
end
|
267
|
+
|
268
|
+
# Get variables from the source of lightwaverfhost.co.uk
|
269
|
+
# Separated out so it can be tested
|
270
|
+
#
|
271
|
+
def get_variables_from body = '', debug = nil
|
272
|
+
# debug and ( p '[Info - LightWaveRF Gem] body was ' + body.to_s )
|
273
|
+
variables = { }
|
274
|
+
# Extract JavaScript variables from the page
|
275
|
+
# var gDeviceNames = [""]
|
276
|
+
# var gDeviceStatus = [""]
|
277
|
+
# var gRoomNames = [""]
|
278
|
+
# var gRoomStatus = [""]
|
279
|
+
# http://rubular.com/r/UH0H4b4afF
|
280
|
+
body.scan( /var (gDeviceNames|gDeviceStatus|gRoomNames|gRoomStatus)\s*=\s*([^;]*)/ ).each do | variable |
|
281
|
+
if variable[0]
|
282
|
+
variables[variable[0]] = variable[1].scan /"([^"]*)\"/
|
283
|
+
end
|
284
|
+
end
|
285
|
+
debug and ( p '[Info - LightWaveRF Gem] so variables are ' + variables.inspect )
|
286
|
+
variables
|
287
|
+
end
|
288
|
+
|
199
289
|
# Get a cleaned up version of the rooms and devices from the config file
|
200
290
|
def self.get_rooms config = { 'room' => [ ]}, debug = false
|
201
291
|
rooms = { }
|
202
292
|
r = 1
|
203
293
|
config['room'].each do | room |
|
204
294
|
debug and ( puts room['name'] + ' = R' + r.to_s )
|
205
|
-
rooms[room['name']] = { 'id' => 'R' + r.to_s, 'name' => room['name'], 'device' => { }}
|
295
|
+
rooms[room['name']] = { 'id' => 'R' + r.to_s, 'name' => room['name'], 'device' => { }, 'mood' => { }, 'learnmood' => { }}
|
206
296
|
d = 1
|
207
|
-
room['device'].
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
297
|
+
unless room['device'].nil?
|
298
|
+
room['device'].each do | device |
|
299
|
+
# @todo possibly need to complicate this to get a device name back in here
|
300
|
+
debug and ( puts ' - ' + device + ' = D' + d.to_s )
|
301
|
+
rooms[room['name']]['device'][device] = 'D' + d.to_s
|
302
|
+
d += 1
|
303
|
+
end
|
304
|
+
end
|
305
|
+
m = 1
|
306
|
+
unless room['mood'].nil?
|
307
|
+
room['mood'].each do | mood |
|
308
|
+
rooms[room['name']]['mood'][mood] = 'FmP' + m.to_s
|
309
|
+
rooms[room['name']]['learnmood'][mood] = 'FsP' + m.to_s
|
310
|
+
m += 1
|
311
|
+
end
|
212
312
|
end
|
313
|
+
# add 'all off' special mood
|
314
|
+
rooms[room['name']]['mood']['alloff'] = 'Fa'
|
213
315
|
r += 1
|
214
316
|
end
|
215
317
|
rooms
|
@@ -235,6 +337,15 @@ class LightWaveRF
|
|
235
337
|
state = 'Fa'
|
236
338
|
when 'on'
|
237
339
|
state = 'F1'
|
340
|
+
# preset dim levels
|
341
|
+
when 'low'
|
342
|
+
state = 'FdP8'
|
343
|
+
when 'mid'
|
344
|
+
state = 'FdP16'
|
345
|
+
when 'high'
|
346
|
+
state = 'FdP24'
|
347
|
+
when 'full'
|
348
|
+
state = 'FdP32'
|
238
349
|
when 1..100
|
239
350
|
state = 'FdP' + ( state * 0.32 ).round.to_s
|
240
351
|
else
|
@@ -261,7 +372,7 @@ class LightWaveRF
|
|
261
372
|
'666,!' + room['id'] + room['device'][device] + state + '|Turn ' + room['name'] + ' ' + device + '|' + state + ' via @pauly'
|
262
373
|
else
|
263
374
|
'666,!' + room['id'] + state + '|Turn ' + room['name'] + '|' + state + ' via @pauly'
|
264
|
-
end
|
375
|
+
end
|
265
376
|
end
|
266
377
|
|
267
378
|
# Set the Time Zone on the LightWaveRF WiFi Link
|
@@ -278,7 +389,7 @@ class LightWaveRF
|
|
278
389
|
debug and ( puts '[Info - LightWaveRF] timezone: response is ' + data )
|
279
390
|
return (data == "666,OK\r\n")
|
280
391
|
end
|
281
|
-
|
392
|
+
|
282
393
|
# Turn one of your devices on or off or all devices in a room off
|
283
394
|
#
|
284
395
|
# Example:
|
@@ -290,18 +401,36 @@ class LightWaveRF
|
|
290
401
|
# device: (String)
|
291
402
|
# state: (String)
|
292
403
|
def send room = nil, device = nil, state = 'on', debug = false
|
293
|
-
|
404
|
+
success = false
|
405
|
+
debug and ( p 'Executing send on device: ' + device + ' in room: ' + room + ' with state: ' + state )
|
294
406
|
rooms = self.class.get_rooms self.get_config, debug
|
295
|
-
state = 'alloff' if (device.empty? and state == 'off')
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
debug and ( p 'command is ' + command )
|
300
|
-
data = self.raw command
|
301
|
-
debug and ( p 'response is ' + data )
|
407
|
+
state = 'alloff' if ( device.empty? and state == 'off' )
|
408
|
+
|
409
|
+
unless rooms[room] and state
|
410
|
+
STDERR.puts self.usage( room );
|
302
411
|
else
|
303
|
-
|
412
|
+
# support for setting state for all devices in the room (recursive)
|
413
|
+
if device == 'all'
|
414
|
+
debug and ( p 'Processing all devices...' )
|
415
|
+
rooms[room]['device'].each do | device_name, code |
|
416
|
+
debug and ( p "Device is: " + device_name )
|
417
|
+
self.send room, device_name, state, debug
|
418
|
+
sleep 1
|
419
|
+
end
|
420
|
+
success = true
|
421
|
+
# process single device
|
422
|
+
elsif state == 'alloff' || (device and rooms[room]['device'][device])
|
423
|
+
state = self.class.get_state state
|
424
|
+
command = self.command rooms[room], device, state
|
425
|
+
debug and ( p 'command is ' + command )
|
426
|
+
data = self.raw command
|
427
|
+
debug and ( p 'response is ' + data )
|
428
|
+
success = true
|
429
|
+
else
|
430
|
+
STDERR.puts self.usage( room );
|
431
|
+
end
|
304
432
|
end
|
433
|
+
success
|
305
434
|
end
|
306
435
|
|
307
436
|
# A sequence of events
|
@@ -314,11 +443,92 @@ class LightWaveRF
|
|
314
443
|
# name: (String)
|
315
444
|
# debug: (Boolean)
|
316
445
|
def sequence name, debug = false
|
446
|
+
success = true
|
317
447
|
if self.get_config['sequence'][name]
|
318
448
|
self.get_config['sequence'][name].each do | task |
|
319
|
-
|
449
|
+
if task[0] == 'pause'
|
450
|
+
debug and ( p 'Pausing for ' + task[1].to_s + ' seconds...' )
|
451
|
+
sleep task[1].to_i
|
452
|
+
debug and ( p 'Resuming...' )
|
453
|
+
elsif task[0] == 'mood'
|
454
|
+
self.mood task[1], task[2], debug
|
455
|
+
else
|
456
|
+
self.send task[0], task[1], task[2].to_s, debug
|
457
|
+
end
|
458
|
+
sleep 1
|
459
|
+
end
|
460
|
+
success = true
|
461
|
+
end
|
462
|
+
success
|
463
|
+
end
|
464
|
+
|
465
|
+
# Set a mood in one of your rooms
|
466
|
+
#
|
467
|
+
# Example:
|
468
|
+
# >> LightWaveRF.new.mood 'living', 'movie'
|
469
|
+
#
|
470
|
+
# Arguments:
|
471
|
+
# room: (String)
|
472
|
+
# mood: (String)
|
473
|
+
def mood room = nil, mood = nil, debug = false
|
474
|
+
success = false
|
475
|
+
debug and (p 'Executing mood: ' + mood + ' in room: ' + room)
|
476
|
+
#debug and ( puts 'config is ' + self.get_config.to_s )
|
477
|
+
rooms = self.class.get_rooms self.get_config
|
478
|
+
# support for setting a mood in all rooms (recursive)
|
479
|
+
if room == 'all'
|
480
|
+
debug and ( p "Processing all rooms..." )
|
481
|
+
rooms.each do | config, each_room |
|
482
|
+
room = each_room['name']
|
483
|
+
debug and ( p "Room is: " + room )
|
484
|
+
success = self.mood room, mood, debug
|
320
485
|
sleep 1
|
321
486
|
end
|
487
|
+
success = true
|
488
|
+
# process single mood
|
489
|
+
else
|
490
|
+
if rooms[room] and mood
|
491
|
+
if rooms[room]['mood'][mood]
|
492
|
+
command = self.command rooms[room], nil, rooms[room]['mood'][mood]
|
493
|
+
debug and ( p 'command is ' + command )
|
494
|
+
self.raw command
|
495
|
+
success = true
|
496
|
+
# support for special "moods" via device looping
|
497
|
+
elsif mood[0,3] == 'all'
|
498
|
+
state = mood[3..-1]
|
499
|
+
debug and (p 'Selected state is: ' + state)
|
500
|
+
rooms[room]['device'].each do | device |
|
501
|
+
p 'Processing device: ' + device[0]
|
502
|
+
self.send room, device[0], state, debug
|
503
|
+
sleep 1
|
504
|
+
end
|
505
|
+
success = true
|
506
|
+
end
|
507
|
+
else
|
508
|
+
STDERR.puts self.usage( room );
|
509
|
+
end
|
510
|
+
end
|
511
|
+
success
|
512
|
+
end
|
513
|
+
|
514
|
+
# Learn a mood in one of your rooms
|
515
|
+
#
|
516
|
+
# Example:
|
517
|
+
# >> LightWaveRF.new.learnmood 'living', 'movie'
|
518
|
+
#
|
519
|
+
# Arguments:
|
520
|
+
# room: (String)
|
521
|
+
# mood: (String)
|
522
|
+
def learnmood room = nil, mood = nil, debug = false
|
523
|
+
debug and (p 'Learning mood: ' + mood)
|
524
|
+
#debug and ( puts 'config is ' + self.get_config.to_s )
|
525
|
+
rooms = self.class.get_rooms self.get_config
|
526
|
+
if rooms[room] and mood and rooms[room]['learnmood'][mood]
|
527
|
+
command = self.command rooms[room], nil, rooms[room]['learnmood'][mood]
|
528
|
+
debug and ( p 'command is ' + command )
|
529
|
+
self.raw command
|
530
|
+
else
|
531
|
+
STDERR.puts self.usage( room )
|
322
532
|
end
|
323
533
|
end
|
324
534
|
|
@@ -336,8 +546,7 @@ class LightWaveRF
|
|
336
546
|
data['message']['annotation'] = { 'title' => title.to_s, 'text' => note.to_s }
|
337
547
|
end
|
338
548
|
debug and ( p data )
|
339
|
-
|
340
|
-
File.open( self.get_log_file, 'a' ) do |f|
|
549
|
+
File.open( self.get_log_file, 'a' ) do | f |
|
341
550
|
f.write( data.to_json + "\n" )
|
342
551
|
end
|
343
552
|
data['message']
|
@@ -348,7 +557,7 @@ class LightWaveRF
|
|
348
557
|
response = nil
|
349
558
|
# Get host address or broadcast address
|
350
559
|
host = self.get_config['host'] || '255.255.255.255'
|
351
|
-
# Create socket
|
560
|
+
# Create socket
|
352
561
|
listener = UDPSocket.new
|
353
562
|
# Add broadcast socket options if necessary
|
354
563
|
if (host == '255.255.255.255')
|
@@ -372,81 +581,370 @@ class LightWaveRF
|
|
372
581
|
response
|
373
582
|
end
|
374
583
|
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
# interval: (Integer)
|
391
|
-
# debug: (Boolean)
|
392
|
-
#
|
393
|
-
def timer interval = 5, debug = false
|
394
|
-
require 'net/http'
|
395
|
-
require 'rexml/document'
|
396
|
-
url = LightWaveRF.new.get_config['calendar'] + '?singleevents=true&start-min=' + Date.today.strftime( '%Y-%m-%d' ) + '&start-max=' + Date.today.next.strftime( '%Y-%m-%d' )
|
584
|
+
def update_timers past = 60, future = 1440, debug = false
|
585
|
+
p '----------------'
|
586
|
+
p "Updating timers..."
|
587
|
+
|
588
|
+
# determine the window to query
|
589
|
+
now = Time.new
|
590
|
+
query_start = now - self.class.to_seconds( past )
|
591
|
+
query_end = now + self.class.to_seconds( future )
|
592
|
+
|
593
|
+
url = LightWaveRF.new.get_config['calendar']
|
594
|
+
# url += '?ctz=' + Time.new.zone
|
595
|
+
url += '?ctz=UTC'
|
596
|
+
url += '&singleevents=true'
|
597
|
+
url += '&start-min=' + query_start.strftime( '%FT%T%:z' ).sub('+', '%2B')
|
598
|
+
url += '&start-max=' + query_end.strftime( '%FT%T%:z' ).sub('+', '%2B')
|
397
599
|
debug and ( p url )
|
398
600
|
parsed_url = URI.parse url
|
399
601
|
http = Net::HTTP.new parsed_url.host, parsed_url.port
|
400
602
|
begin
|
401
603
|
http.use_ssl = true
|
402
604
|
rescue
|
403
|
-
debug and ( p 'cannot use ssl' )
|
605
|
+
debug and ( p 'cannot use ssl, tried ' + parsed_url.host + ', ' + parsed_url.port.to_s )
|
606
|
+
url.gsub! 'https:', 'http:'
|
607
|
+
debug and ( p 'so fetching ' + url )
|
608
|
+
parsed_url = URI.parse url
|
609
|
+
http = Net::HTTP.new parsed_url.host
|
404
610
|
end
|
405
611
|
request = Net::HTTP::Get.new parsed_url.request_uri
|
406
612
|
response = http.request request
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
#
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
613
|
+
|
614
|
+
# if we get a good response
|
615
|
+
debug and ( p "Response code is: " + response.code)
|
616
|
+
if response.code == '200'
|
617
|
+
debug and ( p "Retrieved calendar ok")
|
618
|
+
doc = REXML::Document.new response.body
|
619
|
+
now = Time.now.strftime '%H:%M'
|
620
|
+
|
621
|
+
events = [ ]
|
622
|
+
states = [ ]
|
623
|
+
|
624
|
+
# refresh the list of entries for the caching period
|
625
|
+
doc.elements.each 'feed/entry' do | e |
|
626
|
+
debug and ( p "-------------------")
|
627
|
+
debug and ( p "Processing entry...")
|
628
|
+
event = Hash.new
|
629
|
+
|
630
|
+
# tokenise the title
|
631
|
+
debug and ( p "Event title is: " + e.elements['title'].text )
|
632
|
+
command = e.elements['title'].text.split
|
633
|
+
command_length = command.length
|
634
|
+
debug and ( p "Number of words is: " + command_length.to_s )
|
635
|
+
if command and command.length >= 1
|
636
|
+
first_word = command[0].to_s
|
637
|
+
# determine the type of the entry
|
638
|
+
if first_word[0,1] == '#'
|
639
|
+
debug and ( p "Type is: state" )
|
640
|
+
event['type'] = 'state' # temporary type, will be overridden later
|
641
|
+
event['room'] = nil
|
642
|
+
event['device'] = nil
|
643
|
+
event['state'] = first_word[1..-1].to_s
|
644
|
+
modifier_start = command_length # can't have modifiers on states
|
645
|
+
else
|
646
|
+
case first_word
|
647
|
+
when 'mood'
|
648
|
+
debug and ( p "Type is: mood" )
|
649
|
+
event['type'] = 'mood'
|
650
|
+
event['room'] = command[1].to_s
|
651
|
+
event['device'] = nil
|
652
|
+
event['state'] = command[2].to_s
|
653
|
+
modifier_start = 3
|
654
|
+
when 'sequence'
|
655
|
+
debug and ( p "Type is: sequence" )
|
656
|
+
event['type'] = 'sequence'
|
657
|
+
event['room'] = nil
|
658
|
+
event['device'] = nil
|
659
|
+
event['state'] = command[1].to_s
|
660
|
+
modifier_start = 2
|
661
|
+
else
|
662
|
+
debug and ( p "Type is: device" )
|
663
|
+
event['type'] = 'device'
|
664
|
+
event['room'] = command[0].to_s
|
665
|
+
event['device'] = command[1].to_s
|
666
|
+
# handle optional state
|
667
|
+
if command_length > 2
|
668
|
+
third_word = command[2].to_s
|
669
|
+
first_char = third_word[0,1]
|
670
|
+
debug and ( p "First char is: " + first_char )
|
671
|
+
# if the third word does not start with a modifier flag, assume it's a state
|
672
|
+
if first_char != '@' and first_char != '!' and first_char != '+' and first_char != '-'
|
673
|
+
debug and ( p "State has been given.")
|
674
|
+
event['state'] = command[2].to_s
|
675
|
+
modifier_start = 3
|
676
|
+
else
|
677
|
+
debug and ( p "State has not been given." )
|
678
|
+
modifier_start = 2
|
679
|
+
end
|
680
|
+
else
|
681
|
+
debug and ( p "State has not been given." )
|
682
|
+
event['state'] = nil
|
683
|
+
modifier_start = 2
|
684
|
+
end
|
685
|
+
end
|
686
|
+
end
|
687
|
+
|
688
|
+
# get modifiers if they exist
|
689
|
+
time_modifier = 0
|
690
|
+
if command_length > modifier_start
|
691
|
+
debug and ( p "May have modifiers..." )
|
692
|
+
when_modifiers = Array.new
|
693
|
+
unless_modifiers = Array.new
|
694
|
+
modifier_count = command_length - modifier_start
|
695
|
+
debug and ( p "Count of modifiers is " + modifier_count.to_s )
|
696
|
+
for i in modifier_start..(command_length-1)
|
697
|
+
modifier = command[i]
|
698
|
+
if modifier[0,1] == '@'
|
699
|
+
debug and ( p "Found when modifier: " + modifier[1..-1] )
|
700
|
+
when_modifiers.push modifier[1..-1]
|
701
|
+
elsif modifier[0,1] == '!'
|
702
|
+
debug and ( p "Found unless modifier: " + modifier[1..-1] )
|
703
|
+
unless_modifiers.push modifier[1..-1]
|
704
|
+
elsif modifier[0,1] == '+'
|
705
|
+
debug and ( p "Found positive time modifier: " + modifier[1..-1] )
|
706
|
+
time_modifier = modifier[1..-1].to_i
|
707
|
+
elsif modifier[0,1] == '-'
|
708
|
+
debug and ( p "Found negative time modifier: " + modifier[1..-1] )
|
709
|
+
time_modifier = modifier[1..-1].to_i * -1
|
710
|
+
end
|
711
|
+
end
|
712
|
+
# add when/unless modifiers to the event
|
713
|
+
event['when_modifiers'] = when_modifiers
|
714
|
+
event['unless_modifiers'] = unless_modifiers
|
715
|
+
end
|
716
|
+
|
717
|
+
# parse the date string
|
718
|
+
debug and ( p "Time string is: " + e.elements['summary'].text)
|
719
|
+
event_time = /When: ([\w ]+) (\d\d:\d\d) to ([\w ]+)?(\d\d:\d\d) \n(.*)<br>(.*)/.match e.elements['summary'].text
|
720
|
+
debug and ( p "Event times are: " + event_time.to_s )
|
721
|
+
start_date = event_time[1].to_s
|
722
|
+
start_time = event_time[2].to_s
|
723
|
+
end_date = event_time[3].to_s
|
724
|
+
end_time = event_time[4].to_s
|
725
|
+
timezone = event_time[5].to_s
|
726
|
+
if end_date == '' or end_date.nil? # copy start date to end date if it wasn't given (as the same date)
|
727
|
+
end_date = start_date
|
728
|
+
end
|
729
|
+
debug and ( p "Start date: " + start_date)
|
730
|
+
debug and ( p "Start time: " + start_time)
|
731
|
+
debug and ( p "End date: " + end_date)
|
732
|
+
debug and ( p "End time: " + end_time)
|
733
|
+
debug and ( p "Timezone: " + timezone)
|
734
|
+
|
735
|
+
# convert to datetimes
|
736
|
+
start_dt = DateTime.parse(start_date.strip + ' ' + start_time.strip + ' ' + timezone.strip)
|
737
|
+
end_dt = DateTime.parse(end_date.strip + ' ' + end_time.strip + ' ' + timezone.strip)
|
738
|
+
|
739
|
+
# apply time modifier if it exists
|
740
|
+
if time_modifier != 0
|
741
|
+
debug and ( p "Adjusting timings by: " + time_modifier.to_s)
|
742
|
+
start_dt = ((start_dt.to_time) + time_modifier*60).to_datetime
|
743
|
+
end_dt = ((end_dt.to_time) + time_modifier*60).to_datetime
|
744
|
+
end
|
745
|
+
|
746
|
+
debug and ( p "Start datetime: " + start_dt.to_s)
|
747
|
+
debug and ( p "End datetime: " + end_dt.to_s)
|
748
|
+
|
749
|
+
# populate the dates
|
750
|
+
event['date'] = start_dt
|
751
|
+
# handle device entries without explicit on/off state
|
752
|
+
if event['type'] == 'device' and ( event['state'].nil? or ( event['state'] != 'on' and event['state'] != 'off' ))
|
753
|
+
debug and ( p "Duplicating event without explicit on/off state...")
|
754
|
+
# if not state was given, assume we meant 'on'
|
755
|
+
if event['state'].nil?
|
756
|
+
event['state'] = 'on'
|
757
|
+
end
|
758
|
+
end_event = event.dup # duplicate event for start and end
|
759
|
+
end_event['date'] = end_dt
|
760
|
+
end_event['state'] = 'off'
|
761
|
+
events.push event
|
762
|
+
events.push end_event
|
763
|
+
# create state plus start and end events if a state
|
764
|
+
elsif event['type'] == 'state'
|
765
|
+
debug and ( p "Processing state : " + event['state'])
|
766
|
+
# create state
|
767
|
+
state = Hash.new
|
768
|
+
state['name'] = event['state']
|
769
|
+
state['start'] = start_dt.dup
|
770
|
+
state['end'] = end_dt.dup
|
771
|
+
states.push state
|
772
|
+
# convert event to start and end sequence
|
773
|
+
event['type'] = 'sequence'
|
774
|
+
event['state'] = state['name'] + '_start'
|
775
|
+
end_event = event.dup # duplicate event for start and end
|
776
|
+
end_event['date'] = end_dt
|
777
|
+
end_event['state'] = state['name'] + '_end'
|
778
|
+
events.push event
|
779
|
+
events.push end_event
|
780
|
+
# else just add the event
|
781
|
+
else
|
782
|
+
events.push event
|
783
|
+
end
|
784
|
+
|
429
785
|
end
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
786
|
+
|
787
|
+
end
|
788
|
+
|
789
|
+
# record some timestamps
|
790
|
+
info = { }
|
791
|
+
info['updated_at'] = Time.new.strftime( '%FT%T%:z' )
|
792
|
+
info['start_time'] = query_start.strftime( '%FT%T%:z' )
|
793
|
+
info['end_time'] = query_end.strftime( '%FT%T%:z' )
|
794
|
+
|
795
|
+
# build final timer config
|
796
|
+
timers = { }
|
797
|
+
timers['info'] = info
|
798
|
+
timers['events'] = events
|
799
|
+
timers['states'] = states
|
800
|
+
|
801
|
+
p 'Timer list is: ' + YAML.dump( timers )
|
802
|
+
|
803
|
+
# store the list
|
804
|
+
put_timer_cache timers
|
805
|
+
self.log_timer_event 'update', nil, nil, nil, true
|
806
|
+
|
807
|
+
else
|
808
|
+
self.log_timer_event 'update', nil, nil, nil, false
|
809
|
+
end
|
810
|
+
end
|
811
|
+
|
812
|
+
# Convert a string to seconds, assume it is in minutes
|
813
|
+
def self.to_seconds interval = 0
|
814
|
+
match = /^(\d+)([shd])$/.match( interval.to_s )
|
815
|
+
if match
|
816
|
+
case match[2]
|
817
|
+
when 's'
|
818
|
+
return match[1].to_i
|
819
|
+
when 'h'
|
820
|
+
return match[1].to_i * 3600
|
821
|
+
when 'd'
|
822
|
+
return match[1].to_i * 86400
|
823
|
+
end
|
824
|
+
end
|
825
|
+
return interval.to_i * 60
|
826
|
+
end
|
827
|
+
|
828
|
+
def run_timers interval = 5, debug = false
|
829
|
+
p '----------------'
|
830
|
+
p "Running timers..."
|
831
|
+
get_timer_cache
|
832
|
+
debug and ( p 'Timer list is: ' + YAML.dump( @timers ))
|
833
|
+
|
834
|
+
# get the current time and end interval time
|
835
|
+
now = Time.new
|
836
|
+
start_tm = now - now.sec
|
837
|
+
end_tm = start_tm + self.class.to_seconds( interval )
|
838
|
+
|
839
|
+
# convert to datetimes
|
840
|
+
start_horizon = DateTime.parse start_tm.to_s
|
841
|
+
end_horizon = DateTime.parse end_tm.to_s
|
842
|
+
p '----------------'
|
843
|
+
p 'Start horizon is: ' + start_horizon.to_s
|
844
|
+
p 'End horizon is: ' + end_horizon.to_s
|
845
|
+
|
846
|
+
# sort the events and states (to guarantee order if longer intervals are used)
|
847
|
+
@timers['events'].sort! { | x, y | x['date'] <=> y['date'] }
|
848
|
+
@timers['states'].sort! { | x, y | x['date'] <=> y['date'] }
|
849
|
+
|
850
|
+
# array to hold events that should be executed this run
|
851
|
+
run_list = [ ]
|
852
|
+
|
853
|
+
# process each event
|
854
|
+
@timers['events'].each do | event |
|
855
|
+
debug and ( p '----------------' )
|
856
|
+
debug and ( p 'Processing event: ' + event.to_s )
|
857
|
+
debug and ( p 'Event time is: ' + event['date'].to_s )
|
858
|
+
|
859
|
+
# first, assume we'll not be running the event
|
860
|
+
run_now = false
|
861
|
+
|
862
|
+
# check that it is in the horizon time
|
863
|
+
unless event['date'] >= start_horizon and event['date'] < end_horizon
|
864
|
+
debug and ( p 'Event is NOT in horizon...ignoring')
|
865
|
+
else
|
866
|
+
debug and ( p 'Event is in horizon...')
|
867
|
+
run_now = true
|
868
|
+
|
869
|
+
# if has modifiers, check modifiers against states
|
870
|
+
unless event['when_modifiers'].nil?
|
871
|
+
debug and ( p 'Event has when modifiers. Checking they are all met...')
|
872
|
+
|
873
|
+
# determine which states apply at the time of the event
|
874
|
+
applicable_states = Array.new
|
875
|
+
@timers['states'].each do | state |
|
876
|
+
if event['date'] >= state['start'] and event['date'] < state['end']
|
877
|
+
applicable_states.push state['name']
|
878
|
+
end
|
437
879
|
end
|
880
|
+
debug and ( p 'Applicable states are: ' + applicable_states.to_s)
|
881
|
+
|
882
|
+
# check that each when modifier exists in appliable states
|
883
|
+
event['when_modifiers'].each do | modifier |
|
884
|
+
unless applicable_states.include? modifier
|
885
|
+
debug and ( p 'Event when modifier not met: ' + modifier)
|
886
|
+
run_now = false
|
887
|
+
break
|
888
|
+
end
|
889
|
+
end
|
890
|
+
|
891
|
+
# check that each unless modifier does not exist in appliable states
|
892
|
+
event['unless_modifiers'].each do | modifier |
|
893
|
+
if applicable_states.include? modifier
|
894
|
+
debug and ( p 'Event unless modifier not met: ' + modifier)
|
895
|
+
run_now = false
|
896
|
+
break
|
897
|
+
end
|
898
|
+
end
|
899
|
+
end
|
900
|
+
|
901
|
+
# if we have determined the event should run, add to the run list
|
902
|
+
if run_now
|
903
|
+
run_list.push event
|
438
904
|
end
|
439
905
|
end
|
440
906
|
end
|
441
|
-
|
907
|
+
|
908
|
+
# process the run list
|
909
|
+
p '-----------------------'
|
910
|
+
p 'Events to execute this run are: ' + run_list.to_s
|
911
|
+
|
912
|
+
triggered = [ ]
|
913
|
+
|
914
|
+
run_list.each do | event |
|
915
|
+
# execute based on type
|
916
|
+
case event['type']
|
917
|
+
when 'mood'
|
918
|
+
p 'Executing mood. Room: ' + event['room'] + ', Mood: ' + event['state']
|
919
|
+
result = self.mood event['room'], event['state'], debug
|
920
|
+
sleep 1
|
921
|
+
triggered << [ event['room'], event['device'], event['state'] ]
|
922
|
+
when 'sequence'
|
923
|
+
p 'Executing sequence. Sequence: ' + event['state']
|
924
|
+
result = self.sequence event['state'], debug
|
925
|
+
sleep 1
|
926
|
+
triggered << [ event['room'], event['device'], event['state'] ]
|
927
|
+
else
|
928
|
+
p 'Executing device. Room: ' + event['room'] + ', Device: ' + event['device'] + ', State: ' + event['state']
|
929
|
+
result = self.send event['room'], event['device'], event['state'], debug
|
930
|
+
sleep 1
|
931
|
+
triggered << [ event['room'], event['device'], event['state'] ]
|
932
|
+
end
|
933
|
+
self.log_timer_event event['type'], event['room'], event['device'], event['state'], result
|
934
|
+
end
|
935
|
+
|
936
|
+
# update energy log
|
442
937
|
title = nil
|
443
938
|
text = nil
|
444
939
|
if triggered.length > 0
|
445
940
|
debug and ( p triggered.length.to_s + ' events so annotating energy log too...' )
|
446
941
|
title = 'timer'
|
447
|
-
text = triggered.map { |e| e.join " " }.join ", "
|
942
|
+
text = triggered.map { | e | e.join " " }.join ", "
|
448
943
|
end
|
449
944
|
self.energy title, text, debug
|
945
|
+
|
946
|
+
self.log_timer_event 'run', nil, nil, nil, true
|
450
947
|
end
|
948
|
+
|
451
949
|
end
|
452
950
|
|
metadata
CHANGED
@@ -1,20 +1,53 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lightwaverf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Paul Clarke
|
9
9
|
- Ian Perrin
|
10
|
+
- Julian McLean
|
10
11
|
autorequire:
|
11
12
|
bindir: bin
|
12
13
|
cert_chain: []
|
13
|
-
date: 2013-
|
14
|
-
dependencies:
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
date: 2013-06-22 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: htmlentities
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ! '>='
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '0'
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ! '>='
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: '0'
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: json
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ! '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
description: ! " Interact with lightwaverf wifi-link from code or the command line.\n
|
49
|
+
\ Control your lights, heating, sockets etc.\n Also set up timers using a google
|
50
|
+
calendar and log energy usage.\n"
|
18
51
|
email: pauly@clarkeology.com
|
19
52
|
executables:
|
20
53
|
- lightwaverf
|
@@ -48,5 +81,5 @@ rubyforge_project:
|
|
48
81
|
rubygems_version: 1.8.23
|
49
82
|
signing_key:
|
50
83
|
specification_version: 3
|
51
|
-
summary: Home automation
|
84
|
+
summary: Home automation with lightwaverf
|
52
85
|
test_files: []
|