ruby-macrodroid 0.5.0 → 0.7.1

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: fc528ce6042bfe75807bb76fcc73cc255b45b6fe0bf138d4a643a5facd7fdaf1
4
- data.tar.gz: 294fed6fbe1ff71b05ea4384f9070e5795ceb1c41979bba419f89a4273164124
3
+ metadata.gz: a2f43b1d737df8ffc21bb31f9a7df56964963ba3b88a6e51e008c45ca0d4e5f6
4
+ data.tar.gz: 6e587a62b683867b37f28dfba70728971e19647cfa803a804ea123c1eb0955b3
5
5
  SHA512:
6
- metadata.gz: 76925a5b3cba1509ec62635c04d5d796ded37f2e8cee958748a59c88938876129853adc91df55281e594b6c9043ba5f8fdaba2307a9ff4727d70cf611f4a2d8f
7
- data.tar.gz: 55f01e2512265e988d14cc63f576d18a5cc6fdee2a3d109ed47b0ef54bf3d4947ca4064696e405619bb5c2b5e545866fa771d040a13964e205bd343c7b111325
6
+ metadata.gz: b247e2d22eac6c837d9bcacf38697b63ab80a41c2affca2adb7389b39423813c9d1816f9c9dd697a013ad75b5417ca2143ca4e040be819a09a67a939765aae2f
7
+ data.tar.gz: 1a91db74afbbfbe1622d3e19241c9a99ddd05c0ef975e7ac037102104497d12a191c305a4ceb9ab0f5d7d7d476060398b4648ea927bc3135ab52b5633f562e0f
Binary file
data.tar.gz.sig CHANGED
Binary file
@@ -2,11 +2,126 @@
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'
6
115
  require 'rxfhelper'
7
116
  require 'chronic_cron'
8
117
 
9
118
 
119
+ MODEL =<<EOF
120
+ device
121
+ connectivity
122
+ airplane_mode is disabled
123
+ EOF
124
+
10
125
  class TriggersNlp
11
126
  include AppRoutes
12
127
 
@@ -20,11 +135,50 @@ class TriggersNlp
20
135
 
21
136
  def triggers(params)
22
137
 
23
- get /^at (\d+:\d+(?:[ap]m)?) on (.*)/i do |time, days|
138
+ get /^(?:at )?(\d+:\d+(?:[ap]m)?) (?:on )?(.*)/i do |time, days|
24
139
  [TimerTrigger, {time: time, days: days}]
25
140
  end
26
141
 
142
+ # time.is? 'at 18:30pm on Mon or Tue'
143
+ get /^time.is\? ['"](?:at )?(\d+:\d+(?:[ap]m)?) (?:on )?(.*)['"]/i do |time, days|
144
+ [TimerTrigger, {time: time, days: days.gsub(' or ',', ')}]
145
+ end
146
+
147
+ get /^shake[ _]device\??$/i do
148
+ [ShakeDeviceTrigger, {}]
149
+ end
150
+
151
+ get /^Flip Device (.*)$/i do |motion|
152
+ facedown = motion =~ /Face Up (?:->|to) Face Down/i
153
+ [FlipDeviceTrigger, {face_down: facedown }]
154
+ end
155
+
156
+ get /^flip_device_down\?$/i do
157
+ [FlipDeviceTrigger, {face_down: true }]
158
+ end
27
159
 
160
+ get /^flip_device_up\?$/i do
161
+ [FlipDeviceTrigger, {face_down: false }]
162
+ end
163
+
164
+ get /^Failed Login Attempt$/i do
165
+ [FailedLoginTrigger, {}]
166
+ end
167
+
168
+ get /^failed_login?$/i do
169
+ [FailedLoginTrigger, {}]
170
+ end
171
+
172
+ get /^Geofence (Entry|Exit) \(([^\)]+)/i do |direction, name|
173
+ enter_area = direction.downcase.to_sym == :entry
174
+ [GeofenceTrigger, {name: name, enter_area: enter_area}]
175
+ end
176
+
177
+ get /^location (entered|exited) \(([^\)]+)/i do |direction, name|
178
+ enter_area = direction.downcase.to_sym == :entered
179
+ [GeofenceTrigger, {name: name, enter_area: enter_area}]
180
+ end
181
+
28
182
  end
29
183
 
30
184
  alias find_trigger run_route
@@ -44,10 +198,79 @@ class ActionsNlp
44
198
 
45
199
  def actions(params)
46
200
 
201
+ # e.g. message popup: hello world!
47
202
  get /^message popup: (.*)/i do |msg|
48
203
  [ToastAction, {msg: msg}]
49
204
  end
50
205
 
206
+ # e.g. Popup Message 'hello world!'
207
+ get /^Popup[ _]Message ['"]([^'"]+)/i do |msg|
208
+ [ToastAction, {msg: msg}]
209
+ end
210
+
211
+ # e.g. say current time
212
+ get /^say current[ _]time/i do
213
+ [SayTimeAction, {}]
214
+ end
215
+
216
+ get /^Torch :?(.*)/i do |onoffstate|
217
+ state = onoffstate.downcase == 'on' ? 0 : 1
218
+ [CameraFlashLightAction, {state: state}]
219
+ end
220
+
221
+ get /^Take Picture/i do
222
+ [TakePictureAction, {}]
223
+ end
224
+
225
+ get /^take_picture/i do
226
+ [TakePictureAction, {}]
227
+ end
228
+
229
+ # e.g. Display Notification: Hi there: This is the body of the message
230
+ get /^Display Notification: ([^:]+): [^$]+$/i do |subject, text|
231
+ [NotificationAction, {subject: subject, text: text}]
232
+ end
233
+
234
+
235
+ # e.g. Enable Wifi
236
+ get /^(Enable|Disable) Wifi$/i do |raw_state|
237
+
238
+ state = raw_state.downcase.to_sym == :enable ? 0 : 1
239
+ [SetWifiAction, {state: state}]
240
+
241
+ end
242
+
243
+ # e.g. Play: Altair
244
+ get /^Play: (.*)$/i do |name|
245
+
246
+ [PlaySoundAction, {file_path: name}]
247
+
248
+ end
249
+
250
+ # e.g. Launch Settings
251
+ get /^Launch (.*)$/i do |application|
252
+
253
+ h = {
254
+ application_name: application,
255
+ package_to_launch: 'com.android.' + application.downcase
256
+ }
257
+ [LaunchActivityAction, h]
258
+
259
+ end
260
+
261
+ # e.g. HTTP GET http://someurl.com/something
262
+ get /^HTTP GET ([^$]+)$/i do |url|
263
+
264
+ [OpenWebPageAction, url_to_open: url]
265
+
266
+ end
267
+
268
+ # e.g. webhook entered_kitchen
269
+ #
270
+ get /webhook|HTTP GET/i do
271
+ [OpenWebPageAction, {}]
272
+ end
273
+
51
274
 
52
275
  end
53
276
 
@@ -136,11 +359,11 @@ class Macro
136
359
  using Params
137
360
 
138
361
  attr_reader :local_variables, :triggers, :actions, :constraints, :guid
139
- attr_accessor :title
362
+ attr_accessor :title, :description
140
363
 
141
- def initialize(name=nil, debug: false)
364
+ def initialize(name=nil, geofences: geofences, debug: false)
142
365
 
143
- @title, @debug = name, debug
366
+ @title, @geofences, @debug = name, geofences, debug
144
367
 
145
368
  puts 'inside Macro#initialize' if @debug
146
369
 
@@ -178,7 +401,7 @@ class Macro
178
401
  m_action_list: @actions.map(&:to_h),
179
402
  m_constraint_list: @constraints.map(&:to_h),
180
403
  m_description: '',
181
- m_name: @title,
404
+ m_name: title(),
182
405
  m_excludeLog: false,
183
406
  m_GUID: guid(),
184
407
  m_isOrCondition: false,
@@ -194,6 +417,14 @@ class Macro
194
417
 
195
418
  def import_h(h)
196
419
 
420
+ if @debug then
421
+ puts 'inside import_h'
422
+ puts 'h:' + h.inspect
423
+ end
424
+
425
+ @title = h[:name]
426
+ @description = h[:description]
427
+
197
428
  # fetch the local variables
198
429
  @local_variables = h['local_variables']
199
430
 
@@ -223,18 +454,20 @@ class Macro
223
454
  end
224
455
 
225
456
  def import_xml(node)
226
-
457
+
227
458
  if @debug then
228
459
  puts 'inside Macro#import_xml'
229
460
  puts 'node: ' + node.xml.inspect
230
461
  end
231
462
 
232
- @title = node.attributes[:name]
233
-
234
463
  if node.element('triggers') then
235
-
464
+
236
465
  # level 2
237
466
 
467
+ @title = node.attributes[:name]
468
+ @description = node.attributes[:description]
469
+
470
+
238
471
  # get all the triggers
239
472
  @triggers = node.xpath('triggers/*').map do |e|
240
473
 
@@ -270,6 +503,12 @@ class Macro
270
503
 
271
504
  # Level 1
272
505
 
506
+ puts 'import_xml: inside level 1' if @debug
507
+
508
+ @title = node.text('macro') || node.attributes[:name]
509
+
510
+ #@description = node.attributes[:description]
511
+
273
512
  tp = TriggersNlp.new
274
513
 
275
514
  @triggers = node.xpath('trigger').map do |e|
@@ -279,7 +518,11 @@ class Macro
279
518
  puts 'found trigger ' + r.inspect if @debug
280
519
 
281
520
  if r then
282
- r[0].new(r[1])
521
+ if r[0] == GeofenceTrigger then
522
+ GeofenceTrigger.new(r[1], geofences: @geofences)
523
+ else
524
+ r[0].new(r[1])
525
+ end
283
526
  end
284
527
 
285
528
  end
@@ -288,11 +531,21 @@ class Macro
288
531
 
289
532
  @actions = node.xpath('action').map do |e|
290
533
 
534
+ puts 'action e: ' + e.xml.inspect if @debug
291
535
  r = ap.find_action e.text
292
536
  puts 'found action ' + r.inspect if @debug
293
537
 
294
538
  if r then
295
- r[0].new(r[1])
539
+
540
+ a = e.xpath('item/*')
541
+
542
+ h = if a.any? then
543
+ a.map {|node| [node.name.to_sym, node.text.to_s]}.to_h
544
+ else
545
+ {}
546
+ end
547
+
548
+ r[0].new(r[1].merge(h))
296
549
  end
297
550
 
298
551
  end
@@ -316,16 +569,16 @@ class Macro
316
569
 
317
570
  end
318
571
 
319
- def match?(triggerx, detail={time: $env[:time]} )
572
+ def match?(triggerx, detail={time: $env[:time]}, model=nil )
320
573
 
321
- if @triggers.any? {|x| x.type == triggerx and x.match?(detail) } then
574
+ if @triggers.any? {|x| x.type == triggerx and x.match?(detail, model) } then
322
575
 
323
576
  if @debug then
324
577
  puts 'checking constraints ...'
325
578
  puts '@constraints: ' + @constraints.inspect
326
579
  end
327
580
 
328
- if @constraints.all? {|x| x.match?($env.merge(detail)) } then
581
+ if @constraints.all? {|x| x.match?($env.merge(detail), model) } then
329
582
 
330
583
  true
331
584
 
@@ -339,9 +592,65 @@ class Macro
339
592
 
340
593
  end
341
594
 
595
+ # invokes the actions
596
+ #
342
597
  def run()
343
598
  @actions.map(&:invoke)
344
- end
599
+ end
600
+
601
+ # prepares the environment in order for triggers to test fire successfully
602
+ # Used for testing
603
+ #
604
+ def set_env()
605
+ @triggers.each(&:set_env)
606
+ end
607
+
608
+ def to_pc()
609
+
610
+ heading = '# ' + @title
611
+ heading += '\n# ' + @description if @description
612
+ condition = @triggers.first.to_pc
613
+ actions = @actions.map(&:to_pc).join("\n")
614
+
615
+ <<EOF
616
+ #{heading}
617
+
618
+ if #{condition} then
619
+ #{actions}
620
+ end
621
+ EOF
622
+ end
623
+
624
+ def to_s()
625
+
626
+ a = [
627
+ 'm: ' + @title,
628
+ @triggers.map {|x| "t: %s" % x}.join("\n"),
629
+ @actions.map {|x| "a: %s" % x}.join("\n"),
630
+ @constraints.map {|x| "a: %s" % x}.join("\n")
631
+ ]
632
+
633
+ if @description and @description.length >= 1 then
634
+ a.insert(1, 'd: ' + @description)
635
+ end
636
+
637
+ a.join("\n")
638
+
639
+ end
640
+
641
+ def to_summary()
642
+
643
+ a = [
644
+ 'm: ' + @title,
645
+ 't: ' + @triggers.map(&:to_s).join(", "),
646
+ 'a: ' + @actions.map(&:to_s).join(", "),
647
+ ]
648
+
649
+ a << 'c: ' + @constraints.map(&:to_s).join(", ") if @constraints.any?
650
+
651
+ a.join("\n") + "\n"
652
+
653
+ end
345
654
 
346
655
  private
347
656
 
@@ -353,34 +662,86 @@ class Macro
353
662
 
354
663
  puts ('inside object h:' + h.inspect).debug if @debug
355
664
  klass = Object.const_get h[:class_type]
356
- klass.new h
665
+ puts klass.inspect.highlight if $debug
666
+
667
+ if klass == GeofenceTrigger then
668
+ puts 'GeofenceTrigger found'.highlight if $debug
669
+ GeofenceTrigger.new(h, geofences: @geofences)
670
+ else
671
+ klass.new h
672
+ end
673
+
357
674
  end
358
675
 
359
676
  end
360
677
 
361
678
 
679
+ class MacroDroidError < Exception
680
+ end
681
+
362
682
  class MacroDroid
363
683
  using ColouredText
364
684
  using Params
365
685
 
366
- attr_reader :macros
686
+ attr_reader :macros, :geofences
367
687
 
368
688
  def initialize(obj=nil, debug: false)
369
689
 
370
690
  @debug = debug
371
691
 
692
+ @geofences = {}
693
+
372
694
  if obj then
373
695
 
374
- s, _ = RXFHelper.read(obj)
696
+ raw_s, _ = RXFHelper.read(obj)
697
+
698
+ s = raw_s.strip
375
699
 
376
700
  if s[0] == '{' then
701
+
377
702
  import_json(s)
703
+
378
704
  elsif s[0] == '<'
705
+
379
706
  import_xml(s)
380
707
  @h = build_h
708
+
381
709
  else
382
- import_xml(text_to_xml(s))
710
+
711
+ puts 's: ' + s.inspect if @debug
712
+
713
+ if s =~ /m(?:acro)?:\s/ then
714
+
715
+ puts 'before RowX.new' if @debug
716
+
717
+ s2 = s.gsub(/^g:/,'geofence:').gsub(/^m:/,'macro:')\
718
+ .gsub(/^t:/,'trigger:').gsub(/^a:/,'action:')\
719
+ .gsub(/^c:/,'constraint:').gsub(/^#.*/,'')
720
+
721
+ raw_macros, raw_geofences = s2.split(/(?=^macro:)/,2).reverse
722
+
723
+ if raw_geofences then
724
+
725
+ geoxml = RowX.new(raw_geofences).to_xml
726
+
727
+ geodoc = Rexle.new(geoxml)
728
+ geofences = geodoc.root.xpath('item/geofence')
729
+ @geofences = fetch_geofences(geofences) if geofences.any?
730
+
731
+ end
732
+
733
+ xml = RowX.new(raw_macros).to_xml
734
+ import_rowxml(xml)
735
+
736
+ elsif s =~ /^# /
737
+ xml = pc_to_xml(s)
738
+ import_xml(xml)
739
+ else
740
+ raise MacroDroidError, 'invalid input'
741
+ end
742
+
383
743
  @h = build_h
744
+
384
745
  end
385
746
 
386
747
  else
@@ -432,15 +793,85 @@ class MacroDroid
432
793
 
433
794
  alias to_json export_json
434
795
 
435
- def import_json(s)
436
796
 
437
- @h = JSON.parse(s, symbolize_names: true).to_snake_case
438
- puts ('@h: ' + @h.pretty_inspect).debug if @debug
797
+ def to_h()
798
+
799
+ h = {
800
+ geofence_data: {
801
+ geofence_map: @geofences.map {|key, value| [key, value.to_h] }.to_h
802
+ },
803
+ macro_list: @macros.map(&:to_h)
804
+ }
805
+ @h.merge(h).to_camel_case
806
+
807
+ end
808
+
809
+ # returns pseudocode
810
+ #
811
+ def to_pc()
812
+ @macros.map(&:to_pc).join("\n\n")
813
+ end
814
+
815
+ def to_s()
816
+ @macros.map(&:to_s).join("\n")
817
+ end
818
+
819
+ def to_summary()
820
+ @macros.map(&:to_summary).join("\n")
821
+ end
822
+
823
+ private
824
+
825
+ def fetch_geofences(nodes)
826
+
827
+ nodes.map do |e|
828
+
829
+ name = e.text.to_s.strip
830
+ item = e.element('item')
831
+ coordinates = item.text('coordinates')
832
+ latitude, longitude = coordinates.split(/, */,2)
833
+ radius = item.text('radius')
834
+
835
+ id = UUID.new.generate
836
+
837
+ h = {
838
+ name: name,
839
+ longitude: longitude,
840
+ latitude: latitude,
841
+ radius: radius,
842
+ id: id
843
+ }
844
+
845
+ [id.to_sym, GeofenceMap.new(h)]
846
+
847
+ end.to_h
439
848
 
849
+ end
850
+
851
+ def import_json(s)
852
+
853
+ h = JSON.parse(s, symbolize_names: true)
854
+ puts 'json_to_yaml: ' + h.to_yaml if @debug
855
+
856
+ @h = h.to_snake_case
857
+ puts ('@h: ' + @h.inspect).debug if @debug
858
+
859
+
860
+ # fetch the geofence data
861
+ if @h[:geofence_data] then
862
+
863
+ @geofences = @h[:geofence_data][:geofence_map].map do |id, properties|
864
+ [id, GeofenceMap.new(properties)]
865
+ end.to_h
866
+
867
+ end
868
+
440
869
  @macros = @h[:macro_list].map do |macro|
441
870
 
442
- puts ('macro: ' + macro.pretty_inspect).debug if @debug
443
- m = Macro.new(debug: @debug)
871
+ puts ('macro: ' + macro.inspect).debug if @debug
872
+ # puts '@geofences: ' + @geofences.inspect if @debug
873
+
874
+ m = Macro.new(geofences: @geofences.map(&:last), debug: @debug )
444
875
  m.import_h(macro)
445
876
  m
446
877
 
@@ -450,57 +881,92 @@ class MacroDroid
450
881
 
451
882
  end
452
883
 
884
+ def import_rowxml(raws)
885
+
886
+ s = RXFHelper.read(raws).first
887
+ puts 's: ' + s.inspect if @debug
888
+ doc = Rexle.new(s)
889
+ puts 'after doc' if @debug
890
+ puts 'import_rowxml: @geofences: ' + @geofences.inspect if @debug
891
+ geofences = @geofences
892
+
893
+ @macros = doc.root.xpath('item').map do |node|
894
+ puts ('geofences: ' + geofences.inspect).highlight if @debug
895
+ Macro.new(geofences: geofences.map(&:last), debug: @debug).import_xml(node)
896
+
897
+ end
898
+
899
+ end
900
+
453
901
  def import_xml(raws)
454
902
 
903
+ if @debug then
904
+ puts 'inside import_xml'
905
+
906
+ puts 'raws: ' + raws.inspect
907
+ end
455
908
  s = RXFHelper.read(raws).first
456
909
  puts 's: ' + s.inspect if @debug
457
910
  doc = Rexle.new(s)
458
- puts 'after doc' if @debug
459
911
 
912
+ if @debug then
913
+ puts 'doc: ' + doc.root.xml
914
+ end
915
+
460
916
  @macros = doc.root.xpath('macro').map do |node|
461
917
 
462
- macro = Macro.new @title, debug: @debug
463
- macro.import_xml(node)
464
- macro
918
+ Macro.new(geofences: @geofences.map(&:last), debug: @debug).import_xml(node)
465
919
 
466
920
  end
467
921
  end
468
922
 
469
- def text_to_xml(s)
923
+ def pc_to_xml(s)
470
924
 
471
- a = s.split(/.*(?=^m:)/); a.shift
472
- a.map!(&:chomp)
473
-
474
- macros = a.map do |x|
475
-
476
- lines = x.lines
477
- puts 'lines: ' + lines.inspect if @debug
478
-
479
- name = lines.shift[/^m: +(.*)/,1]
480
- h = {t: [], a: [], c: []}
925
+ macros = s.strip.split(/(?=#)/).map do |raw_macro|
481
926
 
482
- lines.each {|line| h[line[0].to_sym] << line[/^\w: +(.*)/,1] }
483
- triggers = h[:t].map {|text| [:trigger, {}, text]}
484
- actions = h[:a].map {|text| [:action, {}, text]}
485
- constraints = h[:c].map {|text| [:constraint, {}, text]}
927
+ a = raw_macro.lines
928
+ name = a.shift[/(?<=# ).*/]
929
+ description = a.shift[/(?<=# ).*/] if a[0][/^# /]
930
+ body = a.join.strip
486
931
 
487
- [:macro, {name: name},'', *triggers, *actions, *constraints]
932
+ a2 = body.lines
933
+ # get the trigger
934
+ trigger = [:trigger, {}, a2[0][/^if (.*) then/,1]]
935
+ action = [:action, {}, a2[1].strip]
936
+ [:macro, {name: name, description: description}, trigger, action, []]
488
937
 
489
938
  end
490
939
 
491
940
  doc = Rexle.new([:macros, {}, '', *macros])
492
941
  doc.root.xml pretty: true
493
942
 
494
- end
495
-
496
- def to_h()
943
+ end
497
944
 
498
- @h.merge(macro_list: @macros.map(&:to_h)).to_camel_case
945
+ end
499
946
 
947
+ class GeofenceMap
948
+
949
+ attr_accessor :name, :longitude, :latitude, :radius, :id
950
+
951
+ def initialize(id: '', longitude: '', latitude: '', name: '', radius: '')
952
+
953
+ @id, @latitude, @longitude, @name, @radius = id, latitude, \
954
+ longitude, name, radius
955
+
500
956
  end
501
-
502
-
503
-
957
+
958
+ def to_h()
959
+
960
+ {
961
+ id: @id,
962
+ longitude: @longitude,
963
+ latitude: @latitude,
964
+ name: @name,
965
+ radius: @radius
966
+ }
967
+
968
+ end
969
+
504
970
  end
505
971
 
506
972
  class MacroObject
@@ -511,6 +977,8 @@ class MacroObject
511
977
 
512
978
  def initialize(h={})
513
979
 
980
+ $env ||= {}
981
+
514
982
  @h = {constraint_list: [], is_or_condition: false,
515
983
  is_disabled: false}.merge(h)
516
984
  @list = []
@@ -539,6 +1007,10 @@ class MacroObject
539
1007
  h2.merge('m_classType' => self.class.to_s)
540
1008
 
541
1009
  end
1010
+
1011
+ def to_s()
1012
+ "#<%s %s>" % [self.class, @h.inspect]
1013
+ end
542
1014
 
543
1015
  protected
544
1016
 
@@ -562,10 +1034,11 @@ class Trigger < MacroObject
562
1034
  @list << 'fakeIcon'
563
1035
  end
564
1036
 
565
- def match?(detail={})
1037
+ def match?(detail={}, model=nil)
1038
+
1039
+ # only match where the key exists in the trigger object
1040
+ detail.select {|k,v| @h.include? k }.all? {|key,value| @h[key] == value}
566
1041
 
567
- detail.all? {|key,value| @h[key] == value}
568
-
569
1042
  end
570
1043
 
571
1044
  end
@@ -1052,27 +1525,50 @@ class TimerTrigger < Trigger
1052
1525
  use_alarm: false
1053
1526
  }
1054
1527
 
1055
- super(options.merge filter(options,h))
1528
+ super(options.merge filter(options, h))
1056
1529
 
1057
1530
  end
1058
1531
 
1059
- def match?(detail={time: $env[:time]})
1532
+ def match?(detail={time: $env[:time]}, model=nil)
1533
+
1534
+ time() == detail[:time]
1060
1535
 
1061
- a = @h[:days_of_week]
1062
- a.unshift a.pop
1536
+ end
1537
+
1538
+ # sets the environmental conditions for this trigger to fire
1539
+ #
1540
+ def set_env()
1541
+ $env[:time] = time()
1542
+ end
1543
+
1544
+ def to_pc()
1545
+ "time.is? '%s'" % self.to_s.gsub(',', ' or')
1546
+ end
1547
+
1548
+ def to_s()
1549
+
1550
+ dow = @h[:days_of_week]
1063
1551
 
1064
- dow = a.map.with_index {|x, i| x ? i : nil }.compact.join(',')
1552
+ a = Date::ABBR_DAYNAMES
1065
1553
 
1066
- s = "%s %s * * %s" % [@h[:minute], @h[:hour], dow]
1554
+ time = Time.parse("%s:%s" % [@h[:hour], @h[:minute]]).strftime("%-H:%M%P")
1555
+ days = (a[1..-1] << a.first).zip(dow).select {|_,b| b}.map(&:first)
1067
1556
 
1068
- if $debug then
1069
- puts 's: ' + s.inspect
1070
- puts 'detail: ' + detail.inspect
1071
- puts '@h: ' + @h.inspect
1072
- end
1557
+ "at %s on %s" % [time, days.join(', ')]
1558
+ end
1559
+
1560
+ private
1561
+
1562
+ def time()
1073
1563
 
1074
- ChronicCron.new(s, detail[:time]).to_time == detail[:time]
1564
+ a = @h[:days_of_week].clone
1565
+ a.unshift a.pop
1075
1566
 
1567
+ dow = a.map.with_index {|x, i| x ? i : nil }.compact.join(',')
1568
+ s = "%s %s * * %s" % [@h[:minute], @h[:hour], dow]
1569
+ recent_time = ($env && $env[:time]) ? $env[:time] : Time.now
1570
+ ChronicCron.new(s, recent_time).to_time
1571
+
1076
1572
  end
1077
1573
 
1078
1574
  end
@@ -1145,6 +1641,15 @@ class RegularIntervalTrigger < Trigger
1145
1641
 
1146
1642
  end
1147
1643
 
1644
+ class DeviceEventsTrigger < Trigger
1645
+
1646
+ def initialize(h={})
1647
+ super(h)
1648
+ @group = 'device_events'
1649
+ end
1650
+
1651
+ end
1652
+
1148
1653
  # Category: Device Events
1149
1654
  #
1150
1655
  # Airplane Mode Changed
@@ -1156,7 +1661,7 @@ end
1156
1661
  # shorthand example:
1157
1662
  # airplanemode: enabled
1158
1663
  #
1159
- class AirplaneModeTrigger < Trigger
1664
+ class AirplaneModeTrigger < DeviceEventsTrigger
1160
1665
 
1161
1666
  def initialize(h={})
1162
1667
 
@@ -1172,7 +1677,7 @@ end
1172
1677
 
1173
1678
  # Category: Device Events
1174
1679
  #
1175
- class AutoSyncChangeTrigger < Trigger
1680
+ class AutoSyncChangeTrigger < DeviceEventsTrigger
1176
1681
 
1177
1682
  def initialize(h={})
1178
1683
 
@@ -1188,7 +1693,7 @@ end
1188
1693
 
1189
1694
  # Category: Device Events
1190
1695
  #
1191
- class DayDreamTrigger < Trigger
1696
+ class DayDreamTrigger < DeviceEventsTrigger
1192
1697
 
1193
1698
  def initialize(h={})
1194
1699
 
@@ -1204,7 +1709,7 @@ end
1204
1709
 
1205
1710
  # Category: Device Events
1206
1711
  #
1207
- class DockTrigger < Trigger
1712
+ class DockTrigger < DeviceEventsTrigger
1208
1713
 
1209
1714
  def initialize(h={})
1210
1715
 
@@ -1220,7 +1725,30 @@ end
1220
1725
 
1221
1726
  # Category: Device Events
1222
1727
  #
1223
- class GPSEnabledTrigger < Trigger
1728
+ class FailedLoginTrigger < DeviceEventsTrigger
1729
+
1730
+ def initialize(h={})
1731
+
1732
+ options = {
1733
+ num_failures: 1
1734
+ }
1735
+
1736
+ super(options.merge h)
1737
+
1738
+ end
1739
+
1740
+ def to_pc()
1741
+ 'failed_login?'
1742
+ end
1743
+
1744
+ def to_s()
1745
+ 'Failed Login Attempt'
1746
+ end
1747
+ end
1748
+
1749
+ # Category: Device Events
1750
+ #
1751
+ class GPSEnabledTrigger < DeviceEventsTrigger
1224
1752
 
1225
1753
  def initialize(h={})
1226
1754
 
@@ -1236,7 +1764,7 @@ end
1236
1764
 
1237
1765
  # Category: Device Events
1238
1766
  #
1239
- class MusicPlayingTrigger < Trigger
1767
+ class MusicPlayingTrigger < DeviceEventsTrigger
1240
1768
 
1241
1769
  def initialize(h={})
1242
1770
 
@@ -1253,7 +1781,7 @@ end
1253
1781
 
1254
1782
  # Category: Device Events
1255
1783
  #
1256
- class DeviceUnlockedTrigger < Trigger
1784
+ class DeviceUnlockedTrigger < DeviceEventsTrigger
1257
1785
 
1258
1786
  def initialize(h={})
1259
1787
 
@@ -1268,7 +1796,7 @@ end
1268
1796
 
1269
1797
  # Category: Device Events
1270
1798
  #
1271
- class AutoRotateChangeTrigger < Trigger
1799
+ class AutoRotateChangeTrigger < DeviceEventsTrigger
1272
1800
 
1273
1801
  def initialize(h={})
1274
1802
 
@@ -1284,7 +1812,7 @@ end
1284
1812
 
1285
1813
  # Category: Device Events
1286
1814
  #
1287
- class ClipboardChangeTrigger < Trigger
1815
+ class ClipboardChangeTrigger < DeviceEventsTrigger
1288
1816
 
1289
1817
  def initialize(h={})
1290
1818
 
@@ -1301,7 +1829,7 @@ end
1301
1829
 
1302
1830
  # Category: Device Events
1303
1831
  #
1304
- class BootTrigger < Trigger
1832
+ class BootTrigger < DeviceEventsTrigger
1305
1833
 
1306
1834
  def initialize(h={})
1307
1835
 
@@ -1316,7 +1844,7 @@ end
1316
1844
 
1317
1845
  # Category: Device Events
1318
1846
  #
1319
- class IntentReceivedTrigger < Trigger
1847
+ class IntentReceivedTrigger < DeviceEventsTrigger
1320
1848
 
1321
1849
  def initialize(h={})
1322
1850
 
@@ -1336,7 +1864,7 @@ end
1336
1864
 
1337
1865
  # Category: Device Events
1338
1866
  #
1339
- class NotificationTrigger < Trigger
1867
+ class NotificationTrigger < DeviceEventsTrigger
1340
1868
 
1341
1869
  def initialize(h={})
1342
1870
 
@@ -1362,7 +1890,7 @@ end
1362
1890
 
1363
1891
  # Category: Device Events
1364
1892
  #
1365
- class ScreenOnOffTrigger < Trigger
1893
+ class ScreenOnOffTrigger < DeviceEventsTrigger
1366
1894
 
1367
1895
  def initialize(h={})
1368
1896
 
@@ -1378,7 +1906,7 @@ end
1378
1906
 
1379
1907
  # Category: Device Events
1380
1908
  #
1381
- class SilentModeTrigger < Trigger
1909
+ class SilentModeTrigger < DeviceEventsTrigger
1382
1910
 
1383
1911
  def initialize(h={})
1384
1912
 
@@ -1421,8 +1949,15 @@ end
1421
1949
  #
1422
1950
  class GeofenceTrigger < Trigger
1423
1951
 
1424
- def initialize(h={})
1952
+ def initialize( h={}, geofences: {})
1425
1953
 
1954
+ if h[:name] then
1955
+ puts ('geofences2: ' + geofences.inspect)
1956
+ found = geofences.find {|x| x.name.downcase == h[:name].downcase}
1957
+ h[:geofence_id] = found.id if found
1958
+
1959
+ end
1960
+
1426
1961
  options = {
1427
1962
  update_rate_text: '5 Minutes',
1428
1963
  geofence_id: '',
@@ -1431,8 +1966,27 @@ class GeofenceTrigger < Trigger
1431
1966
  enter_area: true
1432
1967
  }
1433
1968
 
1434
- super(options.merge h)
1969
+ super(options.merge filter(options, h))
1970
+ @geofences = geofences
1971
+
1972
+ end
1973
+
1974
+ def to_s()
1975
+
1976
+ if $debug then
1977
+ puts ' @geofences: ' + @geofences.inspect
1978
+ puts '@h: ' + @h.inspect
1979
+ puts '@h[:geofence_id]: ' + @h[:geofence_id].inspect
1980
+ end
1981
+
1982
+ direction = @h[:enter_area] ? 'Entry' : 'Exit'
1983
+
1984
+ found = @geofences.find {|x| x.id == @h[:geofence_id]}
1985
+ puts 'found: ' + found.inspect
1986
+ label = found ? found.name : 'error: name not found'
1435
1987
 
1988
+ "Geofence %s (%s)" % [direction, label]
1989
+
1436
1990
  end
1437
1991
 
1438
1992
  end
@@ -1454,9 +2008,19 @@ class SunriseSunsetTrigger < Trigger
1454
2008
 
1455
2009
  end
1456
2010
 
2011
+
2012
+ class SensorsTrigger < Trigger
2013
+
2014
+ def initialize(h={})
2015
+ super(h)
2016
+ @group = 'sensors'
2017
+ end
2018
+
2019
+ end
2020
+
1457
2021
  # Category: Sensors
1458
2022
  #
1459
- class ActivityRecognitionTrigger < Trigger
2023
+ class ActivityRecognitionTrigger < SensorsTrigger
1460
2024
 
1461
2025
  def initialize(h={})
1462
2026
 
@@ -1473,7 +2037,7 @@ end
1473
2037
 
1474
2038
  # Category: Sensors
1475
2039
  #
1476
- class ProximityTrigger < Trigger
2040
+ class ProximityTrigger < SensorsTrigger
1477
2041
 
1478
2042
  def initialize(h={})
1479
2043
 
@@ -1490,7 +2054,7 @@ end
1490
2054
 
1491
2055
  # Category: Sensors
1492
2056
  #
1493
- class ShakeDeviceTrigger < Trigger
2057
+ class ShakeDeviceTrigger < SensorsTrigger
1494
2058
 
1495
2059
  def initialize(h={})
1496
2060
 
@@ -1500,12 +2064,25 @@ class ShakeDeviceTrigger < Trigger
1500
2064
  super(options.merge h)
1501
2065
 
1502
2066
  end
2067
+
2068
+ def to_pc()
2069
+ 'shake_device?'
2070
+ end
2071
+
2072
+ def to_s()
2073
+ 'Shake Device'
2074
+ end
1503
2075
 
1504
2076
  end
1505
2077
 
1506
2078
  # Category: Sensors
1507
2079
  #
1508
- class FlipDeviceTrigger < Trigger
2080
+ # options:
2081
+ # Face Up -> Face Down
2082
+ # Face Down -> Face Up
2083
+ # Any -> Face Down
2084
+ #
2085
+ class FlipDeviceTrigger < SensorsTrigger
1509
2086
 
1510
2087
  def initialize(h={})
1511
2088
 
@@ -1517,13 +2094,23 @@ class FlipDeviceTrigger < Trigger
1517
2094
 
1518
2095
  super(options.merge h)
1519
2096
 
2097
+ end
2098
+
2099
+ def to_pc()
2100
+ @h[:face_down] ? 'flip_device_down?' : 'flip_device_up?'
1520
2101
  end
2102
+
2103
+ def to_s()
2104
+
2105
+ action = @h[:face_down] ? 'Face Up -> Face Down' : 'Face Down -> Face Up'
2106
+ 'Flip Device ' + action
2107
+ end
1521
2108
 
1522
2109
  end
1523
2110
 
1524
2111
  # Category: Sensors
1525
2112
  #
1526
- class OrientationTrigger < Trigger
2113
+ class OrientationTrigger < SensorsTrigger
1527
2114
 
1528
2115
  def initialize(h={})
1529
2116
 
@@ -1705,6 +2292,10 @@ class LaunchActivityAction < ApplicationAction
1705
2292
  super(options.merge h)
1706
2293
 
1707
2294
  end
2295
+
2296
+ def to_s()
2297
+ 'Launch ' + @h[:application_name]
2298
+ end
1708
2299
 
1709
2300
  end
1710
2301
 
@@ -1730,6 +2321,8 @@ end
1730
2321
  class OpenWebPageAction < ApplicationAction
1731
2322
 
1732
2323
  def initialize(h={})
2324
+
2325
+ h[:url_to_open] = h[:url] if h[:url]
1733
2326
 
1734
2327
  options = {
1735
2328
  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},
@@ -1739,9 +2332,13 @@ class OpenWebPageAction < ApplicationAction
1739
2332
  block_next_action: false
1740
2333
  }
1741
2334
 
1742
- super(options.merge h)
2335
+ super(options.merge filter(options,h))
1743
2336
 
1744
2337
  end
2338
+
2339
+ def to_s()
2340
+ "HTTP GET\n url: " + @h[:url_to_open]
2341
+ end
1745
2342
 
1746
2343
  end
1747
2344
 
@@ -1789,6 +2386,15 @@ class TakePictureAction < CameraAction
1789
2386
  super(options.merge h)
1790
2387
 
1791
2388
  end
2389
+
2390
+ def to_pc()
2391
+ camera = @h[:use_front_camera] ? :front : :back
2392
+ 'take_photo :' + camera.to_s
2393
+ end
2394
+
2395
+ def to_s()
2396
+ 'Take Picture'
2397
+ end
1792
2398
 
1793
2399
  end
1794
2400
 
@@ -1817,6 +2423,11 @@ class SetWifiAction < ConnectivityAction
1817
2423
  super(options.merge h)
1818
2424
 
1819
2425
  end
2426
+
2427
+ def to_s()
2428
+ action = @h[:state] == 0 ? 'Enable' : 'Disable'
2429
+ action + ' Wifi'
2430
+ end
1820
2431
 
1821
2432
  end
1822
2433
 
@@ -1947,6 +2558,20 @@ class SayTimeAction < DateTimeAction
1947
2558
  super(options.merge h)
1948
2559
 
1949
2560
  end
2561
+
2562
+ def invoke()
2563
+ time = ($env and $env[:time]) ? $env[:time] : Time.now
2564
+ tformat = @h['12_hour'] ? "%-I:%M%P" : "%H:%M"
2565
+ super(time.strftime(tformat))
2566
+ end
2567
+
2568
+ def to_pc()
2569
+ 'say current_time()'
2570
+ end
2571
+
2572
+ def to_s()
2573
+ 'Say Current Time'
2574
+ end
1950
2575
 
1951
2576
  end
1952
2577
 
@@ -2118,6 +2743,14 @@ class CameraFlashLightAction < DeviceSettingsAction
2118
2743
 
2119
2744
  end
2120
2745
 
2746
+ def to_pc()
2747
+ 'torch :on'
2748
+ end
2749
+
2750
+ def to_s()
2751
+ 'Torch On'
2752
+ end
2753
+
2121
2754
  end
2122
2755
 
2123
2756
  # Category: Device Settings
@@ -2455,6 +3088,10 @@ class PlaySoundAction < MediaAction
2455
3088
  super(options.merge h)
2456
3089
 
2457
3090
  end
3091
+
3092
+ def to_s()
3093
+ 'Play: ' + @h[:file_path]
3094
+ end
2458
3095
 
2459
3096
  end
2460
3097
 
@@ -2659,6 +3296,9 @@ end
2659
3296
  class NotificationAction < NotificationsAction
2660
3297
 
2661
3298
  def initialize(h={})
3299
+
3300
+ h[:notification_subject] = h[:subject] if h[:subject]
3301
+ h[:notification_text] = h[:text] if h[:text]
2662
3302
 
2663
3303
  options = {
2664
3304
  ringtone_name: 'Default',
@@ -2674,9 +3314,13 @@ class NotificationAction < NotificationsAction
2674
3314
  run_macro_when_pressed: false
2675
3315
  }
2676
3316
 
2677
- super(options.merge h)
3317
+ super(options.merge filter(options, h))
2678
3318
 
2679
3319
  end
3320
+
3321
+ def to_s()
3322
+ 'Display Notification: ' + "%s: %s" % [@h[:notification_subject], @h[:notification_text]]
3323
+ end
2680
3324
 
2681
3325
  end
2682
3326
 
@@ -2709,6 +3353,14 @@ class ToastAction < NotificationsAction
2709
3353
  def invoke()
2710
3354
  super(@h[:message_text])
2711
3355
  end
3356
+
3357
+ def to_pc()
3358
+ "popup_message '%s'" % @h[:message_text]
3359
+ end
3360
+
3361
+ def to_s()
3362
+ "Popup Message '%s'" % @h[:message_text]
3363
+ end
2712
3364
 
2713
3365
  end
2714
3366
 
@@ -3037,6 +3689,30 @@ class Constraint < MacroObject
3037
3689
  def initialize(h={})
3038
3690
  super(h)
3039
3691
  end
3692
+
3693
+ def match?(detail={}, model=nil)
3694
+
3695
+ detail.select {|k,v| @h.include? k }.all? {|key,value| @h[key] == value}
3696
+
3697
+ end
3698
+
3699
+ #def to_s()
3700
+ # ''
3701
+ #end
3702
+
3703
+ protected
3704
+
3705
+ def toggle_match?(key, val)
3706
+
3707
+ if @h[key] == true and val == key.to_s then
3708
+ true
3709
+ elsif @h[key] == false and val != key.to_s
3710
+ true
3711
+ else
3712
+ false
3713
+ end
3714
+
3715
+ end
3040
3716
 
3041
3717
  end
3042
3718
 
@@ -3398,6 +4074,41 @@ class AirplaneModeConstraint < Constraint
3398
4074
  super(options.merge h)
3399
4075
 
3400
4076
  end
4077
+
4078
+ def match?(detail={}, model=nil)
4079
+
4080
+ puts 'inside airplaneModeConstraint#match?' if $debug
4081
+
4082
+ if detail.has_key? :enabled then
4083
+
4084
+ puts 'detail has the key' if $debug
4085
+ super(detail)
4086
+
4087
+ elsif model
4088
+
4089
+ if $debug then
4090
+ puts 'checking the model'
4091
+ switch = model.connectivity.airplane_mode.switch
4092
+ puts 'switch: ' + switch.inspect
4093
+ end
4094
+
4095
+ toggle_match?(:enabled, switch)
4096
+
4097
+ end
4098
+
4099
+ end
4100
+
4101
+ def to_pc()
4102
+ status = @h[:enabled] ? 'enabled?' : 'disabled?'
4103
+ 'airplane_mode.' + status
4104
+ end
4105
+
4106
+ def to_s()
4107
+
4108
+ status = @h[:enabled] ? 'Enabled' : 'Disabled'
4109
+ 'Airplane Mode ' + status
4110
+
4111
+ end
3401
4112
 
3402
4113
  end
3403
4114