]> Git — Sourcephile - julm/air-duino.git/blob - collect.pl
add comment
[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 => "temperature"
43 , type => "GAUGE"
44 , min => -15
45 , max => 49
46 , heartbeat => 2 * $step # seconds
47 }
48 , data_source =>
49 { name => "humidity"
50 , type => "GAUGE"
51 , min => 0
52 , max => 100
53 , heartbeat => 2 * $step # seconds
54 }
55 , data_source =>
56 { name => "air"
57 , type => "GAUGE"
58 , min => 0
59 , max => 1000
60 , heartbeat => 2 * $step # seconds
61 }
62 , data_source =>
63 { name => "dust"
64 , type => "GAUGE"
65 , min => 0
66 , max => 100000
67 , heartbeat => 2 * $step # seconds
68 }
69 , data_source =>
70 { name => "co_ch4_lpg"
71 , type => "GAUGE"
72 , min => 0
73 , max => 5
74 , heartbeat => 2 * $step # seconds
75 }
76 , archive =>
77 { cpoints => 1
78 , cfunc => 'AVERAGE'
79 , rows => $month_seconds / $step
80 , xff => 0.99 # ignore unknowns values (U)
81 }
82 );
83 }
84 return $rrd;
85 }
86 sub dev_init ($) {
87 my ($file) = @_;
88 my $dev = Device::SerialPort->new($file);
89 $dev->baudrate(9600); # MUST: match *uino Serial.begin()
90 #$dev->buffers(4096, 4096); # NOTE: no-op on POSIX
91 $dev->databits(8);
92 $dev->dtr_active(1); # NOTE: reset the *uino on serial connection
93 $dev->handshake('none');
94 $dev->parity("none");
95 $dev->read_char_time(0); # NOTE: don't wait for each character
96 $dev->read_const_time(1000); # NOTE: 1 second per unfulfilled "read" call
97 $dev->stopbits(1);
98 $dev->write_settings;
99 return $dev;
100 }
101
102 sub main () {
103 my $rrd = rrd_init();
104 my $dev = dev_init($ARGV[0]);
105
106 #autoflush STDOUT 1;
107 my $read_timeout = 60;
108 my $timeout = $read_timeout;
109 my $buffer = "";
110 my $last_time = 0;
111 my @collect = ();
112 while ($timeout>0) {
113 my $curr_day = (localtime)[3];
114 if ($curr_day == 1) {
115 # NOTE: month changed, change RRD
116 # FIXME: avoid to check that every second...
117 $rrd = rrd_init();
118 }
119 my ($count, $saw) = $dev->read(1);
120 # NOTE: this could have read up to 255 chars (max for portability)
121 # but reading only 1 char at a time
122 # enables to add a more accurate timestamp.
123 if (defined $count and $count > 0) {
124 $buffer .= $saw;
125 #print STDERR "saw: $buffer\n";
126 my @lines = split /\r?\n/, $buffer;
127 if (@lines > 1) {
128 my $time = time;
129 # NOTE: process only ended lines
130 $buffer = pop @lines;
131 #print STDERR "buffer: $buffer\n";
132 foreach (@lines) {
133 #print STDERR "line: $_\n";
134 my @fields
135 = $_ =~ m/^loop=[0-9]+;up=[0-9]+;temp=([0-9]+.[0-9]+)?;humi=([0-9]+.[0-9]+)?;air=([0-9]+)?;dust=([0-9]+.[0-9]+)?;fire=([0-9]+.[0-9]+)?$/;
136 my $i;
137 for ($i = 0; $i < @fields; $i++) {
138 if (defined $fields[$i] && $fields[$i] ne '') {
139 $collect[$i] = $fields[$i];
140 }
141 elsif (not (exists $collect[$i])) {
142 $collect[$i] = undef;
143 }
144 }
145 #print STDERR "collect: ".Dumper(\@collect)."\n";
146 if ($time >= $last_time + $step && @collect == 5) {
147 $rrd->update
148 ( time => $time
149 , values => [join (':', map { defined $_ ? $_ : 'U' } @collect)]
150 );
151 @collect = ();
152 $last_time = $time;
153 }
154 #else {
155 # print STDOUT ($time, ";", $_, "\n");
156 # }
157 }
158 }
159 $timeout = $read_timeout;
160 }
161 else {
162 $timeout--;
163 }
164 }
165 if ($timeout==0) {
166 die "Connection timeout (after ${read_timeout}s)\n";
167 }
168 undef $dev;
169 }
170 main;
171 1;