lightwaverf 0.7 → 0.8.0

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.
@@ -1,5 +1,5 @@
1
- <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
2
- <script src="//www.google.com/jsapi"></script>
1
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
2
+ <script src="https://www.google.com/jsapi"></script>
3
3
  <script>
4
4
  var gauge, gauge_data, gauge_options;
5
5
  google.load( 'visualization', '1.0', { packages: [ 'corechart', 'gauge', 'annotatedtimeline' ] } );
@@ -3,4 +3,3 @@ require 'lightwaverf'
3
3
  config = LightWaveRF.new.get_config
4
4
  config['spreadsheet']['password'] = nil
5
5
  puts JSON.generate config
6
- require 'json'
data/lib/lightwaverf.rb CHANGED
@@ -4,7 +4,6 @@
4
4
  # Get rid of references in yaml cache file - use dup more? Or does it not matter?
5
5
  # Cope with events that start and end in the same run?
6
6
  # Add info about states to timer log
7
- # Build / update cron job automatically
8
7
 
9
8
  require 'yaml'
10
9
  require 'socket'
@@ -12,9 +11,9 @@ require 'net/http'
12
11
  require 'uri'
13
12
  require 'net/https'
14
13
  require 'json'
15
- require 'rexml/document'
16
14
  require 'time'
17
15
  require 'date'
16
+ require 'ri_cal'
18
17
  include Socket::Constants
19
18
 
20
19
  class LightWaveRF
@@ -27,7 +26,6 @@ class LightWaveRF
27
26
  @timers = nil
28
27
  @time = nil
29
28
 
30
- # Display usage info
31
29
  def usage room = nil
32
30
  rooms = self.class.get_rooms self.get_config
33
31
  config = 'usage: lightwaverf ' + rooms.values.first['name'].to_s + ' ' + rooms.values.first['device'].keys.first.to_s + ' on'
@@ -50,10 +48,10 @@ class LightWaveRF
50
48
  # Display help
51
49
  def help
52
50
  help = self.usage + "\n"
53
- help += "your rooms, devices, and sequences, as defined in " + self.get_config_file + ":\n\n"
51
+ help += "your rooms, and devices, as defined in " + self.get_config_file + ":\n\n"
54
52
  help += YAML.dump self.get_config['room']
55
- room = self.get_config['room'].last['name'].to_s
56
- device = self.get_config['room'].last['device'].last.to_s
53
+ room = self.get_config['room'].first['name'].to_s
54
+ device = self.get_config['room'].first['device'].first['name'].to_s
57
55
  help += "\n\nso to turn on " + room + " " + device + " type \"lightwaverf " + room + " " + device + " on\"\n"
58
56
  end
59
57
 
@@ -66,12 +64,31 @@ class LightWaveRF
66
64
  puts 'What is the ip address of your wifi link? (currently "' + self.get_config['host'].to_s + '"). Enter a blank line to broadcast UDP commands (ok to just hit enter here).'
67
65
  host = STDIN.gets.chomp
68
66
  config['host'] = host if ! host.to_s.empty?
69
- puts 'What is the address of your google calendar? (currently "' + self.get_config['calendar'].to_s + '"). Optional (ok to just hit enter here).'
67
+ puts 'What is the address of your calendar ics file? (currently "' + self.get_config['calendar'].to_s + '")'
68
+ puts '(ok to just hit enter here)'
70
69
  calendar = STDIN.gets.chomp
71
70
  config['calendar'] = calendar if ! calendar.to_s.empty?
71
+
72
+ puts 'Do you have an energy monitor? [Y/n]'
73
+ puts '(ok to just hit enter here)'
74
+ monitor = STDIN.gets.chomp.to_s
75
+ if ! monitor.empty?
76
+ puts 'got "' + monitor + '"' if debug
77
+ config['monitor'] = true if monitor.byteslice( 0 ).downcase == 'y'
78
+ puts 'made that into "' + config['monitor'].to_s + '"' if debug
79
+ end
80
+
81
+ puts 'Shall we create a web page on this server? (currently "' + self.get_config['web'].to_s + '"). Optional (ok to just hit enter here)'
82
+ web = STDIN.gets.chomp.to_s
83
+ puts 'got "' + web + '"' if debug
84
+ config['web'] = web if ! web.empty?
85
+ config['web'] = '/tmp/lightwaverf_web.html' if config['web'].to_s.empty?
86
+ puts 'going with "' + config['web'].to_s + '"' if debug
87
+
72
88
  device = 'x'
73
89
  while ! device.to_s.empty?
74
90
  puts 'Enter the name of a room and its devices, space separated. For example "lounge light socket tv". Enter a blank line to finish.'
91
+ puts 'If you already have rooms and devices set up on another lightwaverf app then hit enter here, and "lightwaverf update" first.'
75
92
  if device = STDIN.gets.chomp
76
93
  parts = device.split ' '
77
94
  if !parts[0].to_s.empty? and !parts[1].to_s.empty?
@@ -97,35 +114,69 @@ class LightWaveRF
97
114
  end
98
115
  debug and ( p 'end of configure, config is now ' + config.to_s )
99
116
  file = self.put_config config
117
+
118
+ executable = `which lightwaverf`.chomp
119
+ crontab = `crontab -l`.split( /\n/ ) || [ ]
120
+ crontab = crontab.reject do | line |
121
+ line =~ Regexp.new( Regexp.escape executable )
122
+ end
123
+ crontab << '# new crontab added by `' + executable + ' configure`'
124
+
125
+ if config['monitor']
126
+ crontab << '# ' + executable + ' energy monitor check ever 2 mins + summarise every 5'
127
+ crontab << '*/2 * * * * ' + executable + ' energy > /tmp/lightwaverf_energy.out 2>&1'
128
+ crontab << '*/5 * * * * ' + executable + ' summarise 7 > /tmp/lightwaverf_summarise.out 2>&1'
129
+ end
130
+
131
+ if config['web']
132
+ crontab << '# ' + executable + ' web page generated every hour'
133
+ crontab << '45 * * * * ' + executable + ' web > ' + config['web'] + ' 2> /tmp/lightwaverf_web.out'
134
+ end
135
+
136
+ if config['calendar']
137
+ crontab << '# ' + executable + ' cache timed events 1 hour back 4 hours ahead'
138
+ crontab << '58 * * * * ' + executable + ' update_timers 60 240 > /tmp/lightwaverf_update_timers.out 2>&1'
139
+ crontab << '# ' + executable + ' update_timers on reboot (works for me on raspbian)'
140
+ crontab << '@reboot ' + executable + ' update_timers 60 240 > /tmp/lightwaverf_update_timers.out 2>&1'
141
+ crontab << '# ' + executable + ' timer every 10 mins off peak'
142
+ crontab << '*/10 0-6,9-16,23 * * * ' + executable + ' timer 10 > /tmp/lightwaverf_timer.out 2>&1'
143
+ crontab << '# ' + executable + ' timer every 2 minutes peak'
144
+ crontab << '*/2 7,8,17-22 * * * ' + executable + ' timer 2 > /tmp/lightwaverf_timer.out 2>&1'
145
+ end
146
+
147
+ config['room'].each do | room |
148
+ next unless room['device']
149
+ room['device'].each do | device |
150
+ next unless device['reboot']
151
+ out_file = '/tmp/' + room['name'] + device['name'] + '.reboot.out'
152
+ out_file.gsub! /\s/, ''
153
+ crontab << '@reboot ' + executable + ' ' + room['name'] + ' ' + device['name'] + ' ' + device['reboot'] + ' > ' + out_file + ' 2>&1'
154
+ end
155
+ end
156
+
100
157
  'Saved config file ' + file
101
158
  end
102
159
 
103
- # Config file setter
104
160
  def set_config_file file
105
161
  @config_file = file
106
162
  end
107
163
 
108
- # Config file getter
109
164
  def get_config_file
110
165
  @config_file || File.expand_path('~') + '/lightwaverf-config.yml'
111
166
  end
112
167
 
113
- # Log file getter
114
168
  def get_log_file
115
169
  @log_file || File.expand_path('~') + '/lightwaverf.log'
116
170
  end
117
171
 
118
- # Summary file getter
119
172
  def get_summary_file
120
173
  @summary_file || File.expand_path('~') + '/lightwaverf-summary.json'
121
174
  end
122
175
 
123
- # Timer log file getter
124
176
  def get_timer_log_file
125
177
  @timer_log_file || File.expand_path('~') + '/lightwaverf-timer.log'
126
178
  end
127
179
 
128
- # Timer logger
129
180
  def log_timer_event type, room = nil, device = nil, state = nil, result = false
130
181
  # create log message
131
182
  message = nil
@@ -172,7 +223,7 @@ class LightWaveRF
172
223
  end
173
224
 
174
225
  # Write the config file
175
- def put_config config = { 'room' => [ { 'name' => 'our', 'device' => [ 'light' => { 'name' => 'light' }, 'lights' => { 'name' => 'lights' } ] } ] }
226
+ def put_config config = { 'room' => [ { 'name' => 'default-room', 'device' => [ 'light' => { 'name' => 'default-device' } ] } ] }
176
227
  File.open( self.get_config_file, 'w' ) do | handle |
177
228
  handle.write YAML.dump( config )
178
229
  end
@@ -187,16 +238,6 @@ class LightWaveRF
187
238
  self.put_config
188
239
  end
189
240
  @config = YAML.load_file self.get_config_file
190
- # fix where update made names and devices into arrays
191
- # if @config['room']
192
- # @config['room'].map! do | room |
193
- # room['name'] = room['name'].kind_of?( Array ) ? room['name'][0] : room['name']
194
- # room['device'].map! do | device |
195
- # device = device.kind_of?( Array ) ? device[0] : device
196
- # end
197
- # room
198
- # end
199
- # end
200
241
  end
201
242
  @config
202
243
  end
@@ -215,8 +256,14 @@ class LightWaveRF
215
256
  # wonko - http://lightwaverfcommunity.org.uk/forums/topic/querying-configuration-information-from-the-lightwaverf-website/
216
257
  def update_config email = nil, pin = nil, debug = false
217
258
 
259
+ if ! email && ! pin
260
+ STDERR.puts 'missing email and / or pin'
261
+ STDERR.puts 'usage: lightwaverf update email@email.com 1111'
262
+ return
263
+ end
264
+
218
265
  # Login to LightWaveRF Host server
219
- uri = URI.parse 'https://lightwaverfhost.co.uk/manager/index.php'
266
+ uri = URI.parse 'https://www.lightwaverfhost.co.uk/manager/index.php'
220
267
  http = Net::HTTP.new uri.host, uri.port
221
268
  http.use_ssl = true if uri.scheme == 'https'
222
269
  data = 'pin=' + pin + '&email=' + email
@@ -651,34 +698,16 @@ class LightWaveRF
651
698
  response
652
699
  end
653
700
 
654
- def update_timers past = 60, future = 1440, debug = false
655
- p '----------------'
656
- p 'Updating timers...'
657
-
658
- # determine the window to query
659
- now = Time.new
660
- query_start = now - self.class.to_seconds( past )
661
- query_end = now + self.class.to_seconds( future )
662
-
663
- # url = LightWaveRF.new.get_config['calendar']
701
+ def get_calendar_url debug = false
664
702
  url = self.get_config['calendar']
665
-
666
- ctz = 'UTC'
667
- case Time.new.zone
668
- when 'BST'
669
- ctz = 'Europe/London'
670
- else
671
- p 'time zone is ' + Time.new.zone + ' so look out...'
672
- end
673
- url += '?ctz=' + ctz
674
- if ctz != 'UTC'
675
- p 'using time zone is ' + ctz + ' so look out...'
703
+ if ! /\.ics/.match url
704
+ STDERR.puts 'we need ical .ics format now, so using default ' + url + ' for dev'
705
+ STDERR.puts 'This contains my test events, not yours! Add your ical url to your config file'
706
+ url = 'https://www.google.com/calendar/ical/aar79qh62fej54nprq6334s7ck%40group.calendar.google.com/public/basic.ics'
676
707
  end
708
+ end
677
709
 
678
- url += '&singleevents=true'
679
- url += '&start-min=' + query_start.strftime( '%FT%T%:z' ).sub( '+', '%2B' )
680
- url += '&start-max=' + query_end.strftime( '%FT%T%:z' ).sub( '+', '%2B' )
681
- debug and ( p url )
710
+ def request url
682
711
  parsed_url = URI.parse url
683
712
  http = Net::HTTP.new parsed_url.host, parsed_url.port
684
713
  begin
@@ -692,212 +721,165 @@ class LightWaveRF
692
721
  end
693
722
  request = Net::HTTP::Get.new parsed_url.request_uri
694
723
  response = http.request request
724
+ end
695
725
 
696
- # if we get a good response
697
- debug and ( p "Response code is: " + response.code)
698
- if response.code == '200'
699
- debug and ( p "Retrieved calendar ok")
700
- doc = REXML::Document.new response.body
701
- now = Time.now.strftime '%H:%M'
702
-
703
- events = [ ]
704
- states = [ ]
705
-
706
- # refresh the list of entries for the caching period
707
- doc.elements.each 'feed/entry' do | e |
708
- debug and ( p '-------------------' )
709
- debug and ( p 'Processing entry...' )
710
- event = Hash.new
711
-
712
- # tokenise the title
713
- debug and ( p 'Event title is: ' + e.elements['title'].text )
714
- command = e.elements['title'].text.split
715
- command_length = command.length
716
- debug and ( p 'Number of words is: ' + command_length.to_s )
717
- if command and command.length >= 1
718
- first_word = command[0].to_s
719
- # determine the type of the entry
720
- if first_word[0,1] == '#'
721
- debug and ( p 'Type is: state' )
722
- event['type'] = 'state' # temporary type, will be overridden later
723
- event['room'] = nil
724
- event['device'] = nil
725
- event['state'] = first_word[1..-1].to_s
726
- modifier_start = command_length # can't have modifiers on states
726
+ def set_event_type event, debug = false
727
+ if event['command'].first[0,1] == '#'
728
+ event['type'] = 'state' # temporary type, will be overridden later
729
+ event['room'] = nil
730
+ event['device'] = nil
731
+ event['state'] = event['command'].first[1..-1].to_s
732
+ event['modifier_start'] = event['command'].length # can't have modifiers on states
733
+ else
734
+ case event['command'].first.to_s
735
+ when 'mood'
736
+ event['type'] = 'mood'
737
+ event['room'] = event['command'][1].to_s
738
+ event['device'] = nil
739
+ event['state'] = event['command'][2].to_s
740
+ event['modifier_start'] = 3
741
+ when 'sequence'
742
+ event['type'] = 'sequence'
743
+ event['room'] = nil
744
+ event['device'] = nil
745
+ event['state'] = event['command'][1].to_s
746
+ event['modifier_start'] = 2
747
+ else
748
+ event['type'] = 'device'
749
+ event['room'] = event['command'].first.to_s
750
+ event['device'] = event['command'][1].to_s
751
+ # handle optional state
752
+ if event['command'].length > 2
753
+ first_char = event['command'][2].to_s[0,1]
754
+ debug and ( p 'First char is: ' + first_char )
755
+ # if the third word does not start with a modifier flag, assume it's a state
756
+ if /\w/.match first_char
757
+ event['state'] = event['command'][2].to_s
758
+ event['modifier_start'] = 3
727
759
  else
728
- case first_word
729
- when 'mood'
730
- debug and ( p 'Type is: mood' )
731
- event['type'] = 'mood'
732
- event['room'] = command[1].to_s
733
- event['device'] = nil
734
- event['state'] = command[2].to_s
735
- modifier_start = 3
736
- when 'sequence'
737
- debug and ( p 'Type is: sequence' )
738
- event['type'] = 'sequence'
739
- event['room'] = nil
740
- event['device'] = nil
741
- event['state'] = command[1].to_s
742
- modifier_start = 2
743
- else
744
- debug and ( p 'Type is: device' )
745
- event['type'] = 'device'
746
- event['room'] = command[0].to_s
747
- event['device'] = command[1].to_s
748
- # handle optional state
749
- if command_length > 2
750
- third_word = command[2].to_s
751
- first_char = third_word[0,1]
752
- debug and ( p 'First char is: ' + first_char )
753
- # if the third word does not start with a modifier flag, assume it's a state
754
- # if first_char != '@' and first_char != '!' and first_char != '+' and first_char != '-'
755
- if /\w/.match first_char
756
- debug and ( p 'State has been given.')
757
- event['state'] = command[2].to_s
758
- modifier_start = 3
759
- else
760
- debug and ( p 'State has not been given.' )
761
- modifier_start = 2
762
- end
763
- else
764
- debug and ( p 'State has not been given.' )
765
- event['state'] = nil
766
- modifier_start = 2
767
- end
768
- end
769
- end
770
-
771
- # get modifiers if they exist
772
- time_modifier = 0
773
- if command_length > modifier_start
774
- debug and ( p 'May have modifiers...' )
775
- when_modifiers = [ ]
776
- unless_modifiers = [ ]
777
- modifier_count = command_length - modifier_start
778
- debug and ( p 'Count of modifiers is ' + modifier_count.to_s )
779
- for i in modifier_start..(command_length-1)
780
- modifier = command[i]
781
- if modifier[0,1] == '@'
782
- debug and ( p 'Found when modifier: ' + modifier[1..-1] )
783
- when_modifiers.push modifier[1..-1]
784
- elsif modifier[0,1] == '!'
785
- debug and ( p 'Found unless modifier: ' + modifier[1..-1] )
786
- unless_modifiers.push modifier[1..-1]
787
- elsif modifier[0,1] == '+'
788
- debug and ( p 'Found positive time modifier: ' + modifier[1..-1] )
789
- time_modifier = modifier[1..-1].to_i
790
- elsif modifier[0,1] == '-'
791
- debug and ( p 'Found negative time modifier: ' + modifier[1..-1] )
792
- time_modifier = modifier[1..-1].to_i * -1
793
- end
794
- end
795
- # add when/unless modifiers to the event
796
- event['when_modifiers'] = when_modifiers
797
- event['unless_modifiers'] = unless_modifiers
760
+ event['modifier_start'] = 2
798
761
  end
762
+ else
763
+ event['state'] = nil
764
+ event['modifier_start'] = 2
765
+ end
766
+ end
767
+ end
768
+ event
769
+ end
799
770
 
800
- # parse the date string
801
- event_time = /When: ([\w ]+) (\d\d:\d\d) to ([\w ]+)?(\d\d:\d\d)&nbsp;\n(.*)<br>(.*)/.match e.elements['summary'].text
802
- debug and ( p 'Event times are: ' + event_time.to_s )
803
- start_date = event_time[1].to_s
804
- start_time = event_time[2].to_s
805
- end_date = event_time[3].to_s
806
- end_time = event_time[4].to_s
807
- timezone = event_time[5].to_s
808
- if end_date == '' or end_date.nil? # copy start date to end date if it wasn't given (as the same date)
809
- end_date = start_date
810
- end
811
-
812
- time_modifier += self.class.variance( e.elements['title'].text ).to_i
813
- event['annotate'] = ! ( /do not annotate/.match e.elements['title'].text )
814
-
815
- debug and ( p 'Start date: ' + start_date )
816
- debug and ( p 'Start time: ' + start_time )
817
- debug and ( p 'End date: ' + end_date )
818
- debug and ( p 'End time: ' + end_time )
819
- debug and ( p 'Timezone: ' + timezone )
820
-
821
- # convert to datetimes
822
- start_dt = DateTime.parse( start_date.strip + ' ' + start_time.strip + ' ' + timezone.strip )
823
- end_dt = DateTime.parse( end_date.strip + ' ' + end_time.strip + ' ' + timezone.strip )
824
-
825
- # apply time modifier if it exists
826
- if time_modifier != 0
827
- debug and ( p 'Adjusting timings by: ' + time_modifier.to_s )
828
- start_dt = (( start_dt.to_time ) + time_modifier * 60 ).to_datetime
829
- end_dt = (( end_dt.to_time ) + time_modifier * 60 ).to_datetime
830
- end
771
+ def get_modifiers event, debug = false
772
+ event['time_modifier'] = 0
773
+ if event['command'].length > event['modifier_start']
774
+ event['when_modifiers'] = [ ]
775
+ event['unless_modifiers'] = [ ]
776
+ for i in event['modifier_start']..(event['command'].length-1)
777
+ modifier = event['command'][i]
778
+ if modifier[0,1] == '@'
779
+ debug and ( p 'Found when modifier: ' + modifier[1..-1] )
780
+ event['when_modifiers'].push modifier[1..-1]
781
+ elsif modifier[0,1] == '!'
782
+ debug and ( p 'Found unless modifier: ' + modifier[1..-1] )
783
+ event['unless_modifiers'].push modifier[1..-1]
784
+ elsif modifier[0,1] == '+'
785
+ debug and ( p 'Found positive time modifier: ' + modifier[1..-1] )
786
+ event['time_modifier'] = modifier[1..-1].to_i
787
+ elsif modifier[0,1] == '-'
788
+ debug and ( p 'Found negative time modifier: ' + modifier[1..-1] )
789
+ event['time_modifier'] = modifier[1..-1].to_i * -1
790
+ end
791
+ end
792
+ end
793
+ event['time_modifier'] += self.class.variance( event['summary'] ).to_i
794
+ if event['time_modifier'] != 0
795
+ debug and ( p 'Adjusting timings by: ' + event['time_modifier'].to_s )
796
+ event['date'] = (( event['date'].to_time ) + event['time_modifier'] * 60 ).to_datetime
797
+ event['end'] = (( event['end'].to_time ) + event['time_modifier'] * 60 ).to_datetime
798
+ end
799
+ event
800
+ end
831
801
 
832
- debug and ( p 'Start datetime: ' + start_dt.to_s )
833
- debug and ( p 'End datetime: ' + end_dt.to_s )
802
+ def tokenise_event e, debug = false
803
+ event = { }
804
+ event['summary'] = e.summary
805
+ event['command'] = event['summary'].split
806
+ event['annotate'] = !( /do not annotate/.match event['summary'] )
807
+ event['date'] = e.dtstart
808
+ event['end'] = e.dtend
809
+ event = set_event_type event, debug
810
+ end
834
811
 
835
- # populate the dates
836
- event['date'] = start_dt
837
- # handle device entries without explicit on/off state
812
+ def update_timers past = 60, future = 1440, debug = false
813
+ p '-- Updating timers...'
838
814
 
839
- # has a PROBLEM with a calendar event set to turn lights to 50% say - automatically adds an off!
840
- # fix this with something like
841
- # if self.get_state event['state'] ! starts with F
815
+ query_start = Time.new - self.class.to_seconds( past )
816
+ query_end = Time.new + self.class.to_seconds( future )
842
817
 
843
- if event['type'] == 'device' and ( event['state'].nil? or ( event['state'] != 'on' and event['state'] != 'off' ))
844
- debug and ( p 'Duplicating event without explicit on/off state...' )
845
- # if not state was given, assume we meant 'on'
846
- if event['state'].nil?
847
- event['state'] = 'on'
848
- end
849
- end_event = event.dup # duplicate event for start and end
850
- end_event['date'] = end_dt
851
- end_event['state'] = 'off'
852
- events.push event
853
- events.push end_event
854
- # create state plus start and end events if a state
855
- elsif event['type'] == 'state'
856
- debug and ( p 'Processing state : ' + event['state'] )
857
- # create state
858
- state = Hash.new
859
- state['name'] = event['state']
860
- state['start'] = start_dt.dup
861
- state['end'] = end_dt.dup
862
- states.push state
863
- # convert event to start and end sequence
864
- event['type'] = 'sequence'
865
- event['state'] = state['name'] + '_start'
866
- end_event = event.dup # duplicate event for start and end
867
- end_event['date'] = end_dt
868
- end_event['state'] = state['name'] + '_end'
869
- events.push event
870
- events.push end_event
871
- # else just add the event
872
- else
873
- events.push event
874
- end
818
+ url = self.get_calendar_url debug
819
+ debug and ( p url )
820
+ response = self.request url
821
+ if response.code != '200'
822
+ debug and ( p "Response code is: " + response.code)
823
+ return self.log_timer_event 'update', nil, nil, nil, false
824
+ end
875
825
 
826
+ cals = RiCal.parse_string( response.body )
827
+
828
+ timers = { }
829
+ timers['events'] = [ ]
830
+ timers['states'] = [ ]
831
+
832
+ cals.first.events.each do | e |
833
+ occurs = e.occurrences( :overlapping => [ query_start, query_end ] )
834
+ next if occurs.length == 0
835
+ occurs.each do | occurrence |
836
+
837
+ event = self.tokenise_event occurrence, debug
838
+ debug and ( p event.inspect )
839
+
840
+ event = self.get_modifiers event, debug
841
+ event.delete 'command'
842
+ event.delete 'modifier_start'
843
+ event.delete 'time_modifier'
844
+
845
+ # handle device entries without explicit on/off state
846
+ # has a PROBLEM with a calendar event set to turn lights to 50% say - automatically adds an off!
847
+ # fix this with something like
848
+ # if self.get_state event['state'] ! starts with F
849
+
850
+ if event['type'] == 'device' and event['state'] != 'on' and event['state'] != 'off'
851
+ debug and ( p 'Duplicating ' + event['summary'] + ' with state ' + event['state'].to_s )
852
+ event['state'] = 'on' if event['state'].nil?
853
+ end_event = event.dup # duplicate event for start and end
854
+ end_event['date'] = event['end']
855
+ end_event['state'] = 'off'
856
+ timers['events'].push event
857
+ timers['events'].push end_event
858
+ elsif event['type'] == 'state'
859
+ debug and ( p 'Create state ' + event['state'] + ' plus start and end events' )
860
+ state = { }
861
+ state['name'] = event['state']
862
+ state['start'] = event['start'].dup
863
+ state['end'] = event['end'].dup
864
+ timers['states'].push state
865
+ event['type'] = 'sequence'
866
+ event['state'] = state['name'] + '_start'
867
+ end_event = event.dup # duplicate event for start and end
868
+ end_event['date'] = event['end']
869
+ end_event['state'] = state['name'] + '_end'
870
+ timers['events'].push event
871
+ timers['events'].push end_event
872
+ else
873
+ timers['events'].push event
876
874
  end
877
-
875
+
878
876
  end
879
877
 
880
- # record some timestamps
881
- info = { }
882
- info['updated_at'] = Time.new.strftime( '%FT%T%:z' )
883
- info['start_time'] = query_start.strftime( '%FT%T%:z' )
884
- info['end_time'] = query_end.strftime( '%FT%T%:z' )
885
-
886
- # build final timer config
887
- timers = { }
888
- timers['info'] = info
889
- timers['events'] = events
890
- timers['states'] = states
891
-
892
- p 'Timer list is: ' + YAML.dump( timers )
878
+ end
893
879
 
894
- # store the list
895
- put_timer_cache timers
896
- self.log_timer_event 'update', nil, nil, nil, true
880
+ put_timer_cache timers
881
+ self.log_timer_event 'update', nil, nil, nil, true
897
882
 
898
- else
899
- self.log_timer_event 'update', nil, nil, nil, false
900
- end
901
883
  end
902
884
 
903
885
  # Return the randomness value that may be in the event title
@@ -1187,9 +1169,9 @@ class LightWaveRF
1187
1169
  debug and ( puts 'got ' + data.length.to_s + ' lines in the log' )
1188
1170
  data = data.last 60 * 24 * days
1189
1171
  debug and ( puts 'now got ' + data.length.to_s + ' lines in the log ( 60 * 24 * ' + days.to_s + ' = ' + ( 60 * 24 * days ).to_s + ' )' )
1190
- if data and data[0]
1191
- debug and ( puts 'data[0] is ' + data[0].to_s )
1192
- if data[0][0] != start_date
1172
+ if data and data.first
1173
+ debug and ( puts 'data.first is ' + data.first.to_s )
1174
+ if data.first[0] != start_date
1193
1175
  data[0][0] += start_date
1194
1176
  end
1195
1177
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lightwaverf
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.7'
4
+ version: 0.8.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2014-10-24 00:00:00.000000000 Z
14
+ date: 2015-10-04 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: htmlentities
@@ -46,9 +46,8 @@ dependencies:
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  description: ! " Interact with lightwaverf wifi-link from code or the command line.\n
49
- \ Control your lights, heating, sockets, sprinkler etc.\n Also use a google
50
- calendar, for timers, log energy usage, and build a website.\n And now optionally
51
- logging to a google doc too.\n"
49
+ \ Control your lights, heating, sockets, sprinkler etc.\n Also use ical calendar,
50
+ for timers, log energy usage, and build a website.\n"
52
51
  email: pauly@clarkeology.com
53
52
  executables:
54
53
  - lightwaverf
@@ -60,7 +59,7 @@ files:
60
59
  - app/views/_graphs.ejs
61
60
  - bin/lightwaverf
62
61
  - bin/lightwaverf-config-json
63
- homepage: http://www.clarkeology.com/wiki/lightwaverf+ruby
62
+ homepage: http://www.clarkeology.com/project/lightwaverf
64
63
  licenses: []
65
64
  post_install_message:
66
65
  rdoc_options: []