saal 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/bin/saal_envoy_generate_config +53 -0
 - data/bin/saal_envoy_read +143 -0
 - data/lib/dinrelay.rb +1 -18
 - data/lib/envoy.rb +284 -0
 - data/lib/http.rb +51 -0
 - data/lib/saal.rb +3 -1
 - data/lib/sensors.rb +16 -2
 - data/saal.gemspec +6 -2
 - metadata +26 -11
 - data/bin/saal_chart~ +0 -90
 - data/bin/saal_daemon~ +0 -31
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 62d7f9b51083356448d50bcb5ad66c729d84a38e152f85470ca4fa5f9f5fed15
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 73f51e4736d14ee249531535577a8ff0568e0c19fb3115a4cf76bb0801d8e504
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 349eed054b1fd37de656840840b92e1be279e77fe677b92cc47864fe96079f88e6dc784e608bf5a7843b08cc0025b1f141d1bf88be1cef553e18f104cc0c3d74
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: e07a22761c8f4f6c01d631abb29b01c63d6888c449fd67762e558ddd7693cd1bdfdf5b8bd7cdc35575006c6cb2fc9cbef49329bf08b754d5101d5dfc20d3421d
         
     | 
| 
         @@ -0,0 +1,53 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/env ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require File.dirname(__FILE__)+'/../lib/saal.rb'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            if ARGV.size != 1 && ARGV.size != 3
         
     | 
| 
      
 6 
     | 
    
         
            +
              $stderr.puts "USAGE: saal_envoy_generate_config <host> [<user> <password>]"
         
     | 
| 
      
 7 
     | 
    
         
            +
              exit(1)
         
     | 
| 
      
 8 
     | 
    
         
            +
            end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            puts "power:"
         
     | 
| 
      
 11 
     | 
    
         
            +
            puts "  envoy_power_energy:"
         
     | 
| 
      
 12 
     | 
    
         
            +
            puts "    host: \"#{ARGV[0]}\""
         
     | 
| 
      
 13 
     | 
    
         
            +
            puts
         
     | 
| 
      
 14 
     | 
    
         
            +
            puts "ac:"
         
     | 
| 
      
 15 
     | 
    
         
            +
            puts "  envoy_ac_quality:"
         
     | 
| 
      
 16 
     | 
    
         
            +
            puts "    host: \"#{ARGV[0]}\""
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            user = "envoy"
         
     | 
| 
      
 19 
     | 
    
         
            +
            password = nil
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            if ARGV[2]
         
     | 
| 
      
 22 
     | 
    
         
            +
              user = ARGV[1]
         
     | 
| 
      
 23 
     | 
    
         
            +
              password = ARGV[2]
         
     | 
| 
      
 24 
     | 
    
         
            +
            else
         
     | 
| 
      
 25 
     | 
    
         
            +
              Net::HTTP.get(ARGV[0], '/home').split("\n").each do |line|
         
     | 
| 
      
 26 
     | 
    
         
            +
                if line.include?("serial:")
         
     | 
| 
      
 27 
     | 
    
         
            +
                  password = line.split('"')[1][-6..-1]
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
              end
         
     | 
| 
      
 30 
     | 
    
         
            +
              if !password
         
     | 
| 
      
 31 
     | 
    
         
            +
                $stderr.puts "Couldn't find serial number for envoy"
         
     | 
| 
      
 32 
     | 
    
         
            +
                exit 2
         
     | 
| 
      
 33 
     | 
    
         
            +
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
            end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
            envoy = SAAL::Envoy::Inverters::new(
         
     | 
| 
      
 37 
     | 
    
         
            +
              :host => ARGV[0],
         
     | 
| 
      
 38 
     | 
    
         
            +
              :user => user,
         
     | 
| 
      
 39 
     | 
    
         
            +
              :password => password,
         
     | 
| 
      
 40 
     | 
    
         
            +
            )
         
     | 
| 
      
 41 
     | 
    
         
            +
            envoy.set_all_inverters!
         
     | 
| 
      
 42 
     | 
    
         
            +
            exit 0 if envoy.inverters.size <= 0
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
            puts
         
     | 
| 
      
 45 
     | 
    
         
            +
            puts "inverters:"
         
     | 
| 
      
 46 
     | 
    
         
            +
            puts "  envoy_inverters:"
         
     | 
| 
      
 47 
     | 
    
         
            +
            puts "    host: \"#{ARGV[0]}\""
         
     | 
| 
      
 48 
     | 
    
         
            +
            puts "    user: \"#{user}\""
         
     | 
| 
      
 49 
     | 
    
         
            +
            puts "    password: \"#{password}\""
         
     | 
| 
      
 50 
     | 
    
         
            +
            puts "    inverters:"
         
     | 
| 
      
 51 
     | 
    
         
            +
            envoy.inverters.each do |serial|
         
     | 
| 
      
 52 
     | 
    
         
            +
              puts "    - #{serial}"
         
     | 
| 
      
 53 
     | 
    
         
            +
            end
         
     | 
    
        data/bin/saal_envoy_read
    ADDED
    
    | 
         @@ -0,0 +1,143 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/env ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require File.dirname(__FILE__)+'/../lib/saal.rb'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            if ARGV.size != 3
         
     | 
| 
      
 6 
     | 
    
         
            +
              $stderr.puts "USAGE: saal_envoy_read <host> <user> <password>"
         
     | 
| 
      
 7 
     | 
    
         
            +
              exit(1)
         
     | 
| 
      
 8 
     | 
    
         
            +
            end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            def fdisp(val)
         
     | 
| 
      
 11 
     | 
    
         
            +
              if val
         
     | 
| 
      
 12 
     | 
    
         
            +
                '%10.0f' % val
         
     | 
| 
      
 13 
     | 
    
         
            +
              else
         
     | 
| 
      
 14 
     | 
    
         
            +
                "       n/a"
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
            end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            def fdispk(val)
         
     | 
| 
      
 19 
     | 
    
         
            +
              if val
         
     | 
| 
      
 20 
     | 
    
         
            +
                '%10.2f' % (val / 1000.0)
         
     | 
| 
      
 21 
     | 
    
         
            +
              else
         
     | 
| 
      
 22 
     | 
    
         
            +
                "       n/a"
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
            def fdisp_dec(val)
         
     | 
| 
      
 27 
     | 
    
         
            +
              if val
         
     | 
| 
      
 28 
     | 
    
         
            +
                '%10.2f' % val
         
     | 
| 
      
 29 
     | 
    
         
            +
              else
         
     | 
| 
      
 30 
     | 
    
         
            +
                "       n/a"
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
            end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
            def l(vals, name)
         
     | 
| 
      
 35 
     | 
    
         
            +
              sensor = vals[name]
         
     | 
| 
      
 36 
     | 
    
         
            +
              if sensor
         
     | 
| 
      
 37 
     | 
    
         
            +
                sensor.read
         
     | 
| 
      
 38 
     | 
    
         
            +
              else
         
     | 
| 
      
 39 
     | 
    
         
            +
                nil
         
     | 
| 
      
 40 
     | 
    
         
            +
              end
         
     | 
| 
      
 41 
     | 
    
         
            +
            end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
            def lratio(vals, name1, name2)
         
     | 
| 
      
 44 
     | 
    
         
            +
              v1 = l(vals, name1)
         
     | 
| 
      
 45 
     | 
    
         
            +
              v2 = l(vals, name2)
         
     | 
| 
      
 46 
     | 
    
         
            +
              if v1 && v2
         
     | 
| 
      
 47 
     | 
    
         
            +
                v1 / v2
         
     | 
| 
      
 48 
     | 
    
         
            +
              else
         
     | 
| 
      
 49 
     | 
    
         
            +
                nil
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
            end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
            ac_quality = SAAL::Envoy::ACQuality::new(:host => ARGV[0]).create_sensors
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
            puts " ========= AC QUALITY ========"
         
     | 
| 
      
 56 
     | 
    
         
            +
            puts "        voltage (V)  freq (Hz)"
         
     | 
| 
      
 57 
     | 
    
         
            +
            def qual_line(vals,name, type)
         
     | 
| 
      
 58 
     | 
    
         
            +
              puts " #{name} \
         
     | 
| 
      
 59 
     | 
    
         
            +
            #{fdisp_dec(l(vals,"ac_#{type}_voltage"))} \
         
     | 
| 
      
 60 
     | 
    
         
            +
            #{fdisp_dec(l(vals,"ac_#{type}_frequency"))} \
         
     | 
| 
      
 61 
     | 
    
         
            +
            "
         
     | 
| 
      
 62 
     | 
    
         
            +
            end
         
     | 
| 
      
 63 
     | 
    
         
            +
            qual_line(ac_quality, "Total: ", "total")
         
     | 
| 
      
 64 
     | 
    
         
            +
            qual_line(ac_quality, "Phase1:", "phase1")
         
     | 
| 
      
 65 
     | 
    
         
            +
            qual_line(ac_quality, "Phase2:", "phase2")
         
     | 
| 
      
 66 
     | 
    
         
            +
            qual_line(ac_quality, "Phase3:", "phase3")
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
            production = SAAL::Envoy::PowerEnergy::new(:host => ARGV[0]).create_sensors
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
            puts ""
         
     | 
| 
      
 71 
     | 
    
         
            +
            puts " ============ TRUE POWER (W) ============  ======= TRUE ENERGY (kWh) ======="
         
     | 
| 
      
 72 
     | 
    
         
            +
            puts "        consumption production        net  consumption production        net"
         
     | 
| 
      
 73 
     | 
    
         
            +
            def p_line(vals, name, type, metric)
         
     | 
| 
      
 74 
     | 
    
         
            +
              puts " #{name} \
         
     | 
| 
      
 75 
     | 
    
         
            +
            #{fdisp(l(vals,"pv_total_consumption_#{type}_#{metric}_now"))} \
         
     | 
| 
      
 76 
     | 
    
         
            +
            #{fdisp(l(vals,"pv_production_#{type}_#{metric}_now"))} \
         
     | 
| 
      
 77 
     | 
    
         
            +
            #{fdisp(l(vals,"pv_net_consumption_#{type}_#{metric}_now"))}   \
         
     | 
| 
      
 78 
     | 
    
         
            +
            #{fdispk(l(vals,"pv_total_consumption_#{type}_#{metric}h_lifetime"))} \
         
     | 
| 
      
 79 
     | 
    
         
            +
            #{fdispk(l(vals,"pv_production_#{type}_#{metric}h_lifetime"))} \
         
     | 
| 
      
 80 
     | 
    
         
            +
            #{fdispk(l(vals,"pv_net_consumption_#{type}_#{metric}h_lifetime"))} \
         
     | 
| 
      
 81 
     | 
    
         
            +
            "
         
     | 
| 
      
 82 
     | 
    
         
            +
            end
         
     | 
| 
      
 83 
     | 
    
         
            +
            p_line(production, "Total: ", "total", "w")
         
     | 
| 
      
 84 
     | 
    
         
            +
            p_line(production, "Phase1:", "phase1", "w")
         
     | 
| 
      
 85 
     | 
    
         
            +
            p_line(production, "Phase2:", "phase2", "w")
         
     | 
| 
      
 86 
     | 
    
         
            +
            p_line(production, "Phase3:", "phase3", "w")
         
     | 
| 
      
 87 
     | 
    
         
            +
            puts " Total Inverters:   \
         
     | 
| 
      
 88 
     | 
    
         
            +
            #{fdisp(l(production,"pv_production_inverters_w_now"))} \
         
     | 
| 
      
 89 
     | 
    
         
            +
                                    \
         
     | 
| 
      
 90 
     | 
    
         
            +
            #{fdispk(l(production,"pv_production_inverters_wh_lifetime"))} \
         
     | 
| 
      
 91 
     | 
    
         
            +
            "
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
            puts ""
         
     | 
| 
      
 94 
     | 
    
         
            +
            puts " ========== APPARENT POWER (VA) ========= ===== APPARENT ENERGY (kVAh) ===="
         
     | 
| 
      
 95 
     | 
    
         
            +
            puts "        consumption production        net  consumption production        net"
         
     | 
| 
      
 96 
     | 
    
         
            +
            p_line(production, "Total: ", "total", "va")
         
     | 
| 
      
 97 
     | 
    
         
            +
            p_line(production, "Phase1:", "phase1", "va")
         
     | 
| 
      
 98 
     | 
    
         
            +
            p_line(production, "Phase2:", "phase2", "va")
         
     | 
| 
      
 99 
     | 
    
         
            +
            p_line(production, "Phase3:", "phase3", "va")
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
            def pf_line(vals, name, type, metric)
         
     | 
| 
      
 102 
     | 
    
         
            +
              pf_total_consumption_instant = lratio(vals,"pv_total_consumption_#{type}_w_now","pv_total_consumption_#{type}_va_now")
         
     | 
| 
      
 103 
     | 
    
         
            +
              pf_total_production_instant = lratio(vals,"pv_production_#{type}_w_now","pv_production_#{type}_va_now")
         
     | 
| 
      
 104 
     | 
    
         
            +
              pf_net_production_instant = lratio(vals,"pv_net_consumption_#{type}_w_now","pv_net_consumption_#{type}_va_now")
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
              pf_total_consumption_lifetime = lratio(vals,"pv_total_consumption_#{type}_wh_lifetime","pv_total_consumption_#{type}_vah_lifetime")
         
     | 
| 
      
 107 
     | 
    
         
            +
              pf_total_production_lifetime = lratio(vals,"pv_production_#{type}_wh_lifetime","pv_production_#{type}_vah_lifetime")
         
     | 
| 
      
 108 
     | 
    
         
            +
              pf_net_production_lifetime = lratio(vals,"pv_net_consumption_#{type}_wh_lifetime","pv_net_consumption_#{type}_vah_lifetime")
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
              puts " #{name} \
         
     | 
| 
      
 111 
     | 
    
         
            +
            #{fdisp_dec(pf_total_consumption_instant)} \
         
     | 
| 
      
 112 
     | 
    
         
            +
            #{fdisp_dec(pf_total_production_instant)} \
         
     | 
| 
      
 113 
     | 
    
         
            +
            #{fdisp_dec(pf_net_production_instant)}   \
         
     | 
| 
      
 114 
     | 
    
         
            +
            #{fdisp_dec(pf_total_consumption_lifetime)} \
         
     | 
| 
      
 115 
     | 
    
         
            +
            #{fdisp_dec(pf_total_production_lifetime)} \
         
     | 
| 
      
 116 
     | 
    
         
            +
            #{fdisp_dec(pf_net_production_lifetime)} \
         
     | 
| 
      
 117 
     | 
    
         
            +
            "
         
     | 
| 
      
 118 
     | 
    
         
            +
            end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
            puts ""
         
     | 
| 
      
 121 
     | 
    
         
            +
            puts " ========= INSTANT POWER FACTOR ========= ====== LIFETIME POWER FACTOR ====="
         
     | 
| 
      
 122 
     | 
    
         
            +
            puts "        consumption production        net  consumption production        net"
         
     | 
| 
      
 123 
     | 
    
         
            +
            pf_line(production, "Total: ", "total", "va")
         
     | 
| 
      
 124 
     | 
    
         
            +
            pf_line(production, "Phase1:", "phase1", "va")
         
     | 
| 
      
 125 
     | 
    
         
            +
            pf_line(production, "Phase2:", "phase2", "va")
         
     | 
| 
      
 126 
     | 
    
         
            +
            pf_line(production, "Phase3:", "phase3", "va")
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
            puts ""
         
     | 
| 
      
 129 
     | 
    
         
            +
            envoy = SAAL::Envoy::Inverters::new(
         
     | 
| 
      
 130 
     | 
    
         
            +
              :host => ARGV[0],
         
     | 
| 
      
 131 
     | 
    
         
            +
              :user => ARGV[1],
         
     | 
| 
      
 132 
     | 
    
         
            +
              :password => ARGV[2],
         
     | 
| 
      
 133 
     | 
    
         
            +
            )
         
     | 
| 
      
 134 
     | 
    
         
            +
            envoy.set_all_inverters!
         
     | 
| 
      
 135 
     | 
    
         
            +
            inverters = envoy.create_sensors
         
     | 
| 
      
 136 
     | 
    
         
            +
            puts "Found #{envoy.inverters.size} inverters"
         
     | 
| 
      
 137 
     | 
    
         
            +
            envoy.inverters.each do |serial|
         
     | 
| 
      
 138 
     | 
    
         
            +
              puts "INVERTER: #{serial} \
         
     | 
| 
      
 139 
     | 
    
         
            +
            date:#{l(inverters,"inverter_#{serial}_last_report_date")} \
         
     | 
| 
      
 140 
     | 
    
         
            +
            lastWatts:#{l(inverters,"inverter_#{serial}_w_now")} \
         
     | 
| 
      
 141 
     | 
    
         
            +
            maxWatts:#{l(inverters,"inverter_#{serial}_w_max")} \
         
     | 
| 
      
 142 
     | 
    
         
            +
            "
         
     | 
| 
      
 143 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/dinrelay.rb
    CHANGED
    
    | 
         @@ -61,25 +61,8 @@ module SAAL 
     | 
|
| 
       61 
61 
     | 
    
         | 
| 
       62 
62 
     | 
    
         
             
                  private
         
     | 
| 
       63 
63 
     | 
    
         
             
                  def do_get(path)
         
     | 
| 
       64 
     | 
    
         
            -
                     
     | 
| 
       65 
     | 
    
         
            -
                      http = Net::HTTP.new(@host,@port)
         
     | 
| 
       66 
     | 
    
         
            -
                      # Timeout faster when the other side doesn't respond
         
     | 
| 
       67 
     | 
    
         
            -
                      http.open_timeout = @timeout
         
     | 
| 
       68 
     | 
    
         
            -
                      http.read_timeout = @timeout
         
     | 
| 
       69 
     | 
    
         
            -
                      req = Net::HTTP::Get.new(path)
         
     | 
| 
       70 
     | 
    
         
            -
                      req.basic_auth @user, @pass
         
     | 
| 
       71 
     | 
    
         
            -
                      response = http.request(req)
         
     | 
| 
       72 
     | 
    
         
            -
                      if response.code != "200"
         
     | 
| 
       73 
     | 
    
         
            -
                        #$stderr.puts "ERROR: Code #{response.code}"
         
     | 
| 
       74 
     | 
    
         
            -
                        #$stderr.puts response.body
         
     | 
| 
       75 
     | 
    
         
            -
                        return nil
         
     | 
| 
       76 
     | 
    
         
            -
                      end
         
     | 
| 
       77 
     | 
    
         
            -
                      return response
         
     | 
| 
       78 
     | 
    
         
            -
                    rescue Exception
         
     | 
| 
       79 
     | 
    
         
            -
                      return nil
         
     | 
| 
       80 
     | 
    
         
            -
                    end
         
     | 
| 
      
 64 
     | 
    
         
            +
                    SAAL::do_http_get(@host, @port, path, @user, @pass, @timeout)
         
     | 
| 
       81 
65 
     | 
    
         
             
                  end
         
     | 
| 
       82 
     | 
    
         
            -
             
     | 
| 
       83 
66 
     | 
    
         
             
                  def parse_index_html(str)
         
     | 
| 
       84 
67 
     | 
    
         
             
                    doc = Nokogiri::HTML(str)
         
     | 
| 
       85 
68 
     | 
    
         
             
                    outlets = doc.css('tr[bgcolor="#F4F4F4"]')
         
     | 
    
        data/lib/envoy.rb
    ADDED
    
    | 
         @@ -0,0 +1,284 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'json'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module SAAL
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Envoy
         
     | 
| 
      
 5 
     | 
    
         
            +
                class PowerEnergyUnderlying < SensorUnderlying
         
     | 
| 
      
 6 
     | 
    
         
            +
                  def initialize(key, production)
         
     | 
| 
      
 7 
     | 
    
         
            +
                    @key = key
         
     | 
| 
      
 8 
     | 
    
         
            +
                    @production = production
         
     | 
| 
      
 9 
     | 
    
         
            +
                  end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  def read(uncached = false)
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @production.read_val(@key)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                class PowerEnergy
         
     | 
| 
      
 17 
     | 
    
         
            +
                  DEFAULT_HOST = "envoy.local"
         
     | 
| 
      
 18 
     | 
    
         
            +
                  DEFAULT_TIMEOUT = 2
         
     | 
| 
      
 19 
     | 
    
         
            +
                  DEFAULT_CACHE_TIMEOUT = 50
         
     | 
| 
      
 20 
     | 
    
         
            +
                  DEFAULT_SOURCES = [
         
     | 
| 
      
 21 
     | 
    
         
            +
                    "production_inverters",
         
     | 
| 
      
 22 
     | 
    
         
            +
                    "production_phase1", "production_phase2", "production_phase3", "production_total",
         
     | 
| 
      
 23 
     | 
    
         
            +
                    "net_consumption_phase1", "net_consumption_phase2", "net_consumption_phase3", "net_consumption_total",
         
     | 
| 
      
 24 
     | 
    
         
            +
                    "total_consumption_phase1", "total_consumption_phase2", "total_consumption_phase3", "total_consumption_total",
         
     | 
| 
      
 25 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 26 
     | 
    
         
            +
                  DEFAULT_TYPES = [
         
     | 
| 
      
 27 
     | 
    
         
            +
                    "w_now", "wh_lifetime", "va_now", "vah_lifetime",
         
     | 
| 
      
 28 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 29 
     | 
    
         
            +
                  DEFAULT_PREFIX = "pv"
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  def initialize(defs, opts={})
         
     | 
| 
      
 32 
     | 
    
         
            +
                    @host = defs[:host] || defs['host'] || DEFAULT_HOST
         
     | 
| 
      
 33 
     | 
    
         
            +
                    @timeout = opts[:timeout] || opts['timeout'] || DEFAULT_TIMEOUT
         
     | 
| 
      
 34 
     | 
    
         
            +
                    @cache_timeout = opts[:cache_timeout] || opts['cache_timeout'] || DEFAULT_CACHE_TIMEOUT
         
     | 
| 
      
 35 
     | 
    
         
            +
                    @cache = nil
         
     | 
| 
      
 36 
     | 
    
         
            +
                    @cachetime = nil
         
     | 
| 
      
 37 
     | 
    
         
            +
                    @sources = defs[:sources] || defs['source'] || DEFAULT_SOURCES
         
     | 
| 
      
 38 
     | 
    
         
            +
                    @types = defs[:types] || defs['types'] || DEFAULT_TYPES
         
     | 
| 
      
 39 
     | 
    
         
            +
                    @prefix = defs[:prefix] || defs['prefix'] || DEFAULT_PREFIX
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  def read_val(name)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    if !@cachetime or @cachetime < Time.now - @cache_timeout
         
     | 
| 
      
 44 
     | 
    
         
            +
                      @cache = read_all()
         
     | 
| 
      
 45 
     | 
    
         
            +
                      @cachetime = Time.now
         
     | 
| 
      
 46 
     | 
    
         
            +
                    end
         
     | 
| 
      
 47 
     | 
    
         
            +
                    return @cache ? @cache[name] : nil
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                  def create_sensors
         
     | 
| 
      
 51 
     | 
    
         
            +
                    sensors = {}
         
     | 
| 
      
 52 
     | 
    
         
            +
                    @sources.product(@types).each do |source, type|
         
     | 
| 
      
 53 
     | 
    
         
            +
                      key = "#{@prefix}_#{source}_#{type}"
         
     | 
| 
      
 54 
     | 
    
         
            +
                      sensors[key] = PowerEnergyUnderlying.new(key, self)
         
     | 
| 
      
 55 
     | 
    
         
            +
                    end
         
     | 
| 
      
 56 
     | 
    
         
            +
                    sensors
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  private
         
     | 
| 
      
 60 
     | 
    
         
            +
                  def save_vals(dest, name, source)
         
     | 
| 
      
 61 
     | 
    
         
            +
                    {
         
     | 
| 
      
 62 
     | 
    
         
            +
                     "wNow" => "w_now",
         
     | 
| 
      
 63 
     | 
    
         
            +
                     "apprntPwr" => "va_now",
         
     | 
| 
      
 64 
     | 
    
         
            +
                     "whLifetime" => "wh_lifetime",
         
     | 
| 
      
 65 
     | 
    
         
            +
                     "vahLifetime" => "vah_lifetime",
         
     | 
| 
      
 66 
     | 
    
         
            +
                    }.each do |type, label|
         
     | 
| 
      
 67 
     | 
    
         
            +
                      dest["#{@prefix}_#{name}_#{label}"] = source[type]
         
     | 
| 
      
 68 
     | 
    
         
            +
                    end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                    # Hack around the fact that apprntPwr is broken on the total consumption
         
     | 
| 
      
 71 
     | 
    
         
            +
                    # calculation for the three-phase sum at least
         
     | 
| 
      
 72 
     | 
    
         
            +
                    # In those cases it seems to be missing a divide by three, so when the
         
     | 
| 
      
 73 
     | 
    
         
            +
                    # calculation for voltage and current alone is close do the extra divide
         
     | 
| 
      
 74 
     | 
    
         
            +
                    va_now = dest["#{@prefix}_#{name}_va_now"]
         
     | 
| 
      
 75 
     | 
    
         
            +
                    if va_now && !name.include?("phase")
         
     | 
| 
      
 76 
     | 
    
         
            +
                      voltage = source["rmsVoltage"]
         
     | 
| 
      
 77 
     | 
    
         
            +
                      current = source["rmsCurrent"]
         
     | 
| 
      
 78 
     | 
    
         
            +
                      if voltage && current
         
     | 
| 
      
 79 
     | 
    
         
            +
                        va_alt = voltage * current
         
     | 
| 
      
 80 
     | 
    
         
            +
                        if ((va_alt / va_now) - 1.0).abs < 0.05
         
     | 
| 
      
 81 
     | 
    
         
            +
                          dest["#{@prefix}_#{name}_va_now"] = va_now / 3.0
         
     | 
| 
      
 82 
     | 
    
         
            +
                        end
         
     | 
| 
      
 83 
     | 
    
         
            +
                      end
         
     | 
| 
      
 84 
     | 
    
         
            +
                    end
         
     | 
| 
      
 85 
     | 
    
         
            +
                  end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                  def read_all
         
     | 
| 
      
 88 
     | 
    
         
            +
                    response = SAAL::do_http_get(@host, 80, "/production.json?details=1", nil, nil, @timeout)
         
     | 
| 
      
 89 
     | 
    
         
            +
                    return nil if !response
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                    values = JSON.parse(response.body)
         
     | 
| 
      
 92 
     | 
    
         
            +
                    outputs = {}
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                    values["production"].each do |source|
         
     | 
| 
      
 95 
     | 
    
         
            +
                      type = source["type"]
         
     | 
| 
      
 96 
     | 
    
         
            +
                      case type
         
     | 
| 
      
 97 
     | 
    
         
            +
                      when "inverters"
         
     | 
| 
      
 98 
     | 
    
         
            +
                        save_vals(outputs, "production_inverters", source)
         
     | 
| 
      
 99 
     | 
    
         
            +
                      when "eim"
         
     | 
| 
      
 100 
     | 
    
         
            +
                        if source["lines"]
         
     | 
| 
      
 101 
     | 
    
         
            +
                          save_vals(outputs, "production_phase1", source["lines"][0])
         
     | 
| 
      
 102 
     | 
    
         
            +
                          save_vals(outputs, "production_phase2", source["lines"][1])
         
     | 
| 
      
 103 
     | 
    
         
            +
                          save_vals(outputs, "production_phase3", source["lines"][2])
         
     | 
| 
      
 104 
     | 
    
         
            +
                        end
         
     | 
| 
      
 105 
     | 
    
         
            +
                        save_vals(outputs, "production_total", source)
         
     | 
| 
      
 106 
     | 
    
         
            +
                      else
         
     | 
| 
      
 107 
     | 
    
         
            +
                        $stderr.puts "WARNING: ENVOY: don't know source type #{type}"
         
     | 
| 
      
 108 
     | 
    
         
            +
                      end
         
     | 
| 
      
 109 
     | 
    
         
            +
                    end
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                    values["consumption"].each do |source|
         
     | 
| 
      
 112 
     | 
    
         
            +
                      type = {
         
     | 
| 
      
 113 
     | 
    
         
            +
                        "total-consumption" => "total",
         
     | 
| 
      
 114 
     | 
    
         
            +
                        "net-consumption" => "net",
         
     | 
| 
      
 115 
     | 
    
         
            +
                      }[source["measurementType"]] || "unknown";
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                      if source["lines"]
         
     | 
| 
      
 118 
     | 
    
         
            +
                        save_vals(outputs, "#{type}_consumption_phase1", source["lines"][0])
         
     | 
| 
      
 119 
     | 
    
         
            +
                        save_vals(outputs, "#{type}_consumption_phase2", source["lines"][1])
         
     | 
| 
      
 120 
     | 
    
         
            +
                        save_vals(outputs, "#{type}_consumption_phase3", source["lines"][2])
         
     | 
| 
      
 121 
     | 
    
         
            +
                      end
         
     | 
| 
      
 122 
     | 
    
         
            +
                      save_vals(outputs, "#{type}_consumption_total", source)
         
     | 
| 
      
 123 
     | 
    
         
            +
                    end
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                    outputs
         
     | 
| 
      
 126 
     | 
    
         
            +
                  end
         
     | 
| 
      
 127 
     | 
    
         
            +
                end
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                class ACQualityUnderlying < SensorUnderlying
         
     | 
| 
      
 130 
     | 
    
         
            +
                  def initialize(key, production)
         
     | 
| 
      
 131 
     | 
    
         
            +
                    @key = key
         
     | 
| 
      
 132 
     | 
    
         
            +
                    @production = production
         
     | 
| 
      
 133 
     | 
    
         
            +
                  end
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
                  def read(uncached = false)
         
     | 
| 
      
 136 
     | 
    
         
            +
                    @production.read_val(@key)
         
     | 
| 
      
 137 
     | 
    
         
            +
                  end
         
     | 
| 
      
 138 
     | 
    
         
            +
                end
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
                class ACQuality
         
     | 
| 
      
 141 
     | 
    
         
            +
                  DEFAULT_HOST = "envoy.local"
         
     | 
| 
      
 142 
     | 
    
         
            +
                  DEFAULT_TIMEOUT = 2
         
     | 
| 
      
 143 
     | 
    
         
            +
                  DEFAULT_CACHE_TIMEOUT = 50
         
     | 
| 
      
 144 
     | 
    
         
            +
                  DEFAULT_SOURCES = ["total","phase1","phase2","phase3",]
         
     | 
| 
      
 145 
     | 
    
         
            +
                  DEFAULT_TYPES = ["frequency","voltage"]
         
     | 
| 
      
 146 
     | 
    
         
            +
                  DEFAULT_PREFIX = "ac"
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
                  def initialize(defs, opts={})
         
     | 
| 
      
 149 
     | 
    
         
            +
                    @host = defs[:host] || defs['host'] || DEFAULT_HOST
         
     | 
| 
      
 150 
     | 
    
         
            +
                    @timeout = opts[:timeout] || opts['timeout'] || DEFAULT_TIMEOUT
         
     | 
| 
      
 151 
     | 
    
         
            +
                    @cache_timeout = opts[:cache_timeout] || opts['cache_timeout'] || DEFAULT_CACHE_TIMEOUT
         
     | 
| 
      
 152 
     | 
    
         
            +
                    @cache = nil
         
     | 
| 
      
 153 
     | 
    
         
            +
                    @cachetime = nil
         
     | 
| 
      
 154 
     | 
    
         
            +
                    @sources = defs[:sources] || defs['source'] || DEFAULT_SOURCES
         
     | 
| 
      
 155 
     | 
    
         
            +
                    @types = defs[:types] || defs['types'] || DEFAULT_TYPES
         
     | 
| 
      
 156 
     | 
    
         
            +
                    @prefix = defs[:prefix] || defs['prefix'] || DEFAULT_PREFIX
         
     | 
| 
      
 157 
     | 
    
         
            +
                  end
         
     | 
| 
      
 158 
     | 
    
         
            +
             
     | 
| 
      
 159 
     | 
    
         
            +
                  def read_val(name)
         
     | 
| 
      
 160 
     | 
    
         
            +
                    if !@cachetime or @cachetime < Time.now - @cache_timeout
         
     | 
| 
      
 161 
     | 
    
         
            +
                      @cache = read_all()
         
     | 
| 
      
 162 
     | 
    
         
            +
                      @cachetime = Time.now
         
     | 
| 
      
 163 
     | 
    
         
            +
                    end
         
     | 
| 
      
 164 
     | 
    
         
            +
                    return @cache ? @cache[name] : nil
         
     | 
| 
      
 165 
     | 
    
         
            +
                  end
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
                  def create_sensors
         
     | 
| 
      
 168 
     | 
    
         
            +
                    sensors = {}
         
     | 
| 
      
 169 
     | 
    
         
            +
                    @sources.product(@types).each do |source, type|
         
     | 
| 
      
 170 
     | 
    
         
            +
                      key = "#{@prefix}_#{source}_#{type}"
         
     | 
| 
      
 171 
     | 
    
         
            +
                      sensors[key] = ACQualityUnderlying.new(key, self)
         
     | 
| 
      
 172 
     | 
    
         
            +
                    end
         
     | 
| 
      
 173 
     | 
    
         
            +
                    sensors
         
     | 
| 
      
 174 
     | 
    
         
            +
                  end
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
                  private
         
     | 
| 
      
 177 
     | 
    
         
            +
                  def save_vals(dest, name, source)
         
     | 
| 
      
 178 
     | 
    
         
            +
                    {
         
     | 
| 
      
 179 
     | 
    
         
            +
                     "voltage" => "voltage",
         
     | 
| 
      
 180 
     | 
    
         
            +
                     "freq" => "frequency",
         
     | 
| 
      
 181 
     | 
    
         
            +
                    }.each do |type, label|
         
     | 
| 
      
 182 
     | 
    
         
            +
                      dest["#{@prefix}_#{name}_#{label}"] = source[type]
         
     | 
| 
      
 183 
     | 
    
         
            +
                    end
         
     | 
| 
      
 184 
     | 
    
         
            +
                  end
         
     | 
| 
      
 185 
     | 
    
         
            +
             
     | 
| 
      
 186 
     | 
    
         
            +
                  def read_all
         
     | 
| 
      
 187 
     | 
    
         
            +
                    response = SAAL::do_http_get(@host, 80, "/ivp/meters/readings", nil, nil, @timeout)
         
     | 
| 
      
 188 
     | 
    
         
            +
                    return nil if !response
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
                    values = JSON.parse(response.body)
         
     | 
| 
      
 191 
     | 
    
         
            +
                    outputs = {}
         
     | 
| 
      
 192 
     | 
    
         
            +
                    source = values[0]
         
     | 
| 
      
 193 
     | 
    
         
            +
                    save_vals(outputs, "total", source)
         
     | 
| 
      
 194 
     | 
    
         
            +
                    if source["channels"]
         
     | 
| 
      
 195 
     | 
    
         
            +
                      save_vals(outputs, "phase1", source["channels"][0])
         
     | 
| 
      
 196 
     | 
    
         
            +
                      save_vals(outputs, "phase2", source["channels"][1])
         
     | 
| 
      
 197 
     | 
    
         
            +
                      save_vals(outputs, "phase3", source["channels"][2])
         
     | 
| 
      
 198 
     | 
    
         
            +
                    end
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
                    outputs
         
     | 
| 
      
 201 
     | 
    
         
            +
                  end
         
     | 
| 
      
 202 
     | 
    
         
            +
                end
         
     | 
| 
      
 203 
     | 
    
         
            +
             
     | 
| 
      
 204 
     | 
    
         
            +
                class InverterUnderlying < SensorUnderlying
         
     | 
| 
      
 205 
     | 
    
         
            +
                  def initialize(key, inverters)
         
     | 
| 
      
 206 
     | 
    
         
            +
                    @key = key
         
     | 
| 
      
 207 
     | 
    
         
            +
                    @inverters = inverters
         
     | 
| 
      
 208 
     | 
    
         
            +
                  end
         
     | 
| 
      
 209 
     | 
    
         
            +
             
     | 
| 
      
 210 
     | 
    
         
            +
                  def read(uncached = false)
         
     | 
| 
      
 211 
     | 
    
         
            +
                    @inverters.read_val(@key)
         
     | 
| 
      
 212 
     | 
    
         
            +
                  end
         
     | 
| 
      
 213 
     | 
    
         
            +
                end
         
     | 
| 
      
 214 
     | 
    
         
            +
             
     | 
| 
      
 215 
     | 
    
         
            +
                class Inverters
         
     | 
| 
      
 216 
     | 
    
         
            +
                  DEFAULT_TIMEOUT = 2
         
     | 
| 
      
 217 
     | 
    
         
            +
                  DEFAULT_CACHE_TIMEOUT = 50
         
     | 
| 
      
 218 
     | 
    
         
            +
                  DEFAULT_SOURCES = []
         
     | 
| 
      
 219 
     | 
    
         
            +
                  DEFAULT_TYPES = ["last_report_date", "watts_now", "watts_max"]
         
     | 
| 
      
 220 
     | 
    
         
            +
                  DEFAULT_USER = nil
         
     | 
| 
      
 221 
     | 
    
         
            +
                  DEFAULT_PASSWORD = nil
         
     | 
| 
      
 222 
     | 
    
         
            +
                  attr_reader :inverters
         
     | 
| 
      
 223 
     | 
    
         
            +
             
     | 
| 
      
 224 
     | 
    
         
            +
                  def initialize(defs, opts={})
         
     | 
| 
      
 225 
     | 
    
         
            +
                    @host = defs[:host] || defs['host'] || DEFAULT_HOST
         
     | 
| 
      
 226 
     | 
    
         
            +
                    @user = defs[:user] || defs['user'] || DEFAULT_USER
         
     | 
| 
      
 227 
     | 
    
         
            +
                    @password = defs[:password] || defs['password'] || DEFAULT_PASSWORD
         
     | 
| 
      
 228 
     | 
    
         
            +
                    @timeout = opts[:timeout] || opts['timeout'] || DEFAULT_TIMEOUT
         
     | 
| 
      
 229 
     | 
    
         
            +
                    @cache_timeout = opts[:cache_timeout] || opts['cache_timeout'] || DEFAULT_CACHE_TIMEOUT
         
     | 
| 
      
 230 
     | 
    
         
            +
                    @cache = nil
         
     | 
| 
      
 231 
     | 
    
         
            +
                    @cachetime = nil
         
     | 
| 
      
 232 
     | 
    
         
            +
                    @inverters_list = {}
         
     | 
| 
      
 233 
     | 
    
         
            +
                    @inverters = defs[:inverters] || defs['inverters'] || DEFAULT_SOURCES
         
     | 
| 
      
 234 
     | 
    
         
            +
                    @types = defs[:types] || defs['types'] || DEFAULT_TYPES
         
     | 
| 
      
 235 
     | 
    
         
            +
                  end
         
     | 
| 
      
 236 
     | 
    
         
            +
             
     | 
| 
      
 237 
     | 
    
         
            +
                  def read_val(name)
         
     | 
| 
      
 238 
     | 
    
         
            +
                    if !@cachetime or @cachetime < Time.now - @cache_timeout
         
     | 
| 
      
 239 
     | 
    
         
            +
                      @cache = read_all()
         
     | 
| 
      
 240 
     | 
    
         
            +
                      @cachetime = Time.now
         
     | 
| 
      
 241 
     | 
    
         
            +
                    end
         
     | 
| 
      
 242 
     | 
    
         
            +
                    return @cache ? @cache[name] : nil
         
     | 
| 
      
 243 
     | 
    
         
            +
                  end
         
     | 
| 
      
 244 
     | 
    
         
            +
             
     | 
| 
      
 245 
     | 
    
         
            +
                  def enumerate
         
     | 
| 
      
 246 
     | 
    
         
            +
                    read_val("foo") # Force a read to make sure the inverter serials are stored
         
     | 
| 
      
 247 
     | 
    
         
            +
                    @inverters_list.keys
         
     | 
| 
      
 248 
     | 
    
         
            +
                  end
         
     | 
| 
      
 249 
     | 
    
         
            +
             
     | 
| 
      
 250 
     | 
    
         
            +
                  def set_all_inverters!
         
     | 
| 
      
 251 
     | 
    
         
            +
                    @inverters = self.enumerate
         
     | 
| 
      
 252 
     | 
    
         
            +
                  end
         
     | 
| 
      
 253 
     | 
    
         
            +
             
     | 
| 
      
 254 
     | 
    
         
            +
                  def create_sensors
         
     | 
| 
      
 255 
     | 
    
         
            +
                    sensors = {}
         
     | 
| 
      
 256 
     | 
    
         
            +
                    @inverters.product(@types).each do |source, type|
         
     | 
| 
      
 257 
     | 
    
         
            +
                      key = "inverter_#{source}_#{type}"
         
     | 
| 
      
 258 
     | 
    
         
            +
                      sensors[key] = InverterUnderlying.new(key, self)
         
     | 
| 
      
 259 
     | 
    
         
            +
                    end
         
     | 
| 
      
 260 
     | 
    
         
            +
                    sensors
         
     | 
| 
      
 261 
     | 
    
         
            +
                  end
         
     | 
| 
      
 262 
     | 
    
         
            +
             
     | 
| 
      
 263 
     | 
    
         
            +
                  private
         
     | 
| 
      
 264 
     | 
    
         
            +
                  def read_all
         
     | 
| 
      
 265 
     | 
    
         
            +
                    response = SAAL::do_http_get_digest(@host, 80, "/api/v1/production/inverters", @user, @password, @timeout)
         
     | 
| 
      
 266 
     | 
    
         
            +
                    return nil if !response
         
     | 
| 
      
 267 
     | 
    
         
            +
             
     | 
| 
      
 268 
     | 
    
         
            +
                    values = JSON.parse(response.body)
         
     | 
| 
      
 269 
     | 
    
         
            +
                    inverters = {}
         
     | 
| 
      
 270 
     | 
    
         
            +
                    values.each do |inverter|
         
     | 
| 
      
 271 
     | 
    
         
            +
                      {"lastReportDate" => "last_report_date",
         
     | 
| 
      
 272 
     | 
    
         
            +
                       "lastReportWatts" => "watts_now",
         
     | 
| 
      
 273 
     | 
    
         
            +
                       "maxReportWatts" => "watts_max",
         
     | 
| 
      
 274 
     | 
    
         
            +
                      }.each do |type, label|
         
     | 
| 
      
 275 
     | 
    
         
            +
                        inverters["inverter_#{inverter["serialNumber"]}_#{label}"] = inverter[type]
         
     | 
| 
      
 276 
     | 
    
         
            +
                        @inverters_list[inverter["serialNumber"]] = true
         
     | 
| 
      
 277 
     | 
    
         
            +
                      end
         
     | 
| 
      
 278 
     | 
    
         
            +
                    end
         
     | 
| 
      
 279 
     | 
    
         
            +
             
     | 
| 
      
 280 
     | 
    
         
            +
                    inverters
         
     | 
| 
      
 281 
     | 
    
         
            +
                  end
         
     | 
| 
      
 282 
     | 
    
         
            +
                end
         
     | 
| 
      
 283 
     | 
    
         
            +
              end
         
     | 
| 
      
 284 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/http.rb
    ADDED
    
    | 
         @@ -0,0 +1,51 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'uri'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'net/http'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'net/http/digest_auth'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            def SAAL::do_http_get(host, port, path, user, pass, timeout)
         
     | 
| 
      
 6 
     | 
    
         
            +
              begin
         
     | 
| 
      
 7 
     | 
    
         
            +
                http = Net::HTTP.new(host,port)
         
     | 
| 
      
 8 
     | 
    
         
            +
                # Timeout faster when the other side doesn't respond
         
     | 
| 
      
 9 
     | 
    
         
            +
                http.open_timeout = timeout
         
     | 
| 
      
 10 
     | 
    
         
            +
                http.read_timeout = timeout
         
     | 
| 
      
 11 
     | 
    
         
            +
                req = Net::HTTP::Get.new(path)
         
     | 
| 
      
 12 
     | 
    
         
            +
                req.basic_auth(user, pass) if user && pass
         
     | 
| 
      
 13 
     | 
    
         
            +
                response = http.request(req)
         
     | 
| 
      
 14 
     | 
    
         
            +
                if response.code != "200"
         
     | 
| 
      
 15 
     | 
    
         
            +
                  #$stderr.puts "ERROR: Code #{response.code}"
         
     | 
| 
      
 16 
     | 
    
         
            +
                  #$stderr.puts response.body
         
     | 
| 
      
 17 
     | 
    
         
            +
                  return nil
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
                return response
         
     | 
| 
      
 20 
     | 
    
         
            +
              rescue Exception
         
     | 
| 
      
 21 
     | 
    
         
            +
                return nil
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
            end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            def SAAL::do_http_get_digest(host, port, path, user, pass, timeout)
         
     | 
| 
      
 26 
     | 
    
         
            +
              begin
         
     | 
| 
      
 27 
     | 
    
         
            +
                uri = URI.parse "http://#{host}:#{port}/#{path}"
         
     | 
| 
      
 28 
     | 
    
         
            +
                digest_auth = Net::HTTP::DigestAuth.new
         
     | 
| 
      
 29 
     | 
    
         
            +
                uri.user = user
         
     | 
| 
      
 30 
     | 
    
         
            +
                uri.password = pass
         
     | 
| 
      
 31 
     | 
    
         
            +
                http = Net::HTTP.new(host,port)
         
     | 
| 
      
 32 
     | 
    
         
            +
                # Timeout faster when the other side doesn't respond
         
     | 
| 
      
 33 
     | 
    
         
            +
                http.open_timeout = timeout
         
     | 
| 
      
 34 
     | 
    
         
            +
                http.read_timeout = timeout
         
     | 
| 
      
 35 
     | 
    
         
            +
                req = Net::HTTP::Get.new(path)
         
     | 
| 
      
 36 
     | 
    
         
            +
                response = http.request(req)
         
     | 
| 
      
 37 
     | 
    
         
            +
                if response.code == "401" && user && pass
         
     | 
| 
      
 38 
     | 
    
         
            +
                  auth = digest_auth.auth_header uri, response['www-authenticate'], 'GET'
         
     | 
| 
      
 39 
     | 
    
         
            +
                  req.add_field 'Authorization', auth
         
     | 
| 
      
 40 
     | 
    
         
            +
                  response = http.request(req)
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
                if response.code != "200"
         
     | 
| 
      
 43 
     | 
    
         
            +
                  #$stderr.puts "ERROR: Code #{response.code}"
         
     | 
| 
      
 44 
     | 
    
         
            +
                  #$stderr.puts response.body
         
     | 
| 
      
 45 
     | 
    
         
            +
                  return nil
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
                return response
         
     | 
| 
      
 48 
     | 
    
         
            +
              rescue Exception
         
     | 
| 
      
 49 
     | 
    
         
            +
                return nil
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/saal.rb
    CHANGED
    
    | 
         @@ -10,7 +10,7 @@ module SAAL 
     | 
|
| 
       10 
10 
     | 
    
         
             
                DBCONF = CONFDIR+"database.yml"
         
     | 
| 
       11 
11 
     | 
    
         
             
                CHARTSCONF = CONFDIR+"charts.yml"
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
       13 
     | 
    
         
            -
                VERSION = '0.3. 
     | 
| 
      
 13 
     | 
    
         
            +
                VERSION = '0.3.1'
         
     | 
| 
       14 
14 
     | 
    
         
             
            end
         
     | 
| 
       15 
15 
     | 
    
         | 
| 
       16 
16 
     | 
    
         
             
            require File.dirname(__FILE__)+'/dbstore.rb'
         
     | 
| 
         @@ -23,4 +23,6 @@ require File.dirname(__FILE__)+'/chart.rb' 
     | 
|
| 
       23 
23 
     | 
    
         
             
            require File.dirname(__FILE__)+'/chart_data.rb'
         
     | 
| 
       24 
24 
     | 
    
         
             
            require File.dirname(__FILE__)+'/outliercache.rb'
         
     | 
| 
       25 
25 
     | 
    
         
             
            require File.dirname(__FILE__)+'/dinrelay.rb'
         
     | 
| 
      
 26 
     | 
    
         
            +
            require File.dirname(__FILE__)+'/envoy.rb'
         
     | 
| 
      
 27 
     | 
    
         
            +
            require File.dirname(__FILE__)+'/http.rb'
         
     | 
| 
       26 
28 
     | 
    
         | 
    
        data/lib/sensors.rb
    CHANGED
    
    | 
         @@ -40,9 +40,23 @@ module SAAL 
     | 
|
| 
       40 
40 
     | 
    
         
             
                      defs.merge!('name' => outlet_descriptions[num])
         
     | 
| 
       41 
41 
     | 
    
         
             
                      Sensor.new(dbstore, oname, DINRelay::Outlet.new(num.to_i, og), defs, opts)
         
     | 
| 
       42 
42 
     | 
    
         
             
                    end
         
     | 
| 
      
 43 
     | 
    
         
            +
                  elsif defs['envoy_power_energy']
         
     | 
| 
      
 44 
     | 
    
         
            +
                    defs = defs['envoy_power_energy'].merge('prefix' => name)
         
     | 
| 
      
 45 
     | 
    
         
            +
                    pe = SAAL::Envoy::PowerEnergy::new(defs)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    sensors = pe.create_sensors
         
     | 
| 
      
 47 
     | 
    
         
            +
                    return sensors.map do |name, underlying|
         
     | 
| 
      
 48 
     | 
    
         
            +
                      Sensor.new(dbstore, name, underlying, defs, opts)
         
     | 
| 
      
 49 
     | 
    
         
            +
                    end
         
     | 
| 
      
 50 
     | 
    
         
            +
                  elsif defs['envoy_ac_quality']
         
     | 
| 
      
 51 
     | 
    
         
            +
                    defs = defs['envoy_ac_quality'].merge('prefix' => name)
         
     | 
| 
      
 52 
     | 
    
         
            +
                    pe = SAAL::Envoy::ACQuality::new(defs)
         
     | 
| 
      
 53 
     | 
    
         
            +
                    sensors = pe.create_sensors
         
     | 
| 
      
 54 
     | 
    
         
            +
                    return sensors.map do |name, underlying|
         
     | 
| 
      
 55 
     | 
    
         
            +
                      Sensor.new(dbstore, name, underlying, defs, opts)
         
     | 
| 
      
 56 
     | 
    
         
            +
                    end
         
     | 
| 
       43 
57 
     | 
    
         
             
                  else
         
     | 
| 
       44 
     | 
    
         
            -
                     
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
      
 58 
     | 
    
         
            +
                    p defs, name
         
     | 
| 
      
 59 
     | 
    
         
            +
                    raise UnknownSensorType, "Couldn't figure out a valid sensor type for #{name}"
         
     | 
| 
       46 
60 
     | 
    
         
             
                  end
         
     | 
| 
       47 
61 
     | 
    
         
             
                end
         
     | 
| 
       48 
62 
     | 
    
         
             
              end
         
     | 
    
        data/saal.gemspec
    CHANGED
    
    | 
         @@ -6,8 +6,8 @@ Gem::Specification.new do |s| 
     | 
|
| 
       6 
6 
     | 
    
         
             
              s.platform = Gem::Platform::RUBY
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
8 
     | 
    
         
             
              s.name              = 'saal'
         
     | 
| 
       9 
     | 
    
         
            -
              s.version           = '0.3. 
     | 
| 
       10 
     | 
    
         
            -
              s.date              = ' 
     | 
| 
      
 9 
     | 
    
         
            +
              s.version           = '0.3.1'
         
     | 
| 
      
 10 
     | 
    
         
            +
              s.date              = '2020-12-19'
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
              s.summary     = "Thin abstraction layer for interfacing and recording sensors (currently onewire) and actuators (currently dinrelay)"
         
     | 
| 
       13 
13 
     | 
    
         
             
              s.description = <<EOF
         
     | 
| 
         @@ -32,6 +32,7 @@ EOF 
     | 
|
| 
       32 
32 
     | 
    
         
             
              s.add_runtime_dependency 'ownet', "~>0.2"
         
     | 
| 
       33 
33 
     | 
    
         
             
              s.add_runtime_dependency 'mysql2', "~>0.5"
         
     | 
| 
       34 
34 
     | 
    
         
             
              s.add_runtime_dependency 'nokogiri', '~>1.8'
         
     | 
| 
      
 35 
     | 
    
         
            +
              s.add_runtime_dependency 'net-http-digest_auth', '~>1.4'
         
     | 
| 
       35 
36 
     | 
    
         | 
| 
       36 
37 
     | 
    
         
             
              # = MANIFEST =
         
     | 
| 
       37 
38 
     | 
    
         
             
              s.files = %w[
         
     | 
| 
         @@ -45,6 +46,7 @@ EOF 
     | 
|
| 
       45 
46 
     | 
    
         
             
                bin/saal_chart
         
     | 
| 
       46 
47 
     | 
    
         
             
                bin/saal_daemon
         
     | 
| 
       47 
48 
     | 
    
         
             
                bin/saal_dump_database
         
     | 
| 
      
 49 
     | 
    
         
            +
                bin/saal_envoy_read
         
     | 
| 
       48 
50 
     | 
    
         
             
                bin/saal_import_mysql
         
     | 
| 
       49 
51 
     | 
    
         
             
                bin/saal_readall
         
     | 
| 
       50 
52 
     | 
    
         
             
                lib/chart.rb
         
     | 
| 
         @@ -53,6 +55,8 @@ EOF 
     | 
|
| 
       53 
55 
     | 
    
         
             
                lib/daemon.rb
         
     | 
| 
       54 
56 
     | 
    
         
             
                lib/dbstore.rb
         
     | 
| 
       55 
57 
     | 
    
         
             
                lib/dinrelay.rb
         
     | 
| 
      
 58 
     | 
    
         
            +
                lib/envoy.rb
         
     | 
| 
      
 59 
     | 
    
         
            +
                lib/http.rb
         
     | 
| 
       56 
60 
     | 
    
         
             
                lib/outliercache.rb
         
     | 
| 
       57 
61 
     | 
    
         
             
                lib/owsensor.rb
         
     | 
| 
       58 
62 
     | 
    
         
             
                lib/saal.rb
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: saal
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 0.3. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.3.1
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Pedro Côrte-Real
         
     | 
| 
       8 
     | 
    
         
            -
            autorequire: 
     | 
| 
      
 8 
     | 
    
         
            +
            autorequire:
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date:  
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2020-12-19 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: ownet
         
     | 
| 
         @@ -52,6 +52,20 @@ dependencies: 
     | 
|
| 
       52 
52 
     | 
    
         
             
                - - "~>"
         
     | 
| 
       53 
53 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       54 
54 
     | 
    
         
             
                    version: '1.8'
         
     | 
| 
      
 55 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 56 
     | 
    
         
            +
              name: net-http-digest_auth
         
     | 
| 
      
 57 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 58 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 59 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 60 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 61 
     | 
    
         
            +
                    version: '1.4'
         
     | 
| 
      
 62 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 63 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 64 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 65 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 66 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 67 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 68 
     | 
    
         
            +
                    version: '1.4'
         
     | 
| 
       55 
69 
     | 
    
         
             
            description: "A daemon and libraries to create an abstraction layer that interfaces
         
     | 
| 
       56 
70 
     | 
    
         
             
              with \nsensors and actuators, recording their state, responding to requests \nfor
         
     | 
| 
       57 
71 
     | 
    
         
             
              current and historical values, and allowing changes of state.\n"
         
     | 
| 
         @@ -60,12 +74,12 @@ executables: 
     | 
|
| 
       60 
74 
     | 
    
         
             
            - dinrelaystatus
         
     | 
| 
       61 
75 
     | 
    
         
             
            - dinrelayset
         
     | 
| 
       62 
76 
     | 
    
         
             
            - saal_import_mysql
         
     | 
| 
       63 
     | 
    
         
            -
            - saal_chart~
         
     | 
| 
       64 
     | 
    
         
            -
            - saal_daemon~
         
     | 
| 
       65 
77 
     | 
    
         
             
            - saal_dump_database
         
     | 
| 
       66 
78 
     | 
    
         
             
            - saal_readall
         
     | 
| 
       67 
79 
     | 
    
         
             
            - saal_chart
         
     | 
| 
       68 
80 
     | 
    
         
             
            - saal_daemon
         
     | 
| 
      
 81 
     | 
    
         
            +
            - saal_envoy_generate_config
         
     | 
| 
      
 82 
     | 
    
         
            +
            - saal_envoy_read
         
     | 
| 
       69 
83 
     | 
    
         
             
            extensions: []
         
     | 
| 
       70 
84 
     | 
    
         
             
            extra_rdoc_files:
         
     | 
| 
       71 
85 
     | 
    
         
             
            - README.rdoc
         
     | 
| 
         @@ -79,10 +93,10 @@ files: 
     | 
|
| 
       79 
93 
     | 
    
         
             
            - bin/dinrelayset
         
     | 
| 
       80 
94 
     | 
    
         
             
            - bin/dinrelaystatus
         
     | 
| 
       81 
95 
     | 
    
         
             
            - bin/saal_chart
         
     | 
| 
       82 
     | 
    
         
            -
            - bin/saal_chart~
         
     | 
| 
       83 
96 
     | 
    
         
             
            - bin/saal_daemon
         
     | 
| 
       84 
     | 
    
         
            -
            - bin/saal_daemon~
         
     | 
| 
       85 
97 
     | 
    
         
             
            - bin/saal_dump_database
         
     | 
| 
      
 98 
     | 
    
         
            +
            - bin/saal_envoy_generate_config
         
     | 
| 
      
 99 
     | 
    
         
            +
            - bin/saal_envoy_read
         
     | 
| 
       86 
100 
     | 
    
         
             
            - bin/saal_import_mysql
         
     | 
| 
       87 
101 
     | 
    
         
             
            - bin/saal_readall
         
     | 
| 
       88 
102 
     | 
    
         
             
            - lib/chart.rb
         
     | 
| 
         @@ -91,6 +105,8 @@ files: 
     | 
|
| 
       91 
105 
     | 
    
         
             
            - lib/daemon.rb
         
     | 
| 
       92 
106 
     | 
    
         
             
            - lib/dbstore.rb
         
     | 
| 
       93 
107 
     | 
    
         
             
            - lib/dinrelay.rb
         
     | 
| 
      
 108 
     | 
    
         
            +
            - lib/envoy.rb
         
     | 
| 
      
 109 
     | 
    
         
            +
            - lib/http.rb
         
     | 
| 
       94 
110 
     | 
    
         
             
            - lib/outliercache.rb
         
     | 
| 
       95 
111 
     | 
    
         
             
            - lib/owsensor.rb
         
     | 
| 
       96 
112 
     | 
    
         
             
            - lib/saal.rb
         
     | 
| 
         @@ -118,7 +134,7 @@ homepage: https://github.com/pedrocr/saal 
     | 
|
| 
       118 
134 
     | 
    
         
             
            licenses:
         
     | 
| 
       119 
135 
     | 
    
         
             
            - LGPL-2.1
         
     | 
| 
       120 
136 
     | 
    
         
             
            metadata: {}
         
     | 
| 
       121 
     | 
    
         
            -
            post_install_message: 
     | 
| 
      
 137 
     | 
    
         
            +
            post_install_message:
         
     | 
| 
       122 
138 
     | 
    
         
             
            rdoc_options:
         
     | 
| 
       123 
139 
     | 
    
         
             
            - "-S"
         
     | 
| 
       124 
140 
     | 
    
         
             
            - "-w 2"
         
     | 
| 
         @@ -137,9 +153,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       137 
153 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       138 
154 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       139 
155 
     | 
    
         
             
            requirements: []
         
     | 
| 
       140 
     | 
    
         
            -
             
     | 
| 
       141 
     | 
    
         
            -
             
     | 
| 
       142 
     | 
    
         
            -
            signing_key: 
         
     | 
| 
      
 156 
     | 
    
         
            +
            rubygems_version: 3.1.2
         
     | 
| 
      
 157 
     | 
    
         
            +
            signing_key:
         
     | 
| 
       143 
158 
     | 
    
         
             
            specification_version: 2
         
     | 
| 
       144 
159 
     | 
    
         
             
            summary: Thin abstraction layer for interfacing and recording sensors (currently onewire)
         
     | 
| 
       145 
160 
     | 
    
         
             
              and actuators (currently dinrelay)
         
     | 
    
        data/bin/saal_chart~
    DELETED
    
    | 
         @@ -1,90 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            #!/usr/bin/env ruby
         
     | 
| 
       2 
     | 
    
         
            -
             
     | 
| 
       3 
     | 
    
         
            -
            NUM_VALUES_SMALL = 500 # Datapoints in "small" charts
         
     | 
| 
       4 
     | 
    
         
            -
            NUM_VALUES_LARGE = 150 # Datapoints in "large" charts
         
     | 
| 
       5 
     | 
    
         
            -
            LARGE_CHART_THRESHOLD = 30*24*60*60 # Threshold for a large chart (in seconds)
         
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
            require File.dirname(__FILE__)+'/../lib/saal.rb'
         
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
            def usage
         
     | 
| 
       10 
     | 
    
         
            -
              $stderr.puts("Usage: saal_chart <chart dir>")
         
     | 
| 
       11 
     | 
    
         
            -
            end
         
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
            if ARGV.size != 1
         
     | 
| 
       14 
     | 
    
         
            -
              usage
         
     | 
| 
       15 
     | 
    
         
            -
              exit (2)
         
     | 
| 
       16 
     | 
    
         
            -
            end
         
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
       18 
     | 
    
         
            -
            SENSOR_RANGES = {:temperature=>[-15, 45], :humidity=>[0,100], :pressure=>[950,1050]}
         
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
            SAAL::Charts.new.each do |chart|
         
     | 
| 
       22 
     | 
    
         
            -
              $stderr.puts "Generating chart #{chart.name}"
         
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
              pngfile = ARGV[0]+'/chart-'+chart.name.to_s+'.png'
         
     | 
| 
       25 
     | 
    
         
            -
              ymlfile = ARGV[0]+'/chart-'+chart.name.to_s+'.yml'
         
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
              @mins = chart.minimum
         
     | 
| 
       28 
     | 
    
         
            -
              @maxs = chart.maximum
         
     | 
| 
       29 
     | 
    
         
            -
              @avgs = chart.average
         
     | 
| 
       30 
     | 
    
         
            -
              @minmax = {}
         
     | 
| 
       31 
     | 
    
         
            -
              chart.sensors.each do |s|
         
     | 
| 
       32 
     | 
    
         
            -
                s = s.name.to_sym
         
     | 
| 
       33 
     | 
    
         
            -
                @minmax[s] = {:maximum => @maxs[s], :minimum => @mins[s], :average => @avgs[s]}
         
     | 
| 
       34 
     | 
    
         
            -
              end
         
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
              File.open(ymlfile, 'w').write(YAML::dump(@minmax))
         
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
              def normalize_data(data, min, max)
         
     | 
| 
       39 
     | 
    
         
            -
                data.map do |i|
         
     | 
| 
       40 
     | 
    
         
            -
                  if i.nil?
         
     | 
| 
       41 
     | 
    
         
            -
                    -1.0
         
     | 
| 
       42 
     | 
    
         
            -
                  elsif i < min
         
     | 
| 
       43 
     | 
    
         
            -
                    0.0
         
     | 
| 
       44 
     | 
    
         
            -
                  elsif i > max
         
     | 
| 
       45 
     | 
    
         
            -
                    100.0
         
     | 
| 
       46 
     | 
    
         
            -
                  else
         
     | 
| 
       47 
     | 
    
         
            -
                    (((i-min)/(max-min).to_f)*1000).round/10.0
         
     | 
| 
       48 
     | 
    
         
            -
                  end
         
     | 
| 
       49 
     | 
    
         
            -
                end
         
     | 
| 
       50 
     | 
    
         
            -
              end
         
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
              @periodnames = chart.periodnames
         
     | 
| 
       53 
     | 
    
         
            -
              @numperiods = @periodnames.size
         
     | 
| 
       54 
     | 
    
         
            -
              num_values = ((chart.to-chart.from)>LARGE_CHART_THRESHOLD) ?
         
     | 
| 
       55 
     | 
    
         
            -
                           NUM_VALUES_LARGE : 
         
     | 
| 
       56 
     | 
    
         
            -
                           NUM_VALUES_SMALL
         
     | 
| 
       57 
     | 
    
         
            -
             
     | 
| 
       58 
     | 
    
         
            -
              @averages = chart.average(num_values)
         
     | 
| 
       59 
     | 
    
         
            -
             
     | 
| 
       60 
     | 
    
         
            -
              @data = chart.sensors.map do |sensor|
         
     | 
| 
       61 
     | 
    
         
            -
                normalize_data(@averages[sensor.name.to_sym], *(SENSOR_RANGES[sensor.sensor_type]))
         
     | 
| 
       62 
     | 
    
         
            -
              end
         
     | 
| 
       63 
     | 
    
         
            -
             
     | 
| 
       64 
     | 
    
         
            -
              @dataurl = @data.map {|values| values.join(",")}.join('|')
         
     | 
| 
       65 
     | 
    
         
            -
             
     | 
| 
       66 
     | 
    
         
            -
              r = {}
         
     | 
| 
       67 
     | 
    
         
            -
              case chart.alignlabels
         
     | 
| 
       68 
     | 
    
         
            -
              when :center
         
     | 
| 
       69 
     | 
    
         
            -
                @periodnamesurl = "||"+@periodnames.join('||')+"||"
         
     | 
| 
       70 
     | 
    
         
            -
              when :left
         
     | 
| 
       71 
     | 
    
         
            -
                @periodnamesurl = "|"+@periodnames.join('|')+"||"
         
     | 
| 
       72 
     | 
    
         
            -
                r[:chxs] = "0,555555,11,-1,lt"
         
     | 
| 
       73 
     | 
    
         
            -
              end
         
     | 
| 
       74 
     | 
    
         
            -
              @xincr = 100.0/@numperiods.to_f*10000.truncate.to_f/10000
         
     | 
| 
       75 
     | 
    
         
            -
             
     | 
| 
       76 
     | 
    
         
            -
              r[:chof] = "png"
         
     | 
| 
       77 
     | 
    
         
            -
              r[:chs] = "700x300"
         
     | 
| 
       78 
     | 
    
         
            -
              r[:cht] = "lc"
         
     | 
| 
       79 
     | 
    
         
            -
              r[:chco] = "00ff00,ff0000,0000ff,ff9933,800080"
         
     | 
| 
       80 
     | 
    
         
            -
              r[:chxt] = "x,y,y,r"
         
     | 
| 
       81 
     | 
    
         
            -
              r[:chxl] = "0:#{@periodnamesurl}1:|-15ºC||0||15||30||45ºC|2:|0%|25|50|75|100%|3:|950||975||1000||1025||1050 hPa"
         
     | 
| 
       82 
     | 
    
         
            -
              r[:chg] = "#{@xincr},12.5,1,5"
         
     | 
| 
       83 
     | 
    
         
            -
              r[:chd] = "t:#{@dataurl}"
         
     | 
| 
       84 
     | 
    
         
            -
             
     | 
| 
       85 
     | 
    
         
            -
              @url = "http://chart.apis.google.com/chart?&"
         
     | 
| 
       86 
     | 
    
         
            -
              @postdata = r.map{|k,v| k.to_s+"="+v}.join("&")
         
     | 
| 
       87 
     | 
    
         
            -
             
     | 
| 
       88 
     | 
    
         
            -
             
     | 
| 
       89 
     | 
    
         
            -
              system "wget --quiet \"#{@url}\" --post-data=\"#{@postdata}\" -O #{pngfile}"
         
     | 
| 
       90 
     | 
    
         
            -
            end
         
     | 
    
        data/bin/saal_daemon~
    DELETED
    
    | 
         @@ -1,31 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            #!/usr/bin/env ruby
         
     | 
| 
       2 
     | 
    
         
            -
             
     | 
| 
       3 
     | 
    
         
            -
            SENSOR_INTERVAL = 60 # seconds between consecutive measurements of the sensors
         
     | 
| 
       4 
     | 
    
         
            -
            DBCONF = "/etc/saal/database.yml"
         
     | 
| 
       5 
     | 
    
         
            -
            SENSORSCONF = "/etc/saal/sensors.yml"
         
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
            require File.dirname(__FILE__)+'/../lib/saal.rb'
         
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
            def usage
         
     | 
| 
       10 
     | 
    
         
            -
              $stderr.puts "Usage: saal_daemon <pidfile|--foreground>"
         
     | 
| 
       11 
     | 
    
         
            -
            end
         
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
            if ARGV.size != 1
         
     | 
| 
       14 
     | 
    
         
            -
              usage
         
     | 
| 
       15 
     | 
    
         
            -
              exit 2
         
     | 
| 
       16 
     | 
    
         
            -
            else
         
     | 
| 
       17 
     | 
    
         
            -
              pidfile = ARGV[0]
         
     | 
| 
       18 
     | 
    
         
            -
              foreground = (ARGV[0] == '--foreground')
         
     | 
| 
       19 
     | 
    
         
            -
              d = SAAL::Daemon.new(:interval => SENSOR_INTERVAL,
         
     | 
| 
       20 
     | 
    
         
            -
                                   :sensorconf => SENSORSCONF,
         
     | 
| 
       21 
     | 
    
         
            -
                                   :dbconf => DBCONF,
         
     | 
| 
       22 
     | 
    
         
            -
                                   :foreground => foreground,
         
     | 
| 
       23 
     | 
    
         
            -
                                   :keep_stdin => true)
         
     | 
| 
       24 
     | 
    
         
            -
              pid = d.run
         
     | 
| 
       25 
     | 
    
         
            -
              if !foreground
         
     | 
| 
       26 
     | 
    
         
            -
                File.open(pidfile, 'w') do |f|
         
     | 
| 
       27 
     | 
    
         
            -
                  f.write(pid)
         
     | 
| 
       28 
     | 
    
         
            -
                  f.close
         
     | 
| 
       29 
     | 
    
         
            -
                end
         
     | 
| 
       30 
     | 
    
         
            -
              end
         
     | 
| 
       31 
     | 
    
         
            -
            end
         
     |