diff --git a/Gemfile b/Gemfile index 7399999..0190583 100644 --- a/Gemfile +++ b/Gemfile @@ -9,4 +9,5 @@ gem 'json' gem 'rexml/document' gem 'time' gem 'date' +gem 'google_drive' diff --git a/README.md b/README.md index 45e5615..d45e66c 100644 --- a/README.md +++ b/README.md @@ -48,12 +48,16 @@ and that will create something like the following. room: - name: our device: - - light - - lights + - name: light + type: D + - name: lights + type: D - name: dining device: - - light - - lights + - name: light + type: D + - name: lights + type: D That needs to be valid yml so the spacing etc is important. @@ -68,17 +72,22 @@ The first time you try to pair a device from the computer look out for the "pair Note that if you are already using the iPhone/other app, then your device pairings may already be done. The wifilink is a single transmitter from the actual device's perspective - all clients (so your iPhone and PC running this ruby program) are the same thing. ## How to install on your raspberry pi - sudo apt-get update - sudo apt-get upgrade sudo apt-get install ruby git-core gem - git clone git://github.com/pauly/lightwaverf.git # you don't need much from here, but have the whole source anyway + git clone git://github.com/pauly/lightwaverf.git cd lightwaverf && crontab cron.tab # set up the timer and energy monitor sudo gem install lightwaverf # or build the gem locally, see below lightwaverf configure # or lightwaverf update + lightwaverf initlink true lightwaverf dining lights on # pair one of your devices like you would with any remote control ## How to build the gem from the source + sudo apt-get install ruby git-core gem + git clone git://github.com/pauly/lightwaverf.git + cd lightwaverf + git submodule update --init + crontab cron.tab # set up the timer and energy monitor gem build lightwaverf.gemspec && sudo gem install ./lightwaverf-0.5.0.gem # or whatever the latest version is + lightwaverf initlink true ## Where did the website in this repo go? @@ -95,7 +104,7 @@ Set up the crontab to rebuild the web page regularly # rebuild the website every hour, on the hour 0 * * * * /usr/local/bin/lightwaverf web 5 > /var/www/lightwaverf.html -Todo: make that web page configurable. Any suggestions? If you don't have the energy monitor there is not much on that web page for you right now. +If you don't have the energy monitor there is not much on that web page for you right now. # Usage @@ -125,7 +134,21 @@ Tip: I have found that you can actually pair a single device to 2 different devi This means that you can set up a 'device' in slot D4 which will actually control all the devices at once. Just remember not to to call it 'all' as this is used as a keyword in the code to do the same thing in a different way as per the above. -## Mood support +# Automated timers (via Google Calendar) + +This functionality allows you to create simple or complex schedules to automatically control all your devices (and set moods, run sequences) by simply creating a Google Calendar (gcal) and adding entries to it for the actions you wish to take. You have all the power of gcal's recurrence capabilities to set repeating events. + +## How to set up the google calendar timers + + * make yourself a google calendar http://www.google.com/calendar + * click on "my calendars" + * click on "create a new calendar" + * add some events called "lounge light" + * get the private address address of your calendar by going to calendar settings and clicking on the XML button at the bottom for the private address + * put this private address of the calendar into the lightwaverf-config.yml file + * setup crontab (see below) + +# Mood support Moods are now supported if they are added to the lightwaverf-config.yml file as follows: @@ -161,7 +184,7 @@ And moods are supported in google calendar timers by creating an event with the Note that this will set the mood active at the start time of the event and will not "undo" anything at the end of the event. A separate event should be created to set another mood at another time -## Sequence support +# Sequence support Sequences can execute a number of tasks in order, either simple device commands or setting moods, as per the following example: @@ -178,20 +201,6 @@ Note that pauses can be added (in seconds) - all - off -# Automated timers (via Google Calendar) - -This functionality allows you to create simple or complex schedules to automatically control all your devices (and set moods, run sequences) by simply creating a Google Calendar (gcal) and adding entries to it for the actions you wish to take. You have all the power of gcal's recurrence capabilities to set repeating events. - -## How to set up the google calendar timers - - * make yourself a google calendar http://www.google.com/calendar - * click on "my calendars" - * click on "create a new calendar" - * add some events called "lounge light" - * get the private address address of your calendar by going to calendar settings and clicking on the XML button at the bottom for the private address - * put this private address of the calendar into the lightwaverf-config.yml file - * setup crontab (see below) - ## Crontab setup The timer function utilises 2 separate functions that need scheduling with cron independently: @@ -305,7 +314,7 @@ Here are some ideas on things to automate with the timers: # History - * v 0.7 log to spreadsheet, fixes for broken firmware + * v 0.7 log to spreadsheet, fixed + smoothed graphs, embiggened config file * v 0.6.4 stop "update" corrupting config * v 0.6.3 fix corrupted devices in config * v 0.6.2 another timezone fix diff --git a/app b/app index 547e4d5..45eea06 160000 --- a/app +++ b/app @@ -1 +1 @@ -Subproject commit 547e4d501f3a432e1cb14c79c2c98c20e162bbcf +Subproject commit 45eea063c5328743c7cfd548d9cbf9441f477946 diff --git a/bin/lightwaverf b/bin/lightwaverf index 9f3b6ec..9fcf5e8 100755 --- a/bin/lightwaverf +++ b/bin/lightwaverf @@ -1,32 +1,37 @@ #!/usr/bin/ruby require 'lightwaverf' +obj = LightWaveRF.new case ARGV[0] when 'help' - puts LightWaveRF.new.help + puts obj.help + when 'initlink' + puts obj.firmware ARGV[1] + when 'firmware' + puts obj.firmware ARGV[1] when 'timezone' - puts LightWaveRF.new.timezone ARGV[1] + puts obj.timezone ARGV[1] when 'configure' - puts LightWaveRF.new.configure + puts obj.configure ARGV[1] when 'sequence' - puts LightWaveRF.new.sequence ARGV[1], ARGV[2] + puts obj.sequence ARGV[1], ARGV[2] when 'mood' - puts LightWaveRF.new.mood ARGV[1], ARGV[2], ARGV[3] + puts obj.mood ARGV[1], ARGV[2], ARGV[3] when 'learnmood' - puts LightWaveRF.new.learnmood ARGV[1], ARGV[2], ARGV[3] + puts obj.learnmood ARGV[1], ARGV[2], ARGV[3] when 'energy' - puts LightWaveRF.new.energy ARGV[1], ARGV[2], ARGV[3] + puts obj.energy ARGV[1], ARGV[2], ARGV[3] when 'update_timers' - puts LightWaveRF.new.update_timers ARGV[1], ARGV[2], ARGV[3] + puts obj.update_timers ARGV[1], ARGV[2], ARGV[3] when 'timer' - puts LightWaveRF.new.run_timers ARGV[1], ARGV[2] + puts obj.run_timers ARGV[1], ARGV[2] when 'run_timers' - puts LightWaveRF.new.run_timers ARGV[1], ARGV[2] + puts obj.run_timers ARGV[1], ARGV[2] when 'update' - puts LightWaveRF.new.update_config ARGV[1], ARGV[2] + puts obj.update_config ARGV[1], ARGV[2] when 'web' - puts LightWaveRF.new.build_web_page ARGV[1] + puts obj.build_web_page ARGV[1] when 'summarise' - puts LightWaveRF.new.summarise ARGV[1], ARGV[2] + puts obj.summarise ARGV[1], ARGV[2] else - LightWaveRF.new.send ARGV[0], ARGV[1], ARGV[2], ARGV[3] + obj.send ARGV[0], ARGV[1], ARGV[2], ARGV[3] end diff --git a/bin/lightwaverf-config-json b/bin/lightwaverf-config-json index 7112e60..f40dc91 100755 --- a/bin/lightwaverf-config-json +++ b/bin/lightwaverf-config-json @@ -1,4 +1,6 @@ #!/usr/bin/ruby require 'lightwaverf' +config = LightWaveRF.new.get_config +config['spreadsheet']['password'] = nil +puts JSON.generate config require 'json' -puts JSON.generate LightWaveRF.new.get_config diff --git a/lib/lightwaverf.rb b/lib/lightwaverf.rb index aee2011..5cb2b90 100644 --- a/lib/lightwaverf.rb +++ b/lib/lightwaverf.rb @@ -22,9 +22,10 @@ class LightWaveRF @config_file = nil @log_file = nil @summary_file = nil - @log_timer_file = nil + @timer_log_file = nil @config = nil @timers = nil + @time = nil # Display usage info def usage room = nil @@ -40,6 +41,12 @@ def usage room = nil config end + # For debug timing, why is this so slow? + def time label = nil + @time = @time || Time.now + label.to_s + ' (' + ( Time.now - @time ).to_s + ')' + end + # Display help def help help = self.usage + "\n" @@ -53,19 +60,15 @@ def help # Configure, build config file. Interactive command line stuff # # Arguments: - # debug: (Boolean + # debug: (Boolean) def configure debug = false config = self.get_config - # puts 'What is the ip address of your wifi link? (' + self.get_config['host'] + '). Enter a blank line to broadcast UDP commands.' - # host = STDIN.gets.chomp - # if ! host.to_s.empty? - # config['host'] = host - # end - puts 'What is the address of your google calendar? (' + self.get_config['calendar'] + '). Optional!' + 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).' + host = STDIN.gets.chomp + config['host'] = host if ! host.to_s.empty? + puts 'What is the address of your google calendar? (currently "' + self.get_config['calendar'].to_s + '"). Optional (ok to just hit enter here).' calendar = STDIN.gets.chomp - if ! calendar.to_s.empty? - config['calendar'] = calendar - end + config['calendar'] = calendar if ! calendar.to_s.empty? device = 'x' while ! device.to_s.empty? puts 'Enter the name of a room and its devices, space separated. For example "lounge light socket tv". Enter a blank line to finish.' @@ -77,6 +80,9 @@ def configure debug = false found = false config['room'].each do | room | if room['name'] == new_room + parts.map! do | device | + { 'name' => device, 'type' => 'O' } + end room['device'] = parts found = true end @@ -85,12 +91,13 @@ def configure debug = false if ! found config['room'].push 'name' => new_room, 'device' => parts, 'mood' => nil end - debug and ( p 'added ' + parts.to_s + ' to ' + new_room ) + debug and ( p 'added ' + parts.to_s + ' to ' + new_room.to_s ) end end end debug and ( p 'end of configure, config is now ' + config.to_s ) - self.put_config config + file = self.put_config config + 'Saved config file ' + file end # Config file setter @@ -136,7 +143,7 @@ def log_timer_event type, room = nil, device = nil, state = nil, result = false end unless message.nil? File.open( self.get_timer_log_file, 'a' ) do | f | - f.write("\n" + Time.now.to_s + ' - ' + message + ' - ' + ( result ? 'SUCCESS!' : 'FAILED!' )) + f.write( "\n" + Time.now.to_s + ' - ' + message + ' - ' + ( result ? 'SUCCESS!' : 'FAILED!' )) end end end @@ -164,10 +171,12 @@ def put_timer_cache timers = { 'events' => [ ] } end end - def put_config config = { 'room' => [ { 'name' => 'our', 'device' => [ 'light', 'lights' ] } ] } + # Write the config file + def put_config config = { 'room' => [ { 'name' => 'our', 'device' => [ 'light' => { 'name' => 'light' }, 'lights' => { 'name' => 'lights' } ] } ] } File.open( self.get_config_file, 'w' ) do | handle | handle.write YAML.dump( config ) end + self.get_config_file end # Get the config file, create it if it does not exist @@ -179,15 +188,15 @@ def get_config end @config = YAML.load_file self.get_config_file # fix where update made names and devices into arrays - if @config['room'] - @config['room'].map! do | room | - room['name'] = room['name'].kind_of?( Array ) ? room['name'][0] : room['name'] - room['device'].map! do | device | - device = device.kind_of?( Array ) ? device[0] : device - end - room - end - end + # if @config['room'] + # @config['room'].map! do | room | + # room['name'] = room['name'].kind_of?( Array ) ? room['name'][0] : room['name'] + # room['device'].map! do | device | + # device = device.kind_of?( Array ) ? device[0] : device + # end + # room + # end + # end end @config end @@ -209,9 +218,7 @@ def update_config email = nil, pin = nil, debug = false # Login to LightWaveRF Host server uri = URI.parse 'https://lightwaverfhost.co.uk/manager/index.php' http = Net::HTTP.new uri.host, uri.port - if uri.scheme == 'https' - http.use_ssl = true - end + http.use_ssl = true if uri.scheme == 'https' data = 'pin=' + pin + '&email=' + email headers = { 'Content-Type'=> 'application/x-www-form-urlencoded' } resp, data = http.post uri.request_uri, data, headers @@ -262,7 +269,7 @@ def get_rooms_from body = '', debug = nil # o: All Off deviceStatusIndex = roomIndex * 10 + deviceIndex if variables['gDeviceStatus'] and variables['gDeviceStatus'][deviceStatusIndex] and variables['gDeviceStatus'][deviceStatusIndex][0] != 'I' - roomDevices << deviceName + roomDevices << { 'name' => deviceName, 'type' => variables['gDeviceStatus'][deviceStatusIndex][0] } end end # Create a hash of the active room and active devices and add to rooms array @@ -298,18 +305,19 @@ def get_variables_from body = '', debug = nil end # Get a cleaned up version of the rooms and devices from the config file - def self.get_rooms config = { 'room' => [ ]}, debug = false + def self.get_rooms config = { 'room' => [ ] }, debug = false rooms = { } r = 1 config['room'].each do | room | - debug and ( puts room['name'] + ' = R' + r.to_s ) + room = room.first if room.is_a? Array rooms[room['name']] = { 'id' => 'R' + r.to_s, 'name' => room['name'], 'device' => { }, 'mood' => { }, 'learnmood' => { }} d = 1 unless room['device'].nil? room['device'].each do | device | - # @todo possibly need to complicate this to get a device name back in here - debug and ( puts ' - ' + device + ' = D' + d.to_s ) - rooms[room['name']]['device'][device] = 'D' + d.to_s + device = device.first if device.is_a? Array + device = { 'name' => device } if device.is_a? String + device['id'] = 'D' + d.to_s + rooms[room['name']]['device'][device['name']] = device d += 1 end end @@ -374,9 +382,10 @@ def self.get_state state = 'on' # state: (String) def command room, device, state # @todo get the device name in here... + device = device.to_s # Command structure is ,|| if room and device and !device.empty? and state - '666,!' + room['id'] + room['device'][device] + state + '|Turn ' + room['name'] + ' ' + device + '|' + state + ' via @pauly' + '666,!' + room['id'] + room['device'][device]['id'] + state + '|Turn ' + room['name'] + ' ' + device + '|' + state + ' via @pauly' else '666,!' + room['id'] + state + '|Turn ' + room['name'] + '|' + state + ' via @pauly' end @@ -390,11 +399,9 @@ def command room, device, state # Arguments: # debug: (Boolean) def timezone debug = false - command = '666,!FzP' + (Time.now.gmt_offset/60/60).to_s - debug and ( puts '[Info - LightWaveRF] timezone: command is ' + command ) - data = self.raw command - debug and ( puts '[Info - LightWaveRF] timezone: response is ' + data ) - return (data == "666,OK\r\n") + command = '666,!FzP' + ( Time.now.gmt_offset/60/60 ).to_s + data = self.raw command, true, debug + return data == "666,OK\r\n" end # Turn one of your devices on or off or all devices in a room off @@ -411,9 +418,11 @@ def timezone debug = false # device: (String) # state: (String) def send room = nil, device = nil, state = 'on', debug = false + debug and ( p self.time 'send' ) success = false debug and ( p 'Executing send on device: ' + device + ' in room: ' + room + ' with state: ' + state ) rooms = self.class.get_rooms self.get_config, debug + debug and ( p self.time 'got rooms' ) unless rooms[room] and state debug and ( p 'Missing room (' + room.to_s + ') or state (' + state.to_s + ')' ); @@ -432,9 +441,9 @@ def send room = nil, device = nil, state = 'on', debug = false elsif device and rooms[room]['device'][device] state = self.class.get_state state command = self.command rooms[room], device, state - debug and ( p 'command is ' + command ) + debug and ( p self.time 'command is ' + command ) data = self.raw command - debug and ( p 'response is ' + data ) + debug and ( p self.time 'response is ' + data.to_s ) success = true else STDERR.puts self.usage( room ); @@ -482,15 +491,14 @@ def sequence name, debug = false # mood: (String) def mood room = nil, mood = nil, debug = false success = false - debug and (p 'Executing mood: ' + mood + ' in room: ' + room) - #debug and ( puts 'config is ' + self.get_config.to_s ) + debug and ( p 'Executing mood: ' + mood + ' in room: ' + room ) rooms = self.class.get_rooms self.get_config # support for setting a mood in all rooms (recursive) if room == 'all' - debug and ( p "Processing all rooms..." ) + debug and ( p 'Processing all rooms...' ) rooms.each do | config, each_room | room = each_room['name'] - debug and ( p "Room is: " + room ) + debug and ( p 'Room is: ' + room ) success = self.mood room, mood, debug sleep 1 end @@ -508,8 +516,8 @@ def mood room = nil, mood = nil, debug = false state = mood[3..-1] debug and (p 'Selected state is: ' + state) rooms[room]['device'].each do | device | - p 'Processing device: ' + device[0] - self.send room, device[0], state, debug + p 'Processing device: ' + device[0].to_s + self.send room, device[0]['name'], state, debug sleep 1 end success = true @@ -530,8 +538,7 @@ def mood room = nil, mood = nil, debug = false # room: (String) # mood: (String) def learnmood room = nil, mood = nil, debug = false - debug and (p 'Learning mood: ' + mood) - #debug and ( puts 'config is ' + self.get_config.to_s ) + debug and ( p 'Learning mood: ' + mood ) rooms = self.class.get_rooms self.get_config if rooms[room] and mood and rooms[room]['learnmood'][mood] command = self.command rooms[room], nil, rooms[room]['learnmood'][mood] @@ -542,10 +549,9 @@ def learnmood room = nil, mood = nil, debug = false end end - def energy title = nil, note = nil, debug = false - debug and note and ( p 'energy: ' + note ) - data = self.raw '666,@?' - debug and ( p data ) + def energy title = nil, text = nil, debug = false + debug and text and ( p 'energy: ' + text ) + data = self.raw '666,@?', true # /W=(?\d+),(?\d+),(?\d+),(?\d+)/.match data # ruby 1.9 only? match = /W=(\d+),(\d+),(\d+),(\d+)/.match data debug and ( p match ) @@ -558,8 +564,38 @@ def energy title = nil, note = nil, debug = false } } data['timestamp'] = Time.now.to_s - if note - data['message']['annotation'] = { 'title' => title.to_s, 'text' => note.to_s } + if text + data['message']['annotation'] = { 'title' => title.to_s, 'text' => text.to_s } + end + + if text + if self.get_config['spreadsheet'] + spreadsheet = self.get_config['spreadsheet']['url'] + match = /key=([\w-]+)/.match spreadsheet + debug and ( p match ) + if match + spreadsheet = match[1] + end + debug and ( p 'spreadsheet is ' + spreadsheet ) + if spreadsheet + require 'google_drive' + session = GoogleDrive.login self.get_config['spreadsheet']['username'], self.get_config['spreadsheet']['password'] + ws = session.spreadsheet_by_key( spreadsheet ).worksheets[0] + rows = ws.num_rows + debug and ( p rows.to_s + ' rows in ' + spreadsheet ) + row = rows + 1 + ws[ row, 1 ] = data['timestamp'] + ws[ row, 2 ] = data['message']['usage'] + ws[ row, 3 ] = data['message']['max'] + ws[ row, 4 ] = data['message']['today'] + ws[ row, 5 ] = data['message']['annotation']['title'] + ws[ row, 6 ] = data['message']['annotation']['text'] + ws.save( ) + end + else + debug and ( p 'no spreadsheet in your config file...' ) + end + end debug and ( p data ) begin @@ -567,13 +603,7 @@ def energy title = nil, note = nil, debug = false f.write( data.to_json + "\n" ) end file = self.get_summary_file.gsub 'summary', 'daily' - json = self.class.get_contents file - begin - data['message']['history'] = JSON.parse json - rescue => e - data['message']['error'] = 'error parsing ' + file + '; ' + e.to_s - data['message']['history_json'] = json - end + data['message']['history'] = self.class.get_json file data['message'] rescue puts 'error writing to log' @@ -581,37 +611,49 @@ def energy title = nil, note = nil, debug = false end end - def raw command + def raw command, listen = false, debug = false + debug and ( p self.time + ' ' + __method__.to_s + ' ' + command ) response = nil # Get host address or broadcast address host = self.get_config['host'] || '255.255.255.255' + debug and ( p self.time 'got ' + host ) # Create socket listener = UDPSocket.new + debug and ( p self.time 'got listener' ) # Add broadcast socket options if necessary - if (host == '255.255.255.255') - listener.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true) + if host == '255.255.255.255' + listener.setsockopt Socket::SOL_SOCKET, Socket::SO_BROADCAST, true end if listener - # Bind socket to listen for response - begin - listener.bind '0.0.0.0',9761 - rescue - response = "can't bind to listen for a reply" + if listen + # Bind socket to listen for response + begin + listener.bind '0.0.0.0', 9761 + rescue + response = "can't bind to listen for a reply" + end end # Broadcast command to server - listener.send(command, 0, host, 9760) + debug and ( p self.time 'sending...' ) + listener.send command, 0, host, 9760 + debug and ( p self.time 'sent' ) # Receive response - if ! response + if listen and ! response + debug and ( p self.time 'receiving...' ) response, addr = listener.recvfrom 200 + debug and ( p self.time 'received' ) end + debug and ( p self.time 'closing...' ) listener.close + debug and ( p self.time 'closed' ) end + debug and ( puts '[Info - LightWaveRF] ' + __method__.to_s + ': response is ' + response.to_s ) response end def update_timers past = 60, future = 1440, debug = false p '----------------' - p "Updating timers..." + p 'Updating timers...' # determine the window to query now = Time.new @@ -983,15 +1025,16 @@ def run_timers interval = 5, debug = false p 'Executing sequence. Sequence: ' + event['state'] result = self.sequence event['state'], debug else - p 'Executing device. Room: ' + event['room'] + ', Device: ' + event['device'] + ', State: ' + event['state'] - result = self.send event['room'], event['device'], event['state'], debug + p 'Executing device. Room: ' + event['room'] + ', Device: ' + event['device'].to_s + ', State: ' + event['state'] + # result = self.send event['room'], event['device']['name'], event['state'], debug + result = self.send event['room'], event['device'].to_s, event['state'], debug # is this right? end sleep 1 - triggered << [ event['room'], event['device'], event['state'] ] + triggered << [ event['room'], event['device'].to_s, event['state'] ] if event['annotate'] annotate = true end - self.log_timer_event event['type'], event['room'], event['device'], event['state'], result + self.log_timer_event event['type'], event['room'], event['device'].to_s, event['state'], result end # update energy log @@ -1053,7 +1096,7 @@ def build_web_page debug = nil Sample page generated #{date} with lightwaverf web. Check out the new simplified repo for details or gem install lightwaverf && lightwaverf web... -
@todo make a decent, useful, simple, configurable web page... +
@todo merge this with robot butler... end help = list html = <<-end @@ -1105,20 +1148,29 @@ def summarise days = 7, debug = nil daily = self.class.get_json file start_date = 0 d = nil + last = nil + prev = nil File.open( self.get_log_file, 'r' ).each_line do | line | - line = JSON.parse line - if line and line['timestamp'] + begin + line = JSON.parse line + rescue + line = nil + end + if line and line['timestamp'] and ( last != line['message']['usage'] ) new_line = [] d = line['timestamp'][2..3] + line['timestamp'][5..6] + line['timestamp'][8..9] # compact version of date ts = Time.parse( line['timestamp'] ).strftime '%s' ts = ts.to_i - if start_date > 0 - ts = ts - start_date - else - start_date = ts + ts = ts - start_date + if start_date == 0 + # start_date = ts # can't get this delta working end new_line << ts - new_line << line['message']['usage'].to_i / 10 + smoothedUsage = line['message']['usage'].to_i + if last && prev + smoothedUsage = ( smoothedUsage + last + prev ) / 3 # average of last 3 readings + end + new_line << smoothedUsage / 10 if line['message']['annotation'] and line['message']['annotation']['title'] and line['message']['annotation']['text'] new_line << line['message']['annotation']['title'] new_line << line['message']['annotation']['text'] @@ -1128,6 +1180,8 @@ def summarise days = 7, debug = nil daily[d] = line['message'] daily[d].delete 'usage' end + prev = last + last = line['message']['usage'].to_i end end debug and ( puts 'got ' + data.length.to_s + ' lines in the log' ) @@ -1141,7 +1195,8 @@ def summarise days = 7, debug = nil end summary_file = self.get_summary_file File.open( summary_file, 'w' ) do |file| - file.write data.to_s + # file.write data.to_s + file.write( JSON.pretty_generate( data )) end # @todo fix the daily stats, every night it reverts to the minimum value because the timezones are different # so 1am on the wifi-link looks midnight on the server @@ -1153,4 +1208,9 @@ def summarise days = 7, debug = nil end end + # http://lightwaverfcommunity.org.uk/forums/topic/link-no-longer-responding-to-udp-commands-any-advice/page/4/#post-16098 + def firmware debug = true + self.raw '666,!F*p', true, debug + end + end diff --git a/lightwaverf-config.yml b/lightwaverf-config.yml index 60cb69e..04ff2f1 100644 --- a/lightwaverf-config.yml +++ b/lightwaverf-config.yml @@ -1,46 +1,55 @@ ---- -sequence: - lights: - - - our - - light - - "on" - - - our - - lights - - "on" -room: +--- +sequence: + bedtime: + - - lounge + - all + - 'off' + - - dining + - all + - 'off' + - - kitchen + - all + - 'off' +room: - name: our - device: - - light - - lights - - kettle - - tv -- name: dining - device: - - light - - lights -- name: harry device: - - light -- name: tommy + - name: light + type: D + - name: lights + type: D + - name: kettle + type: O + - name: tv + type: O +- name: dining device: - - light + - name: light + type: D + - name: lights + type: D - name: lounge device: - - light - - lights + - name: light + type: D + - name: lights + type: D + - name: tv + type: O + - name: lamp1 + type: O + - name: lamp2 + type: O + - name: surround + type: O - name: kitchen device: - - kettle - - toaster - - radio - - socket -- name: cloakroom - device: - - light -- name: hall - device: - - light -- name: landing - device: - - light + - name: kettle + type: O + - name: toaster + type: O + - name: radio + type: O + - name: socket + type: O calendar: 'https://www.google.com/calendar/feeds/aar79qh62fej54nprq6334s7ck%40group.calendar.google.com/public/basic' +host: 192.168.1.64 diff --git a/lightwaverf.gemspec b/lightwaverf.gemspec index 60ed05f..0ba9fde 100644 --- a/lightwaverf.gemspec +++ b/lightwaverf.gemspec @@ -1,12 +1,13 @@ Gem::Specification.new do |s| s.name = 'lightwaverf' - s.version = '0.6.6' + s.version = '0.7' s.date = Time.now.strftime '%Y-%m-%d' s.summary = 'Home automation with lightwaverf' s.description = <<-end Interact with lightwaverf wifi-link from code or the command line. Control your lights, heating, sockets, sprinkler etc. Also use a google calendar, for timers, log energy usage, and build a website. + And now optionally logging to a google doc too. end s.authors = [ 'Paul Clarke', 'Ian Perrin', 'Julian McLean' ] s.email = 'pauly@clarkeology.com' diff --git a/test/test_lightwaverf.rb b/test/test_lightwaverf.rb index 559c2b2..20daec9 100644 --- a/test/test_lightwaverf.rb +++ b/test/test_lightwaverf.rb @@ -3,6 +3,51 @@ class LightWaveRFTest < Test::Unit::TestCase + def test_get_rooms_old_style + config = <<-END +room: +- name: our + device: + - light + - lights + END + config = YAML.load config + rooms = LightWaveRF.get_rooms config, true + p rooms['our']['device'] + assert_equal rooms['our']['device'], { 'light' => { 'id' => 'D1', 'name' => 'light' }, 'lights' => { 'id' => 'D2', 'name' => 'lights' }} + end + + def test_get_rooms_broken_style + config = <<-END +room: +- name: our + device: + - - light + - - lights + END + config = YAML.load config + rooms = LightWaveRF.get_rooms config, true + p rooms['our']['device'] + assert_equal rooms['our']['device'], { 'light' => { 'id' => 'D1', 'name' => 'light' }, 'lights' => { 'id' => 'D2', 'name' => 'lights' }} + end + + def test_get_rooms_new_style + config = <<-END +room: +- name: our + device: + - name: light + type: light + status: on + - name: lights + type: light + status: on + END + config = YAML.load config + rooms = LightWaveRF.get_rooms config, true + assert_equal rooms['our']['device'], { 'light' => { 'name' => 'light', 'id' => 'D1', 'type' => 'light', 'status' => true }, 'lights' => { 'name' => 'lights', 'id' => 'D2', 'type' => 'light', 'status' => true }} + end + def test_default_state_on assert_equal 'F1', LightWaveRF.get_state end @@ -56,7 +101,9 @@ def test_get_variables obj = LightWaveRF.new js = <<-END END vars = obj.get_variables_from js, true @@ -65,6 +112,20 @@ def test_get_variables assert_equal 'Light', vars['gDeviceNames'][0] assert_equal 'Lights', vars['gDeviceNames'][1] assert_equal 'TV', vars['gDeviceNames'][2] + # puts vars.inspect + end + + def test_get_variables_again + obj = LightWaveRF.new + js = <<-END + + END + vars = obj.get_variables_from js, true + assert_equal 'Ceiling', vars['gDeviceNames'][0] + assert_equal 'TonyLamp', vars['gDeviceNames'][1] + assert_equal 'Dev3', vars['gDeviceNames'][2] puts vars.inspect end