ruby-macrodroid 0.5.0 → 0.7.1

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: 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