Md. Khairul Alam
Published © MIT

IoT Healthy Toilet

A smart toilet that maintains a healthy environment analyzing air quality data using machine learning.

AdvancedFull instructions provided3 days4,855
IoT Healthy Toilet

Things used in this project

Hardware components

AWS IoT EduKit
Amazon Web Services AWS IoT EduKit
Core2 AWS IoT EduKit will collect all the sensors data and publish it securely to AWS IoT Core. It also responses based on the message receives from AWS IoT Core.
×1
DHT11 Temperature & Humidity Sensor (3 pins)
DHT11 Temperature & Humidity Sensor (3 pins)
This sensor will collect the temperature and humidity from the environment (e.g. from the toilet in our case). You can use Grove DHT11 sensor to directly connect it to Core2 Kit using Grove cable.
×1
Seeed Studio MICS6814 Multichannel Analog Gas Sensor
This multichannel gas sensor will collect important air quality data like Carbon monoxide, ammonia, nitrogen dioxide, and methane. If you use Grove I2C multi-channel gas sensor then you need to modify the code accordingly. I use analog MICS6814 with an I2C A/D converter module.
×1
Adafruit ADS1115 16-Bit ADC - 4 Channel with Programmable Gain Amplifier
As the MICS6814 multichannel gas sensor provides data through three analog channels this 4 channel analog to digital converter is used to collect the data from all the channels. Edu Kit communicates using the I2C bus to this AD converter. I modified I2C ADS1115 library for Core2.
×1
Sulfur Dioxide SO2 Qualitative Detection Sensor Module 2SH12
Sulfur dioxide is an important air parameter for the toilet. I used a 2SH12 sensor for reading sulfur dioxide in this project. This sensor provides analog data and I connected it to ADS1115 for reading the using Core2.
×1
Seeed Studio Grove - Universal 4 Pin Cable
As Edu Kit has built-in Grove connectors, it is easy to connect all the sensors to the Core2 EduKit using grove cables. I used Port A of Core2 to connect with the ADS1115 AD converter module. Port A supports I2C. DHT11 is connected to Port B and used GPIO 26 for data transmission.
×1
NodeMCU ESP8266 Breakout Board
NodeMCU ESP8266 Breakout Board
The exhaust fan of the toilet is controlled by NodeMCU with a Relay module. NodeMCU subscribes to a specific topic to receive the fan control signal generated by AWS IoT Analytics based on the air quality data received from the toilet.
×1
Gravity: Digital 5A Relay Module
DFRobot Gravity: Digital 5A Relay Module
Relay is used to control the high-power exhaust fan of the toilet from NodeMCU.
×1
Axial Fan, 12 VDC
Axial Fan, 12 VDC
This fan is used to demonstrate the exhaust fan operation of the toilet.
×1

Software apps and online services

Visual Studio Code Extension for Arduino
Microsoft Visual Studio Code Extension for Arduino
Visual Studio Code is used to host PlatformIO.
PlatformIO IDE
PlatformIO IDE
Visual Studio Code Extension for developing firmware for Core2 AWS IoT EduKit.
AWS IoT
Amazon Web Services AWS IoT
AWS IoT Core lets you connect IoT devices to the AWS cloud without the need to provision or manage servers. AWS IoT Core can support billions of devices and trillions of messages, and can process and route those messages to AWS endpoints and to other devices reliably and securely. With AWS IoT Core, your applications can keep track of and communicate with all your devices, all the time, even when they aren’t connected.
AWS S3
Amazon Web Services AWS S3
Amazon S3 or Amazon Simple Storage Service is a service offered by Amazon Web Services that provides object storage through a web service interface. Amazon S3 uses the same scalable storage infrastructure that Amazon.com uses to run its global e-commerce network.
AWS IAM
Amazon Web Services AWS IAM
AWS Identity and Access Management (IAM) enables you to manage access to AWS services and resources securely. Using IAM, you can create and manage AWS users and groups, and use permissions to allow and deny their access to AWS resources.
AWS SNS
Amazon Web Services AWS SNS
Amazon Simple Notification Service is a notification service provided as part of Amazon Web Services. It provides a low-cost infrastructure for the mass delivery of messages (e.g. SMS, Email), predominantly to mobile users.
AWS Lambda
Amazon Web Services AWS Lambda
AWS Lambda is a serverless compute service that lets you run code without provisioning or managing servers, creating workload-aware cluster scaling logic, maintaining event integrations, or managing runtimes. With Lambda, you can run code for virtually any type of application or backend service - all with zero administration. Just upload your code as a ZIP file or container image, and Lambda automatically and precisely allocates compute execution power and runs your code based on the incoming request or event, for any scale of traffic.
Amazon Web Services - AWS Analytics
AWS IoT Analytics is a fully managed service that operationalizes analyses and scales automatically to support up to petabytes of IoT data. With AWS IoT Analytics, you can analyze data from millions of devices and build fast, responsive IoT applications without managing hardware or infrastructure.
Amazon Web Services - Amazon SageMaker
Amazon SageMaker is a cloud machine-learning platform that was launched in November 2017. SageMaker enables developers to create, train, and deploy machine-learning models in the cloud. SageMaker also enables developers to deploy ML models on embedded systems and edge-devices.
Amazon Web Services Amazon FreeRTOS
FreeRTOS is a real-time operating system kernel for embedded devices that has been ported to 35 microcontroller platforms. It is distributed under the MIT License.
Espressif ESP-IDF Components library
Espressif IoT Development Framework (esp-idf) is the official development framework for the ESP32, ESP32-S, and ESP32-C Series SoCs.
Arduino IDE
Arduino IDE
Probably you know better than me. I used Arduino IDE here to program NodeMCU.

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free
Wire Stripper & Cutter, 18-10 AWG / 0.75-4mm² Capacity Wires
Wire Stripper & Cutter, 18-10 AWG / 0.75-4mm² Capacity Wires

Story

Read more

Schematics

Exhaust Fan Circuit

Fan is controlled by Node MCU

Cover Image

Connection for Sensors and Core2

Core2 AWS IoT EduKit Schematic

System Block Diagram

Code

Main Program for the project

C/C++
/*
 * AWS IoT EduKit - Core2 for AWS IoT EduKit
 * Smart Thermostat v1.2.1
 * main.c
 * 
 * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * Additions Copyright 2016 Espressif Systems (Shanghai) PTE LTD
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */
/**
 * @file main.c
 * @brief simple MQTT publish, subscribe, and device shadows for use with AWS IoT EduKit reference hardware.
 *
 * This example takes the parameters from the build configuration and establishes a connection to AWS IoT Core over MQTT.
 *
 * Some configuration is required. Visit https://edukit.workshop.aws
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <limits.h>
#include <string.h>
#include <math.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/event_groups.h"
#include "esp_log.h"

#include "aws_iot_config.h"
#include "aws_iot_log.h"
#include "aws_iot_version.h"
#include "aws_iot_mqtt_client_interface.h"
#include "aws_iot_shadow_interface.h"

#include "core2forAWS.h"

#include "wifi.h"
#include "fft.h"
#include "ui.h"

#include "ads1115.h"
#include "dht.h"

static const char *TAG = "MAIN";

#define READY   "READY"
#define UNCLEAN "UNCLEAN"
#define BUSY  "BUSY"
#define INITIAL_STATE "INITIAL_STATE"

#define STARTING_ROOMTEMPERATURE 0.0f
#define STARTING_ROOMHUMIDITY 0.0f
#define STARTING_ROOMCO 0.0f
#define STARTING_ROOMNO2 0.0f
#define STARTING_ROOMNH3 0.0f
#define STARTING_ROOMSO2 0.0f
#define STARTING_ROOMCH4 0.0f
#define STARTING_SOUNDLEVEL 0x00
#define STARTING_FANSTATUS false   
#define STARTING_TOILETSTATUS INITIAL_STATE

#define CO_CHNNEL 0
#define NH3_CHNNEL 1
#define NO2_CHNNEL 2
#define SO2_CHNNEL 3

static const dht_sensor_type_t sensor_type = DHT_TYPE_DHT11;
static const gpio_num_t dht_gpio = GPIO_NUM_26;

// Number of slices to split the microphone sample into
#define AUDIO_TIME_SLICES 60

#define MAX_LENGTH_OF_UPDATE_JSON_BUFFER 300

/* CA Root certificate */
extern const uint8_t aws_root_ca_pem_start[] asm("_binary_aws_root_ca_pem_start");
extern const uint8_t aws_root_ca_pem_end[] asm("_binary_aws_root_ca_pem_end");

/* Default MQTT HOST URL is pulled from the aws_iot_config.h */
char HostAddress[255] = AWS_IOT_MQTT_HOST;

/* Default MQTT port is pulled from the aws_iot_config.h */
uint32_t port = AWS_IOT_MQTT_PORT;

/*
Semaphore for sound levels
*/
SemaphoreHandle_t xMaxNoiseSemaphore;

void iot_subscribe_callback_handler(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen,
                                    IoT_Publish_Message_Params *params, void *pData) {
    ESP_LOGI(TAG, "Subscribe callback");
    ESP_LOGI(TAG, "%.*s\t%.*s", topicNameLen, topicName, (int) params->payloadLen, (char *)params->payload);
}

void disconnect_callback_handler(AWS_IoT_Client *pClient, void *data) {
    ESP_LOGW(TAG, "MQTT Disconnect");
    ui_textarea_add("Disconnected from AWS IoT Core...", NULL, 0);

    IoT_Error_t rc = FAILURE;

    if(NULL == pClient) {
        return;
    }

    if(aws_iot_is_autoreconnect_enabled(pClient)) {
        ESP_LOGI(TAG, "Auto Reconnect is enabled, Reconnecting attempt will start now");
    } else {
        ESP_LOGW(TAG, "Auto Reconnect not enabled. Starting manual reconnect...");
        rc = aws_iot_mqtt_attempt_reconnect(pClient);
        if(NETWORK_RECONNECTED == rc) {
            ESP_LOGW(TAG, "Manual Reconnect Successful");
        } else {
            ESP_LOGW(TAG, "Manual Reconnect Failed - %d", rc);
        }
    }
}

static bool shadowUpdateInProgress;

void ShadowUpdateStatusCallback(const char *pThingName, ShadowActions_t action, Shadow_Ack_Status_t status,
                                const char *pReceivedJsonDocument, void *pContextData) {
    IOT_UNUSED(pThingName);
    IOT_UNUSED(action);
    IOT_UNUSED(pReceivedJsonDocument);
    IOT_UNUSED(pContextData);

    shadowUpdateInProgress = false;

    if(SHADOW_ACK_TIMEOUT == status) {
        ESP_LOGE(TAG, "Update timed out");
    } else if(SHADOW_ACK_REJECTED == status) {
        ESP_LOGE(TAG, "Update rejected");
    } else if(SHADOW_ACK_ACCEPTED == status) {
        ESP_LOGI(TAG, "Update accepted");
    }
}

void exhaustFan_Callback(const char *pJsonString, uint32_t JsonStringDataLen, jsonStruct_t *pContext) {
    IOT_UNUSED(pJsonString);
    IOT_UNUSED(JsonStringDataLen);

    //char * status = (char *) (pContext->pData);
    //bool * status = (bool *) (pContext->pData);
    bool status = (bool *) (pContext->pData);

    if(pContext != NULL) {
        ESP_LOGI(TAG, "Delta - fanStatus state changed to %s", status? "ON":"OFF");
    }

    //if(strcmp(status, HEATING) == 0) {
    if(status == false) {
        ESP_LOGI(TAG, "setting one side LEDs to green");
        //Core2ForAWS_Sk6812_SetSideColor(SK6812_SIDE_LEFT, 0x00FF00);
        //Core2ForAWS_Sk6812_SetSideColor(SK6812_SIDE_RIGHT, 0xFF0000);
        //Core2ForAWS_Sk6812_Show();
    } else if(status == true) {
        //ESP_LOGI(TAG, "setting one side LEDs to orange");
        //Core2ForAWS_Sk6812_SetSideColor(SK6812_SIDE_LEFT, 0x00FFFF);
        //Core2ForAWS_Sk6812_SetSideColor(SK6812_SIDE_RIGHT, 0x0000FF);
        //Core2ForAWS_Sk6812_Show();
    } else {
        //ESP_LOGI(TAG, "clearing side LEDs");
        //Core2ForAWS_Sk6812_Clear();
        //Core2ForAWS_Sk6812_Show();
    }
}

void toilet_status_Callback(const char *pJsonString, uint32_t JsonStringDataLen, jsonStruct_t *pContext) {
    IOT_UNUSED(pJsonString);
    IOT_UNUSED(JsonStringDataLen);

    char * status = (char *) (pContext->pData);

    if(pContext != NULL) {
        ESP_LOGI(TAG, "Delta - toiletStatus state changed to %s", status);
    }

    if(strcmp(status, BUSY) == 0) {
        ESP_LOGI(TAG, "setting side LEDs to Yellow");
        Core2ForAWS_Sk6812_SetSideColor(SK6812_SIDE_LEFT, 0xFFFF00);
        Core2ForAWS_Sk6812_SetSideColor(SK6812_SIDE_RIGHT, 0xFFFF00);
        Core2ForAWS_Sk6812_Show();
    } else if(strcmp(status, UNCLEAN) == 0) {
        ESP_LOGI(TAG, "setting side LEDs to Red");
        Core2ForAWS_Sk6812_SetSideColor(SK6812_SIDE_LEFT, 0xFF0000);
        Core2ForAWS_Sk6812_SetSideColor(SK6812_SIDE_RIGHT, 0xFF0000);
        Core2ForAWS_Sk6812_Show();
    } else if(strcmp(status, READY) == 0) {
        ESP_LOGI(TAG, "clearing side Green");
        Core2ForAWS_Sk6812_SetSideColor(SK6812_SIDE_LEFT, 0x00FF00);
        Core2ForAWS_Sk6812_SetSideColor(SK6812_SIDE_RIGHT, 0x00FF00);
        //Core2ForAWS_Sk6812_Clear();
        Core2ForAWS_Sk6812_Show();
    }
}

float temperature = STARTING_ROOMTEMPERATURE;
float humidity = STARTING_ROOMHUMIDITY;
float nitrogen_dioxide = STARTING_ROOMNO2;
float ammonia = STARTING_ROOMNH3;
float carbon_monoxide = STARTING_ROOMCO;
float sulfur_dioxide = STARTING_ROOMSO2;
float methane = STARTING_ROOMCH4;
uint8_t soundBuffer = STARTING_SOUNDLEVEL;
uint8_t reportedSound = STARTING_SOUNDLEVEL;
bool fan_status = STARTING_FANSTATUS;
char toilet_status[14] = STARTING_TOILETSTATUS;

void read_temperature(){
    
    int16_t temperature_data = 0;
    int16_t humidity_data = 0;

    if (dht_read_data(sensor_type, dht_gpio, &humidity_data, &temperature_data) == ESP_OK){
        temperature = (float) temperature_data/10;
        humidity = (float) humidity_data/10;
    }

}

void read_airquality(){
    
    int16_t adc0, adc1, adc2;
    //float nitrogen_dioxide, ammonia, carbon_monoxide;
    adc0 = ADS1115_readADC_SingleEnded(CO_CHNNEL);
    carbon_monoxide = ADS1115_computeVolts(adc0);
    adc1 = ADS1115_readADC_SingleEnded(NH3_CHNNEL);
    ammonia = ADS1115_computeVolts(adc1);
    adc2 = ADS1115_readADC_SingleEnded(NO2_CHNNEL);
    nitrogen_dioxide = ADS1115_computeVolts(adc2); 

}


uint16_t _baseNH3;
uint16_t _baseCO;
uint16_t _baseNO2;

typedef enum
{
	CH_CO,
	CH_NO2,
	CH_NH3
} channel_t;

typedef enum
{
	CO,
	NO2,
	NH3,
    CH4
} gas_t;

void airquality_calibrate ()
{
	// The number of seconds that must pass before
	// than we will assume that the calibration is complete
	// (Less than 64 seconds to avoid overflow)
	uint8_t seconds = 10;

	// Tolerance for the average of the current value
	uint8_t delta = 2;

	// Measurement buffers
	uint16_t bufferNH3 [seconds];
	uint16_t bufferCO [seconds];
	uint16_t bufferNO2 [seconds];

	// Pointers for the next item in the buffer
	uint8_t pntrNH3 = 0;
	uint8_t pntrCO = 0;
	uint8_t pntrNO2 = 0;

	// The current floating amount in the buffer
    uint32_t fltSumNH3 = 0;
	uint32_t fltSumCO = 0;
	uint32_t fltSumNO2 = 0;

	// Current measurement
	uint16_t curNH3;
	uint16_t curCO;
	uint16_t curNO2;

	// Flag of stability of indications
	bool isStableNH3 = false;
	bool isStableCO = false;
	bool isStableNO2 = false;

	// We kill the buffer with zeros
	for (int i = 0; i <seconds; ++ i)
	{
		bufferNH3 [i] = 0;
		bufferCO [i] = 0;
		bufferNO2 [i] = 0;
	}

	// Calibrate
	do
	{
		vTaskDelay(pdMS_TO_TICKS(1000));

		unsigned long rs = 0;

		vTaskDelay(pdMS_TO_TICKS(50));
		for (int i = 0; i <3; i ++)
		{
			vTaskDelay(pdMS_TO_TICKS(1));
			rs += ADS1115_readADC_SingleEnded(NH3_CHNNEL);
		}
        
		curNH3 = rs / 3;  //printf("cur NH3 Rs : %d", curNH3);
		rs = 0;

		vTaskDelay(pdMS_TO_TICKS(50));
		for (int i = 0; i <3; i ++)
		{
			vTaskDelay(pdMS_TO_TICKS(1));
			rs += ADS1115_readADC_SingleEnded(CO_CHNNEL);
		}

		curCO = rs / 3;  //printf("cur co Rs : %d", curCO);
		rs = 0;

		vTaskDelay(pdMS_TO_TICKS(50));
		for (int i = 0; i <3; i ++)
		{
			vTaskDelay(pdMS_TO_TICKS(1));
			rs += ADS1115_readADC_SingleEnded(NO2_CHNNEL);
		}

		curNO2 = rs / 3; //printf("cur NO2 Rs : %d", curNO2);

		// Update the floating amount by subtracting the value, 
		// to be overwritten, and adding a new value.

		fltSumNH3 = fltSumNH3 + curNH3 - bufferNH3 [pntrNH3];
		fltSumCO = fltSumCO + curCO - bufferCO [pntrCO];
		fltSumNO2 = fltSumNO2 + curNO2 - bufferNO2 [pntrNO2];
        //printf("\tNH3 %d\n", fltSumNH3);

		// Store d buffer new values
		bufferNH3 [pntrNH3] = curNH3;
		bufferCO [pntrCO] = curCO;
		bufferNO2 [pntrNO2] = curNO2; 

		// Define flag states
		isStableNH3 = abs (fltSumNH3 / seconds - curNH3) <delta;
		isStableCO = abs (fltSumCO / seconds - curCO) <delta;
		isStableNO2 = abs (fltSumNO2 / seconds - curNO2) <delta;

		// Pointer to a buffer
		pntrNH3 = (pntrNH3 + 1)% seconds;
		pntrCO = (pntrCO + 1)% seconds;
		pntrNO2 = (pntrNO2 + 1)% seconds;
	} while (! isStableNH3 ||! isStableCO ||! isStableNO2);

	_baseNH3 = fltSumNH3 / seconds;
	_baseCO = fltSumCO / seconds;
	_baseNO2 = fltSumNO2 / seconds;
}

uint16_t getBaseResistance (channel_t channel)
{
	switch (channel)
	{
	case CH_NH3:
		return _baseNH3;
	case CH_CO:
		return _baseCO;
	case CH_NO2:
		return _baseNO2;
	}

	return 0;
}

uint16_t getResistance (channel_t channel)
{
	unsigned long rs = 0;
	int counter = 0;
    int i = 0;

	switch (channel)
	{
	case CH_CO:
		for (i = 0; i <100; i ++)
		{
			rs += ADS1115_readADC_SingleEnded(CO_CHNNEL);
			counter ++;
			vTaskDelay(pdMS_TO_TICKS(2));
		}
        break;
	case CH_NO2:
		for (i = 0; i <100; i ++)
		{
			rs += ADS1115_readADC_SingleEnded(NO2_CHNNEL);
			counter ++;
			vTaskDelay(pdMS_TO_TICKS(2));
		}
        break;
	case CH_NH3:
		for (i = 0; i <100; i ++)
		{
			rs += ADS1115_readADC_SingleEnded(NH3_CHNNEL);
			counter ++;
			vTaskDelay(pdMS_TO_TICKS(2));
		}
        break;
	}

	return counter != 0? rs / counter: 0;
}

float getCurrentRatio (channel_t channel)
{
	float baseResistance = (float) getBaseResistance (channel);
	float resistance = (float) getResistance (channel);

	return resistance / baseResistance * (1023.0 - baseResistance) / (1023.0 - resistance);

	return -1.0;
}

float measure_in_ppm (gas_t gas)
{
	float ratio;
	float c = 0;

	switch (gas)
	{
	case CO:
		ratio = getCurrentRatio (CH_CO);
		c = pow (ratio, -1.179) * 4.385;
		break;
	case NO2:
		ratio = getCurrentRatio (CH_NO2);
		c = pow (ratio, 1.007) / 6.855;
		break;
	case NH3:
		ratio = getCurrentRatio (CH_NH3);
		c = pow (ratio, -1.67) / 1.47;
		break;
  case CH4:
    ratio = getCurrentRatio (CH_CO);
    c = pow (ratio, -4.093) * 0.837; 
    break;
	}

	return isnan (c)? -1: c;
}

void read_airquality_ppm(){

    carbon_monoxide = measure_in_ppm(CO);
    nitrogen_dioxide = measure_in_ppm(NO2);
    ammonia = measure_in_ppm(NH3);
    methane = measure_in_ppm(CH4);
   
}

void read_sulfur_dioxide(){

    int rl = 10;
    float r0 = 76.63;
    int value = ADS1115_readADC_SingleEnded(SO2_CHNNEL);
    float rs = ( ( 5.0 * rl ) - ( rl * value ) ) / value;  
    float ratio = rs/r0;
    ratio = ratio * 0.3611;
    float SO2_PPM = (146.15 * (2.868 - ratio) + 10);
    sulfur_dioxide = SO2_PPM;
}


// helper function for working with audio data
long map(long x, long in_min, long in_max, long out_min, long out_max) {
    long divisor = (in_max - in_min);
    if(divisor == 0){
        return -1; //AVR returns -1, SAM returns 0
    }
    return (x - in_min) * (out_max - out_min) / divisor + out_min;
}

void microphone_task(void *arg) {
    static int8_t i2s_readraw_buff[1024];
    size_t bytesread;
    int16_t *buffptr;
    double data = 0;

    Microphone_Init();
    uint8_t maxSound = 0x00;
    uint8_t currentSound = 0x00;

    for (;;) {
        maxSound = 0x00;
        fft_config_t *real_fft_plan = fft_init(512, FFT_REAL, FFT_FORWARD, NULL, NULL);
        i2s_read(I2S_NUM_0, (char *)i2s_readraw_buff, 1024, &bytesread, pdMS_TO_TICKS(100));
        buffptr = (int16_t *)i2s_readraw_buff;
        for (uint16_t count_n = 0; count_n < real_fft_plan->size; count_n++) {
            real_fft_plan->input[count_n] = (float)map(buffptr[count_n], INT16_MIN, INT16_MAX, -1000, 1000);
        }
        fft_execute(real_fft_plan);

        for (uint16_t count_n = 1; count_n < AUDIO_TIME_SLICES; count_n++) {
            data = sqrt(real_fft_plan->output[2 * count_n] * real_fft_plan->output[2 * count_n] + real_fft_plan->output[2 * count_n + 1] * real_fft_plan->output[2 * count_n + 1]);
            currentSound = map(data, 0, 2000, 0, 256);
            if(currentSound > maxSound) {
                maxSound = currentSound;
            }
        }
        fft_destroy(real_fft_plan);

        // store max of sample in semaphore
        xSemaphoreTake(xMaxNoiseSemaphore, portMAX_DELAY);
        soundBuffer = maxSound;
        xSemaphoreGive(xMaxNoiseSemaphore);
    }
}

void aws_iot_task(void *param) {
    IoT_Error_t rc = FAILURE;

    char JsonDocumentBuffer[MAX_LENGTH_OF_UPDATE_JSON_BUFFER];
    size_t sizeOfJsonDocumentBuffer = sizeof(JsonDocumentBuffer) / sizeof(JsonDocumentBuffer[0]);

    jsonStruct_t temperatureHandler;
    temperatureHandler.cb = NULL;
    temperatureHandler.pKey = "temperature";
    temperatureHandler.pData = &temperature;
    temperatureHandler.type = SHADOW_JSON_FLOAT;
    temperatureHandler.dataLength = sizeof(float);

    jsonStruct_t humidityHandler;
    humidityHandler.cb = NULL;
    humidityHandler.pKey = "humidity";
    humidityHandler.pData = &humidity;
    humidityHandler.type = SHADOW_JSON_FLOAT;
    humidityHandler.dataLength = sizeof(float);

    jsonStruct_t carbonMonoxideHandler;
    carbonMonoxideHandler.cb = NULL;
    carbonMonoxideHandler.pKey = "carbon_monoxide";
    carbonMonoxideHandler.pData = &carbon_monoxide;
    carbonMonoxideHandler.type = SHADOW_JSON_FLOAT;
    carbonMonoxideHandler.dataLength = sizeof(float);

    jsonStruct_t ammoniaHandler;
    ammoniaHandler.cb = NULL;
    ammoniaHandler.pKey = "ammonia";
    ammoniaHandler.pData = &ammonia;
    ammoniaHandler.type = SHADOW_JSON_FLOAT;
    ammoniaHandler.dataLength = sizeof(float);

    jsonStruct_t nitrogenDioxideHandler;
    nitrogenDioxideHandler.cb = NULL;
    nitrogenDioxideHandler.pKey = "nitrogen_dioxide";
    nitrogenDioxideHandler.pData = &nitrogen_dioxide;
    nitrogenDioxideHandler.type = SHADOW_JSON_FLOAT;
    nitrogenDioxideHandler.dataLength = sizeof(float);

    jsonStruct_t sulfurDioxideHandler;
    sulfurDioxideHandler.cb = NULL;
    sulfurDioxideHandler.pKey = "sulfur_dioxide";
    sulfurDioxideHandler.pData = &sulfur_dioxide;
    sulfurDioxideHandler.type = SHADOW_JSON_FLOAT;
    sulfurDioxideHandler.dataLength = sizeof(float);

    jsonStruct_t methaneHandler;
    methaneHandler.cb = NULL;
    methaneHandler.pKey = "methane";
    methaneHandler.pData = &methane;
    methaneHandler.type = SHADOW_JSON_FLOAT;
    methaneHandler.dataLength = sizeof(float);

    jsonStruct_t soundHandler;
    soundHandler.cb = NULL;
    soundHandler.pKey = "sound";
    soundHandler.pData = &reportedSound;
    soundHandler.type = SHADOW_JSON_UINT8;
    soundHandler.dataLength = sizeof(uint8_t);

    jsonStruct_t exhaustFanActuator;
    exhaustFanActuator.cb = exhaustFan_Callback;
    exhaustFanActuator.pKey = "fan_status";
    exhaustFanActuator.pData = &fan_status;  
    exhaustFanActuator.type = SHADOW_JSON_BOOL;
    exhaustFanActuator.dataLength = sizeof(bool);

    jsonStruct_t toiletStatusActuator;
    toiletStatusActuator.cb = toilet_status_Callback;
    toiletStatusActuator.pKey = "toilet_status";
    toiletStatusActuator.pData = &toilet_status;
    toiletStatusActuator.type = SHADOW_JSON_STRING;
    toiletStatusActuator.dataLength = strlen(toilet_status)+1;

    ESP_LOGI(TAG, "AWS IoT SDK Version %d.%d.%d-%s", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, VERSION_TAG);

    // initialize the mqtt client
    AWS_IoT_Client iotCoreClient;

    ShadowInitParameters_t sp = ShadowInitParametersDefault;
    sp.pHost = HostAddress;
    sp.port = port;
    sp.enableAutoReconnect = false;
    sp.disconnectHandler = disconnect_callback_handler;

    sp.pRootCA = (const char *)aws_root_ca_pem_start;
    sp.pClientCRT = "#";
    sp.pClientKey = "#0";
    
    #define CLIENT_ID_LEN (ATCA_SERIAL_NUM_SIZE * 2)
    char *client_id = malloc(CLIENT_ID_LEN + 1);
    ATCA_STATUS ret = Atecc608_GetSerialString(client_id);
    if (ret != ATCA_SUCCESS){
        ESP_LOGE(TAG, "Failed to get device serial from secure element. Error: %i", ret);
        abort();
    }

    ui_textarea_add("\nDevice client Id:\n>> %s <<\n", client_id, CLIENT_ID_LEN);

    /* Wait for WiFI to show as connected */
    xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
                        false, true, portMAX_DELAY);

    ESP_LOGI(TAG, "Shadow Init");

    rc = aws_iot_shadow_init(&iotCoreClient, &sp);
    if(SUCCESS != rc) {
        ESP_LOGE(TAG, "aws_iot_shadow_init returned error %d, aborting...", rc);
        abort();
    }

    ShadowConnectParameters_t scp = ShadowConnectParametersDefault;
    scp.pMyThingName = client_id;
    scp.pMqttClientId = client_id;
    scp.mqttClientIdLen = CLIENT_ID_LEN;

    ESP_LOGI(TAG, "Shadow Connect");
    rc = aws_iot_shadow_connect(&iotCoreClient, &scp);
    if(SUCCESS != rc) {
        ESP_LOGE(TAG, "aws_iot_shadow_connect returned error %d, aborting...", rc);
        abort();
    }
    ui_textarea_add("\nConnected to AWS IoT Core and pub/sub to the device shadow state\n", NULL, 0);

    xTaskCreatePinnedToCore(&microphone_task, "microphone_task", 4096, NULL, 1, NULL, 1);

    /*
     * Enable Auto Reconnect functionality. Minimum and Maximum time of Exponential backoff are set in aws_iot_config.h
     *  #AWS_IOT_MQTT_MIN_RECONNECT_WAIT_INTERVAL
     *  #AWS_IOT_MQTT_MAX_RECONNECT_WAIT_INTERVAL
     */
    rc = aws_iot_shadow_set_autoreconnect_status(&iotCoreClient, true);
    if(SUCCESS != rc) {
        ESP_LOGE(TAG, "Unable to set Auto Reconnect to true - %d, aborting...", rc);
        abort();
    }

    // register delta callback for roomOccupancy
    rc = aws_iot_shadow_register_delta(&iotCoreClient, &toiletStatusActuator);
    if(SUCCESS != rc) {
        ESP_LOGE(TAG, "Shadow Register Delta Error");
    }

    // register delta callback for fanStatus
    rc = aws_iot_shadow_register_delta(&iotCoreClient, &exhaustFanActuator);
    if(SUCCESS != rc) {
        ESP_LOGE(TAG, "Shadow Register Delta Error");
    }

    // loop and publish changes
    while(NETWORK_ATTEMPTING_RECONNECT == rc || NETWORK_RECONNECTED == rc || SUCCESS == rc) {
        rc = aws_iot_shadow_yield(&iotCoreClient, 200);
        if(NETWORK_ATTEMPTING_RECONNECT == rc || shadowUpdateInProgress) {
            rc = aws_iot_shadow_yield(&iotCoreClient, 1000);
            // If the client is attempting to reconnect, or already waiting on a shadow update,
            // we will skip the rest of the loop.
            continue;
        }

        // START get sensor readings
        // sample temperature, convert to fahrenheit
        //MPU6886_GetTempData(&temperature);
        //temperature = (temperature * 1.8)  + 32 - 50;
        /*****************************************************************************************/
        read_temperature();
        //read_airquality();
        read_airquality_ppm();
        read_sulfur_dioxide();
        /*****************************************************************************************/
        // sample from soundBuffer (latest reading from microphone)
        xSemaphoreTake(xMaxNoiseSemaphore, portMAX_DELAY);
        reportedSound = soundBuffer;
        xSemaphoreGive(xMaxNoiseSemaphore);

        // END get sensor readings

        ESP_LOGI(TAG, "*****************************************************************************************");
        ESP_LOGI(TAG, "On Device: Toilet status: %s", toilet_status);
        ESP_LOGI(TAG, "On Device: Fan status: %s", fan_status? "ON" : "OFF");
        ESP_LOGI(TAG, "On Device: Temperature: %f C", temperature);
        ESP_LOGI(TAG, "On Device: Sound: %d", reportedSound);
        ESP_LOGI(TAG, "On Device: Humidity: %f %%", humidity);
        ESP_LOGI(TAG, "On Device: Carbon monoxide: %f ppm", carbon_monoxide);
        ESP_LOGI(TAG, "On Device: Ammonia: %f ppm", ammonia);
        ESP_LOGI(TAG, "On Device: Nitrogen dioxide: %f ppm", nitrogen_dioxide);
        ESP_LOGI(TAG, "On Device: Sulfur dioxide: %f ppm", sulfur_dioxide);
        ESP_LOGI(TAG, "On Device: Methane: %f ppm", methane);
        ESP_LOGI(TAG, "*****************************************************************************************");

        rc = aws_iot_shadow_init_json_document(JsonDocumentBuffer, sizeOfJsonDocumentBuffer);
        if(SUCCESS == rc) {
            rc = aws_iot_shadow_add_reported(JsonDocumentBuffer, sizeOfJsonDocumentBuffer, 10, &temperatureHandler,
                                             &humidityHandler, &carbonMonoxideHandler, &ammoniaHandler, &nitrogenDioxideHandler, &sulfurDioxideHandler,
                                             &methaneHandler, &soundHandler, &toiletStatusActuator, &exhaustFanActuator);
            if(SUCCESS == rc) {
                rc = aws_iot_finalize_json_document(JsonDocumentBuffer, sizeOfJsonDocumentBuffer);
                if(SUCCESS == rc) {
                    ESP_LOGI(TAG, "Update Shadow: %s", JsonDocumentBuffer);
                    rc = aws_iot_shadow_update(&iotCoreClient, client_id, JsonDocumentBuffer,
                                               ShadowUpdateStatusCallback, NULL, 10, true);
                    shadowUpdateInProgress = true;
                }
            }
        }
        ESP_LOGI(TAG, "*****************************************************************************************");
        ESP_LOGI(TAG, "Stack remaining for task '%s' is %d bytes", pcTaskGetTaskName(NULL), uxTaskGetStackHighWaterMark(NULL));

        vTaskDelay(pdMS_TO_TICKS(15000));
    }

    if(SUCCESS != rc) {
        ESP_LOGE(TAG, "An error occurred in the loop %d", rc);
    }

    ESP_LOGI(TAG, "Disconnecting");
    rc = aws_iot_shadow_disconnect(&iotCoreClient);

    if(SUCCESS != rc) {
        ESP_LOGE(TAG, "Disconnect error %d", rc);
    }

    vTaskDelete(NULL);
}

void app_main()
{   
    Core2ForAWS_Init();
    Core2ForAWS_Display_SetBrightness(80);
    Core2ForAWS_LED_Enable(1);

    ADS1115_I2CInit();
    ADS1115_setGain(GAIN_TWOTHIRDS);
    airquality_calibrate ();

    xMaxNoiseSemaphore = xSemaphoreCreateMutex();

    ui_init(); 
    initialise_wifi();

    xTaskCreatePinnedToCore(&aws_iot_task, "aws_iot_task", 4096*2, NULL, 5, NULL, 1);
}

Node MCU Firmware

Arduino
This is for controlling fan.
/******************************************************************************************
Connect NodeMCU to AWS IoT Core.
Publish Messages from NodeMCU to the Core
   
Tutorial by https://nerdyelectronics.com :
1)  AWS IoT - Create a Thing
    https://nerdyelectronics.com/iot/how-to-create-a-thing-in-aws-iot/ :
2)  Convert Certificates from .pem to .der format
    https://nerdyelectronics.com/iot/how-to-convert-certificates-from-pem-to-der-format/ :
3)  Connect NodeMCU to AWS IoT Core
    https://nerdyelectronics.com/iot/how-to-connect-nodemcu-to-aws-iot-core/ :
  
*********************************************************************************************/
   
#include "FS.h"
#include <ESP8266WiFi.h>  //tested esp8266 core version: 2.5.2
#include <PubSubClient.h> //tested version: 2.7.0
#include <NTPClient.h>    //tested version: 3.2.0
#include <WiFiUdp.h>
#include <ArduinoJson.h>  //tested version: 6.18.4

#define RELAY D3 //you can use any digital pin here
String message = "";

DynamicJsonDocument doc(1024);

   
// Update these with values suitable for your network.
   
const char* ssid = "YOUR WIFI NAME";  //Edit this line and put in your Wifi Name
const char* password = "PASSWORD";   //put in your Wifi Password
int count = 0;
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org");
   
const char* AWS_endpoint = "XXXXXXXXXXX-ats.iot.us-west-2.amazonaws.com"; // Edit your AWS Endpoint here
  
void callback(char* topic, byte* payload, unsigned int length) 
{
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) 
  {
    message.concat((char)payload[i]); 
  }
  Serial.print(message);
  deserializeJson(doc, message);
  bool fan_status = doc["state"]["desired"]["fan_status"];
  Serial.println();
  if(fan_status == true){
    //turn on fan
    digitalWrite(RELAY, HIGH);
    Serial.println("Fan is ON");
    }
  else if(fan_status == false){
    //turn off fan
    digitalWrite(RELAY, LOW);
    Serial.println("Fan is OFF");
    }
  message = "";
}
  
WiFiClientSecure espClient;
PubSubClient client(AWS_endpoint, 8883, callback, espClient); //set MQTT port number to 8883 as per //standard
long lastMsg = 0;
char msg[50];  //buffer to hold the message to be published
int value = 0;
  
void setup_wifi() 
{
  
  delay(10);
  // We start by connecting to a WiFi network
  espClient.setBufferSizes(512, 512);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  
  timeClient.begin();
  while (!timeClient.update()) {
    timeClient.forceUpdate();
  }
  
  espClient.setX509Time(timeClient.getEpochTime());
  
}
  
void reconnect() 
{
  // Loop until we're reconnected
  while (!client.connected()) 
  {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ESPthing")) 
    {
      Serial.println("connected");
      client.subscribe("node/mcu/fan/state");
      Serial.println("subscribed");
    } 
    else
    {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
  
      char buf[256];
      espClient.getLastSSLError(buf, 256);
      Serial.print("WiFiClientSecure SSL error: ");
      Serial.println(buf);
  
      // Wait 5 seconds before retrying
      delay(5000);
    }

    
  }
}
  
void setup()
{
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(RELAY, OUTPUT);
  setup_wifi();
  delay(1000);
  if (!SPIFFS.begin()) 
  {
    Serial.println("Failed to mount file system");
    return;
  }
  
  Serial.print("Heap: "); Serial.println(ESP.getFreeHeap());
  
  // Load certificate file
  File cert = SPIFFS.open("/cert.der", "r"); //replace cert.crt with your uploaded file name
  if (!cert) 
  {
    Serial.println("Failed to open cert file");
  }
  else
    Serial.println("Successfully opened cert file");
  
  delay(1000);
  
  if (espClient.loadCertificate(cert)) // add the thing certificate to the client
    Serial.println("cert loaded");
  else
    Serial.println("cert not loaded");
  
  // Load private key file
  File private_key = SPIFFS.open("/private.der", "r"); //replace private with your uploaded file name
  if (!private_key) 
  {
    Serial.println("Failed to open private cert file");
  }
  else
    Serial.println("Successfully opened private cert file");
  
  delay(1000);
  
  if (espClient.loadPrivateKey(private_key))  // add the private key to the client
    Serial.println("private key loaded");
  else
    Serial.println("private key not loaded");
  
  // Load CA file
  File ca = SPIFFS.open("/ca.der", "r"); //replace ca with your uploaded file name
  if (!ca) 
  {
    Serial.println("Failed to open ca ");
  }
  else
    Serial.println("Successfully opened open ca");
  
  delay(1000);
  
  if (espClient.loadCACert(ca))   // add the AWS root certificate to the client
    Serial.println("ca loaded");
  else
    Serial.println("ca failed");
  
  Serial.print("Heap: "); Serial.println(ESP.getFreeHeap());
}
  
void loop() 
{ 
  if (!client.connected()) 
  {
    reconnect();
  }
  client.loop();
}

MICS6814 reference document

Plain text
This document is collected from the internet.
No preview (download only).

DHT11 Library Header File

C Header File
/**
 * @file dht.h
 * @defgroup dht dht
 * @{
 *
 * ESP-IDF driver for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321), Itead Si7021
 *
 * Ported from esp-open-rtos
 *
 * Copyright (c) 2016 Jonathan Hartsuiker <https://github.com/jsuiker>\n
 * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>\n
 *
 *
 */
#pragma once

#include <driver/gpio.h>
#include <esp_err.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * Sensor type
 */
typedef enum
{
    DHT_TYPE_DHT11 = 0,   //!< DHT11
    DHT_TYPE_AM2301,      //!< AM2301 (DHT21, DHT22, AM2302, AM2321)
    DHT_TYPE_SI7021       //!< Itead Si7021
} dht_sensor_type_t;

/**
 * @brief Read integer data from sensor on specified pin
 *
 * Humidity and temperature are returned as integers.
 * For example: humidity=625 is 62.5 %, temperature=244 is 24.4 degrees Celsius
 *
 * @param sensor_type DHT11 or DHT22
 * @param pin GPIO pin connected to sensor OUT
 * @param[out] humidity Humidity, percents * 10, nullable
 * @param[out] temperature Temperature, degrees Celsius * 10, nullable
 * @return `ESP_OK` on success
 */
esp_err_t dht_read_data(dht_sensor_type_t sensor_type, gpio_num_t pin,
        int16_t *humidity, int16_t *temperature);

/**
 * @brief Read float data from sensor on specified pin
 *
 * Humidity and temperature are returned as floats.
 *
 * @param sensor_type DHT11 or DHT22
 * @param pin GPIO pin connected to sensor OUT
 * @param[out] humidity Humidity, percents, nullable
 * @param[out] temperature Temperature, degrees Celsius, nullable
 * @return `ESP_OK` on success
 */
esp_err_t dht_read_float_data(dht_sensor_type_t sensor_type, gpio_num_t pin,
        float *humidity, float *temperature);

#ifdef __cplusplus
}
#endif

DHT11 Library Source File

C/C++
/*
 * Copyright (c) 2016 Jonathan Hartsuiker <https://github.com/jsuiker>
 * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
 *
 * This code is modified by Md. Khairul Alam
 * to work with AWS Core2 EDU KIT
 * ESP-IDF driver for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321), Itead Si7021
 * 
 */
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_log.h>
#include <string.h>
#include <driver/gpio.h>

#include "dht.h"

// DHT timer precision in microseconds
#define DHT_TIMER_INTERVAL 2
#define DHT_DATA_BITS 40
#define DHT_DATA_BYTES (DHT_DATA_BITS / 8)

/*
 *  Note:
 *  A suitable pull-up resistor should be connected to the selected GPIO line
 *
 *  __           ______          _______                              ___________________________
 *    \    A    /      \   C    /       \   DHT duration_data_low    /                           \
 *     \_______/   B    \______/    D    \__________________________/   DHT duration_data_high    \__
 *
 *
 *  Initializing communications with the DHT requires four 'phases' as follows:
 *
 *  Phase A - MCU pulls signal low for at least 18000 us
 *  Phase B - MCU allows signal to float back up and waits 20-40us for DHT to pull it low
 *  Phase C - DHT pulls signal low for ~80us
 *  Phase D - DHT lets signal float back up for ~80us
 *
 *  After this, the DHT transmits its first bit by holding the signal low for 50us
 *  and then letting it float back high for a period of time that depends on the data bit.
 *  duration_data_high is shorter than 50us for a logic '0' and longer than 50us for logic '1'.
 *
 *  There are a total of 40 data bits transmitted sequentially. These bits are read into a byte array
 *  of length 5.  The first and third bytes are humidity (%) and temperature (C), respectively.  Bytes 2 and 4
 *  are zero-filled and the fifth is a checksum such that:
 *
 *  byte_5 == (byte_1 + byte_2 + byte_3 + byte_4) & 0xFF
 *
 */

static const char *TAG = "DHT";

static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
#define PORT_ENTER_CRITICAL() portENTER_CRITICAL(&mux)
#define PORT_EXIT_CRITICAL() portEXIT_CRITICAL(&mux)

#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)

#define CHECK_LOGE(x, msg, ...) do { \
        esp_err_t __; \
        if ((__ = x) != ESP_OK) { \
            PORT_EXIT_CRITICAL(); \
            ESP_LOGE(TAG, msg, ## __VA_ARGS__); \
            return __; \
        } \
    } while (0)

/**
 * Wait specified time for pin to go to a specified state.
 * If timeout is reached and pin doesn't go to a requested state
 * false is returned.
 * The elapsed time is returned in pointer 'duration' if it is not NULL.
 */
static esp_err_t dht_await_pin_state(gpio_num_t pin, uint32_t timeout,
       int expected_pin_state, uint32_t *duration)
{
    /* XXX dht_await_pin_state() should save pin direction and restore
     * the direction before return. however, the SDK does not provide
     * gpio_get_direction().
     */
    gpio_set_direction(pin, GPIO_MODE_INPUT);
    // Enabling pull-up is required if the sensor has no physical pull-up resistor
    gpio_set_pull_mode(pin, GPIO_PULLUP_ONLY);  

    for (uint32_t i = 0; i < timeout; i += DHT_TIMER_INTERVAL)
    {
        // need to wait at least a single interval to prevent reading a jitter
        ets_delay_us(DHT_TIMER_INTERVAL);
        if (gpio_get_level(pin) == expected_pin_state)
        {
            if (duration)
                *duration = i;
            return ESP_OK;
        }
    }

    return ESP_ERR_TIMEOUT;
}

/**
 * Request data from DHT and read raw bit stream.
 * The function call should be protected from task switching.
 * Return false if error occurred.
 */
static inline esp_err_t dht_fetch_data(dht_sensor_type_t sensor_type, gpio_num_t pin, uint8_t data[DHT_DATA_BYTES])
{
    uint32_t low_duration;
    uint32_t high_duration;

    // Phase 'A' pulling signal low to initiate read sequence
    gpio_set_direction(pin, GPIO_MODE_OUTPUT_OD);
    gpio_set_level(pin, 0);
    ets_delay_us(sensor_type == DHT_TYPE_SI7021 ? 500 : 20000);
    gpio_set_level(pin, 1);

    // Step through Phase 'B', 40us
    CHECK_LOGE(dht_await_pin_state(pin, 40, 0, NULL),
            "Initialization error, problem in phase 'B'");
    // Step through Phase 'C', 88us
    CHECK_LOGE(dht_await_pin_state(pin, 88, 1, NULL),
            "Initialization error, problem in phase 'C'");
    // Step through Phase 'D', 88us
    CHECK_LOGE(dht_await_pin_state(pin, 88, 0, NULL),
            "Initialization error, problem in phase 'D'");

    // Read in each of the 40 bits of data...
    for (int i = 0; i < DHT_DATA_BITS; i++)
    {
        CHECK_LOGE(dht_await_pin_state(pin, 65, 1, &low_duration),
                "LOW bit timeout");
        CHECK_LOGE(dht_await_pin_state(pin, 75, 0, &high_duration),
                "HIGH bit timeout");

        uint8_t b = i / 8;
        uint8_t m = i % 8;
        if (!m)
            data[b] = 0;

        data[b] |= (high_duration > low_duration) << (7 - m);
    }

    return ESP_OK;
}

/**
 * Pack two data bytes into single value and take into account sign bit.
 */
static inline int16_t dht_convert_data(dht_sensor_type_t sensor_type, uint8_t msb, uint8_t lsb)
{
    int16_t data;

    if (sensor_type == DHT_TYPE_DHT11)
    {
        data = msb * 10;
    }
    else
    {
        data = msb & 0x7F;
        data <<= 8;
        data |= lsb;
        if (msb & BIT(7))
            data = -data;       // convert it to negative
    }

    return data;
}

esp_err_t dht_read_data(dht_sensor_type_t sensor_type, gpio_num_t pin,
        int16_t *humidity, int16_t *temperature)
{
    CHECK_ARG(humidity || temperature);

    uint8_t data[DHT_DATA_BYTES] = { 0 };

    gpio_set_direction(pin, GPIO_MODE_OUTPUT_OD);
    gpio_set_level(pin, 1);

    PORT_ENTER_CRITICAL();
    esp_err_t result = dht_fetch_data(sensor_type, pin, data);
    if (result == ESP_OK)
        PORT_EXIT_CRITICAL();

    /* restore GPIO direction because, after calling dht_fetch_data(), the
     * GPIO direction mode changes */
    gpio_set_direction(pin, GPIO_MODE_OUTPUT_OD);
    gpio_set_level(pin, 1);

    if (result != ESP_OK)
        return result;

    if (data[4] != ((data[0] + data[1] + data[2] + data[3]) & 0xFF))
    {
        ESP_LOGE(TAG, "Checksum failed, invalid data received from sensor");
        return ESP_ERR_INVALID_CRC;
    }

    if (humidity)
        *humidity = dht_convert_data(sensor_type, data[0], data[1]);
    if (temperature)
        *temperature = dht_convert_data(sensor_type, data[2], data[3]);

    ESP_LOGD(TAG, "Sensor data: humidity=%d, temp=%d", *humidity, *temperature);

    return ESP_OK;
}

esp_err_t dht_read_float_data(dht_sensor_type_t sensor_type, gpio_num_t pin,
        float *humidity, float *temperature)
{
    CHECK_ARG(humidity || temperature);

    int16_t i_humidity, i_temp;

    esp_err_t res = dht_read_data(sensor_type, pin, humidity ? &i_humidity : NULL, temperature ? &i_temp : NULL);
    if (res != ESP_OK)
        return res;

    if (humidity)
        *humidity = i_humidity / 10.0;
    if (temperature)
        *temperature = i_temp / 10.0;

    return ESP_OK;
}

ADS1115 Library Header File

C Header File
#pragma once

#include "stdint.h"
#include "esp_log.h"

#define PORT_A_SDA_PIN GPIO_NUM_32
#define PORT_A_SCL_PIN GPIO_NUM_33
#define PORT_A_I2C_STANDARD_BAUD 100000

#define ADS1115_ADDRESS                           0x48 ///< 1001 000 (ADDR = GND)
/*=========================================================================*/

/*=========================================================================
    POINTER REGISTER
-----------------------------------------------------------------------*/
#define ADS1115_REG_POINTER_MASK                  0x03      ///< Point mask
#define ADS1115_REG_POINTER_CONVERT               0x00   ///< Conversion
#define ADS1115_REG_POINTER_CONFIG                0x01    ///< Configuration
#define ADS1115_REG_POINTER_LOWTHRESH             0x02 ///< Low threshold
#define ADS1115_REG_POINTER_HITHRESH              0x03  ///< High threshold
/*=========================================================================*/

/*=========================================================================
    CONFIG REGISTER
-----------------------------------------------------------------------*/
#define ADS1115_REG_CONFIG_OS_MASK                0x8000 ///< OS Mask
#define ADS1115_REG_CONFIG_OS_SINGLE              0x8000 ///< Write: Set to start a single-conversion
#define ADS1115_REG_CONFIG_OS_BUSY                0x0000 ///< Read: Bit = 0 when conversion is in progress
#define ADS1115_REG_CONFIG_OS_NOTBUSY             0x8000 ///< Read: Bit = 1 when device is not performing a conversion

#define ADS1115_REG_CONFIG_MUX_MASK               0x7000 ///< Mux Mask
#define ADS1115_REG_CONFIG_MUX_DIFF_0_1           0x0000 ///< Differential P = AIN0, N = AIN1 (default)
#define ADS1115_REG_CONFIG_MUX_DIFF_0_3           0x1000 ///< Differential P = AIN0, N = AIN3
#define ADS1115_REG_CONFIG_MUX_DIFF_1_3           0x2000 ///< Differential P = AIN1, N = AIN3
#define ADS1115_REG_CONFIG_MUX_DIFF_2_3           0x3000 ///< Differential P = AIN2, N = AIN3
#define ADS1115_REG_CONFIG_MUX_SINGLE_0           0x4000 ///< Single-ended AIN0
#define ADS1115_REG_CONFIG_MUX_SINGLE_1           0x5000 ///< Single-ended AIN1
#define ADS1115_REG_CONFIG_MUX_SINGLE_2           0x6000 ///< Single-ended AIN2
#define ADS1115_REG_CONFIG_MUX_SINGLE_3           0x7000 ///< Single-ended AIN3

#define ADS1115_REG_CONFIG_PGA_MASK               0x0E00   ///< PGA Mask
#define ADS1115_REG_CONFIG_PGA_6_144V             0x0000 ///< +/-6.144V range = Gain 2/3
#define ADS1115_REG_CONFIG_PGA_4_096V             0x0200 ///< +/-4.096V range = Gain 1
#define ADS1115_REG_CONFIG_PGA_2_048V             0x0400 ///< +/-2.048V range = Gain 2 (default)
#define ADS1115_REG_CONFIG_PGA_1_024V             0x0600 ///< +/-1.024V range = Gain 4
#define ADS1115_REG_CONFIG_PGA_0_512V             0x0800 ///< +/-0.512V range = Gain 8
#define ADS1115_REG_CONFIG_PGA_0_256V             0x0A00 ///< +/-0.256V range = Gain 16

#define ADS1115_REG_CONFIG_MODE_MASK              0x0100   ///< Mode Mask
#define ADS1115_REG_CONFIG_MODE_CONTIN            0x0000 ///< Continuous conversion mode
#define ADS1115_REG_CONFIG_MODE_SINGLE            0x0100 ///< Power-down single-shot mode (default)

#define ADS1115_REG_CONFIG_RATE_MASK              0x00E0 ///< Data Rate Mask

#define ADS1115_REG_CONFIG_CMODE_MASK             0x0010 ///< CMode Mask
#define ADS1115_REG_CONFIG_CMODE_TRAD             0x0000 ///< Traditional comparator with hysteresis (default)
#define ADS1115_REG_CONFIG_CMODE_WINDOW           0x0010  ///< Window comparator

#define ADS1115_REG_CONFIG_CPOL_MASK              0x0008 ///< CPol Mask
#define ADS1115_REG_CONFIG_CPOL_ACTVLOW           0x0000 ///< ALERT/RDY pin is low when active (default)
#define ADS1115_REG_CONFIG_CPOL_ACTVHI            0x0008 ///< ALERT/RDY pin is high when active

#define ADS1115_REG_CONFIG_CLAT_MASK              0x0004 ///< Determines if ALERT/RDY pin latches once asserted
#define ADS1115_REG_CONFIG_CLAT_NONLAT            0x0000  ///< Non-latching comparator (default)
#define ADS1115_REG_CONFIG_CLAT_LATCH             0x0004 ///< Latching comparator

#define ADS1115_REG_CONFIG_CQUE_MASK              0x0003 ///< CQue Mask
#define ADS1115_REG_CONFIG_CQUE_1CONV             0x0000 ///< Assert ALERT/RDY after one conversions
#define ADS1115_REG_CONFIG_CQUE_2CONV             0x0001 ///< Assert ALERT/RDY after two conversions
#define ADS1115_REG_CONFIG_CQUE_4CONV             0x0002 ///< Assert ALERT/RDY after four conversions
#define ADS1115_REG_CONFIG_CQUE_NONE              0x0003 ///< Disable the comparator and put ALERT/RDY in high state (default)
/*=========================================================================*/

/** Data rates */
#define RATE_ADS1115_8SPS                         0x0000  ///Slowest speed with up to 8 conversions each second, this is also the range with least noise.
#define RATE_ADS1115_16SPS                        0x0020  
#define RATE_ADS1115_32SPS                        0x0040  
#define RATE_ADS1115_64SPS                        0x0060  
#define RATE_ADS1115_128SPS                       0x0080  ///< 128 samples per second (default)
#define RATE_ADS1115_250SPS                       0x00A0 
#define RATE_ADS1115_475SPS                       0x00C0 
#define RATE_ADS1115_860SPS                       0x00E0  ///Fastest speed with up to 860 conversions each second.

/** Gain settings */
typedef enum {
  GAIN_TWOTHIRDS = ADS1115_REG_CONFIG_PGA_6_144V,//Maximum scale is 6.144V, but input will be limited by VCC  
  GAIN_ONE = ADS1115_REG_CONFIG_PGA_4_096V,      //Maximum scale is 4.096V, but will be lower in a 3.3V system
  GAIN_TWO = ADS1115_REG_CONFIG_PGA_2_048V,      //Maximum scale is 2.048V
  GAIN_FOUR = ADS1115_REG_CONFIG_PGA_1_024V,     //Maximum scale is 1.024V
  GAIN_EIGHT = ADS1115_REG_CONFIG_PGA_0_512V,    //Maximum scale is 0.512V
  GAIN_SIXTEEN = ADS1115_REG_CONFIG_PGA_0_256V   //Maximum scale is 0.256V
} adsGain_t;

//uint16_t m_dataRate; // = RATE_ADS1115_860SPS;       // 

void ADS1115_I2CInit();
esp_err_t ADS1115_I2C_Read(uint8_t register_address, uint8_t *data, uint16_t length);
esp_err_t ADS1115_I2C_Write(uint8_t register_address, uint8_t *data, uint16_t length);
//void ADS1115_I2C_Close(ads1115_device);
bool ADS1115_I2C_ReadU16(uint8_t reg_addr, uint16_t* value);
bool ADS1115_I2C_WriteU16(uint8_t reg_addr, uint16_t value);
void ADS1115_setGain(adsGain_t gain);
adsGain_t ADS1115_getGain();
void ADS1115_setDataRate(uint16_t rate);
uint16_t ADS1115_getDataRate();
int16_t ADS1115_readADC_SingleEnded(uint8_t channel);
void ADS1115_startComparator_SingleEnded(uint8_t channel, int16_t threshold);
int16_t ADS1115_getLastConversionResults();
float ADS1115_computeVolts(int16_t counts);
bool ADS1115_conversionComplete();

ADS1115 Library Source File

C/C++
#include "i2c_device.h"
#include "esp_err.h"
#include "ads1115.h"


static I2CDevice_t ads1115_device;

void ADS1115_I2CInit() {
    ads1115_device = i2c_malloc_device(I2C_NUM_0, PORT_A_SDA_PIN, PORT_A_SCL_PIN, PORT_A_I2C_STANDARD_BAUD, ADS1115_ADDRESS);
}

esp_err_t ADS1115_I2C_Read(uint8_t register_address, uint8_t *data, uint16_t length){
    return i2c_read_bytes(ads1115_device, register_address, data, length);
}

esp_err_t ADS1115_I2C_Write(uint8_t register_address, uint8_t *data, uint16_t length){
    return i2c_write_bytes(ads1115_device, register_address, data, length);
}
/*
void ADS1115_I2C_Close(ads1115_device){
    i2c_free_device(ads1115_device);
}
*/
bool ADS1115_I2C_ReadU16(uint8_t reg_addr, uint16_t* value) {
  uint8_t read_buf[2] = {0x00, 0x00};
  bool result = ADS1115_I2C_Read(reg_addr, read_buf, 2);
  *value = (read_buf[0] << 8) | read_buf[1];
  return result;
}

bool ADS1115_I2C_WriteU16(uint8_t reg_addr, uint16_t value) {
  uint8_t write_buf[2];
  write_buf[0] = value >> 8;
  write_buf[1] = value & 0xff;
  return ADS1115_I2C_Write(reg_addr, write_buf, 2);
}

static adsGain_t m_gain = GAIN_TWOTHIRDS;               //+/- 6.144V range (limited to VDD +0.3V max!) 
uint16_t m_dataRate = RATE_ADS1115_860SPS;       //

void ADS1115_setGain(adsGain_t gain) { m_gain = gain; }
adsGain_t ADS1115_getGain() { return m_gain; }
void ADS1115_setDataRate(uint16_t rate) { m_dataRate = rate; }
uint16_t ADS1115_getDataRate() { return m_dataRate; }

int16_t ADS1115_readADC_SingleEnded(uint8_t channel) {
  if (channel > 3) {
    return 0;
  }

  // Start with default values
  uint16_t config =
      ADS1115_REG_CONFIG_CQUE_NONE |    // Disable the comparator (default val)
      ADS1115_REG_CONFIG_CLAT_NONLAT |  // Non-latching (default val)
      ADS1115_REG_CONFIG_CPOL_ACTVLOW | // Alert/Rdy active low   (default val)
      ADS1115_REG_CONFIG_CMODE_TRAD |   // Traditional comparator (default val)
      ADS1115_REG_CONFIG_MODE_SINGLE;   // Single-shot mode (default)

  // Set PGA/voltage range
  config |= m_gain;

  // Set data rate
  config |= m_dataRate;

  // Set single-ended input channel
  switch (channel) {
  case (0):
    config |= ADS1115_REG_CONFIG_MUX_SINGLE_0;
    break;
  case (1):
    config |= ADS1115_REG_CONFIG_MUX_SINGLE_1;
    break;
  case (2):
    config |= ADS1115_REG_CONFIG_MUX_SINGLE_2;
    break;
  case (3):
    config |= ADS1115_REG_CONFIG_MUX_SINGLE_3;
    break;
  }

  // Set 'start single-conversion' bit
  config |= ADS1115_REG_CONFIG_OS_SINGLE;

  // Write config register to the ADC
  ADS1115_I2C_WriteU16(ADS1115_REG_POINTER_CONFIG, config);

  // Wait for the conversion to complete
  while (!ADS1115_conversionComplete())
    ;

  // Read the conversion results
  return ADS1115_getLastConversionResults();
}


void ADS1115_startComparator_SingleEnded(uint8_t channel, int16_t threshold) {
  // Start with default values
  uint16_t config =
      ADS1115_REG_CONFIG_CQUE_1CONV |   // Comparator enabled and asserts on 1
                                        // match
      ADS1115_REG_CONFIG_CLAT_LATCH |   // Latching mode
      ADS1115_REG_CONFIG_CPOL_ACTVLOW | // Alert/Rdy active low   (default val)
      ADS1115_REG_CONFIG_CMODE_TRAD |   // Traditional comparator (default val)
      ADS1115_REG_CONFIG_MODE_CONTIN |  // Continuous conversion mode
      ADS1115_REG_CONFIG_MODE_CONTIN;   // Continuous conversion mode

  // Set PGA/voltage range
  config |= m_gain;

  // Set data rate
  config |= m_dataRate;

  // Set single-ended input channel
  switch (channel) {
  case (0):
    config |= ADS1115_REG_CONFIG_MUX_SINGLE_0;
    break;
  case (1):
    config |= ADS1115_REG_CONFIG_MUX_SINGLE_1;
    break;
  case (2):
    config |= ADS1115_REG_CONFIG_MUX_SINGLE_2;
    break;
  case (3):
    config |= ADS1115_REG_CONFIG_MUX_SINGLE_3;
    break;
  }

  // Set the high threshold register
  ADS1115_I2C_WriteU16(ADS1115_REG_POINTER_HITHRESH, threshold);

  // Write config register to the ADC
  ADS1115_I2C_WriteU16(ADS1115_REG_POINTER_CONFIG, config);
}

int16_t ADS1115_getLastConversionResults() {
  // Read the conversion results
  uint16_t value = 0x00;
  ADS1115_I2C_ReadU16(ADS1115_REG_POINTER_CONVERT, &value);
  return value;
}

float ADS1115_computeVolts(int16_t counts) {
  // see data sheet Table 3
  float fsRange;
  switch (m_gain) {
  case GAIN_TWOTHIRDS:
    fsRange = 6.144f;
    break;
  case GAIN_ONE:
    fsRange = 4.096f;
    break;
  case GAIN_TWO:
    fsRange = 2.048f;
    break;
  case GAIN_FOUR:
    fsRange = 1.024f;
    break;
  case GAIN_EIGHT:
    fsRange = 0.512f;
    break;
  case GAIN_SIXTEEN:
    fsRange = 0.256f;
    break;
  default:
    fsRange = 0.0f;
  }
  return counts * (fsRange / 32768);
}

bool ADS1115_conversionComplete() {
  uint16_t value = 0x00;
  ADS1115_I2C_ReadU16(ADS1115_REG_POINTER_CONFIG, &value);
  return (value & 0x8000) != 0;
}

GitHub Link of IoT Healthy Toilet Project

Credits

Md. Khairul Alam

Md. Khairul Alam

67 projects • 582 followers
Developer, Maker & Hardware Hacker. Currently working as a faculty at the University of Asia Pacific, Dhaka, Bangladesh.

Comments