Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!
Makestreme
Published © GPL3+

Gus: a Smart Robot Whose Eyes Indicate Your Room's Health

Meet Gus, a fun robot that helps you monitor air quality, temp, and humidity by making his eyes react to your room’s health!

IntermediateFull instructions provided8 hours627
Gus: a Smart Robot Whose Eyes Indicate Your Room's Health

Things used in this project

Hardware components

Grove - OLED Display 1.12'' V2
Seeed Studio Grove - OLED Display 1.12'' V2
×1
DHT22 Temperature Sensor
DHT22 Temperature Sensor
×1
mq135 Air quality sensor
×1
Seeed Studio XIAO ESP32S3 Sense
Seeed Studio XIAO ESP32S3 Sense
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

Wiring diagram

Code

Get R0

Arduino
Use this to get R0 of air quality sensor
#include "MQ135.h"
void setup (){
Serial.begin (9600);
}
void loop() {
MQ135 gasSensor = MQ135(A0); // Attach sensor to pin A0
float rzero = gasSensor.getRZero();
Serial.println (rzero);
delay(1000);
}

Test oled

Arduino
Use this to test the OLED display
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// Define the screen size and the OLED reset pin
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET    -1  // No reset pin needed for SSD1315

// Create an OLED display object
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Function to draw open eyes
void drawOpenEyes() {
  display.clearDisplay();
  // Draw two open eyes (represented by circles)
  display.fillEllipse(40, 32, 10, WHITE);  // Left eye
  display.fillEllipse(88, 32, 10, WHITE);  // Right eye
  display.display();
}

// Function to draw closed eyes (simulating a blink)
void drawClosedEyes() {
  display.clearDisplay();
  // Draw two closed eyes (represented by horizontal lines)
  display.fillRect(30, 28, 20, 4, WHITE);  // Left eye closed
  display.fillRect(78, 28, 20, 4, WHITE);  // Right eye closed
  display.display();
}

void setup() {
  // Initialize the display
  if (!display.begin(SSD1306_I2C_ADDRESS, OLED_RESET)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;); // Infinite loop if display setup fails
  }

  display.display();  // Initial screen update
  delay(2000);        // Wait for 2 seconds
}

void loop() {
  drawOpenEyes();  // Draw open eyes
  delay(500);      // Wait for 0.5 second

  drawClosedEyes();  // Draw closed eyes (blink)
  delay(200);        // Wait for 0.2 second

  drawOpenEyes();  // Draw open eyes again
  delay(500);      // Wait for 0.5 second
}

Get PPM

Arduino
Use this to get PPM from Air quality sensor
#include <MQ135.h>

/*  MQ135 gas sensor
    Datasheet can be found here: https://www.olimex.com/Products/Components/Sensors/SNS-MQ135/resources/SNS-MQ135.pdf

    Application
    They are used in air quality control equipments for buildings/offices, are suitable for detecting of NH3, NOx, alcohol, Benzene, smoke, CO2, etc

    Original creator of this library: https://github.com/GeorgK/MQ135
*/

#define PIN_MQ135 A0

MQ135 mq135_sensor(PIN_MQ135);

float temperature = 21.0; // Assume current temperature. Recommended to measure with DHT22
float humidity = 25.0; // Assume current humidity. Recommended to measure with DHT22

void setup() {
  Serial.begin(9600);
}

void loop() {
  float rzero = mq135_sensor.getRZero();
  float correctedRZero = mq135_sensor.getCorrectedRZero(temperature, humidity);
  float resistance = mq135_sensor.getResistance();
  float ppm = mq135_sensor.getPPM();
  float correctedPPM = mq135_sensor.getCorrectedPPM(temperature, humidity);

  Serial.print("MQ135 RZero: ");
  Serial.print(rzero);
  Serial.print("\t Corrected RZero: ");
  Serial.print(correctedRZero);
  Serial.print("\t Resistance: ");
  Serial.print(resistance);
  Serial.print("\t PPM: ");
  Serial.print(ppm);
  Serial.print("\t Corrected PPM: ");
  Serial.print(correctedPPM);
  Serial.println("ppm");

  delay(300);
}

Test DHT22

Arduino
// Example testing sketch for various DHT humidity/temperature sensors
// Written by ladyada, public domain

// REQUIRES the following Arduino libraries:
// - DHT Sensor Library: https://github.com/adafruit/DHT-sensor-library
// - Adafruit Unified Sensor Lib: https://github.com/adafruit/Adafruit_Sensor

#include "DHT.h"

#define DHTPIN A0     // Digital pin connected to the DHT sensor
// Feather HUZZAH ESP8266 note: use pins 3, 4, 5, 12, 13 or 14 --
// Pin 15 can work but DHT must be disconnected during program upload.

// Uncomment whatever type you're using!
//#define DHTTYPE DHT11   // DHT 11
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321
//#define DHTTYPE DHT21   // DHT 21 (AM2301)

DHT dht(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(9600);
  Serial.println(F("DHTxx test!"));

  dht.begin();
}

void loop() {
  // Wait a few seconds between measurements.
  delay(2000);

  // Reading temperature or humidity takes about 250 milliseconds!
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  float h = dht.readHumidity();
  // Read temperature as Celsius (the default)
  float t = dht.readTemperature();
  // Read temperature as Fahrenheit (isFahrenheit = true)
  float f = dht.readTemperature(true);

  // Check if any reads failed and exit early (to try again).
  if (isnan(h) || isnan(t) || isnan(f)) {
    Serial.println(F("Failed to read from DHT sensor!"));
    return;
  }

  // Compute heat index in Fahrenheit (the default)
  float hif = dht.computeHeatIndex(f, h);
  // Compute heat index in Celsius (isFahreheit = false)
  float hic = dht.computeHeatIndex(t, h, false);

  Serial.print(F("Humidity: "));
  Serial.print(h);
  Serial.print(F("%  Temperature: "));
  Serial.print(t);
  Serial.print(F("C "));
  Serial.print(f);
  Serial.print(F("F  Heat index: "));
  Serial.print(hic);
  Serial.print(F("C "));
  Serial.print(hif);
  Serial.println(F("F"));
}

Gus_main

Arduino
This is the main code
#include <DHT.h>
#include <MQ135.h>
#include <time.h>
#include <U8g2lib.h>

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);  // High speed I2C

// DHT Sensor setup
#define DHTPIN A1    // Pin connected to DHT sensor
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

// Air Quality Sensor setup
#define MQ135_PIN A0 // Pin connected to MQ135 sensor

MQ135 mq135_sensor(MQ135_PIN);

// Comfort thresholds
const float TEMP_MIN = 18.0;  // Minimum comfortable temperature
const float TEMP_MAX = 35.0;  // Maximum comfortable temperature
const float HUM_MIN = 30.0;   // Minimum comfortable humidity
const float HUM_MAX = 75.0;   // Maximum comfortable humidity
const int AQI_THRESHOLD = 500; // Threshold for poor air quality (adjust based on sensor calibration)

// Variables
float temperature;
float humidity;
int airQuality;

// Animation control
unsigned long lastBlinkTime = 0;
bool isBlinking = false;
int blinkCount = 0

void setup() {

  // Initialize serial communication
  Serial.begin(9600);

  // Initialize OLED display
  u8g2.begin();

  // Initialize DHT sensor
  dht.begin();
}

void loop() {

  u8g2.clearBuffer(); // clear the internal memory

  // Read sensor data
  temperature = dht.readHumidity();
  // Read temperature as Celsius (the default)
  humidity = dht.readTemperature();

  // Validate sensor data
  if (isnan(temperature) || isnan(humidity)) {
    Serial.println(F("Failed to read from DHT sensor!"));
    return;
  }

  airQuality = mq135_sensor.getCorrectedPPM(temperature, humidity);

  Serial.print(F("Temp: "));
  Serial.print(temperature);
  Serial.print(F("  Humidity: "));
  Serial.print(humidity);
  Serial.print(F("  AQI: "));
  Serial.print(airQuality);

  // Calculate eyelid level based on conditions
  int eyelidLevel;

  eyelidLevel = calculateEyelidLevel(temperature, humidity, airQuality); 

  handleAnimations();

  // Display the condition on OLED
  displayEyelids(eyelidLevel);

  // Delay for the next loop
  delay(500);
}

int calculateEyelidLevel(float temp, float hum, int airQ) {
  int comfortScore = 100; // Start with max comfort

  // Adjust score based on temperature
  if (temp < TEMP_MIN || temp > TEMP_MAX) comfortScore -= 30;

  // Adjust score based on humidity
  if (hum < HUM_MIN || hum > HUM_MAX) comfortScore -= 30;

  // Adjust score based on air quality
  if (airQ > AQI_THRESHOLD) comfortScore -= 40;

  // Determine eyelid level
  if (comfortScore > 80) return 0;      // Fully open eyes
  else if (comfortScore > 50) return 1; // Half-closed eyes
  else return 2;                        // Mostly closed eyes
}

void handleAnimations() {
  unsigned long currentTime = millis();

  // Handle blinking
  if (currentTime - lastBlinkTime > random(180000, 300000)) { // Blink every ~3-5 minutes
    isBlinking = true;
    lastBlinkTime = currentTime;
  }

  if (blinkCount >= 2)
  {
    isBlinking = false;
    blinkCount = 0;
  }
}

void displayEyelids(int level) {

  // Define eye dimensions
  int eyeWidth = 58;  // Width of each eye
  int eyeHeight = 64; // Height of each eye
  int eyeSpacing = 10; // Space between the two eyes
  int eyelidHeight = 0;

  // Eye positions
  int leftEyeX = 0;
  int rightEyeX = leftEyeX + eyeWidth + eyeSpacing;
  int eyeY = 0; // Top position of the eyes

  // Adjust eyelid height for regular states (non-blinking or when eye lid is almost fully down)
  if (!isBlinking || eyelidHeight >= (3 * eyeHeight) / 4) {
    if (level == 1) {
      eyelidHeight = eyeHeight / 2; // Half-closed eyes
    } else if (level == 2) {
      eyelidHeight = (3 * eyeHeight) / 4; // Mostly closed eyes
    }
  } 
  
  // blink twice
  else {
    int blink_height = eyelidHeight;
    for (int i = 1; i <= 6; i++) { //eyelid goes down
      u8g2.clearBuffer();
      blink_height = eyelidHeight + int(i*(eyeHeight-eyelidHeight)/6); //divide into six steps
      drawEye(leftEyeX, eyeY, eyeWidth, eyeHeight, blink_height);
      drawEye(rightEyeX, eyeY, eyeWidth, eyeHeight, blink_height);
      u8g2.sendBuffer();
  }
  for (int i = 5; i >= 0; i--) { //eyelid comes up
      u8g2.clearBuffer();
      blink_height = eyelidHeight + int(i*(eyeHeight-eyelidHeight)/6); //divide into six steps
      drawEye(leftEyeX, eyeY, eyeWidth, eyeHeight, blink_height);
      drawEye(rightEyeX, eyeY, eyeWidth, eyeHeight, blink_height);
      u8g2.sendBuffer();
  }
  blinkCount += 1;
  }

  // Draw eyes
  drawEye(leftEyeX, eyeY, eyeWidth, eyeHeight, eyelidHeight);
  drawEye(rightEyeX, eyeY, eyeWidth, eyeHeight, eyelidHeight);

  u8g2.sendBuffer(); // Display the drawing

}

void drawEye(int x, int y, int w, int h, int eyelidHeight) {

  u8g2.setDrawColor(1);  // Set fill color to white
  
  // Draw the filled ellipse (eye shape)
  u8g2.drawFilledEllipse(x + w / 2, y + h / 2, w / 2, h / 2);

  // If eyelidHeight is greater than 0, simulate an eyelid by covering the eye
  if (eyelidHeight > 0) {
    u8g2.setDrawColor(0);  // Set fill color to black
    u8g2.drawBox(x, y, w+1, eyelidHeight);  // Draw black rectangle to simulate eyelid

}
}

Gus Main

Arduino
#include <DHT.h>
#include <MQ135.h>
#include <time.h>
#include <U8g2lib.h>

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);  // High speed I2C

// DHT Sensor setup
#define DHTPIN A1    // Pin connected to DHT sensor
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

// Air Quality Sensor setup
#define MQ135_PIN A0 // Pin connected to MQ135 sensor

MQ135 mq135_sensor(MQ135_PIN);

// Comfort thresholds
const float TEMP_MIN = 18.0;  // Minimum comfortable temperature
const float TEMP_MAX = 35.0;  // Maximum comfortable temperature
const float HUM_MIN = 30.0;   // Minimum comfortable humidity
const float HUM_MAX = 75.0;   // Maximum comfortable humidity
const int AQI_THRESHOLD = 500; // Threshold for poor air quality (adjust based on sensor calibration)

// Variables
float temperature;
float humidity;
int airQuality;

// Animation control
unsigned long lastBlinkTime = 0;
bool isBlinking = false;
int blinkCount = 0;

void drawEye(int x, int y, int w, int h, int eyelidHeight) {

  u8g2.setDrawColor(1);  // Set fill color to white
  
  // Draw the filled ellipse (eye shape)
  u8g2.drawFilledEllipse(x + w / 2, y + h / 2, w / 2, h / 2);

  // If eyelidHeight is greater than 0, simulate an eyelid by covering the eye
  if (eyelidHeight > 0) {
    u8g2.setDrawColor(0);  // Set fill color to black
    u8g2.drawBox(x, y, w+1, eyelidHeight);  // Draw black rectangle to simulate eyelid

}
}

int calculateEyelidLevel(float temp, float hum, int airQ) {
  int comfortScore = 100; // Start with max comfort

  // Adjust score based on temperature
  if (temp < TEMP_MIN || temp > TEMP_MAX) comfortScore -= 30;

  // Adjust score based on humidity
  if (hum < HUM_MIN || hum > HUM_MAX) comfortScore -= 30;

  // Adjust score based on air quality
  if (airQ > AQI_THRESHOLD) comfortScore -= 40;

  // Determine eyelid level
  if (comfortScore > 80) return 0;      // Fully open eyes
  else if (comfortScore > 50) return 1; // Half-closed eyes
  else return 2;                        // Mostly closed eyes
}

void handleAnimations() {
  unsigned long currentTime = millis();

  // Handle blinking
  if (currentTime - lastBlinkTime > random(180000, 300000)) { // Blink every ~3-5 minutes
    isBlinking = true;
    lastBlinkTime = currentTime;
  }

  if (blinkCount >= 2)
  {
    isBlinking = false;
    blinkCount = 0;
  }
}

void displayEyelids(int level) {

  // Define eye dimensions
  int eyeWidth = 58;  // Width of each eye
  int eyeHeight = 64; // Height of each eye
  int eyeSpacing = 10; // Space between the two eyes
  int eyelidHeight = 0;

  // Eye positions
  int leftEyeX = 0;
  int rightEyeX = leftEyeX + eyeWidth + eyeSpacing;
  int eyeY = 0; // Top position of the eyes

  // Adjust eyelid height for regular states (non-blinking or when eye lid is almost fully down)
  if (!isBlinking || eyelidHeight >= (3 * eyeHeight) / 4) {
    if (level == 1) {
      eyelidHeight = eyeHeight / 2; // Half-closed eyes
    } else if (level == 2) {
      eyelidHeight = (3 * eyeHeight) / 4; // Mostly closed eyes
    }
  } 
  
  // blink twice
  else {
    int blink_height = eyelidHeight;
    for (int i = 1; i <= 6; i++) { //eyelid goes down
      u8g2.clearBuffer();
      blink_height = eyelidHeight + int(i*(eyeHeight-eyelidHeight)/6); //divide into six steps
      drawEye(leftEyeX, eyeY, eyeWidth, eyeHeight, blink_height);
      drawEye(rightEyeX, eyeY, eyeWidth, eyeHeight, blink_height);
      u8g2.sendBuffer();
  }
  for (int i = 5; i >= 0; i--) { //eyelid comes up
      u8g2.clearBuffer();
      blink_height = eyelidHeight + int(i*(eyeHeight-eyelidHeight)/6); //divide into six steps
      drawEye(leftEyeX, eyeY, eyeWidth, eyeHeight, blink_height);
      drawEye(rightEyeX, eyeY, eyeWidth, eyeHeight, blink_height);
      u8g2.sendBuffer();
  }
  blinkCount += 1;
  }

  // Draw eyes
  drawEye(leftEyeX, eyeY, eyeWidth, eyeHeight, eyelidHeight);
  drawEye(rightEyeX, eyeY, eyeWidth, eyeHeight, eyelidHeight);

  u8g2.sendBuffer(); // Display the drawing

}

void setup() {

  // Initialize serial communication
  Serial.begin(9600);

  // Initialize OLED display
  u8g2.begin();

  // Initialize DHT sensor
  dht.begin();
}

void loop() {

  u8g2.clearBuffer(); // clear the internal memory

  // Read sensor data
  temperature = dht.readHumidity();
  // Read temperature as Celsius (the default)
  humidity = dht.readTemperature();

  // Validate sensor data
  if (isnan(temperature) || isnan(humidity)) {
    Serial.println(F("Failed to read from DHT sensor!"));
    return;
  }

  airQuality = mq135_sensor.getCorrectedPPM(temperature, humidity);

  Serial.print(F("Temp: "));
  Serial.print(temperature);
  Serial.print(F("  Humidity: "));
  Serial.print(humidity);
  Serial.print(F("  AQI: "));
  Serial.print(airQuality);

  // Calculate eyelid level based on conditions
  int eyelidLevel;

  eyelidLevel = calculateEyelidLevel(temperature, humidity, airQuality); 

  handleAnimations();

  // Display the condition on OLED
  displayEyelids(eyelidLevel);

  // Delay for the next loop
  delay(500);
}

Credits

Makestreme
9 projects • 10 followers
I make DIY projects that are fun and interesting! An engineer by profession :) youtube.com/@makestreme
Contact

Comments

Please log in or sign up to comment.