]> Git — Sourcephile - julm/air-duino.git/blob - collect.pl
rename out/ -> view/, harden collect.pl parsing, add @id in .html5
[julm/air-duino.git] / collect.pl
1 #!/usr/bin/perl -w
2 # Licence: WTFPLv2 <http://www.wtfpl.net/txt/copying/>
3 # Copyright 2015: Julien Moutinho <julm+air@autogeree.net>
4
5 use strict;
6 use warnings;
7 use DateTime::Duration;
8 use DateTime;
9 use Device::SerialPort qw( 0.07 );
10 use File::Basename;
11 use File::Path;
12 use File::Spec;
13 use Log::Log4perl qw(:easy);
14 use RRDTool::OO;
15 #use Data::Dumper qw(Dumper);
16 #use IO::Handle;
17 my $step = 30; # NOTE: sampling interval in seconds
18
19 sub rrd_init () {
20 Log::Log4perl->easy_init(
21 { level => $INFO
22 , category => 'rrdtool'
23 , layout => '%m%n'
24 });
25 my $now
26 = DateTime->now
27 ( time_zone => 'local'
28 #, locale => $config{locale}
29 ); # ->set_time_zone('floating');
30 my $dir = File::Spec->catdir(dirname($0), 'rrd', (sprintf '%0d', $now->year()));
31 my $file = File::Spec->catfile($dir, (sprintf '%02d.rrd', $now->month()));
32 File::Path::make_path($dir);
33 my $rrd = RRDTool::OO->new(file => $file);
34 if (not (-e $file)) {
35 my $one_month = DateTime::Duration->new(months => 1, end_of_month => 'limit');
36 my $month_begin = $now->clone->truncate(to => 'month');
37 my $month_end = $month_begin->clone->add_duration($one_month);
38 my $month_seconds = $month_end->epoch() - $month_begin->epoch();
39 $rrd->create
40 ( step => $step
41 , data_source =>
42 { name => "humidity"
43 , type => "GAUGE"
44 , min => 0
45 , max => 100
46 , heartbeat => 2 * $step # seconds
47 }
48 , data_source =>
49 { name => "temperature"
50 , type => "GAUGE"
51 , min => -15
52 , max => 50
53 , heartbeat => 2 * $step # seconds
54 }
55 , data_source =>
56 { name => "quality"
57 , type => "GAUGE"
58 , min => 0
59 , max => 1000
60 , heartbeat => 2 * $step # seconds
61 }
62 , data_source =>
63 { name => "particles"
64 , type => "GAUGE"
65 , min => 0
66 , max => 100000
67 , heartbeat => 2 * $step # seconds
68 }
69 , archive =>
70 { cpoints => 1
71 , cfunc => 'AVERAGE'
72 , rows => $month_seconds / $step
73 , xff => 0.99 # ignore unknowns values (U)
74 }
75 );
76 }
77 return $rrd;
78 }
79 sub dev_init ($) {
80 my ($file) = @_;
81 my $dev = Device::SerialPort->new($file);
82 $dev->baudrate(9600); # MUST: match *uino Serial.begin()
83 #$dev->buffers(4096, 4096); # NOTE: no-op on POSIX
84 $dev->databits(8);
85 $dev->dtr_active(1); # NOTE: reset the *uino on serial connection
86 $dev->handshake('none');
87 $dev->parity("none");
88 $dev->read_char_time(0); # NOTE: don't wait for each character
89 $dev->read_const_time(1000); # NOTE: 1 second per unfulfilled "read" call
90 $dev->stopbits(1);
91 $dev->write_settings;
92 return $dev;
93 }
94
95 sub main () {
96 my $rrd = rrd_init();
97 my $dev = dev_init($ARGV[0]);
98
99 #autoflush STDOUT 1;
100 my $read_timeout = 60;
101 my $timeout = $read_timeout;
102 my $buffer = "";
103 my $last_time = 0;
104 my $last_day = (localtime)[3];
105 my @collect = ();
106 while ($timeout>0) {
107 my $curr_day = (localtime)[3];
108 if ($curr_day < $last_day) {
109 # NOTE: month changed, change RRD
110 $rrd = rrd_init();
111 $last_day = $curr_day;
112 }
113 my ($count, $saw) = $dev->read(1);
114 # NOTE: this could have read up to 255 chars (max for portability)
115 # but reading only 1 char at a time
116 # enables to add a more accurate timestamp.
117 if ($count > 0) {
118 $buffer .= $saw;
119 my @lines = split /\r\n/, $buffer;
120 if (@lines > 1) {
121 my $time = time;
122 # NOTE: process only ended lines
123 $buffer = pop @lines;
124 foreach (@lines) {
125 if ($_ =~ /^\d+;/) {
126 #print STDOUT ($time, ";", $_, "\n");
127 my @fields = split /;/, $_;
128 next if @fields != 6;
129 my ($counter, $uptime, $humidity, $temperature, $quality, $particles)
130 = @fields;
131 $collect[0] = $humidity if ($humidity ne '');
132 $collect[1] = $temperature if ($temperature ne '');
133 $collect[2] = $quality if ($quality ne '');
134 $collect[3] = $particles if ($particles ne '');
135 if ($time >= $last_time + $step && @collect == 4) {
136 $rrd->update
137 ( time => $time
138 , values => [join (':', @collect)]
139 );
140 @collect = ();
141 $last_time = $time;
142 }
143 }
144 else {
145 print STDERR ($_, "\n");
146 }
147 }
148 }
149 $timeout = $read_timeout;
150 }
151 else {
152 $timeout--;
153 }
154 }
155 if ($timeout==0) {
156 die "Connection timeout (after ${read_timeout}s)\n";
157 }
158 undef $dev;
159 }
160 main;
161 1;