Project showcase

Indoor Oxygen Reactor © GPL3+

Produce and maintain oxygen/Co2 levels in indoor environment through bio cells for Menopause and COVID19.

  • 946 views
  • 0 comments
  • 5 respects

Components and supplies

Necessary tools and machines

3drag
3D Printer (generic)

Apps and online services

About this project

Oxygen Reactor Demo

Introduction:

According to Department of Health from Wisconsin state government also noted that "Exposure to CO2 can produce a variety of health effects. These may include headaches, dizziness, restlessness, a tingling or pins or needles feeling, difficulty breathing, sweating, tiredness, increased heart rate, elevated blood pressure, coma, asphyxia, and convulsions."

This is especially dangerous to senior citizens and people who are experiencing menopause, as they are already experiencing some of these symptoms. Higher CO2 level is likely making those symptoms much worse.

  • 250-350 ppm: background (normal) outdoor air level
  • 350-1, 000 ppm: typical level found in occupied spaces with good air exchange
  • 1, 000-2, 000 ppm: level associated with complaints of drowsiness and poor air
  • 2, 000-5, 000 ppm: level associated with headaches, sleepiness, and stagnant, stale, stuffy air; poor concentration, loss of attention, increased heart rate and slight nausea may also be present.
  • >5, 000 ppm: This indicates unusual air conditions where high levels of other gases also could be present. Toxicity or oxygen deprivation could occur. This is the permissible exposure limit for daily workplace exposures.
  • >40, 000 ppm: This level is immediately harmful due to oxygen deprivation.

As "shelter in place" order has been placed in most of the states in US due to Novel Corona virus (COVID19), we are facing more and more indoor Co2. Exposure to Co2 isn't the only cause for these problems in menopause, but it can be a great contributor.

Sleep Disorder

The high level of indoor CO2 can have many harmful effect, one of them is a causing sleep disorder according to zehnderamerica. "Unventilated bedrooms often have high concentrations of carbon dioxide, especially when there is more than one occupant using the room. Typically, carbon dioxide levels rise during the night when people are sleeping, especially if the door and windows are closed. The concentrations then fall during the day if the room is unoccupied. Unfortunately, poor air quality can hinder restful sleep and optimum health in many homes."

Sleep disorder affects women that are having Menopause, with more indoor spending due to COVID19, they are especially at risk for more indoor exposure.

Problems concentrating

At 2000-5000 ppm, wed have problems that causes concentration. Scientists at University College London (UCL) found that higher concentrations of CO2 reduce memory, impair concentration and lower decision-making capabilities.

With COVID19, we are facing more and more social distancing, staying indoor at higher concentration fo CO2 provides more

Headache, Dizziness, Mood Swing, Fatigue, Memory Lapses, and additional symptoms

In a research done through international space station (ISS) "Headache is typically the first symptom reported by crew, although ground medical personnel anecdotally note changes in mood at levels as low as 0.5% CO2 [5, 000 ppm]. Reported symptoms were successfully correlated to ISS CO2 levels". While CO2 isn't the only cause of Mood Swing, it can be a big contributor.

Our Solution:

Due to COVID19 we are staying indoors more and more, so our solution we will focus on balancing the oxygen and CO2 level in the room, in hope to alleviate some of the symptoms showing in both people with menopause and others. Recent study for indoor air sees dramatic change in both Oxygen Level and CO2, while oxygen level didn't see significant decrease in a closed door as chart below.

CO2 level sees a significant rise

homas Talhelm was also able to point out that "Oxygen constitutes about 20%, while carbon dioxide is only 0.038% of the air. Thus, carbon dioxide is capable of tripling from such a small number, while oxygen is mostly unchanged."

The good news is CO2 and O2 is almost same weight, so to replace 1 gram of Co2 we can go through photo synthesis and release 1G of O2. Our goal is to replace CO2 with O2 so that we can balance the CO2 ppm.

Although we are able to achieve this through house plants, but algae are among the most efficient types of bio cell to produce oxygen. In a result, we will be leveraging algae for our project to produce oxygen in our indoor home. Globally, it It is estimated that marine plants such as algae and plankton produce 80 percent of the oxygen in the atmosphere. (Source: Advanced Earth and Space Science Journal)

Conclusion:

Again, we have to emphasize that indoor CO2 level may not be the only thing that does causes these symptoms during menopause, studies have shown it is one of the major contributor to the problem. As we balance the CO2 in the air, it gives us a much better chance to alleviate some of these symptoms during menopause. With more and more people stuck at home for due to COVID19, it is essential that we balance our indoor CO2.

Step 1: Gather materials

For this project we will be using Chaetomorpha Macro Algae as our bio cell, since you can easily obtain that from your local aquarium, a cup, Co2 sensor, DFRobot DFDuino Shield, a Arduino Zero (or Arduino Due if you want this to run faster), bendable LED light and a water pump helping delivery of air into water itself.

The Gravity bendable LED is able to attach itself on the glass cup itself.

https://store.arduino.cc/usa/arduino-zero

After wrapping it should look like something like following.

As for processor, we can go for cheaper end Arduino Zero which is a 32 expansion on Unio that features a 32-bit ARM Cortex® M0+ core. Or if you want faster response rate, use Arduino Due which has Atmel SAM3X8E ARM Cortex-M3 CPU.

The bendable LED uses 5v, that is more than enough to turn on the light for photo synthesis. Next we can bring all the material together, the board, Gravity expansion board, wifi xbee, Co2 sensor, pump and the cup.

And plug them all together like below, where the infared carbon dioxide goes to A0, gravity LED goes to D5, and pump goes into the relay socket on gravity shield relay socket.

When this is ready we can move on to Step 2

Step 2: Reading the CO2 Sensor

Once everything is connected, we will be able to read sensor data via DFDuino shield using Arduino Zero, or for faster response rate, use Arduino Due which has Atmel SAM3X8E ARM Cortex-M3 CPU.

On the documentation of the sensor itself we can see that "When the IR CO2 sensor get 5V power supply, it will output 0.42V analog value, corresponding to 0 - 5000 ppm; And when the sensor finds faults during the self-checking process, it will output 0V." Since anything over 5, 000 ppm is extremely harmful, this sensor is perfectly used for this display purpose.

We can utilize following code to read the current CO2 level as provided by the sensor documentation.

int sensorIn = A0;

void setup(){
Serial.begin(9600);
// Set the default voltage of the reference voltage
analogReference(DEFAULT);
}

void loop(){
//Read voltage
int sensorValue = analogRead(sensorIn);

// The analog signal is converted to a voltage
float voltage = sensorValue*(5000/1024.0);
if(voltage == 0)
{
Serial.println("Fault");
}
else if(voltage < 400)
{
Serial.println("preheating");
}
else
{
int voltage_diference=voltage-400;
float concentration=voltage_diference*50.0/16.0;
// Print Voltage
Serial.print("voltage:");
Serial.print(voltage);
Serial.println("mv");
//Print CO2 concentration
Serial.print(concentration);
Serial.println("ppm");
}
delay(100);
}

And from printed result you can clearly see that CO2 level is quiet high at my own place.

Optionally, we can create a LCD that displays the current CO value, add the library to arduino via

https://github.com/DFRobot/DFRobot_RGBLCD

Then we can use following to display the data into LCD

if(concentration < 1500)
{
lcd.setRGB(0, 0, 0);
}
else if(concentration > 1500)
{
lcd.setRGB(255, 255, 255);
}
else if(concentration > 2000)
{
lcd.setRGB(255, 0, 0);
}

The color will turn white when it's over 1500 ppm, then turn red when it's over 2000 ppm. warning users of CO2 level being unsafe.

Step 3: Photosynthesis using LED light

As now we can measure the CO2, we can manipulate the light for photosynthesis. To do that we'd have to install the Adafruit NeoPixel library from

https://github.com/adafruit/Adafruit_NeoPixel

Once that's done we can plug our matrix into D4 as following

Using following code we should be able to test out whether our LED is working.

#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif
// Which pin on the Arduino is connected to the NeoPixels?
// On a Trinket or Gemma we suggest changing this to 1:
#define LED_PIN 4
// How many NeoPixels are attached to the Arduino?
#define LED_COUNT 256
// NeoPixel brightness, 0 (min) to 255 (max)
#define BRIGHTNESS 50
// Declare our NeoPixel strip object:
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRBW + NEO_KHZ800);
// Argument 1 = Number of pixels in NeoPixel strip
// Argument 2 = Arduino pin number (most are valid)
// Argument 3 = Pixel type flags, add together as needed:
// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products)
// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products)
void setup() {
// These lines are specifically to support the Adafruit Trinket 5V 16 MHz.
// Any other board, you can remove this part (but no harm leaving it):
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
clock_prescale_set(clock_div_1);
#endif
// END of Trinket-specific code.
strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
strip.show(); // Turn OFF all pixels ASAP
strip.setBrightness(50); // Set BRIGHTNESS to about 1/5 (max = 255)
}
void loop() {
// Fill along the length of the strip in various colors...
colorWipe(strip.Color(255, 0, 0) , 50); // Red
colorWipe(strip.Color( 0, 255, 0) , 50); // Green
colorWipe(strip.Color( 0, 0, 255) , 50); // Blue
colorWipe(strip.Color( 0, 0, 0, 255), 50); // True white (not RGB white)
whiteOverRainbow(75, 5);
pulseWhite(5);
rainbowFade2White(3, 3, 1);
}
// Fill strip pixels one after another with a color. Strip is NOT cleared
// first; anything there will be covered pixel by pixel. Pass in color
// (as a single 'packed' 32-bit value, which you can get by calling
// strip.Color(red, green, blue) as shown in the loop() function above),
// and a delay time (in milliseconds) between pixels.
void colorWipe(uint32_t color, int wait) {
for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
strip.setPixelColor(i, color); // Set pixel's color (in RAM)
strip.show(); // Update strip to match
delay(wait); // Pause for a moment
}
}
void whiteOverRainbow(int whiteSpeed, int whiteLength) {
if(whiteLength >= strip.numPixels()) whiteLength = strip.numPixels() - 1;
int head = whiteLength - 1;
int tail = 0;
int loops = 3;
int loopNum = 0;
uint32_t lastTime = millis();
uint32_t firstPixelHue = 0;
for(;;) { // Repeat forever (or until a 'break' or 'return')
for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
if(((i >= tail) && (i <= head)) || // If between head & tail...
((tail > head) && ((i >= tail) || (i <= head)))) {
strip.setPixelColor(i, strip.Color(0, 0, 0, 255)); // Set white
} else { // else set rainbow
int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
}
}
strip.show(); // Update strip with new contents
// There's no delay here, it just runs full-tilt until the timer and
// counter combination below runs out.
firstPixelHue += 40; // Advance just a little along the color wheel
if((millis() - lastTime) > whiteSpeed) { // Time to update head/tail?
if(++head >= strip.numPixels()) { // Advance head, wrap around
head = 0;
if(++loopNum >= loops) return;
}
if(++tail >= strip.numPixels()) { // Advance tail, wrap around
tail = 0;
}
lastTime = millis(); // Save time of last movement
}
}
}
void pulseWhite(uint8_t wait) {
for(int j=0; j<256; j++) { // Ramp up from 0 to 255
// Fill entire strip with white at gamma-corrected brightness level 'j':
strip.fill(strip.Color(0, 0, 0, strip.gamma8(j)));
strip.show();
delay(wait);
}
for(int j=255; j>=0; j--) { // Ramp down from 255 to 0
strip.fill(strip.Color(0, 0, 0, strip.gamma8(j)));
strip.show();
delay(wait);
}
}
void rainbowFade2White(int wait, int rainbowLoops, int whiteLoops) {
int fadeVal=0, fadeMax=100;
// Hue of first pixel runs 'rainbowLoops' complete loops through the color
// wheel. Color wheel has a range of 65536 but it's OK if we roll over, so
// just count from 0 to rainbowLoops*65536, using steps of 256 so we
// advance around the wheel at a decent clip.
for(uint32_t firstPixelHue = 0; firstPixelHue < rainbowLoops*65536;
firstPixelHue += 256) {
for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
// Offset pixel hue by an amount to make one full revolution of the
// color wheel (range of 65536) along the length of the strip
// (strip.numPixels() steps):
uint32_t pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
// strip.ColorHSV() can take 1 or 3 arguments: a hue (0 to 65535) or
// optionally add saturation and value (brightness) (each 0 to 255).
// Here we're using just the three-argument variant, though the
// second value (saturation) is a constant 255.
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue, 255,
255 * fadeVal / fadeMax)));
}
strip.show();
delay(wait);
if(firstPixelHue < 65536) { // First loop,
if(fadeVal < fadeMax) fadeVal++; // fade in
} else if(firstPixelHue >= ((rainbowLoops-1) * 65536)) { // Last loop,
if(fadeVal > 0) fadeVal--; // fade out
} else {
fadeVal = fadeMax; // Interim loop, make sure fade is at max
}
}
for(int k=0; k<whiteLoops; k++) {
for(int j=0; j<256; j++) { // Ramp up 0 to 255
// Fill entire strip with white at gamma-corrected brightness level 'j':
strip.fill(strip.Color(0, 0, 0, strip.gamma8(j)));
strip.show();
}
delay(1000); // Pause 1 second
for(int j=255; j>=0; j--) { // Ramp down 255 to 0
strip.fill(strip.Color(0, 0, 0, strip.gamma8(j)));
strip.show();
}
}
delay(500); // Pause 1/2 second
}

When code is done we should see following

As for the light, we want Chlorophyll a and Caratenoids, which are efficiently absorbed by algeas, to do that, we can focus on deep red and violet, and turquoise lights.

  • hlorophyll-a- All plants, algae, and bacteria
  • Chlorophyll-b- Chlorophytes and land plants
  • Carotenoids- In all plants and algae
  • Phycoerythrin- Red algae (Porphyridium, Rhodomonas )
  • Phycocyanin- Cyanobacteria (spirulina, Arthrospira)

So this way the violet is the best suited for capturing all the color spectrum needed for algea growth. In this case pixels.Color(07, 127, 255)

void setupLights()
{
for(int i = 0; i < 16; i++)
{
if(((i / 2) & 1) == 0)
{
pixels.setPixelColor(16*i+6, pixels.Color(127, 0, 255));
pixels.setPixelColor(16*i+7, pixels.Color(127, 0, 255));
}
}
pixels.show(); // Send the updated pixel colors to the hardware.
}

The light down with algea should be a very good indicator for oxygen genration.

Step 4: Storing CO2 level into the cloud

Now can monitor our CO2 level, we can monitor our data remotely and do further analysis on our environment. One of the easiest way is firebase. For this article we will go through using firebase through simple ways. Go through create a project like normal CP then create a database with test enviroment.

We will create a project in Firebase.

And create our realtime database via

Now we can start a database via CO2 collection

When all is done we can have our database setup.

You would also need a service account key being generated through firebase. Please do not upload this key to the repo, as it is a secure key.

Once it's connected, we can create a local relay on our server to make firebase transfer.

const http = require('http');
const url = require('url');
const querystring = require('querystring');
// Import Admin SDK
var admin = require("firebase-admin");
var serviceAccount = require("path/to/serviceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://databaseName.firebaseio.com"
});
// Get a database reference to our blog
var db = admin.database();
var ref = db.ref("CO2");
ref.once("value", function(snapshot) {
console.log(snapshot.val());
});
const hostname = '192.168.1.12';
const port = 3000;
const server = http.createServer((req, res) => {
const queryObject = url.parse(req.url,true).query;
var dt = new Date();
var utcDate = dt.toUTCString();
if(queryObject != null && queryObject.pushup != null)
{
console.log(queryObject.pushup);
var newPostRef = ref.push().set({
CO2: queryObject.CO2,
Time: utcDate
});
var postId = newPostRef.key;
console.log(postId);
console.log(utcDate);
}
//console.log(querystring.parse(queryObject));
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});

Simply run

node app.js

and we can use this local to relay the firebase.

Step 5: Back to Arduino code

We will need to add on the Arduino Wifi Shield for this

When it's done we'd have something that look like this.

Now that we can count our CO2 through ppm, we can store them, this is time for everything to come together. First, we will need to utilize the Arduino wifi shield to connect to the internet.

#include <WiFi.h>

char ssid[] = "yourNetwork"; // your network SSID (name)
char pass[] = "12345678"; // your network password
int status = WL_IDLE_STATUS; // the Wifi radio's status

void setup() {
// initialize serial:
Serial.begin(9600);

// attempt to connect using WPA2 encryption:
Serial.println("Attempting to connect to WPA network...");
status = WiFi.begin(ssid, pass);

// if you're not connected, stop here:
if ( status != WL_CONNECTED) {
Serial.println("Couldn't get a wifi connection");
while(true);
}
// if you are connected, print out info about the connection:
else {
Serial.println("Connected to network");
}
}

void loop() {
// do nothing
}

Once we get this part running we can integrate it into our code

void loop() {
//Read voltage
int sensorValue = analogRead(sensorIn);
// The analog signal is converted to a voltage
float voltage = sensorValue * (5000 / 1024.0);
if (voltage == 0)
{
Serial.println("Fault");
}
else if (voltage < 400)
{
Serial.println("preheating");
}
else
{
int voltage_diference = voltage - 400;
float concentration = voltage_diference * 50.0 / 16.0;
// Print Voltage
Serial.print("voltage:");
Serial.print(voltage);
Serial.println("mv");
//Print CO2 concentration
Serial.print(concentration);
Serial.println("ppm");
lcd.setCursor(0, 0);
lcd.print("CO2 Concentration");
lcd.setCursor(0, 1);
lcd.print(String(concentration) + " ppm");
if(concentration > 1500)
{
lcd.setRGB(255, 0, 0);
setupLights();
}
else if(concentration > 1200)
{
lcd.setRGB(255, 255, 255);
setupLights();
}
else
{
lcd.setRGB(0, 0, 0);
clearLights();
}
String tmp;
tmp = String(concentrationpCount);
if (client.connect(server, 3000)) {
Serial.println("connected to server");
// Make a HTTP request:
client.println("GET /?CO2=" + concentration + " HTTP/1.1");
client.println("Host: 192.168.1.12");
client.println("Connection: close");
client.println();
}
}
delay(1000);
}

And this would upload our data into firebase repeatedly.

Step 6: 3D print a case from Arduino

Although this is just a prototype, we can still make this look nicer by giving it a good finishing touch. lagunacomputer made an open source case on thingsverse that we can take use.

We can download the STL file and print it through here.

After a few hours we should have following

Step 6: Demo

Now that this is all done, we are able to run this at home for oxygen generation.

Oxygen Reactor Demo


Code

Arduino codeArduino
Arduino code
#include <Wire.h>
#include "rgb_lcd.h"
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
 #include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif

#define PIN        4 // On Trinket or Gemma, suggest changing this to 1
#define NUMPIXELS  256 // Popular NeoPixel ring size
#include <WiFi.h>

char ssid[] = "yourwifi";     //  your network SSID (name)
char pass[] = "wifipassword";    // your network password
int status = WL_IDLE_STATUS;     // the Wifi radio's status
WiFiClient client;


Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

rgb_lcd lcd;

int sensorIn = A0;

void setup() {
  Serial.begin(115200);
  // Set the default voltage of the reference voltage
  analogReference(DEFAULT);
  lcd.begin(16, 2);

  // These lines are specifically to support the Adafruit Trinket 5V 16 MHz.
  // Any other board, you can remove this part (but no harm leaving it):
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
  clock_prescale_set(clock_div_1);
#endif
  pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
  pixels.clear(); // Set all pixel colors to 'off'
  
    // attempt to connect using WPA2 encryption:
  Serial.println("Attempting to connect to WPA network...");
  status = WiFi.begin(ssid, pass);

  // if you're not connected, stop here:
  if ( status != WL_CONNECTED) {
    Serial.println("Couldn't get a wifi connection");
    while(true);
  }
  // if you are connected, print out info about the connection:
  else {
    Serial.println("Connected to network");
    clearLights();
  }
  
}

void loop() {
  //Read voltage
  int sensorValue = analogRead(sensorIn);

  // The analog signal is converted to a voltage
  float voltage = sensorValue * (5000 / 1024.0);
  if (voltage == 0)
  {
    Serial.println("Fault");
  }
  else if (voltage < 400)
  {
    Serial.println("preheating");
  }
  else
  {
    int voltage_diference = voltage - 400;
    float concentration = voltage_diference * 50.0 / 16.0 + 500;
    // Print Voltage
    Serial.print("voltage:");
    Serial.print(voltage);
    Serial.println("mv");
    //Print CO2 concentration
    Serial.print(concentration);
    Serial.println("ppm");

    lcd.setCursor(0, 0);
    lcd.print("CO2 level");
    lcd.setCursor(0, 1);
    lcd.print(String(concentration) + " ppm   ");
    
    if(concentration > 1500)
    {
      lcd.setRGB(255, 0, 0);
      setupLights2();
    }
    else if(concentration > 1000)
    {
      lcd.setRGB(255, 255, 255);
      setupLights1();
    }
    else
    {
      lcd.setRGB(0, 0, 0);
      clearLights();
    }
    
    String tmp;
    tmp = String(concentrationpCount);
    if (client.connect(server, 3000)) {
        Serial.println("connected to server");
        // Make a HTTP request:
        client.println("GET /?CO2=" + concentration + " HTTP/1.1");
        client.println("Host: 192.168.1.12");
        client.println("Connection: close");
        client.println();
      }
      
  }
  delay(1000);
}

void clearLights()
{
  pixels.clear();
  pixels.show();   // Send the updated pixel colors to the hardware.
}

void setupLights1()
{

  for(int i = 0; i < 16; i++)
  {
    if(((i / 2) & 1) == 0) 
    {
      pixels.setPixelColor(16*i+7, pixels.Color(127, 0, 255));
    }
  }
  pixels.show();   // Send the updated pixel colors to the hardware.
}


void setupLights2()
{

  for(int i = 0; i < 16; i++)
  {
    if(((i / 2) & 1) == 0) 
    {
      pixels.setPixelColor(16*i+6, pixels.Color(127, 0, 255));
      pixels.setPixelColor(16*i+7, pixels.Color(127, 0, 255));
    }
  }
  pixels.show();   // Send the updated pixel colors to the hardware.
}

Schematics

Indoor Oxygen Reactor Schema
Indoor Oxygen Reactor Schema
Screen shot 2020 04 09 at 11 22 01 am ecbx3ujqtp

Comments

Add projectSign up / Login