ruby-macrodroid 0.5.2 → 0.7.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b105d3fc8c1fe8e43ad4ea6b8b9018a26e376949c94c1623a11540ba8d5850c
4
- data.tar.gz: 22b28e9b200b82fe6c048a0781845c032502c9b24e8ff566e455b8f2193d97f1
3
+ metadata.gz: cd79e55449071a3fd0ed864f58fc606dedd69059413876c7466cb071f651fef4
4
+ data.tar.gz: 70dbc1fd844ff449d0ef1ca680e9b47e5233ff82148ab93063b65d9a87e5f70c
5
5
  SHA512:
6
- metadata.gz: c09be84265a4961e278e378c346a6152924848f3cf2a347aa3c52c2b87dab6711092c07be1dbb3820576e5779e73818c96b34958c1e2044edf29b24a973c5e35
7
- data.tar.gz: 333fc854bc4ce0ff61b1921a971835a1f4d1f75d65ae8608de0990c710f5060ecc7bfedb0468baba7f575ca73b3df96e9036d93a64339ba0e08a73d9c9612ecc
6
+ metadata.gz: 1912e55be98c1236a2df6f17e5e19aa1de75e89854ca3be343821133e841656bb45c116e195237a313726b590c31bb7c898f8cf54f868f37c26f3dd9b11f4694
7
+ data.tar.gz: fdc3adcb802125e4346bbed8fc089e57a303d2f5624e20fe6b597b03a594ae50ca388b13c54bcb43da3bfb265e6fabc1b46f00119a48f199645ee5d8bdcf0c24
Binary file
data.tar.gz.sig CHANGED
Binary file
@@ -2,9 +2,121 @@
2
2
 
3
3
  # file: ruby-macrodroid.rb
4
4
 
5
- require 'uuid'
5
+ # This file contains the following classes:
6
+ #
7
+ # ## Nlp classes
8
+ #
9
+ # TriggersNlp ActionsNlp ConstraintsNlp
10
+ #
11
+ #
12
+ # ## Macro class
13
+ #
14
+ # Macro
15
+ #
16
+ #
17
+ # ## Error class
18
+ #
19
+ # MacroDroidError
20
+ #
21
+ #
22
+ # ## Droid class
23
+ #
24
+ # MacroDroid
25
+ #
26
+ #
27
+ # ## Map class
28
+ #
29
+ # GeofenceMap
30
+ #
31
+ #
32
+ # ## Object class
33
+ #
34
+ # MacroObject
35
+ #
36
+ #
37
+ # ## Trigger classes
38
+ #
39
+ # Trigger WebHookTrigger WifiConnectionTrigger
40
+ # ApplicationInstalledRemovedTrigger ApplicationLaunchedTrigger
41
+ # BatteryLevelTrigger BatteryTemperatureTrigger PowerButtonToggleTrigger
42
+ # ExternalPowerTrigger CallActiveTrigger IncomingCallTrigger
43
+ # OutgoingCallTrigger CallEndedTrigger CallMissedTrigger IncomingSMSTrigger
44
+ # WebHookTrigger WifiConnectionTrigger BluetoothTrigger HeadphonesTrigger
45
+ # SignalOnOffTrigger UsbDeviceConnectionTrigger WifiSSIDTrigger
46
+ # CalendarTrigger TimerTrigger StopwatchTrigger DayTrigger
47
+ # RegularIntervalTrigger DeviceEventsTrigger AirplaneModeTrigger
48
+ # AutoSyncChangeTrigger DayDreamTrigger DockTrigger FailedLoginTrigger
49
+ # GPSEnabledTrigger MusicPlayingTrigger DeviceUnlockedTrigger
50
+ # AutoRotateChangeTrigger ClipboardChangeTrigger BootTrigger
51
+ # IntentReceivedTrigger NotificationTrigger ScreenOnOffTrigger
52
+ # SilentModeTrigger WeatherTrigger GeofenceTrigger SunriseSunsetTrigger
53
+ # SensorsTrigger ActivityRecognitionTrigger ProximityTrigger
54
+ # ShakeDeviceTrigger FlipDeviceTrigger OrientationTrigger
55
+ # FloatingButtonTrigger ShortcutTrigger VolumeButtonTrigger
56
+ # MediaButtonPressedTrigger SwipeTrigger
57
+ #
58
+ #
59
+ # ## Action classes
60
+ #
61
+ # Action LocationAction ShareLocationAction ApplicationAction
62
+ # LaunchActivityAction KillBackgroundAppAction OpenWebPageAction CameraAction
63
+ # UploadPhotoAction TakePictureAction ConnectivityAction SetWifiAction
64
+ # SetBluetoothAction SetBluetoothAction SendIntentAction DateTimeAction
65
+ # SetAlarmClockAction StopWatchAction SayTimeAction DeviceAction
66
+ # AndroidShortcutsAction ClipboardAction PressBackAction SpeakTextAction
67
+ # UIInteractionAction VoiceSearchAction DeviceSettingsAction
68
+ # ExpandCollapseStatusBarAction LaunchHomeScreenAction CameraFlashLightAction
69
+ # VibrateAction SetAutoRotateAction DayDreamAction SetKeyboardAction
70
+ # SetKeyguardAction CarModeAction ChangeKeyboardAction SetWallpaperAction
71
+ # FileAction OpenFileAction LocationAction ForceLocationUpdateAction
72
+ # ShareLocationAction SetLocationUpdateRateAction LoggingAction
73
+ # AddCalendarEntryAction LogAction ClearLogAction MediaAction
74
+ # RecordMicrophoneAction PlaySoundAction MessagingAction SendEmailAction
75
+ # SendSMSAction UDPCommandAction NotificationsAction ClearNotificationsAction
76
+ # MessageDialogAction AllowLEDNotificationLightAction
77
+ # SetNotificationSoundAction SetNotificationSoundAction
78
+ # SetNotificationSoundAction NotificationAction ToastAction PhoneAction
79
+ # AnswerCallAction ClearCallLogAction OpenCallLogAction RejectCallAction
80
+ # MakeCallAction SetRingtoneAction ScreenAction SetBrightnessAction
81
+ # ForceScreenRotationAction ScreenOnAction DimScreenAction KeepAwakeAction
82
+ # SetScreenTimeoutAction VolumeAction SilentModeVibrateOffAction
83
+ # SetVibrateAction VolumeIncrementDecrementAction SpeakerPhoneAction
84
+ # SetVolumeAction
85
+ #
86
+ #
87
+ # ## Constraint classes
88
+ #
89
+ # Constraint TimeOfDayConstraint BatteryLevelConstraint
90
+ # BatterySaverStateConstraint BatteryTemperatureConstraint
91
+ # ExternalPowerConstraint BluetoothConstraint GPSEnabledConstraint
92
+ # LocationModeConstraint SignalOnOffConstraint WifiConstraint
93
+ # CellTowerConstraint IsRoamingConstraint DataOnOffConstraint
94
+ # WifiHotSpotConstraint CalendarConstraint DayOfWeekConstraint
95
+ # TimeOfDayConstraint DayOfMonthConstraint MonthOfYearConstraint
96
+ # SunsetSunriseConstraint AirplaneModeConstraint AutoRotateConstraint
97
+ # DeviceLockedConstraint RoamingOnOffConstraint TimeSinceBootConstraint
98
+ # AutoSyncConstraint NFCStateConstraint IsRootedConstraint VpnConstraint
99
+ # MacroEnabledConstraint ModeConstraint TriggerThatInvokedConstraint
100
+ # LastRunTimeConstraint HeadphonesConnectionConstraint MusicActiveConstraint
101
+ # NotificationPresentConstraint PriorityModeConstraint
102
+ # NotificationVolumeConstraint InCallConstraint PhoneRingingConstraint
103
+ # BrightnessConstraint VolumeConstraint SpeakerPhoneConstraint
104
+ # DarkThemeConstraint ScreenOnOffConstraint VolumeLevelConstraint
105
+ # FaceUpDownConstraint LightLevelConstraint DeviceOrientationConstraint
106
+ # ProximitySensorConstraint
107
+
108
+
109
+
6
110
  require 'yaml'
7
- require 'rxfhelper'
111
+ require 'rowx'
112
+ require 'uuid'
113
+ #require 'glw'
114
+ #require 'geozone'
115
+ require 'subunit'
116
+ #require 'rxfhelper'
117
+ require 'requestor'
118
+ eval Requestor.read('http://a0.jamesrobertson.eu/rorb/r/ruby'){|x| x.require 'rxfhelper' }
119
+
8
120
  require 'chronic_cron'
9
121
 
10
122
 
@@ -60,7 +172,25 @@ class TriggersNlp
60
172
  get /^failed_login?$/i do
61
173
  [FailedLoginTrigger, {}]
62
174
  end
63
-
175
+
176
+ get /^Geofence (Entry|Exit) \(([^\)]+)/i do |direction, name|
177
+ enter_area = direction.downcase.to_sym == :entry
178
+ [GeofenceTrigger, {name: name, enter_area: enter_area}]
179
+ end
180
+
181
+ get /^location (entered|exited) \(([^\)]+)/i do |direction, name|
182
+ enter_area = direction.downcase.to_sym == :entered
183
+ [GeofenceTrigger, {name: name, enter_area: enter_area}]
184
+ end
185
+
186
+ # eg. Proximity Sensor (Near)
187
+ #
188
+ get /^Proximity Sensor \(([^\)]+)\)/i do |distance|
189
+
190
+ [ProximityTrigger, {distance: distance}]
191
+ end
192
+
193
+
64
194
 
65
195
  end
66
196
 
@@ -81,14 +211,17 @@ class ActionsNlp
81
211
 
82
212
  def actions(params)
83
213
 
214
+ # e.g. message popup: hello world!
84
215
  get /^message popup: (.*)/i do |msg|
85
216
  [ToastAction, {msg: msg}]
86
217
  end
87
218
 
219
+ # e.g. Popup Message 'hello world!'
88
220
  get /^Popup[ _]Message ['"]([^'"]+)/i do |msg|
89
221
  [ToastAction, {msg: msg}]
90
222
  end
91
223
 
224
+ # e.g. say current time
92
225
  get /^say current[ _]time/i do
93
226
  [SayTimeAction, {}]
94
227
  end
@@ -105,6 +238,78 @@ class ActionsNlp
105
238
  get /^take_picture/i do
106
239
  [TakePictureAction, {}]
107
240
  end
241
+
242
+ # e.g. Display Notification: Hi there: This is the body of the message
243
+ get /^Display Notification: ([^:]+): [^$]+$/i do |subject, text|
244
+ [NotificationAction, {subject: subject, text: text}]
245
+ end
246
+
247
+
248
+ # e.g. Enable Wifi
249
+ get /^(Enable|Disable) Wifi$/i do |raw_state|
250
+
251
+ state = raw_state.downcase.to_sym == :enable ? 0 : 1
252
+ [SetWifiAction, {state: state}]
253
+
254
+ end
255
+
256
+ # e.g. Play: Altair
257
+ get /^Play: (.*)$/i do |name|
258
+
259
+ [PlaySoundAction, {file_path: name}]
260
+
261
+ end
262
+
263
+ # e.g. Launch Settings
264
+ get /^Launch (.*)$/i do |application|
265
+
266
+ h = {
267
+ application_name: application,
268
+ package_to_launch: 'com.android.' + application.downcase
269
+ }
270
+ [LaunchActivityAction, h]
271
+
272
+ end
273
+
274
+ # e.g. HTTP GET http://someurl.com/something
275
+ get /^HTTP GET ([^$]+)$/i do |url|
276
+
277
+ [OpenWebPageAction, url_to_open: url]
278
+
279
+ end
280
+
281
+ # e.g. webhook entered_kitchen
282
+ #
283
+ get /webhook|HTTP GET/i do
284
+ [OpenWebPageAction, {}]
285
+ end
286
+
287
+ #a: Keep Device Awake Screen On Until Disabled
288
+ #
289
+ get /Keep Device Awake Screen On Until Disabled/i do
290
+ [KeepAwakeAction, {enabled: true, permanent: true, screen_option: 0}]
291
+ end
292
+
293
+
294
+ #a: Keep Device Awake Screen On 1h 1m 1s
295
+ #
296
+ get /Keep Device Awake Screen On ([^$]+)/i do |duration|
297
+
298
+ a = duration.split.map(&:to_i)
299
+ secs = Subunit.new(units={minutes:60, hours:60, seconds: 60}, a).to_i
300
+
301
+ h = {
302
+ permanent: true, screen_option: 0, seconds_to_stay_awake_for: secs
303
+ }
304
+ [KeepAwakeAction, h]
305
+ end
306
+
307
+ #a: Disable Keep Awake
308
+ #
309
+ get /Disable Keep Awake/i do
310
+ [KeepAwakeAction, {enabled: false, screen_option: 0}]
311
+ end
312
+
108
313
 
109
314
  end
110
315
 
@@ -195,9 +400,9 @@ class Macro
195
400
  attr_reader :local_variables, :triggers, :actions, :constraints, :guid
196
401
  attr_accessor :title, :description
197
402
 
198
- def initialize(name=nil, debug: false)
403
+ def initialize(name=nil, geofences: geofences, debug: false)
199
404
 
200
- @title, @debug = name, debug
405
+ @title, @geofences, @debug = name, geofences, debug
201
406
 
202
407
  puts 'inside Macro#initialize' if @debug
203
408
 
@@ -288,19 +493,20 @@ class Macro
288
493
  end
289
494
 
290
495
  def import_xml(node)
291
-
496
+
292
497
  if @debug then
293
498
  puts 'inside Macro#import_xml'
294
499
  puts 'node: ' + node.xml.inspect
295
500
  end
296
501
 
297
- @title = node.attributes[:name]
298
- @description = node.attributes[:description]
299
-
300
502
  if node.element('triggers') then
301
-
503
+
302
504
  # level 2
303
505
 
506
+ @title = node.attributes[:name]
507
+ @description = node.attributes[:description]
508
+
509
+
304
510
  # get all the triggers
305
511
  @triggers = node.xpath('triggers/*').map do |e|
306
512
 
@@ -336,6 +542,12 @@ class Macro
336
542
 
337
543
  # Level 1
338
544
 
545
+ puts 'import_xml: inside level 1' if @debug
546
+
547
+ @title = node.text('macro') || node.attributes[:name]
548
+
549
+ #@description = node.attributes[:description]
550
+
339
551
  tp = TriggersNlp.new
340
552
 
341
553
  @triggers = node.xpath('trigger').map do |e|
@@ -345,7 +557,11 @@ class Macro
345
557
  puts 'found trigger ' + r.inspect if @debug
346
558
 
347
559
  if r then
348
- r[0].new(r[1])
560
+ if r[0] == GeofenceTrigger then
561
+ GeofenceTrigger.new(r[1], geofences: @geofences)
562
+ else
563
+ r[0].new(r[1])
564
+ end
349
565
  end
350
566
 
351
567
  end
@@ -354,11 +570,21 @@ class Macro
354
570
 
355
571
  @actions = node.xpath('action').map do |e|
356
572
 
573
+ puts 'action e: ' + e.xml.inspect if @debug
357
574
  r = ap.find_action e.text
358
575
  puts 'found action ' + r.inspect if @debug
359
576
 
360
577
  if r then
361
- r[0].new(r[1])
578
+
579
+ a = e.xpath('item/*')
580
+
581
+ h = if a.any? then
582
+ a.map {|node| [node.name.to_sym, node.text.to_s]}.to_h
583
+ else
584
+ {}
585
+ end
586
+
587
+ r[0].new(r[1].merge(h))
362
588
  end
363
589
 
364
590
  end
@@ -405,14 +631,23 @@ class Macro
405
631
 
406
632
  end
407
633
 
634
+ # invokes the actions
635
+ #
408
636
  def run()
409
637
  @actions.map(&:invoke)
410
638
  end
639
+
640
+ # prepares the environment in order for triggers to test fire successfully
641
+ # Used for testing
642
+ #
643
+ def set_env()
644
+ @triggers.each(&:set_env)
645
+ end
411
646
 
412
647
  def to_pc()
413
648
 
414
- heading = '# ' + @title + "\n"
415
- heading += '# ' + @description if @description
649
+ heading = '# ' + @title
650
+ heading += '\n# ' + @description if @description
416
651
  condition = @triggers.first.to_pc
417
652
  actions = @actions.map(&:to_pc).join("\n")
418
653
 
@@ -426,14 +661,71 @@ EOF
426
661
  end
427
662
 
428
663
  def to_s()
664
+
665
+ indent = 0
666
+ actions = @actions.map do |x|
667
+
668
+ s = x.to_s
669
+
670
+ r = if indent <= 0 then
671
+
672
+ "a: %s" % s
673
+
674
+ elsif indent > 0
675
+
676
+ if s =~ /^Else/ then
677
+ (' ' * (indent-1)) + "%s" % s
678
+ elsif s =~ /^End/
679
+ indent -= 1
680
+ (' ' * indent) + "%s" % s
681
+ else
682
+ (' ' * indent) + "%s" % s
683
+ end
684
+
685
+ end
686
+
687
+ if s =~ /^If/i then
688
+ if indent < 1 then
689
+ r = "a:\n %s" % s
690
+ indent += 1
691
+ else
692
+ r = (' ' * indent) + "%s" % s
693
+ end
694
+
695
+ indent += 1
696
+ end
697
+
698
+ r
699
+
700
+ end.join("\n")
701
+
429
702
  a = [
430
703
  'm: ' + @title,
431
704
  @triggers.map {|x| "t: %s" % x}.join("\n"),
432
- @actions.map {|x| "a: %s" % x}.join("\n"),
705
+ actions,
433
706
  @constraints.map {|x| "a: %s" % x}.join("\n")
434
707
  ]
435
- a.insert(1, 'd: ' + @description) if @description
436
- a.join("\n")
708
+
709
+ if @description and @description.length >= 1 then
710
+ a.insert(1, 'd: ' + @description.gsub(/\n/,"\n "))
711
+ end
712
+
713
+ a.join("\n") + "\n"
714
+
715
+ end
716
+
717
+ def to_summary()
718
+
719
+ a = [
720
+ 'm: ' + @title,
721
+ 't: ' + @triggers.map(&:to_summary).join(", "),
722
+ 'a: ' + @actions.map(&:to_summary).join(", "),
723
+ ]
724
+
725
+ a << 'c: ' + @constraints.map(&:to_summary).join(", ") if @constraints.any?
726
+
727
+ a.join("\n") + "\n"
728
+
437
729
  end
438
730
 
439
731
  private
@@ -446,7 +738,15 @@ EOF
446
738
 
447
739
  puts ('inside object h:' + h.inspect).debug if @debug
448
740
  klass = Object.const_get h[:class_type]
449
- klass.new h
741
+ puts klass.inspect.highlight if $debug
742
+
743
+ if klass == GeofenceTrigger then
744
+ puts 'GeofenceTrigger found'.highlight if $debug
745
+ GeofenceTrigger.new(h, geofences: @geofences)
746
+ else
747
+ klass.new h
748
+ end
749
+
450
750
  end
451
751
 
452
752
  end
@@ -459,12 +759,14 @@ class MacroDroid
459
759
  using ColouredText
460
760
  using Params
461
761
 
462
- attr_reader :macros
762
+ attr_reader :macros, :geofences, :yaml
463
763
 
464
764
  def initialize(obj=nil, debug: false)
465
765
 
466
766
  @debug = debug
467
767
 
768
+ @geofences = {}
769
+
468
770
  if obj then
469
771
 
470
772
  raw_s, _ = RXFHelper.read(obj)
@@ -481,18 +783,40 @@ class MacroDroid
481
783
  @h = build_h
482
784
 
483
785
  else
786
+
787
+ puts 's: ' + s.inspect if @debug
484
788
 
485
- xml = if s =~ /^m: / then
486
- text_to_xml(s)
789
+ if s =~ /m(?:acro)?:\s/ then
790
+
791
+ puts 'before RowX.new' if @debug
792
+
793
+ s2 = s.gsub(/^g:/,'geofence:').gsub(/^m:/,'macro:')\
794
+ .gsub(/^t:/,'trigger:').gsub(/^a:/,'action:')\
795
+ .gsub(/^c:/,'constraint:').gsub(/^#.*/,'')
796
+
797
+ raw_macros, raw_geofences = s2.split(/(?=^macro:)/,2).reverse
798
+
799
+ if raw_geofences then
800
+
801
+ geoxml = RowX.new(raw_geofences).to_xml
802
+
803
+ geodoc = Rexle.new(geoxml)
804
+ geofences = geodoc.root.xpath('item/geofence')
805
+ @geofences = fetch_geofences(geofences) if geofences.any?
806
+
807
+ end
808
+
809
+ xml = RowX.new(raw_macros).to_xml
810
+ import_rowxml(xml)
811
+
487
812
  elsif s =~ /^# /
488
- pc_to_xml(s)
813
+ xml = pc_to_xml(s)
814
+ import_xml(xml)
489
815
  else
490
816
  raise MacroDroidError, 'invalid input'
491
817
  end
492
- import_xml(xml)
493
- @h = build_h
494
818
 
495
-
819
+ @h = build_h
496
820
 
497
821
  end
498
822
 
@@ -545,18 +869,95 @@ class MacroDroid
545
869
 
546
870
  alias to_json export_json
547
871
 
872
+
873
+ def to_h()
874
+
875
+ h = {
876
+ geofence_data: {
877
+ geofence_map: @geofences.map {|key, value| [key, value.to_h] }.to_h
878
+ },
879
+ macro_list: @macros.map(&:to_h)
880
+ }
881
+ @h.merge(h).to_camel_case
882
+
883
+ end
884
+
885
+ # returns pseudocode
886
+ #
887
+ def to_pc()
888
+ @macros.map(&:to_pc).join("\n\n")
889
+ end
890
+
891
+ def to_s()
892
+
893
+ lines = []
894
+
895
+ if @geofences.any? then
896
+ lines << @geofences.map {|_, value| 'g: ' + value.to_s}.join("\n\n") + "\n"
897
+ end
898
+
899
+ lines << @macros.map(&:to_s).join("\n")
900
+ lines.join("\n")
901
+
902
+ end
903
+
904
+ def to_summary()
905
+ @macros.map(&:to_summary).join("\n")
906
+ end
907
+
908
+ private
909
+
910
+ def fetch_geofences(nodes)
911
+
912
+ nodes.map do |e|
913
+
914
+ name = e.text.to_s.strip
915
+ item = e.element('item')
916
+ coordinates = item.text('coordinates')
917
+ latitude, longitude = coordinates.split(/, */,2)
918
+ radius = item.text('radius')
919
+
920
+ id = UUID.new.generate
921
+
922
+ h = {
923
+ name: name,
924
+ longitude: longitude,
925
+ latitude: latitude,
926
+ radius: radius,
927
+ id: id
928
+ }
929
+
930
+ [id.to_sym, GeofenceMap.new(h)]
931
+
932
+ end.to_h
933
+
934
+ end
935
+
548
936
  def import_json(s)
549
937
 
550
938
  h = JSON.parse(s, symbolize_names: true)
551
939
  puts 'json_to_yaml: ' + h.to_yaml if @debug
940
+ @yaml = h.to_yaml # helpful for debugging and testing
552
941
 
553
942
  @h = h.to_snake_case
554
943
  puts ('@h: ' + @h.inspect).debug if @debug
555
-
944
+
945
+
946
+ # fetch the geofence data
947
+ if @h[:geofence_data] then
948
+
949
+ @geofences = @h[:geofence_data][:geofence_map].map do |id, properties|
950
+ [id, GeofenceMap.new(properties)]
951
+ end.to_h
952
+
953
+ end
954
+
556
955
  @macros = @h[:macro_list].map do |macro|
557
956
 
558
957
  puts ('macro: ' + macro.inspect).debug if @debug
559
- m = Macro.new(debug: @debug)
958
+ # puts '@geofences: ' + @geofences.inspect if @debug
959
+
960
+ m = Macro.new(geofences: @geofences.map(&:last), debug: @debug )
560
961
  m.import_h(macro)
561
962
  m
562
963
 
@@ -566,9 +967,30 @@ class MacroDroid
566
967
 
567
968
  end
568
969
 
970
+ def import_rowxml(raws)
971
+
972
+ s = RXFHelper.read(raws).first
973
+ puts 's: ' + s.inspect if @debug
974
+ doc = Rexle.new(s)
975
+ puts 'after doc' if @debug
976
+ puts 'import_rowxml: @geofences: ' + @geofences.inspect if @debug
977
+ geofences = @geofences
978
+
979
+ @macros = doc.root.xpath('item').map do |node|
980
+ puts ('geofences: ' + geofences.inspect).highlight if @debug
981
+ Macro.new(geofences: geofences.map(&:last), debug: @debug).import_xml(node)
982
+
983
+ end
984
+
985
+ end
986
+
569
987
  def import_xml(raws)
570
988
 
571
- puts 'raws: ' + raws.inspect if @debug
989
+ if @debug then
990
+ puts 'inside import_xml'
991
+
992
+ puts 'raws: ' + raws.inspect
993
+ end
572
994
  s = RXFHelper.read(raws).first
573
995
  puts 's: ' + s.inspect if @debug
574
996
  doc = Rexle.new(s)
@@ -576,14 +998,10 @@ class MacroDroid
576
998
  if @debug then
577
999
  puts 'doc: ' + doc.root.xml
578
1000
  end
579
-
580
- debug = @debug
581
-
1001
+
582
1002
  @macros = doc.root.xpath('macro').map do |node|
583
1003
 
584
- macro = Macro.new @title, debug: debug
585
- macro.import_xml(node)
586
- macro
1004
+ Macro.new(geofences: @geofences.map(&:last), debug: @debug).import_xml(node)
587
1005
 
588
1006
  end
589
1007
  end
@@ -608,51 +1026,40 @@ class MacroDroid
608
1026
  doc = Rexle.new([:macros, {}, '', *macros])
609
1027
  doc.root.xml pretty: true
610
1028
 
611
- end
612
-
613
- def text_to_xml(s)
614
-
615
- a = s.split(/.*(?=^m:)/); a.shift
616
- a.map!(&:chomp)
617
-
618
- macros = a.map do |x|
619
-
620
- lines = x.lines
621
- puts 'lines: ' + lines.inspect if @debug
622
-
623
- name = lines.shift[/^m: +(.*)/,1]
624
- h = {t: [], a: [], c: []}
625
-
626
- lines.each {|line| h[line[0].to_sym] << line[/^\w: +(.*)/,1] }
627
- triggers = h[:t].map {|text| [:trigger, {}, text]}
628
- actions = h[:a].map {|text| [:action, {}, text]}
629
- constraints = h[:c].map {|text| [:constraint, {}, text]}
630
-
631
- [:macro, {name: name},'', *triggers, *actions, *constraints]
1029
+ end
632
1030
 
633
- end
1031
+ end
634
1032
 
635
- doc = Rexle.new([:macros, {}, '', *macros])
636
- doc.root.xml pretty: true
1033
+ class GeofenceMap
1034
+
1035
+ attr_accessor :name, :longitude, :latitude, :radius, :id
1036
+
1037
+ def initialize(id: '', longitude: '', latitude: '', name: '', radius: '')
1038
+
1039
+ @id, @latitude, @longitude, @name, @radius = id, latitude, \
1040
+ longitude, name, radius
637
1041
 
638
1042
  end
639
-
1043
+
640
1044
  def to_h()
641
-
642
- @h.merge(macro_list: @macros.map(&:to_h)).to_camel_case
643
-
1045
+
1046
+ {
1047
+ id: @id,
1048
+ longitude: @longitude,
1049
+ latitude: @latitude,
1050
+ name: @name,
1051
+ radius: @radius
1052
+ }
1053
+
644
1054
  end
645
1055
 
646
- # returns pseudocode
647
- #
648
- def to_pc()
649
- @macros.map(&:to_pc).join("\n\n")
650
- end
651
-
652
1056
  def to_s()
653
- @macros.map(&:to_s).join("\n")
1057
+
1058
+ coordinates = "%s, %s" % [@longitude, @latitude]
1059
+ "%s\n coordinates: %s\n radius: %s" % [@name, coordinates, @radius]
1060
+
654
1061
  end
655
-
1062
+
656
1063
  end
657
1064
 
658
1065
  class MacroObject
@@ -663,6 +1070,8 @@ class MacroObject
663
1070
 
664
1071
  def initialize(h={})
665
1072
 
1073
+ $env ||= {}
1074
+
666
1075
  @h = {constraint_list: [], is_or_condition: false,
667
1076
  is_disabled: false}.merge(h)
668
1077
  @list = []
@@ -691,6 +1100,12 @@ class MacroObject
691
1100
  h2.merge('m_classType' => self.class.to_s)
692
1101
 
693
1102
  end
1103
+
1104
+ def to_s()
1105
+ "#<%s %s>" % [self.class, @h.inspect]
1106
+ end
1107
+
1108
+ alias to_summary to_s
694
1109
 
695
1110
  protected
696
1111
 
@@ -705,13 +1120,32 @@ class MacroObject
705
1120
  UUID.new.generate
706
1121
  end
707
1122
 
1123
+ def object(h={})
1124
+
1125
+ puts ('inside object h:' + h.inspect).debug if @debug
1126
+ klass = Object.const_get h[:class_type]
1127
+ puts klass.inspect.highlight if $debug
1128
+
1129
+ klass.new h
1130
+
1131
+ end
1132
+
708
1133
  end
709
1134
 
710
1135
  class Trigger < MacroObject
711
-
1136
+ using Params
1137
+
1138
+ attr_reader :constraints
1139
+
712
1140
  def initialize(h={})
713
1141
  super({fakeIcon: 0}.merge(h))
714
1142
  @list << 'fakeIcon'
1143
+
1144
+ # fetch the constraints
1145
+ @constraints = h[:constraint_list].map do |constraint|
1146
+ object(constraint.to_snake_case)
1147
+ end
1148
+
715
1149
  end
716
1150
 
717
1151
  def match?(detail={}, model=nil)
@@ -824,6 +1258,11 @@ class BatteryLevelTrigger < Trigger
824
1258
  super(options.merge h)
825
1259
 
826
1260
  end
1261
+
1262
+ def to_s()
1263
+ operator = @h[:decreases_to] ? '<=' : '>='
1264
+ "Battery %s %s%%" % [operator, @h[:battery_level]]
1265
+ end
827
1266
 
828
1267
  end
829
1268
 
@@ -1205,29 +1644,22 @@ class TimerTrigger < Trigger
1205
1644
  use_alarm: false
1206
1645
  }
1207
1646
 
1208
- super(options.merge filter(options,h))
1647
+ super(options.merge filter(options, h))
1209
1648
 
1210
1649
  end
1211
1650
 
1212
1651
  def match?(detail={time: $env[:time]}, model=nil)
1213
-
1214
- a = @h[:days_of_week]
1215
- a.unshift a.pop
1216
-
1217
- dow = a.map.with_index {|x, i| x ? i : nil }.compact.join(',')
1218
-
1219
- s = "%s %s * * %s" % [@h[:minute], @h[:hour], dow]
1220
-
1221
- if $debug then
1222
- puts 's: ' + s.inspect
1223
- puts 'detail: ' + detail.inspect
1224
- puts '@h: ' + @h.inspect
1225
- end
1226
1652
 
1227
- ChronicCron.new(s, detail[:time]).to_time == detail[:time]
1653
+ time() == detail[:time]
1228
1654
 
1229
1655
  end
1230
1656
 
1657
+ # sets the environmental conditions for this trigger to fire
1658
+ #
1659
+ def set_env()
1660
+ $env[:time] = time()
1661
+ end
1662
+
1231
1663
  def to_pc()
1232
1664
  "time.is? '%s'" % self.to_s.gsub(',', ' or')
1233
1665
  end
@@ -1243,6 +1675,20 @@ class TimerTrigger < Trigger
1243
1675
 
1244
1676
  "at %s on %s" % [time, days.join(', ')]
1245
1677
  end
1678
+
1679
+ private
1680
+
1681
+ def time()
1682
+
1683
+ a = @h[:days_of_week].clone
1684
+ a.unshift a.pop
1685
+
1686
+ dow = a.map.with_index {|x, i| x ? i : nil }.compact.join(',')
1687
+ s = "%s %s * * %s" % [@h[:minute], @h[:hour], dow]
1688
+ recent_time = ($env && $env[:time]) ? $env[:time] : Time.now
1689
+ ChronicCron.new(s, recent_time).to_time
1690
+
1691
+ end
1246
1692
 
1247
1693
  end
1248
1694
 
@@ -1464,6 +1910,10 @@ class DeviceUnlockedTrigger < DeviceEventsTrigger
1464
1910
  super(options.merge h)
1465
1911
 
1466
1912
  end
1913
+
1914
+ def to_s()
1915
+ 'Screen Unlocked'
1916
+ end
1467
1917
 
1468
1918
  end
1469
1919
 
@@ -1622,8 +2072,15 @@ end
1622
2072
  #
1623
2073
  class GeofenceTrigger < Trigger
1624
2074
 
1625
- def initialize(h={})
2075
+ def initialize( h={}, geofences: {})
1626
2076
 
2077
+ if h[:name] then
2078
+ puts ('geofences2: ' + geofences.inspect)
2079
+ found = geofences.find {|x| x.name.downcase == h[:name].downcase}
2080
+ h[:geofence_id] = found.id if found
2081
+
2082
+ end
2083
+
1627
2084
  options = {
1628
2085
  update_rate_text: '5 Minutes',
1629
2086
  geofence_id: '',
@@ -1632,9 +2089,28 @@ class GeofenceTrigger < Trigger
1632
2089
  enter_area: true
1633
2090
  }
1634
2091
 
1635
- super(options.merge h)
2092
+ super(options.merge filter(options, h))
2093
+ @geofences = geofences
1636
2094
 
1637
2095
  end
2096
+
2097
+ def to_s()
2098
+
2099
+ if $debug then
2100
+ puts ' @geofences: ' + @geofences.inspect
2101
+ puts '@h: ' + @h.inspect
2102
+ puts '@h[:geofence_id]: ' + @h[:geofence_id].inspect
2103
+ end
2104
+
2105
+ direction = @h[:enter_area] ? 'Entry' : 'Exit'
2106
+
2107
+ found = @geofences.find {|x| x.id == @h[:geofence_id]}
2108
+ puts 'found: ' + found.inspect if @debug
2109
+ label = found ? found.name : 'error: name not found'
2110
+
2111
+ "Geofence %s (%s)" % [direction, label]
2112
+
2113
+ end
1638
2114
 
1639
2115
  end
1640
2116
 
@@ -1677,8 +2153,28 @@ class ActivityRecognitionTrigger < SensorsTrigger
1677
2153
  }
1678
2154
 
1679
2155
  super(options.merge h)
2156
+
2157
+ @activity = ['In Vehicle', 'On Bicycle', 'Running', 'Walking', 'Still']
1680
2158
 
1681
2159
  end
2160
+
2161
+ def to_s()
2162
+ activity = @activity[@h[:selected_index]]
2163
+ 'Activity - ' + activity
2164
+ end
2165
+
2166
+ def to_summary
2167
+
2168
+ activity = @activity[@h[:selected_index]]
2169
+ s = if activity.length > 10 then
2170
+ activity[0..7] + '..'
2171
+ else
2172
+ activity
2173
+ end
2174
+
2175
+ 'Activity - ' + s
2176
+
2177
+ end
1682
2178
 
1683
2179
  end
1684
2180
 
@@ -1688,14 +2184,33 @@ class ProximityTrigger < SensorsTrigger
1688
2184
 
1689
2185
  def initialize(h={})
1690
2186
 
2187
+ if h[:distance] then
2188
+
2189
+ case h[:distance].to_sym
2190
+ when :near
2191
+ options[:near] = true
2192
+ end
2193
+ end
2194
+
1691
2195
  options = {
1692
2196
  near: true,
1693
2197
  selected_option: 0
1694
2198
  }
1695
2199
 
1696
- super(options.merge h)
2200
+ super(options.merge filter(options,h))
1697
2201
 
1698
2202
  end
2203
+
2204
+ def to_s()
2205
+
2206
+ distance = if @h[:near] then
2207
+ 'Near'
2208
+ else
2209
+ 'Far'
2210
+ end
2211
+
2212
+ "Proximity Sensor (%s)" % distance
2213
+ end
1699
2214
 
1700
2215
  end
1701
2216
 
@@ -1868,9 +2383,17 @@ end
1868
2383
 
1869
2384
 
1870
2385
  class Action < MacroObject
2386
+ using Params
2387
+
2388
+ attr_reader :constraints
1871
2389
 
1872
2390
  def initialize(h={})
1873
2391
  super(h)
2392
+
2393
+ # fetch the constraints
2394
+ @constraints = h[:constraint_list].map do |constraint|
2395
+ object(constraint.to_snake_case)
2396
+ end
1874
2397
  end
1875
2398
 
1876
2399
  def invoke(s='')
@@ -1939,6 +2462,10 @@ class LaunchActivityAction < ApplicationAction
1939
2462
  super(options.merge h)
1940
2463
 
1941
2464
  end
2465
+
2466
+ def to_s()
2467
+ 'Launch ' + @h[:application_name]
2468
+ end
1942
2469
 
1943
2470
  end
1944
2471
 
@@ -1964,6 +2491,8 @@ end
1964
2491
  class OpenWebPageAction < ApplicationAction
1965
2492
 
1966
2493
  def initialize(h={})
2494
+
2495
+ h[:url_to_open] = h[:url] if h[:url]
1967
2496
 
1968
2497
  options = {
1969
2498
  variable_to_save_response: {:m_stringValue=>"", :m_name=>"", :m_decimalValue=>0.0, :isLocal=>true, :m_booleanValue=>false, :excludeFromLog=>false, :m_intValue=>0, :m_type=>2},
@@ -1973,9 +2502,13 @@ class OpenWebPageAction < ApplicationAction
1973
2502
  block_next_action: false
1974
2503
  }
1975
2504
 
1976
- super(options.merge h)
2505
+ super(options.merge filter(options,h))
1977
2506
 
1978
2507
  end
2508
+
2509
+ def to_s()
2510
+ "HTTP GET\n url: " + @h[:url_to_open]
2511
+ end
1979
2512
 
1980
2513
  end
1981
2514
 
@@ -2035,6 +2568,64 @@ class TakePictureAction < CameraAction
2035
2568
 
2036
2569
  end
2037
2570
 
2571
+ class IfConditionAction < Action
2572
+
2573
+ def initialize(h={})
2574
+
2575
+ options = {
2576
+ a: true,
2577
+ constraint_list: ''
2578
+ }
2579
+
2580
+ super(options.merge h)
2581
+
2582
+ end
2583
+
2584
+ def to_s()
2585
+
2586
+ operator = @h[:is_or_condition] ? 'OR' : 'AND'
2587
+ r = 'If ' + @constraints.map(&:to_s).join(" %s " % operator)
2588
+ puts 'if ... @h ' + @h.inspect
2589
+ r
2590
+
2591
+ end
2592
+ end
2593
+
2594
+ class ElseAction < Action
2595
+
2596
+ def initialize(h={})
2597
+
2598
+ options = {
2599
+ constraint_list: ''
2600
+ }
2601
+
2602
+ super(options.merge h)
2603
+
2604
+ end
2605
+
2606
+ def to_s()
2607
+ 'Else'
2608
+ end
2609
+
2610
+ end
2611
+
2612
+ class EndIfAction < Action
2613
+
2614
+ def initialize(h={})
2615
+
2616
+ options = {
2617
+ constraint_list: ''
2618
+ }
2619
+
2620
+ super(options.merge h)
2621
+
2622
+ end
2623
+
2624
+ def to_s()
2625
+ 'End If'
2626
+ end
2627
+
2628
+ end
2038
2629
 
2039
2630
  class ConnectivityAction < Action
2040
2631
 
@@ -2060,6 +2651,11 @@ class SetWifiAction < ConnectivityAction
2060
2651
  super(options.merge h)
2061
2652
 
2062
2653
  end
2654
+
2655
+ def to_s()
2656
+ action = @h[:state] == 0 ? 'Enable' : 'Disable'
2657
+ action + ' Wifi'
2658
+ end
2063
2659
 
2064
2660
  end
2065
2661
 
@@ -2284,6 +2880,10 @@ class SpeakTextAction < DeviceAction
2284
2880
  super(options.merge h)
2285
2881
 
2286
2882
  end
2883
+
2884
+ def to_s()
2885
+ "Speak Text (%s)" % @h[:text_to_say]
2886
+ end
2287
2887
 
2288
2888
  end
2289
2889
 
@@ -2398,6 +2998,17 @@ class VibrateAction < DeviceSettingsAction
2398
2998
  super(options.merge h)
2399
2999
 
2400
3000
  end
3001
+
3002
+ def to_s()
3003
+
3004
+ pattern = [
3005
+ 'Blip', 'Short Buzz', 'Long Buzz', 'Rapid', 'Slow', 'Increasing',
3006
+ 'Constant', 'Decreasing', 'Final Fantasy', 'Game Over', 'Star Wars',
3007
+ 'Mini Blip', 'Micro Blip'
3008
+ ]
3009
+
3010
+ 'Vibrate ' + "(%s)" % pattern[@h[:vibrate_pattern].to_i]
3011
+ end
2401
3012
 
2402
3013
  end
2403
3014
 
@@ -2720,6 +3331,10 @@ class PlaySoundAction < MediaAction
2720
3331
  super(options.merge h)
2721
3332
 
2722
3333
  end
3334
+
3335
+ def to_s()
3336
+ 'Play: ' + @h[:file_path]
3337
+ end
2723
3338
 
2724
3339
  end
2725
3340
 
@@ -2924,6 +3539,9 @@ end
2924
3539
  class NotificationAction < NotificationsAction
2925
3540
 
2926
3541
  def initialize(h={})
3542
+
3543
+ h[:notification_subject] = h[:subject] if h[:subject]
3544
+ h[:notification_text] = h[:text] if h[:text]
2927
3545
 
2928
3546
  options = {
2929
3547
  ringtone_name: 'Default',
@@ -2939,9 +3557,13 @@ class NotificationAction < NotificationsAction
2939
3557
  run_macro_when_pressed: false
2940
3558
  }
2941
3559
 
2942
- super(options.merge h)
3560
+ super(options.merge filter(options, h))
2943
3561
 
2944
3562
  end
3563
+
3564
+ def to_s()
3565
+ 'Display Notification: ' + "%s: %s" % [@h[:notification_subject], @h[:notification_text]]
3566
+ end
2945
3567
 
2946
3568
  end
2947
3569
 
@@ -3174,6 +3796,10 @@ end
3174
3796
 
3175
3797
  # Category: Screen
3176
3798
  #
3799
+ # options:
3800
+ # keep awake, screen on => enabled: true
3801
+ # disable keep awake => enabled: false
3802
+ #
3177
3803
  class KeepAwakeAction < ScreenAction
3178
3804
 
3179
3805
  def initialize(h={})
@@ -3188,7 +3814,30 @@ class KeepAwakeAction < ScreenAction
3188
3814
  super(options.merge h)
3189
3815
 
3190
3816
  end
3191
-
3817
+
3818
+ def to_s()
3819
+
3820
+ screen = @h[:screen_option] == 0 ? 'Screen On' : 'Screen Off'
3821
+
3822
+ if @h[:enabled] then
3823
+
3824
+ whenx = if @h[:seconds_to_stay_awake_for] == 0 then
3825
+
3826
+ 'Until Disabled'
3827
+
3828
+ else
3829
+ scnds = @h[:seconds_to_stay_awake_for]
3830
+ Subunit.new(units={minutes:60, hours:60}, seconds: scnds).strfunit("%x")
3831
+ end
3832
+
3833
+ 'Keep Device Awake ' + screen + ' ' + whenx
3834
+
3835
+ else
3836
+ 'Disable Keep Awake'
3837
+ end
3838
+
3839
+
3840
+ end
3192
3841
  end
3193
3842
 
3194
3843
  # Category: Screen
@@ -3369,6 +4018,21 @@ class BatteryLevelConstraint < Constraint
3369
4018
  super(options.merge h)
3370
4019
 
3371
4020
  end
4021
+
4022
+ def to_s()
4023
+
4024
+ operator = if @h[:greater_than] then
4025
+ '>'
4026
+ elsif @h[:equals]
4027
+ '='
4028
+ else
4029
+ '<'
4030
+ end
4031
+
4032
+ level = @h[:battery_level]
4033
+
4034
+ "Battery %s %s%%" % [operator, level]
4035
+ end
3372
4036
 
3373
4037
  end
3374
4038
 
@@ -3420,6 +4084,11 @@ class ExternalPowerConstraint < Constraint
3420
4084
  super(options.merge h)
3421
4085
 
3422
4086
  end
4087
+
4088
+ def to_s()
4089
+ connection = @h[:external_power] ? 'Connected' : 'Disconnected'
4090
+ 'Power ' + connection
4091
+ end
3423
4092
 
3424
4093
  end
3425
4094
 
@@ -3438,6 +4107,12 @@ class BluetoothConstraint < Constraint
3438
4107
  super(options.merge h)
3439
4108
 
3440
4109
  end
4110
+
4111
+ def to_s()
4112
+ device = @h[:device_name] #== 'Any Device' ? 'Any' : @h[:device_name]
4113
+ "Device Connected (%s)" % device
4114
+ end
4115
+
3441
4116
 
3442
4117
  end
3443
4118
 
@@ -3762,6 +4437,10 @@ class DeviceLockedConstraint < Constraint
3762
4437
  super(options.merge h)
3763
4438
 
3764
4439
  end
4440
+
4441
+ def to_s()
4442
+ 'Device ' + (@h[:locked] ? 'Locked' : 'Unlocked')
4443
+ end
3765
4444
 
3766
4445
  end
3767
4446
 
@@ -3948,6 +4627,11 @@ class HeadphonesConnectionConstraint < Constraint
3948
4627
  super(options.merge h)
3949
4628
 
3950
4629
  end
4630
+
4631
+ def to_s()
4632
+ connection = @h[:connected] ? 'Connected' : 'Disconnected'
4633
+ 'Headphones ' + connection
4634
+ end
3951
4635
 
3952
4636
  end
3953
4637
 
@@ -4137,6 +4821,10 @@ class ScreenOnOffConstraint < Constraint
4137
4821
  super(options.merge h)
4138
4822
 
4139
4823
  end
4824
+
4825
+ def to_s()
4826
+ 'Screen ' + (@h[:screen_on] ? 'On' : 'Off')
4827
+ end
4140
4828
 
4141
4829
  end
4142
4830
 
@@ -4190,6 +4878,14 @@ class LightLevelConstraint < Constraint
4190
4878
  super(options.merge h)
4191
4879
 
4192
4880
  end
4881
+
4882
+ def to_s()
4883
+
4884
+ operator = @h[:light_level] == -1 ? 'Less than' : 'Greater than'
4885
+ condition = operator + ' ' + @h[:light_level_float].to_s + 'lx'
4886
+ 'Light Sensor ' + condition
4887
+
4888
+ end
4193
4889
 
4194
4890
  end
4195
4891
 
@@ -4222,5 +4918,9 @@ class ProximitySensorConstraint < Constraint
4222
4918
  super(options.merge h)
4223
4919
 
4224
4920
  end
4921
+
4922
+ def to_s()
4923
+ 'Proximity Sensor: ' + (@h[:near] ? 'Near' : 'Far')
4924
+ end
4225
4925
 
4226
4926
  end