remotedroid 0.1.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.
@@ -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