ruby-macrodroid 0.5.1 → 0.7.2

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: c73d4f6bcddec53817e6ae7c59224759e58dd490ab53b2cab91728ceb98961f7
4
- data.tar.gz: 37887f1b46a2158d5101b2c51ae444016dcf34606df34f8e6f58aed05482efb9
3
+ metadata.gz: 70dd1172a7d2ad827ff72771851910b140616b3f41fd7843ef65dc5bdf55fd5e
4
+ data.tar.gz: d5ef7e748dd16bfbc83e0ca05932a2b7723c61ea2ee2fbc8f69ef1c77c651ef0
5
5
  SHA512:
6
- metadata.gz: 6bb6b3059e62aa4ce7924207a6fe545bc5b588d4ea4eab315361be595c47f343602a617dc7c2fdbc4701b44456dfcd18b255dc0fccfd904f7b56b5455f6f67a4
7
- data.tar.gz: 2b7f77a7b582a0a8d7beda8ba09d10973e06a4844d114398a5bf534e755e66914cede0023693605abe0100f882befe7598271d70aaa5747bde9dac39c04d03d3
6
+ metadata.gz: 91746fa312b8c1c5069c16f5c7337b1f6f438af8afecedc5e6469f21e00f98108788f3248d8d93284d9829858b0bf11ce5ad1591bba4a7f8f4ed1005a71c111c
7
+ data.tar.gz: bd1cefc341fd57809b563f1a4f21f013b3fe3e93143cef0c07862bdfe738ca6479e39c86a80355c110bd786da5b63849cc59616c7d67be58750c4ff2c68a47c3
Binary file
data.tar.gz.sig CHANGED
Binary file
@@ -2,7 +2,117 @@
2
2
 
3
3
  # file: ruby-macrodroid.rb
4
4
 
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
+
110
+ require 'yaml'
111
+ require 'rowx'
5
112
  require 'uuid'
113
+ #require 'glw'
114
+ #require 'geozone'
115
+ require 'subunit'
6
116
  require 'rxfhelper'
7
117
  require 'chronic_cron'
8
118
 
@@ -30,7 +140,55 @@ class TriggersNlp
30
140
  [TimerTrigger, {time: time, days: days}]
31
141
  end
32
142
 
143
+ # time.is? 'at 18:30pm on Mon or Tue'
144
+ get /^time.is\? ['"](?:at )?(\d+:\d+(?:[ap]m)?) (?:on )?(.*)['"]/i do |time, days|
145
+ [TimerTrigger, {time: time, days: days.gsub(' or ',', ')}]
146
+ end
147
+
148
+ get /^shake[ _]device\??$/i do
149
+ [ShakeDeviceTrigger, {}]
150
+ end
151
+
152
+ get /^Flip Device (.*)$/i do |motion|
153
+ facedown = motion =~ /Face Up (?:->|to) Face Down/i
154
+ [FlipDeviceTrigger, {face_down: facedown }]
155
+ end
156
+
157
+ get /^flip_device_down\?$/i do
158
+ [FlipDeviceTrigger, {face_down: true }]
159
+ end
160
+
161
+ get /^flip_device_up\?$/i do
162
+ [FlipDeviceTrigger, {face_down: false }]
163
+ end
164
+
165
+ get /^Failed Login Attempt$/i do
166
+ [FailedLoginTrigger, {}]
167
+ end
168
+
169
+ get /^failed_login?$/i do
170
+ [FailedLoginTrigger, {}]
171
+ end
172
+
173
+ get /^Geofence (Entry|Exit) \(([^\)]+)/i do |direction, name|
174
+ enter_area = direction.downcase.to_sym == :entry
175
+ [GeofenceTrigger, {name: name, enter_area: enter_area}]
176
+ end
177
+
178
+ get /^location (entered|exited) \(([^\)]+)/i do |direction, name|
179
+ enter_area = direction.downcase.to_sym == :entered
180
+ [GeofenceTrigger, {name: name, enter_area: enter_area}]
181
+ end
182
+
183
+ # eg. Proximity Sensor (Near)
184
+ #
185
+ get /^Proximity Sensor \(([^\)]+)\)/i do |distance|
186
+
187
+ [ProximityTrigger, {distance: distance}]
188
+ end
33
189
 
190
+
191
+
34
192
  end
35
193
 
36
194
  alias find_trigger run_route
@@ -50,13 +208,105 @@ class ActionsNlp
50
208
 
51
209
  def actions(params)
52
210
 
211
+ # e.g. message popup: hello world!
53
212
  get /^message popup: (.*)/i do |msg|
54
213
  [ToastAction, {msg: msg}]
55
214
  end
56
215
 
57
- get /^Popup Message ['"][^'"]+/i do |msg|
216
+ # e.g. Popup Message 'hello world!'
217
+ get /^Popup[ _]Message ['"]([^'"]+)/i do |msg|
58
218
  [ToastAction, {msg: msg}]
59
219
  end
220
+
221
+ # e.g. say current time
222
+ get /^say current[ _]time/i do
223
+ [SayTimeAction, {}]
224
+ end
225
+
226
+ get /^Torch :?(.*)/i do |onoffstate|
227
+ state = onoffstate.downcase == 'on' ? 0 : 1
228
+ [CameraFlashLightAction, {state: state}]
229
+ end
230
+
231
+ get /^Take Picture/i do
232
+ [TakePictureAction, {}]
233
+ end
234
+
235
+ get /^take_picture/i do
236
+ [TakePictureAction, {}]
237
+ end
238
+
239
+ # e.g. Display Notification: Hi there: This is the body of the message
240
+ get /^Display Notification: ([^:]+): [^$]+$/i do |subject, text|
241
+ [NotificationAction, {subject: subject, text: text}]
242
+ end
243
+
244
+
245
+ # e.g. Enable Wifi
246
+ get /^(Enable|Disable) Wifi$/i do |raw_state|
247
+
248
+ state = raw_state.downcase.to_sym == :enable ? 0 : 1
249
+ [SetWifiAction, {state: state}]
250
+
251
+ end
252
+
253
+ # e.g. Play: Altair
254
+ get /^Play: (.*)$/i do |name|
255
+
256
+ [PlaySoundAction, {file_path: name}]
257
+
258
+ end
259
+
260
+ # e.g. Launch Settings
261
+ get /^Launch (.*)$/i do |application|
262
+
263
+ h = {
264
+ application_name: application,
265
+ package_to_launch: 'com.android.' + application.downcase
266
+ }
267
+ [LaunchActivityAction, h]
268
+
269
+ end
270
+
271
+ # e.g. HTTP GET http://someurl.com/something
272
+ get /^HTTP GET ([^$]+)$/i do |url|
273
+
274
+ [OpenWebPageAction, url_to_open: url]
275
+
276
+ end
277
+
278
+ # e.g. webhook entered_kitchen
279
+ #
280
+ get /webhook|HTTP GET/i do
281
+ [OpenWebPageAction, {}]
282
+ end
283
+
284
+ #a: Keep Device Awake Screen On Until Disabled
285
+ #
286
+ get /Keep Device Awake Screen On Until Disabled/i do
287
+ [KeepAwakeAction, {enabled: true, permanent: true, screen_option: 0}]
288
+ end
289
+
290
+
291
+ #a: Keep Device Awake Screen On 1h 1m 1s
292
+ #
293
+ get /Keep Device Awake Screen On ([^$]+)/i do |duration|
294
+
295
+ a = duration.split.map(&:to_i)
296
+ secs = Subunit.new(units={minutes:60, hours:60, seconds: 60}, a).to_i
297
+
298
+ h = {
299
+ permanent: true, screen_option: 0, seconds_to_stay_awake_for: secs
300
+ }
301
+ [KeepAwakeAction, h]
302
+ end
303
+
304
+ #a: Disable Keep Awake
305
+ #
306
+ get /Disable Keep Awake/i do
307
+ [KeepAwakeAction, {enabled: false, screen_option: 0}]
308
+ end
309
+
60
310
 
61
311
  end
62
312
 
@@ -145,11 +395,11 @@ class Macro
145
395
  using Params
146
396
 
147
397
  attr_reader :local_variables, :triggers, :actions, :constraints, :guid
148
- attr_accessor :title
398
+ attr_accessor :title, :description
149
399
 
150
- def initialize(name=nil, debug: false)
400
+ def initialize(name=nil, geofences: geofences, debug: false)
151
401
 
152
- @title, @debug = name, debug
402
+ @title, @geofences, @debug = name, geofences, debug
153
403
 
154
404
  puts 'inside Macro#initialize' if @debug
155
405
 
@@ -209,6 +459,7 @@ class Macro
209
459
  end
210
460
 
211
461
  @title = h[:name]
462
+ @description = h[:description]
212
463
 
213
464
  # fetch the local variables
214
465
  @local_variables = h['local_variables']
@@ -239,18 +490,20 @@ class Macro
239
490
  end
240
491
 
241
492
  def import_xml(node)
242
-
493
+
243
494
  if @debug then
244
495
  puts 'inside Macro#import_xml'
245
496
  puts 'node: ' + node.xml.inspect
246
497
  end
247
498
 
248
- @title = node.attributes[:name]
249
-
250
499
  if node.element('triggers') then
251
-
500
+
252
501
  # level 2
253
502
 
503
+ @title = node.attributes[:name]
504
+ @description = node.attributes[:description]
505
+
506
+
254
507
  # get all the triggers
255
508
  @triggers = node.xpath('triggers/*').map do |e|
256
509
 
@@ -286,6 +539,12 @@ class Macro
286
539
 
287
540
  # Level 1
288
541
 
542
+ puts 'import_xml: inside level 1' if @debug
543
+
544
+ @title = node.text('macro') || node.attributes[:name]
545
+
546
+ #@description = node.attributes[:description]
547
+
289
548
  tp = TriggersNlp.new
290
549
 
291
550
  @triggers = node.xpath('trigger').map do |e|
@@ -295,7 +554,11 @@ class Macro
295
554
  puts 'found trigger ' + r.inspect if @debug
296
555
 
297
556
  if r then
298
- r[0].new(r[1])
557
+ if r[0] == GeofenceTrigger then
558
+ GeofenceTrigger.new(r[1], geofences: @geofences)
559
+ else
560
+ r[0].new(r[1])
561
+ end
299
562
  end
300
563
 
301
564
  end
@@ -304,11 +567,21 @@ class Macro
304
567
 
305
568
  @actions = node.xpath('action').map do |e|
306
569
 
570
+ puts 'action e: ' + e.xml.inspect if @debug
307
571
  r = ap.find_action e.text
308
572
  puts 'found action ' + r.inspect if @debug
309
573
 
310
574
  if r then
311
- r[0].new(r[1])
575
+
576
+ a = e.xpath('item/*')
577
+
578
+ h = if a.any? then
579
+ a.map {|node| [node.name.to_sym, node.text.to_s]}.to_h
580
+ else
581
+ {}
582
+ end
583
+
584
+ r[0].new(r[1].merge(h))
312
585
  end
313
586
 
314
587
  end
@@ -355,17 +628,64 @@ class Macro
355
628
 
356
629
  end
357
630
 
631
+ # invokes the actions
632
+ #
358
633
  def run()
359
634
  @actions.map(&:invoke)
360
- end
635
+ end
636
+
637
+ # prepares the environment in order for triggers to test fire successfully
638
+ # Used for testing
639
+ #
640
+ def set_env()
641
+ @triggers.each(&:set_env)
642
+ end
643
+
644
+ def to_pc()
645
+
646
+ heading = '# ' + @title
647
+ heading += '\n# ' + @description if @description
648
+ condition = @triggers.first.to_pc
649
+ actions = @actions.map(&:to_pc).join("\n")
650
+
651
+ <<EOF
652
+ #{heading}
653
+
654
+ if #{condition} then
655
+ #{actions}
656
+ end
657
+ EOF
658
+ end
361
659
 
362
660
  def to_s()
363
- [
661
+
662
+ a = [
364
663
  'm: ' + @title,
365
664
  @triggers.map {|x| "t: %s" % x}.join("\n"),
366
665
  @actions.map {|x| "a: %s" % x}.join("\n"),
367
666
  @constraints.map {|x| "a: %s" % x}.join("\n")
368
- ].join("\n")
667
+ ]
668
+
669
+ if @description and @description.length >= 1 then
670
+ a.insert(1, 'd: ' + @description)
671
+ end
672
+
673
+ a.join("\n")
674
+
675
+ end
676
+
677
+ def to_summary()
678
+
679
+ a = [
680
+ 'm: ' + @title,
681
+ 't: ' + @triggers.map(&:to_summary).join(", "),
682
+ 'a: ' + @actions.map(&:to_summary).join(", "),
683
+ ]
684
+
685
+ a << 'c: ' + @constraints.map(&:to_summary).join(", ") if @constraints.any?
686
+
687
+ a.join("\n") + "\n"
688
+
369
689
  end
370
690
 
371
691
  private
@@ -378,34 +698,86 @@ class Macro
378
698
 
379
699
  puts ('inside object h:' + h.inspect).debug if @debug
380
700
  klass = Object.const_get h[:class_type]
381
- klass.new h
701
+ puts klass.inspect.highlight if $debug
702
+
703
+ if klass == GeofenceTrigger then
704
+ puts 'GeofenceTrigger found'.highlight if $debug
705
+ GeofenceTrigger.new(h, geofences: @geofences)
706
+ else
707
+ klass.new h
708
+ end
709
+
382
710
  end
383
711
 
384
712
  end
385
713
 
386
714
 
715
+ class MacroDroidError < Exception
716
+ end
717
+
387
718
  class MacroDroid
388
719
  using ColouredText
389
720
  using Params
390
721
 
391
- attr_reader :macros
722
+ attr_reader :macros, :geofences
392
723
 
393
724
  def initialize(obj=nil, debug: false)
394
725
 
395
726
  @debug = debug
396
727
 
728
+ @geofences = {}
729
+
397
730
  if obj then
398
731
 
399
- s, _ = RXFHelper.read(obj)
732
+ raw_s, _ = RXFHelper.read(obj)
733
+
734
+ s = raw_s.strip
400
735
 
401
736
  if s[0] == '{' then
737
+
402
738
  import_json(s)
739
+
403
740
  elsif s[0] == '<'
741
+
404
742
  import_xml(s)
405
743
  @h = build_h
744
+
406
745
  else
407
- import_xml(text_to_xml(s))
746
+
747
+ puts 's: ' + s.inspect if @debug
748
+
749
+ if s =~ /m(?:acro)?:\s/ then
750
+
751
+ puts 'before RowX.new' if @debug
752
+
753
+ s2 = s.gsub(/^g:/,'geofence:').gsub(/^m:/,'macro:')\
754
+ .gsub(/^t:/,'trigger:').gsub(/^a:/,'action:')\
755
+ .gsub(/^c:/,'constraint:').gsub(/^#.*/,'')
756
+
757
+ raw_macros, raw_geofences = s2.split(/(?=^macro:)/,2).reverse
758
+
759
+ if raw_geofences then
760
+
761
+ geoxml = RowX.new(raw_geofences).to_xml
762
+
763
+ geodoc = Rexle.new(geoxml)
764
+ geofences = geodoc.root.xpath('item/geofence')
765
+ @geofences = fetch_geofences(geofences) if geofences.any?
766
+
767
+ end
768
+
769
+ xml = RowX.new(raw_macros).to_xml
770
+ import_rowxml(xml)
771
+
772
+ elsif s =~ /^# /
773
+ xml = pc_to_xml(s)
774
+ import_xml(xml)
775
+ else
776
+ raise MacroDroidError, 'invalid input'
777
+ end
778
+
408
779
  @h = build_h
780
+
409
781
  end
410
782
 
411
783
  else
@@ -457,15 +829,94 @@ class MacroDroid
457
829
 
458
830
  alias to_json export_json
459
831
 
832
+
833
+ def to_h()
834
+
835
+ h = {
836
+ geofence_data: {
837
+ geofence_map: @geofences.map {|key, value| [key, value.to_h] }.to_h
838
+ },
839
+ macro_list: @macros.map(&:to_h)
840
+ }
841
+ @h.merge(h).to_camel_case
842
+
843
+ end
844
+
845
+ # returns pseudocode
846
+ #
847
+ def to_pc()
848
+ @macros.map(&:to_pc).join("\n\n")
849
+ end
850
+
851
+ def to_s()
852
+
853
+ lines = []
854
+
855
+ if @geofences.any? then
856
+ lines << @geofences.map {|_, value| 'g: ' + value.to_s}.join("\n\n") + "\n"
857
+ end
858
+
859
+ lines << @macros.map(&:to_s).join("\n")
860
+ lines.join("\n")
861
+
862
+ end
863
+
864
+ def to_summary()
865
+ @macros.map(&:to_summary).join("\n")
866
+ end
867
+
868
+ private
869
+
870
+ def fetch_geofences(nodes)
871
+
872
+ nodes.map do |e|
873
+
874
+ name = e.text.to_s.strip
875
+ item = e.element('item')
876
+ coordinates = item.text('coordinates')
877
+ latitude, longitude = coordinates.split(/, */,2)
878
+ radius = item.text('radius')
879
+
880
+ id = UUID.new.generate
881
+
882
+ h = {
883
+ name: name,
884
+ longitude: longitude,
885
+ latitude: latitude,
886
+ radius: radius,
887
+ id: id
888
+ }
889
+
890
+ [id.to_sym, GeofenceMap.new(h)]
891
+
892
+ end.to_h
893
+
894
+ end
895
+
460
896
  def import_json(s)
461
897
 
462
- @h = JSON.parse(s, symbolize_names: true).to_snake_case
898
+ h = JSON.parse(s, symbolize_names: true)
899
+ puts 'json_to_yaml: ' + h.to_yaml if @debug
900
+
901
+ @h = h.to_snake_case
463
902
  puts ('@h: ' + @h.inspect).debug if @debug
464
-
903
+
904
+
905
+ # fetch the geofence data
906
+ if @h[:geofence_data] then
907
+
908
+ @geofences = @h[:geofence_data][:geofence_map].map do |id, properties|
909
+ [id, GeofenceMap.new(properties)]
910
+ end.to_h
911
+
912
+ end
913
+
465
914
  @macros = @h[:macro_list].map do |macro|
466
915
 
467
916
  puts ('macro: ' + macro.inspect).debug if @debug
468
- m = Macro.new(debug: @debug)
917
+ # puts '@geofences: ' + @geofences.inspect if @debug
918
+
919
+ m = Macro.new(geofences: @geofences.map(&:last), debug: @debug )
469
920
  m.import_h(macro)
470
921
  m
471
922
 
@@ -475,59 +926,99 @@ class MacroDroid
475
926
 
476
927
  end
477
928
 
929
+ def import_rowxml(raws)
930
+
931
+ s = RXFHelper.read(raws).first
932
+ puts 's: ' + s.inspect if @debug
933
+ doc = Rexle.new(s)
934
+ puts 'after doc' if @debug
935
+ puts 'import_rowxml: @geofences: ' + @geofences.inspect if @debug
936
+ geofences = @geofences
937
+
938
+ @macros = doc.root.xpath('item').map do |node|
939
+ puts ('geofences: ' + geofences.inspect).highlight if @debug
940
+ Macro.new(geofences: geofences.map(&:last), debug: @debug).import_xml(node)
941
+
942
+ end
943
+
944
+ end
945
+
478
946
  def import_xml(raws)
479
947
 
948
+ if @debug then
949
+ puts 'inside import_xml'
950
+
951
+ puts 'raws: ' + raws.inspect
952
+ end
480
953
  s = RXFHelper.read(raws).first
481
954
  puts 's: ' + s.inspect if @debug
482
955
  doc = Rexle.new(s)
483
- puts 'after doc' if @debug
484
956
 
957
+ if @debug then
958
+ puts 'doc: ' + doc.root.xml
959
+ end
960
+
485
961
  @macros = doc.root.xpath('macro').map do |node|
486
962
 
487
- macro = Macro.new @title, debug: @debug
488
- macro.import_xml(node)
489
- macro
963
+ Macro.new(geofences: @geofences.map(&:last), debug: @debug).import_xml(node)
490
964
 
491
965
  end
492
966
  end
493
967
 
494
- def text_to_xml(s)
968
+ def pc_to_xml(s)
495
969
 
496
- a = s.split(/.*(?=^m:)/); a.shift
497
- a.map!(&:chomp)
498
-
499
- macros = a.map do |x|
500
-
501
- lines = x.lines
502
- puts 'lines: ' + lines.inspect if @debug
503
-
504
- name = lines.shift[/^m: +(.*)/,1]
505
- h = {t: [], a: [], c: []}
970
+ macros = s.strip.split(/(?=#)/).map do |raw_macro|
506
971
 
507
- lines.each {|line| h[line[0].to_sym] << line[/^\w: +(.*)/,1] }
508
- triggers = h[:t].map {|text| [:trigger, {}, text]}
509
- actions = h[:a].map {|text| [:action, {}, text]}
510
- constraints = h[:c].map {|text| [:constraint, {}, text]}
972
+ a = raw_macro.lines
973
+ name = a.shift[/(?<=# ).*/]
974
+ description = a.shift[/(?<=# ).*/] if a[0][/^# /]
975
+ body = a.join.strip
511
976
 
512
- [:macro, {name: name},'', *triggers, *actions, *constraints]
977
+ a2 = body.lines
978
+ # get the trigger
979
+ trigger = [:trigger, {}, a2[0][/^if (.*) then/,1]]
980
+ action = [:action, {}, a2[1].strip]
981
+ [:macro, {name: name, description: description}, trigger, action, []]
513
982
 
514
983
  end
515
984
 
516
985
  doc = Rexle.new([:macros, {}, '', *macros])
517
986
  doc.root.xml pretty: true
518
987
 
519
- end
520
-
521
- def to_h()
988
+ end
522
989
 
523
- @h.merge(macro_list: @macros.map(&:to_h)).to_camel_case
990
+ end
524
991
 
992
+ class GeofenceMap
993
+
994
+ attr_accessor :name, :longitude, :latitude, :radius, :id
995
+
996
+ def initialize(id: '', longitude: '', latitude: '', name: '', radius: '')
997
+
998
+ @id, @latitude, @longitude, @name, @radius = id, latitude, \
999
+ longitude, name, radius
1000
+
525
1001
  end
526
-
1002
+
1003
+ def to_h()
1004
+
1005
+ {
1006
+ id: @id,
1007
+ longitude: @longitude,
1008
+ latitude: @latitude,
1009
+ name: @name,
1010
+ radius: @radius
1011
+ }
1012
+
1013
+ end
1014
+
527
1015
  def to_s()
528
- @macros.map(&:to_s).join("\n\n")
1016
+
1017
+ coordinates = "%s, %s" % [@longitude, @latitude]
1018
+ "%s\n coordinates: %s\n radius: %s" % [@name, coordinates, @radius]
1019
+
529
1020
  end
530
-
1021
+
531
1022
  end
532
1023
 
533
1024
  class MacroObject
@@ -538,6 +1029,8 @@ class MacroObject
538
1029
 
539
1030
  def initialize(h={})
540
1031
 
1032
+ $env ||= {}
1033
+
541
1034
  @h = {constraint_list: [], is_or_condition: false,
542
1035
  is_disabled: false}.merge(h)
543
1036
  @list = []
@@ -566,6 +1059,12 @@ class MacroObject
566
1059
  h2.merge('m_classType' => self.class.to_s)
567
1060
 
568
1061
  end
1062
+
1063
+ def to_s()
1064
+ "#<%s %s>" % [self.class, @h.inspect]
1065
+ end
1066
+
1067
+ alias to_summary to_s
569
1068
 
570
1069
  protected
571
1070
 
@@ -1080,29 +1579,26 @@ class TimerTrigger < Trigger
1080
1579
  use_alarm: false
1081
1580
  }
1082
1581
 
1083
- super(options.merge filter(options,h))
1582
+ super(options.merge filter(options, h))
1084
1583
 
1085
1584
  end
1086
1585
 
1087
1586
  def match?(detail={time: $env[:time]}, model=nil)
1088
-
1089
- a = @h[:days_of_week]
1090
- a.unshift a.pop
1091
-
1092
- dow = a.map.with_index {|x, i| x ? i : nil }.compact.join(',')
1093
-
1094
- s = "%s %s * * %s" % [@h[:minute], @h[:hour], dow]
1095
-
1096
- if $debug then
1097
- puts 's: ' + s.inspect
1098
- puts 'detail: ' + detail.inspect
1099
- puts '@h: ' + @h.inspect
1100
- end
1101
1587
 
1102
- ChronicCron.new(s, detail[:time]).to_time == detail[:time]
1588
+ time() == detail[:time]
1103
1589
 
1104
1590
  end
1105
1591
 
1592
+ # sets the environmental conditions for this trigger to fire
1593
+ #
1594
+ def set_env()
1595
+ $env[:time] = time()
1596
+ end
1597
+
1598
+ def to_pc()
1599
+ "time.is? '%s'" % self.to_s.gsub(',', ' or')
1600
+ end
1601
+
1106
1602
  def to_s()
1107
1603
 
1108
1604
  dow = @h[:days_of_week]
@@ -1114,6 +1610,20 @@ class TimerTrigger < Trigger
1114
1610
 
1115
1611
  "at %s on %s" % [time, days.join(', ')]
1116
1612
  end
1613
+
1614
+ private
1615
+
1616
+ def time()
1617
+
1618
+ a = @h[:days_of_week].clone
1619
+ a.unshift a.pop
1620
+
1621
+ dow = a.map.with_index {|x, i| x ? i : nil }.compact.join(',')
1622
+ s = "%s %s * * %s" % [@h[:minute], @h[:hour], dow]
1623
+ recent_time = ($env && $env[:time]) ? $env[:time] : Time.now
1624
+ ChronicCron.new(s, recent_time).to_time
1625
+
1626
+ end
1117
1627
 
1118
1628
  end
1119
1629
 
@@ -1185,6 +1695,15 @@ class RegularIntervalTrigger < Trigger
1185
1695
 
1186
1696
  end
1187
1697
 
1698
+ class DeviceEventsTrigger < Trigger
1699
+
1700
+ def initialize(h={})
1701
+ super(h)
1702
+ @group = 'device_events'
1703
+ end
1704
+
1705
+ end
1706
+
1188
1707
  # Category: Device Events
1189
1708
  #
1190
1709
  # Airplane Mode Changed
@@ -1196,7 +1715,7 @@ end
1196
1715
  # shorthand example:
1197
1716
  # airplanemode: enabled
1198
1717
  #
1199
- class AirplaneModeTrigger < Trigger
1718
+ class AirplaneModeTrigger < DeviceEventsTrigger
1200
1719
 
1201
1720
  def initialize(h={})
1202
1721
 
@@ -1212,7 +1731,7 @@ end
1212
1731
 
1213
1732
  # Category: Device Events
1214
1733
  #
1215
- class AutoSyncChangeTrigger < Trigger
1734
+ class AutoSyncChangeTrigger < DeviceEventsTrigger
1216
1735
 
1217
1736
  def initialize(h={})
1218
1737
 
@@ -1228,7 +1747,7 @@ end
1228
1747
 
1229
1748
  # Category: Device Events
1230
1749
  #
1231
- class DayDreamTrigger < Trigger
1750
+ class DayDreamTrigger < DeviceEventsTrigger
1232
1751
 
1233
1752
  def initialize(h={})
1234
1753
 
@@ -1244,7 +1763,7 @@ end
1244
1763
 
1245
1764
  # Category: Device Events
1246
1765
  #
1247
- class DockTrigger < Trigger
1766
+ class DockTrigger < DeviceEventsTrigger
1248
1767
 
1249
1768
  def initialize(h={})
1250
1769
 
@@ -1260,7 +1779,30 @@ end
1260
1779
 
1261
1780
  # Category: Device Events
1262
1781
  #
1263
- class GPSEnabledTrigger < Trigger
1782
+ class FailedLoginTrigger < DeviceEventsTrigger
1783
+
1784
+ def initialize(h={})
1785
+
1786
+ options = {
1787
+ num_failures: 1
1788
+ }
1789
+
1790
+ super(options.merge h)
1791
+
1792
+ end
1793
+
1794
+ def to_pc()
1795
+ 'failed_login?'
1796
+ end
1797
+
1798
+ def to_s()
1799
+ 'Failed Login Attempt'
1800
+ end
1801
+ end
1802
+
1803
+ # Category: Device Events
1804
+ #
1805
+ class GPSEnabledTrigger < DeviceEventsTrigger
1264
1806
 
1265
1807
  def initialize(h={})
1266
1808
 
@@ -1276,7 +1818,7 @@ end
1276
1818
 
1277
1819
  # Category: Device Events
1278
1820
  #
1279
- class MusicPlayingTrigger < Trigger
1821
+ class MusicPlayingTrigger < DeviceEventsTrigger
1280
1822
 
1281
1823
  def initialize(h={})
1282
1824
 
@@ -1293,7 +1835,7 @@ end
1293
1835
 
1294
1836
  # Category: Device Events
1295
1837
  #
1296
- class DeviceUnlockedTrigger < Trigger
1838
+ class DeviceUnlockedTrigger < DeviceEventsTrigger
1297
1839
 
1298
1840
  def initialize(h={})
1299
1841
 
@@ -1308,7 +1850,7 @@ end
1308
1850
 
1309
1851
  # Category: Device Events
1310
1852
  #
1311
- class AutoRotateChangeTrigger < Trigger
1853
+ class AutoRotateChangeTrigger < DeviceEventsTrigger
1312
1854
 
1313
1855
  def initialize(h={})
1314
1856
 
@@ -1324,7 +1866,7 @@ end
1324
1866
 
1325
1867
  # Category: Device Events
1326
1868
  #
1327
- class ClipboardChangeTrigger < Trigger
1869
+ class ClipboardChangeTrigger < DeviceEventsTrigger
1328
1870
 
1329
1871
  def initialize(h={})
1330
1872
 
@@ -1341,7 +1883,7 @@ end
1341
1883
 
1342
1884
  # Category: Device Events
1343
1885
  #
1344
- class BootTrigger < Trigger
1886
+ class BootTrigger < DeviceEventsTrigger
1345
1887
 
1346
1888
  def initialize(h={})
1347
1889
 
@@ -1356,7 +1898,7 @@ end
1356
1898
 
1357
1899
  # Category: Device Events
1358
1900
  #
1359
- class IntentReceivedTrigger < Trigger
1901
+ class IntentReceivedTrigger < DeviceEventsTrigger
1360
1902
 
1361
1903
  def initialize(h={})
1362
1904
 
@@ -1376,7 +1918,7 @@ end
1376
1918
 
1377
1919
  # Category: Device Events
1378
1920
  #
1379
- class NotificationTrigger < Trigger
1921
+ class NotificationTrigger < DeviceEventsTrigger
1380
1922
 
1381
1923
  def initialize(h={})
1382
1924
 
@@ -1402,7 +1944,7 @@ end
1402
1944
 
1403
1945
  # Category: Device Events
1404
1946
  #
1405
- class ScreenOnOffTrigger < Trigger
1947
+ class ScreenOnOffTrigger < DeviceEventsTrigger
1406
1948
 
1407
1949
  def initialize(h={})
1408
1950
 
@@ -1418,7 +1960,7 @@ end
1418
1960
 
1419
1961
  # Category: Device Events
1420
1962
  #
1421
- class SilentModeTrigger < Trigger
1963
+ class SilentModeTrigger < DeviceEventsTrigger
1422
1964
 
1423
1965
  def initialize(h={})
1424
1966
 
@@ -1461,8 +2003,15 @@ end
1461
2003
  #
1462
2004
  class GeofenceTrigger < Trigger
1463
2005
 
1464
- def initialize(h={})
2006
+ def initialize( h={}, geofences: {})
1465
2007
 
2008
+ if h[:name] then
2009
+ puts ('geofences2: ' + geofences.inspect)
2010
+ found = geofences.find {|x| x.name.downcase == h[:name].downcase}
2011
+ h[:geofence_id] = found.id if found
2012
+
2013
+ end
2014
+
1466
2015
  options = {
1467
2016
  update_rate_text: '5 Minutes',
1468
2017
  geofence_id: '',
@@ -1471,9 +2020,28 @@ class GeofenceTrigger < Trigger
1471
2020
  enter_area: true
1472
2021
  }
1473
2022
 
1474
- super(options.merge h)
2023
+ super(options.merge filter(options, h))
2024
+ @geofences = geofences
1475
2025
 
1476
2026
  end
2027
+
2028
+ def to_s()
2029
+
2030
+ if $debug then
2031
+ puts ' @geofences: ' + @geofences.inspect
2032
+ puts '@h: ' + @h.inspect
2033
+ puts '@h[:geofence_id]: ' + @h[:geofence_id].inspect
2034
+ end
2035
+
2036
+ direction = @h[:enter_area] ? 'Entry' : 'Exit'
2037
+
2038
+ found = @geofences.find {|x| x.id == @h[:geofence_id]}
2039
+ puts 'found: ' + found.inspect if @debug
2040
+ label = found ? found.name : 'error: name not found'
2041
+
2042
+ "Geofence %s (%s)" % [direction, label]
2043
+
2044
+ end
1477
2045
 
1478
2046
  end
1479
2047
 
@@ -1494,9 +2062,19 @@ class SunriseSunsetTrigger < Trigger
1494
2062
 
1495
2063
  end
1496
2064
 
2065
+
2066
+ class SensorsTrigger < Trigger
2067
+
2068
+ def initialize(h={})
2069
+ super(h)
2070
+ @group = 'sensors'
2071
+ end
2072
+
2073
+ end
2074
+
1497
2075
  # Category: Sensors
1498
2076
  #
1499
- class ActivityRecognitionTrigger < Trigger
2077
+ class ActivityRecognitionTrigger < SensorsTrigger
1500
2078
 
1501
2079
  def initialize(h={})
1502
2080
 
@@ -1506,31 +2084,70 @@ class ActivityRecognitionTrigger < Trigger
1506
2084
  }
1507
2085
 
1508
2086
  super(options.merge h)
2087
+
2088
+ @activity = ['In Vehicle', 'On Bicycle', 'Running', 'Walking', 'Still']
1509
2089
 
1510
2090
  end
2091
+
2092
+ def to_s()
2093
+ activity = @activity[@h[:selected_index]]
2094
+ 'Activity - ' + activity
2095
+ end
2096
+
2097
+ def to_summary
2098
+
2099
+ activity = @activity[@h[:selected_index]]
2100
+ s = if activity.length > 10 then
2101
+ activity[0..7] + '..'
2102
+ else
2103
+ activity
2104
+ end
2105
+
2106
+ 'Activity - ' + s
2107
+
2108
+ end
1511
2109
 
1512
2110
  end
1513
2111
 
1514
2112
  # Category: Sensors
1515
2113
  #
1516
- class ProximityTrigger < Trigger
2114
+ class ProximityTrigger < SensorsTrigger
1517
2115
 
1518
2116
  def initialize(h={})
1519
2117
 
2118
+ if h[:distance] then
2119
+
2120
+ case h[:distance].to_sym
2121
+ when :near
2122
+ options[:near] = true
2123
+ end
2124
+ end
2125
+
1520
2126
  options = {
1521
2127
  near: true,
1522
2128
  selected_option: 0
1523
2129
  }
1524
2130
 
1525
- super(options.merge h)
2131
+ super(options.merge filter(options,h))
1526
2132
 
1527
2133
  end
2134
+
2135
+ def to_s()
2136
+
2137
+ distance = if @h[:near] then
2138
+ 'Near'
2139
+ else
2140
+ 'Far'
2141
+ end
2142
+
2143
+ "Proximity Sensor (%s)" % distance
2144
+ end
1528
2145
 
1529
2146
  end
1530
2147
 
1531
2148
  # Category: Sensors
1532
2149
  #
1533
- class ShakeDeviceTrigger < Trigger
2150
+ class ShakeDeviceTrigger < SensorsTrigger
1534
2151
 
1535
2152
  def initialize(h={})
1536
2153
 
@@ -1540,12 +2157,25 @@ class ShakeDeviceTrigger < Trigger
1540
2157
  super(options.merge h)
1541
2158
 
1542
2159
  end
2160
+
2161
+ def to_pc()
2162
+ 'shake_device?'
2163
+ end
2164
+
2165
+ def to_s()
2166
+ 'Shake Device'
2167
+ end
1543
2168
 
1544
2169
  end
1545
2170
 
1546
2171
  # Category: Sensors
1547
2172
  #
1548
- class FlipDeviceTrigger < Trigger
2173
+ # options:
2174
+ # Face Up -> Face Down
2175
+ # Face Down -> Face Up
2176
+ # Any -> Face Down
2177
+ #
2178
+ class FlipDeviceTrigger < SensorsTrigger
1549
2179
 
1550
2180
  def initialize(h={})
1551
2181
 
@@ -1557,13 +2187,23 @@ class FlipDeviceTrigger < Trigger
1557
2187
 
1558
2188
  super(options.merge h)
1559
2189
 
2190
+ end
2191
+
2192
+ def to_pc()
2193
+ @h[:face_down] ? 'flip_device_down?' : 'flip_device_up?'
1560
2194
  end
2195
+
2196
+ def to_s()
2197
+
2198
+ action = @h[:face_down] ? 'Face Up -> Face Down' : 'Face Down -> Face Up'
2199
+ 'Flip Device ' + action
2200
+ end
1561
2201
 
1562
2202
  end
1563
2203
 
1564
2204
  # Category: Sensors
1565
2205
  #
1566
- class OrientationTrigger < Trigger
2206
+ class OrientationTrigger < SensorsTrigger
1567
2207
 
1568
2208
  def initialize(h={})
1569
2209
 
@@ -1745,6 +2385,10 @@ class LaunchActivityAction < ApplicationAction
1745
2385
  super(options.merge h)
1746
2386
 
1747
2387
  end
2388
+
2389
+ def to_s()
2390
+ 'Launch ' + @h[:application_name]
2391
+ end
1748
2392
 
1749
2393
  end
1750
2394
 
@@ -1770,6 +2414,8 @@ end
1770
2414
  class OpenWebPageAction < ApplicationAction
1771
2415
 
1772
2416
  def initialize(h={})
2417
+
2418
+ h[:url_to_open] = h[:url] if h[:url]
1773
2419
 
1774
2420
  options = {
1775
2421
  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},
@@ -1779,9 +2425,13 @@ class OpenWebPageAction < ApplicationAction
1779
2425
  block_next_action: false
1780
2426
  }
1781
2427
 
1782
- super(options.merge h)
2428
+ super(options.merge filter(options,h))
1783
2429
 
1784
2430
  end
2431
+
2432
+ def to_s()
2433
+ "HTTP GET\n url: " + @h[:url_to_open]
2434
+ end
1785
2435
 
1786
2436
  end
1787
2437
 
@@ -1829,6 +2479,15 @@ class TakePictureAction < CameraAction
1829
2479
  super(options.merge h)
1830
2480
 
1831
2481
  end
2482
+
2483
+ def to_pc()
2484
+ camera = @h[:use_front_camera] ? :front : :back
2485
+ 'take_photo :' + camera.to_s
2486
+ end
2487
+
2488
+ def to_s()
2489
+ 'Take Picture'
2490
+ end
1832
2491
 
1833
2492
  end
1834
2493
 
@@ -1857,6 +2516,11 @@ class SetWifiAction < ConnectivityAction
1857
2516
  super(options.merge h)
1858
2517
 
1859
2518
  end
2519
+
2520
+ def to_s()
2521
+ action = @h[:state] == 0 ? 'Enable' : 'Disable'
2522
+ action + ' Wifi'
2523
+ end
1860
2524
 
1861
2525
  end
1862
2526
 
@@ -1987,6 +2651,20 @@ class SayTimeAction < DateTimeAction
1987
2651
  super(options.merge h)
1988
2652
 
1989
2653
  end
2654
+
2655
+ def invoke()
2656
+ time = ($env and $env[:time]) ? $env[:time] : Time.now
2657
+ tformat = @h['12_hour'] ? "%-I:%M%P" : "%H:%M"
2658
+ super(time.strftime(tformat))
2659
+ end
2660
+
2661
+ def to_pc()
2662
+ 'say current_time()'
2663
+ end
2664
+
2665
+ def to_s()
2666
+ 'Say Current Time'
2667
+ end
1990
2668
 
1991
2669
  end
1992
2670
 
@@ -2067,6 +2745,10 @@ class SpeakTextAction < DeviceAction
2067
2745
  super(options.merge h)
2068
2746
 
2069
2747
  end
2748
+
2749
+ def to_s()
2750
+ "Speak Text (%s)" % @h[:text_to_say]
2751
+ end
2070
2752
 
2071
2753
  end
2072
2754
 
@@ -2158,6 +2840,14 @@ class CameraFlashLightAction < DeviceSettingsAction
2158
2840
 
2159
2841
  end
2160
2842
 
2843
+ def to_pc()
2844
+ 'torch :on'
2845
+ end
2846
+
2847
+ def to_s()
2848
+ 'Torch On'
2849
+ end
2850
+
2161
2851
  end
2162
2852
 
2163
2853
  # Category: Device Settings
@@ -2495,6 +3185,10 @@ class PlaySoundAction < MediaAction
2495
3185
  super(options.merge h)
2496
3186
 
2497
3187
  end
3188
+
3189
+ def to_s()
3190
+ 'Play: ' + @h[:file_path]
3191
+ end
2498
3192
 
2499
3193
  end
2500
3194
 
@@ -2699,6 +3393,9 @@ end
2699
3393
  class NotificationAction < NotificationsAction
2700
3394
 
2701
3395
  def initialize(h={})
3396
+
3397
+ h[:notification_subject] = h[:subject] if h[:subject]
3398
+ h[:notification_text] = h[:text] if h[:text]
2702
3399
 
2703
3400
  options = {
2704
3401
  ringtone_name: 'Default',
@@ -2714,9 +3411,13 @@ class NotificationAction < NotificationsAction
2714
3411
  run_macro_when_pressed: false
2715
3412
  }
2716
3413
 
2717
- super(options.merge h)
3414
+ super(options.merge filter(options, h))
2718
3415
 
2719
3416
  end
3417
+
3418
+ def to_s()
3419
+ 'Display Notification: ' + "%s: %s" % [@h[:notification_subject], @h[:notification_text]]
3420
+ end
2720
3421
 
2721
3422
  end
2722
3423
 
@@ -2750,6 +3451,10 @@ class ToastAction < NotificationsAction
2750
3451
  super(@h[:message_text])
2751
3452
  end
2752
3453
 
3454
+ def to_pc()
3455
+ "popup_message '%s'" % @h[:message_text]
3456
+ end
3457
+
2753
3458
  def to_s()
2754
3459
  "Popup Message '%s'" % @h[:message_text]
2755
3460
  end
@@ -2945,6 +3650,10 @@ end
2945
3650
 
2946
3651
  # Category: Screen
2947
3652
  #
3653
+ # options:
3654
+ # keep awake, screen on => enabled: true
3655
+ # disable keep awake => enabled: false
3656
+ #
2948
3657
  class KeepAwakeAction < ScreenAction
2949
3658
 
2950
3659
  def initialize(h={})
@@ -2959,7 +3668,30 @@ class KeepAwakeAction < ScreenAction
2959
3668
  super(options.merge h)
2960
3669
 
2961
3670
  end
2962
-
3671
+
3672
+ def to_s()
3673
+
3674
+ screen = @h[:screen_option] == 0 ? 'Screen On' : 'Screen Off'
3675
+
3676
+ if @h[:enabled] then
3677
+
3678
+ whenx = if @h[:seconds_to_stay_awake_for] == 0 then
3679
+
3680
+ 'Until Disabled'
3681
+
3682
+ else
3683
+ scnds = @h[:seconds_to_stay_awake_for]
3684
+ Subunit.new(units={minutes:60, hours:60}, seconds: scnds).strfunit("%x")
3685
+ end
3686
+
3687
+ 'Keep Device Awake ' + screen + ' ' + whenx
3688
+
3689
+ else
3690
+ 'Disable Keep Awake'
3691
+ end
3692
+
3693
+
3694
+ end
2963
3695
  end
2964
3696
 
2965
3697
  # Category: Screen
@@ -3489,6 +4221,18 @@ class AirplaneModeConstraint < Constraint
3489
4221
  end
3490
4222
 
3491
4223
  end
4224
+
4225
+ def to_pc()
4226
+ status = @h[:enabled] ? 'enabled?' : 'disabled?'
4227
+ 'airplane_mode.' + status
4228
+ end
4229
+
4230
+ def to_s()
4231
+
4232
+ status = @h[:enabled] ? 'Enabled' : 'Disabled'
4233
+ 'Airplane Mode ' + status
4234
+
4235
+ end
3492
4236
 
3493
4237
  end
3494
4238