remotedroid 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b788429e24f18de3a9237abe1fb9d8ba73ddb252edb59caa63b99fa9be986fad
4
+ data.tar.gz: 5257691864646241e82b92eeece3139c01ca743e1d4c13e49675f441fb5a734d
5
+ SHA512:
6
+ metadata.gz: dc5aee582390fe67b13e0e69876207292ab4b0892761bb55fea80826bbf5bf62515118decd47815d5ff00b28b2dd25f8179a74e3ddf2d4a02a6ee5c90c662711
7
+ data.tar.gz: 0c1fabdaa87e9907f4d79b1fb68285c1f6b7190393daffedfb362436628badfbf0f0c14a8da8d46869197d861f377ad02b7b0bae2681aa3a796d88f7346fd45b
Binary file
Binary file
@@ -0,0 +1,462 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # file: remotedroid.rb
4
+
5
+ require 'onedrb'
6
+ require 'easydom'
7
+ require 'app-routes'
8
+ require 'sps-sub'
9
+ require 'ruby-macrodroid'
10
+
11
+
12
+
13
+ RD_MACROS =<<EOF
14
+ m: Torch
15
+ t: webhook
16
+ a: Torch toggle
17
+
18
+ m: Toast
19
+ v: msg:
20
+ t: WebHook
21
+ a:
22
+ Popup Message
23
+ [lv=msg]
24
+
25
+ m: shake device
26
+ t: shake device
27
+ a: webhook
28
+ EOF
29
+
30
+ module RemoteDroid
31
+
32
+ class Model
33
+ include AppRoutes
34
+
35
+ def initialize(obj=nil, root: 'device1', debug: false)
36
+
37
+ super()
38
+ @root, @debug = root, debug
39
+ @location = nil
40
+
41
+ if obj then
42
+
43
+ s = obj.strip
44
+
45
+ puts 's: ' + s.inspect if @debug
46
+
47
+ if s[0] == '<' or s.lines[1][0..1] == ' ' then
48
+
49
+ puts 'before easydom' if @debug
50
+
51
+ s2 = if s.lines[1][0..1] == ' ' then
52
+
53
+ lines = s.lines.map do |line|
54
+ line.sub(/(\w+) +is +(\w+)$/) {|x| "#{$1} {switch: #{$2}}" }
55
+ end
56
+
57
+ lines.join
58
+
59
+ else
60
+ s
61
+ end
62
+
63
+ @ed = EasyDom.new(s2)
64
+ else
65
+ build(s, root: root)
66
+ end
67
+
68
+ end
69
+
70
+ end
71
+
72
+ def build(raw_requests, root: @root)
73
+
74
+ @ed = EasyDom.new(debug: false, root: root)
75
+ raw_requests.lines.each {|line| request(line) }
76
+
77
+ end
78
+
79
+
80
+ def get_thing(h)
81
+
82
+ h[:thing].gsub!(/ /,'_')
83
+
84
+ if not h.has_key? :location then
85
+ location = false
86
+ h[:location] = find_path(h[:thing])
87
+ else
88
+ location = true
89
+ end
90
+
91
+ puts 'h: ' + h.inspect if @debug
92
+
93
+ a = []
94
+ a += h[:location].split(/ /)
95
+ a << h[:thing]
96
+ status = a.inject(@ed) {|r,x| r.send(x)}.send(h[:action])
97
+
98
+ if location then
99
+ "The %s %s is %s." % [h[:location], h[:thing], status]
100
+ else
101
+ "%s is %s." % [h[:thing].capitalize, status]
102
+ end
103
+
104
+ end
105
+
106
+ # Object Property (op)
107
+ # Helpful for accessing properites in dot notation
108
+ # e.g. op.livingroom.light.switch = 'off'
109
+ #
110
+ def op()
111
+ @ed
112
+ end
113
+
114
+ def query(s)
115
+ @ed.e.element(s)
116
+ end
117
+
118
+ # request accepts a string in plain english
119
+ # e.g. request 'switch the livingroom light on'
120
+ #
121
+ def request(s)
122
+
123
+ params = {request: s}
124
+ requests(params)
125
+ h = find_request(s)
126
+
127
+ method(h.first[-1]).call(h).gsub(/_/,' ')
128
+
129
+ end
130
+
131
+ def set_thing(h)
132
+
133
+ h[:thing].gsub!(/ /,'_')
134
+ h[:location] = find_path(h[:thing]) unless h.has_key? :location
135
+
136
+ a = []
137
+ a += h[:location].split(/ /)
138
+ a << h[:thing]
139
+
140
+ a.inject(@ed) {|r,x| r.send(x)}.send(h[:action], h[:value])
141
+
142
+ end
143
+
144
+ def to_sliml(level: 0)
145
+
146
+ s = @ed.to_sliml
147
+
148
+ return s if level.to_i > 0
149
+
150
+ lines = s.lines.map do |line|
151
+
152
+ line.sub(/\{[^\}]+\}/) do |x|
153
+
154
+ a = x.scan(/\w+: +[^ ]+/)
155
+ if a.length == 1 and x[/switch:/] then
156
+
157
+ val = x[/(?<=switch: ) *["']([^"']+)/,1]
158
+ 'is ' + val
159
+ else
160
+ x
161
+ end
162
+
163
+ end
164
+ end
165
+
166
+ lines.join
167
+
168
+ end
169
+
170
+ def to_xml(options=nil)
171
+ @ed.xml(pretty: true).gsub(' style=\'\'','')
172
+ end
173
+
174
+ alias xml to_xml
175
+
176
+ # to_xml() is the preferred method
177
+
178
+ protected
179
+
180
+ def requests(params)
181
+
182
+ # e.g. switch the livingroom gas_fire off
183
+ #
184
+ get /(?:switch|turn) the ([^ ]+) +([^ ]+) +(on|off)$/ do |location, device, onoff|
185
+ {type: :set_thing, action: 'switch=', location: location, thing: device, value: onoff}
186
+ end
187
+
188
+ # e.g. switch the gas _fire off
189
+ #
190
+ get /(?:switch|turn) the ([^ ]+) +(on|off)$/ do |device, onoff|
191
+ {type: :set_thing, action: 'switch=', thing: device, value: onoff}
192
+ end
193
+
194
+ # e.g. is the livingroom gas_fire on?
195
+ #
196
+ get /is the ([^ ]+) +([^ ]+) +(?:on|off)\??$/ do |location, device|
197
+ {type: :get_thing, action: 'switch', location: location, thing: device}
198
+ end
199
+
200
+ # e.g. enable airplane mode
201
+ #
202
+ get /((?:dis|en)able) ([^$]+)$/ do |state, service|
203
+ {type: :set_thing, action: 'switch=', thing: service, value: state + 'd'}
204
+ end
205
+
206
+ # e.g. switch airplane mode off
207
+ #
208
+ get /switch (.*) (on|off)/ do |service, rawstate|
209
+
210
+ state = rawstate == 'on' ? 'enabled' : 'disabled'
211
+ {type: :set_thing, action: 'switch=', thing: service, value: state}
212
+
213
+ end
214
+
215
+ # e.g. is airplane mode enabed?
216
+ #
217
+ get /is (.*) +(?:(?:dis|en)abled)\??$/ do |service|
218
+ {type: :get_thing, action: 'switch', thing: service.gsub(/ /,'_')}
219
+ end
220
+
221
+ # e.g. is the gas_fire on?
222
+ #
223
+ get /is the ([^ ]+) +(?:on|off)\??$/ do |device|
224
+ location = find_path(device)
225
+ {type: :get_thing, action: 'switch', location: location, thing: device}
226
+ end
227
+
228
+ # e.g. fetch the livingroom temperature reading
229
+ #
230
+ get /fetch the ([^ ]+) +([^ ]+) +(?:reading)$/ do |location, device|
231
+ {type: :get_thing, action: 'reading', location: location, thing: device}
232
+ end
233
+
234
+ # e.g. fetch the temperature reading
235
+ #
236
+ get /fetch the ([^ ]+) +(?:reading)$/ do |device|
237
+ location = find_path(device)
238
+ {type: :get_thing, action: 'reading', location: location, thing: device}
239
+ end
240
+
241
+ end
242
+
243
+ private
244
+
245
+ def find_path(s)
246
+ puts 'find_path s: ' + s.inspect if @debug
247
+ found = query('//'+ s)
248
+ return unless found
249
+ a = found.backtrack.to_xpath.split('/')
250
+ a[1..-2].join(' ')
251
+ end
252
+
253
+ alias find_request run_route
254
+
255
+ end
256
+
257
+ class Controller
258
+
259
+ attr_reader :model, :control
260
+ attr_accessor :title, :macros
261
+
262
+ def initialize(mcs, model=MODEL, deviceid: nil, debug: false)
263
+
264
+ @debug = debug
265
+ @syslog = []
266
+
267
+ @control = Control.new(deviceid)
268
+ @macros = mcs.macros
269
+
270
+ if model then
271
+ @model = Model.new(model)
272
+ end
273
+
274
+ end
275
+
276
+ def export(s)
277
+ @macros = MacroDroid.new(s).macros
278
+ end
279
+
280
+ # Object Property (op)
281
+ # Helpful for accessing properites in dot notation
282
+ # e.g. op.livingroom.light.switch = 'off'
283
+ #
284
+ def op()
285
+ @model.op
286
+ end
287
+
288
+ def request(s)
289
+ @model.request s
290
+ end
291
+
292
+
293
+ def trigger(name, detail={time: Time.now})
294
+
295
+ macros = @macros.select do |macro|
296
+
297
+ puts 'macro: ' + macro.inspect if @debug
298
+
299
+ # fetch the associated properties from the model if possible and
300
+ # merge them into the detail.
301
+ #
302
+ valid_trigger = macro.match?(name, detail, @model.op)
303
+
304
+ puts 'valid_trigger: ' + valid_trigger.inspect if @debug
305
+
306
+ if valid_trigger then
307
+ @syslog << [Time.now, :trigger, name]
308
+ @syslog << [Time.now, :macro, macro.title]
309
+ end
310
+
311
+ valid_trigger
312
+
313
+ end
314
+
315
+ puts 'macros: ' + macros.inspect if @debug
316
+
317
+ macros.flat_map(&:run)
318
+ end
319
+
320
+ alias trigger_fired trigger
321
+
322
+ end
323
+
324
+ class Service
325
+ def initialize(callback)
326
+ @callback = callback
327
+ end
328
+ end
329
+
330
+ class Bluetooth
331
+ def enable()
332
+ end
333
+ end
334
+
335
+ class Toast < Service
336
+
337
+ def invoke()
338
+ @callback.call :toast
339
+ end
340
+
341
+ end
342
+
343
+ class Torch < Service
344
+
345
+ def toggle()
346
+ @callback.http_exec :torch
347
+ end
348
+
349
+ end
350
+
351
+ class Control
352
+
353
+ def initialize(dev=nil, deviceid: dev, remote_url: nil, debug: false)
354
+
355
+ @deviceid, @remote_url, @debug = deviceid, remote_url, debug
356
+ @torch = Torch.new(self)
357
+ end
358
+
359
+ def bluetooth()
360
+ @bluetooth
361
+ end
362
+
363
+ def http_exec(command, options={})
364
+
365
+ url = "https://trigger.macrodroid.com/%s/%s" % [@deviceid, command]
366
+ File.write '/tmp/foo.txt', 'url : ' + url.inspect #if @debug
367
+
368
+ if options and options.any? then
369
+ h = options
370
+ url += '?' + \
371
+ URI.escape(h.map {|key,value| "%s=%s" % [key, value]}.join('&'))
372
+ end
373
+
374
+ s = open(url).read
375
+
376
+ end
377
+
378
+ def toast(options={})
379
+ http_exec 'toast', options
380
+ end
381
+
382
+ def torch()
383
+ @torch
384
+ end
385
+
386
+ def write(s)
387
+
388
+ MacroDroid.new(RD_MACROS, deviceid: @deviceid,
389
+ remote_url: @remote_url, debug: @debug).export s
390
+
391
+ end
392
+
393
+ alias export write
394
+
395
+ def method_missing(method_name, *args)
396
+ http_exec(method_name, args.first)
397
+ end
398
+
399
+ end
400
+
401
+ class Server
402
+
403
+ def initialize(s, drb_host: '127.0.0.1', deviceid: nil)
404
+
405
+ md = MacroDroid.new(s)
406
+ rdc = RemoteDroid::Controller.new(md, deviceid: deviceid)
407
+ @drb = OneDrb::Server.new host: drb_host, port: '5777', obj: rdc
408
+
409
+ end
410
+
411
+ def start
412
+ @drb.start
413
+ end
414
+
415
+ end
416
+
417
+ class TriggerSubscriber < SPSSub
418
+
419
+ def initialize(host: 'sps.home', drb_host: '127.0.0.1')
420
+ @remote = OneDrb::Client.new host: drb_host, port: '5777'
421
+ super(host: host)
422
+ end
423
+
424
+ def subscribe(topic: 'macrodroid/trigger')
425
+
426
+ super(topic: topic) do |msg|
427
+
428
+ trigger, json = msg.split(/:\s+/,2)
429
+ a = @remote.trigger_fired trigger.to_sym,
430
+ JSON.parse(json, symbolize_names: true)
431
+ a.each {|msg| self.notice 'macrodroid/action: ' + msg }
432
+
433
+ end
434
+ end
435
+
436
+ end
437
+
438
+ class ActionSubscriber < SPSSub
439
+
440
+ def initialize(host: 'sps.home', drb_host: '127.0.0.1')
441
+ @remote = OneDrb::Client.new host: drb_host, port: '5777'
442
+ super(host: host)
443
+ end
444
+
445
+ def subscribe(topic: 'macrodroid/action')
446
+
447
+ super(topic: topic) do |msg|
448
+
449
+ context, json = msg.split(/:\s+/,2)
450
+ category, action = context.split('/',2)
451
+ @remote.control.method(action.to_sym)\
452
+ .call(JSON.parse(json, symbolize_names: true))
453
+
454
+ end
455
+
456
+ end
457
+
458
+ end
459
+
460
+
461
+ end
462
+
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: remotedroid
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - James Robertson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain:
11
+ - |
12
+ -----BEGIN CERTIFICATE-----
13
+ MIIEXjCCAsagAwIBAgIBATANBgkqhkiG9w0BAQsFADAsMSowKAYDVQQDDCFnZW1t
14
+ YXN0ZXIvREM9amFtZXNyb2JlcnRzb24vREM9ZXUwHhcNMjAxMDAyMjI0OTAyWhcN
15
+ MjExMDAyMjI0OTAyWjAsMSowKAYDVQQDDCFnZW1tYXN0ZXIvREM9amFtZXNyb2Jl
16
+ cnRzb24vREM9ZXUwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDq6tMR
17
+ goUkxOiD/E2J2UU4fyOz3KcaiZhm6aGXLypRQkjsVT9ZbZUFJP6rL0vudHuw7TGU
18
+ hnX5/SRRoHAcGn7j+mznEqJdIlVrtK5+pQ1h2/cnNMZrKbXhkfLLo/2Pl8JCw5gY
19
+ 8IKQCmf6SDdrxt/MVxyuXCjoVq0QaHgJOYTLuoSobYV1JtXiHrXvDkjDBeugWodb
20
+ +J/AejxIEEzj4VCD4T8eGyyhqx/tDDokfHRCwD69K2CL1TG+v11NN+kwKAw1B5uQ
21
+ juvPpNMMmvdGIRClLLVYS2GZ1PY3eAkt4D682Ct/SIbBs/WZPBQ7V4u1q0xsFskO
22
+ +kZDM3FUpjkjFIAKJQojxMJBgxsBIOFyW7mwUASwYikd47Hptu4AoxO9cyla2ZJ8
23
+ XE49SU79LZzW6xNqYCkuRESjV+V65xyIpRymT/vWFRzfVafjDtxsvBu8zuQvB55E
24
+ EJhU2iK7IxEMD9vK7Vq7opgqgR3XokiNwWuNmt/ak3mcnUcgFBkiNAlO3RsCAwEA
25
+ AaOBijCBhzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUNXjM9pAU
26
+ /zrKDRZKV+SleokUpk8wJgYDVR0RBB8wHYEbZ2VtbWFzdGVyQGphbWVzcm9iZXJ0
27
+ c29uLmV1MCYGA1UdEgQfMB2BG2dlbW1hc3RlckBqYW1lc3JvYmVydHNvbi5ldTAN
28
+ BgkqhkiG9w0BAQsFAAOCAYEAlTd2THoLR/qMTRE/P0KMRYH96GbPyLOH29TOfQ+7
29
+ DSEtxS9dMbl0IQ7jSMkK8/pwYF/OLO11862CeJFh+kKIYEOnrhp8T2kn2HyXNZen
30
+ 0zklZgb3A3NGdK3CKZG1RkMHH9Hu6vBiivR+zLYCATuw842dcLQG/UR/cMGGby6k
31
+ yflohsIKH2MmfQr2fs0h5m04AcvD1Zqc+z1SF6KElcLws40xw2Qfcl2GRK8rcClW
32
+ njOBpwPRQJLE6sio0Kq5VkwuAPbJ72WKK986mwv9bnGWqhfIvz+ZCpa8kKNjk2Z+
33
+ HMPueoY9XP6X0MgDiqpvy9cECNn93LU7+iX1hBu3ALnwSsW/1gWip/So8kXkK/D0
34
+ Jr6sGap//zpHcpzbBI/H5if8VFSIL5nivEX4dUqzIZo5M0ngeOsFf2Ka6PHYSPsZ
35
+ io3WPRDjULC924M5S8wbrus31v2AUjqFBPvmHr7caf/VHErWypV482xcDhWt1eif
36
+ 0G2k2ptozXcBS9odsqGUTb5N
37
+ -----END CERTIFICATE-----
38
+ date: 2020-10-02 00:00:00.000000000 Z
39
+ dependencies:
40
+ - !ruby/object:Gem::Dependency
41
+ name: onedrb
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 0.1.0
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '0.1'
50
+ type: :runtime
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 0.1.0
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '0.1'
60
+ - !ruby/object:Gem::Dependency
61
+ name: easydom
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '0.2'
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: 0.2.1
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '0.2'
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: 0.2.1
80
+ - !ruby/object:Gem::Dependency
81
+ name: sps-sub
82
+ requirement: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - "~>"
85
+ - !ruby/object:Gem::Version
86
+ version: '0.3'
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 0.3.7
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.3'
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: 0.3.7
100
+ - !ruby/object:Gem::Dependency
101
+ name: ruby-macrodroid
102
+ requirement: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - "~>"
105
+ - !ruby/object:Gem::Version
106
+ version: '0.8'
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: 0.8.12
110
+ type: :runtime
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '0.8'
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: 0.8.12
120
+ description:
121
+ email: james@jamesrobertson.eu
122
+ executables: []
123
+ extensions: []
124
+ extra_rdoc_files: []
125
+ files:
126
+ - lib/remotedroid.rb
127
+ homepage: https://github.com/jrobertson/remotedroid
128
+ licenses:
129
+ - MIT
130
+ metadata: {}
131
+ post_install_message:
132
+ rdoc_options: []
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ requirements: []
146
+ rubygems_version: 3.0.3
147
+ signing_key:
148
+ specification_version: 4
149
+ summary: A Ruby-MacroDroid related experiment into triggering macros and responding
150
+ to actions remotely.
151
+ test_files: []
Binary file