ruby-macrodroid 0.5.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
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