Initial commit
This commit is contained in:
commit
e9c20be21c
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
raspberry/data/*
|
||||||
|
raspberry/pictures/*
|
||||||
|
*.fcstd1
|
||||||
|
*.fcstd2
|
||||||
|
boitier/old*
|
||||||
|
.directory
|
4
arduino/SDcard/config.txt
Executable file
4
arduino/SDcard/config.txt
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
#Fichier de configuration du contrôleur du projet
|
||||||
|
# Camétéo
|
||||||
|
time_step=2000
|
||||||
|
data_file=datalog3.csv
|
42
arduino/cameteo-teensy-bug/cameteo-teensy/RaspBerryPi_COM.cpp
Executable file
42
arduino/cameteo-teensy-bug/cameteo-teensy/RaspBerryPi_COM.cpp
Executable file
@ -0,0 +1,42 @@
|
|||||||
|
|
||||||
|
#include "cameteo_teensy.h"
|
||||||
|
|
||||||
|
void stopRPI() {
|
||||||
|
//Stop the Raspberry Pi via the MOSFET (grid connected to the RPI_PWR_PIN)
|
||||||
|
|
||||||
|
pinMode(RPI_PWR_PIN, INPUT); //Eteint via le MOSFET
|
||||||
|
rpi_status = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isStartedPI() {
|
||||||
|
//Return true if the R-Pi respond to an simple request
|
||||||
|
return rpi_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void startRPI() {
|
||||||
|
//Start the Raspberry Pi via the MOSFET (grid connected to the RPI_PWR_PIN)
|
||||||
|
if (!isStartedPI()) {
|
||||||
|
pinMode(RPI_PWR_PIN, OUTPUT);
|
||||||
|
digitalWrite(RPI_PWR_PIN, LOW);
|
||||||
|
rpi_status = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendDataToSerial(String data) {
|
||||||
|
if (isStartedPI()) {
|
||||||
|
char c[100]; // char buffer for conversion String->char
|
||||||
|
data.toCharArray(c, sizeof(data)); //convert data string to char array
|
||||||
|
|
||||||
|
// //start serial comm. if needed
|
||||||
|
// if(!SERIAL_PORT) {
|
||||||
|
// SERIAL_PORT.begin(SERIAL_BAUD_RATE);
|
||||||
|
// //while(!SERIAL_PORT);
|
||||||
|
// }
|
||||||
|
|
||||||
|
SERIAL_PORT.print(c); // send data on serial port
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//error RPI is not started
|
||||||
|
}
|
||||||
|
}
|
55
arduino/cameteo-teensy-bug/cameteo-teensy/SerialMessages.cpp
Executable file
55
arduino/cameteo-teensy-bug/cameteo-teensy/SerialMessages.cpp
Executable file
@ -0,0 +1,55 @@
|
|||||||
|
|
||||||
|
#include "cameteo_teensy.h"
|
||||||
|
|
||||||
|
void BootMessage(String s) {
|
||||||
|
char c[13]; // char buffer for conversion String->char
|
||||||
|
s.toCharArray(c, sizeof(s));
|
||||||
|
SERIAL_PORT.printf("%-12s", c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BootOK() {
|
||||||
|
SERIAL_PORT.println("OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
void BootError() {
|
||||||
|
SERIAL_PORT.println("EE");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendDataOnSerial() {
|
||||||
|
|
||||||
|
//Print date & hour on serial port
|
||||||
|
SERIAL_PORT.printf("%04d/%02d/%02d_%02d:%02d:%02d\n", year(), month(), day(), hour(), minute(), second());
|
||||||
|
|
||||||
|
// if (isnan(dht22_event_hum.relative_humidity) ||
|
||||||
|
// isnan(dht22_event_temp.temperature)) {
|
||||||
|
// SERIAL_PORT.printf("Failed to read from DHT sensor!\n");
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// SERIAL_PORT.printf("Temperature:%8.2f %cC | Humidity:%8.0f %%\n",
|
||||||
|
// dht22_event_temp.temperature, DEGREE, dht22_event_hum.relative_humidity);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (bme280_event.pressure) {
|
||||||
|
SERIAL_PORT.printf("Temperature:%8.2f %cC | Humidity: %8.2f % | Pressure: %8.2f hPa | Altitude:%8.2f m\n",
|
||||||
|
bme280_temp, DEGREE, bme280_press, bme280_alti);
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// SERIAL_PORT.printf("BME280 Sensor error\n");
|
||||||
|
// }
|
||||||
|
|
||||||
|
SERIAL_PORT.printf("Lightning strikes (from start) : %d | Perturb.: %d | Noise : %d | Unknown detect.: %d\n", lightning_nb_total,
|
||||||
|
as3935_perturb_total,
|
||||||
|
as3935_noise_total,
|
||||||
|
as3935_unknown_total);
|
||||||
|
|
||||||
|
//SERIAL_PORT.printf("Temperature:%8.2f %cC\n", tcn75a_temp, DEGREE);
|
||||||
|
|
||||||
|
SERIAL_PORT.printf("GPS data: %04d/%02d/%02d_%02d:%02d:%02d (%d)\n", gps_year, gps_month, gps_day,
|
||||||
|
gps_hour, gps_minutes, gps_second,
|
||||||
|
gps_fix_age);
|
||||||
|
SERIAL_PORT.printf(" Latitude: %11.8f | Longitude: %11.8f | Altitude: %5.2f m\n", gps_latitude, gps_longitude, gps_altitude);
|
||||||
|
SERIAL_PORT.printf(" Speed: %4.1f km/h | Course : %4.1f %c\n", gps_speed, gps_course, DEGREE);
|
||||||
|
SERIAL_PORT.printf(" Chars: %11d | Sentences: %11d | Failed cheksum: %4d\n", gps_chars, gps_sentences, gps_failed_checksum);
|
||||||
|
|
||||||
|
SERIAL_PORT.printf("Battery : %10d mV | Low Battery : %d\n", batt_voltage, low_battery_flag);
|
||||||
|
}
|
36
arduino/cameteo-teensy-bug/cameteo-teensy/cameteo-teensy.ino
Executable file
36
arduino/cameteo-teensy-bug/cameteo-teensy/cameteo-teensy.ino
Executable file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* CAMETEO project
|
||||||
|
*
|
||||||
|
* This is a personnal project of weather station with
|
||||||
|
* automatic photo taking.
|
||||||
|
* This code is the weather station part and is meant
|
||||||
|
* to be run on a PJRC Teensy 3.2 board.
|
||||||
|
*
|
||||||
|
* Author : Arofarn
|
||||||
|
*
|
||||||
|
* Licence : GPL v3
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Protocols
|
||||||
|
#include <Wire.h> // library used with I2C protocol
|
||||||
|
#include <SPI.h> // SPI protocol
|
||||||
|
|
||||||
|
//Teensy3.x Real Time Clock
|
||||||
|
#include <TimeLib.h>
|
||||||
|
|
||||||
|
//SD card
|
||||||
|
#include <SD.h>
|
||||||
|
|
||||||
|
// Sensors
|
||||||
|
#include <Adafruit_Sensor.h> // Generic
|
||||||
|
//#include <DHT.h> // DHT22
|
||||||
|
//#include <DHT_U.h> // DHT22 unified
|
||||||
|
//#include <Adafruit_BMP085_U.h> // BMP180
|
||||||
|
#include <Adafruit_BME280.h> // BME280
|
||||||
|
#include <PWFusion_AS3935.h>
|
||||||
|
|
||||||
|
//GPS
|
||||||
|
//#include <Adafruit_GPS.h> // Adafruit Ultimate GPS
|
||||||
|
#include <TinyGPS.h> //Builtin GPS lib
|
||||||
|
|
399
arduino/cameteo-teensy-bug/cameteo-teensy/cameteo.cpp
Executable file
399
arduino/cameteo-teensy-bug/cameteo-teensy/cameteo.cpp
Executable file
@ -0,0 +1,399 @@
|
|||||||
|
/*
|
||||||
|
* CAMETEO project
|
||||||
|
*
|
||||||
|
* This is a personnal project of weather station with
|
||||||
|
* automatic photo taking.
|
||||||
|
* This code is the weather station part and is meant
|
||||||
|
* to be run on a PJRC Teensy 3.2 board.
|
||||||
|
*
|
||||||
|
* Author : Arofarn
|
||||||
|
*
|
||||||
|
* Licence : GPL v3
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#include "cameteo_teensy.h"
|
||||||
|
#include "SerialMessages.cpp"
|
||||||
|
#include "RaspBerryPi_COM.cpp"
|
||||||
|
|
||||||
|
//sensors_event_t bme280_event;
|
||||||
|
float seaLevelPressure = 1015.0;
|
||||||
|
float bme280_press;
|
||||||
|
float bme280_temp;
|
||||||
|
float bme280_alti;
|
||||||
|
float bme280_hum;
|
||||||
|
|
||||||
|
enum strike_sources { UNKNOWN_SRC, LIGHTNING, PERTURBATION, NOISE };
|
||||||
|
volatile int8_t AS3935_ISR_Trig = 0; // Trigger for AS3935 lightning sensor
|
||||||
|
|
||||||
|
void AS3935_ISR() {
|
||||||
|
AS3935_ISR_Trig = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
PWF_AS3935 lightning(AS3935_CS_PIN, AS3935_IRQ_PIN, 33);
|
||||||
|
|
||||||
|
int as3935_src;
|
||||||
|
int as3935_distance;
|
||||||
|
long lightning_nb_total = 0;
|
||||||
|
int lightning_nb_hour = 0;
|
||||||
|
int lightning_nb_day = 0;
|
||||||
|
int as3935_perturb_total = 0;
|
||||||
|
int as3935_noise_total = 0;
|
||||||
|
int as3935_unknown_total = 0;
|
||||||
|
char lightning_log_file[12] = "lghtnng.log";
|
||||||
|
|
||||||
|
//GPS
|
||||||
|
//Adafruit_GPS GPS(&GPS_SERIAL_PORT);
|
||||||
|
TinyGPS GPS;
|
||||||
|
float gps_latitude, gps_longitude, gps_altitude; // returns +- latitude/longitude in degrees
|
||||||
|
float gps_speed, gps_course;
|
||||||
|
unsigned long gps_time, gps_date, gps_fix_age;
|
||||||
|
int gps_year;
|
||||||
|
byte gps_month, gps_day, gps_hour, gps_minutes, gps_second, gps_hundreths;
|
||||||
|
unsigned long gps_chars, gps_hdop;
|
||||||
|
unsigned short gps_sentences, gps_failed_checksum, gps_satellites;
|
||||||
|
|
||||||
|
//Miscellaneous
|
||||||
|
bool rpi_status;
|
||||||
|
rpi_status = false;
|
||||||
|
int batt_voltage;
|
||||||
|
bool low_battery_flag = false;
|
||||||
|
|
||||||
|
// Tasks timers
|
||||||
|
elapsedMillis since_bme280;
|
||||||
|
//elapsedMillis since_dht;
|
||||||
|
elapsedMillis since_gps;
|
||||||
|
elapsedMillis since_display;
|
||||||
|
elapsedMillis since_serial_send;
|
||||||
|
elapsedMillis since_sd_log;
|
||||||
|
elapsedMillis since_batt_chk;
|
||||||
|
|
||||||
|
//SD Card
|
||||||
|
#define CONFIGFILE "config.txt"
|
||||||
|
|
||||||
|
//Configuration
|
||||||
|
char data_file[13] = "datalog.csv";
|
||||||
|
|
||||||
|
//Delays
|
||||||
|
|
||||||
|
unsigned int bme280_delay = 500;
|
||||||
|
//unsigned int dht_delay = 3000;
|
||||||
|
unsigned int gps_delay = 100;
|
||||||
|
unsigned int serial_send_delay = 5000;
|
||||||
|
unsigned int sd_log_delay = 5000;
|
||||||
|
unsigned int batt_chk_delay = 5000;
|
||||||
|
|
||||||
|
//Date and time
|
||||||
|
int TZ = 1; //Time zone
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SETUP
|
||||||
|
*/
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
|
||||||
|
//To be sure Raspberry-Pi won't be turned on unexpectedly
|
||||||
|
stopRPI();
|
||||||
|
|
||||||
|
pinMode(LOW_BATT_PIN, INPUT_PULLUP);
|
||||||
|
low_battery_flag = !digitalRead(LOW_BATT_PIN);
|
||||||
|
|
||||||
|
pinMode(GPS_BUT_PIN, INPUT_PULLUP);
|
||||||
|
pinMode(GPS_EN_PIN, OUTPUT);
|
||||||
|
gps_power();
|
||||||
|
|
||||||
|
SERIAL_PORT.begin(SERIAL_BAUD_RATE);
|
||||||
|
delay(1000);
|
||||||
|
//while (!SERIAL_PORT) { }; //Wait for serial port to start
|
||||||
|
SERIAL_PORT.printf("Serial Comm. OK\nNow booting...\n");
|
||||||
|
|
||||||
|
BootMessage("SDcard");
|
||||||
|
// see if the card is present and can be initialized:
|
||||||
|
if (!SD.begin(SD_CS_PIN)) {
|
||||||
|
BootError();
|
||||||
|
while(1);
|
||||||
|
}
|
||||||
|
BootOK();
|
||||||
|
|
||||||
|
BootMessage("RT Clock");
|
||||||
|
// set the Time library to use Teensy 3.0's RTC to keep time
|
||||||
|
setSyncProvider(getTeensy3Time);
|
||||||
|
if (timeStatus()!= timeSet) {
|
||||||
|
BootError();
|
||||||
|
while(1);
|
||||||
|
}
|
||||||
|
BootOK();
|
||||||
|
|
||||||
|
// BootMessage("AM2302/DHT22 (Humidity and Temperature)");
|
||||||
|
// dht.begin();
|
||||||
|
// //sensor_t sensor;
|
||||||
|
// BootOK();
|
||||||
|
|
||||||
|
BootMessage("BME280 (Pressure and Temperature)");
|
||||||
|
/* Initialise the sensor */
|
||||||
|
if(!bme.begin())
|
||||||
|
{
|
||||||
|
/* There was a problem detecting the BMP085 ... check your connections */
|
||||||
|
BootError();
|
||||||
|
while(1);
|
||||||
|
}
|
||||||
|
BootOK();
|
||||||
|
|
||||||
|
BootMessage("AS3935 (Lightning)");
|
||||||
|
SPI.begin();
|
||||||
|
SPI.setClockDivider(SPI_CLOCK_DIV16); // SPI speed to SPI_CLOCK_DIV16/1MHz (max 2MHz, NEVER 500kHz!)
|
||||||
|
SPI.setDataMode(SPI_MODE1); // MAX31855 is a Mode 1 device --> clock starts low, read on rising edge
|
||||||
|
SPI.setBitOrder(MSBFIRST); // data sent to chip MSb first
|
||||||
|
lightning.AS3935_DefInit(); // set registers to default
|
||||||
|
// now update sensor cal for your application and power up chip
|
||||||
|
lightning.AS3935_ManualCal(AS3935_CAPACITANCE, AS3935_INDOORS, AS3935_DIST_EN);
|
||||||
|
// enable interrupt (hook IRQ pin to Arduino Uno/Mega interrupt input: 0 -> pin 2, 1 -> pin 3 )
|
||||||
|
attachInterrupt(AS3935_IRQ_PIN, AS3935_ISR, RISING);
|
||||||
|
BootOK();
|
||||||
|
|
||||||
|
BootMessage("GPS");
|
||||||
|
GPS_SERIAL_PORT.begin(GPS_SERIAL_BAUD_RATE);
|
||||||
|
while (!GPS_SERIAL_PORT) {} ;
|
||||||
|
BootOK();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* LOOP
|
||||||
|
*/
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
|
||||||
|
//Power ON/OFF GPS
|
||||||
|
gps_power();
|
||||||
|
|
||||||
|
//Lightning detection
|
||||||
|
if (AS3935_ISR_Trig != 0) {
|
||||||
|
AS3935_ISR_Trig = 0;
|
||||||
|
time_t t = now();
|
||||||
|
int distance = -9999;
|
||||||
|
int energy = -9999;
|
||||||
|
switch (as3935_src) {
|
||||||
|
case UNKNOWN_SRC:
|
||||||
|
//source inconnue
|
||||||
|
as3935_unknown_total++;
|
||||||
|
SERIAL_PORT.printf("Interruption (AS3935) : unkown source (not lightning)\n");
|
||||||
|
break;
|
||||||
|
case LIGHTNING:
|
||||||
|
//Foudre !!!
|
||||||
|
lightning_nb_total++;
|
||||||
|
distance = lightning.AS3935_GetLightningDistKm();
|
||||||
|
energy = lightning.AS3935_GetStrikeEnergyRaw();
|
||||||
|
SERIAL_PORT.printf("Interruption (AS3935) : Lightningbolt !!!\n");
|
||||||
|
SERIAL_PORT.printf("Distance : %4d km | Energy : %d \n", distance, energy);
|
||||||
|
break;
|
||||||
|
case PERTURBATION:
|
||||||
|
//Perturbation
|
||||||
|
as3935_perturb_total++;
|
||||||
|
SERIAL_PORT.printf("Interruption (AS3935) : perturbation...\n");
|
||||||
|
break;
|
||||||
|
case NOISE:
|
||||||
|
//Trop de bruit électromagnétique
|
||||||
|
as3935_noise_total++;
|
||||||
|
SERIAL_PORT.printf("Interruption (AS3935) : Too much electromagnetic noise!\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
writeLightningToSD(as3935_src, t, distance, energy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (since_batt_chk >= batt_chk_delay) {
|
||||||
|
since_batt_chk -= batt_chk_delay;
|
||||||
|
//Check battery status and voltage
|
||||||
|
low_battery_flag = !digitalRead(LOW_BATT_PIN);
|
||||||
|
batt_voltage = getBatteryVoltage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (since_dht >= dht_delay) {
|
||||||
|
// since_dht = since_dht - dht_delay;
|
||||||
|
// // Read temperature or humidity
|
||||||
|
// dht.humidity().getEvent(&dht22_event_hum);
|
||||||
|
// dht.temperature().getEvent(&dht22_event_temp);
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (since_bme280 >= bme280_delay) {
|
||||||
|
since_bme280 = since_bme280 - bme280_delay;
|
||||||
|
/* Get a new sensor event */
|
||||||
|
bmp.getEvent(&bme280_event);
|
||||||
|
|
||||||
|
/* Get the values (barometric pressure is measure in hPa) */
|
||||||
|
if (bme280_event.pressure)
|
||||||
|
{
|
||||||
|
bme280_press = bme280_event.pressure;
|
||||||
|
bmp.getTemperature(&bme280_temp);
|
||||||
|
bme280_alti = bmp.pressureToAltitude(seaLevelPressure, bme280_press);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (since_gps >= gps_delay) {
|
||||||
|
since_gps = since_gps - gps_delay;
|
||||||
|
while (GPS_SERIAL_PORT.available()) {
|
||||||
|
char c = GPS_SERIAL_PORT.read();
|
||||||
|
if (GPS.encode(c)) {
|
||||||
|
GPS.get_datetime(&gps_date, &gps_time, &gps_fix_age);
|
||||||
|
GPS.crack_datetime(&gps_year, &gps_month, &gps_day,
|
||||||
|
&gps_hour, &gps_minutes, &gps_second,
|
||||||
|
&gps_hundreths, &gps_fix_age);
|
||||||
|
GPS.f_get_position(&gps_latitude, &gps_longitude, &gps_fix_age);
|
||||||
|
gps_altitude = GPS.f_altitude();
|
||||||
|
gps_speed = GPS.f_speed_kmph();
|
||||||
|
GPS.stats(&gps_chars, &gps_sentences, &gps_failed_checksum);
|
||||||
|
gps_satellites = GPS.satellites();
|
||||||
|
gps_hdop = GPS.hdop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (since_sd_log >= sd_log_delay) {
|
||||||
|
since_sd_log = since_sd_log - sd_log_delay;
|
||||||
|
writeDataToSD();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (since_serial_send >= serial_send_delay) {
|
||||||
|
since_serial_send = since_serial_send - serial_send_delay;
|
||||||
|
sendDataOnSerial();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FUNCTIONS
|
||||||
|
*/
|
||||||
|
|
||||||
|
time_t getTeensy3Time() {
|
||||||
|
return Teensy3Clock.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void gps_power() {
|
||||||
|
if (digitalRead(GPS_BUT_PIN) == 0) { digitalWrite(GPS_EN_PIN, LOW); }
|
||||||
|
else { digitalWrite(GPS_EN_PIN, HIGH); }
|
||||||
|
}
|
||||||
|
|
||||||
|
int getBatteryVoltage() {
|
||||||
|
long mean = 0;
|
||||||
|
int U = 0;
|
||||||
|
int n = 10;
|
||||||
|
int res = 12;
|
||||||
|
|
||||||
|
analogReadResolution(res);
|
||||||
|
|
||||||
|
for (int i=0; i<n; i++) {
|
||||||
|
mean += analogRead(BATT_VOLT_PIN);
|
||||||
|
//delay(2);
|
||||||
|
}
|
||||||
|
mean /= n;
|
||||||
|
|
||||||
|
U = map(mean, 0, pow(2, res)-1, 0, 3300); // Convert data from ADC into input voltage in mV
|
||||||
|
U *= 2; //Multiple by 2 for the voltage divider
|
||||||
|
|
||||||
|
return U;
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeDataToSD() {
|
||||||
|
|
||||||
|
char dir[20];
|
||||||
|
char path[60];
|
||||||
|
|
||||||
|
String directory = dayDirectory();
|
||||||
|
|
||||||
|
directory.toCharArray(dir, sizeof(directory));
|
||||||
|
sprintf(path, "%s/%s", dir, data_file);
|
||||||
|
|
||||||
|
// Test to know if the file exists before opening it, if it doesn't exist
|
||||||
|
// we will write first an header
|
||||||
|
bool no_header = SD.exists(path);
|
||||||
|
|
||||||
|
// open the file. note that only one file can be open at a time,
|
||||||
|
// so you have to close this one before opening another.
|
||||||
|
File dataFile = SD.open(path, FILE_WRITE);
|
||||||
|
|
||||||
|
// if the file is available, write to it:
|
||||||
|
if (dataFile) {
|
||||||
|
if (!no_header) {
|
||||||
|
SERIAL_PORT.printf("Creating file with CSV header : %s\n", path);
|
||||||
|
dataFile.printf("Date");
|
||||||
|
dataFile.printf(";bme280_Pressure(hPa);BMP180_Temperature(degC);BMP180_Altitude(m)");
|
||||||
|
// dataFile.printf(";DHT22_Humidity(%);DHT22_Temperature(degC)");
|
||||||
|
dataFile.printf(";TotalLightningCount;TotalPerturbationEvents;TotalNoiseDetection;TotalUnknownDetection");
|
||||||
|
dataFile.printf(";GPS_Latitude;GPS_Longitude;GPS_Altitude;GPS_Satellites;GPS_HDOP;GPS_Date");
|
||||||
|
dataFile.printf(";Battery_voltage(mV);Low_Battery_Status");
|
||||||
|
dataFile.printf("\n");
|
||||||
|
}
|
||||||
|
dataFile.printf("%04d/%02d/%02d_%02d:%02d:%02d", year(), month(), day(), hour(), minute(), second());
|
||||||
|
dataFile.printf(";%.2f;%.2f;%.1f", bme280_press, bme280_temp, bme280_alti);
|
||||||
|
// dataFile.printf(";%.0f;%.2f", dht22_event_hum.relative_humidity, dht22_event_temp.temperature);
|
||||||
|
dataFile.printf(";%d;%d;%d;%d", lightning_nb_total, as3935_perturb_total, as3935_noise_total, as3935_unknown_total);
|
||||||
|
dataFile.printf(";%.8f;%.8f;%.2f;%d;%d;%04d/%02d/%02d_%02d:%02d:%02d", gps_latitude, gps_longitude, gps_altitude,
|
||||||
|
gps_satellites, gps_hdop,
|
||||||
|
gps_year, gps_month, gps_day,
|
||||||
|
gps_hour, gps_minutes, gps_second);
|
||||||
|
dataFile.printf(";%d;%d", batt_voltage, low_battery_flag);
|
||||||
|
dataFile.printf("\n");
|
||||||
|
|
||||||
|
dataFile.close();
|
||||||
|
}
|
||||||
|
SERIAL_PORT.printf("Data writen : %s\n", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeLightningToSD(int type, time_t t, int dist, int energy) {
|
||||||
|
|
||||||
|
char dir[20];
|
||||||
|
char path[60];
|
||||||
|
|
||||||
|
String directory = dayDirectory();
|
||||||
|
|
||||||
|
directory.toCharArray(dir, sizeof(directory));
|
||||||
|
sprintf(path, "%s/%s", dir, lightning_log_file);
|
||||||
|
|
||||||
|
// Test to know if the file exists before opening it, if it doesn't exist
|
||||||
|
// we will write first an header
|
||||||
|
bool no_header = SD.exists(path);
|
||||||
|
|
||||||
|
// open the file. note that only one file can be open at a time,
|
||||||
|
// so you have to close this one before opening another.
|
||||||
|
File dataFile = SD.open(path, FILE_WRITE);
|
||||||
|
|
||||||
|
// if the file is available, write to it:
|
||||||
|
if (dataFile) {
|
||||||
|
if (!no_header) {
|
||||||
|
SERIAL_PORT.printf("Creating file with CSV header : %s\n", path);
|
||||||
|
dataFile.printf("Date");
|
||||||
|
dataFile.printf(";distance(km);energie_raw");
|
||||||
|
dataFile.printf(";TotalLightningCount;TotalPerturbationEvents;TotalNoiseDetection;TotalUnknownDetection");
|
||||||
|
dataFile.printf(";BME280_Pressure(hPa);BME280_Temperature(degC);BME280_Altitude(m)");
|
||||||
|
// dataFile.printf(";DHT22_Humidity(%);DHT22_Temperature(degC)");
|
||||||
|
dataFile.printf("\n");
|
||||||
|
}
|
||||||
|
dataFile.printf("%04d/%02d/%02d_%02d:%02d:%02d", year(t), month(t), day(t), hour(t), minute(t), second(t));
|
||||||
|
dataFile.printf(";%d;%d;%d", type, dist, energy);
|
||||||
|
dataFile.printf(";%d;%d;%d;%d", lightning_nb_total, as3935_perturb_total, as3935_noise_total, as3935_unknown_total);
|
||||||
|
dataFile.printf(";%.2f;%.2f;%.1f", bme280_press, bme280_temp, bme280_alti);
|
||||||
|
// dataFile.printf(";%.0f;%.2f", dht22_event_hum.relative_humidity, dht22_event_temp.temperature);
|
||||||
|
dataFile.printf("\n");
|
||||||
|
|
||||||
|
dataFile.close();
|
||||||
|
}
|
||||||
|
SERIAL_PORT.printf("Data writen : %s\n", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
String dayDirectory() {
|
||||||
|
|
||||||
|
char dir[20] ;
|
||||||
|
|
||||||
|
sprintf(dir, "data/%04d/%02d/%02d/", year(), month(), day());
|
||||||
|
|
||||||
|
if (!SD.exists(dir))
|
||||||
|
{
|
||||||
|
SERIAL_PORT.printf("Creating directory : %s ...", dir);
|
||||||
|
SD.mkdir(dir);
|
||||||
|
SERIAL_PORT.printf(" DONE!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(dir);
|
||||||
|
}
|
97
arduino/cameteo-teensy-bug/cameteo-teensy/cameteo_teensy.h
Executable file
97
arduino/cameteo-teensy-bug/cameteo-teensy/cameteo_teensy.h
Executable file
@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* CAMETEO project
|
||||||
|
*
|
||||||
|
* This is a personnal project of weather station with
|
||||||
|
* automatic photo taking.
|
||||||
|
* This code is the weather station part and is meant
|
||||||
|
* to be run on a PJRC Teensy 3.2 board.
|
||||||
|
*
|
||||||
|
* Author : Arofarn
|
||||||
|
*
|
||||||
|
* Licence : GPL v3
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* LIBRARIES
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Protocols
|
||||||
|
#include <Wire.h> // library used with I2C protocol
|
||||||
|
#include <SPI.h> // SPI protocol
|
||||||
|
|
||||||
|
//Teensy3.x Real Time Clock
|
||||||
|
#include <TimeLib.h>
|
||||||
|
|
||||||
|
//SD card
|
||||||
|
#include <SD.h>
|
||||||
|
|
||||||
|
// Sensors
|
||||||
|
#include <Adafruit_Sensor.h> // Generic
|
||||||
|
//#include <DHT.h> // DHT22
|
||||||
|
//#include <DHT_U.h> // DHT22 unified
|
||||||
|
//#include <Adafruit_BMP085_U.h> // BMP180
|
||||||
|
#include <Adafruit_BME280.h> // BME280
|
||||||
|
#include <PWFusion_AS3935.h>
|
||||||
|
|
||||||
|
//GPS
|
||||||
|
//#include <Adafruit_GPS.h> // Adafruit Ultimate GPS
|
||||||
|
#include <TinyGPS.h> //Builtin GPS lib
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DEFINE
|
||||||
|
* &
|
||||||
|
* DECLARE
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Special characteres
|
||||||
|
#define DEGREE (char)176 //degree symbol in ISO 8859-1
|
||||||
|
#define DEGREE_LCD (char)222 //degree symbol for lcd display
|
||||||
|
|
||||||
|
// Pins used
|
||||||
|
#define RX1_PIN 0 // Raspberry Pi2 serial comm. - Hard wired
|
||||||
|
#define TX1_PIN 1 // Raspberry Pi2 serial comm. - Hard wired
|
||||||
|
#define RPI_PWR_PIN 2 // Raspberry Pi power control - Hard wired
|
||||||
|
#define SD_CS_PIN 4 // SPI SD CS - Hard wired
|
||||||
|
#define GPS_EN_PIN 6 // GPS ENable
|
||||||
|
#define RX3_PIN 7 // GPS serial comm.
|
||||||
|
#define TX3_PIN 8 // GPS serial comm.
|
||||||
|
#define AS3935_IRQ_PIN 9 // Interrupts from AS3935 lightning sensor
|
||||||
|
#define AS3935_CS_PIN 10 // SPI CS AS3935 lightning sensor
|
||||||
|
#define SPI_DI_PIN 11 // SPI MOSI AS3935 lightning sensor
|
||||||
|
#define SPI_DO_PIN 12 // SPI MISO AS3935 lightning sensor
|
||||||
|
#define SPI_CK_PIN 13 // SPI clock SD + AS3935 lightning sensor + built-in LED
|
||||||
|
//#define DHT_PIN 14 // Humidity sensor data
|
||||||
|
#define GPS_BUT_PIN 16 // GPS switch power control
|
||||||
|
|
||||||
|
#define I2C_SDA_PIN 18 // I2C SDA
|
||||||
|
#define I2C_SCL_PIN 19 // I2C SCL
|
||||||
|
|
||||||
|
#define BATT_VOLT_PIN 22 // Battery voltage monitoring (Analog Input)
|
||||||
|
#define LOW_BATT_PIN 23 // Low Battery signal from charger (digital input)
|
||||||
|
|
||||||
|
//I2C addresses
|
||||||
|
//#define TCN75A_ADDR 0x00 //Sensor I2C bus address
|
||||||
|
#define BMP180_ADDR 0x77
|
||||||
|
//#define BME280_ADDR 0x00 // ???
|
||||||
|
|
||||||
|
//Serial over USB communication
|
||||||
|
#define SERIAL_PORT Serial
|
||||||
|
#define SERIAL_BAUD_RATE 9600
|
||||||
|
|
||||||
|
//Raspberry Pi Serial Comm.
|
||||||
|
#define RPI_SERIAL_PORT Serial1
|
||||||
|
#define RPI_SERIAL_BAUD_RATE 115200
|
||||||
|
|
||||||
|
//GPS
|
||||||
|
#define GPS_SERIAL_PORT Serial3
|
||||||
|
#define GPS_SERIAL_BAUD_RATE 9600
|
||||||
|
|
||||||
|
// AS3935 Lightning sensor
|
||||||
|
#define AS3935_INDOORS 0
|
||||||
|
#define AS3935_OUTDOORS 1
|
||||||
|
#define AS3935_DIST_DIS 0
|
||||||
|
#define AS3935_DIST_EN 1
|
||||||
|
#define AS3935_CAPACITANCE 72 // 72pF for THIS board (from seller)
|
||||||
|
|
||||||
|
|
553
arduino/cameteo-teensy/cameteo-teensy.ino
Executable file
553
arduino/cameteo-teensy/cameteo-teensy.ino
Executable file
@ -0,0 +1,553 @@
|
|||||||
|
/*
|
||||||
|
* CAMETEO project
|
||||||
|
*
|
||||||
|
* This is a personnal project of weather station with
|
||||||
|
* automatic photo taking.
|
||||||
|
* This code is the weather station part and is meant
|
||||||
|
* to be run on a PJRC Teensy 3.2 board.
|
||||||
|
*
|
||||||
|
* Author : Arofarn
|
||||||
|
*
|
||||||
|
* Licence : GPL v3
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* LIBRARIES
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Protocols
|
||||||
|
#include <Wire.h> // library used with I2C protocol
|
||||||
|
#include <SPI.h> // SPI protocol
|
||||||
|
|
||||||
|
//Teensy3.x Real Time Clock
|
||||||
|
#include <TimeLib.h>
|
||||||
|
|
||||||
|
//SD card
|
||||||
|
#include <SD.h>
|
||||||
|
|
||||||
|
// Sensors
|
||||||
|
#include <Adafruit_Sensor.h> // Generic
|
||||||
|
#include <Adafruit_BME280.h> // BME280
|
||||||
|
#include <PWFusion_AS3935.h>
|
||||||
|
|
||||||
|
//GPS
|
||||||
|
//#include <Adafruit_GPS.h> // Adafruit Ultimate GPS
|
||||||
|
#include <TinyGPS.h> //Builtin GPS lib
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DEFINE
|
||||||
|
* &
|
||||||
|
* DECLARE
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Special characteres
|
||||||
|
#define DEGREE (char)176 //degree symbol in ISO 8859-1
|
||||||
|
#define DEGREE_LCD (char)222 //degree symbol for lcd display
|
||||||
|
|
||||||
|
// Pins used
|
||||||
|
#define RX1_PIN 0 // Raspberry Pi2 serial comm. - Hard wired
|
||||||
|
#define TX1_PIN 1 // Raspberry Pi2 serial comm. - Hard wired
|
||||||
|
#define RPI_PWR_PIN 2 // Raspberry Pi power control - Hard wired
|
||||||
|
#define AS3935_CS_PIN 4 // SPI SD CS - Hard wired
|
||||||
|
#define GPS_EN_PIN 6 // GPS ENable
|
||||||
|
#define RX3_PIN 7 // GPS serial comm.
|
||||||
|
#define TX3_PIN 8 // GPS serial comm.
|
||||||
|
#define AS3935_IRQ_PIN 9 // Interrupts from AS3935 lightning sensor
|
||||||
|
#define SD_CS_PIN 10 // SPI CS AS3935 lightning sensor
|
||||||
|
#define SPI_DI_PIN 11 // SPI MOSI AS3935 lightning sensor
|
||||||
|
#define SPI_DO_PIN 12 // SPI MISO AS3935 lightning sensor
|
||||||
|
#define SPI_CK_PIN 13 // SPI clock SD + AS3935 lightning sensor + built-in LED
|
||||||
|
#define BATT_VOLT_PIN 16 // Battery voltage monitoring (Analog Input)
|
||||||
|
#define LOW_BATT_PIN 17 // Low Battery signal from charger (digital input)
|
||||||
|
#define I2C_SDA_PIN 18 // I2C SDA BME280 sensor
|
||||||
|
#define I2C_SCL_PIN 19 // I2C SCL BME280 sensor
|
||||||
|
#define GPS_BUT_PIN 20 // GPS switch power control (unused)
|
||||||
|
|
||||||
|
//I2C addresses
|
||||||
|
//#define TCN75A_ADDR 0x00 //Sensor I2C bus address
|
||||||
|
//#define BMP180_ADDR 0x77
|
||||||
|
|
||||||
|
//Serial over USB communication
|
||||||
|
#define SERIAL_PORT Serial
|
||||||
|
#define SERIAL_BAUD_RATE 9600
|
||||||
|
|
||||||
|
//Raspberry Pi Serial Comm.
|
||||||
|
#define RPI_SERIAL_PORT Serial1
|
||||||
|
#define RPI_SERIAL_BAUD_RATE 9600
|
||||||
|
|
||||||
|
//GPS
|
||||||
|
#define GPS_SERIAL_PORT Serial3
|
||||||
|
#define GPS_SERIAL_BAUD_RATE 9600
|
||||||
|
|
||||||
|
//Sensors
|
||||||
|
Adafruit_BME280 bme;
|
||||||
|
float seaLevelPressure = SENSORS_PRESSURE_SEALEVELHPA;
|
||||||
|
float bme280_press;
|
||||||
|
float bme280_temp;
|
||||||
|
float bme280_alti;
|
||||||
|
float bme280_humi;
|
||||||
|
|
||||||
|
// AS3935 Lightning sensor
|
||||||
|
#define AS3935_INDOORS 0
|
||||||
|
#define AS3935_OUTDOORS 1
|
||||||
|
#define AS3935_DIST_DIS 0
|
||||||
|
#define AS3935_DIST_EN 1
|
||||||
|
#define AS3935_CAPACITANCE 72 // 72pF for THIS board (from seller)
|
||||||
|
enum strike_sources { UNKNOWN_SRC, LIGHTNING, PERTURBATION, NOISE };
|
||||||
|
volatile int8_t AS3935_ISR_Trig = 0; // Trigger for AS3935 lightning sensor
|
||||||
|
|
||||||
|
void AS3935_ISR() {
|
||||||
|
AS3935_ISR_Trig = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
PWF_AS3935 lightning(AS3935_CS_PIN, AS3935_IRQ_PIN, 33);
|
||||||
|
|
||||||
|
int as3935_src;
|
||||||
|
int as3935_distance;
|
||||||
|
long lightning_nb_total = 0;
|
||||||
|
int lightning_nb_hour = 0;
|
||||||
|
int lightning_nb_day = 0;
|
||||||
|
int as3935_perturb_total = 0;
|
||||||
|
int as3935_noise_total = 0;
|
||||||
|
int as3935_unknown_total = 0;
|
||||||
|
char lightning_log_file[12] = "lghtnng.log";
|
||||||
|
|
||||||
|
//GPS
|
||||||
|
//Adafruit_GPS GPS(&GPS_SERIAL_PORT);
|
||||||
|
TinyGPS GPS;
|
||||||
|
float gps_latitude, gps_longitude, gps_altitude; // returns +- latitude/longitude in degrees
|
||||||
|
float gps_speed, gps_course;
|
||||||
|
unsigned long gps_time, gps_date, gps_fix_age;
|
||||||
|
int gps_year;
|
||||||
|
byte gps_month, gps_day, gps_hour, gps_minutes, gps_second, gps_hundreths;
|
||||||
|
unsigned long gps_chars, gps_hdop;
|
||||||
|
unsigned short gps_sentences, gps_failed_checksum, gps_satellites;
|
||||||
|
|
||||||
|
//Miscellaneous
|
||||||
|
bool rpi_status = true;
|
||||||
|
int batt_voltage;
|
||||||
|
bool low_battery_flag = false;
|
||||||
|
|
||||||
|
// Tasks timers
|
||||||
|
elapsedMillis since_bme280;
|
||||||
|
elapsedMillis since_gps;
|
||||||
|
elapsedMillis since_display;
|
||||||
|
elapsedMillis since_serial_send;
|
||||||
|
elapsedMillis since_sd_log;
|
||||||
|
elapsedMillis since_batt_chk;
|
||||||
|
|
||||||
|
|
||||||
|
void BootMessage(String s) {
|
||||||
|
char c[13]; // char buffer for conversion String->char
|
||||||
|
s.toCharArray(c, sizeof(s));
|
||||||
|
SERIAL_PORT.printf("%-12s", c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BootOK() {
|
||||||
|
SERIAL_PORT.println("OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
void BootError() {
|
||||||
|
SERIAL_PORT.println("EE");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendDataOnSerial() {
|
||||||
|
|
||||||
|
//Print date & hour on serial port
|
||||||
|
SERIAL_PORT.printf("%04d/%02d/%02d_%02d:%02d:%02d\n", year(), month(), day(), hour(), minute(), second());
|
||||||
|
|
||||||
|
//if (bme280_event.pressure) {
|
||||||
|
SERIAL_PORT.printf("Temperature:%8.2f %cC | Pressure: %8.2f hPa | Altitude:%8.2f m\n",
|
||||||
|
bme280_temp, DEGREE, bme280_press, bme280_alti);
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// SERIAL_PORT.printf("BMP180 Sensor error\n");
|
||||||
|
// }
|
||||||
|
|
||||||
|
SERIAL_PORT.printf("Lightning strikes (from start) : %d | Perturb.: %d | Noise : %d | Unknown detect.: %d\n", lightning_nb_total,
|
||||||
|
as3935_perturb_total,
|
||||||
|
as3935_noise_total,
|
||||||
|
as3935_unknown_total);
|
||||||
|
|
||||||
|
//SERIAL_PORT.printf("Temperature:%8.2f %cC\n", tcn75a_temp, DEGREE);
|
||||||
|
|
||||||
|
SERIAL_PORT.printf("GPS data: %04d/%02d/%02d_%02d:%02d:%02d (%d)\n", gps_year, gps_month, gps_day,
|
||||||
|
gps_hour, gps_minutes, gps_second,
|
||||||
|
gps_fix_age);
|
||||||
|
SERIAL_PORT.printf(" Latitude: %11.8f | Longitude: %11.8f | Altitude: %5.2f m\n", gps_latitude, gps_longitude, gps_altitude);
|
||||||
|
SERIAL_PORT.printf(" Speed: %4.1f km/h | Course : %4.1f %c\n", gps_speed, gps_course, DEGREE);
|
||||||
|
SERIAL_PORT.printf(" Chars: %11d | Sentences: %11d | Failed cheksum: %4d\n", gps_chars, gps_sentences, gps_failed_checksum);
|
||||||
|
|
||||||
|
SERIAL_PORT.printf("Battery : %10d mV | Low Battery : %d\n", batt_voltage, low_battery_flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool isStartedPI() {
|
||||||
|
//Return true if the R-Pi respond to an simple request
|
||||||
|
return rpi_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void startRPI() {
|
||||||
|
//Start the Raspberry Pi via the MOSFET (grid connected to the RPI_PWR_PIN)
|
||||||
|
if (!isStartedPI()) {
|
||||||
|
pinMode(RPI_PWR_PIN, OUTPUT);
|
||||||
|
digitalWrite(RPI_PWR_PIN, LOW);
|
||||||
|
rpi_status = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopRPI() {
|
||||||
|
//Stop the Raspberry Pi via the MOSFET (grid connected to the RPI_PWR_PIN)
|
||||||
|
|
||||||
|
pinMode(RPI_PWR_PIN, INPUT); //Eteint via le MOSFET
|
||||||
|
rpi_status = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendDataToRPI() {
|
||||||
|
//if (isStartedPI()) {
|
||||||
|
//Print date & hour on serial port
|
||||||
|
RPI_SERIAL_PORT.printf("%04d/%02d/%02d_%02d:%02d:%02d\n", year(), month(), day(), hour(), minute(), second());
|
||||||
|
|
||||||
|
//if (bme280_event.pressure) {
|
||||||
|
RPI_SERIAL_PORT.printf("Temperature:%8.2f %cC | Pressure: %8.2f hPa | Altitude:%8.2f m\n",
|
||||||
|
bme280_temp, DEGREE, bme280_press, bme280_alti);
|
||||||
|
//}
|
||||||
|
//else {
|
||||||
|
// RPI_SERIAL_PORT.printf("BMP180 Sensor error\n");
|
||||||
|
//}
|
||||||
|
|
||||||
|
RPI_SERIAL_PORT.printf("Lightning strikes (from start) : %d | Perturb.: %d | Noise : %d | Unknown detect.: %d\n", lightning_nb_total,
|
||||||
|
as3935_perturb_total,
|
||||||
|
as3935_noise_total,
|
||||||
|
as3935_unknown_total);
|
||||||
|
|
||||||
|
RPI_SERIAL_PORT.printf("GPS data: %04d/%02d/%02d_%02d:%02d:%02d (%d)\n", gps_year, gps_month, gps_day,
|
||||||
|
gps_hour, gps_minutes, gps_second,
|
||||||
|
gps_fix_age);
|
||||||
|
RPI_SERIAL_PORT.printf(" Latitude: %11.8f | Longitude: %11.8f | Altitude: %5.2f m\n", gps_latitude, gps_longitude, gps_altitude);
|
||||||
|
RPI_SERIAL_PORT.printf(" Speed: %4.1f km/h | Course : %4.1f %c\n", gps_speed, gps_course, DEGREE);
|
||||||
|
RPI_SERIAL_PORT.printf(" Chars: %11d | Sentences: %11d | Failed cheksum: %4d\n", gps_chars, gps_sentences, gps_failed_checksum);
|
||||||
|
|
||||||
|
RPI_SERIAL_PORT.printf("Battery : %10d mV | Low Battery : %d\n", batt_voltage, low_battery_flag);
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// //error RPI is not started
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//SD Card
|
||||||
|
#define CONFIGFILE "config.txt"
|
||||||
|
|
||||||
|
//Configuration
|
||||||
|
char data_file[13] = "datalog.csv";
|
||||||
|
|
||||||
|
//Delays
|
||||||
|
|
||||||
|
unsigned int bme280_delay = 500;
|
||||||
|
//unsigned int dht_delay = 3000;
|
||||||
|
unsigned int gps_delay = 100;
|
||||||
|
unsigned int serial_send_delay = 5000;
|
||||||
|
unsigned int sd_log_delay = 5000;
|
||||||
|
unsigned int batt_chk_delay = 5000;
|
||||||
|
|
||||||
|
//Date and time
|
||||||
|
int TZ = 1; //Time zone
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SETUP
|
||||||
|
*/
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
|
||||||
|
//To be sure Raspberry-Pi won't be turned on unexpectedly
|
||||||
|
stopRPI();
|
||||||
|
|
||||||
|
pinMode(LOW_BATT_PIN, INPUT_PULLUP);
|
||||||
|
low_battery_flag = !digitalRead(LOW_BATT_PIN);
|
||||||
|
|
||||||
|
pinMode(GPS_BUT_PIN, INPUT_PULLUP);
|
||||||
|
pinMode(GPS_EN_PIN, OUTPUT);
|
||||||
|
gps_power();
|
||||||
|
|
||||||
|
SERIAL_PORT.begin(SERIAL_BAUD_RATE);
|
||||||
|
delay(1000);
|
||||||
|
//while (!SERIAL_PORT) { }; //Wait for serial port to start
|
||||||
|
SERIAL_PORT.printf("Serial Comm. OK\nNow booting...\n");
|
||||||
|
|
||||||
|
RPI_SERIAL_PORT.begin(RPI_SERIAL_BAUD_RATE);
|
||||||
|
delay(1000);
|
||||||
|
//while (!RPI_SERIAL_PORT) { }; //Wait for serial port to start
|
||||||
|
RPI_SERIAL_PORT.printf("Raspberry's Serial Comm. OK\nNow booting...\n");
|
||||||
|
|
||||||
|
BootMessage("SDcard");
|
||||||
|
// see if the card is present and can be initialized:
|
||||||
|
if (!SD.begin(SD_CS_PIN)) {
|
||||||
|
BootError();
|
||||||
|
while(1);
|
||||||
|
}
|
||||||
|
BootOK();
|
||||||
|
|
||||||
|
BootMessage("RT Clock");
|
||||||
|
// set the Time library to use Teensy 3.0's RTC to keep time
|
||||||
|
setSyncProvider(getTeensy3Time);
|
||||||
|
if (timeStatus()!= timeSet) {
|
||||||
|
BootError();
|
||||||
|
while(1);
|
||||||
|
}
|
||||||
|
BootOK();
|
||||||
|
|
||||||
|
|
||||||
|
BootMessage("BME280 (Pressure, Humidity and Temperature)");
|
||||||
|
/* Initialise the sensor */
|
||||||
|
// if(!bme.begin())
|
||||||
|
// {
|
||||||
|
// /* There was a problem detecting the BMP085 ... check your connections */
|
||||||
|
// BootError();
|
||||||
|
// while(1);
|
||||||
|
// }
|
||||||
|
BootOK();
|
||||||
|
|
||||||
|
BootMessage("AS3935 (Lightning)");
|
||||||
|
SPI.begin();
|
||||||
|
SPI.setClockDivider(SPI_CLOCK_DIV16); // SPI speed to SPI_CLOCK_DIV16/1MHz (max 2MHz, NEVER 500kHz!)
|
||||||
|
SPI.setDataMode(SPI_MODE1); // MAX31855 is a Mode 1 device --> clock starts low, read on rising edge
|
||||||
|
SPI.setBitOrder(MSBFIRST); // data sent to chip MSb first
|
||||||
|
lightning.AS3935_DefInit(); // set registers to default
|
||||||
|
// now update sensor cal for your application and power up chip
|
||||||
|
lightning.AS3935_ManualCal(AS3935_CAPACITANCE, AS3935_OUTDOORS, AS3935_DIST_EN);
|
||||||
|
// enable interrupt (hook IRQ pin to Arduino Uno/Mega interrupt input: 0 -> pin 2, 1 -> pin 3 )
|
||||||
|
attachInterrupt(AS3935_IRQ_PIN, AS3935_ISR, RISING);
|
||||||
|
BootOK();
|
||||||
|
|
||||||
|
BootMessage("GPS");
|
||||||
|
GPS_SERIAL_PORT.begin(GPS_SERIAL_BAUD_RATE);
|
||||||
|
while (!GPS_SERIAL_PORT) {} ;
|
||||||
|
BootOK();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* LOOP
|
||||||
|
*/
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
|
||||||
|
//Power ON/OFF GPS
|
||||||
|
gps_power();
|
||||||
|
|
||||||
|
//Lightning detection
|
||||||
|
if (AS3935_ISR_Trig != 0) {
|
||||||
|
AS3935_ISR_Trig = 0;
|
||||||
|
time_t t = now();
|
||||||
|
int distance = -9999;
|
||||||
|
int energy = -9999;
|
||||||
|
switch (as3935_src) {
|
||||||
|
case UNKNOWN_SRC:
|
||||||
|
//source inconnue
|
||||||
|
as3935_unknown_total++;
|
||||||
|
SERIAL_PORT.printf("Interruption (AS3935) : unkown source (not lightning)\n");
|
||||||
|
break;
|
||||||
|
case LIGHTNING:
|
||||||
|
//Foudre !!!
|
||||||
|
lightning_nb_total++;
|
||||||
|
distance = lightning.AS3935_GetLightningDistKm();
|
||||||
|
energy = lightning.AS3935_GetStrikeEnergyRaw();
|
||||||
|
SERIAL_PORT.printf("Interruption (AS3935) : Lightningbolt !!!\n");
|
||||||
|
SERIAL_PORT.printf("Distance : %4d km | Energy : %d \n", distance, energy);
|
||||||
|
break;
|
||||||
|
case PERTURBATION:
|
||||||
|
//Perturbation
|
||||||
|
as3935_perturb_total++;
|
||||||
|
SERIAL_PORT.printf("Interruption (AS3935) : perturbation...\n");
|
||||||
|
break;
|
||||||
|
case NOISE:
|
||||||
|
//Trop de bruit électromagnétique
|
||||||
|
as3935_noise_total++;
|
||||||
|
SERIAL_PORT.printf("Interruption (AS3935) : Too much electromagnetic noise!\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
writeLightningToSD(as3935_src, t, distance, energy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (since_batt_chk >= batt_chk_delay) {
|
||||||
|
since_batt_chk -= batt_chk_delay;
|
||||||
|
//Check battery status and voltage
|
||||||
|
low_battery_flag = !digitalRead(LOW_BATT_PIN);
|
||||||
|
batt_voltage = getBatteryVoltage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (since_bme280 >= bme280_delay) {
|
||||||
|
// since_bme280 = since_bme280 - bme280_delay;
|
||||||
|
//
|
||||||
|
// bme280_press = bme.readPressure() / 100.0F;
|
||||||
|
// bme280_alti = bme.readAltitude(seaLevelPressure);
|
||||||
|
// bme280_temp = bme.readTemperature();
|
||||||
|
// bme280_humi = bme.readHumidity();
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (since_gps >= gps_delay) {
|
||||||
|
since_gps = since_gps - gps_delay;
|
||||||
|
while (GPS_SERIAL_PORT.available()) {
|
||||||
|
char c = GPS_SERIAL_PORT.read();
|
||||||
|
if (GPS.encode(c)) {
|
||||||
|
GPS.get_datetime(&gps_date, &gps_time, &gps_fix_age);
|
||||||
|
GPS.crack_datetime(&gps_year, &gps_month, &gps_day,
|
||||||
|
&gps_hour, &gps_minutes, &gps_second,
|
||||||
|
&gps_hundreths, &gps_fix_age);
|
||||||
|
GPS.f_get_position(&gps_latitude, &gps_longitude, &gps_fix_age);
|
||||||
|
gps_altitude = GPS.f_altitude();
|
||||||
|
gps_speed = GPS.f_speed_kmph();
|
||||||
|
GPS.stats(&gps_chars, &gps_sentences, &gps_failed_checksum);
|
||||||
|
gps_satellites = GPS.satellites();
|
||||||
|
gps_hdop = GPS.hdop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (since_sd_log >= sd_log_delay) {
|
||||||
|
since_sd_log = since_sd_log - sd_log_delay;
|
||||||
|
writeDataToSD();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (since_serial_send >= serial_send_delay) {
|
||||||
|
since_serial_send = since_serial_send - serial_send_delay;
|
||||||
|
sendDataOnSerial();
|
||||||
|
sendDataToRPI();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FUNCTIONS
|
||||||
|
*/
|
||||||
|
|
||||||
|
time_t getTeensy3Time() {
|
||||||
|
return Teensy3Clock.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void gps_power() {
|
||||||
|
if (digitalRead(GPS_BUT_PIN) == 0) { digitalWrite(GPS_EN_PIN, LOW); }
|
||||||
|
else { digitalWrite(GPS_EN_PIN, HIGH); }
|
||||||
|
}
|
||||||
|
|
||||||
|
int getBatteryVoltage() {
|
||||||
|
long mean = 0;
|
||||||
|
int U = 0;
|
||||||
|
int n = 10;
|
||||||
|
int res = 12;
|
||||||
|
|
||||||
|
analogReadResolution(res);
|
||||||
|
|
||||||
|
for (int i=0; i<n; i++) {
|
||||||
|
mean += analogRead(BATT_VOLT_PIN);
|
||||||
|
//delay(2);
|
||||||
|
}
|
||||||
|
mean /= n;
|
||||||
|
|
||||||
|
U = map(mean, 0, pow(2, res)-1, 0, 3300); // Convert data from ADC into input voltage in mV
|
||||||
|
U *= 2; //Multiple by 2 for the voltage divider
|
||||||
|
|
||||||
|
return U;
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeDataToSD() {
|
||||||
|
|
||||||
|
char dir[20];
|
||||||
|
char path[60];
|
||||||
|
|
||||||
|
String directory = dayDirectory();
|
||||||
|
|
||||||
|
directory.toCharArray(dir, sizeof(directory));
|
||||||
|
sprintf(path, "%s/%s", dir, data_file);
|
||||||
|
|
||||||
|
// Test to know if the file exists before opening it, if it doesn't exist
|
||||||
|
// we will write first an header
|
||||||
|
bool no_header = SD.exists(path);
|
||||||
|
|
||||||
|
// open the file. note that only one file can be open at a time,
|
||||||
|
// so you have to close this one before opening another.
|
||||||
|
File dataFile = SD.open(path, FILE_WRITE);
|
||||||
|
|
||||||
|
// if the file is available, write to it:
|
||||||
|
if (dataFile) {
|
||||||
|
if (!no_header) {
|
||||||
|
SERIAL_PORT.printf("Creating file with CSV header : %s\n", path);
|
||||||
|
dataFile.printf("Date");
|
||||||
|
dataFile.printf(";BME280_Pressure(hPa);BME280_Temperature(degC);BME280_Humidity(%);BME280_Altitude(m)");
|
||||||
|
dataFile.printf(";TotalLightningCount;TotalPerturbationEvents;TotalNoiseDetection;TotalUnknownDetection");
|
||||||
|
dataFile.printf(";GPS_Latitude;GPS_Longitude;GPS_Altitude;GPS_Satellites;GPS_HDOP;GPS_Date");
|
||||||
|
dataFile.printf(";Battery_voltage(mV);Low_Battery_Status");
|
||||||
|
dataFile.printf("\n");
|
||||||
|
}
|
||||||
|
dataFile.printf("%04d/%02d/%02d_%02d:%02d:%02d", year(), month(), day(), hour(), minute(), second());
|
||||||
|
dataFile.printf(";%.2f;%.2f;%.0f;%.1f", bme280_press, bme280_temp, bme280_humi, bme280_alti);
|
||||||
|
dataFile.printf(";%d;%d;%d;%d", lightning_nb_total, as3935_perturb_total, as3935_noise_total, as3935_unknown_total);
|
||||||
|
dataFile.printf(";%.8f;%.8f;%.2f;%d;%d;%04d/%02d/%02d_%02d:%02d:%02d", gps_latitude, gps_longitude, gps_altitude,
|
||||||
|
gps_satellites, gps_hdop,
|
||||||
|
gps_year, gps_month, gps_day,
|
||||||
|
gps_hour, gps_minutes, gps_second);
|
||||||
|
dataFile.printf(";%d;%d", batt_voltage, low_battery_flag);
|
||||||
|
dataFile.printf("\n");
|
||||||
|
|
||||||
|
dataFile.close();
|
||||||
|
}
|
||||||
|
SERIAL_PORT.printf("Data writen : %s\n", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeLightningToSD(int type, time_t t, int dist, int energy) {
|
||||||
|
|
||||||
|
char dir[20];
|
||||||
|
char path[60];
|
||||||
|
|
||||||
|
String directory = dayDirectory();
|
||||||
|
|
||||||
|
directory.toCharArray(dir, sizeof(directory));
|
||||||
|
sprintf(path, "%s/%s", dir, lightning_log_file);
|
||||||
|
|
||||||
|
// Test to know if the file exists before opening it, if it doesn't exist
|
||||||
|
// we will write first an header
|
||||||
|
bool no_header = SD.exists(path);
|
||||||
|
|
||||||
|
// open the file. note that only one file can be open at a time,
|
||||||
|
// so you have to close this one before opening another.
|
||||||
|
File dataFile = SD.open(path, FILE_WRITE);
|
||||||
|
|
||||||
|
// if the file is available, write to it:
|
||||||
|
if (dataFile) {
|
||||||
|
if (!no_header) {
|
||||||
|
SERIAL_PORT.printf("Creating file with CSV header : %s\n", path);
|
||||||
|
dataFile.printf("Date");
|
||||||
|
dataFile.printf(";distance(km);energie_raw");
|
||||||
|
dataFile.printf(";TotalLightningCount;TotalPerturbationEvents;TotalNoiseDetection;TotalUnknownDetection");
|
||||||
|
dataFile.printf(";BME280_Pressure(hPa);BME280_Temperature(degC);BME280_Humidty(%);BME280_Altitude(m)");
|
||||||
|
dataFile.printf("\n");
|
||||||
|
}
|
||||||
|
dataFile.printf("%04d/%02d/%02d_%02d:%02d:%02d", year(t), month(t), day(t), hour(t), minute(t), second(t));
|
||||||
|
dataFile.printf(";%d;%d;%d", type, dist, energy);
|
||||||
|
dataFile.printf(";%d;%d;%d;%d", lightning_nb_total, as3935_perturb_total, as3935_noise_total, as3935_unknown_total);
|
||||||
|
dataFile.printf(";%.2f;%.2f;%.0f;%.1f", bme280_press, bme280_temp, bme280_humi, bme280_alti);
|
||||||
|
dataFile.printf("\n");
|
||||||
|
|
||||||
|
dataFile.close();
|
||||||
|
}
|
||||||
|
SERIAL_PORT.printf("Data writen : %s\n", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
String dayDirectory() {
|
||||||
|
|
||||||
|
char dir[20] ;
|
||||||
|
|
||||||
|
sprintf(dir, "data/%04d/%02d/%02d/", year(), month(), day());
|
||||||
|
|
||||||
|
if (!SD.exists(dir))
|
||||||
|
{
|
||||||
|
SERIAL_PORT.printf("Creating directory : %s ...", dir);
|
||||||
|
SD.mkdir(dir);
|
||||||
|
SERIAL_PORT.printf(" DONE!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(dir);
|
||||||
|
}
|
||||||
|
|
BIN
arduino/libraries/Adafruit-GFX-Library-master.zip
Executable file
BIN
arduino/libraries/Adafruit-GFX-Library-master.zip
Executable file
Binary file not shown.
BIN
arduino/libraries/Adafruit-PCD8544-Nokia-5110-LCD-library-master.zip
Executable file
BIN
arduino/libraries/Adafruit-PCD8544-Nokia-5110-LCD-library-master.zip
Executable file
Binary file not shown.
BIN
arduino/libraries/Adafruit_BMP085_Unified-master.zip
Executable file
BIN
arduino/libraries/Adafruit_BMP085_Unified-master.zip
Executable file
Binary file not shown.
BIN
arduino/libraries/Adafruit_DHT_Unified_original.zip
Executable file
BIN
arduino/libraries/Adafruit_DHT_Unified_original.zip
Executable file
Binary file not shown.
BIN
arduino/libraries/Adafruit_Sensor-master.zip
Executable file
BIN
arduino/libraries/Adafruit_Sensor-master.zip
Executable file
Binary file not shown.
BIN
arduino/libraries/DHT-sensor-library-master.zip
Executable file
BIN
arduino/libraries/DHT-sensor-library-master.zip
Executable file
Binary file not shown.
BIN
arduino/libraries/TCN75A.zip
Executable file
BIN
arduino/libraries/TCN75A.zip
Executable file
Binary file not shown.
BIN
boitier/cale-carte-principale.fcstd
Executable file
BIN
boitier/cale-carte-principale.fcstd
Executable file
Binary file not shown.
BIN
boitier/camera+BME280-couvercle.fcstd
Executable file
BIN
boitier/camera+BME280-couvercle.fcstd
Executable file
Binary file not shown.
BIN
boitier/camera+BME280.fcstd
Executable file
BIN
boitier/camera+BME280.fcstd
Executable file
Binary file not shown.
BIN
boitier/fixation-USB-RPi-zero.fcstd
Executable file
BIN
boitier/fixation-USB-RPi-zero.fcstd
Executable file
Binary file not shown.
BIN
boitier/hdmicase_bottom.stl
Executable file
BIN
boitier/hdmicase_bottom.stl
Executable file
Binary file not shown.
BIN
boitier/hdmicase_top.stl
Executable file
BIN
boitier/hdmicase_top.stl
Executable file
Binary file not shown.
BIN
boitier/structure.fcstd
Executable file
BIN
boitier/structure.fcstd
Executable file
Binary file not shown.
BIN
doc/cablage-teensy.ods
Executable file
BIN
doc/cablage-teensy.ods
Executable file
Binary file not shown.
170
doc/cameteo.mm
Executable file
170
doc/cameteo.mm
Executable file
@ -0,0 +1,170 @@
|
|||||||
|
<map version="freeplane 1.2.0">
|
||||||
|
<!--To view this file, download free mind mapping software Freeplane from http://freeplane.sourceforge.net -->
|
||||||
|
<node TEXT="datacam" ID="ID_940180243" CREATED="1445275007181" MODIFIED="1445275113016"><hook NAME="MapStyle">
|
||||||
|
|
||||||
|
<map_styles>
|
||||||
|
<stylenode LOCALIZED_TEXT="styles.root_node">
|
||||||
|
<stylenode LOCALIZED_TEXT="styles.predefined" POSITION="right">
|
||||||
|
<stylenode LOCALIZED_TEXT="default" MAX_WIDTH="600" COLOR="#000000" STYLE="as_parent">
|
||||||
|
<font NAME="SansSerif" SIZE="10" BOLD="false" ITALIC="false"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="defaultstyle.details"/>
|
||||||
|
<stylenode LOCALIZED_TEXT="defaultstyle.note"/>
|
||||||
|
<stylenode LOCALIZED_TEXT="defaultstyle.floating">
|
||||||
|
<edge STYLE="hide_edge"/>
|
||||||
|
<cloud COLOR="#f0f0f0" SHAPE="ROUND_RECT"/>
|
||||||
|
</stylenode>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="styles.user-defined" POSITION="right">
|
||||||
|
<stylenode LOCALIZED_TEXT="styles.topic" COLOR="#18898b" STYLE="fork">
|
||||||
|
<font NAME="Liberation Sans" SIZE="10" BOLD="true"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="styles.subtopic" COLOR="#cc3300" STYLE="fork">
|
||||||
|
<font NAME="Liberation Sans" SIZE="10" BOLD="true"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="styles.subsubtopic" COLOR="#669900">
|
||||||
|
<font NAME="Liberation Sans" SIZE="10" BOLD="true"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="styles.important">
|
||||||
|
<icon BUILTIN="yes"/>
|
||||||
|
</stylenode>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="styles.AutomaticLayout" POSITION="right">
|
||||||
|
<stylenode LOCALIZED_TEXT="AutomaticLayout.level.root" COLOR="#000000">
|
||||||
|
<font SIZE="18"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="AutomaticLayout.level,1" COLOR="#0033ff">
|
||||||
|
<font SIZE="16"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="AutomaticLayout.level,2" COLOR="#00b439">
|
||||||
|
<font SIZE="14"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="AutomaticLayout.level,3" COLOR="#990000">
|
||||||
|
<font SIZE="12"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="AutomaticLayout.level,4" COLOR="#111111">
|
||||||
|
<font SIZE="10"/>
|
||||||
|
</stylenode>
|
||||||
|
</stylenode>
|
||||||
|
</stylenode>
|
||||||
|
</map_styles>
|
||||||
|
</hook>
|
||||||
|
<node TEXT="Traitement données" POSITION="right" ID="ID_1847430113" CREATED="1445275240369" MODIFIED="1445275249403">
|
||||||
|
<node TEXT="Python (Rasp. Pi)" ID="ID_270658498" CREATED="1445275265758" MODIFIED="1447080486986">
|
||||||
|
<node TEXT="class Raw_Data" ID="ID_1595052898" CREATED="1445275279482" MODIFIED="1447080748503">
|
||||||
|
<icon BUILTIN="button_ok"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="class Calculated_Data" ID="ID_718485900" CREATED="1445275812612" MODIFIED="1447080463621"/>
|
||||||
|
<node TEXT="Fonction : Ajout des données à une photo" ID="ID_627725476" CREATED="1445275595946" MODIFIED="1445275873150">
|
||||||
|
<node TEXT="Incrustation" ID="ID_733470856" CREATED="1445275875550" MODIFIED="1445275883425">
|
||||||
|
<node TEXT="choix ordre" ID="ID_1297866337" CREATED="1445275719545" MODIFIED="1445275726053"/>
|
||||||
|
<node TEXT="choix données affichée" ID="ID_971249184" CREATED="1445275726924" MODIFIED="1445275736408"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Metadonnée EXIF et/ou XMP" ID="ID_908814226" CREATED="1445275885056" MODIFIED="1445275898061"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Croisement avec données publiques" ID="ID_816020813" CREATED="1447081168096" MODIFIED="1447081190356">
|
||||||
|
<node TEXT="Sources" ID="ID_1534956111" CREATED="1447081192190" MODIFIED="1447081202255">
|
||||||
|
<node TEXT="MétéoFrance" ID="ID_960146793" CREATED="1447081205560" MODIFIED="1447081210611"/>
|
||||||
|
<node TEXT="NOAA (GFS / WRF)" ID="ID_484693743" CREATED="1447081217776" MODIFIED="1447081234298"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Traitements" ID="ID_1239862080" CREATED="1447081274440" MODIFIED="1447081284372">
|
||||||
|
<node TEXT="Recalcul des altitudes (croisement pressions mesurée par datacam/pression organisme météo/ coordonnées GPS)" ID="ID_3490593" CREATED="1447081287034" MODIFIED="1447081341959"/>
|
||||||
|
<node TEXT="Avertissement en cas de divergence importantes avec prévisions" ID="ID_1887237474" CREATED="1447081368574" MODIFIED="1447081389978"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Embarqué Arduino" ID="ID_785409314" CREATED="1447080488757" MODIFIED="1447080501360">
|
||||||
|
<node TEXT="Sur période" ID="ID_1086659337" CREATED="1447080504005" MODIFIED="1447080515713">
|
||||||
|
<node TEXT="Moyenne" ID="ID_1592586385" CREATED="1447080517032" MODIFIED="1447080519825"/>
|
||||||
|
<node TEXT="Ecart-type" ID="ID_592958817" CREATED="1447080521647" MODIFIED="1447080531113"/>
|
||||||
|
<node TEXT="Quantiles" ID="ID_1704132109" CREATED="1447080532182" MODIFIED="1447080704522"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Acquisition photo" POSITION="left" ID="ID_205007013" CREATED="1445275114354" MODIFIED="1445275124232">
|
||||||
|
<node TEXT="PiCam" ID="ID_471792515" CREATED="1445275165918" MODIFIED="1445275170779">
|
||||||
|
<node TEXT="Problème 1 : cable plat très peu pratique" ID="ID_279048394" CREATED="1447417532604" MODIFIED="1447417604312"/>
|
||||||
|
<node TEXT="Problème 2 : connection très sensible (facilement des problèmes de détection" ID="ID_179270345" CREATED="1447417551524" MODIFIED="1447417586481"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="gPhoto2" ID="ID_714902196" CREATED="1445275175392" MODIFIED="1445275366515">
|
||||||
|
<icon BUILTIN="help"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="webcam USB" ID="ID_598071482" CREATED="1445703078725" MODIFIED="1445703092031">
|
||||||
|
<icon BUILTIN="help"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Autonomie" POSITION="right" ID="ID_1591208209" CREATED="1445275213800" MODIFIED="1445275219743">
|
||||||
|
<node TEXT="UI" ID="ID_1359812832" CREATED="1445275221705" MODIFIED="1445275225795"/>
|
||||||
|
<node TEXT="Energie" ID="ID_1154122970" CREATED="1445275227314" MODIFIED="1445275235842">
|
||||||
|
<node TEXT="Allumage R-Pi par Arduino" ID="ID_317304726" CREATED="1445275454078" MODIFIED="1445275473445"/>
|
||||||
|
<node TEXT="Batterie tampon" ID="ID_266523548" CREATED="1445275481734" MODIFIED="1445275494432"/>
|
||||||
|
<node TEXT="Alimentation" ID="ID_1650279501" CREATED="1445275498672" MODIFIED="1445275508689">
|
||||||
|
<node TEXT="Panneau solaire" ID="ID_62589468" CREATED="1445275510537" MODIFIED="1445275517145"/>
|
||||||
|
<node TEXT="Dynamo vélo" ID="ID_187725160" CREATED="1445275518067" MODIFIED="1445275524512"/>
|
||||||
|
<node TEXT="Batterie grosse capacité" ID="ID_538264726" CREATED="1445275525409" MODIFIED="1445275544191"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Boitier" ID="ID_378357561" CREATED="1445275554462" MODIFIED="1445275968197">
|
||||||
|
<node TEXT="résistance à l'eau" ID="ID_1281039465" CREATED="1445275970340" MODIFIED="1445703138475">
|
||||||
|
<node TEXT="Idée 1 : impression 3D et/ou découpe laser" ID="ID_77132726" CREATED="1447417185297" MODIFIED="1447417208635"/>
|
||||||
|
<node TEXT="Idée 2 : format bidon de cycliste (recyclage)" ID="ID_304162178" CREATED="1447417209434" MODIFIED="1447417236747"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="connectique" ID="ID_1602685639" CREATED="1445275980897" MODIFIED="1445275986634">
|
||||||
|
<node TEXT="alimentation" ID="ID_1585946812" CREATED="1445703179449" MODIFIED="1445703189353">
|
||||||
|
<node TEXT="panneau solaire" ID="ID_721407548" CREATED="1447417254583" MODIFIED="1447417273699"/>
|
||||||
|
<node TEXT="dynamo-vélo" ID="ID_629164198" CREATED="1447417278160" MODIFIED="1447417349222">
|
||||||
|
<node TEXT="prise USB classique (version étanche ???)" ID="ID_761396973" CREATED="1447417354551" MODIFIED="1447417370095"/>
|
||||||
|
<node TEXT="adaptateur direct vers la prise étanche existante" ID="ID_632440803" CREATED="1447417370816" MODIFIED="1447417410266"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
<node TEXT="déport capteurs" ID="ID_574169682" CREATED="1445703190116" MODIFIED="1445703200861"/>
|
||||||
|
<node TEXT="déport acquisition photo" ID="ID_990319349" CREATED="1445703201468" MODIFIED="1445703213407"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="fixation" ID="ID_1152348868" CREATED="1445275993576" MODIFIED="1445275996969">
|
||||||
|
<node TEXT="vélo" ID="ID_311023012" CREATED="1445275998224" MODIFIED="1445276000394"/>
|
||||||
|
<node TEXT="rando" ID="ID_1679144491" CREATED="1445276001346" MODIFIED="1445276003720"/>
|
||||||
|
<node TEXT="trépied photo" ID="ID_1795302368" CREATED="1445276004510" MODIFIED="1445276016077"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Acquisition données" POSITION="left" ID="ID_1804667401" CREATED="1445275093295" MODIFIED="1445275570673">
|
||||||
|
<node TEXT="PiSense" ID="ID_684279078" CREATED="1445275152767" MODIFIED="1447080440543">
|
||||||
|
<icon BUILTIN="button_ok"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Arduino" ID="ID_1198550772" CREATED="1445275194375" MODIFIED="1445275205644">
|
||||||
|
<node TEXT="Formation" ID="ID_1233017322" CREATED="1445275253081" MODIFIED="1447080380438">
|
||||||
|
<icon BUILTIN="button_ok"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Matériel" ID="ID_926052951" CREATED="1445275297254" MODIFIED="1445275304641">
|
||||||
|
<node TEXT="RTC" ID="ID_641589009" CREATED="1445275304644" MODIFIED="1447080383273">
|
||||||
|
<icon BUILTIN="button_ok"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="microSD" ID="ID_1775616220" CREATED="1445275316525" MODIFIED="1447080406240">
|
||||||
|
<icon BUILTIN="button_ok"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Temperature" ID="ID_1702799092" CREATED="1445275374067" MODIFIED="1447080408256">
|
||||||
|
<icon BUILTIN="button_ok"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Pression" ID="ID_1448376352" CREATED="1445275384406" MODIFIED="1447080411064">
|
||||||
|
<icon BUILTIN="button_ok"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Humidité" ID="ID_1888552872" CREATED="1445275387795" MODIFIED="1447080412960">
|
||||||
|
<icon BUILTIN="button_ok"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="GPS" ID="ID_1146102034" CREATED="1445275903847" MODIFIED="1451130798717">
|
||||||
|
<icon BUILTIN="button_ok"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="foudre" ID="ID_1917227809" CREATED="1445703116512" MODIFIED="1445703124790"/>
|
||||||
|
<node TEXT="Autre ?" ID="ID_1708926810" CREATED="1445275394108" MODIFIED="1445275397833"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Logiciel" ID="ID_521818033" CREATED="1447080606285" MODIFIED="1447080611344">
|
||||||
|
<node TEXT="Données calculées" ID="ID_238205261" CREATED="1447080619816" MODIFIED="1447080630924">
|
||||||
|
<node TEXT="Altitudes/Pression au niveau de la mer" ID="ID_145976984" CREATED="1447080633632" MODIFIED="1447080658177"/>
|
||||||
|
<node TEXT="Humidité / Point de rosée / Contenu en eau" ID="ID_253996014" CREATED="1447080661740" MODIFIED="1447080683496"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Unification des capteur avec lib. Adafruit Sensors" ID="ID_1043083145" CREATED="1447080689744" MODIFIED="1447080736111"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</map>
|
BIN
doc/connections RPi-Arduino.fzz
Executable file
BIN
doc/connections RPi-Arduino.fzz
Executable file
Binary file not shown.
BIN
doc/connections RPi-teensy.fzz
Executable file
BIN
doc/connections RPi-teensy.fzz
Executable file
Binary file not shown.
BIN
raspberry/__pycache__/datacam.cpython-32.pyc
Executable file
BIN
raspberry/__pycache__/datacam.cpython-32.pyc
Executable file
Binary file not shown.
361
raspberry/cameteo-rpi.py
Executable file
361
raspberry/cameteo-rpi.py
Executable file
@ -0,0 +1,361 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# -*- coding: UTF8 -*-
|
||||||
|
|
||||||
|
"""datacam
|
||||||
|
|
||||||
|
datacam is design to take a picture with the PiCamera and collect environnemental
|
||||||
|
data from the PiSense HAT, (almost) at the same time.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Configuration #
|
||||||
|
#################
|
||||||
|
|
||||||
|
#General
|
||||||
|
version = "v0.4"
|
||||||
|
#time_lapse = 20 # Time between photos et data captures
|
||||||
|
verbose = True
|
||||||
|
|
||||||
|
# Pictures
|
||||||
|
ajust_time = 1 #Time waiting for the camera to ajust before taking the photo (in seconds)
|
||||||
|
x_res = 2592 #X resolution (max = 2592)
|
||||||
|
y_res = 1944 #Y resolution (max = 1944)
|
||||||
|
photo_dir = "pictures" #Photo directory
|
||||||
|
photo_file = "Test_%Y-%m-%d_%H%M%S" # Picture files name (strftime() compatible)
|
||||||
|
photo_rotation = "180" # Image rotation in degree
|
||||||
|
#camera_LED = True # Set to False to disable red camera LED during capture, need root privileges
|
||||||
|
sense_LED_flash = True
|
||||||
|
|
||||||
|
# Data
|
||||||
|
|
||||||
|
#Data to collect :
|
||||||
|
# - 'temperature_h' : temperature from humidity sensor
|
||||||
|
# - 'temperature_p' : temperature from pressure sensor
|
||||||
|
# - 'temperature_cpu' : temperature from CPU
|
||||||
|
# - 'pressure' : pressure
|
||||||
|
# - 'humidity' : relative humidity
|
||||||
|
data_collection = ['temperature_p',
|
||||||
|
'pressure',
|
||||||
|
'temperature_h',
|
||||||
|
'humidity',
|
||||||
|
'temperature_cpu',
|
||||||
|
'blabla',
|
||||||
|
]
|
||||||
|
data_display = ['pressure',
|
||||||
|
'temperature_h',
|
||||||
|
'humidity',
|
||||||
|
'blabla',
|
||||||
|
]
|
||||||
|
data_dir = 'data'
|
||||||
|
data_log = ['date', 'value', 'quality']
|
||||||
|
|
||||||
|
# Fonts
|
||||||
|
fonts_dir = '/usr/share/fonts/truetype/freefont/'
|
||||||
|
font_bold = 'FreeMonoBold.ttf'
|
||||||
|
font_basic = 'FreeMono.ttf'
|
||||||
|
font_italic = 'FreeMonoOblique.ttf'
|
||||||
|
font_bold_italic = 'FreeMonoBoldOblique.ttf'
|
||||||
|
font_default = font_basic
|
||||||
|
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Imports #
|
||||||
|
#################
|
||||||
|
|
||||||
|
from os import path, popen
|
||||||
|
from time import sleep, strftime
|
||||||
|
import csv
|
||||||
|
|
||||||
|
# Picture management with the Pi Cam
|
||||||
|
from picamera import PiCamera
|
||||||
|
|
||||||
|
#Pi Sense HAT module (sensors)
|
||||||
|
from sense_hat import SenseHat
|
||||||
|
|
||||||
|
# Traitement d'images
|
||||||
|
import PIL
|
||||||
|
from PIL import ImageFont
|
||||||
|
from PIL import Image
|
||||||
|
from PIL import ImageDraw
|
||||||
|
|
||||||
|
# For picture metadata (EXIF, IPTC...)
|
||||||
|
import piexif
|
||||||
|
|
||||||
|
#Divers
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
#########################
|
||||||
|
# Déclarations globales #
|
||||||
|
#########################
|
||||||
|
|
||||||
|
sense = SenseHat()
|
||||||
|
data = {}
|
||||||
|
data_dir = path.join('/home/pi/datacam', version, data_dir)
|
||||||
|
photo_dir = path.join('/home/pi/datacam', version, photo_dir)
|
||||||
|
|
||||||
|
#############
|
||||||
|
# Fonctions #
|
||||||
|
#############
|
||||||
|
|
||||||
|
#Get one data from one sensors and return the value in a dict object with some metadata
|
||||||
|
# like time/date, description, unit...
|
||||||
|
|
||||||
|
class Raw_Data:
|
||||||
|
"""Data class.
|
||||||
|
"""
|
||||||
|
dtype = ''
|
||||||
|
date = ''
|
||||||
|
value = ''
|
||||||
|
quality = -1 # -1 : default value/data type unknown, 0 : OK, 1 : non-available
|
||||||
|
metadata = {'desc' : 'Unknow',
|
||||||
|
'unit' : '',
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, data_type):
|
||||||
|
"Initialize new data object of the defined type."
|
||||||
|
self.dtype = data_type
|
||||||
|
if self.dtype == 'temperature_p':
|
||||||
|
self.metadata = {'desc' : 'Air temperature (pressure sensors)',
|
||||||
|
'unit' : '°C',
|
||||||
|
'short' : 'Temperature',
|
||||||
|
'category' : 'Environnement/Temperature',
|
||||||
|
}
|
||||||
|
elif self.dtype == 'temperature_h':
|
||||||
|
self.metadata = {'desc' : 'Air temperature (pressure sensors)',
|
||||||
|
'unit' : '°C',
|
||||||
|
'short' : 'Temperature',
|
||||||
|
'category' : 'Environnement/Temperature',
|
||||||
|
}
|
||||||
|
elif self.dtype == 'pressure':
|
||||||
|
self.metadata = {'desc' : 'Atmospheric Pressure',
|
||||||
|
'unit' : 'mbar',
|
||||||
|
'short' : 'Pression',
|
||||||
|
'category' : 'Environnement/Pressure',
|
||||||
|
}
|
||||||
|
elif self.dtype == 'humidity':
|
||||||
|
self.metadata = {'desc' : 'Relative air humidity',
|
||||||
|
'unit' : '%',
|
||||||
|
'short' : 'Humidity',
|
||||||
|
'category' : 'Environnement/Humidity',
|
||||||
|
}
|
||||||
|
elif self.dtype == 'temperature_cpu':
|
||||||
|
self.metadata = {'desc' : 'CPU temperature',
|
||||||
|
'unit' : '°C',
|
||||||
|
'short' : 'Temperature',
|
||||||
|
'category' : 'System/Temperature',
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
print(data_type + " is unknown.")
|
||||||
|
|
||||||
|
def get_raw(self):
|
||||||
|
"Get raw data from sensors and update information about this data."
|
||||||
|
|
||||||
|
self.date = strftime('%Y-%m-%d_%H:%M:%S')
|
||||||
|
|
||||||
|
if self.dtype == 'temperature_p':
|
||||||
|
try:
|
||||||
|
self.value = float(sense.get_temperature_from_pressure())
|
||||||
|
self.quality = 0
|
||||||
|
except:
|
||||||
|
self.value = None
|
||||||
|
self.quality = 1
|
||||||
|
elif self.dtype == 'temperature_h':
|
||||||
|
try:
|
||||||
|
self.value = float(sense.get_temperature_from_humidity())
|
||||||
|
self.quality = 0
|
||||||
|
except:
|
||||||
|
self.value = None
|
||||||
|
self.quality = 1
|
||||||
|
elif self.dtype == 'pressure':
|
||||||
|
try:
|
||||||
|
self.value = float(sense.get_pressure())
|
||||||
|
self.quality = 0
|
||||||
|
except:
|
||||||
|
self.value = None
|
||||||
|
self.quality = 1
|
||||||
|
elif self.dtype == 'humidity':
|
||||||
|
try:
|
||||||
|
self.value = float(sense.get_humidity())
|
||||||
|
self.quality = 0
|
||||||
|
except:
|
||||||
|
self.value = None
|
||||||
|
self.quality = 1
|
||||||
|
elif self.dtype == 'temperature_cpu':
|
||||||
|
try:
|
||||||
|
temp = popen('/opt/vc/bin/vcgencmd measure_temp').readline()
|
||||||
|
self.value = float(temp.replace('temp=','').replace("'C\n",""))
|
||||||
|
self.quality = 0
|
||||||
|
except:
|
||||||
|
self.value = None
|
||||||
|
self.quality = 1
|
||||||
|
else:
|
||||||
|
print(data_type + " is unknown.")
|
||||||
|
self.metadata.update({'desc' : 'Unknown'})
|
||||||
|
self.value = None
|
||||||
|
self.quality = -1
|
||||||
|
|
||||||
|
def write_csv(self, file_path):
|
||||||
|
"Write data in CSV file. Creates CSV file and metadata text file if they do not exist."
|
||||||
|
|
||||||
|
if path.isfile(file_path):
|
||||||
|
with open(file_path, 'a') as csvfile:
|
||||||
|
datawriter = csv.DictWriter(csvfile, fieldnames=data_log, delimiter=';')
|
||||||
|
datawriter.writerow({n : v for n, v in vars(data[data_type]).items() if n in data_log })
|
||||||
|
|
||||||
|
else:
|
||||||
|
#Creation of metadata file
|
||||||
|
if data[data_type]['desc'] != 'Unknown':
|
||||||
|
with open(path.join(data_dir, data_type + '_desc.txt'), 'w') as metadata_file:
|
||||||
|
for desc, value in data[data_type].items():
|
||||||
|
if desc not in data_log:
|
||||||
|
metadata_file.write(desc + " : " + str(value) + '\n')
|
||||||
|
#Creation of data file
|
||||||
|
with open(csvfile_path, 'w') as csvfile:
|
||||||
|
datawriter = csv.DictWriter(csvfile, fieldnames=data_log, delimiter=';')
|
||||||
|
datawriter.writeheader()
|
||||||
|
datawriter.writerow({n : v for n, v in vars(data[data_type]).items() if n in data_log})
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"For debug"
|
||||||
|
if self.metadata['desc'] == 'Unknown':
|
||||||
|
return '<Data {t:} = {d:} at {dt}>'.format(d=self.metadata['desc'],
|
||||||
|
t=self.dtype,
|
||||||
|
dt=self.date,
|
||||||
|
)
|
||||||
|
elif type(self.value) == float:
|
||||||
|
return '<Data {d:} = {v:.1f}{u} ({q}) at {dt}>'.format(d=self.metadata['desc'],
|
||||||
|
v=self.value,
|
||||||
|
u=self.metadata['unit'],
|
||||||
|
q=self.quality,
|
||||||
|
dt=self.date,
|
||||||
|
)
|
||||||
|
elif self.value == None:
|
||||||
|
return '<Data {d:} = {v} ({q}) at {dt}>'.format(d=self.metadata['desc'],
|
||||||
|
v=self.value,
|
||||||
|
q=self.quality,
|
||||||
|
dt=self.date,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return '<Data {d:} = {v} {u} ({q}) at {dt}>'.format(d=self.metadata['desc'],
|
||||||
|
v=self.value,
|
||||||
|
u=self.metadata['unit'],
|
||||||
|
q=self.quality,
|
||||||
|
dt=self.date,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"For print (to user)"
|
||||||
|
if self.metadata['desc'] == 'Unknown':
|
||||||
|
return '{data_type:} = {desc:}'.format(desc=self.metadata['desc'],
|
||||||
|
data_type=self.dtype,
|
||||||
|
)
|
||||||
|
elif type(self.value) == float:
|
||||||
|
return '{d:} = {v:.1f}{u}'.format(d=self.metadata['desc'],
|
||||||
|
v=self.value,
|
||||||
|
u=self.metadata['unit'],
|
||||||
|
)
|
||||||
|
elif self.value == None:
|
||||||
|
return '{d:} = {v}'.format(d=self.metadata['desc'],
|
||||||
|
v='NA',
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return '{d:} = {v} {u}'.format(d=self.metadata['desc'],
|
||||||
|
v=self.value,
|
||||||
|
u=self.metadata['unit'],
|
||||||
|
)
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
# Take a picture
|
||||||
|
def get_pict():
|
||||||
|
with PiCamera() as camera:
|
||||||
|
camera.resolution = (x_res, y_res)
|
||||||
|
camera.rotation = photo_rotation
|
||||||
|
#camera.led = camera_LED # !!! need root privileges !!!
|
||||||
|
if sense_LED_flash:
|
||||||
|
sense.show_message(text_string="",
|
||||||
|
back_colour=[255, 255, 255],
|
||||||
|
)
|
||||||
|
camera.start_preview()
|
||||||
|
sleep(ajust_time)
|
||||||
|
sense.set_rotation(r=180, redraw=True)
|
||||||
|
#sense.show_message(text_string=":-)",
|
||||||
|
#scroll_speed=0.1 ,
|
||||||
|
#text_colour=[255, 0, 0],
|
||||||
|
#back_colour=[0, 0, 255])
|
||||||
|
|
||||||
|
pict_path = path.join(photo_dir, strftime(photo_file) + '.jpg')
|
||||||
|
camera.capture(pict_path)
|
||||||
|
sense.clear()
|
||||||
|
#camera.led = True # !!! need root privileges !!!
|
||||||
|
|
||||||
|
return pict_path
|
||||||
|
|
||||||
|
########
|
||||||
|
# MAIN #
|
||||||
|
########
|
||||||
|
|
||||||
|
# Collect asked data from sensors
|
||||||
|
for data_type in data_collection:
|
||||||
|
data[data_type] = Raw_Data(data_type)
|
||||||
|
|
||||||
|
data[data_type].get_raw()
|
||||||
|
|
||||||
|
if verbose :
|
||||||
|
print(data[data_type].__repr__())
|
||||||
|
print(data[data_type]())
|
||||||
|
|
||||||
|
#Save data in CSV file
|
||||||
|
|
||||||
|
csvfile_path = path.join(data_dir, data_type + '.csv')
|
||||||
|
|
||||||
|
data[data_type].write_csv(csvfile_path)
|
||||||
|
|
||||||
|
#Take a picture
|
||||||
|
#picture_path = get_pict()
|
||||||
|
|
||||||
|
## Write data on picture in new picture file
|
||||||
|
#pict = Image.open(picture_path)
|
||||||
|
|
||||||
|
#draw = ImageDraw.Draw(pict)
|
||||||
|
## Text line height (=font size) and line space calculation
|
||||||
|
#line_height = int(y_res / 40)
|
||||||
|
#line_space = int(line_height / 10)
|
||||||
|
|
||||||
|
#text = ''
|
||||||
|
#l = line_space
|
||||||
|
|
||||||
|
## Add a line with data for each data to display
|
||||||
|
#for d in data_display:
|
||||||
|
#text = str(data[d]) #.__str__()
|
||||||
|
## Select color and font to match data quality
|
||||||
|
#if data[d].quality == 0:
|
||||||
|
#text_color = (0, 255, 0) # Quality OK : text in green
|
||||||
|
#font = font_default
|
||||||
|
#elif data[d].quality < 0:
|
||||||
|
#text_color = (100, 100, 100) # Unknown in grey
|
||||||
|
#font = font_italic
|
||||||
|
#elif data[d].quality == 1:
|
||||||
|
#text_color = (255, 255, 0) # Sensor unavailable : yellow
|
||||||
|
#font = font_italic
|
||||||
|
#else:
|
||||||
|
#text_color = (255, 0, 0) # Other = error in red
|
||||||
|
#font = font_bold
|
||||||
|
|
||||||
|
#img_font = ImageFont.truetype(path.join(fonts_dir, font), line_height)
|
||||||
|
#draw.text((5,l), text, text_color, img_font)
|
||||||
|
#draw = ImageDraw.Draw(pict)
|
||||||
|
#l = l + line_height + line_space
|
||||||
|
|
||||||
|
##Get EXIF metadata and delete embedded thumbnail
|
||||||
|
#pict_exif = piexif.load(pict.info['exif'])
|
||||||
|
#del(pict_exif['thumbnail'])
|
||||||
|
|
||||||
|
#pict.save(picture_path, 'jpeg', exif=piexif.dump(pict_exif) )
|
||||||
|
|
||||||
|
#if verbose :
|
||||||
|
#print('Picture = ' + picture_path)
|
||||||
|
|
362
raspberry/datacam.py
Executable file
362
raspberry/datacam.py
Executable file
@ -0,0 +1,362 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# -*- coding: UTF8 -*-
|
||||||
|
|
||||||
|
"""datacam
|
||||||
|
v0.3
|
||||||
|
|
||||||
|
datacam is design to take a picture with the PiCamera and collect environnemental
|
||||||
|
data from the PiSense HAT, (almost) at the same time.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Configuration #
|
||||||
|
#################
|
||||||
|
|
||||||
|
#General
|
||||||
|
version = "v0.3"
|
||||||
|
#time_lapse = 20 # Time between photos et data captures
|
||||||
|
verbose = True
|
||||||
|
|
||||||
|
# Pictures
|
||||||
|
ajust_time = 1 #Time waiting for the camera to ajust before taking the photo (in seconds)
|
||||||
|
x_res = 2592 #X resolution (max = 2592)
|
||||||
|
y_res = 1944 #Y resolution (max = 1944)
|
||||||
|
photo_dir = "pictures" #Photo directory
|
||||||
|
photo_file = "Test_%Y-%m-%d_%H%M%S" # Picture files name (strftime() compatible)
|
||||||
|
photo_rotation = "180" # Image rotation in degree
|
||||||
|
#camera_LED = True # Set to False to disable red camera LED during capture, need root privileges
|
||||||
|
sense_LED_flash = True
|
||||||
|
|
||||||
|
# Data
|
||||||
|
|
||||||
|
#Data to collect :
|
||||||
|
# - 'temperature_h' : temperature from humidity sensor
|
||||||
|
# - 'temperature_p' : temperature from pressure sensor
|
||||||
|
# - 'temperature_cpu' : temperature from CPU
|
||||||
|
# - 'pressure' : pressure
|
||||||
|
# - 'humidity' : relative humidity
|
||||||
|
data_collection = ['temperature_p',
|
||||||
|
'pressure',
|
||||||
|
'temperature_h',
|
||||||
|
'humidity',
|
||||||
|
'temperature_cpu',
|
||||||
|
'blabla',
|
||||||
|
]
|
||||||
|
data_display = ['pressure',
|
||||||
|
'temperature_h',
|
||||||
|
'humidity',
|
||||||
|
'blabla',
|
||||||
|
]
|
||||||
|
data_dir = 'data'
|
||||||
|
data_log = ['date', 'value', 'quality']
|
||||||
|
|
||||||
|
# Fonts
|
||||||
|
fonts_dir = '/usr/share/fonts/truetype/freefont/'
|
||||||
|
font_bold = 'FreeMonoBold.ttf'
|
||||||
|
font_basic = 'FreeMono.ttf'
|
||||||
|
font_italic = 'FreeMonoOblique.ttf'
|
||||||
|
font_bold_italic = 'FreeMonoBoldOblique.ttf'
|
||||||
|
font_default = font_basic
|
||||||
|
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Imports #
|
||||||
|
#################
|
||||||
|
|
||||||
|
from os import path, popen
|
||||||
|
from time import sleep, strftime
|
||||||
|
import csv
|
||||||
|
|
||||||
|
# Picture management with the Pi Cam
|
||||||
|
from picamera import PiCamera
|
||||||
|
|
||||||
|
#Pi Sense HAT module (sensors)
|
||||||
|
from sense_hat import SenseHat
|
||||||
|
|
||||||
|
# Traitement d'images
|
||||||
|
import PIL
|
||||||
|
from PIL import ImageFont
|
||||||
|
from PIL import Image
|
||||||
|
from PIL import ImageDraw
|
||||||
|
|
||||||
|
# For picture metadata (EXIF, IPTC...)
|
||||||
|
import piexif
|
||||||
|
|
||||||
|
#Divers
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
#########################
|
||||||
|
# Déclarations globales #
|
||||||
|
#########################
|
||||||
|
|
||||||
|
sense = SenseHat()
|
||||||
|
data = {}
|
||||||
|
data_dir = path.join('/home/pi/datacam', version, data_dir)
|
||||||
|
photo_dir = path.join('/home/pi/datacam', version, photo_dir)
|
||||||
|
|
||||||
|
#############
|
||||||
|
# Fonctions #
|
||||||
|
#############
|
||||||
|
|
||||||
|
#Get one data from one sensors and return the value in a dict object with some metadata
|
||||||
|
# like time/date, description, unit...
|
||||||
|
|
||||||
|
class Raw_Data:
|
||||||
|
"""Data class.
|
||||||
|
"""
|
||||||
|
dtype = ''
|
||||||
|
date = ''
|
||||||
|
value = ''
|
||||||
|
quality = -1 # -1 : default value/data type unknown, 0 : OK, 1 : non-available
|
||||||
|
metadata = {'desc' : 'Unknow',
|
||||||
|
'unit' : '',
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, data_type):
|
||||||
|
"Initialize new data object of the defined type."
|
||||||
|
self.dtype = data_type
|
||||||
|
if self.dtype == 'temperature_p':
|
||||||
|
self.metadata = {'desc' : 'Air temperature (pressure sensors)',
|
||||||
|
'unit' : '°C',
|
||||||
|
'short' : 'Temperature',
|
||||||
|
'category' : 'Environnement/Temperature',
|
||||||
|
}
|
||||||
|
elif self.dtype == 'temperature_h':
|
||||||
|
self.metadata = {'desc' : 'Air temperature (pressure sensors)',
|
||||||
|
'unit' : '°C',
|
||||||
|
'short' : 'Temperature',
|
||||||
|
'category' : 'Environnement/Temperature',
|
||||||
|
}
|
||||||
|
elif self.dtype == 'pressure':
|
||||||
|
self.metadata = {'desc' : 'Atmospheric Pressure',
|
||||||
|
'unit' : 'mbar',
|
||||||
|
'short' : 'Pression',
|
||||||
|
'category' : 'Environnement/Pressure',
|
||||||
|
}
|
||||||
|
elif self.dtype == 'humidity':
|
||||||
|
self.metadata = {'desc' : 'Relative air humidity',
|
||||||
|
'unit' : '%',
|
||||||
|
'short' : 'Humidity',
|
||||||
|
'category' : 'Environnement/Humidity',
|
||||||
|
}
|
||||||
|
elif self.dtype == 'temperature_cpu':
|
||||||
|
self.metadata = {'desc' : 'CPU temperature',
|
||||||
|
'unit' : '°C',
|
||||||
|
'short' : 'Temperature',
|
||||||
|
'category' : 'System/Temperature',
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
print(data_type + " is unknown.")
|
||||||
|
|
||||||
|
def get_raw(self):
|
||||||
|
"Get raw data from sensors and update information about this data."
|
||||||
|
|
||||||
|
self.date = strftime('%Y-%m-%d_%H:%M:%S')
|
||||||
|
|
||||||
|
if self.dtype == 'temperature_p':
|
||||||
|
try:
|
||||||
|
self.value = float(sense.get_temperature_from_pressure())
|
||||||
|
self.quality = 0
|
||||||
|
except:
|
||||||
|
self.value = None
|
||||||
|
self.quality = 1
|
||||||
|
elif self.dtype == 'temperature_h':
|
||||||
|
try:
|
||||||
|
self.value = float(sense.get_temperature_from_humidity())
|
||||||
|
self.quality = 0
|
||||||
|
except:
|
||||||
|
self.value = None
|
||||||
|
self.quality = 1
|
||||||
|
elif self.dtype == 'pressure':
|
||||||
|
try:
|
||||||
|
self.value = float(sense.get_pressure())
|
||||||
|
self.quality = 0
|
||||||
|
except:
|
||||||
|
self.value = None
|
||||||
|
self.quality = 1
|
||||||
|
elif self.dtype == 'humidity':
|
||||||
|
try:
|
||||||
|
self.value = float(sense.get_humidity())
|
||||||
|
self.quality = 0
|
||||||
|
except:
|
||||||
|
self.value = None
|
||||||
|
self.quality = 1
|
||||||
|
elif self.dtype == 'temperature_cpu':
|
||||||
|
try:
|
||||||
|
temp = popen('/opt/vc/bin/vcgencmd measure_temp').readline()
|
||||||
|
self.value = float(temp.replace('temp=','').replace("'C\n",""))
|
||||||
|
self.quality = 0
|
||||||
|
except:
|
||||||
|
self.value = None
|
||||||
|
self.quality = 1
|
||||||
|
else:
|
||||||
|
print(data_type + " is unknown.")
|
||||||
|
self.metadata.update({'desc' : 'Unknown'})
|
||||||
|
self.value = None
|
||||||
|
self.quality = -1
|
||||||
|
|
||||||
|
def write_csv(self, file_path):
|
||||||
|
"Write data in CSV file. Creates CSV file and metadata text file if they do not exist."
|
||||||
|
|
||||||
|
if path.isfile(file_path):
|
||||||
|
with open(file_path, 'a') as csvfile:
|
||||||
|
datawriter = csv.DictWriter(csvfile, fieldnames=data_log, delimiter=';')
|
||||||
|
datawriter.writerow({n : v for n, v in vars(data[data_type]).items() if n in data_log })
|
||||||
|
|
||||||
|
else:
|
||||||
|
#Creation of metadata file
|
||||||
|
if data[data_type]['desc'] != 'Unknown':
|
||||||
|
with open(path.join(data_dir, data_type + '_desc.txt'), 'w') as metadata_file:
|
||||||
|
for desc, value in data[data_type].items():
|
||||||
|
if desc not in data_log:
|
||||||
|
metadata_file.write(desc + " : " + str(value) + '\n')
|
||||||
|
#Creation of data file
|
||||||
|
with open(csvfile_path, 'w') as csvfile:
|
||||||
|
datawriter = csv.DictWriter(csvfile, fieldnames=data_log, delimiter=';')
|
||||||
|
datawriter.writeheader()
|
||||||
|
datawriter.writerow({n : v for n, v in vars(data[data_type]).items() if n in data_log})
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"For debug"
|
||||||
|
if self.metadata['desc'] == 'Unknown':
|
||||||
|
return '<Data {t:} = {d:} at {dt}>'.format(d=self.metadata['desc'],
|
||||||
|
t=self.dtype,
|
||||||
|
dt=self.date,
|
||||||
|
)
|
||||||
|
elif type(self.value) == float:
|
||||||
|
return '<Data {d:} = {v:.1f}{u} ({q}) at {dt}>'.format(d=self.metadata['desc'],
|
||||||
|
v=self.value,
|
||||||
|
u=self.metadata['unit'],
|
||||||
|
q=self.quality,
|
||||||
|
dt=self.date,
|
||||||
|
)
|
||||||
|
elif self.value == None:
|
||||||
|
return '<Data {d:} = {v} ({q}) at {dt}>'.format(d=self.metadata['desc'],
|
||||||
|
v=self.value,
|
||||||
|
q=self.quality,
|
||||||
|
dt=self.date,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return '<Data {d:} = {v} {u} ({q}) at {dt}>'.format(d=self.metadata['desc'],
|
||||||
|
v=self.value,
|
||||||
|
u=self.metadata['unit'],
|
||||||
|
q=self.quality,
|
||||||
|
dt=self.date,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"For print (to user)"
|
||||||
|
if self.metadata['desc'] == 'Unknown':
|
||||||
|
return '{data_type:} = {desc:}'.format(desc=self.metadata['desc'],
|
||||||
|
data_type=self.dtype,
|
||||||
|
)
|
||||||
|
elif type(self.value) == float:
|
||||||
|
return '{d:} = {v:.1f}{u}'.format(d=self.metadata['desc'],
|
||||||
|
v=self.value,
|
||||||
|
u=self.metadata['unit'],
|
||||||
|
)
|
||||||
|
elif self.value == None:
|
||||||
|
return '{d:} = {v}'.format(d=self.metadata['desc'],
|
||||||
|
v='NA',
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return '{d:} = {v} {u}'.format(d=self.metadata['desc'],
|
||||||
|
v=self.value,
|
||||||
|
u=self.metadata['unit'],
|
||||||
|
)
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
# Take a picture
|
||||||
|
def get_pict():
|
||||||
|
with PiCamera() as camera:
|
||||||
|
camera.resolution = (x_res, y_res)
|
||||||
|
camera.rotation = photo_rotation
|
||||||
|
#camera.led = camera_LED # !!! need root privileges !!!
|
||||||
|
if sense_LED_flash:
|
||||||
|
sense.show_message(text_string="",
|
||||||
|
back_colour=[255, 255, 255],
|
||||||
|
)
|
||||||
|
camera.start_preview()
|
||||||
|
sleep(ajust_time)
|
||||||
|
sense.set_rotation(r=180, redraw=True)
|
||||||
|
#sense.show_message(text_string=":-)",
|
||||||
|
#scroll_speed=0.1 ,
|
||||||
|
#text_colour=[255, 0, 0],
|
||||||
|
#back_colour=[0, 0, 255])
|
||||||
|
|
||||||
|
pict_path = path.join(photo_dir, strftime(photo_file) + '.jpg')
|
||||||
|
camera.capture(pict_path)
|
||||||
|
sense.clear()
|
||||||
|
#camera.led = True # !!! need root privileges !!!
|
||||||
|
|
||||||
|
return pict_path
|
||||||
|
|
||||||
|
########
|
||||||
|
# MAIN #
|
||||||
|
########
|
||||||
|
|
||||||
|
# Collect asked data from sensors
|
||||||
|
for data_type in data_collection:
|
||||||
|
data[data_type] = Raw_Data(data_type)
|
||||||
|
|
||||||
|
data[data_type].get_raw()
|
||||||
|
|
||||||
|
if verbose :
|
||||||
|
print(data[data_type].__repr__())
|
||||||
|
print(data[data_type]())
|
||||||
|
|
||||||
|
#Save data in CSV file
|
||||||
|
|
||||||
|
csvfile_path = path.join(data_dir, data_type + '.csv')
|
||||||
|
|
||||||
|
data[data_type].write_csv(csvfile_path)
|
||||||
|
|
||||||
|
#Take a picture
|
||||||
|
picture_path = get_pict()
|
||||||
|
|
||||||
|
# Write data on picture in new picture file
|
||||||
|
pict = Image.open(picture_path)
|
||||||
|
|
||||||
|
draw = ImageDraw.Draw(pict)
|
||||||
|
# Text line height (=font size) and line space calculation
|
||||||
|
line_height = int(y_res / 40)
|
||||||
|
line_space = int(line_height / 10)
|
||||||
|
|
||||||
|
text = ''
|
||||||
|
l = line_space
|
||||||
|
|
||||||
|
# Add a line with data for each data to display
|
||||||
|
for d in data_display:
|
||||||
|
text = str(data[d]) #.__str__()
|
||||||
|
# Select color and font to match data quality
|
||||||
|
if data[d].quality == 0:
|
||||||
|
text_color = (0, 255, 0) # Quality OK : text in green
|
||||||
|
font = font_default
|
||||||
|
elif data[d].quality < 0:
|
||||||
|
text_color = (100, 100, 100) # Unknown in grey
|
||||||
|
font = font_italic
|
||||||
|
elif data[d].quality == 1:
|
||||||
|
text_color = (255, 255, 0) # Sensor unavailable : yellow
|
||||||
|
font = font_italic
|
||||||
|
else:
|
||||||
|
text_color = (255, 0, 0) # Other = error in red
|
||||||
|
font = font_bold
|
||||||
|
|
||||||
|
img_font = ImageFont.truetype(path.join(fonts_dir, font), line_height)
|
||||||
|
draw.text((5,l), text, text_color, img_font)
|
||||||
|
draw = ImageDraw.Draw(pict)
|
||||||
|
l = l + line_height + line_space
|
||||||
|
|
||||||
|
#Get EXIF metadata and delete embedded thumbnail
|
||||||
|
pict_exif = piexif.load(pict.info['exif'])
|
||||||
|
del(pict_exif['thumbnail'])
|
||||||
|
|
||||||
|
pict.save(picture_path, 'jpeg', exif=piexif.dump(pict_exif) )
|
||||||
|
|
||||||
|
if verbose :
|
||||||
|
print('Picture = ' + picture_path)
|
||||||
|
|
5
raspberry/pi_dev_sync.sh
Executable file
5
raspberry/pi_dev_sync.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#Synchronise le répertoire de développement de datacam vers aro-W840
|
||||||
|
|
||||||
|
rsync --archive --compress --delete --verbose -e ssh pi@aro-pi:~/datacam/* ~/Dev/Python/datacam/
|
69
raspberry/serial_module.py
Executable file
69
raspberry/serial_module.py
Executable file
@ -0,0 +1,69 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# -*- coding: UTF8 -*-
|
||||||
|
|
||||||
|
# Reception de données depuis un arduino via port série
|
||||||
|
|
||||||
|
version = "0.1"
|
||||||
|
|
||||||
|
#Configuration du port série
|
||||||
|
port_serie = "/dev/ttyACM1"
|
||||||
|
#port_serie_alt = "/dev/ttyACM1"
|
||||||
|
|
||||||
|
baud_rate = 115200
|
||||||
|
|
||||||
|
#############
|
||||||
|
import serial
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
str_line = [] #une ligne de donnée lue sur le port série et prétraitée
|
||||||
|
data = dict() #une donnée d'un capteur
|
||||||
|
data_package = dict() #un paquet de donnée à un moment
|
||||||
|
data_set = dict() #ensemble des paquets de données
|
||||||
|
|
||||||
|
with serial.Serial(port_serie, baud_rate) as comm: #, timeout = 0
|
||||||
|
|
||||||
|
print("Initialization du microcontroleur en cours...")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
line = comm.readline()
|
||||||
|
if line == b'###Init_end###\r\n' :
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print(".")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
line = comm.readline()
|
||||||
|
if line == b'###Data_start###\r\n' :
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("En attente d'un nouveau paquet de données...")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
line = comm.readline()
|
||||||
|
if line == b'###Data_start###\r\n' :
|
||||||
|
print("Nouveau paquet de données !")
|
||||||
|
#break
|
||||||
|
elif line == b'###Data_end###\r\n' :
|
||||||
|
print("Fin du paquet de données !")
|
||||||
|
else:
|
||||||
|
#Nettoyage des caractères spéciaux inutile (retour chariot...) et
|
||||||
|
# conversion en liste de chaînes de caractères
|
||||||
|
str_line = line.decode("ascii").strip().split('\t')
|
||||||
|
#Mise en tableau de données
|
||||||
|
data['type'] = str_line[0].strip()
|
||||||
|
data['sensor'] = str_line[1].strip()
|
||||||
|
data['value'] = str_line[2].strip()
|
||||||
|
if len(str_line) > 3 and str_line[3].strip() != "-":
|
||||||
|
data['unit'] = str_line[3].strip()
|
||||||
|
else:
|
||||||
|
data['unit'] = None
|
||||||
|
|
||||||
|
if len(str_line) > 4:
|
||||||
|
data['comment'] = str_line[4]
|
||||||
|
else:
|
||||||
|
data['comment'] = None
|
||||||
|
|
||||||
|
data_package[data['type'] + "_" + data['sensor']] = data
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
pprint(data_package)
|
Loading…
Reference in New Issue
Block a user