Low-cost sensor system software

// SPSA: SPS30 Sensirion with Arduino sensor system

// ######### SET THE DEVICEID ####################
String deviceID = "SPSA_0001";

// ######### SET THE LED PIN ##################### -- 8 for [SPSA_0001]
int ledposition = 8;

//taking 10sec measurements
/*
 * SPS030......Mega //sketch parts from sps30 Basic Readings example
 * 1 VCC.......5V
 * 2 SDA.......SDA (nb: with 10k pull up resistor, or other I2C sensor)
 * 3 SCL.......SCL (nb: with 10k pull up resistor, or other I2C sensor)
 * 4 Select....GND (select I2c)
 * 5 GND.......GND
 *
 * BME280......Mega //sketch parts from SparkFun BME280
 * VIN.........5V
 * GND.........GND
 * SDA.........SDA
 * SCL.........SCL

 * SD module...Mega //sketch parts from SdFat SoftwareSPI
 * GND.........GND
 * VCC.........5V
 * MISO........50
 * MOSI........51
 * SCK.........52
 * CS..........53
 *
 * DS3231......Mega //sketch parts from DS3231_TEST
 * GND.........GND
 * VCC.........5V
 * SDA.........SDA
 * SCL.........SCL
 * 
 * Green LED....Mega
 * +............8 [+: longer leg]
 * -..resistor..GND [-: smaller leg]
 */

String sensors = "SPS30,BME280";
String softwareVersion = "V0-1";

#include <SPI.h>
#include "SdFat.h"
#include <Wire.h>
#include "RTClib.h"
#include "sps30.h"
#include <LowPower.h>
#include "SparkFunBME280.h"

SPS30 sps30;
SdFat SD;
RTC_DS3231 rtc;
BME280 bmeSensor;

#define SD_CS_PIN SS
#define SP30_COMMS I2C_COMMS
#define DEBUG 0
#define PERFORMCLEANNOW 1 //SPS030 cleaning
File metaFile;
String meta_filename = "metadata_"+deviceID+".txt";
String metaheader = "datetime;software;deviceID;spsSerial;sensors";
File dataFile;
String data_filename= "";
String month_cov = "";
String dataheader = "counter;datetime;PM1;PM2_5;PM4;PM10;NumPM1;NumPM2_5;NumPM4;NumPM10;PartSize;tempC;humidity;pressure";
String data ="";
String serialdata = "";
struct sps_values val;
String pmString = "";
String bmeString = "";

long counter = 0; // For every restart, a counter is included

void setup() {
  // put your setup code here, to run once:
  pinMode(ledposition,OUTPUT);
  Serial.begin(9600);
  Serial.print("Initializing RTC module...");
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }

  DateTime now = rtc.now();
  Serial.println("Current time according to RTC: "+String(now.timestamp(DateTime::TIMESTAMP_FULL)));
  Serial.println("Starting low-power to let battery charge...");
  delay(1000);
  for(int i=0;i<6;i++){
    LowPower.idle(SLEEP_8S, ADC_OFF, TIMER5_OFF, TIMER4_OFF, TIMER3_OFF, 
          TIMER2_OFF, TIMER1_OFF, TIMER0_OFF, SPI_OFF, USART3_OFF, 
          USART2_OFF, USART1_OFF, USART0_OFF, TWI_OFF);  
  }

  Serial.print("Initializing SD card...");

  if (!SD.begin(SD_CS_PIN)) {
    Serial.println("initialization failed!");
    return;
  }

  Wire.begin();
  bmeSensor.setI2CAddress(0x76);
  if (bmeSensor.beginI2C() == false)
  {
    Serial.println("The BME280 sensor did not respond. Please check wiring.");
    while(1); //Freeze
  }

  Serial.println("initialization done.");
// set driver debug level
  sps30.EnableDebugging(DEBUG);
  // Begin communication channel TO sp30;
  if (!sps30.begin(SP30_COMMS)) 
    {
   while(1);
    }
  
  // check for SPS30 connection
 if (!sps30.probe()) 
   {
    while(1);
   }
  // reset SPS30 connection
  if (!sps30.reset()){
    
    while(1);
  }
  // Write some device info to serial
  char buf[32];
  sps30.GetSerialNumber(buf,32);
  Serial.println("Device ID: "+deviceID+"\tSPS30 Serial: "+String(buf));

  // Write metadata to the SD card
  bool metafile_exists = SD.exists(meta_filename);
  metaFile = SD.open(meta_filename, FILE_WRITE);
  // if the file opened okay, write to it:
  if (metaFile) {
    if(!metafile_exists) {
      metaFile.println(metaheader);
    }
    String metadata = String(now.timestamp(DateTime::TIMESTAMP_FULL))+';' + softwareVersion + ';' + deviceID + ';' + String(buf)+';'+sensors;
    metaFile.println(metadata);
    // close the file:
    metaFile.close();          
  } else {
    // if the file didn't open, print an error
    Serial.println("error opening metadata file");      
  } 

  // start measurement
  sps30.start();

  // wait a moment until the system stablize
  //Clean for 15 seconds
  if (PERFORMCLEANNOW) {
  // clean now
    if (sps30.clean() == true) {
      Serial.println(F("fan-cleaning started"));
    }
    else {
      Serial.println(F("Could NOT start fan-cleaning"));
    }
    delay(30000);
  }
  
  Serial.println(F("Measurements starting..."));
  
  digitalWrite(ledposition, HIGH);
  delay(200);
  digitalWrite(ledposition, LOW);
  delay(200); 
  digitalWrite(ledposition, HIGH);
  delay(200);
  digitalWrite(ledposition, LOW);
    
  delay(10);
}

void loop() 
{
  DateTime now = rtc.now();
  sps30.GetValues(&val);
  long pm1 = static_cast<long>(floor(val.MassPM1+0.5));
  long pm2 = static_cast<long>(floor(val.MassPM2+0.5));
  long pm4 = static_cast<long>(floor(val.MassPM4+0.5));
  long pm10 = static_cast<long>(floor(val.MassPM10+0.5));
  long num1 = static_cast<long>(floor(val.NumPM1+0.5));
  long num2 = static_cast<long>(floor(val.NumPM2+0.5));
  long num4 = static_cast<long>(floor(val.NumPM4+0.5));
  long num10 = static_cast<long>(floor(val.NumPM10+0.5));

  float temp = bmeSensor.readTempC();
  int humidity = static_cast<int>(round(bmeSensor.readFloatHumidity()));
  long int pressure = static_cast<long int>(round(bmeSensor.readFloatPressure()));

  month_cov = String(now.month());
  if(now.month() <10) month_cov = "0"+ month_cov;
  data_filename = deviceID + "_" + String(now.year())+month_cov+".txt";

  pmString = String(pm1)+";"+String(pm2)+";"+String(pm4)+";"+String(pm10)+';'+String(num1)+';'+String(num2)+';'+String(num4)+';'+String(num10)+';'+String(val.PartSize);
  bmeString = String(temp)+";"+String(humidity)+";"+String(pressure);
  data = String(String(counter)+';'+now.timestamp(DateTime::TIMESTAMP_FULL))+";"+pmString+";"+bmeString;
  serialdata = String(String(counter)+'\t'+now.timestamp(DateTime::TIMESTAMP_FULL))+'\t'+pmString+'\t'+bmeString;
  Serial.println(serialdata);

  // Decide whether headers need to be included
  bool datafile_exists = SD.exists(data_filename);

  dataFile = SD.open(data_filename, FILE_WRITE);
  // if the file opened okay, write to it:
  if (dataFile) {
    if(!datafile_exists) {
      dataFile.println(dataheader);
    }

    dataFile.println(data);
    // close the file:
    dataFile.close();
    digitalWrite(ledposition, HIGH);
    delay(1000);
    digitalWrite(ledposition, LOW);
          
  } else {
    // if the file didn't open, print an error and blink 10 times:
    Serial.println("error opening datafile");
    for(int i=0;i<10;i++){
      digitalWrite(ledposition, HIGH);
      delay(200);
      digitalWrite(ledposition, LOW);
      delay(200);  
    }      
  }
  ++counter;
  delay(8500);
}