Code

C++ is used for coding the microcontroller and python is used for server. Main setup initialize led display first then wifi so that led display may show connection status of wifi then lastly initializes all sensors. Then it gose to a loop witch starts sensor reading and send them to server and led display.

../_images/MAS_513.png

Library

  1. beegee-tokyo/DHT sensor library for ESPx@^1.19

  2. adafruit/Adafruit CCS811 Library@^1.1.1

  3. dfrobot/DFRobot_PH@^1.0.0

  4. adafruit/Adafruit SH110X@^2.1.10

Main

Main startup

void setup()
{
    //Set data rate
    Serial.begin(9600);
    wifi net;
    sensor sens;
    oled led;
    //initialize led display
    led.oled_init();
    //initialize wifi
    net.wifi_init();
    //initialize sensors
    sens.sensor_init();
}

Main loop

void loop()
{
    wifi net;
    oled led;
    sensor sens;
    sensor_data data;
    //Read data from DHT11 sensor
    sDHT hum_temp = sens.hum_temp_sens();
    //read data from Air quality 3 click
    sQ quality = sens.quality_sens();
    //read data from Soil sensor 1 and 2
    std::pair<int, int> soil_sens = sens.soil_sens();
    //Store sensor data in a struct
    data.soil1 = soil_sens.first;
    data.soil2 = soil_sens.second;
    data.hum = hum_temp.Hum;
    data.temp = hum_temp.Temp;
    data.quality_temp = quality.Temp;
    data.quality_eCO2 = quality.eCO2;
    data.quality_TVOC = quality.TVOC;
    //read data from pH senor
    data.ph = sens.PH_sens();
    //Print sensor data to led display
    led.oled_Display(data);
    //send data to server
    net.wifi_transfer(data);
    //Sampling delay
    delay(300000);
}

Sensor

Include and defines

#include <sensor.h>
#include <Arduino.h>
#include <DHTesp.h>
#include <SPI.h>
#include <Wire.h>
#include "Adafruit_CCS811.h"
#include "DFRobot_PH.h"
#include <EEPROM.h>

//define pinns
//Soil sensor
#define pin_soil1_power 15
#define pin_soil2_power 3
#define pin_analog PIN_A0
//DHT11
#define pin_hum_temp_input 2
//SEN0161-V2
#define pin_PH_power 0
#define pin_analog PIN_A0            //pH meter Analog output to Arduino Analog Input 0

Adafruit_CCS811 ccs;
DHTesp dht;
DFRobot_PH ph;

Sensor initialization

void sensor::sensor_init()
{
    //soil sensors
    pinMode(pin_soil1_power,OUTPUT);
    digitalWrite(pin_soil1_power, LOW);
    pinMode(pin_soil2_power,OUTPUT);
    digitalWrite(pin_soil2_power, LOW);
    //DHT11
    dht.setup(pin_hum_temp_input, DHTesp::DHT11);
    //Air quality 3 click
    ccs.begin();
    //SEN0161-V2
    pinMode(pin_PH_power,OUTPUT);
    digitalWrite(pin_PH_power, LOW);
    ph.begin();
}

Soil sensor

std::pair<int, int> sensor::soil_sens()  //Function with 2 variables
{

    digitalWrite(pin_soil1_power, HIGH);    // Turn the sensor 1 ON
    delay(10);                                                      // Allow power to settle
    int val1 = analogRead(pin_analog);      // Read the analog value form sensor
    digitalWrite(pin_soil1_power, LOW);             // Turn the sensor 1 OFF
    digitalWrite(pin_soil2_power, HIGH);    // Turn the sensor 2 ON
    delay(10);                                                      // Allow power to settle
    int val2 = analogRead(pin_analog);      // Read the analog value form sensor
    digitalWrite(pin_soil2_power, LOW);             // Turn the sensor 2 OFF
    return std::make_pair(val1, val2);              // Return analog moisture value
}

DHT11

sDHT sensor::hum_temp_sens()
{
    sDHT hum_temp;
    hum_temp.Hum = dht.getHumidity();  // Read humidity from sensor
    hum_temp.Temp = dht.getTemperature(); // Read temperature from sensor
    return hum_temp;                   // Return Both
}

Air quality 3 click

sQ sensor::quality_sens()
{
    sQ quality;
    quality.Temp = ccs.calculateTemperature(); // Calculate temperature from sensor
    ccs.readData(); // Read sensor data
    quality.TVOC = ccs.getTVOC(); // Store TVOC data
    quality.eCO2 = ccs.geteCO2(); // Store CO2 data
    return quality;               // Return data
}

SEN0161-V2

float sensor::PH_sens()
{
    digitalWrite(pin_PH_power, HIGH);       // Turn the sensor ON
    delay(1000);
    static unsigned long timepoint = millis();
    sensor sens;
    sQ temp = sens.quality_sens();
    float voltage;
    float temperature;
    float phValue;
    if(millis()-timepoint>1000U)
    {                  //time interval: 1s
        timepoint = millis();
        temperature = temp.Temp;         // read your temperature sensor to execute temperature compensation
        voltage = analogRead(pin_analog)/1024.0*3300;  // read the voltage
        //float phValue = ph.readPH(voltage,temperature);  // convert voltage to pH with temperature compensation

        float acidVoltage    = 2032.44;    //buffer solution 4.0 at 25C
        float neutralVoltage = 1500.0;     //buffer solution 7.0 at 25C
        float slope = (7.0-4.0)/((neutralVoltage-1500.0)/3.0 - (acidVoltage-1500.0)/3.0);  // two point: (_neutralVoltage,7.0),(_acidVoltage,4.0)
        float intercept =  7.0 - slope*(neutralVoltage-1500.0)/3.0;
        phValue = slope*(voltage-1500.0)/3.0+intercept;  //y = k*x + b
    }
    ph.calibration(voltage,temperature);           // calibration process by Serail CMD
    digitalWrite(pin_PH_power, LOW);        // Turn the sensor OFF
    return phValue;
}

Header

#ifndef SENSOR_H
#define SENSOR_H
#include <DHTesp.h>
#include "Adafruit_CCS811.h"
//Global struct
struct sDHT
{
    float Temp;
    float Hum;
};
struct sQ
{
    float Temp;
    float TVOC;
    float eCO2;
};
struct sensor_data
{
    int soil1;
    int soil2;
    float hum;
    float temp;
    float quality_temp;
    float quality_TVOC;
    float quality_eCO2;
    float ph;
};

class sensor
{
    public:
    void sensor_init();
    std::pair<int, int> soil_sens();
    sDHT hum_temp_sens();
    sQ quality_sens();
    float PH_sens();
};

#endif // SENSOR_H

Oled

Include and defines

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>
#include <oled.h>
#include <Arduino.h>


#define SCREEN_WIDTH 128 //Screen resolution
#define SCREEN_HEIGHT 64 //Screen resolution
#define mosi_pin 13 //I2C MOSI
#define sclk_pin 14 //I2C SCLK
#define dc_pin 16 //data command
#define res_pin 12 //reset
#define cs_pin 1 //chip select


Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT,mosi_pin, sclk_pin, dc_pin, res_pin, cs_pin);

Led display initialization

Wifi startup display

void oled::oled_wifi_start(int count)
{
//print "." each secound while waiting for wifi conection as a loading screen
display.setCursor(count*5, 0);
display.setTextSize(0.5);
display.setTextColor(SH110X_WHITE);
display.println(F("."));
display.display();
}
    void oled::oled_wifi_connect(const IPAddress& ip)
{
//Print microcontrollers ip address
display.clearDisplay();
display.setTextSize(0.5);
display.setTextColor(SH110X_WHITE);
display.setCursor(0, 0);
display.println(F("Connected to WiFi"));
display.println(F("IP Address: "));
display.println(ip);
display.display();
}

Sensor display

void oled::oled_Display(const sensor_data& data)
{
//Print sensor data to display
//First column
display.clearDisplay();
display.setTextSize(0.3);
display.setTextColor(SH110X_WHITE);
display.setCursor(0, 0);
display.println(F("S_1: "));
display.setCursor(30, 0);
display.println(data.soil1);
display.setCursor(0, 18);
display.println(F("T_i: "));
display.setCursor(30, 18);
display.println(data.temp);
display.setCursor(0, 36);
display.println(F("Hum: "));
display.setCursor(30, 36);
display.println(data.hum);
display.setCursor(0, 54);
display.println(F("eCO2: "));
display.setCursor(30, 54);
display.println(data.quality_eCO2);
//Secound column
display.setCursor(64, 0);
display.println(F("S_2: "));
display.setCursor(94, 0);
display.println(data.soil2);
display.setCursor(64, 18);
display.println(F("T_o: "));
display.setCursor(94, 18);
display.println(data.quality_temp);
display.setCursor(64, 36);
display.println(F("TVOC: "));
display.setCursor(94, 36);
display.println(data.quality_TVOC);
display.setCursor(64, 54);
display.println(F("PH: "));
display.setCursor(94, 54);
display.println(data.ph);
display.display();
}

Sensor display

#include <sensor.h>
#include <string>
#include <IPAddress.h>
class oled
{
    public:
    void oled_init();
    void oled_Display(const sensor_data& data);
    void oled_wifi_start(int count);
    void oled_wifi_connect(const IPAddress& ip);
};

Wifi

Include and defines

#include <wifi.h>
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <oled.h>

const char* ssid     = "GL-MT300N-V2-fe9"; //wifi name
const char* password = "goodlife";  //wifi password
const char* host     = "http://192.168.8.130:8080/endpoint"; //server ip and port

oled led;

Wifi initialization

void wifi::wifi_init()
{
    WiFi.begin(ssid,password); //connect to wifi
    int count = 0;
    while (WiFi.status() != WL_CONNECTED) //wait for connection
    {
        delay(1000);
        led.oled_wifi_start(count); //display loading screen
        count ++;
    }

    IPAddress localIP = WiFi.localIP(); //read microcontrollers ip adress
    led.oled_wifi_connect(localIP); //display microcontrollers new ip
}

Wifi connection

void wifi::wifi_transfer(const sensor_data& data)
{
    if (WiFi.status() == WL_CONNECTED)
    {
        WiFiClient client;
        HTTPClient http;
        // Set the target server address
        http.begin(client, host);
        // Set the content type header for JSON data
        http.addHeader("Content-Type", "application/json");
        // Your JSON data to send
        String jsonData = "{\"Soil1\": "+String(data.soil1)+", \"Soil2\": "+String(data.soil2)+", \"Hum\": "+String(data.hum)+", \"Temp inn\": "+String(data.temp)+", \"Temp out\": "+String(data.quality_temp)+", \"eCO2\": "+String(data.quality_eCO2)+", \"TVOC\": "+String(data.quality_TVOC)+", \"PH\": "+String(data.ph)+"}";
        // Send a POST request with the JSON data
        int httpResponseCode = http.POST(jsonData);
        // Check the response code
        if (httpResponseCode > 0)
        {
            String response = http.getString();
            Serial.println("HTTP Response: " + response);
        }
        else
        {
            Serial.println("HTTP Error: " + String(httpResponseCode));
        }
        http.end();
    }
}

Header

#include <sensor.h>
class wifi
{
    public:
    void wifi_init();
    void wifi_transfer(const sensor_data& data);
};

Server

from http.server import BaseHTTPRequestHandler, HTTPServer
import json
import csv
import os
from datetime import datetime

class RequestHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        #get sensor data
        content_length = int(self.headers['Content-Length'])
        post_data = self.rfile.read(content_length)
        json_data = json.loads(post_data.decode('utf-8'))
        # get current time
        current_time = str(datetime.now())
        #print data recived
        print("Received JSON data:")
        print(json_data)
        #add time stamp to sensor data
        json_data['time'] = current_time

        self.save_to_csv(json_data)

        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"Data received successfully!")

    def save_to_csv(self, data):
        # choose path to save CSV
        csv_file_path = 'C:/python_venvs/MAS512_2023_AUTUMN/Assignment_1/server/data.csv'
        # Check if the CSV file already exists; if not, create it and write the header
        if not os.path.isfile(csv_file_path):
            with open(csv_file_path, 'w', newline='') as csv_file:
                csv_writer = csv.DictWriter(csv_file, fieldnames=data.keys())
                csv_writer.writeheader()
        # Append the received data to the CSV file
        with open(csv_file_path, 'a', newline='') as csv_file:
            csv_writer = csv.DictWriter(csv_file, fieldnames=data.keys())
            csv_writer.writerow(data)


if __name__ == '__main__':
    server_address = ('192.168.8.236', 8080)  # server ip and port
    httpd = HTTPServer(server_address, RequestHandler)
    print('Server running on port 8080...')
    httpd.serve_forever()