projectsimulator 0.2.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/lib/projectsimulator.rb +14 -535
- data/lib/projectsimulator/controller.rb +116 -0
- data/lib/projectsimulator/model.rb +232 -0
- metadata +9 -27
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7e70e7f4a42e34c20b395b0fab1113c1ed59bbb00c2e0b0b117b201708d8578c
|
4
|
+
data.tar.gz: 3c9431a5a680b62c1070023a2b32f4b540530145d0eb896af6bc629105643d2f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0683c9527e84ffa413551d16b9d9fff3805fe9042a7068b3bdbf8b7c6ede26894aa6d2ce26a7cef07144abfa7501c577f0db9aa13418fe40f5283c283563be46'
|
7
|
+
data.tar.gz: a64eb0b3ba73be9163927edcdb8f202f955fc29474a9c65539a456a3f4e70f3612cbe97c9892a996b62d529436485319183cce26c80f37883f6b162fd204b443
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/lib/projectsimulator.rb
CHANGED
@@ -2,556 +2,35 @@
|
|
2
2
|
|
3
3
|
# file: projectsimulator.rb
|
4
4
|
|
5
|
-
|
5
|
+
|
6
6
|
require 'easydom'
|
7
|
+
require 'unichron'
|
7
8
|
require 'app-routes'
|
8
|
-
require 'chronic_between'
|
9
9
|
|
10
10
|
|
11
11
|
module ProjectSimulator
|
12
12
|
|
13
|
-
class
|
14
|
-
include AppRoutes
|
15
|
-
|
16
|
-
def initialize(obj=nil, root: 'building1')
|
17
|
-
|
18
|
-
super()
|
19
|
-
@root = root
|
20
|
-
@location = nil
|
21
|
-
build(obj, root: root) if obj
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
def build(raw_requests, root: @root)
|
26
|
-
|
27
|
-
@ed = EasyDom.new(debug: false, root: root)
|
28
|
-
raw_requests.lines.each {|line| request(line) }
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
def get_device(h)
|
33
|
-
|
34
|
-
a = h[:location].split(/ /)
|
35
|
-
a << h[:device]
|
36
|
-
status = a.inject(@ed) {|r,x| r.send(x)}.send(h[:action])
|
37
|
-
"The %s %s is %s." % [h[:location], h[:device], status]
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
def op()
|
42
|
-
@ed
|
43
|
-
end
|
44
|
-
|
45
|
-
def query(s)
|
46
|
-
@ed.e.element(s)
|
47
|
-
end
|
48
|
-
|
49
|
-
def request(s)
|
50
|
-
|
51
|
-
params = {request: s}
|
52
|
-
requests(params)
|
53
|
-
h = find_request(s)
|
54
|
-
|
55
|
-
method(h.first[-1]).call(h)
|
56
|
-
|
57
|
-
end
|
13
|
+
class Server
|
58
14
|
|
59
|
-
def
|
60
|
-
|
61
|
-
a = h[:location].split(/ /)
|
62
|
-
a << h[:device]
|
63
|
-
a.inject(@ed) {|r,x| r.send(x)}.send(h[:action], h[:value])
|
64
|
-
|
65
|
-
end
|
66
|
-
|
67
|
-
def to_sliml()
|
68
|
-
@ed.to_sliml
|
69
|
-
end
|
70
|
-
|
71
|
-
def xml(options=nil)
|
72
|
-
@ed.xml(pretty: true).gsub(' style=\'\'','')
|
73
|
-
end
|
74
|
-
|
75
|
-
protected
|
76
|
-
|
77
|
-
def requests(params)
|
78
|
-
|
79
|
-
# e.g. switch the livingroom gas_fire off
|
80
|
-
#
|
81
|
-
get /(?:switch|turn) the ([^ ]+) +([^ ]+) +(on|off)$/ do |location, device, onoff|
|
82
|
-
{type: :set_device, action: 'switch=', location: location, device: device, value: onoff}
|
83
|
-
end
|
15
|
+
def initialize(macros_package, drb_host: '127.0.0.1', devices: nil,
|
16
|
+
debug: false)
|
84
17
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
location = dev_location(device)
|
89
|
-
{type: :set_device, action: 'switch=', location: location, device: device, value: onoff}
|
90
|
-
end
|
18
|
+
rdc = ProjectSimulator::Controller.new(macros_package, devices: devices,
|
19
|
+
debug: debug)
|
20
|
+
@drb = OneDrb::Server.new host: drb_host, port: '5777', obj: rdc
|
91
21
|
|
92
|
-
# e.g. is the livingroom gas_fire on?
|
93
|
-
#
|
94
|
-
get /is the ([^ ]+) +([^ ]+) +(?:on|off)\??$/ do |location, device|
|
95
|
-
{type: :get_device, action: 'switch', location: location, device: device}
|
96
|
-
end
|
97
|
-
|
98
|
-
# e.g. is the gas_fire on?
|
99
|
-
#
|
100
|
-
get /is the ([^ ]+) +(?:on|off)\??$/ do |device|
|
101
|
-
location = dev_location(device)
|
102
|
-
{type: :get_device, action: 'switch', location: location, device: device}
|
103
|
-
end
|
104
|
-
|
105
|
-
# e.g. fetch the livingroom temperature reading
|
106
|
-
#
|
107
|
-
get /fetch the ([^ ]+) +([^ ]+) +(?:reading)$/ do |location, device|
|
108
|
-
{type: :get_device, action: 'reading', location: location, device: device}
|
109
|
-
end
|
110
|
-
|
111
|
-
# e.g. fetch the temperature reading
|
112
|
-
#
|
113
|
-
get /fetch the ([^ ]+) +(?:reading)$/ do |device|
|
114
|
-
location = dev_location(device)
|
115
|
-
{type: :get_device, action: 'reading', location: location, device: device}
|
116
|
-
end
|
117
|
-
|
118
22
|
end
|
119
23
|
|
120
|
-
|
121
|
-
|
122
|
-
def dev_location(device)
|
123
|
-
a = query('//'+ device).backtrack.to_xpath.split('/')
|
124
|
-
a[1..-2].join(' ')
|
24
|
+
def start
|
25
|
+
@drb.start
|
125
26
|
end
|
126
|
-
|
127
|
-
alias find_request run_route
|
128
27
|
|
129
28
|
end
|
130
29
|
|
131
30
|
|
132
|
-
|
133
|
-
|
134
|
-
attr_accessor :title
|
135
|
-
attr_reader :triggers, :actions, :constraints, :messages
|
136
|
-
|
137
|
-
def initialize(e, time: nil, title: '', debug: false)
|
138
|
-
|
139
|
-
@time, @debug = time, debug
|
140
|
-
@title = e.text('event')
|
141
|
-
@actions = []
|
142
|
-
@triggers = []
|
143
|
-
@constraints = []
|
144
|
-
|
145
|
-
e.xpath('trigger').each do |x|
|
146
|
-
@triggers << Trigger.new(x.text().strip, time: time, debug: debug)\
|
147
|
-
.to_type
|
148
|
-
end
|
149
|
-
|
150
|
-
e.xpath('action').each do |x|
|
151
|
-
@actions << Action.new(x.text().strip, debug: debug).to_type
|
152
|
-
end
|
153
|
-
|
154
|
-
e.xpath('constraint').each do |x|
|
155
|
-
puts 'before Constraints.new'
|
156
|
-
@constraints << Constraint.new(x.text().strip, \
|
157
|
-
time: time, debug: debug).to_type
|
158
|
-
end
|
159
|
-
|
160
|
-
end
|
161
|
-
|
162
|
-
def match(trigger: nil, location: '')
|
163
|
-
|
164
|
-
@messages = []
|
165
|
-
|
166
|
-
h = {motion: MotionTrigger}
|
167
|
-
|
168
|
-
if @triggers.any? {|x| x.is_a? h[trigger.to_sym] and x.match } then
|
169
|
-
|
170
|
-
if @constraints.all?(&:match) then
|
171
|
-
|
172
|
-
@messages = @actions.map(&:call)
|
173
|
-
|
174
|
-
else
|
175
|
-
|
176
|
-
puts 'else reset?' if @debug
|
177
|
-
a = @constraints.select {|x| x.is_a? FrequencyConstraint }
|
178
|
-
puts 'a:' + a.inspect if @debug
|
179
|
-
a.each {|x| x.reset if x.counter > 0 }
|
180
|
-
return false
|
181
|
-
end
|
182
|
-
|
183
|
-
end
|
184
|
-
|
185
|
-
end
|
186
|
-
|
187
|
-
def time=(val)
|
188
|
-
@time = val
|
189
|
-
@constraints.each {|x| x.time = val if x.is_a? TimeConstraint }
|
190
|
-
end
|
191
|
-
|
192
|
-
def to_node()
|
193
|
-
|
194
|
-
e = Rexle::Element.new(:event, attributes: {title: @title})
|
195
|
-
|
196
|
-
e.add node_collection(:triggers, @triggers)
|
197
|
-
e.add node_collection(:actions, @actions)
|
198
|
-
e.add node_collection(:constraints, @constraints)
|
199
|
-
|
200
|
-
return e
|
201
|
-
end
|
202
|
-
|
203
|
-
def to_rowx()
|
204
|
-
|
205
|
-
s = "event: %s\n\n" % @title
|
206
|
-
s + [@triggers, @actions, @constraints]\
|
207
|
-
.map {|x| x.collect(&:to_rowx).join("\n")}.join("\n")
|
208
|
-
end
|
209
|
-
|
210
|
-
private
|
211
|
-
|
212
|
-
def node_collection(name, a)
|
213
|
-
|
214
|
-
e = Rexle::Element.new(name)
|
215
|
-
a.each {|x| e.add x.to_node}
|
216
|
-
return e
|
217
|
-
|
218
|
-
end
|
219
|
-
|
220
|
-
end
|
221
|
-
|
222
|
-
class Action
|
223
|
-
include AppRoutes
|
224
|
-
|
225
|
-
attr_reader :to_type
|
226
|
-
|
227
|
-
def initialize(s, event: '', debug: false)
|
228
|
-
|
229
|
-
super()
|
230
|
-
@debug = debug
|
231
|
-
params = {s: s, event: event}
|
232
|
-
actions(params)
|
233
|
-
@to_type = find_action(s) || {}
|
234
|
-
|
235
|
-
end
|
236
|
-
|
237
|
-
protected
|
238
|
-
|
239
|
-
def actions(params)
|
240
|
-
|
241
|
-
puts 'inside actions'
|
242
|
-
# e.g. Say 'Good morning'
|
243
|
-
#
|
244
|
-
get /say ['"]([^'"]+)/i do |s|
|
245
|
-
puts 's: ' + s.inspect if @debug
|
246
|
-
SayAction.new(s)
|
247
|
-
end
|
248
|
-
|
249
|
-
# e.g. webhook entered_kitchen
|
250
|
-
#
|
251
|
-
get /webhook (.*)/i do |name|
|
252
|
-
WebhookAction.new(name)
|
253
|
-
end
|
254
|
-
|
255
|
-
get /.*/ do
|
256
|
-
puts 'action unknown' if @debug
|
257
|
-
{}
|
258
|
-
end
|
259
|
-
|
260
|
-
end
|
261
|
-
|
262
|
-
private
|
263
|
-
|
264
|
-
alias find_action run_route
|
265
|
-
|
266
|
-
end
|
267
|
-
|
268
|
-
class Trigger
|
269
|
-
include AppRoutes
|
270
|
-
|
271
|
-
attr_reader :to_type
|
272
|
-
|
273
|
-
def initialize(s, time: nil, debug: false)
|
274
|
-
|
275
|
-
super()
|
276
|
-
@time, @debug = time, debug
|
277
|
-
params = {s: s}
|
278
|
-
puts 'inside Trigger'
|
279
|
-
puts 'params: ' + params.inspect
|
280
|
-
triggers(params)
|
281
|
-
@to_type = find_trigger(s)
|
282
|
-
|
283
|
-
end
|
284
|
-
|
285
|
-
protected
|
286
|
-
|
287
|
-
def triggers(params)
|
288
|
-
|
289
|
-
puts 'inside triggers'
|
290
|
-
|
291
|
-
# e.g. Motion detected in the kitchen
|
292
|
-
#
|
293
|
-
get /motion detected in the (.*)/i do |location|
|
294
|
-
puts 'motion detection trigger'
|
295
|
-
MotionTrigger.new(location)
|
296
|
-
end
|
297
|
-
|
298
|
-
end
|
299
|
-
|
300
|
-
private
|
301
|
-
|
302
|
-
alias find_trigger run_route
|
303
|
-
|
304
|
-
end
|
305
|
-
|
306
|
-
class Constraint
|
307
|
-
include AppRoutes
|
308
|
-
|
309
|
-
attr_reader :to_type
|
310
|
-
|
311
|
-
def initialize(s, time: nil, debug: false)
|
312
|
-
|
313
|
-
super()
|
314
|
-
@time, @debug = time, debug
|
315
|
-
|
316
|
-
params = {s: s }
|
317
|
-
constraints(params)
|
318
|
-
@to_type = find_constraint(s)
|
319
|
-
|
320
|
-
end
|
321
|
-
|
322
|
-
protected
|
323
|
-
|
324
|
-
def constraints(params)
|
325
|
-
|
326
|
-
puts 'inside constraints' if @debug
|
327
|
-
# e.g. Between 8am and 10am
|
328
|
-
#
|
329
|
-
get /^between (.*)/i do |s|
|
330
|
-
TimeConstraint.new(s, time: @time)
|
331
|
-
end
|
332
|
-
|
333
|
-
get /^on a (.*)/i do |s|
|
334
|
-
TimeConstraint.new(s, time: @time)
|
335
|
-
end
|
336
|
-
|
337
|
-
get /^(after .*)/i do |s|
|
338
|
-
TimeConstraint.new(s, time: @time)
|
339
|
-
end
|
340
|
-
|
341
|
-
get /^once only|only once|once|one time|1 time$/i do |s|
|
342
|
-
FrequencyConstraint.new(1, debug: @debug)
|
343
|
-
end
|
344
|
-
|
345
|
-
get /^twice only|only twice|twice|two times|2 times$/i do |s|
|
346
|
-
FrequencyConstraint.new(2, debug: @debug)
|
347
|
-
end
|
348
|
-
|
349
|
-
get /^(Maximum|Max|Up to) ?three times|3 times$/i do |s|
|
350
|
-
FrequencyConstraint.new(3, debug: @debug)
|
351
|
-
end
|
352
|
-
|
353
|
-
get /^(Maximum|Max|Up to) ?four times|4 times$/i do |s|
|
354
|
-
FrequencyConstraint.new(4, debug: @debug)
|
355
|
-
end
|
356
|
-
|
357
|
-
end
|
358
|
-
|
359
|
-
private
|
360
|
-
|
361
|
-
alias find_constraint run_route
|
362
|
-
end
|
363
|
-
|
364
|
-
class MotionTrigger
|
365
|
-
|
366
|
-
attr_reader :location
|
367
|
-
|
368
|
-
def initialize(locationx, location: locationx)
|
369
|
-
@location = location
|
370
|
-
end
|
371
|
-
|
372
|
-
def match()
|
373
|
-
@location.downcase == location.downcase
|
374
|
-
end
|
375
|
-
|
376
|
-
def to_node()
|
377
|
-
Rexle::Element.new(:trigger, \
|
378
|
-
attributes: {type: :motion, location: @location})
|
379
|
-
end
|
380
|
-
|
381
|
-
def to_rowx()
|
382
|
-
"trigger: Motion detected in the %s" % @location
|
383
|
-
end
|
384
|
-
|
385
|
-
end
|
386
|
-
|
387
|
-
class SayAction
|
388
|
-
|
389
|
-
def initialize(s, text: s)
|
390
|
-
@s = s
|
391
|
-
end
|
392
|
-
|
393
|
-
def call()
|
394
|
-
"say: %s" % @s
|
395
|
-
end
|
396
|
-
|
397
|
-
def to_node()
|
398
|
-
Rexle::Element.new(:action, attributes: {type: :say, text: @s})
|
399
|
-
end
|
400
|
-
|
401
|
-
def to_rowx()
|
402
|
-
"action: say %s" % @s
|
403
|
-
end
|
404
|
-
|
405
|
-
end
|
406
|
-
|
407
|
-
class WebhookAction
|
408
|
-
|
409
|
-
attr_accessor :url
|
410
|
-
|
411
|
-
def initialize(namex, name: namex, url: '127.0.0.1')
|
412
|
-
@name = name
|
413
|
-
@url = url
|
414
|
-
end
|
415
|
-
|
416
|
-
def call()
|
417
|
-
"webhook: %s" % @url
|
418
|
-
end
|
419
|
-
|
420
|
-
def to_node()
|
421
|
-
Rexle::Element.new(:action, \
|
422
|
-
attributes: {type: :webhook, name: @name, url: @url})
|
423
|
-
end
|
424
|
-
|
425
|
-
def to_rowx()
|
426
|
-
s = "action: webhook %s" % @name
|
427
|
-
s += "\n url: %s" % @url
|
428
|
-
end
|
429
|
-
|
430
|
-
end
|
431
|
-
|
432
|
-
class TimeConstraint
|
433
|
-
|
434
|
-
attr_accessor :time
|
435
|
-
|
436
|
-
def initialize(timesx, times: timesx, time: nil)
|
437
|
-
@times, @time = times, time
|
438
|
-
end
|
439
|
-
|
440
|
-
def match()
|
441
|
-
ChronicBetween.new(@times).within?(@time)
|
442
|
-
end
|
443
|
-
|
444
|
-
def to_node()
|
445
|
-
Rexle::Element.new(:constraint, \
|
446
|
-
attributes: {type: :time, times: @times})
|
447
|
-
end
|
448
|
-
|
449
|
-
def to_rowx()
|
450
|
-
"constraint: %s" % @times
|
451
|
-
end
|
452
|
-
|
453
|
-
end
|
454
|
-
|
455
|
-
class FrequencyConstraint
|
456
|
-
|
457
|
-
def initialize(freqx, freq: freqx, debug: false)
|
458
|
-
@freq, @debug = freq, debug
|
459
|
-
@counter = 0
|
460
|
-
end
|
461
|
-
|
462
|
-
def counter()
|
463
|
-
@counter
|
464
|
-
end
|
465
|
-
|
466
|
-
def increment()
|
467
|
-
@counter += 1
|
468
|
-
end
|
469
|
-
|
470
|
-
def match()
|
471
|
-
@counter < @freq
|
472
|
-
end
|
473
|
-
|
474
|
-
def reset()
|
475
|
-
puts 'resetting' if @debug
|
476
|
-
@counter = 0
|
477
|
-
end
|
478
|
-
|
479
|
-
def to_node()
|
480
|
-
Rexle::Element.new(:constraint, \
|
481
|
-
attributes: {type: :frequency, freq: @freq})
|
482
|
-
end
|
483
|
-
|
484
|
-
def to_rowx()
|
485
|
-
|
486
|
-
freq = case @freq
|
487
|
-
when 1
|
488
|
-
'Once'
|
489
|
-
when 2
|
490
|
-
'Twice'
|
491
|
-
else
|
492
|
-
"Maximum %s times" % @freq
|
493
|
-
end
|
494
|
-
|
495
|
-
"constraint: %s" % freq
|
496
|
-
|
497
|
-
end
|
498
|
-
|
499
|
-
end
|
500
|
-
|
501
|
-
class Controller
|
502
|
-
|
503
|
-
attr_reader :events
|
504
|
-
attr_accessor :time
|
505
|
-
|
506
|
-
def initialize(s, time: Time.now, debug: false)
|
507
|
-
|
508
|
-
@time, @debug = time, debug
|
509
|
-
|
510
|
-
doc = Rexle.new(RowX.new(s).to_xml)
|
511
|
-
|
512
|
-
@events = doc.root.xpath('item')\
|
513
|
-
.map {|e| Event.new(e, time: @time, debug: debug) }
|
514
|
-
|
515
|
-
end
|
516
|
-
|
517
|
-
def time=(val)
|
518
|
-
@time = val
|
519
|
-
@events.each {|event| event.time = val }
|
520
|
-
end
|
521
|
-
|
522
|
-
def to_doc()
|
523
|
-
|
524
|
-
doc = Rexle.new('<events/>')
|
525
|
-
@events.each {|event| doc.root.add event.to_node }
|
526
|
-
return doc
|
527
|
-
|
528
|
-
end
|
529
|
-
|
530
|
-
def to_rowx()
|
531
|
-
@events.collect(&:to_rowx).join("\n\n#{'-'*50}\n\n")
|
532
|
-
end
|
533
|
-
|
534
|
-
def trigger(name, location: '')
|
535
|
-
|
536
|
-
events = @events.select do |event|
|
537
|
-
|
538
|
-
puts 'event: ' + event.inspect if @debug
|
31
|
+
end
|
539
32
|
|
540
|
-
event.match(trigger: 'motion', location: location)
|
541
|
-
|
542
|
-
end
|
543
|
-
|
544
|
-
puts 'events: ' + events.inspect if @debug
|
545
|
-
|
546
|
-
events.each do |event|
|
547
|
-
c = event.constraints.select {|x| x.is_a? FrequencyConstraint }
|
548
|
-
puts 'c:' + c.inspect
|
549
|
-
c.each(&:increment)
|
550
|
-
end
|
551
|
-
|
552
|
-
events.flat_map(&:messages)
|
553
|
-
|
554
|
-
end
|
555
33
|
|
556
|
-
|
557
|
-
|
34
|
+
require 'projectsimulator/model'
|
35
|
+
require 'projectsimulator/controller'
|
36
|
+
require 'projectsimulator/client'
|
@@ -0,0 +1,116 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# file: projectsimulator/controller.rb
|
4
|
+
|
5
|
+
|
6
|
+
module ProjectSimulator
|
7
|
+
|
8
|
+
class Controller
|
9
|
+
|
10
|
+
attr_reader :macros, :model
|
11
|
+
attr_accessor :title, :speed
|
12
|
+
|
13
|
+
def initialize(mcs, model=nil, time: Time.now, speed: 1, debug: false)
|
14
|
+
|
15
|
+
@debug = debug
|
16
|
+
@syslog = []
|
17
|
+
|
18
|
+
@macros = mcs.macros
|
19
|
+
|
20
|
+
if model then
|
21
|
+
@model = Model.new(model)
|
22
|
+
end
|
23
|
+
|
24
|
+
@state = :stop
|
25
|
+
@speed = speed
|
26
|
+
|
27
|
+
@qt = UnichronUtils::Quicktime.new time: time, speed: @speed
|
28
|
+
@qt.start
|
29
|
+
@qt.pause
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
# Object Property (op)
|
34
|
+
# Helpful for accessing properites in dot notation
|
35
|
+
# e.g. op.livingroom.light.switch = 'off'
|
36
|
+
#
|
37
|
+
def op()
|
38
|
+
@model.op if @model
|
39
|
+
end
|
40
|
+
|
41
|
+
def pause()
|
42
|
+
@state = :pause
|
43
|
+
@qt.pause
|
44
|
+
end
|
45
|
+
|
46
|
+
def play()
|
47
|
+
@state = :play
|
48
|
+
@qt.play
|
49
|
+
end
|
50
|
+
|
51
|
+
def request(s)
|
52
|
+
@model.request s
|
53
|
+
end
|
54
|
+
|
55
|
+
def start()
|
56
|
+
|
57
|
+
|
58
|
+
Thread.new do
|
59
|
+
|
60
|
+
old_time = @qt.time - 1
|
61
|
+
|
62
|
+
loop do
|
63
|
+
|
64
|
+
interval = (1 / (2.0 * @speed))
|
65
|
+
(sleep interval; next) if @state != :play or old_time == @qt.time
|
66
|
+
#puts Time.now.inspect if @debug
|
67
|
+
r = self.trigger :timer, {time: @qt.time}
|
68
|
+
|
69
|
+
yield(r) if r.any?
|
70
|
+
|
71
|
+
puts 'r: ' + r.inspect if @debug and r.any?
|
72
|
+
sleep interval
|
73
|
+
old_time = @qt.time
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def stop()
|
80
|
+
@state = :stop
|
81
|
+
@qt.pause
|
82
|
+
end
|
83
|
+
|
84
|
+
def time()
|
85
|
+
@qt.time
|
86
|
+
end
|
87
|
+
|
88
|
+
def trigger(name, detail={})
|
89
|
+
|
90
|
+
macros = @macros.select do |macro|
|
91
|
+
|
92
|
+
#puts 'macro: ' + macro.inspect if @debug
|
93
|
+
|
94
|
+
# fetch the associated properties from the model if possible and
|
95
|
+
# merge them into the detail.
|
96
|
+
#
|
97
|
+
valid_trigger = macro.match?(name, detail, op())
|
98
|
+
|
99
|
+
#puts 'valid_trigger: ' + valid_trigger.inspect if @debug
|
100
|
+
|
101
|
+
if valid_trigger then
|
102
|
+
@syslog << [Time.now, :trigger, name]
|
103
|
+
@syslog << [Time.now, :macro, macro.title]
|
104
|
+
end
|
105
|
+
|
106
|
+
valid_trigger
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
#puts 'macros: ' + macros.inspect if @debug
|
111
|
+
|
112
|
+
macros.flat_map(&:run)
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# file: projectsimulator/controller.rb
|
4
|
+
|
5
|
+
|
6
|
+
module ProjectSimulator
|
7
|
+
|
8
|
+
class Model
|
9
|
+
include AppRoutes
|
10
|
+
|
11
|
+
def initialize(obj=nil, root: 'building1', debug: false)
|
12
|
+
|
13
|
+
super()
|
14
|
+
@root, @debug = root, debug
|
15
|
+
@location = nil
|
16
|
+
|
17
|
+
if obj then
|
18
|
+
|
19
|
+
s = obj.strip
|
20
|
+
|
21
|
+
puts 's: ' + s.inspect if @debug
|
22
|
+
|
23
|
+
if s[0] == '<' or s.lines[1][0..1] == ' ' then
|
24
|
+
|
25
|
+
puts 'before easydom' if @debug
|
26
|
+
|
27
|
+
s2 = if s.lines[1][0..1] == ' ' then
|
28
|
+
|
29
|
+
lines = s.lines.map do |line|
|
30
|
+
line.sub(/(\w+) +is +(\w+)$/) {|x| "#{$1} {switch: #{$2}}" }
|
31
|
+
end
|
32
|
+
|
33
|
+
lines.join
|
34
|
+
|
35
|
+
else
|
36
|
+
s
|
37
|
+
end
|
38
|
+
|
39
|
+
@ed = EasyDom.new(s2)
|
40
|
+
else
|
41
|
+
build(s, root: root)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
def build(raw_requests, root: @root)
|
49
|
+
|
50
|
+
@ed = EasyDom.new(debug: false, root: root)
|
51
|
+
raw_requests.lines.each {|line| request(line) }
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def get_thing(h)
|
57
|
+
|
58
|
+
h[:thing].gsub!(/ /,'_')
|
59
|
+
|
60
|
+
if not h.has_key? :location then
|
61
|
+
location = false
|
62
|
+
h[:location] = find_path(h[:thing])
|
63
|
+
else
|
64
|
+
location = true
|
65
|
+
end
|
66
|
+
|
67
|
+
puts 'h: ' + h.inspect if @debug
|
68
|
+
|
69
|
+
a = []
|
70
|
+
a += h[:location].split(/ /)
|
71
|
+
a << h[:thing]
|
72
|
+
status = a.inject(@ed) {|r,x| r.send(x)}.send(h[:action])
|
73
|
+
|
74
|
+
if location then
|
75
|
+
"The %s %s is %s." % [h[:location], h[:thing], status]
|
76
|
+
else
|
77
|
+
"%s is %s." % [h[:thing].capitalize, status]
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
# Object Property (op)
|
83
|
+
# Helpful for accessing properites in dot notation
|
84
|
+
# e.g. op.livingroom.light.switch = 'off'
|
85
|
+
#
|
86
|
+
def op()
|
87
|
+
@ed
|
88
|
+
end
|
89
|
+
|
90
|
+
def query(s)
|
91
|
+
@ed.e.element(s)
|
92
|
+
end
|
93
|
+
|
94
|
+
# request accepts a string in plain english
|
95
|
+
# e.g. request 'switch the livingroom light on'
|
96
|
+
#
|
97
|
+
def request(s)
|
98
|
+
|
99
|
+
params = {request: s}
|
100
|
+
requests(params)
|
101
|
+
h = find_request(s)
|
102
|
+
|
103
|
+
method(h.first[-1]).call(h).gsub(/_/,' ')
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
def set_thing(h)
|
108
|
+
|
109
|
+
h[:thing].gsub!(/ /,'_')
|
110
|
+
h[:location] = find_path(h[:thing]) unless h.has_key? :location
|
111
|
+
|
112
|
+
a = []
|
113
|
+
a += h[:location].split(/ /)
|
114
|
+
a << h[:thing]
|
115
|
+
|
116
|
+
a.inject(@ed) {|r,x| r.send(x)}.send(h[:action], h[:value])
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
def to_sliml(level: 0)
|
121
|
+
|
122
|
+
s = @ed.to_sliml
|
123
|
+
|
124
|
+
return s if level.to_i > 0
|
125
|
+
|
126
|
+
lines = s.lines.map do |line|
|
127
|
+
|
128
|
+
line.sub(/\{[^\}]+\}/) do |x|
|
129
|
+
|
130
|
+
a = x.scan(/\w+: +[^ ]+/)
|
131
|
+
if a.length == 1 and x[/switch:/] then
|
132
|
+
|
133
|
+
val = x[/(?<=switch: ) *["']([^"']+)/,1]
|
134
|
+
'is ' + val
|
135
|
+
else
|
136
|
+
x
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
lines.join
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
def to_xml(options=nil)
|
147
|
+
@ed.xml(pretty: true).gsub(' style=\'\'','')
|
148
|
+
end
|
149
|
+
|
150
|
+
alias xml to_xml
|
151
|
+
|
152
|
+
# to_xml() is the preferred method
|
153
|
+
|
154
|
+
protected
|
155
|
+
|
156
|
+
def requests(params)
|
157
|
+
|
158
|
+
# e.g. switch the livingroom gas_fire off
|
159
|
+
#
|
160
|
+
get /(?:switch|turn) the ([^ ]+) +([^ ]+) +(on|off)$/ do |location, device, onoff|
|
161
|
+
{type: :set_thing, action: 'switch=', location: location, thing: device, value: onoff}
|
162
|
+
end
|
163
|
+
|
164
|
+
# e.g. switch the gas _fire off
|
165
|
+
#
|
166
|
+
get /(?:switch|turn) the ([^ ]+) +(on|off)$/ do |device, onoff|
|
167
|
+
{type: :set_thing, action: 'switch=', thing: device, value: onoff}
|
168
|
+
end
|
169
|
+
|
170
|
+
# e.g. is the livingroom gas_fire on?
|
171
|
+
#
|
172
|
+
get /is the ([^ ]+) +([^ ]+) +(?:on|off)\??$/ do |location, device|
|
173
|
+
{type: :get_thing, action: 'switch', location: location, thing: device}
|
174
|
+
end
|
175
|
+
|
176
|
+
# e.g. enable airplane mode
|
177
|
+
#
|
178
|
+
get /((?:dis|en)able) ([^$]+)$/ do |state, service|
|
179
|
+
{type: :set_thing, action: 'switch=', thing: service, value: state + 'd'}
|
180
|
+
end
|
181
|
+
|
182
|
+
# e.g. switch airplane mode off
|
183
|
+
#
|
184
|
+
get /switch (.*) (on|off)/ do |service, rawstate|
|
185
|
+
|
186
|
+
state = rawstate == 'on' ? 'enabled' : 'disabled'
|
187
|
+
{type: :set_thing, action: 'switch=', thing: service, value: state}
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
# e.g. is airplane mode enabed?
|
192
|
+
#
|
193
|
+
get /is (.*) +(?:(?:dis|en)abled)\??$/ do |service|
|
194
|
+
{type: :get_thing, action: 'switch', thing: service.gsub(/ /,'_')}
|
195
|
+
end
|
196
|
+
|
197
|
+
# e.g. is the gas_fire on?
|
198
|
+
#
|
199
|
+
get /is the ([^ ]+) +(?:on|off)\??$/ do |device|
|
200
|
+
location = find_path(device)
|
201
|
+
{type: :get_thing, action: 'switch', location: location, thing: device}
|
202
|
+
end
|
203
|
+
|
204
|
+
# e.g. fetch the livingroom temperature reading
|
205
|
+
#
|
206
|
+
get /fetch the ([^ ]+) +([^ ]+) +(?:reading)$/ do |location, device|
|
207
|
+
{type: :get_thing, action: 'reading', location: location, thing: device}
|
208
|
+
end
|
209
|
+
|
210
|
+
# e.g. fetch the temperature reading
|
211
|
+
#
|
212
|
+
get /fetch the ([^ ]+) +(?:reading)$/ do |device|
|
213
|
+
location = find_path(device)
|
214
|
+
{type: :get_thing, action: 'reading', location: location, thing: device}
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
|
219
|
+
private
|
220
|
+
|
221
|
+
def find_path(s)
|
222
|
+
puts 'find_path s: ' + s.inspect if @debug
|
223
|
+
found = query('//'+ s)
|
224
|
+
return unless found
|
225
|
+
a = found.backtrack.to_xpath.split('/')
|
226
|
+
a[1..-2].join(' ')
|
227
|
+
end
|
228
|
+
|
229
|
+
alias find_request run_route
|
230
|
+
|
231
|
+
end
|
232
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: projectsimulator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Robertson
|
@@ -35,28 +35,28 @@ cert_chain:
|
|
35
35
|
2GrtfOXGVOS+2jvmCQC6vU+ew9WBxiDUbebI95TeTwMs2o0cs3IASXX5rIn4TPcz
|
36
36
|
SCccB3fVg2yfsy5DivaWaZwg
|
37
37
|
-----END CERTIFICATE-----
|
38
|
-
date: 2020-
|
38
|
+
date: 2020-11-18 00:00:00.000000000 Z
|
39
39
|
dependencies:
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
|
-
name:
|
41
|
+
name: unichron
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
44
|
- - ">="
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: 0.
|
46
|
+
version: 0.2.0
|
47
47
|
- - "~>"
|
48
48
|
- !ruby/object:Gem::Version
|
49
|
-
version: '0.
|
49
|
+
version: '0.2'
|
50
50
|
type: :runtime
|
51
51
|
prerelease: false
|
52
52
|
version_requirements: !ruby/object:Gem::Requirement
|
53
53
|
requirements:
|
54
54
|
- - ">="
|
55
55
|
- !ruby/object:Gem::Version
|
56
|
-
version: 0.
|
56
|
+
version: 0.2.0
|
57
57
|
- - "~>"
|
58
58
|
- !ruby/object:Gem::Version
|
59
|
-
version: '0.
|
59
|
+
version: '0.2'
|
60
60
|
- !ruby/object:Gem::Dependency
|
61
61
|
name: easydom
|
62
62
|
requirement: !ruby/object:Gem::Requirement
|
@@ -97,26 +97,6 @@ dependencies:
|
|
97
97
|
- - ">="
|
98
98
|
- !ruby/object:Gem::Version
|
99
99
|
version: 0.1.19
|
100
|
-
- !ruby/object:Gem::Dependency
|
101
|
-
name: chronic_between
|
102
|
-
requirement: !ruby/object:Gem::Requirement
|
103
|
-
requirements:
|
104
|
-
- - "~>"
|
105
|
-
- !ruby/object:Gem::Version
|
106
|
-
version: '0.3'
|
107
|
-
- - ">="
|
108
|
-
- !ruby/object:Gem::Version
|
109
|
-
version: 0.3.1
|
110
|
-
type: :runtime
|
111
|
-
prerelease: false
|
112
|
-
version_requirements: !ruby/object:Gem::Requirement
|
113
|
-
requirements:
|
114
|
-
- - "~>"
|
115
|
-
- !ruby/object:Gem::Version
|
116
|
-
version: '0.3'
|
117
|
-
- - ">="
|
118
|
-
- !ruby/object:Gem::Version
|
119
|
-
version: 0.3.1
|
120
100
|
description:
|
121
101
|
email: james@jamesrobertson.eu
|
122
102
|
executables: []
|
@@ -124,6 +104,8 @@ extensions: []
|
|
124
104
|
extra_rdoc_files: []
|
125
105
|
files:
|
126
106
|
- lib/projectsimulator.rb
|
107
|
+
- lib/projectsimulator/controller.rb
|
108
|
+
- lib/projectsimulator/model.rb
|
127
109
|
homepage: https://github.com/jrobertson/projectsimulator
|
128
110
|
licenses:
|
129
111
|
- MIT
|
metadata.gz.sig
CHANGED
Binary file
|