Caden Gamache
Published © MIT

Stranger Watering System

This is a smart watering system made for the Internet of Things boot-camp at CNM. I took inspiration from Stranger Things in my design.

IntermediateWork in progress66
Stranger Watering System

Things used in this project

Story

Read more

Custom parts and enclosures

Previous Idea

I almost went the simple route and would have made a waterfall and instead of using a pump to give water I would flip up a servo. This idea didn't excite me in any way so I later abandoned it.

Your Plants House (With component holes)

I used an AI generated infill pattern to use the least material possible. It was my first time and I am extremely surprised by the quality.

Base House Design

This is an untouched model of the floor plan.

Schematics

Fritz

The relay's NO pin is meant to plug into a water pump.

Schematic

The relay's NO pin is meant to plug into a water pump.

Code

Stranger_Watering_System

C/C++
/*
 * Project L11_04_SeeedSensors
 * Description: Smart Watering System that takes inspiration from Stranger Things. Uses neopixels to dynamicly dysplay the plants nees
 * Author: Caden Gamache
 * Date: 03/17/2023 - 
 * 
 ********* NOTES *********
 * Neopixel1 = Bedroom
 * Neopixel2 = Kitchen
 * Neopixel3 = Hall Right
 * Neopixel4 = Hall Middle
 * Neopixel5 = Hall Left
 * Neopixel6 = Bathroom
 * Neopixel7 = Meditation
 * Neopixel8 = Living room
 * Neopixel9 = Fireplace
`*/

/****************** Libraries ******************/ 
#include "credentials.h"

#include <math.h>

#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT/Adafruit_MQTT_SPARK.h"
#include "Adafruit_MQTT/Adafruit_MQTT.h"

#include "Air_Quality_Sensor.h"

#include <Adafruit_BME280.h>

#include <Adafruit_SSD1306.h>
#include <Adafruit_GFX.h>

#include <Neopixel.h>

/****************** Global State ******************/ 
TCPClient TheClient;
Adafruit_MQTT_SPARK mqtt(&TheClient,AIO_SERVER,AIO_SERVERPORT,AIO_USERNAME,AIO_KEY); 

/********************* Feeds *********************/  
Adafruit_MQTT_Subscribe sub_buttonOnOff = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/buttonOnOff"); 
Adafruit_MQTT_Publish pub_concentration = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/concentration");
Adafruit_MQTT_Publish pub_airQuality = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/airQuality");
Adafruit_MQTT_Publish pub_tempF = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/tempF");
Adafruit_MQTT_Publish pub_humidity = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/humidity");
Adafruit_MQTT_Publish pub_pressure = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/pressure");
Adafruit_MQTT_Publish pub_moisture = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/moisture");
Adafruit_MQTT_Publish pub_buttonOnOff = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/buttonOnOff");
Adafruit_MQTT_Publish pub_currentRoom = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/currentRoom");

/******************* Variables *******************/
// Pins
const int DUSTPIN = D8;
const int AIRQUALITYPIN = A0;
const int MOISTUREPIN = A5;
const int BUTTONPIN = D2;
const int PUMPPPIN = D3;
const int NEOPIXPIN = TX;

//misc
int pubNum;

// Generic loop variables
int i, j, k;

// Bools
bool buttonOnOff, subButtonOnOff, fireUpDown;

// Timers
unsigned int pubTime, airTime, dustTime, fireTime, flickerTime, plantTime, moistureTime;
unsigned int sampleTime = 30000;

// Dust Sensor
unsigned int duration, lowPulseOccupancy;
float ratio, concentration;

// Air Quality Sensor
int quality, airValue;

// BME Sensor
bool status;
int tempF, pressureinHG, humidity;

// Moisture Sensor
int moisture;

// Neopixels
  // Setup
  const int PIXNUM = 46;
  int brightness = 25;
  // Fireplace
  int randomNum1, randomNum2, randomBright;
  int fireMax;
  // Room Management
  int currentPlantPos, lastFavRoom, randomRoom, lastRoom;

/********************* Objects *********************/ 
AirQualitySensor airQualityPatrol(AIRQUALITYPIN);
Adafruit_BME280 bme;
Adafruit_SSD1306 display(D4);
Adafruit_NeoPixel neopix(PIXNUM,NEOPIXPIN,WS2812B);

/******************** Functions ********************/
// Checks if wifi is connected and re-connects if false. If true return without doing anything
void MQTT_connect();
// Sends a small ping every two minutes to minimize disconnection chance
bool MQTT_ping();
// Fills a range of pixels instantly
void pixelFillInstant(int start, int end,int color);
// Creepy flickering light effect that goes off when a condition is met
void creepyFlicker();
// Checks the plants current room and turns on a neopixel
void lightPlantPos(int plantPos);

SYSTEM_MODE(SEMI_AUTOMATIC);

void setup() {
  // Serial Monitor
  Serial.begin(9600);
  waitFor(Serial.isConnected,10000);

  // Wifi
  WiFi.on();
  WiFi.connect();
  while(WiFi.connecting()) {
    Serial.printf(".");
  }

  // Setup MQTT subscription
  mqtt.subscribe(&sub_buttonOnOff);

  // Pins
  pinMode(DUSTPIN,INPUT);
  pinMode(MOISTUREPIN,INPUT);
  pinMode(BUTTONPIN,INPUT);
  pinMode(PUMPPPIN,OUTPUT);

  // Get current time to measure sample time
  dustTime = millis();
  airTime = millis();

  // Air quality
  while (!Serial);
  Serial.println("Waiting sensor to init...");
  delay(20000);
  if (airQualityPatrol.init()) {
    Serial.println("Sensor ready.");
  }
  else {
    Serial.println("Sensor ERROR!");
  }

  // BME Sensor
  bme.begin(0x76);
  status = bme.begin(0x76);
  if (status == FALSE) {
    Serial.printf("failed to connect bme device");
  }

  // Display
  display.begin(SSD1306_SWITCHCAPVCC,0x3C);
  display.clearDisplay();
  display.display();
  display.setCursor(0,0);
  display.setTextColor(WHITE);
  display.setTextSize(1);

  // Neopixels
  neopix.begin();
  neopix.setBrightness(brightness);
  lastFavRoom = 100;
  fireMax = random(10,20);

  // Timers
  fireTime = millis();

}

void loop() {
  MQTT_connect();
  MQTT_ping();
  display.clearDisplay();
  buttonOnOff = digitalRead(D2);

  // Resets the plants position to the meditation room with an 90% chance, otherwise set to kitchen.
  randomRoom = random(1,100);
  if (randomRoom > 3) {
    currentPlantPos = 4;
  }
  else {
    currentPlantPos = 1;
  }

  /*********************** Dust Sensor ***********************/
  duration = pulseIn(DUSTPIN, LOW);
  lowPulseOccupancy = lowPulseOccupancy+duration;

  if (millis() - dustTime > sampleTime) {
    ratio = lowPulseOccupancy/(sampleTime*10.0);
    concentration = 1.1*pow(ratio,3)-3.8*pow(ratio,2)+520*ratio+0.62;
    Serial.printf("\nLow Pulse Occupancy: %i\nRatio: %0.2f\nConcentration: %0.2f\n\n",lowPulseOccupancy,ratio,concentration);
    lowPulseOccupancy = 0;

    dustTime = millis();
  }

  // Moves plant position to bedroom if there is to much dust
  if (concentration > 6000) {
    currentPlantPos = 0;
  }

  /******************* Air Quality Sensor *******************/
  if((millis()-airTime > 1000)) {
    quality = airQualityPatrol.slope();
    airValue = airQualityPatrol.getValue();

    Serial.printf("\nQuality: %i Sensor value: %i\n",quality, airValue);
  
    if (quality == AirQualitySensor::FORCE_SIGNAL) {
      Serial.printf("\nHigh pollution! Force signal active.\n");
    }
    else if (quality == AirQualitySensor::HIGH_POLLUTION) {
      Serial.printf("\nHigh pollution!\n");
    }
    else if (quality == AirQualitySensor::LOW_POLLUTION) {
      Serial.printf("\nLow pollution!\n");
    }
    else if (quality == AirQualitySensor::FRESH_AIR) {
      Serial.printf("\nFresh air.\n");
    } 

    airTime = millis();
  }
  // Moves plant position to bedroom if the air quality gets bad
  if (quality == 1 || quality == 2) {
    currentPlantPos = 0;
  }

  /*********************** BME Sensor ***********************/
  tempF = (bme.readTemperature()*1.8 + 32);
  pressureinHG = bme.readPressure()/3386.3886666667;
  humidity = bme.readHumidity();

  Serial.printf("\nTempature Farenheight = %i Pressure inHG = %i Humidity = %i",tempF,pressureinHG,humidity);

  // Moves plant position to living room if it gets to cold or humid
  if (tempF < 70 || humidity > 50) {
    currentPlantPos = 5;
  }

  /******************** Moisture Sensor ********************/
  moisture = analogRead(MOISTUREPIN);
  Serial.printf("\nMoisture value = %i",moisture);
  // Moves plant position to bathroom if it gets to dry
  if (millis()-moistureTime > 10000) {
    if (moisture > 2800 || buttonOnOff || subButtonOnOff) {
      currentPlantPos = 3;
      digitalWrite(D3,HIGH);
      delay(500);
      digitalWrite(D3,LOW);
      moistureTime = millis();
      subButtonOnOff = 0;
      buttonOnOff = 0;
    }
  }

  /*********************** Subscriptions ***********************/
  Adafruit_MQTT_Subscribe *subscription;
  while ((subscription = mqtt.readSubscription(100))) {
    if (subscription == &sub_buttonOnOff) {
      subButtonOnOff = atof((char *)sub_buttonOnOff.lastread);
    }
  }

  /*********************** Publishes ***********************/
  if((millis()-pubTime > 1500)) {
    pubNum++;
    if(mqtt.Update()) {
      if (buttonOnOff) {pub_buttonOnOff.publish(buttonOnOff); Serial.printf("\nPublishing %i \n",buttonOnOff);}
      if (pubNum == 1) {pub_concentration.publish(concentration); Serial.printf("\nPublishing %0.2f \n",concentration);}
      if (pubNum == 2) {pub_airQuality.publish(airValue); Serial.printf("\nPublishing %i \n",airValue); }
      if (pubNum == 3) {pub_tempF.publish(tempF); Serial.printf("\nPublishing %i \n",tempF); }
      if (pubNum == 4) {pub_pressure.publish(pressureinHG); Serial.printf("\nPublishing %i \n",pressureinHG);}
      if (pubNum == 5) {pub_humidity.publish(humidity); Serial.printf("\nPublishing %i \n",humidity); } 
      if (pubNum == 6) {pub_moisture.publish(moisture); Serial.printf("\nPublishing %i \n",moisture);}
      if (pubNum == 7) {pub_currentRoom.publish(currentPlantPos); Serial.printf("\nPublishing %i \n",currentPlantPos);}
    }
    if (pubNum == 7) {
      pubNum = 0;
    }
    pubTime = millis();
  }

  /************************ Display ************************/
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(5,15);
  display.printf("Tempature F = %i\n\n Humidity = %i\n\n CurrentRoom = %i",tempF,humidity,currentPlantPos);
  display.display();

  /*********************** Neopixels ***********************/
  //Ambient Fire lighting
  if (k == fireMax) {
    randomNum1 = random(153,155);
    randomNum2 = random(78,80);
    randomBright = random(15,25);
    k = 0;
    fireMax = random(200,300);
    fireTime = millis();
  }
  if (k == fireMax/2) {fireUpDown = !fireUpDown; fireTime = millis();}
  neopix.setBrightness(randomBright);
  if (fireUpDown) {
    neopix.setColor(8,(randomNum1-(millis()-fireTime)/30000),(randomNum2-(millis()-fireTime)/30000),0);
    neopix.show();
    k++;
    Serial.printf("up");
  }
  else {
    neopix.setColor(8,(randomNum1+(millis()-fireTime)/30000),(randomNum2+(millis()-fireTime)/30000),0);
    neopix.show();
    k++;
    Serial.printf("Down");
  }

  // Every 3 seconds checks if the plant position has changed and updates neopixels only if it's different
  Serial.printf("\nplant pos = %i\n",currentPlantPos);
  if (millis()-plantTime > 3000 && currentPlantPos != lastRoom) {
    if (currentPlantPos != lastRoom && currentPlantPos != 4 && currentPlantPos != 1) {
      creepyFlicker();
    }
    lightPlantPos(currentPlantPos);
    lastFavRoom = randomRoom;
    plantTime = millis();
    lastRoom = currentPlantPos;
  }

}

void MQTT_connect() {
  int8_t ret;
 
  // Return if already connected.
  if (mqtt.connected()) {
    return;
  }
 
  Serial.print("Connecting to MQTT... ");
 
  while ((ret = mqtt.connect()) != 0) { // Connect will return 0 for connected
    Serial.printf("Error Code %s\n",mqtt.connectErrorString(ret));
    Serial.printf("Retrying MQTT connection in 5 seconds...\n");
    mqtt.disconnect();
    delay(5000);  // Wait 5 seconds and try again
  }
  Serial.printf("MQTT Connected!\n");
}

bool MQTT_ping() {
  static unsigned int last;
  bool pingStatus;

  if ((millis()-last)>120000) {
    Serial.printf("Pinging MQTT \n");
    pingStatus = mqtt.ping();
    if(!pingStatus) {
      Serial.printf("Disconnecting \n");
      mqtt.disconnect();
    }
    last = millis();
  }
  return pingStatus;
}

void pixelFillInstant(int start, int end, int color) {
  int neopixNum;
  for ( neopixNum = start; neopixNum <= end; neopixNum ++) {
    neopix.setPixelColor(neopixNum, color);
    neopix.show();
  }
}

void creepyFlicker() {
  int neopixelOnOff;
  int flick, flick2, flick3, flick4, flick5;

  for (i = 0;i<random(10,20);i++) {
    // Stores an array with random on or off states and lights up neopixels accordingly
    flick = random(0,8);
    flick2 = random(0,8);
    flick3 = random(0,8);
    flick4 = random(0,8);
    flick5 = random(0,8);
    Serial.printf("\nflicks: %i %i %i %i %i",flick, flick2, flick3, flick4, flick5);
    neopixelOnOff = random(0,100);
    Serial.printf("\nonOff %i",neopixelOnOff);
    if (neopixelOnOff > 30) {
      neopix.setPixelColor(flick, 0xFEAA00);
      neopix.setPixelColor(flick2, 0xFEAA00);
      neopix.setPixelColor(flick3, 0xFEAA00);
      neopix.setPixelColor(flick4, 0xFEAA00);
      neopix.setPixelColor(flick5, 0xFEAA00);
    }
    else {
      neopix.setPixelColor(flick, 0x000000);
      neopix.setPixelColor(flick2, 0x000000);
      neopix.setPixelColor(flick3, 0x000000);
      neopix.setPixelColor(flick4, 0x000000);
      neopix.setPixelColor(flick5, 0x000000);
    }
    neopix.show();
    //Randomly decides to turn off the current pixel or leave it on
    neopixelOnOff = random(0,1);
    if (neopixelOnOff == 0) {
      delay(random(10,125));
      neopix.setPixelColor(flick, 0x000000);
      neopix.setPixelColor(flick2, 0x000000);
      neopix.setPixelColor(flick3, 0x000000);
      neopix.setPixelColor(flick4, 0x000000);
      neopix.setPixelColor(flick5, 0x000000);
    }
    neopix.show();
    delay(random(0,70));
  }
  // Turns off all neopixels
  pixelFillInstant(0,7,0x000000);
}

void lightPlantPos(int plantPos) {
  static int CURRENTPLANTPOS [6] {0,1,2,5,6,7};
  
  // Resets lights to off
  pixelFillInstant(0,7,0x000000);
  // Meditation didn't change
  if (CURRENTPLANTPOS[plantPos] == 6 && lastFavRoom > 10) {
    // Lights up updated current room
    neopix.setPixelColor(6, 0xFFFFFF);
    neopix.show();
    Serial.printf("\nPlant is still deep in meditation\n");
    return;
  }
  // Kitchen didn't change
  if (CURRENTPLANTPOS[plantPos] == 1 && lastFavRoom < 11) {
    // Lights up updated current room
    neopix.setPixelColor(1, 0x00FE00);
    neopix.show();
    Serial.printf("\nPlant is still munching on a snack\n");
    return;
  }

  // Bedroom
  if (CURRENTPLANTPOS[plantPos] == 0) {
    // Lights up updated current room
    neopix.setPixelColor(0, 0xFEFE00);
    neopix.show();
    Serial.printf("\nPlant is taking a breather in bed\n");
  }
  // Kitchen
  if (CURRENTPLANTPOS[plantPos] == 1 && lastFavRoom > 10) {
    // Lights up updated current room
    neopix.setPixelColor(1, 0x00FE00);
    neopix.show();
    Serial.printf("\nPlant is getting a snack\n");
  }
  // Bathroom
  if (CURRENTPLANTPOS[plantPos] == 5) {
    // Lights up updated current room
    neopix.setPixelColor(5, 0x00A0FE);
    neopix.show();
    Serial.printf("\nPlant is in the bathroom\n");
  }
  // Meditation
  if (CURRENTPLANTPOS[plantPos] == 6 && lastFavRoom < 11) {
    pixelFillInstant(2,4,0x000000);
    neopix.show();
    // Lights up updated current room
    neopix.setPixelColor(6, 0xFFFFFF);
    neopix.show();
    Serial.printf("\nPlant is Meditating\n");
  }
  // Livingroom
  if (CURRENTPLANTPOS[plantPos] == 7) {
    // Lights up updated current room
    neopix.setPixelColor(7, 0xFE0000);
    neopix.show();
    Serial.printf("\nPlant is chilling by the fire\n");
  }
}

Stranger_Watering_System

Credits

Caden Gamache
4 projects • 9 followers
Finished CNM's internet of things boot-camp where I learned IoT skills. I have a passion for learning and try to build skills wherever I am!

Comments