lightwaverf 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/app/views/_graphs.ejs +81 -0
- data/bin/lightwaverf +4 -0
- data/lib/lightwaverf.rb +132 -15
- metadata +5 -4
@@ -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
|
-
|
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
|
+
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-
|
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
|
50
|
-
calendar
|
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
|