Initial commit
This commit is contained in:
commit
e9c20be21c
|
@ -0,0 +1,6 @@
|
|||
raspberry/data/*
|
||||
raspberry/pictures/*
|
||||
*.fcstd1
|
||||
*.fcstd2
|
||||
boitier/old*
|
||||
.directory
|
|
@ -0,0 +1,4 @@
|
|||
#Fichier de configuration du contrôleur du projet
|
||||
# Camétéo
|
||||
time_step=2000
|
||||
data_file=datalog3.csv
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
|
@ -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)
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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/
|
|
@ -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