The CLEAN Fixed Monitor Prototype Firmware

The firmware of the mobile prototype was developed for the Microchip ATMega2560 microcontroller embedded on an Arduino Mega platform. The code was developed in the C/C++ programming language using the Arduino Framework, available at the PlatformIO IDE for the Microsoft Visual Studio Code editor (VSCode). For more details on programming the Arduino MEGA, please refer to The Arduino MEGA Programming Guide.

The code consists of two parts; one for setup and the other for the main loop. The current version of the firmware has three main functionalities which are in accordance with the hardware of the monitor, as follows:

  • Reading the gas samples from the serial interface of the SPEC sensors and the analog output signal from the Alphasense sensors
  • Storing the gas concentration information into an SD card
  • Sending the gas concentration information to an HTTP server through the ESP8266 microcontroller

A detailed description of the hardware components of this prototype and the way they connect to the Arduino platform can be found at The CLEAN Fixed Monitor Prototype and The CLEAN Fixed Monitor Prototype Mounting Guide.

Figure 1. Flowchart of the firmware programmed for the Arduino MEGA microcontroller

Figure 1 shows a flowchart of the code programmed for the ATMega2560. As with every program of the Arduino Framework the code is executed in two main functions: setup() and loop(). In the current version of the firmware, the setup() prepares the communication between the external modules (i.e.: RTC, Wi-Fi, SD Card, and SPEC sensors) and the microcontroller. The loop() function first verifies if the ESP8266 hasn’t been responsive for some time, in which case the Arduino will send a RESET signal to the ESP8266. The rest of the function is divided into three sections that are executed periodically and control the above-mentioned functionalities. Next, we describe the different sections of the code.

The firmware includes another function separate from the main flow of the program, which is serialEvent3(). This function handles interrupt events from the UART3 and is executed whenever data is received at the serial buffer.

Firstly, identify the device and its sensors

One crucial part of the firmware is the identification of the device and the sensors connected to it. These identifications must match the ones configured in the RENOVAr server, and will be used by the backend application to update the databases. First, you must set the Device id, as follows:

unsigned long Device::id = <THE NUMBER OF YOUR DEVICE>;

After that, you can define an enum to storage the IDs of the sensors, like the one we used in our code (see below). Each value of the enum represents a variable at the RENOVAr application.

enum iotId_e    {   CO_ID       = <The ID of the CO gas concentration read from SPEC sensor>, 
                    NO2_ID      = <The ID of the NO2 gas concentration read from SPEC sensor>, 
                    O3_ID       = <The ID of the O3 gas concentration  read from SPEC sensor>,
                    SO2_ID      = <The ID of the SO2 gas concentration  read from SPEC sensor>, 
                    TEMP_ID     = <The ID of the temperature variable>,
                    RHUM_ID     = <The ID of the relative humidity variable>,
                    ALPHA_CO_ID = <The ID of the CO gas concentration read from Alphasense sensor>,
                    ALPHA_NO2_ID= <The ID of the NO2 gas concentration read from Alphasense sensor>,
                    ALPHA_OX_ID = <The ID of the O3 gas concentration read from Alphasense sensor>,
                    ALPHA_SO2_ID = <The ID of the SO2 gas concentration read from Alphasense sensor>,
                    ALPHA_H2S_ID = <The ID of the H2S gas concentration read from Alphasense sensor>,
                    ALPHA_OX2_ID = <The ID of the O3 gas concentration read from Alphasense sensor>,
};

Setup

This function prepares the communication between the external modules and the microcontroller. The code of this function is shown below. Firstly, the program sets up the serial ports that will be used for communication with the SPEC sensors (RS485_2), and the ESP8266 (Serial3). Each serial port is initialized at a baud rate previously defined in the code, as will be described later. The serial port UART0 (Serial) is used for debugging. The RS485_2 implements a serial interface to the RS485 bus that connects the sensors to the microcontroller. The digital pin D13 is used in this version as a voltage reference for the level shifter used to interfacing the ESP8266, but it could be ignored if using another source of 5 V. The setup() function also restarts the ESP8266 microcontroller through the object espIoT, initializes the interfaces for the SD card and RTC modules, and sets the system time. Table 1 describes the objects, constants, and functions that are used in this part of the code.

void setup()
{  
  Serial.begin(9600);
  RS485_2.setPins(RS485_DEFAULT_TX1_PIN, (int)MASTERPIN, (int)MASTERPIN);
  RS485_2.begin(DEFAULT_BAUDRATE);
  Serial3.begin(9600UL);

  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);
  
  espIoT.restart();

  SD.begin(CHIPSEL_PIN);
  
  Rtc.Begin();
  if (!Rtc.GetIsRunning())
  {
    print_debug(F("WARNING: RTC was not actively running, starting it now."));
    Rtc.SetIsRunning(true);
  }
  #ifdef DS3231
    // Reset the DS3231 RTC status in case it was wrongly configured
    Rtc.Enable32kHzPin(false);
    Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeNone);
  #endif
  time_t now = Rtc.GetDateTime().Epoch32Time();
  print_debug(F("Time is: %lu\n"), now);
  setSyncProvider(sync_time);
  setSyncInterval(5*SECS_PER_MIN);
  print_debug(F("timestamp: %lu"), now);
}

Table 1. Objects, constants, and functions used in the setup function

NameDescription
Serial
Serial2
Serial3
These objects, which are declared in the Arduino core, represent the UART Ports of the microcontroller. For more information, please refer to the Arduino Documentation. The objects are initialized by the begin() function, which receives the baud rate of the serial communication.
RS485_2This object establishes an interface for controlling the RS485 bus that connects the gas sensors. It is declared in the rs-485 library as an instance of RS485Class. The object is initialized with the method begin(), similar to the Serial objects. For more information on this library refer to The Hardware Interfaces Package Documentation.
RS485Class::setPins(int txPin, int dePin, int rePin)The function setPins() defines the digital pins of the microcontroller that will be used in the RS485 hardware interface. Those pins are the Tx pin, for data transmission, and the DE, RE pins that control the data flow (for reading or for writing). The pins used for the current version of the firmware are:
txPin - RS485_DEFAULT_TX1_PIN. Defined in the rs-485 library as D18
dePin, rePin - MASTERPIN. Defined as D10 within the enumerate en_rs485_pinout defined in the sensor library.
espIoTThis is an object of the class ESPSerialInterface defined in the serial-internet-interface library. This object controls the communication with the ESP8266 connected to the UART3 of the Arduino. The function restart() sends a RESET signal to the ESP8266.
The object espIoT is defined previously in the code as follows:
ESPSerialInterface espIoT(&Serial3);
SDThis is an object of SDClass declared in the Arduino core for interfacing SD Card modules. It is initialized with a begin() method that receives the digital pin that connects to the CS pin of the module. The digital pin used for the current version of the hardware and the firmware is defined in the file hardstorage.h as follows:
#define CHIPSEL_PIN 53
RtcThis is an object of the class RtcDS3231 defined in the library Rtc by Makuna. The object Rtc is declared previously in the code as follows:
#define I2C Wire
RtcDS3231<TwoWire> Rtc(I2C);

Rtc is initialized with a begin() and afterward the code in setup() checks if the module is running by a call to the method GetIsRunning(). In case it isn’t running the code calls the method SetIsRunning(true). The code also resets the DS3231 RTC status in case it was wrongly configured by the following code:
#ifdef DS3231
Rtc.Enable32kHzPin(false);
Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeNone);
#endif

For more information on this library please refer to the library documentation.
Rtc.GetDateTime().Epoch32Time()This method of the class RtcDS3231 gets the current time from the RTC module in UNIX format.
setSyncProvider(getExternalTime getTimeFunction)

setSyncInterval(time_t interval)
These are functions from the Time library that allow for automatic synchronization of the system time with a clock source. In this case, the source used for synchronization is the RTC module. The function setSyncProvider()receives a pointer to a function that returns the current timestamp as a time_t variable. In our case, this function is sync_time(), declared previously in the code as shown below:
RTCDS3231Interface My_RTCInterface(&Rtc);
time_t sync_time() { return RTCDriver<RtcDS3231<TwoWire>>::sync_time_from_RTC(&My_RTCInterface); }

For more information please refer to The Drivers Package Documentation.
The function setSyncInterval()receives the period for syncing the system time. In our case, it was set as 5 seconds.

Serial3 Interruption

The code of the function that handles the UART3 interruption is shown below. Every time data is available in the input buffer, the object espIoT will parse the String received. For more information on that, please refer to The IoT Package Documentation.

void serialEvent3()
{
  if(Serial3.available())
  {
    espIoT.parse_esp_string(Serial3.readStringUntil(';'));
  }
}

The main loop

The main loop is executed within the function loop() of the Arduino framework, as shown in the code below. This function is responsible for handling the four main functionalities described previously, following a sequence as illustrated in Figure 1.

void loop()
{
  static uint32_t mLastTime = 0, mLastTimeuSD = 0, mLastTimeHTTP = 0;
  static bool sd_ok = false;

  espIoT.watch_dog();
  
  if((!TimeDriver::_already_up_to_date()))  espIoT.request_time();
  if((!My_RTCInterface.is_up_to_date()))    
    RTCDriver<RtcDS3231<TwoWire>>::update_rtc(&My_RTCInterface, now());
  
  if((millis() - mLastTimeuSD) >= uSD_TIME_MSEC)  // Counts for sample time
  {
    mLastTimeuSD = millis();
    static uint8_t data_index_uSD = 0;
    Vars[data_index_uSD]->sense(data[data_index_uSD]);
    
    char* filename = (char*)malloc(strlen_P(filenames[data_index_uSD])+1);
    strcpy_P(filename, filenames[data_index_uSD]);
    if(open_file(filename)) 
      sd_ok = !save_to_file<sensorData<Variable>>(data[data_index_uSD], filename);
    else  SD.begin(CHIPSEL_PIN);
    free(filename);
  
    data_index_uSD = (data_index_uSD >= numSensors-1) ? 0 : data_index_uSD + 1;
  }
  
  if((millis() - mLastTimeHTTP) >= HTTP_TIME_SEC*1000)  // Counts for sample time
  {
    mLastTimeHTTP = millis();

    static uint8_t data_index_iot = 0;
    if(!sd_ok)
    {
      if(!espIoT.send_http_post((DataContainer*)data[data_index_iot]))  print_debug("Couldn't post!");
      data_index_iot = (data_index_iot >= numSensors-1) ? 0 : data_index_iot + 1;
    }
  }
  
  if ((millis() - mLastTime) >= SAMPLE_TIME_SEC*1000)
  {
    mLastTime = millis();
    static uint8_t index  = 0;
    if(index <= ALPH_H2S_ID)  Alpha_H2S.readConc(Vars[index]->getSmoother());
    else if(index < SPEC_ID) {
      static uint8_t alphaComp_index = 0;
      alphaCompSensors[alphaComp_index]->readConc_Comp(Vars[ALPH_COMP_ID+alphaComp_index]->getSmoother(),Vars[TEMP_ID]->getValue());
      alphaComp_index = (alphaComp_index >= numAlpha-2) ? 0 : alphaComp_index + 1;
    }
    else  {
      static uint8_t spec_index = 0;
      specSensors[spec_index]->readSensor(Vars[spec_index+SPEC_ID]->getSmoother(),Vars[TEMP_ID]->getSmoother(), Vars[HUMD_ID]->getSmoother());
      spec_index = (spec_index >= numSpec-1) ? 0 : spec_index + 1;
    }
    index = (index >= TEMP_ID-1) ? 0 : index + 1;
  }
}

The code of loop()is divided into three sections that execute the actions related to each functionality. Each section is executed periodically controlled by the variables mLastTime, mLastTimeuSD, and mLastTimeHTTP. Those variables store the timestamp when each functionality was last executed. The constants uSD_TIME_MSEC, HTTP_TIME_SEC, and SAMPLE_TIME_SEC, represent the periods of execution of each functionality, as summarized in Table 2. In every loop cycle, the object espIoT calls its method watch_dog() in order to check if any request sent to the ESP8266 has experienced a timeout. In case it has, the ESP8266 will be restarted. The loop() function also checks if the microcontroller has updated its time from an NTP server. In case it hasn’t, a request is sent to the ESP8266. For more information on the connection and communication between the Arduino MEGA and the ESP8266, please refer to The CLEAN Fixed Monitor Prototype, The ESP8266 Firmware, and The IoT Package Documentation.

Likewise, the code of the main() function checks if the RTC module has been updated with the internet time. In case it hasn’t, it calls the method update_rtc() from the class RTCDriver. The class RTCDriver is a template class for controlling the functionalities related to an RTC module, like updating its time, for instance. The method update_rtc() receives a pointer to an instance of the class RTCInterface, which creates an interface to the hardware of an RTC module. RTCInterface is an abstract class, so in order to implement the actual interface to an RTC module, an instance should be inherited from it. In our case, this instance was implemented in the object My_RTCInterface, which is declared previously in the code as shown below.

Figure 2. Modules and interfaces used for controlling and interfacing the RTC
class RTCDS3231Interface : public RTCInterface<RtcDS3231<TwoWire>>
{
public:
  RTCDS3231Interface(RtcDS3231<TwoWire>* rtc) : 
    RTCInterface<RtcDS3231<TwoWire>>(rtc) {}
  
  virtual void set_time(time_t t)
  {
    RtcDateTime dt;
    dt.InitWithEpoch32Time(t);
    _rtc->SetDateTime(dt);
  }

  virtual time_t get_time() { return _rtc->GetDateTime().Epoch32Time(); }
};

#define I2C Wire
RtcDS3231<TwoWire> Rtc(I2C);
RTCDS3231Interface My_RTCInterface(&Rtc);

As can be observed, the class RTCDS3231Interface inherits from RTCInterface<RtcDS3231<TwoWire>>. When the object of that class is declared, it receives in its constructor a reference to Rtc, which represents the actual DS3231 module, as was described previously. Summarizing, the Rtc object, represents the DS3231 module; the My_RTCInterface object interfaces the RTC module with our code; and the class RTCDriver controls the module’s functionalities within our code. That relationship is represented in the diagram of Figure 2. For more details please refer to The Hardware Interfaces Documentation and The Drivers Documentation.

Table 2. Constants and variables used for controlling the execution of each functionality in the firmware

FunctionalityPeriodPeriod constant in the codeDefinition of the period constant in the codeControl variable
Storing data to SD Card5 suSD_TIME_MSECconst uint32_t uSD_TIME_MSEC = 5000;
Defined in main.cpp
mLastTimeuSD
Sending data for HTTP POST75 sHTTP_TIME_SECconst uint32_t HTTP_TIME_SEC = 75;
Defined in main.cpp
mLastTimeHTTP
Reading gas sensors5 sSAMPLE_TIME_SECconst uint8_t SAMPLE_TIME_SEC = 5;
Defined in main.cpp
mLastTime
Reading the gas sensors

The section of code that reads the gas sensors is shown below:

if ((millis() - mLastTime) >= SAMPLE_TIME_SEC*1000)
{
  mLastTime = millis();
  static uint8_t index  = 0;
  if(index <= ALPH_H2S_ID) Alpha_H2S.readConc(Vars[index]->getSmoother());
  else if(index < SPEC_ID) {
    static uint8_t alphaComp_index = 0;
  alphaCompSensors[alphaComp_index]->readConc_Comp(Vars[ALPH_COMP_ID+alphaComp_index]->getSmoother(), Vars[TEMP_ID]->getValue());
    alphaComp_index = (alphaComp_index >= numAlpha-2) ? 0 : alphaComp_index + 1;
  }
  else  {
    static uint8_t spec_index = 0;
  specSensors[spec_index]->readSensor(Vars[spec_index+SPEC_ID]->getSmoother(),Vars[TEMP_ID]->getSmoother(), Vars[HUMD_ID]->getSmoother());
    spec_index = (spec_index >= numSpec-1) ? 0 : spec_index + 1;
  }
  index = (index >= TEMP_ID-1) ? 0 : index + 1;
}

The code first checks if the time for reading the sensors has elapsed and updates the variable mLastTime. This section of the code basically iterates in the arrays specSensors and alphaCompSensors in order to get the gas concentration reading of each sensor, as well as the temperature and the relative humidity. For more information on the sensors used in this prototype please refer to The CLEAN Fixed Monitor Prototype.

The variableindex is the iterator for the different variables as alphaComp_index and spec_index are iterators for the arrays specSensors and alphaCompSensors. The constants ALPH_H2S_ID

The variable Alpha_H2S is an object of the class AlphaSenseISB, which represents an Alphasense sensor interfaced through an Individual Sensor Board, for signal conditioning. Alpha_H2S is the only Alphasense sensor that is modeled in our code as an AlphaSenseISB object, because a compensation algorithm for this sensor was not implemented. It is defined previously in the code as shown below. The parameters for the constructor of these objects are explained in detail in The Sensor Package Documentation.

const uint8_t numAlpha = 6;
AlphaSenseISB Alpha_H2S(ALPHA_H2SB4_ID, H2SB, Sen_H2SB4_163480136, WEe_H2SB4_163480136, WAe_H2SB4_163480136, WEo_H2SB4_163480136, WAo_H2SB4_163480136, ALPHA_H2S_WPIN, ALPHA_H2S_APIN);

The array alphaCompSensors is defined previously in the code as an array of pointers to the class AlphaSenseCompensator, as shown below. The class AlphaSenseCompensator represents an Alphasense sensor interfaced by an Individual Sensor Board, which output signal is compensated for the effect of temperature and relative humidity. For more information refer to The Sensor Package Documentation.

AlphaSenseCompensator Alpha_NO2Comp(comp_algorithm_3, ALPHA_NO2B43F_ID, NO2B, Sen_NO2B43F_202401512, WEe_NO2B43F_202401512, WAe_NO2B43F_202401512, WEo_NO2B43F_202401512, WAo_NO2B43F_202401512, ALPHA_NO2_WPIN, ALPHA_NO2_APIN);

AlphaOXCompensator Alpha_O31Comp(&Alpha_NO2Comp, comp_algorithm_1, ALPHA_OXB431_ID1, OXB, Sen_OXB431_204660048, WEe_OXB431_204660048, WAe_OXB431_204660048, WEo_OXB431_204660048, WAo_OXB431_204660048, ALPHA_OX_WPIN, ALPHA_OX_APIN);

AlphaOXCompensator Alpha_O32Comp(&Alpha_NO2Comp, comp_algorithm_1, ALPHA_OXB431_ID2, OXB, Sen_OXB431_204660049, WEe_OXB431_204660049, WAe_OXB431_204660049, WEo_OXB431_204660049, WAo_OXB431_204660049, ALPHA_OX2_WPIN, ALPHA_OX2_APIN);

AlphaSenseCompensator* alphaCompSensors[numAlpha-1] =
  {
    &Alpha_NO2Comp,
    new AlphaSenseCompensator(comp_algorithm_2, ALPHA_SO2B4_ID  , SO2B, Sen_SO2B4_164702360,    WEe_SO2B4_164702360,    WAe_SO2B4_164702360,    WEo_SO2B4_164702360,    WAo_SO2B4_164702360,    ALPHA_SO2_WPIN, ALPHA_SO2_APIN),
    new AlphaSenseCompensator(comp_algorithm_1, ALPHA_COB4_ID   , COB,  Sen_COB4_162482658,     WEe_COB4_162482658,     WAe_COB4_162482658,     WEo_COB4_162482658,     WAo_COB4_162482658,     ALPHA_CO_WPIN,  ALPHA_CO_APIN ),
    &Alpha_O31Comp,
    &Alpha_O32Comp
  };

The array specSensors is also defined previously in the code as an array of pointers to the classspecDGS_sensor, as shown below. The class specDGS_sensor represents a digital sensor from SPEC Sensor and is defined in the sensor library. For more information refer to The Sensor Package Documentation.

const uint32_t CO_SERIAL  = 12651;
const uint32_t O3_SERIAL  = 20926;
const uint32_t SO2_SERIAL = 21209;
const uint32_t NO2_SERIAL = 10323;

const uint8_t numSpec = 4;
specDGS_sensor* specSensors[numSpec] = 
  { new specDGS_sensor(new specDGS_RS485(DE_S1PIN, RE_S1PIN), &RS485_2, CO_SERIAL ),
    new specDGS_sensor(new specDGS_RS485(DE_S2PIN, RE_S2PIN), &RS485_2, O3_SERIAL ),
    new specDGS_sensor(new specDGS_RS485(DE_S3PIN, RE_S3PIN), &RS485_2, SO2_SERIAL),
    new specDGS_sensor(new specDGS_RS485(DE_S4PIN, RE_S4PIN), &RS485_2, NO2_SERIAL)
  };

The code above creates an array of four pointers to the class specDGS_sensor called specSensors. Each instance of that class receives a pointer to an instance of the class serial_sensor, a pointer to an instance of the class Stream and the sensor ID. In this version of the firmware, the instance of serial_sensor is a pointer to the class specDGS_RS485, which inherits from serial_sensor. Refer to The Sensor Package Documentation for further details. The pointer to Stream is the serial interface; in this case, it’s a reference to the object RS485_2. The sensors IDs are the last five digits of the factory barcode.

The method readSensor() of the class specDGS_sensor reads the gas concentration, temperature, and relative humidity data from each SPEC sensor and stores them into a correspondent item of the array Vars. Each item in Vars is related to a sensor by the variable sensores_index. Vars is an array of pointers to the class Variable, declared previously in the code as shown below. sensores_index, on the other hand, is initialized by the constant SPEC_ID, which indicates the position of the first gas sensor in the array Vars. TEMP_ID and HUMD_ID are the positions of the temperature and relative humidity sensors respectively.

Storing the data into an SD card

The section of code that stores the data into an SD card is shown below:

if((millis() - mLastTimeuSD) >= uSD_TIME_MSEC)  // Counts for sample time
{
  mLastTimeuSD = millis();
  static uint8_t data_index_uSD = 0;
  Vars[data_index_uSD]->sense(data[data_index_uSD]);
    
  char* filename = (char*)malloc(strlen_P(filenames[data_index_uSD])+1);
  strcpy_P(filename, filenames[data_index_uSD]);
  if(open_file(filename)) 
    sd_ok = !save_to_file<sensorData<Variable>>(data[data_index_uSD], filename);
  else  SD.begin(CHIPSEL_PIN);
  free(filename);
  
  data_index_uSD = (data_index_uSD >= numSensors-1) ? 0 : data_index_uSD + 1;
}

The code first checks if the time storing the data has elapsed and updates the variable mLastTimeuSD. This section of the code basically transfers the data of each Variable to a sensorData object that will subsequently be used to storing the information of each variable. The method used for transferring the information in Variable to sensorData is sense(), which receives a pointer to sensorData. In this case, the function receives an item of the array data, which is an array of pointers to sensorData<Variable>. This array is defined previously in the code as shown below. Once the data have been transferred to the instance of sensorData, it is stored into a file in the SD card. For more information, please refer to The Data Package Documentation.

sensorData<Variable>* data[numSensors] = 
{
  new sensorData<Variable>(Vars[ALPH_H2S_ID]),
  new sensorData<Variable>(Vars[ALPH_COMP_ID]),
  new sensorData<Variable>(Vars[ALPH_COMP_ID+1]),
  new sensorData<Variable>(Vars[ALPH_COMP_ID+2]),
  new sensorData<Variable>(Vars[ALPH_COMP_ID+3]),
  new sensorData<Variable>(Vars[ALPH_COMP_ID+4]),
  new sensorData<Variable>(Vars[SPEC_ID]),
  new sensorData<Variable>(Vars[SPEC_ID+1]),
  new sensorData<Variable>(Vars[SPEC_ID+2]),
  new sensorData<Variable>(Vars[SPEC_ID+3]),
  new sensorData<Variable>(Vars[TEMP_ID]),
  new sensorData<Variable>(Vars[HUMD_ID])
};

const char ISB_NO2  [] PROGMEM  = "ISB_NO2.csv\0" ;
const char ISB_SO2  [] PROGMEM  = "ISB_SO2.csv\0" ;
const char ISB_CO   [] PROGMEM  = "ISB_CO.csv\0"  ;
const char ISB_O3   [] PROGMEM  = "ISB_O3.csv\0"  ;
const char ISB_O32  [] PROGMEM  = "ISB_O32.csv\0" ;
const char ISB_H2S  [] PROGMEM  = "ISB_H2S.csv\0" ;
const char CO_log   [] PROGMEM  = "CO_log.csv\0"  ;
const char O3_log   [] PROGMEM  = "O3_log.csv\0"  ;
const char SO2_log  [] PROGMEM  = "SO2_log.csv\0" ;
const char NO2_log  [] PROGMEM  = "NO2_log.csv\0" ;
const char TMP_log  [] PROGMEM  = "TMP_log.csv\0" ;
const char RH_log   [] PROGMEM  = "RH_log.csv\0"  ;

const char* filenames[numSensors] = { ISB_H2S, ISB_NO2, ISB_SO2, ISB_CO, ISB_O3, ISB_O32,
                                      CO_log, O3_log, SO2_log, NO2_log,
                                      TMP_log, RH_log
                                    };
Sending data for an HTTP POST

The section of code that sends the data to the ESP8266 for posting into an HTTP server is shown below:

if((millis() - mLastTimeHTTP) >= HTTP_TIME_SEC*1000)  // Counts for sample time
{
  mLastTimeHTTP = millis();

  static uint8_t data_index_iot = 0;
  if(!sd_ok)
  {
    if(!espIoT.send_http_post((DataContainer*)data[data_index_iot]))  print_debug("Couldn't post!");
    data_index_iot = (data_index_iot >= numSensors-1) ? 0 : data_index_iot + 1;
  }
}

Like the other sections of code, this section first checks if the time for sending the data has elapsed and updates the variable mLastTimeHTTP. After that, it iterates in the array data in order to send the information acquired for each Variable. If the last sample was successfully written to the SD card, the espIoT object will send a JSON string containing the information to be posted by the ESP8266. The method send_http_post() receives a pointer to DataContainer. Since the class sensorData inherits from DataContainer, each item in data can be converted to a pointer to DataContainer. For more information refer to The Data Package Documentation.

Translate