Building an IoT Device with Adafruit IO and p5.js

p5.js

Arduino IDE

Adafruit IO

Design Brief

For this assignment, I built an Internet-connected device that uses two sensors (temperature sensor and photoresistor) to collect and visualize data from my home environment. The device gathers temperature and light data, storing them in Adafruit IO feeds respectively. I then created a p5.js sketch to visually represent the collected data, providing an engaging way to analyze and explore the information.

Software:

The Concept

The goal of this project was to design a device that could monitor temperature and light changes and provide a visual representation of its findings. By collecting and visualizing these two datasets, the project offers insights into the relationship between environmental conditions in my home.

Components Used

  • Hardware:

    • ESP32 Feather microcontroller

    • Temperature sensor

    • Photoresistor (light sensor)

    • LED for status indication

    • Breadboard and jumper wires

    • USB power supply

  • Software:

    • Arduino IDE for programming the ESP32 and managing sensor data

    • Adafruit IO for cloud data storage

    • p5.js for data visualization

The Process

Building the IoT Device

Sensor Integration:
I connected the temperature sensor and photoresistor to the ESP32 Feather. Both sensors were placed on the same breadboard, located by a lamp and window in the living room, to collect data from the same area.

Adding the LED:
An LED was added to the breadboard as a visual indicator to confirm that the device was functioning properly. By glancing at the LED, I could quickly verify if data was being recorded without needing to check the Adafruit IO feed.

Programming the ESP32:

  • The ESP32 was programmed using Arduino IDE to read data from the sensors.

  • Data from both sensors was uploaded to Adafruit IO via Wi-Fi every five seconds.

  • The temperature readings were stored in a "temp-feed," while the light sensor data was stored in a "photo-feed."

Time-Based Data Collection:

  • On the first day, the device collected data only from sunset to sunrise. On the second day, I adjusted the program to collect data throughout the day.

  • Setting up a time client in the Arduino code was particularly challenging, as it initially gave me the wrong timezone. Debugging and ensuring the correct time settings were essential to align the data collection period with local conditions.

Power Considerations:

  • The ESP32, sensors, and LED were powered via a USB connection to a wall adapter.

Arduino Code
for time specific data collection

Main Sketch Code

config.h Code

#include "config.h"
// your sensor/potentiometer pin
#define TEMP_PIN A2
#define SENSOR_PIN A3


// the name (not the key) of your Adafruit IO feed
AdafruitIO_Feed *tempFeed = io.feed("temp-feed");
AdafruitIO_Feed *photoFeed = io.feed("photo-feed");


unsigned long lastUploadTime = 0;
const unsigned long uploadInterval = 5000; // 5 seconds for both sensors


// Define the sunset and sunrise times (e.g., 6:00 PM and 6:00 AM)
//const int sunsetHour = 16;
const int startHour = 16; // 4pm
//const int sunsetMinute = 0;
const int startMinute = 0;
//const int sunriseHour = 6; //6am
const int endHour = 15;  // 3pm
//const int sunriseMinute = 0;
const int endMinute = 0;


//LED
#define LEDC_TIMER_12_BIT 12 // resolution
#define LEDC_BASE_FREQ 5000 // timer frequency
#define LED_PIN 21


const int MAX_DUTY_CYCLE = (int)(pow(2, LEDC_TIMER_12_BIT) - 1);
const int DELAY_MS = 5;
int _ledFadeStep = 5;


void setup() {
 // start the serial connection
 Serial.begin(9600);
 // wait for serial monitor to open
 while(! Serial);


// connect to io.adafruit.com
 Serial.print("Connecting to Adafruit IO");
 io.connect();
 // wait for a connection
 while(io.status() < AIO_CONNECTED){
   Serial.print(".");
   delay(500);
 }
 // we are connected
 Serial.println();
 Serial.println(io.statusText());


// Initialize the NTPClient
 timeClient.begin();


//PHOTORESISTOR
 // make sure your sensor pin is an input
 pinMode(SENSOR_PIN, INPUT);


//TEMPERATURE SENSOR
 // make sure your sensor pin is an input
 pinMode(TEMP_PIN, INPUT);


// LED
 ledcAttach(LED_PIN, LEDC_BASE_FREQ, LEDC_TIMER_12_BIT);
 Serial.begin(9600);
}


void loop() {
 io.run(); // Run AdafruitIO


 // Update the time (sunset and sunrise) each time
 timeClient.update();


 // Get the current time
 int currentHour = timeClient.getHours();
 int currentMinute = timeClient.getMinutes();


 //Debugging: Print the current time to the Serial Monitor
 Serial.print("Current Time: ");
 Serial.print(currentHour);
 Serial.print(":");
 Serial.println(currentMinute);


 // Check if it's within the sunset to sunrise period
 bool isTime = false;


 // If the current time is after 16:00 and before midnight, or between 00:00 and 15:00
 if ((currentHour >= startHour && currentHour <= 23) || (currentHour >= 0 && currentHour < endHour)) {
   isTime = true; // It's time, record data
 }


 // If it's within the sunset to sunrise time period, record data
 if (isTime) {
   unsigned long currentTime = millis();
   unsigned long elapsedTime = currentTime - lastUploadTime; // Track time since last upload


   // If it's within the sunset to sunrise time period, record data
   if (elapsedTime >= uploadInterval) {
     // Read the photoresistor and temperature values
     float photoVal = analogRead(SENSOR_PIN);
     float tempVal = analogRead(TEMP_PIN);


     // Photoresistor upload logic
     Serial.print("Sending photoresistor: ");
     Serial.println(photoVal);
     photoFeed->save(photoVal);


     // Temperature sensor upload logic
     Serial.print("Sending temperature sensor: ");
     Serial.println(tempVal);
     tempFeed->save(tempVal);


     // Update the last upload time
     lastUploadTime = currentTime;
   }
 } else {
   // If it's not night time, do nothing (or print a message for debugging)
   Serial.println("It's not time, not recording data.");
 }


// LED
 for (int duty = 0; duty <= MAX_DUTY_CYCLE; duty+= _ledFadeStep){
   ledcWrite(LED_PIN, duty);
   delay (DELAY_MS);
 }


 for (int duty = MAX_DUTY_CYCLE; duty >= 0; duty-= _ledFadeStep){
   ledcWrite(LED_PIN, duty);
   delay (DELAY_MS);
 }
#ifndef CONFIG_H
#define CONFIG_H

// get your username and key by clicking
// on the big yellow key button in the nav bar
// of https://io.adafruit.com/
#define IO_USERNAME  "danitorg"
#define IO_KEY       "aio_NWjC84oTRo61oKAXUMxXBcWJyVOQ"

// Wi-Fi credentials
#define WIFI_SSID "Verizon_DXTVW6"
#define WIFI_PASS "vat3-tatter-yaw"

#include "AdafruitIO_WiFi.h"
AdafruitIO_WiFi io(IO_USERNAME, IO_KEY, 
WIFI_SSID, WIFI_PASS);

// Include necessary libraries for time handling
#include <WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>

// NTP client setup
WiFiUDP ntpUDP; 
NTPClient timeClient(ntpUDP, "us.pool.ntp.org", 
-28800, 60000); 
// UTC-8 for PST, adjust for your timezone

// Function to initialize the NTP client
void initializeTime() {
    // Start the NTP client
    timeClient.begin();
}

#endif // CONFIG_H 

Data being sent to AdafruitIO from photoresistor and temperature sensor.

IoT device collecting data by living room lamp:

Visualizing the Data with p5.js:

The second part of the project focused on creating a p5.js sketch to retrieve and display the data from Adafruit IO. Here’s how I did it:

Data Retrieval:

  • The sketch used loadJSON() to fetch data from the "temp-feed" and "photo-feed."

  • Temperature and light values were stored in separate arrays for visualization.

Data Representation:

  • Temperature readings were represented as circles, with their size mapped to the range of recorded values.

  • Light levels were visualized using circle colors, transitioning smoothly with Perlin noise to represent varying light conditions over time.

The Visualization:

  • The canvas displayed a row of circles where each circle's diameter represented a temperature reading, and its color reflected the corresponding light level.

  • The smooth color transitions added an aesthetic dimension to the visualization.

Here’s how I set up the visualization logic:

let noiseOffset = 0;

function draw() {
  background(1);

  if (tempValues.length > 0) {
    tempValues.forEach((value, i) => {
      // Use noise instead of random
      let hue = noise(noiseOffset + i * 0.1) * TWO_PI; // Scale noise to match hue range
      fill(hue, 1, 1, 0.5);
      noStroke();
      let diameter = map(value, 0, 4095, 0, height);
      circle(width / limitNumber * i + width / limitNumber * 0.5, height * 0.5, diameter);
    });
  }

  if (photoValues.length > 0) {
    photoValues.forEach((value, i) => {
      // Use noise instead of random
      let hue = noise(noiseOffset + i * 0.1) * TWO_PI; // Scale noise to match hue range
      fill(hue, 1, 1, 0.5);
      noStroke();
      let diameter = map(value, 0, 50, 0, height);
      circle(width / limitNumber * i + width / limitNumber * 0.5, height * 0.5, diameter);
    });
  }

  // Increment noise offset for continuous variation
  noiseOffset += 0.01;
}

Challenges Faced:

Time Client Setup:

  • Configuring the time client to accurately align with my local timezone was the hardest part. Initially, the device recorded data using incorrect times, which required troubleshooting the NTP (Network Time Protocol) client settings.

Data Transmission:

  • Initial data upload intervals overwhelmed Adafruit IO’s feed limits. Adjusting the update frequency resolved this issue.

LED Integration:

  • Ensuring the LED reliably indicated device functionality without disrupting sensor data collection added an extra layer of complexity.

Visualization Smoothness:

  • Random colors initially made the visualization chaotic. Switching to Perlin noise added continuity and harmony.

The Final Result = Data Visualization;

The final device successfully collected and visualized data over a three day period. The temperature variations were clearly represented through the sizes of the circles, while the light intensity was depicted with smooth color transitions. This combination of hardware and software demonstrated the potential of IoT devices to make data engaging and accessible.

FINAL ARDUINO CODE

MAIN SKETCH CODE

CONFIG.H CODE

#include "config.h"
// your sensor/potentiometer pin
#define TEMP_PIN A2
#define SENSOR_PIN A3

// the name (not the key) of your Adafruit IO feed
AdafruitIO_Feed *tempFeed = io.feed("temp-feed");
AdafruitIO_Feed *photoFeed = io.feed("photo-feed");

//LED
#define LEDC_TIMER_12_BIT 12 // resolution
#define LEDC_BASE_FREQ 5000 // timer frequency
#define LED_PIN 21

const int MAX_DUTY_CYCLE = (int)(pow(2, LEDC_TIMER_12_BIT) - 1);
const int DELAY_MS = 5;
int _ledFadeStep = 5;

void setup() {
  // start the serial connection
  Serial.begin(9600);
  // wait for serial monitor to open
  while(! Serial);
  
  // connect to io.adafruit.com
  Serial.print("Connecting to Adafruit IO");
  io.connect();
  // wait for a connection
  while(io.status() < AIO_CONNECTED){
    Serial.print(".");
    delay(500);
  }
  // we are connected
  Serial.println();
  Serial.println(io.statusText());

  // make sure your sensor pin is an input
  //PHOTORESISTOR
  pinMode(SENSOR_PIN, INPUT);
  //TEMPERATURE SENSOR
  pinMode(TEMP_PIN, INPUT);

  // LED
  ledcAttach(LED_PIN, LEDC_BASE_FREQ, LEDC_TIMER_12_BIT);
  Serial.begin(9600);
}

void loop() {
  io.run();
  float tempVal = analogRead(TEMP_PIN);
  Serial.print("Sending Tempeture Sensor: ");
  Serial.println(tempVal);
  tempFeed->save(tempVal);
 
float photoVal = analogRead(SENSOR_PIN);
  Serial.print("Sending Photoresistor: ");
  Serial.println(photoVal);
  photoFeed->save(photoVal);

  // LED
 for (int duty = 0; duty <= MAX_DUTY_CYCLE; duty+= _ledFadeStep){
   ledcWrite(LED_PIN, duty);
   delay (DELAY_MS);
 }

 for (int duty = MAX_DUTY_CYCLE; duty >= 0; duty-= _ledFadeStep){
   ledcWrite(LED_PIN, duty);
   delay (DELAY_MS);
 }
 
 delay(5000);
}

FINAL P5.JS CODE

// get your username and key by clicking
// on the big yellow key button in the nav bar
// of https://io.adafruit.com/
#define IO_USERNAME  "username"
#define IO_KEY       "key"

// Wi-Fi credentials
#define WIFI_SSID "wifiname"
#define WIFI_PASS "wifipassword"

#include "AdafruitIO_WiFi.h"
AdafruitIO_WiFi io(IO_USERNAME, IO_KEY, WIFI_SSID, WIFI_PASS);
let myData;

let baseTempURL = "https://io.adafruit.com/api/v2/danitorg/feeds/temp-feed/"
let basePhotoURL = "https://io.adafruit.com/api/v2/danitorg/feeds/photo-feed/"

let dataOption = "data"
let limitQuery = "limit="
let limitNumber = 10
let includeQuery = "include"

let tempValues = []
let photoValues = []

let intervalPhotoID
let intervalTempID

// Introduce a noise offset variable
let noiseOffset = 0;

function setup() {
  createCanvas(600, 200);
  colorMode(HSB, TWO_PI, 1, 1, 1);

  intervalTempID = setInterval(getTempFeedData, 5000);
  intervalPhotoID = setInterval(getPhotoFeedData, 5000);
}

function draw() {
  background(1);

  if (tempValues.length > 0) {
    tempValues.forEach((value, i) => {
      // Use noise instead of random
      let hue = noise(noiseOffset + i * 0.1) * TWO_PI; // Scale noise to match hue range
      fill(hue, 1, 1, 0.5);
      noStroke();
      let diameter = map(value, 0, 4095, 0, height);
      circle(width / limitNumber * i + width / limitNumber * 0.5, height * 0.5, diameter);
    });
  }

  if (photoValues.length > 0) {
    photoValues.forEach((value, i) => {
      // Use noise instead of random
      let hue = noise(noiseOffset + i * 0.1) * TWO_PI; // Scale noise to match hue range
      fill(hue, 1, 1, 0.5);
      noStroke();
      let diameter = map(value, 0, 50, 0, height);
      circle(width / limitNumber * i + width / limitNumber * 0.5, height * 0.5, diameter);
    });
  }

  // Increment noise offset for continuous variation
  noiseOffset += 0.01;
}

function getTempFeedData() {
  myData =
    loadJSON(baseTempURL + dataOption + "?" + limitQuery + limitNumber + "&" + includeQuery + "value", handleTempData, handleError);
}

function handleTempData(data) {
  console.log("My temp data is: ");
  tempValues = [];
  data.forEach((dO) => {
    tempValues.push(parseInt(dO.value));
  });

  console.log(tempValues);
}

function getPhotoFeedData() {
  myData =
    loadJSON(basePhotoURL + dataOption + "?" + limitQuery + limitNumber + "&" + includeQuery + "value", handlePhotoData, handleError);
}

function handlePhotoData(data) {
  console.log("My photo data is: ");
  photoValues = [];
  data.forEach((dO) => {
    photoValues.push(parseInt(dO.value));
  });

  console.log(photoValues);
}

function handleError() {
  console.log("Sorry, there was an error. It was: " + error);
}