DEPARTMENT OF 

Molecular Biosciences and Bioengineering


Wireless Environmental Sensors

by Ryan Kurasaki May 16, 2022

Top Shell

Top Shell

Acrylic cover plate and bracket mounted to top shell.

Sensor Shell

Sensor Shell

Two layer housing with solar panel to charge battery during daylight hours.

Top Bracket

Top Bracket

Top bracket for internal mounting standoffs.

Top Cover

Top Cover

Transparent cover for light sensor.

Bottom Bracket

Bottom Bracket

Base of unit.

  • Overview
  • Sensors
  • Components
  • Programming and Wiring
  • Assembly Instructions
  • Calibration

Environmental Sensors

This weather resistant assembly is composed of two sensors, the BE680 and SI1145, which combine to provide temperature, humidty, barometric pressure, VOC gas, visible light, and infrared light readings. These readings can be taken continuously or at timed intervals and sent directly to a computer through a USB connection or wirelessly to a web server. Potential applications include outdoor and indoor crop monitoring with the capability to feedback the measurements to a controller for cooling, irrigation, and shading.

Weather Resistance

The sensors are housed in bowls that have been spray painted to improve resistance to UV light degradation and to increase infrared light reflectance. The top bowl has been covered with a layer of plexiglass to allow light transmission to the SI1145 mounted at the top of the unit.

Wireless

The sensors are read with an Adafruit ESP32 Huzzah Feather microcontroller board and all data can be reported wirelessly to a realtime database.

Sensors

BME680. Temperature, humidty, and pressure.

SI1145. Visible and infrared light.

Downloads

Top Cover

Top Bracket

Base

Connections

Sensor connections to the board can be made with a I2C quick connection. One connection from the ESP32 Feather board to the BME 680 sensor. The I2C connection is passed through the BME 680 at which point connect the other I2C connector to the SI1145.

Arduino Sketch

/*

Ryan Kurasaki

Communciation with a Firebase database in this software was adapted from example code from Random Nerd Tutorials. See below.

  Rui Santos
  Complete project details at our blog: https://RandomNerdTutorials.com/esp32-data-logging-firebase-realtime-database/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/

#include <Adafruit_BME680.h>
#include <bme68x.h>
#include <bme68x_defs.h>
#include <Arduino.h>
#include <WiFi.h>
#include <Firebase_ESP_Client.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_SI1145.h"

#include "time.h"
Adafruit_SI1145 uv = Adafruit_SI1145();
byte Hawaii_day;
byte Hawaii_month;
int  Hawaii_year;
int Hawaii_min;
int Hawaii_sec;
int Hawaii_hour;

const long  gmtOffset_sec = -36000;         //Hawaii is 10 hour behind on GMT
const int   daylightOffset_sec = 0;         // Hawaii DST offset is zero between spring and winter

 

// Provide the token generation process info.
#include "addons/TokenHelper.h"
// Provide the RTDB payload printing info and other helper functions.
#include "addons/RTDBHelper.h"

//// Insert your network credentials
#define WIFI_SSID "SSID"
#define WIFI_PASSWORD "PASSWORD"


// Insert Firebase project API Key
#define API_KEY "AIzaSyDTSq0beE3OilNvo3OxioCsNhXvI1Cv99s"

// Insert Authorized Email and Corresponding Password
#define USER_EMAIL "EMAIL"
#define USER_PASSWORD "PASSWORD"

// Insert RTDB URLefine the RTDB URL
#define DATABASE_URL "https://indoor-environment-39280-default-rtdb.firebaseio.com"

// Define Firebase objects
FirebaseData fbdo;
FirebaseAuth auth;
FirebaseConfig config;

// Variable to save USER UID
String uid;

// Database main path (to be updated in setup with the user UID)
String databasePath;
// Database child nodes
String locPath = "/area";
String tempPath = "/temperature";
String humPath = "/humidity";
String presPath = "/pressure";
String visPath = "/visible";
String irPath = "/infrared";
String uviPath = "/ultraviolet";
String timePath = "/timestamp";

// Parent Node (to be updated in every loop)
String parentPath;

int timestamp;
FirebaseJson json;

const char* ntpServer = "ntp.hawaii.edu";

// BME280 sensor
Adafruit_BME680 bme; // I2C
float temperature;
float humidity;
float pressure;

// Timer variables (send new readings every three minutes)
unsigned long sendDataPrevMillis = 0;
unsigned long timerDelay = 3600000;

// Initialize BME680
void initBME() {
  if (!bme.begin()) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }
}

// Initialize WiFi
void initWiFi() {
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to WiFi ..");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(1000);
  }
  Serial.println(WiFi.localIP());
  Serial.println();
}

// Function that gets current epoch time
unsigned long getTime() {
  time_t now;
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    //Serial.println("Failed to obtain time");
    return (0);
  }
  time(&now);
  return now;
}

//Time Function to GET LOCAL TIME FROM NTP SERVER
struct tm LocalTime(int &Hawaii_hour)
{
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Failed to obtain time");

  }
  //Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
  Hawaii_day = timeinfo.tm_mday;
  Hawaii_month = timeinfo.tm_mon + 1;
  Hawaii_year = timeinfo.tm_year + 1900;
  Hawaii_hour = timeinfo.tm_hour;
  Hawaii_min  = timeinfo.tm_min;
  Hawaii_sec  = timeinfo.tm_sec;
  //   Serial.println(Hawaii_day);
  //   Serial.println(Hawaii_month);
  //   Serial.println(Hawaii_year);
  //   Serial.println(Hawaii_hour);
  //   Serial.println(Hawaii_min);
  //   Serial.println(Hawaii_sec);
  return timeinfo;
}

 

 

 

void setup() {
  Serial.begin(115200);

  // Initialize BME280 sensor
  initBME();
  if (! uv.begin()) {
    Serial.println("Didn't find Si1145");
    while (1);
  }
 
  initWiFi();
  configTime(0, 0, ntpServer);

  // Assign the api key (required)
  config.api_key = API_KEY;

  // Assign the user sign in credentials
  auth.user.email = USER_EMAIL;
  auth.user.password = USER_PASSWORD;

  // Assign the RTDB URL (required)
  config.database_url = DATABASE_URL;

  Firebase.reconnectWiFi(true);
  fbdo.setResponseSize(4096);

  // Assign the callback function for the long running token generation task */
  config.token_status_callback = tokenStatusCallback; //see addons/TokenHelper.h

  // Assign the maximum retry of token generation
  config.max_token_generation_retry = 5;

  // Initialize the library with the Firebase authen and config
  Firebase.begin(&config, &auth);

  // Getting the user UID might take a few seconds
  Serial.println("Getting User UID");
  while ((auth.token.uid) == "") {
    Serial.print('.');
    delay(1000);
  }
  // Print user UID
  uid = auth.token.uid.c_str();
  Serial.print("User UID: ");
  Serial.println(uid);

  // Update database path
  databasePath = "/UsersData/" + uid + "/readings 113";
}

void loop() {

  // Send new readings to database
  if (Firebase.ready() && (millis() - sendDataPrevMillis > timerDelay || sendDataPrevMillis == 0)) {

    Serial.println (millis() - sendDataPrevMillis);
    sendDataPrevMillis = millis();

    configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

    struct tm timeinfo;
    if (!getLocalTime(&timeinfo)) {
      Serial.println("Failed to obtain time");

    }
    //Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
    Hawaii_day = timeinfo.tm_mday;
    Hawaii_month = timeinfo.tm_mon + 1;
    Hawaii_year = timeinfo.tm_year + 1900;
    Hawaii_hour = timeinfo.tm_hour;
    Hawaii_min  = timeinfo.tm_min;
    Hawaii_sec  = timeinfo.tm_sec;

    int z = 0;
    float temp = 0;
    float humidity = 0;
    float pressure = 0;
    float vis = 0;
    float ir = 0;
    float uvi = 0;
    while (z < 100)
    {
      temp = temp + bme.readTemperature();
      humidity = humidity + bme.readHumidity();
      pressure = pressure + bme.readPressure() / 100.0F;
      vis = vis + uv.readVisible();
      ir = ir + uv.readIR();
      uvi = uvi + uv.readUV();
      z = z + 1;
    }
    temp = temp / 100;
    humidity = humidity / 100;
    pressure = pressure / 100;
    vis = vis /100;
    ir = ir / 100;
    uvi  = uvi / 100 /100;
    timestamp=getTime();

    parentPath = databasePath + "/" + String(timestamp);

    json.set(locPath.c_str(), String(113));
    json.set(timePath, String(timestamp));
    json.set(tempPath.c_str(), String(temp));
    json.set(humPath.c_str(), String(humidity));
    json.set(presPath.c_str(), String(pressure));
    json.set(visPath.c_str(), String(vis));
    json.set(irPath.c_str(), String(ir));
    json.set(uviPath.c_str(), String(uvi));

    Serial.printf("Set json... %s\n", Firebase.RTDB.setJSON(&fbdo, parentPath.c_str(), &json) ? "ok" : fbdo.errorReason().c_str());
  }
}

Tools Required

  • Hand drill with drill bits
  • Step drill bit
  • M2.5 and M4 taps
  • Laser cutter (alternatives: jigsaw, table saw, band saw, hacksaw)
  • Screw driver

Shells

  1. Mark and drill four 3/16" holes into the base of each bowl. Each hole is located 7/8" from the center of the bowl in the X and Y directions.
  2. Mark and drill one 1" hole in the center of the base of each bowl.
  3. In each bowl that is not the top shell of the housing, drill one 1/4" to pass the light sensor wires to the lower layer.

Transparent Shield

  1. Using a laser cutter or saw, cut a circular disc from 1/8" thick acrylic sheet.
  2. Using a laser cutter or saw, cut another circular disc from 1/8" thick acrylic sheet. This disc has four mounting holes and one 1" cut out.
  3. Tap M4 threads into the second acrylic disc.
  4. Optional: Join both discs together with acrylic cement, epoxy, or other adhesive. WARNING. Most adhesives contain hazardous materials. Read and understand the product label and safety data sheet for information on specific hazards associated with the product and how to protect yourself should you choose to use an adhesive for this step.

Spacers

 

Sensor Mounting

 

 

Calibration

Calibration refers to the process of ensuring that each reading accurately quantifies the actual value of the parameter measured. For example, adjusting the temperature reading to reflect the actual air temperature.

Temperature

Place the assembly into a vessel or atmosphere along with another temperature measurement device which can serve as a reference temperature reading. An incubator, if available, could serve this purpose. Keep the environmental sensor and reference sensor in the same environment for 15 minutes and then record both temperatures. Adjust the incubator temperature or relocate to another environment at a different temperature. Wait 15-30 minutes prior to taking another reading. Take several readings over a range of temperatures that the environmental sensor is anticipated to be exposed to.

Collect all the data points. Using a spreadsheet, plot the data from the BME680 sensor on the X-axis and the reference temperature data on the Y-axis. Look for a noticable trend. If the relationship looks linear, find the slope and intercept. You can evaluate whether the linear relationship assumption is appropriate by examing the correlation of fit (R2). A value of 1 or close to 1 indicates a good fit. In your code, multiple the BME 608 value by the slope and add the offset. Once that code has been uploaded, the reported temperature readings should be closer to the reference temperature.

PAR Light

Take the assembly outside and hold it along side a PAR meter which will serve as a reference PAR light reading. Hold both at the same level and orientation. Repeat the data collection at different points in the day to obtain samples at a range of light levels.

Collect all the data points. Using a spreadsheet, plot the data from the SI1145 sensor on the X-axis and the reference PAR meter data on the Y-axis. Look for a noticable trend. If the relationship looks linear, find the slope and intercept. You can evaluate whether the linear relationship assumption is appropriate by examing the correlation of fit (R2). A value of 1 or close to 1 indicates a good fit. In your code, multiple the SI1145 by the slope and add the offset. Once that code has been uploaded, the reported PAR readings should be clsoe to the reference PAR meter values.

Infrared Light

Using the same procedure as for the PAR light calibration, take the assembly outside and hold it along side a IR meter which will serve as a reference IR light reading. Hold both at the same level and orientation. Repeat the data collection at different points in the day to obtain samples at a range of light levels.

Collect all the data points. Using a spreadsheet, plot the data from the SI1145 sensor on the X-axis and the reference PAR meter data on the Y-axis. Look for a noticable trend. If the relationship looks linear, find the slope and intercept. You can evaluate whether the linear relationship assumption is appropriate by examing the correlation of fit (R2). A value of 1 or close to 1 indicates a good fit. In your code, multiple the SI1145 by the slope and add the offset. Once that code has been uploaded, the reported IR readings should be clsoe to the reference IR meter values.

Ryan Kurasaki

 rkurasak@hawaii.edu

License