smartermeter 0.1.0 → 0.2.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.
- data/CHANGELOG.md +4 -0
- data/Gemfile +3 -0
- data/Rakefile +5 -2
- data/bin/smartermeter +2 -1
- data/lib/smartermeter/daemon.rb +52 -54
- data/lib/smartermeter/interfaces/cli.rb +41 -0
- data/lib/smartermeter/interfaces/swing.rb +85 -0
- data/lib/smartermeter/main.rb +3 -1
- data/lib/smartermeter/service.rb +66 -48
- data/lib/smartermeter.rb +2 -1
- data/smartermeter.gemspec +5 -2
- metadata +6 -3
    
        data/CHANGELOG.md
    ADDED
    
    
    
        data/Gemfile
    CHANGED
    
    
    
        data/Rakefile
    CHANGED
    
    | @@ -86,7 +86,7 @@ namespace :rawr do | |
| 86 86 | 
             
                dir = File.join(File.dirname(__FILE__), "vendor", "gems")
         | 
| 87 87 | 
             
                FileUtils.rm_rf(dir)
         | 
| 88 88 | 
             
                FileUtils.mkdir_p(dir)
         | 
| 89 | 
            -
                ["nokogiri", "mechanize", "crypt"].each do |gem|
         | 
| 89 | 
            +
                ["nokogiri", "mechanize", "crypt", "profligacy"].each do |gem|
         | 
| 90 90 | 
             
                  `gem unpack -t "#{dir}" #{gem}`
         | 
| 91 91 | 
             
                end
         | 
| 92 92 |  | 
| @@ -100,6 +100,9 @@ namespace :rawr do | |
| 100 100 | 
             
                Dir.glob(File.join(dir, "nokogiri.old", "lib", "*")).each do |file|
         | 
| 101 101 | 
             
                  FileUtils.mv(file, File.join(dir))
         | 
| 102 102 | 
             
                end
         | 
| 103 | 
            +
                Dir.glob(File.join(dir, "profligacy.old", "lib", "*")).each do |file|
         | 
| 104 | 
            +
                  FileUtils.mv(file, File.join(dir))
         | 
| 105 | 
            +
                end
         | 
| 103 106 | 
             
                FileUtils.mv(File.join(dir, "crypt.old", "crypt"), File.join(dir, "crypt"))
         | 
| 104 107 | 
             
                Dir.glob(File.join(dir, "mechanize.old", "lib", "*")).each do |file|
         | 
| 105 108 | 
             
                  FileUtils.mv(file, File.join(dir))
         | 
| @@ -127,7 +130,7 @@ task :release => :build do | |
| 127 130 | 
             
              sh "git tag v#{version}"
         | 
| 128 131 | 
             
              sh "git push origin master"
         | 
| 129 132 | 
             
              sh "git push origin v#{version}"
         | 
| 130 | 
            -
               | 
| 133 | 
            +
              sh "gem push pkg/#{name}-#{version}.gem"
         | 
| 131 134 | 
             
            end
         | 
| 132 135 |  | 
| 133 136 | 
             
            desc "Build #{gem_file} into the pkg directory"
         | 
    
        data/bin/smartermeter
    CHANGED
    
    
    
        data/lib/smartermeter/daemon.rb
    CHANGED
    
    | @@ -1,11 +1,15 @@ | |
| 1 | 
            +
            require 'fileutils'
         | 
| 1 2 | 
             
            require 'crypt/blowfish'
         | 
| 2 3 | 
             
            require 'yaml'
         | 
| 3 | 
            -
            require 'logger'
         | 
| 4 4 | 
             
            require 'date'
         | 
| 5 5 |  | 
| 6 6 | 
             
            module SmarterMeter
         | 
| 7 7 | 
             
              class Daemon
         | 
| 8 8 |  | 
| 9 | 
            +
                def initialize(interface)
         | 
| 10 | 
            +
                  @ui = interface
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 9 13 | 
             
                # Loads the configuration, and starts
         | 
| 10 14 | 
             
                #
         | 
| 11 15 | 
             
                # Never returns.
         | 
| @@ -28,13 +32,6 @@ module SmarterMeter | |
| 28 32 | 
             
                  File.expand_path(File.join(@config[:data_dir], date.strftime("%Y-%m-%d.csv")))
         | 
| 29 33 | 
             
                end
         | 
| 30 34 |  | 
| 31 | 
            -
                def log
         | 
| 32 | 
            -
                  return @logger if @logger
         | 
| 33 | 
            -
                  @logger = Logger.new STDOUT
         | 
| 34 | 
            -
                  @logger.level = Logger::INFO
         | 
| 35 | 
            -
                  @logger
         | 
| 36 | 
            -
                end
         | 
| 37 | 
            -
             | 
| 38 35 | 
             
                # Loads the configuration and prompts for required settings if they are
         | 
| 39 36 | 
             
                # missing.
         | 
| 40 37 | 
             
                #
         | 
| @@ -49,7 +46,7 @@ module SmarterMeter | |
| 49 46 | 
             
                # Returns the configuration hash.
         | 
| 50 47 | 
             
                def load_configuration
         | 
| 51 48 | 
             
                  @config = {
         | 
| 52 | 
            -
                    :start_date => Date.today,
         | 
| 49 | 
            +
                    :start_date => Date.today - 1,
         | 
| 53 50 | 
             
                    :data_dir => default_data_dir
         | 
| 54 51 | 
             
                  }
         | 
| 55 52 |  | 
| @@ -79,32 +76,22 @@ module SmarterMeter | |
| 79 76 | 
             
                  end
         | 
| 80 77 | 
             
                end
         | 
| 81 78 |  | 
| 79 | 
            +
                # Returns true if the all of the required configuration has been set.
         | 
| 80 | 
            +
                def has_configuration?
         | 
| 81 | 
            +
                  @config[:username] and @config[:password]
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
             | 
| 82 84 | 
             
                # Prompts the user for required settings that are blank.
         | 
| 83 85 | 
             
                #
         | 
| 84 86 | 
             
                # Returns nothing.
         | 
| 85 87 | 
             
                def verify_configuration
         | 
| 86 | 
            -
                  return if  | 
| 87 | 
            -
             | 
| 88 | 
            -
                  puts
         | 
| 89 | 
            -
                  puts "Smartermeter: Initial Configuration"
         | 
| 90 | 
            -
                  puts "--------------------------------------------------------------------------------"
         | 
| 91 | 
            -
                  puts "This program stores your PG&E account username and password on disk. The"
         | 
| 92 | 
            -
                  puts "password is encrypted but could be retrieved fairly easily. If this makes you"
         | 
| 93 | 
            -
                  puts "uncomfortable quit now (use ctrl-c)."
         | 
| 94 | 
            -
                  puts "--------------------------------------------------------------------------------"
         | 
| 95 | 
            -
             | 
| 96 | 
            -
                  unless @config[:username]
         | 
| 97 | 
            -
                    print "PG&E account username: "
         | 
| 98 | 
            -
                    @config[:username] = gets.strip
         | 
| 99 | 
            -
                  end
         | 
| 88 | 
            +
                  return if has_configuration?
         | 
| 100 89 |  | 
| 101 | 
            -
                   | 
| 102 | 
            -
                     | 
| 103 | 
            -
                    self.password =  | 
| 90 | 
            +
                  @ui.setup do |config|
         | 
| 91 | 
            +
                    @config.merge!(config)
         | 
| 92 | 
            +
                    self.password = config[:password] if config.has_key? :password
         | 
| 93 | 
            +
                    save_configuration
         | 
| 104 94 | 
             
                  end
         | 
| 105 | 
            -
             | 
| 106 | 
            -
                  save_configuration
         | 
| 107 | 
            -
                  puts "Setup complete"
         | 
| 108 95 | 
             
                end
         | 
| 109 96 |  | 
| 110 97 | 
             
                # Saves the current configuration to disk.
         | 
| @@ -124,13 +111,19 @@ module SmarterMeter | |
| 124 111 | 
             
                  one_hour = 60 * 60
         | 
| 125 112 |  | 
| 126 113 | 
             
                  while true
         | 
| 114 | 
            +
                    unless has_configuration?
         | 
| 115 | 
            +
                      @ui.log.info("Waiting for configuration")
         | 
| 116 | 
            +
                      sleep(5)
         | 
| 117 | 
            +
                      next
         | 
| 118 | 
            +
                    end
         | 
| 119 | 
            +
             | 
| 127 120 | 
             
                    dates = dates_requiring_data
         | 
| 128 121 | 
             
                    unless dates.empty?
         | 
| 129 | 
            -
                      log.info("Attempting to fetch data for: #{dates.join(",")}")
         | 
| 122 | 
            +
                      @ui.log.info("Attempting to fetch data for: #{dates.join(",")}")
         | 
| 130 123 | 
             
                      results = fetch_dates(dates)
         | 
| 131 | 
            -
                      log.info("Successfully fetched: #{results.join(",")}")
         | 
| 124 | 
            +
                      @ui.log.info("Successfully fetched: #{results.join(",")}")
         | 
| 132 125 | 
             
                    else
         | 
| 133 | 
            -
                      log.info("Sleeping")
         | 
| 126 | 
            +
                      @ui.log.info("Sleeping")
         | 
| 134 127 | 
             
                    end
         | 
| 135 128 | 
             
                    sleep(one_hour)
         | 
| 136 129 | 
             
                  end
         | 
| @@ -141,32 +134,34 @@ module SmarterMeter | |
| 141 134 | 
             
                # Note: An authorization failure will cause an exits, as it is a dire
         | 
| 142 135 | 
             
                # condition.
         | 
| 143 136 | 
             
                #
         | 
| 144 | 
            -
                # Returns a new Service instance which has been properly authorized | 
| 137 | 
            +
                # Returns a new Service instance which has been properly authorized and nil
         | 
| 138 | 
            +
                #   otherwise.
         | 
| 145 139 | 
             
                def service
         | 
| 146 140 | 
             
                  service = Service.new
         | 
| 147 | 
            -
                  log.info("Logging in as #{@config[:username]}")
         | 
| 148 | 
            -
                   | 
| 149 | 
            -
                    log. | 
| 150 | 
            -
                     | 
| 151 | 
            -
             | 
| 141 | 
            +
                  @ui.log.info("Logging in as #{@config[:username]}")
         | 
| 142 | 
            +
                  if service.login(@config[:username], password)
         | 
| 143 | 
            +
                    @ui.log.info("Logged in as #{@config[:username]}")
         | 
| 144 | 
            +
                    service
         | 
| 145 | 
            +
                  else
         | 
| 146 | 
            +
                    @ui.log.error("Login failed.")
         | 
| 147 | 
            +
                    @ui.log.error(service.last_page) if service.last_page
         | 
| 148 | 
            +
                    @ui.log.error(service.last_exception) if service.last_exception
         | 
| 149 | 
            +
                    @ui.log.error("If this happens repeatedly your login information may be incorrect")
         | 
| 150 | 
            +
                    @ui.log.error("Remove ~/.smartermeter and restart to re-configure smartermeter.")
         | 
| 151 | 
            +
                    nil
         | 
| 152 152 | 
             
                  end
         | 
| 153 | 
            -
                  log.info("Logged in as #{@config[:username]}")
         | 
| 154 | 
            -
                  service
         | 
| 155 153 | 
             
                end
         | 
| 156 154 |  | 
| 157 155 | 
             
                # Connect and authenticate to the PG&E Website.
         | 
| 158 156 | 
             
                #
         | 
| 159 157 | 
             
                # It provides an instance of Service to the provided block
         | 
| 160 | 
            -
                # for direct manipulation.
         | 
| 158 | 
            +
                # for direct manipulation. If there was a failure logging into the service
         | 
| 159 | 
            +
                # the block will not be executed.
         | 
| 161 160 | 
             
                #
         | 
| 162 161 | 
             
                # Returns nothing.
         | 
| 163 162 | 
             
                def connect
         | 
| 164 163 | 
             
                  s = service
         | 
| 165 | 
            -
                   | 
| 166 | 
            -
                    yield s
         | 
| 167 | 
            -
                  rescue SocketError => e
         | 
| 168 | 
            -
                    log.error("Could not access the PG&E site, are you connected to the Internet?")
         | 
| 169 | 
            -
                  end
         | 
| 164 | 
            +
                  yield s if s
         | 
| 170 165 | 
             
                end
         | 
| 171 166 |  | 
| 172 167 | 
             
                # Attempts to retrieve power data for each of the dates in the list.
         | 
| @@ -179,25 +174,28 @@ module SmarterMeter | |
| 179 174 |  | 
| 180 175 | 
             
                  connect do |service|
         | 
| 181 176 | 
             
                    dates.each do |date|
         | 
| 182 | 
            -
                      log.info("Fetching #{date}")
         | 
| 177 | 
            +
                      @ui.log.info("Fetching #{date}")
         | 
| 178 | 
            +
             | 
| 183 179 | 
             
                      data = service.fetch_csv(date)
         | 
| 180 | 
            +
                      next if data.empty?
         | 
| 184 181 |  | 
| 185 | 
            -
                      log.info("Verifying #{date}")
         | 
| 182 | 
            +
                      @ui.log.info("Verifying #{date}")
         | 
| 186 183 | 
             
                      samples = Sample.parse_csv(data).values.first
         | 
| 187 184 | 
             
                      first_sample = samples.first
         | 
| 188 185 |  | 
| 189 186 | 
             
                      if first_sample.kwh
         | 
| 190 | 
            -
                        log.info("Saving #{date}")
         | 
| 187 | 
            +
                        @ui.log.info("Saving #{date}")
         | 
| 188 | 
            +
                        FileUtils.mkdir_p(File.dirname(data_file(date)))
         | 
| 191 189 | 
             
                        File.open(data_file(date), "w") do |f|
         | 
| 192 190 | 
             
                          f.write(data)
         | 
| 193 191 | 
             
                        end
         | 
| 194 192 |  | 
| 195 193 | 
             
                        upload(date, samples)
         | 
| 196 194 |  | 
| 197 | 
            -
                        log.info("Completed #{date}")
         | 
| 195 | 
            +
                        @ui.log.info("Completed #{date}")
         | 
| 198 196 | 
             
                        completed << date
         | 
| 199 197 | 
             
                      else
         | 
| 200 | 
            -
                        log.info("Incomplete #{date}")
         | 
| 198 | 
            +
                        @ui.log.info("Incomplete #{date}")
         | 
| 201 199 | 
             
                      end
         | 
| 202 200 | 
             
                    end
         | 
| 203 201 | 
             
                  end
         | 
| @@ -208,12 +206,12 @@ module SmarterMeter | |
| 208 206 | 
             
                def upload(date, samples)
         | 
| 209 207 | 
             
                  case @config[:transport]
         | 
| 210 208 | 
             
                  when :google_powermeter
         | 
| 211 | 
            -
                    log.info("Uploading #{date} to Google PowerMeter")
         | 
| 209 | 
            +
                    @ui.log.info("Uploading #{date} to Google PowerMeter")
         | 
| 212 210 | 
             
                    transport = SmarterMeter::Transports::GooglePowerMeter.new(@config[:google_powermeter])
         | 
| 213 211 | 
             
                    if transport.upload(samples)
         | 
| 214 | 
            -
                      log.info("Upload for #{date} complete")
         | 
| 212 | 
            +
                      @ui.log.info("Upload for #{date} complete")
         | 
| 215 213 | 
             
                    else
         | 
| 216 | 
            -
                      log.info("Upload for #{date} failed")
         | 
| 214 | 
            +
                      @ui.log.info("Upload for #{date} failed")
         | 
| 217 215 | 
             
                    end
         | 
| 218 216 | 
             
                  end
         | 
| 219 217 | 
             
                end
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            require 'logger'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module SmarterMeter
         | 
| 4 | 
            +
              module Interfaces
         | 
| 5 | 
            +
                class CLI
         | 
| 6 | 
            +
                  # Returns a logger like interface to log errors and warnings to.
         | 
| 7 | 
            +
                  def log
         | 
| 8 | 
            +
                    return @logger if @logger
         | 
| 9 | 
            +
                    @logger = Logger.new STDOUT
         | 
| 10 | 
            +
                    @logger.level = Logger::INFO
         | 
| 11 | 
            +
                    @logger
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  # Public: Called when ~/.smartermeter needs to be configured.
         | 
| 15 | 
            +
                  # Yields a hash containing the configuration by the user.
         | 
| 16 | 
            +
                  #
         | 
| 17 | 
            +
                  # Returns nothing
         | 
| 18 | 
            +
                  def setup
         | 
| 19 | 
            +
                    puts
         | 
| 20 | 
            +
                    puts "Smartermeter: Initial Configuration"
         | 
| 21 | 
            +
                    puts "--------------------------------------------------------------------------------"
         | 
| 22 | 
            +
                    puts "This program stores your PG&E account username and password on disk. The"
         | 
| 23 | 
            +
                    puts "password is encrypted but could be retrieved fairly easily. If this makes you"
         | 
| 24 | 
            +
                    puts "uncomfortable quit now (use ctrl-c)."
         | 
| 25 | 
            +
                    puts "--------------------------------------------------------------------------------"
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    config = {}
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    print "PG&E account username: "
         | 
| 30 | 
            +
                    config[:username] = gets.strip
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    print "PG&E account password: "
         | 
| 33 | 
            +
                    config[:password] = gets.strip
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    puts "Configuration finished"
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    yield config
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
            end
         | 
| @@ -0,0 +1,85 @@ | |
| 1 | 
            +
            require 'logger'
         | 
| 2 | 
            +
            require 'profligacy/swing'
         | 
| 3 | 
            +
            require 'profligacy/lel'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module SmarterMeter
         | 
| 6 | 
            +
              module Interfaces
         | 
| 7 | 
            +
                class Swing
         | 
| 8 | 
            +
                  include_package "java.awt"
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def initialize
         | 
| 11 | 
            +
                    # TODO: Implement a way to update the settings
         | 
| 12 | 
            +
                    #settings_item = MenuItem.new("Settings")
         | 
| 13 | 
            +
                    #settings_item.add_action_listener { SettingsWindow.new do |config|
         | 
| 14 | 
            +
                    #    puts config.inspect
         | 
| 15 | 
            +
                    #  end
         | 
| 16 | 
            +
                    #}
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    exit_item = MenuItem.new("Exit")
         | 
| 19 | 
            +
                    exit_item.add_action_listener {java.lang.System::exit(0)}
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    popup = PopupMenu.new
         | 
| 22 | 
            +
                    #popup.add(settings_item)
         | 
| 23 | 
            +
                    popup.add(exit_item)
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    image = Toolkit::default_toolkit.get_image("icon.png")
         | 
| 26 | 
            +
                    tray_icon = TrayIcon.new(image, "Smartermeter", popup)
         | 
| 27 | 
            +
                    tray_icon.image_auto_size = true
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    tray = SystemTray::system_tray
         | 
| 30 | 
            +
                    tray.add(tray_icon)
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  # Returns a logger like interface to log errors and warnings to.
         | 
| 34 | 
            +
                  def log
         | 
| 35 | 
            +
                    return @logger if @logger
         | 
| 36 | 
            +
                    @logger = Logger.new(File.expand_path("~/.smartermeter.log"))
         | 
| 37 | 
            +
                    @logger.level = Logger::INFO
         | 
| 38 | 
            +
                    @logger
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  # Public: Called when ~/.smartermeter needs to be configured.
         | 
| 42 | 
            +
                  # Yields a hash containing the configuration specified by the user.
         | 
| 43 | 
            +
                  #
         | 
| 44 | 
            +
                  # Returns nothing.
         | 
| 45 | 
            +
                  def setup
         | 
| 46 | 
            +
                    SettingsWindow.new do |config|
         | 
| 47 | 
            +
                      yield config
         | 
| 48 | 
            +
                    end
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                class SettingsWindow
         | 
| 53 | 
            +
                  include_package "javax.swing"
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  def initialize(&block)
         | 
| 56 | 
            +
                    layout = "
         | 
| 57 | 
            +
                        [ username_label | (150)username_field ]
         | 
| 58 | 
            +
                        [ password_label | (150)password_field ]
         | 
| 59 | 
            +
                        [ _ | >save_button ]
         | 
| 60 | 
            +
                    "
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                    @ui = Profligacy::Swing::LEL.new(JFrame, layout) do |c,i|
         | 
| 63 | 
            +
                      c.username_label = JLabel.new "PG&E Username:"
         | 
| 64 | 
            +
                      c.username_field = JTextField.new
         | 
| 65 | 
            +
                      c.password_label = JLabel.new "PG&E Password:"
         | 
| 66 | 
            +
                      c.password_field = JPasswordField.new
         | 
| 67 | 
            +
                      c.save_button = JButton.new("Save")
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                      i.save_button = { :action => proc do |t, e|
         | 
| 70 | 
            +
                          config = {
         | 
| 71 | 
            +
                            :username => @ui.username_field.text,
         | 
| 72 | 
            +
                            :password => @ui.password_field.text
         | 
| 73 | 
            +
                          }
         | 
| 74 | 
            +
                          @frame.dispose
         | 
| 75 | 
            +
                          yield config
         | 
| 76 | 
            +
                        end
         | 
| 77 | 
            +
                      }
         | 
| 78 | 
            +
                    end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                    @frame = @ui.build(:args => "Smartermeter Settings")
         | 
| 81 | 
            +
                    @frame.defaultCloseOperation = JFrame::DISPOSE_ON_CLOSE
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
              end
         | 
| 85 | 
            +
            end
         | 
    
        data/lib/smartermeter/main.rb
    CHANGED
    
    
    
        data/lib/smartermeter/service.rb
    CHANGED
    
    | @@ -7,6 +7,9 @@ module SmarterMeter | |
| 7 7 | 
             
                OVERVIEW_URL = "https://www.pge.com/csol/actions/login.do?aw"
         | 
| 8 8 | 
             
                ENERGYGUIDE_AUTH_URL = "https://www.energyguide.com/LoadAnalysis/LoadAnalysis.aspx?Referrerid=154"
         | 
| 9 9 |  | 
| 10 | 
            +
                attr_reader :last_page
         | 
| 11 | 
            +
                attr_reader :last_exception
         | 
| 12 | 
            +
             | 
| 10 13 | 
             
                def initialize
         | 
| 11 14 | 
             
                  @agent = WWW::Mechanize.new { |agent|
         | 
| 12 15 | 
             
                    agent.user_agent_alias = 'Mac Safari'
         | 
| @@ -15,64 +18,79 @@ module SmarterMeter | |
| 15 18 |  | 
| 16 19 | 
             
                # Returns true upon succesful login and false otherwise
         | 
| 17 20 | 
             
                def login(username, password)
         | 
| 18 | 
            -
                   | 
| 19 | 
            -
                     | 
| 20 | 
            -
                       | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 21 | 
            +
                  begin
         | 
| 22 | 
            +
                    @agent.get(LOGIN_URL) do |page|
         | 
| 23 | 
            +
                      logged_in_page = page.form_with(:action => 'https://www.pge.com/eum/login') do |login|
         | 
| 24 | 
            +
                        login.USER = username
         | 
| 25 | 
            +
                        login.PASSWORD = password
         | 
| 26 | 
            +
                      end.submit
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    # There is a crazy meta-reload thing here that mechanize doesn't handle
         | 
| 30 | 
            +
                    # correctly by itself so let's help it along...
         | 
| 31 | 
            +
                    @agent.get(OVERVIEW_URL) do |page|
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                      return false if page.title =~ /PG&E Login/
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                      # Load the PG&E Terms of Use page
         | 
| 36 | 
            +
                      tou_link = page.link_with(:href => '/csol/actions/billingDisclaimer.do?actionType=hourly')
         | 
| 37 | 
            +
                      unless tou_link
         | 
| 38 | 
            +
                        @last_page = page
         | 
| 39 | 
            +
                        return false
         | 
| 40 | 
            +
                      end
         | 
| 41 | 
            +
                      tou_page = @agent.click(tou_link)
         | 
| 42 | 
            +
                      form = tou_page.forms().first
         | 
| 43 | 
            +
                      agree_button = form.button_with(:value => 'I Understand - Proceed')
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                      # Agree to the terms of use
         | 
| 46 | 
            +
                      form['agreement'] = 'yes'
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                      # Load up the PG&E frame page for historical data
         | 
| 49 | 
            +
                      hourly_usage_container = form.submit(agree_button)
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                      # Now load up the frame with the content
         | 
| 52 | 
            +
                      hourly_usage = @agent.click(hourly_usage_container.frames.select{|f| f.href == "/csol/nexus/content.jsp"}.first)
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                      # Now post the authentication information from PG&E to energyguide.com
         | 
| 55 | 
            +
                      @data_page = hourly_usage.form_with(:action => ENERGYGUIDE_AUTH_URL).submit
         | 
| 56 | 
            +
                    end
         | 
| 57 | 
            +
                    @authenticated = true
         | 
| 58 | 
            +
                  rescue Exception => e
         | 
| 59 | 
            +
                    @last_exception = e
         | 
| 60 | 
            +
                    return false
         | 
| 23 61 | 
             
                  end
         | 
| 24 | 
            -
             | 
| 25 | 
            -
                  # There is a crazy meta-reload thing here that mechanize doesn't handle
         | 
| 26 | 
            -
                  # correctly by itself so let's help it along...
         | 
| 27 | 
            -
                  @agent.get(OVERVIEW_URL) do |page|
         | 
| 28 | 
            -
             | 
| 29 | 
            -
                    return false if page.title =~ /PG&E Login/
         | 
| 30 | 
            -
             | 
| 31 | 
            -
                    # Load the PG&E Terms of Use page
         | 
| 32 | 
            -
                    tou_page = @agent.click(page.link_with(:href => '/csol/actions/billingDisclaimer.do?actionType=hourly'))
         | 
| 33 | 
            -
                    form = tou_page.forms().first
         | 
| 34 | 
            -
                    agree_button = form.button_with(:value => 'I Understand - Proceed')
         | 
| 35 | 
            -
             | 
| 36 | 
            -
                    # Agree to the terms of use
         | 
| 37 | 
            -
                    form['agreement'] = 'yes'
         | 
| 38 | 
            -
             | 
| 39 | 
            -
                    # Load up the PG&E frame page for historical data
         | 
| 40 | 
            -
                    hourly_usage_container = form.submit(agree_button)
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                    # Now load up the frame with the content
         | 
| 43 | 
            -
                    hourly_usage = @agent.click(hourly_usage_container.frames.select{|f| f.href == "/csol/nexus/content.jsp"}.first)
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                    # Now post the authentication information from PG&E to energyguide.com
         | 
| 46 | 
            -
                    @data_page = hourly_usage.form_with(:action => ENERGYGUIDE_AUTH_URL).submit
         | 
| 47 | 
            -
                  end
         | 
| 48 | 
            -
                  true
         | 
| 49 62 | 
             
                end
         | 
| 50 63 |  | 
| 51 64 | 
             
                def fetch_csv(date)
         | 
| 52 | 
            -
                   | 
| 65 | 
            +
                  raise RuntimeException, "login must be called before fetch_csv" unless @authenticated
         | 
| 53 66 |  | 
| 54 67 | 
             
                  # Now we almost actually have data. However we need to setup the desired
         | 
| 55 68 | 
             
                  # parameters first before we can get the exportable data. This really shouldn't
         | 
| 56 69 | 
             
                  # be necessary.
         | 
| 57 | 
            -
                   | 
| 58 | 
            -
                     | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 70 | 
            +
                  begin
         | 
| 71 | 
            +
                    hourly_data = @data_page.form_with(:action => "/LoadAnalysis/LoadAnalysis.aspx") do |form|
         | 
| 72 | 
            +
                      form['__EVENTTARGET'] = "objChartSelect$butSubmit"
         | 
| 73 | 
            +
                      form['objTimePeriods$objExport$hidChart'] = "Hourly Usage"
         | 
| 74 | 
            +
                      form['objTimePeriods$objExport$hidChartID'] = 8
         | 
| 75 | 
            +
                      form['objChartSelect$ddChart'] = 8 # Hourly usage
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                      form['objTimePeriods$objExport$hidTimePeriod'] = "Week"
         | 
| 78 | 
            +
                      form['objTimePeriods$objExport$hidTimePeriodID'] = 3
         | 
| 79 | 
            +
                      form['objTimePeriods$rlPeriod'] = 3
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                      form['objChartSelect$ccSelectedDate1'] = date.strftime("%m/%d/%Y")
         | 
| 82 | 
            +
                    end.submit
         | 
| 69 83 |  | 
| 70 | 
            -
             | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 84 | 
            +
                    # Now the beautiful data...
         | 
| 85 | 
            +
                    hourly_csv = hourly_data.form_with(:action => "/LoadAnalysis/LoadAnalysis.aspx") do |form|
         | 
| 86 | 
            +
                      form['__EVENTTARGET'] = "objTimePeriods$objExport$butExport"
         | 
| 87 | 
            +
                    end.submit
         | 
| 74 88 |  | 
| 75 | 
            -
             | 
| 89 | 
            +
                    hourly_csv.body
         | 
| 90 | 
            +
                  rescue Timeout::Error => e
         | 
| 91 | 
            +
                    @last_exception = e
         | 
| 92 | 
            +
                    return ""
         | 
| 93 | 
            +
                  end
         | 
| 76 94 | 
             
                end
         | 
| 77 95 | 
             
              end
         | 
| 78 96 | 
             
            end
         | 
    
        data/lib/smartermeter.rb
    CHANGED
    
    
    
        data/smartermeter.gemspec
    CHANGED
    
    | @@ -13,8 +13,8 @@ Gem::Specification.new do |s| | |
| 13 13 | 
             
              ## If your rubyforge_project name is different, then edit it and comment out
         | 
| 14 14 | 
             
              ## the sub! line in the Rakefile
         | 
| 15 15 | 
             
              s.name              = 'smartermeter'
         | 
| 16 | 
            -
              s.version           = '0. | 
| 17 | 
            -
              s.date              = '2011-01- | 
| 16 | 
            +
              s.version           = '0.2.0'
         | 
| 17 | 
            +
              s.date              = '2011-01-25'
         | 
| 18 18 | 
             
              s.rubyforge_project = 'smartermeter'
         | 
| 19 19 |  | 
| 20 20 | 
             
              ## Make sure your summary is short. The description may be as long
         | 
| @@ -61,6 +61,7 @@ Gem::Specification.new do |s| | |
| 61 61 | 
             
              ## THE MANIFEST COMMENTS, they are used as delimiters by the task.
         | 
| 62 62 | 
             
              # = MANIFEST =
         | 
| 63 63 | 
             
              s.files = %w[
         | 
| 64 | 
            +
                CHANGELOG.md
         | 
| 64 65 | 
             
                Gemfile
         | 
| 65 66 | 
             
                README.md
         | 
| 66 67 | 
             
                Rakefile
         | 
| @@ -68,6 +69,8 @@ Gem::Specification.new do |s| | |
| 68 69 | 
             
                build_configuration.rb
         | 
| 69 70 | 
             
                lib/smartermeter.rb
         | 
| 70 71 | 
             
                lib/smartermeter/daemon.rb
         | 
| 72 | 
            +
                lib/smartermeter/interfaces/cli.rb
         | 
| 73 | 
            +
                lib/smartermeter/interfaces/swing.rb
         | 
| 71 74 | 
             
                lib/smartermeter/main.rb
         | 
| 72 75 | 
             
                lib/smartermeter/sample.rb
         | 
| 73 76 | 
             
                lib/smartermeter/service.rb
         | 
    
        metadata
    CHANGED
    
    | @@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version | |
| 4 4 | 
             
              prerelease: false
         | 
| 5 5 | 
             
              segments: 
         | 
| 6 6 | 
             
                - 0
         | 
| 7 | 
            -
                -  | 
| 7 | 
            +
                - 2
         | 
| 8 8 | 
             
                - 0
         | 
| 9 | 
            -
              version: 0. | 
| 9 | 
            +
              version: 0.2.0
         | 
| 10 10 | 
             
            platform: ruby
         | 
| 11 11 | 
             
            authors: 
         | 
| 12 12 | 
             
              - Matt Colyer
         | 
| @@ -14,7 +14,7 @@ autorequire: | |
| 14 14 | 
             
            bindir: bin
         | 
| 15 15 | 
             
            cert_chain: []
         | 
| 16 16 |  | 
| 17 | 
            -
            date: 2011-01- | 
| 17 | 
            +
            date: 2011-01-25 00:00:00 -08:00
         | 
| 18 18 | 
             
            default_executable: smartermeter
         | 
| 19 19 | 
             
            dependencies: 
         | 
| 20 20 | 
             
              - !ruby/object:Gem::Dependency 
         | 
| @@ -82,6 +82,7 @@ extensions: [] | |
| 82 82 | 
             
            extra_rdoc_files: []
         | 
| 83 83 |  | 
| 84 84 | 
             
            files: 
         | 
| 85 | 
            +
              - CHANGELOG.md
         | 
| 85 86 | 
             
              - Gemfile
         | 
| 86 87 | 
             
              - README.md
         | 
| 87 88 | 
             
              - Rakefile
         | 
| @@ -89,6 +90,8 @@ files: | |
| 89 90 | 
             
              - build_configuration.rb
         | 
| 90 91 | 
             
              - lib/smartermeter.rb
         | 
| 91 92 | 
             
              - lib/smartermeter/daemon.rb
         | 
| 93 | 
            +
              - lib/smartermeter/interfaces/cli.rb
         | 
| 94 | 
            +
              - lib/smartermeter/interfaces/swing.rb
         | 
| 92 95 | 
             
              - lib/smartermeter/main.rb
         | 
| 93 96 | 
             
              - lib/smartermeter/sample.rb
         | 
| 94 97 | 
             
              - lib/smartermeter/service.rb
         |