lightwaverf 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,81 @@
1
+ <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
2
+ <script type="text/javascript" src="https://www.google.com/jsapi"></script>
3
+ <script type="text/javascript">
4
+ var gauge, gauge_data, gauge_options;
5
+ google.load( 'visualization', '1.0', { packages: [ 'corechart', 'gauge', 'annotatedtimeline' ] } );
6
+ google.setOnLoadCallback( function ( ) {
7
+ var energy_data = new google.visualization.DataTable( );
8
+ energy_data.addColumn( 'date', 'Date' );
9
+ energy_data.addColumn( 'number', 'Electricity used' );
10
+ energy_data.addColumn( 'string', 'title1' );
11
+ energy_data.addColumn( 'string', 'text1' );
12
+ if (!Array.prototype.map) {
13
+ Array.prototype.map = function(callback, thisArg) {
14
+ var T, A, k;
15
+ if (this == null) {
16
+ throw new TypeError(" this is null or not defined");
17
+ }
18
+ var O = Object(this);
19
+ var len = O.length >>> 0;
20
+ if (typeof callback !== "function") {
21
+ throw new TypeError(callback + " is not a function");
22
+ }
23
+ if (thisArg) {
24
+ T = thisArg;
25
+ }
26
+ A = new Array(len);
27
+ k = 0;
28
+ while(k < len) {
29
+ var kValue, mappedValue;
30
+ if (k in O) {
31
+ kValue = O[ k ];
32
+ mappedValue = callback.call(T, kValue, k, O);
33
+ A[ k ] = mappedValue;
34
+ }
35
+ k++;
36
+ }
37
+ return A;
38
+ };
39
+ }
40
+ var raw_data = <%- summary %>;
41
+ var start_date = raw_data[0][0];
42
+ energy_data.addRows( raw_data.map( function ( e ) {
43
+ if ( e[0] !== start_date ) e[0] += start_date;
44
+ e[0] = new Date( 1000 * e[0] ); // as it is now a timestamp
45
+ // var d = '' + e[0];
46
+ // e[0] = new Date( '20' + d[0] + d[1] + '-' + d[2] + d[3] + '-' + d[4] + d[5] + ' ' + d[6] + d[7] + ':' + d[8] + d[9] );
47
+ e[1] = e[1] * 10;
48
+ e[2] = e[2] || '';
49
+ e[3] = e[3] || '';
50
+ return e;
51
+ } ));
52
+ var chart = new google.visualization.AnnotatedTimeLine( document.getElementById( 'energy_chart' ));
53
+ chart.draw( energy_data, { displayAnnotations: true, title: '24 hours electricity usage' } );
54
+
55
+ gauge = new google.visualization.Gauge( document.getElementById( 'gauge_div' ));
56
+ gauge_data = google.visualization.arrayToDataTable( [ ["Label", "Value"], ["Electric", raw_data.pop[1] ] ] );
57
+ gauge_options = {
58
+ width: '200',
59
+ height: '200',
60
+ redFrom: 90,
61
+ redTo: 100,
62
+ yellowFrom: 75,
63
+ yellowTo: 90,
64
+ minorTicks: 5
65
+ };
66
+ gauge.draw( gauge_data, gauge_options );
67
+ } );
68
+ $( function ( ) {
69
+ $('dt a').click( function ( ) {
70
+ $(this).parent( ).next('dd').slideDown( );
71
+ return false;
72
+ } );
73
+ $('a.ajax').click( function ( ) {
74
+ $a = $(this);
75
+ $.get( $a.attr('href'), function ( js ) {
76
+ alert( js.result || $a.text( ));
77
+ } );
78
+ return false;
79
+ } );
80
+ } );
81
+ </script>
data/bin/lightwaverf CHANGED
@@ -23,6 +23,10 @@ case ARGV[0]
23
23
  puts LightWaveRF.new.run_timers ARGV[1], ARGV[2]
24
24
  when 'update'
25
25
  puts LightWaveRF.new.update_config ARGV[1], ARGV[2]
26
+ when 'web'
27
+ puts LightWaveRF.new.build_web_page ARGV[1]
28
+ when 'summarise'
29
+ puts LightWaveRF.new.summarise ARGV[1], ARGV[2]
26
30
  else
27
31
  LightWaveRF.new.send ARGV[0], ARGV[1], ARGV[2], ARGV[3]
28
32
  end
data/lib/lightwaverf.rb CHANGED
@@ -23,6 +23,7 @@ class LightWaveRF
23
23
 
24
24
  @config_file = nil
25
25
  @log_file = nil
26
+ @summary_file = nil
26
27
  @log_timer_file = nil
27
28
  @config = nil
28
29
  @timers = nil
@@ -108,6 +109,11 @@ class LightWaveRF
108
109
  @log_file || File.expand_path('~') + '/lightwaverf.log'
109
110
  end
110
111
 
112
+ # Summary file getter
113
+ def get_summary_file
114
+ @summary_file || File.expand_path('~') + '/lightwaverf-summary.json'
115
+ end
116
+
111
117
  # Timer log file getter
112
118
  def get_timer_log_file
113
119
  @timer_log_file || File.expand_path('~') + '/lightwaverf-timer.log'
@@ -143,18 +149,12 @@ class LightWaveRF
143
149
 
144
150
  # Get timer cache file, create it if needed
145
151
  def get_timer_cache
146
- p 'getting timer cache...'
147
152
  if ! @timers
148
- p 'no timers!'
149
153
  if ! File.exists? self.get_timer_cache_file
150
- p 'no file!'
151
- self.update_timers
152
- p 'updated...'
154
+ self.update_timers
153
155
  end
154
156
  @timers = YAML.load_file self.get_timer_cache_file
155
- p 'timers now ' + @timers.to_s
156
157
  end
157
- p 'returning, timers now ' + @timers.to_s
158
158
  @timers
159
159
  end
160
160
 
@@ -733,24 +733,24 @@ class LightWaveRF
733
733
  debug and ( p "Timezone: " + timezone)
734
734
 
735
735
  # convert to datetimes
736
- start_dt = DateTime.parse(start_date.strip + ' ' + start_time.strip + ' ' + timezone.strip)
737
- end_dt = DateTime.parse(end_date.strip + ' ' + end_time.strip + ' ' + timezone.strip)
736
+ start_dt = DateTime.parse( start_date.strip + ' ' + start_time.strip + ' ' + timezone.strip )
737
+ end_dt = DateTime.parse( end_date.strip + ' ' + end_time.strip + ' ' + timezone.strip )
738
738
 
739
739
  # apply time modifier if it exists
740
740
  if time_modifier != 0
741
- debug and ( p "Adjusting timings by: " + time_modifier.to_s)
742
- start_dt = ((start_dt.to_time) + time_modifier*60).to_datetime
743
- end_dt = ((end_dt.to_time) + time_modifier*60).to_datetime
741
+ debug and ( p "Adjusting timings by: " + time_modifier.to_s )
742
+ start_dt = (( start_dt.to_time ) + time_modifier * 60 ).to_datetime
743
+ end_dt = (( end_dt.to_time ) + time_modifier * 60 ).to_datetime
744
744
  end
745
745
 
746
- debug and ( p "Start datetime: " + start_dt.to_s)
747
- debug and ( p "End datetime: " + end_dt.to_s)
746
+ debug and ( p "Start datetime: " + start_dt.to_s )
747
+ debug and ( p "End datetime: " + end_dt.to_s )
748
748
 
749
749
  # populate the dates
750
750
  event['date'] = start_dt
751
751
  # handle device entries without explicit on/off state
752
752
  if event['type'] == 'device' and ( event['state'].nil? or ( event['state'] != 'on' and event['state'] != 'off' ))
753
- debug and ( p "Duplicating event without explicit on/off state...")
753
+ debug and ( p "Duplicating event without explicit on/off state..." )
754
754
  # if not state was given, assume we meant 'on'
755
755
  if event['state'].nil?
756
756
  event['state'] = 'on'
@@ -946,5 +946,122 @@ class LightWaveRF
946
946
  self.log_timer_event 'run', nil, nil, nil, true
947
947
  end
948
948
 
949
+ def self.get_contents file
950
+ file = File.open file, 'r'
951
+ content = file.read
952
+ file.close
953
+ content
954
+ end
955
+
956
+ def build_web_page debug = nil
957
+
958
+ rooms = self.class.get_rooms self.get_config
959
+ list = '<dl>'
960
+ config = 'usage: lightwaverf ' + rooms.values.first['name'] + ' ' + rooms.values.first['device'].keys.first.to_s + ' on # where "' + rooms.keys.first + '" is a room in ' + self.get_config_file
961
+ rooms.each do | name, room |
962
+ debug and ( puts name + ' is ' + room.to_s )
963
+ list += '<dt>' + name + '</dt><dd><ul>'
964
+ room['device'].each do | device |
965
+ debug and ( puts 'device is ' + device.to_s )
966
+ list += '<li>' + room['name'].to_s + ' ' + device.first.to_s + '</li>'
967
+ end
968
+ list += '</ul></dd>'
969
+ end
970
+ list += '</dl>'
971
+
972
+ summary = self.class.get_contents self.get_summary_file
973
+ js = self.class.get_contents( File.dirname( __FILE__ ) + '/../app/views/_graphs.ejs' ).gsub( '<%- summary %>', summary )
974
+ date = Time.new.to_s
975
+ title = self.get_config.has_key?('title') ? self.get_config['title'] : ( 'Lightwaverf energy stats ' + date )
976
+ intro = <<-end
977
+ Sample page generated #{date} with <code>lightwaverf web</code>.
978
+ Check out <a href="https://github.com/pauly/lightwaverf">the new simplified repo</a> for details
979
+ or <a href="https://rubygems.org/gems/lightwaverf">gem install lightwaverf && lightwaverf web</a>...
980
+ <br />@todo make a decent, useful, simple, configurable web page...
981
+ end
982
+ help = list
983
+ html = <<-end
984
+ <html>
985
+ <head>
986
+ <title>#{title}</title>
987
+ <style type="text/css">
988
+ body { font-family: arial, verdana, sans-serif; }
989
+ div#energy_chart { width: 800px; height: 600px; }
990
+ div#gauge_div { width: 100px; height: 100px; }
991
+ </style>
992
+ </head>
993
+ <body>
994
+ <div class="container">
995
+ <div class="row">
996
+ <div class="col">
997
+ <h1>#{title}</h1>
998
+ <p class="intro">#{intro}</p>
999
+ <div id="energy_chart"></div>
1000
+ <h2>Rooms and devices</h2>
1001
+ <p>@todo make these links to control the devices...</p>
1002
+ <p class="help">#{help}</p>
1003
+ #{js}
1004
+ </div>
1005
+ <div class="col">
1006
+ <div class="col" id="gauge_div"></div>
1007
+ </div>
1008
+ </div>
1009
+ </div>
1010
+ <p>By <a href="http://www.clarkeology.com/blog/">Paul Clarke</a>, a work in progress.</p>
1011
+ </body>
1012
+ </html>
1013
+ end
1014
+ end
1015
+
1016
+ # summarise the log data for ease of use
1017
+ def summarise days = 7, debug = nil
1018
+ days = days.to_i
1019
+ data = [ ]
1020
+ daily = { }
1021
+ start_date = 0
1022
+ d = nil
1023
+ File.open( self.get_log_file, 'r' ).each_line do | line |
1024
+ line = JSON.parse( line )
1025
+ if line and line['timestamp']
1026
+ new_line = []
1027
+ d = line['timestamp'][2..3] + line['timestamp'][5..6] + line['timestamp'][8..9] # compact version of date
1028
+ ts = Time.parse( line['timestamp'] ).strftime '%s'
1029
+ ts = ts.to_i
1030
+ if start_date > 0
1031
+ ts = ts - start_date
1032
+ else
1033
+ start_date = ts
1034
+ end
1035
+ new_line << ts
1036
+ new_line << line['message']['usage'].to_i / 10
1037
+ if line['message']['annotation'] and line['message']['annotation']['title'] and line['message']['annotation']['text']
1038
+ new_line << line['message']['annotation']['title']
1039
+ new_line << line['message']['annotation']['text']
1040
+ end
1041
+ data << new_line
1042
+ if line['message']['today'] > daily[d]['today']
1043
+ daily[d] = line['message']
1044
+ end
1045
+ end
1046
+ end
1047
+ debug and ( puts 'got ' + data.length.to_s + ' lines in the log' )
1048
+ data = data.last( 60 * 24 * days )
1049
+ debug and ( puts 'now got ' + data.length.to_s + ' lines in the log ( 60 * 24 * ' + days.to_s + ' = ' + ( 60 * 24 * days ).to_s + ' )' )
1050
+ if data[0][0] != start_date
1051
+ data[0][0] += start_date
1052
+ end
1053
+ summary_file = self.get_summary_file
1054
+ File.open( summary_file, 'w' ) do |file|
1055
+ file.write data.to_s
1056
+ end
1057
+ # @todo fix the daily stats, every night it reverts to the minimum value...
1058
+ File.open( summary_file.gsub( 'summary', 'daily' ), 'w' ) do | file |
1059
+ file.write daily.to_s
1060
+ end
1061
+ File.open( summary_file.gsub( 'summary', 'daily.' + d ), 'w' ) do | file |
1062
+ file.write daily.select { |key| key == daily.keys.last }.to_s
1063
+ end
1064
+ end
1065
+
949
1066
  end
950
1067
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lightwaverf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2013-06-22 00:00:00.000000000 Z
14
+ date: 2013-07-04 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: htmlentities
@@ -46,8 +46,8 @@ dependencies:
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  description: ! " Interact with lightwaverf wifi-link from code or the command line.\n
49
- \ Control your lights, heating, sockets etc.\n Also set up timers using a google
50
- calendar and log energy usage.\n"
49
+ \ Control your lights, heating, sockets, sprinkler etc.\n Also use a google
50
+ calendar, for timers, log energy usage, and build a website.\n"
51
51
  email: pauly@clarkeology.com
52
52
  executables:
53
53
  - lightwaverf
@@ -56,6 +56,7 @@ extensions: []
56
56
  extra_rdoc_files: []
57
57
  files:
58
58
  - lib/lightwaverf.rb
59
+ - app/views/_graphs.ejs
59
60
  - bin/lightwaverf
60
61
  - bin/lightwaverf-config-json
61
62
  homepage: http://www.clarkeology.com/wiki/lightwaverf+ruby