From cc3382302d5289678d8ac3a2f2bc0377f0a7ead8 Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Fri, 30 Oct 2015 14:01:05 +0100 Subject: [PATCH 1/1] init --- .gitignore | 4 ++ GNUmakefile | 89 ++++++++++++++++++++++++++++ README | 11 ++++ collect.pl | 153 ++++++++++++++++++++++++++++++++++++++++++++++++ send.ino | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 421 insertions(+) create mode 100644 .gitignore create mode 100644 GNUmakefile create mode 100644 README create mode 100755 collect.pl create mode 100644 send.ino diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..672088a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build-*/ +old/ +png/ +rrd/ diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..b078823 --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,89 @@ +# Licence: WTFPLv2 +# Copyright 2015: Julien Moutinho + +RRD_CF:=AVERAGE +SENSORS:= \ + humidity \ + temperature \ + quality \ + particles + +.rrd = $(wildcard rrd/*/*.rrd) + +all: png + +# +## INO +### + +# For GNU/Linux +ARDUINO_DIR := /usr/share/arduino/ +ARDMK_DIR := /usr/share/arduino/ +AVR_TOOLS_DIR := /usr + +# For Seeeduino +BOARD_TAG := uno + +ARDUINO_LIBS := \ + AirQuality_Sensor \ + DustSensor \ + Humidity_Temperature_Sensor \ + LCD_Display9696 \ + Wire + +USER_LIB_PATH := $(abspath ./libraries) + +include $(ARDMK_DIR)/Arduino.mk + +ino: $(TARGET_EEP) $(TARGET_HEX) + +# +## PNG +### +.PHONY: png $(addprefix png/,$(SENSORS)) +png: $(addprefix png/,$(SENSORS)) + + +ym=$(patsubst %.rrd,,$(notdir $*)) +month_begin=$(shell date +'%s' -d '$(ym)/01') +month_end=$(shell date +'%s' -d '$(ym)/01 + 1 month - 1 second') +month_length=$(shell date +'%d' -d '$(ym)/01 + 1 month - 1 second') +day_begin=$(shell date +'%s' -d '$(ym)/$(day)') +day_end=$(shell date +'%s' -d '$(ym)/$(day) + 1 month - 1 second') + +define png/sensor +sensor:=$1 +label:=$2 +png/$(sensor): \ + $(patsubst rrd/%.rrd,png/$(sensor)/%.png,$(.rrd)) + +png/$(sensor)/%.png: rrd/%.rrd + mkdir -p png/$(sensor)/$$* + rrdtool graph $$@ \ + -w 785 -h 120 -a PNG \ + --slope-mode \ + --start $$(month_begin) --end $$(month_end) \ + --vertical-label '$(label)' \ + --x-grid HOUR:8:DAY:1:DAY:1:86400:%d \ + DEF:$(sensor)=rrd/$$*.rrd:$(sensor):$$(RRD_CF) \ + LINE1:$(sensor)'#ff0000':"$(sensor)" + for day in $$(shell seq -w $$(month_length)); \ + do \ + day_begin=$$$$(date +'%s' -d "$$(ym)/$$$$day"); \ + day_end=$$$$(date +'%s' -d "$$(ym)/$$$$day + 1 day - 1 second"); \ + rrdtool graph png/$(sensor)/$$*/$$$$day.png \ + -w 785 -h 120 -a PNG \ + --slope-mode \ + --start $$$$day_begin --end $$$$day_end \ + --vertical-label '$(label)' \ + --x-grid MINUTE:10:HOUR:1:HOUR:1:0:%H \ + DEF:$(sensor)=rrd/$$*.rrd:$(sensor):$$(RRD_CF) \ + LINE1:$(sensor)'#ff0000':"$(sensor)"; \ + done +endef + +)=) +$(eval $(call png/sensor,temperature,Température (°C$))) +$(eval $(call png/sensor,humidity,Humidité (%$))) +$(eval $(call png/sensor,particles,Particules)) +$(eval $(call png/sensor,quality,Qualité de l’air)) diff --git a/README b/README new file mode 100644 index 0000000..46542ca --- /dev/null +++ b/README @@ -0,0 +1,11 @@ +# Building in build-uno/ +make ino + +# Uploading on /dev/ttyUSB0 (to be adapted) +make DEVICE_PATH=/dev/ttyUSB0 upload + +# Collecting in rrd/ from /dev/ttyUSB0 (to be adapted) +./collect.pl /dev/ttyUSB0 & + +# Rendering in png/ +make png diff --git a/collect.pl b/collect.pl new file mode 100755 index 0000000..36342a6 --- /dev/null +++ b/collect.pl @@ -0,0 +1,153 @@ +#!/usr/bin/perl -w +# Licence: WTFPLv2 +# Copyright 2015: Julien Moutinho + +use strict; +use warnings; +use DateTime::Duration; +use DateTime; +use Device::SerialPort qw( 0.07 ); +use File::Basename; +use File::Path; +use File::Spec; +use Log::Log4perl qw(:easy); +use RRDTool::OO; +#use Data::Dumper qw(Dumper); +#use IO::Handle; + +sub rrd_init () { + Log::Log4perl->easy_init( + { level => $INFO + , category => 'rrdtool' + , layout => '%m%n' + }); + my $now + = DateTime->now + ( time_zone => 'local' + #, locale => $config{locale} + ); # ->set_time_zone('floating'); + my $dir = File::Spec->catdir(dirname($0), 'rrd', (sprintf '%0d', $now->year())); + my $file = File::Spec->catfile($dir, (sprintf '%02d.rrd', $now->month())); + File::Path::make_path($dir); + my $rrd = RRDTool::OO->new(file => $file); + if (not (-e $file)) { + my $one_month = DateTime::Duration->new(months => 1, end_of_month => 'limit'); + my $month_begin = $now->clone->truncate(to => 'month'); + my $month_end = $month_begin->clone->add_duration($one_month); + my $month_seconds = $month_end->epoch() - $month_begin->epoch(); + my $step = 30; # NOTE: sampling interval in seconds + $rrd->create + ( step => $step + , data_source => + { name => "humidity" + , type => "GAUGE" + , min => 0 + , max => 100 + , heartbeat => 2 * $step # seconds + } + , data_source => + { name => "temperature" + , type => "GAUGE" + , min => -15 + , max => 50 + , heartbeat => 2 * $step # seconds + } + , data_source => + { name => "quality" + , type => "GAUGE" + , min => 0 + , max => 1000 + , heartbeat => 2 * $step # seconds + } + , data_source => + { name => "particles" + , type => "GAUGE" + , min => 0 + , max => 10000 + , heartbeat => 2 * $step # seconds + } + , archive => + { cpoints => 1 + , cfunc => 'AVERAGE' + , rows => $month_seconds / $step + } + ); + } + return $rrd; + } +sub dev_init ($) { + my ($file) = @_; + my $dev = Device::SerialPort->new($file); + $dev->baudrate(9600); # MUST: match *uino Serial.begin() + #$dev->buffers(4096, 4096); # NOTE: no-op on POSIX + $dev->databits(8); + $dev->dtr_active(1); # NOTE: reset the *uino on serial connection + $dev->handshake('none'); + $dev->parity("none"); + $dev->read_char_time(0); # NOTE: don't wait for each character + $dev->read_const_time(1000); # NOTE: 1 second per unfulfilled "read" call + $dev->stopbits(1); + $dev->write_settings; + $dev->lookclear; # NOTE: discard buffered data within the TTY device + return $dev; + } + +sub main () { + my $rrd = rrd_init(); + my $dev = dev_init($ARGV[0]); + + #autoflush STDOUT 1; + my $read_timeout = 60; + my $timeout = $read_timeout; + my $buffer = ""; + my $last_time = 0; + my $last_day = (localtime)[3]; + while ($timeout>0) { + my $curr_day = (localtime)[3]; + if ($curr_day < $last_day) { + # NOTE: month changed, change RRD + $rrd = rrd_init(); + $last_day = $curr_day; + } + my ($count, $saw) = $dev->read(1); + # NOTE: this could have read up to 255 chars (max for portability) + # but reading only 1 char at a time + # enables to add a more accurate timestamp. + if ($count > 0) { + $buffer .= $saw; + my @lines = split /\r\n/, $buffer; + if (@lines > 1) { + my $time = time; + # NOTE: process only ended lines + $buffer = pop @lines; + foreach (@lines) { + if ($_ =~ /^\d+;/) { + #print STDOUT ($time, ";", $_, "\n"); + my ($counter, $uptime, $humidity, $temperature, $quality, $particules) + = split /;/, $_; + if ($time > $last_time) { + $rrd->update + ( time => $time + , values => [$humidity,$temperature,$quality,$particules] + ); + $last_time = $time; + } + } + else { + print STDERR ($_, "\n"); + } + } + } + $timeout = $read_timeout; + } + else { + $timeout--; + } + } + if ($timeout==0) { + die "Connection timeout (after ${read_timeout}s)\n"; + } + undef $dev; +} +main; +1; diff --git a/send.ino b/send.ino new file mode 100644 index 0000000..8f8fd29 --- /dev/null +++ b/send.ino @@ -0,0 +1,164 @@ +// Licence: WTFPLv2 +// Copyright 2015: Julien Moutinho + +#include +#include +#include "AirQuality.h" +#include "Arduino.h" +#include "DHT.h" +#define DHTPIN 2 + // What pin the DHT is connected to. + +// Uncomment whatever type you're using! +//#define DHTTYPE DHT11 // DHT 11 +#define DHTTYPE DHT22 // DHT 22 (AM2302) +DHT dht(DHTPIN, DHTTYPE); + +AirQuality airqualitysensor; +int current_quality = -1; +#define BUZZER 10 + +int dust_pin = 4; +unsigned long duration; +unsigned long starttime; +unsigned long sampletime_ms = 30000; +unsigned long lowpulseoccupancy = 0; +unsigned long counter; +float ratio = 0; +float concentration = 0; + +void setup() { + Serial.begin(9600); + //while (!Serial) { + // ; // wait for serial port to connect. Needed for native USB port only + // } + + Wire.begin(); // Initialize I2C + SeeedGrayOled.init(); // Initialize SEEED OLED display + SeeedGrayOled.clearDisplay(); // Clear the screen and set start position to top left corner + SeeedGrayOled.setNormalDisplay(); // Set display to normal mode (i.e non-inverse mode) + SeeedGrayOled.setVerticalMode(); + //SeeedGrayOled.setPageMode(); // Set addressing mode to Page Mode + SeeedGrayOled.setTextXY(0,0); // Set the cursor to Xth Page, Yth Column + SeeedGrayOled.putString("Initializing"); // Print the String + + pinMode(BUZZER, OUTPUT); + digitalWrite(BUZZER, LOW); + airqualitysensor.init(14); + dht.begin(); + + pinMode(dust_pin,INPUT); + starttime = millis(); // Get the current time + Serial.println("counter;uptime;humidity;temperature;quality;particules"); + } + +void loop() { + // Reading temperature or humidity takes about 250 milliseconds! + // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor) + float h = dht.readHumidity(); + float t = dht.readTemperature(); + counter = counter + 1; + SeeedGrayOled.setTextXY(8,0); + // Check if returns are valid, + // if they are NaN (not a number) + // then something went wrong! + Serial.print(counter); + Serial.write(';'); + Serial.print(millis()); + if (isnan(t) || isnan(h)) { + SeeedGrayOled.putString("DHT ERROR"); + Serial.write(';'); + Serial.write(';'); + } + else { + SeeedGrayOled.putString("Humidity:"); + SeeedGrayOled.setTextXY(9,0); + SeeedGrayOled.putString(" "); + SeeedGrayOled.putFloat(h); + SeeedGrayOled.putString("%"); + SeeedGrayOled.setTextXY(10,0); + SeeedGrayOled.putString("Temp.:"); + SeeedGrayOled.setTextXY(11,0); + SeeedGrayOled.putString(" "); + SeeedGrayOled.putFloat(t); + SeeedGrayOled.putString("*C"); + Serial.write(';'); + Serial.print(h); + Serial.write(';'); + Serial.print(t); + } + Serial.write(';'); + Serial.print(airqualitysensor.first_vol,DEC); + + // Checking Air Quality + current_quality=airqualitysensor.slope(); + if (current_quality >= 0) { + if (current_quality <= 1) + // Air quality is bad. + // Let's revert display to make some light! + SeeedGrayOled.setInverseDisplay(); + else + SeeedGrayOled.setNormalDisplay(); + + SeeedGrayOled.setTextXY(2,0); + SeeedGrayOled.putString("Air Quality:"); // Print the String + SeeedGrayOled.setTextXY(3,3); + SeeedGrayOled.putNumber(airqualitysensor.first_vol); // Print the String + SeeedGrayOled.putString(" "); // Clear any old character + + SeeedGrayOled.setTextXY(0,0); + if (current_quality==0) + SeeedGrayOled.putString("Emergency "); + else if (current_quality==1) + SeeedGrayOled.putString("Hi Pollution"); + else if (current_quality==2) + SeeedGrayOled.putString("Low Polution"); + else if (current_quality==3) + SeeedGrayOled.putString("Air OK :-) "); + + if (current_quality<1) + digitalWrite(BUZZER, HIGH); + else + digitalWrite(BUZZER, LOW); + } + + // Checking Dust Sensor + duration = pulseIn(dust_pin, LOW); + lowpulseoccupancy = lowpulseoccupancy+duration; + + Serial.write(';'); + if ((millis()-starttime) > sampletime_ms) { + //if the sample time == 30s + ratio = lowpulseoccupancy / (sampletime_ms*10.0); // Integer percentage 0=>100 + concentration = 1.1*pow(ratio,3)-3.8*pow(ratio,2)+520*ratio+0.62; // Using spec sheet curve + /* + Serial.print(lowpulseoccupancy); + Serial.write(';'); + Serial.print(ratio); + */ + Serial.print(concentration); + SeeedGrayOled.setTextXY(4,0); + SeeedGrayOled.putString("Particles:"); + SeeedGrayOled.setTextXY(5,3); + SeeedGrayOled.putString(" "); + SeeedGrayOled.setTextXY(5,3); + SeeedGrayOled.putNumber(concentration); + lowpulseoccupancy = 0; + starttime = millis(); + } + Serial.println(); + } + +ISR(TIMER2_OVF_vect) { + if(airqualitysensor.counter==122) { + // set 2 seconds as a detected duty + airqualitysensor.last_vol=airqualitysensor.first_vol; + airqualitysensor.first_vol=analogRead(A0); + airqualitysensor.counter=0; + airqualitysensor.timer_index=1; + //PORTB=PORTB^0x20; + } + else { + airqualitysensor.counter++; + } + } -- 2.42.0