Crit 1: Motion – The Plant Rotater

The automatic plant rotater helps indoor plant owners ensure that their plant gets even exposure to sunlight, even when the source of sunlight is one directional. This reduces the amount that a plant will grow to one side, which can result in an undesirable aesthetic, and in extreme cases, a plant that needs additional weights and staking to stay up. A common solution is for the caretaker to rotate the pot regularly, but this can be a difficult task to keep track of. See the picture below for extreme case of succulents trying to seek out the light (and an owner irregularly rotating the pot for correction)!

The hormone auxin is commonly responsible for triggering a plant to grow towards the light. Unless a plant is irregularly shaded in its environment, this hormonal response usually encourages plant to grow upward. 

A light sensor is placed near the plant, on the side exposed to the most amount of light. It measures the light exposure at regular intervals, and when it has detected the “preferred total exposure” it will rotate the plant by a quarter turn. Since sunlight will vary throughout the day, the plant rotates based on exposure instead of based on a timer. If desired, the owner can press a button to rotate the plant before the “preferred total exposure” is reached, which allows for greater control.

When connected to p5js, each time the plant turns, information about the latest total light sum (light exposure since the last turn), the preferred total exposure, and the total number of turns is sent through the Serial Port to p5js. The P5js sketch below does several tasks:

  1. Lists the array of numbers sent from the Arduino
  2. Calculates “percent complete” which is a percentage of Total Light Exposure Since Last Turn/Preferred Total Exposure. For turns triggered by the sensor, we would expect this number to be about 100% while it could be anything lowered when the turn is triggered by a button press.  If the plant is exposed to a significant amount of light at once, the number could be over 100%.
  3. Illustrates the Total Light Exposure adjacent to the Preferred Total Exposure to provide a user with a visual.
  4. If the Percent Complete value falls within a defined range (ex: 60-125%), there will be a smiley face on the screen. If the value falls outside of this range, a frowny face will appear instead. The visualization provides a quick reference for the user to understand what is going on without having to interpret the numbers flashing on the screen.

Arduino Sketch:

//motor and light sensor

#define lightPin A0 //Ambient light sensor reading 
#include <Stepper.h>

//data smoothing and light sensor
const int numReadings = 100;
float ligReadings [numReadings];
int ligReadIndex = 0;
float ligTotal = 0;
int inputPin = lightPin; //put sensor pin here
float ligPer=0;
float light_percent=0;
int prefLigExp=150;

//stepper motor
int stepsPerRevolution = 2048; //see motor spec sheet
int motSpeed = 10;
int dt = 500;
Stepper stepmot(stepsPerRevolution, 8, 10, 9, 11); //see motor and driver specs

//for millis
unsigned long lastReadTime = 0;

//for button
const int buttonPin = 2;
int buttonValue = 0;

//p5jsturncount
int numberOfTurns = 0;

void setup() {
  pinMode(lightPin, INPUT);
  pinMode(buttonPin, INPUT_PULLUP);

  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
    ligReadings[thisReading] = 0;
  }

  Serial.begin(9600);
  lastReadTime = millis();
  stepmot.setSpeed(motSpeed);

}


void loop() {
  //millis and light sensor
  if ((millis() - lastReadTime) >= 5000UL) {
    //Serial.println(lastReadTime);
    //Serial.println(millis());
    lastReadTime = millis();
    //light sensor read
    float light = analogRead(lightPin);
    float light_ratio = light / 1023.0;
    float light_percent = light_ratio * 100;

    ligTotal = ligTotal - ligReadings[ligReadIndex];
    ligReadings[ligReadIndex] = light_percent;
    ligTotal = ligTotal + ligReadings [ligReadIndex];
    ligReadIndex = ligReadIndex + 1;


  }

  buttonValue = digitalRead(buttonPin);


  if (buttonValue == LOW) {
    stepmot.step(stepsPerRevolution / 4);
    numberOfTurns=numberOfTurns+1;
    Serial.print(ligTotal);
    Serial.print(",");
    Serial.print(prefLigExp);
    Serial.print(",");
    Serial.println(numberOfTurns);
    ligTotal = 0;
  }


  if (ligTotal >= prefLigExp) {
    stepmot.step(stepsPerRevolution / 4);
    numberOfTurns=numberOfTurns+1;
    delay(3000);
    Serial.print(ligTotal);
    Serial.print(",");
    Serial.print(prefLigExp);
    Serial.print(",");
    Serial.println(numberOfTurns);
    //make step stop rotating
    ligTotal = 0;
    //ligReadIndex=0;
  }

  else {
    stepmot.step(0);
  }


}

 

 

p5js Sketch:

//smiley sketch: https://medium.com/spidernitt/introduction-to-digital-art-with-p5-js-d8ba63080b77
//serial, sensors, arrays, and p5js: https://www.youtube.com/watch?v=feL_-clJQMs

let serial; 
let sensors = [256,256,0];
let newData = "waiting for data";
let px, py;
let rad1;
let rad2;
let rad3;
let potDiameter;

function setup() {
  createCanvas(windowWidth, windowHeight);
  stroke(255);
  let radius = min(width,height)/2;
  let rad1=radius*0.7;
  let rad2= radius*.5;
  let rad3= radius*.25
  px= width/2;
  py = height/2;
  potDiameter = radius*1.5
  serial = new p5.SerialPort();
  serial.list();
  serial.open('COM5');
  serial.on('connected', serverConnected);
  serial.on('list',gotList);
  serial.on('data',gotData);
  serial.on('error',gotError);
  serial.on('open',gotOpen);
  
}

function serverConnected() {
  print("Connected to Server");
}

function gotList(thelist) {
  print("List of Serial Ports:");
  
  for (let i=0; i < thelist.length; i++) {
    print(i + " " + thelist[i]);
    
  }
}

function gotOpen() {
  print("Serial Port is Open");
}

function gotClose() {
  print("Serial Port is Closed");
  newData = "Serial Port is Closed";
}

function gotError(theerror) {
  print(theerror);
}

function gotData() {
  let currentString = serial.readLine();
  trim(currentString);
  if (!currentString) return;
  sensors=split(currentString, ',');
  console.log(sensors);
  newData = currentString;
}





function draw() {
  background(255,255,255);
  fill(50,25,0);
  strokeWeight();
  text(newData,10,10);
  
  //numTurns=newData
  //text(numTurns,px,py)
  
  totExp=sensors[0];
  prefExp=sensors[1];
  numTurns=sensors[2];
  //strokeWeight(.5);
  text("Turn:"+ numTurns,px,py+75);
  perc = (totExp)/(prefExp)*100;
  text("Percent Complete:"+ perc,px,py+90);

  text("Preferred",px+60,py+20);
  noStroke();
  fill(200, 150, 0);
  rect(px+55,py, 55,-sensors[1]);
  
  strokeWeight(1);
  fill(50,25,0);
  text("Received",px,py+20);
  
  noStroke();
  fill(200, 200, 0);
  rect(px,py,55,-sensors[0]);
  
  if (perc >=60 || perc <=125) {
    smiley(px, py-10, 30);
  }
  if (perc <60 || perc >125) {
    frowny(px,py-10,30);
  }
  
  
function smiley(x, y, diameter) {
  // Face
  fill(255, 255, 0);                  //fills the face with yellow color
  stroke(0);                          //outline in black color 
  strokeWeight(2);                    //outline weight set to 2
  ellipse(x, y, diameter, diameter);  //creates the outer circle

  // Smile
  var startAngle = 0.1 * PI;          //start angle of the arc
  var endAngle = 0.9 * PI;            //end angle
  var smilediameter = 0.5 * diameter;  
  arc(x, y, smilediameter, smilediameter, startAngle, endAngle);

  // Eyes
  var offset = 0.15 * diameter;
  var eyediameter = 0.05 * diameter;
  fill(0);
  ellipse(x - offset, y - offset, eyediameter, eyediameter);
  ellipse(x + offset, y - offset, eyediameter, eyediameter);
}

  function frowny(x, y, diameter) {
  // Face
  fill(255, 150, 0);                  //fills the face with yellow color
  stroke(0);                          //outline in black color 
  strokeWeight(2);                    //outline weight set to 2
  ellipse(x, y, diameter, diameter);  //creates the outer circle

  var startAngle = 1.2 * PI;          //start angle of the arc
  var endAngle = -0.2 * PI;            //end angle
  var smilediameter = 0.5 * diameter;  
  arc(x, y+8, smilediameter, smilediameter, startAngle, endAngle);

  // Eyes
  var offset = 0.15 * diameter;
  var eyediameter = 0.05 * diameter;
  fill(0);
  ellipse(x - offset, y - offset, eyediameter, eyediameter);
  ellipse(x + offset, y - offset, eyediameter, eyediameter);
}


  
}

 

Crit 1 (Using the outdoors for heating/cooling) – Jud

Idea:

The main idea behind this project is to open the window to cool or heat your room when the outside temperature is what you want your room temperature to be. For example, in the summer, if it is a super nice day out and your room is very hot, you could enter a temperature into the system and it would automatically open the window to let outside air in to adjust the room temperature. This also has a dual purpose in the winter for if your house is too hot and the system could open the window to let a cool breeze in.

The main components of this system are two thermistors for measuring outside and inside temperature, a motor with an encoder to actuate the window, a motor driver to control the speed of the motor, and a p5 script animating the information from the Arduino in a helpful way. The Arduino sends the data over serial communication to the p5 script which then displays this information to the user and animates the virtual window corresponding to the current motor position.

As shown in the demo video below, the system works very well detecting a need for opening the window and acting upon it. In addition, the p5 script very accurately shows the degree of openness of the window. While these component work very well together, the controller is still fairly simple. Currently, there is just one setting for open and closed meaning the window doesn’t open a certain degree based off the temperature difference between the room and the goal temp. With more time this feature could be implemented, but for demonstration purposes of this concept, the current controller works well.

Images:

Here are images of the final system concept. The first image is an overall image showing both the physical build and the virtual animation. The second image is a close up of the virtual animation screen showing the displayed outside and inside temperatures as well as the window itself. Finally, the third image shows a top view of the physical wiring for the system.

Videos:

Video demonstration of the motor position feedback controlling the amount the virtual window is open.

Here is a video of the full system demo. As shown, when the outside temperature rises above the inside temperature, the window opens. When the outside temperature falls below the inside temperature, the window closes.

Code:

P5.js

let serial;
let latestData = "waiting for data";
var outData;

var minWidth = 600;   //set min width and height for canvas
var minHeight = 400;
var width, height;    // actual width and height for the sketch
var squareWidth = 100;

var OuterWindowHeight = 800;
var OuterWindowLength = 600;

var InnerWindowHeight = OuterWindowHeight/2;
var InnerWindowLength = OuterWindowLength;

var panelThickness = 10;
var RoomLength = 800;

var bottomInnerWindowY;
var y = 0;

var roomColor = '#f7c07c';
var skyColor = '#c2f9ff';

var outsideTemp = 100;
var insideTemp = 100;

var prevString;


function setup() {
 // set the canvas to match the window size
 if (window.innerWidth > minWidth){
  width = window.innerWidth;
  } else {
    width = minWidth;
  }
  if (window.innerHeight > minHeight) {
    height = window.innerHeight;
  } else {
    height = minHeight;
  }

  bottomInnerWindowY = (height - OuterWindowHeight)/2 + 50 + OuterWindowHeight - InnerWindowHeight/2;

  //set up canvas
  createCanvas(width, height);
  noStroke();

 serial = new p5.SerialPort();

 serial.list();
 serial.open('/dev/tty.usbmodem144101');

 serial.on('connected', serverConnected);

 serial.on('list', gotList);

 serial.on('data', gotData);

 serial.on('error', gotError);

 serial.on('open', gotOpen);

 serial.on('close', gotClose);
}

function serverConnected() {
 print("Connected to Server");
}

function gotList(thelist) {
 print("List of Serial Ports:");

 for (let i = 0; i < thelist.length; i++) {
  print(i + " " + thelist[i]);
 }
}

function gotOpen() {
 print("Serial Port is Open");
}

function gotClose(){
 print("Serial Port is Closed");
 latestData = "Serial Port is Closed";
}

function gotError(theerror) {
 print(theerror);
}

function gotData() {
  let currentString = serial.readLine();
  trim(currentString);
  if (!currentString) return;
  console.log(currentString);
  // latestData = currentString;
  // currentString = Number(currentString) - 10000;
  if ((Number(currentString) - 10000) < 10000) {
    insideTemp = (Number(currentString) - 10000);
    return;
  }
  else if ((Number(currentString) - 20000) < 10000) {
    outsideTemp = (Number(currentString) - 20000);
    return;
  }
  else if ((Number(currentString) - 30000) < 10000) {
    y = (Number(currentString) - 30000)/1000;
    return;
  }
}

function draw() {

  var outerWindowBottomY = (height - OuterWindowHeight)/2 + 50;
  var topInnerWindowY = outerWindowBottomY + InnerWindowHeight/2;

  //Update bottom window position
  bottomInnerWindowY = (height - OuterWindowHeight)/2 + 50 + OuterWindowHeight - InnerWindowHeight/2 - (y/3.1415)*(OuterWindowHeight - InnerWindowHeight);
  
  background(0);

  //Draw canvas
  strokeWeight(4);
  stroke('white');
  fill('black');
  noStroke();

  // //Draw Room
  strokeWeight(4);
  stroke('black');
  fill(roomColor);
  rect((width - RoomLength)/2, 0, RoomLength, height);

  fill('black');
  stroke(roomColor);
  textSize(30);
  textAlign(CENTER);
  text("Window Opener", width/2, 80);
  textSize(16);
  text("Outside Temp: " + outsideTemp + "\t\t\t\t\t\t\t\t\t\t Inside Temp: " + insideTemp, width/2, 125);    // displaying the input

  //Draw outer window
  strokeWeight(4);
  stroke('black');
  fill(skyColor);
  rect((width - OuterWindowLength)/2, outerWindowBottomY, OuterWindowLength, OuterWindowHeight);

  //Draw top inner window
  strokeWeight(4);
  stroke('black');
  strokeWeight(10);
  noFill();
  rect((width - InnerWindowLength)/2, topInnerWindowY - InnerWindowHeight/2, InnerWindowLength, InnerWindowHeight);
  strokeWeight(10);
  line((width - InnerWindowLength)/2, topInnerWindowY, (width - InnerWindowLength)/2 + InnerWindowLength, topInnerWindowY);//y line
  line(width/2, outerWindowBottomY, width/2, outerWindowBottomY + InnerWindowHeight);//x line

  //Draw bottom inner window
  strokeWeight(4);
  stroke('black');
  strokeWeight(10);
  noFill();
  rect((width - InnerWindowLength)/2, bottomInnerWindowY - InnerWindowHeight/2, InnerWindowLength, InnerWindowHeight);
  strokeWeight(10);
  line((width - InnerWindowLength)/2, bottomInnerWindowY, (width - InnerWindowLength)/2 + InnerWindowLength, bottomInnerWindowY);//y line
  line(width/2, bottomInnerWindowY - InnerWindowHeight/2, width/2, bottomInnerWindowY + InnerWindowHeight/2);//x line

}


function mousePressed() {
  
}

function mouseDragged() {

}

Arduino

/*
 * Window Climate Controller
 * Judson Kyle
 * judsonk
 * 
 * Collaboration:
 *  - Used thermistor code from this site: https://www.circuitbasics.com/arduino-thermistor-temperature-sensor-tutorial/
 *  
 */

#define THERMISTOR1_PIN   A0
#define THERMISTOR2_PIN   A1
#define encoderPinA   18
#define encoderPinB   19
#define motorPin1     10
#define motorPin2     11
#define motorPWM      9

#define P_I 3.14159

int encoderCountRev = 48; //48 encoder counts per revolution
int incomingByte = 0;

// https://www.pololu.com/product/4824
float gearRatio = 2*(22.0*20.0*22.0*22.0*23.0)/(12.0*12.0*10.0*10.0*10.0); 

bool motorSpinning = false;

// Motor distance measuring variables
volatile long currentEncoder_pos_f = 0, prevEncoder_pos = 0, revs = 0;
float motorCurPos = 0;

//PID Variables
float ep = 0; //Proportional error
float ei = 0; //Integral error
float ed = 0; //Derivative error
float prev_ep = 0;
double curTime = 0;
double prevTime = 0;

int pwmCommand = 0;

float kp = 50; // P gain
float ki = 0; // I gain
float kd = 0; // D gain
float motorGoalPos = P_I; // desired angle [radians]

float temp1, temp2;
float goalTemp = 480; //Degrees F

float insideOutsideDiff = 0;
float insideGoalDiff = 0;

float windowOpeningLength = P_I; //radians at the moment
float maxDiff = 100;

void setup() {
  //Initialize motor control pins
  pinMode(motorPin1, OUTPUT);
  pinMode(motorPin2, OUTPUT);
  pinMode(motorPWM, OUTPUT);

  //Initialize interrupts
  attachInterrupt(digitalPinToInterrupt(encoderPinA), encoderA, CHANGE);
  attachInterrupt(digitalPinToInterrupt(encoderPinB), encoderB, CHANGE);
  
  Serial.begin(9600);
}

void loop() {

  //Update motor current position
  motorCurPos = (2*P_I*currentEncoder_pos_f) / (gearRatio*encoderCountRev);

  temp1 = readTemp('v', THERMISTOR1_PIN);
  temp2 = readTemp('v', THERMISTOR2_PIN);

  insideOutsideDiff = temp1 - temp2; //Add in conditionals for if temperature difference is negative
  insideGoalDiff = temp1 - goalTemp; //Add in conditionals for if temperature difference is negative

  //Update motor goal position based off of difference in temperature
  if (insideGoalDiff < 0) { //Goal temp is higher than inside temp
    if (insideOutsideDiff < 0) { //Outside temp higher than inside temp
//      int openDistance = abs(insideGoalDiff/maxDiff)*windowOpeningLength;
      motorGoalPos = P_I;
    }
    else { //Ouside temp lower than or equal to inside temp
      motorGoalPos = 0;
    }
  }
  else if (insideGoalDiff > 0) { //Goal temp lower than inside temp
    if (insideOutsideDiff > 0) { //Outside temp lower than inside temp
//      int openDistance = (insideGoalDiff/maxDiff)*windowOpeningLength;
      motorGoalPos = P_I;
    }
    else { //Ouside temp higher than or equal to inside temp
      motorGoalPos = 0;
    }
  }

  updateMotorCmd(updatePID(motorGoalPos, motorCurPos));

  //Send data to p5 script for visualization
  sendData();
}

float readTemp(char degreeType, int ThermistorPin) {
  float logR2, R2, T;
  float c1 = 1.009249522e-03, c2 = 2.378405444e-04, c3 = 2.019202697e-07;
  
  int R1 = 1000;
  
  int Vo = analogRead(ThermistorPin);
  R2 = R1 * (1023.0 / (float)Vo - 1.0);
  logR2 = log(R2);

  T = (1.0 / (c1 + c2*logR2 + c3*logR2*logR2*logR2));

  switch (degreeType){
    case 'f':
    case 'F':
      T = T - 273.15;
      T = (T * 9.0)/ 5.0 + 32.0;
      break;
    case 'c':
    case 'C':
      T = T - 273.15;
      break;
    default:
      return Vo;
      break;
  }

  return T;
}

int updatePID(float goalPos, float curPos) {
  int pwmCmd;
  
  // PID Controller
  curTime = millis();
  ep = (goalPos - curPos); // error in position (p)
  ei = ei + ep; // integral error in position (i)
  ed = (ep - prev_ep) / (curTime - prevTime); // derivative error in position (d)

  prev_ep = ep;
  prevTime = curTime;

  pwmCmd = (ep*kp + ei*ki + ed*kd);

  return pwmCmd;
}

void sendData() {
  int temp1Send = 10000 + temp1;
  Serial.println(temp1Send); //Inside temp
  int temp2Send = 20000 + temp2;
  Serial.println(temp2Send); //Outside temp
  int sendData = 30000 + (int) (motorCurPos*1000);
  Serial.println(sendData); //Wnidow Opening
}

void updateMotorCmd(int pwmCmd) {
  // switch directions if pass set point
  if(ep < 0) {
    // Turn on motor A & B
    digitalWrite(motorPin1, LOW);
    digitalWrite(motorPin2, HIGH);
  }
  else {
    // Turn on motor A & B
    digitalWrite(motorPin1, HIGH);
    digitalWrite(motorPin2, LOW);
  }

  pwmCmd = abs(pwmCmd);
  if (pwmCmd > 255) {
    pwmCmd = 255;
  }
  analogWrite(motorPWM, pwmCmd);
}

void encoderA(){
  // look for a low-to-high on channel A
  if (digitalRead(encoderPinA) == HIGH) { 
    // check channel B to see which way encoder is turning
    if (digitalRead(encoderPinB) == LOW) {  
      
        currentEncoder_pos_f = currentEncoder_pos_f + 1;         // CW
    } 
    else {
        currentEncoder_pos_f = currentEncoder_pos_f - 1;        // CCW
    }
  }
  else   // must be a high-to-low edge on channel A                                       
  { 
    // check channel B to see which way encoder is turning  
    if (digitalRead(encoderPinB) == HIGH) {   
      
        currentEncoder_pos_f = currentEncoder_pos_f + 1;          // CW
    } 
    else {
        currentEncoder_pos_f = currentEncoder_pos_f - 1;          // CCW
    }
  }
}

void encoderB(){

  // look for a low-to-high on channel B
  if (digitalRead(encoderPinB) == HIGH) {   
   // check channel A to see which way encoder is turning
    if (digitalRead(encoderPinA) == HIGH) {  
       
        currentEncoder_pos_f = currentEncoder_pos_f + 1;         // CW
    } 
    else {
        currentEncoder_pos_f = currentEncoder_pos_f - 1;         // CCW
    }
  }
  // Look for a high-to-low on channel B
  else { 
    // check channel B to see which way encoder is turning  
    if (digitalRead(encoderPinA) == LOW) {   
        currentEncoder_pos_f = currentEncoder_pos_f + 1;          // CW
    } 
    else {
        currentEncoder_pos_f = currentEncoder_pos_f - 1;         // CCW
    }
  }
}

 

Electrical Schematic:

Crit1 Development: Stepper Motor Progress

I have 2 alternatives to power the turning plant- a DC motor or a stepper motor. I have run through a test of a DC motor (attached to a fan) and 2 tests for a stepper motor. I was inclined to try to use the stepper motor, but neither of the stepper motor tests have worked. It appears that the power supply module I’m using (which is supposed to be compatible with the driver and stepper motor I’m using) is failing to provide power to the driver and the stepper motor. Not clear yet where the failure is happening, although the dim LED light on the power supply module suggests that the 9V battery power supply I am using is not adequate. I will need to test another power supply to see if I can get the stepper motor to work. Since the DC motor with fan seemed to have varying success depending on the speed, I tried two stepper motor tests just in case a difference in the speed would make the stepper motor move.

UPDATE 2/28: With a new power supply (wall plug), the stepper motor code is working. Next step is to make a sketch that combines the stepper motor and the light sensor TEMT6000.

Stepper Motor Test 1 Sketch:

#include <Stepper.h>
int stepsPerRevolution=2048; //see motor spec sheet
int motSpeed=10;
int dt=500;
Stepper stepmot(stepsPerRevolution, 8,10,9,11);//see motor and driver specs

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  stepmot.setSpeed(motSpeed);

}

void loop() {
  // put your main code here, to run repeatedly:
  stepmot.step(stepsPerRevolution);
  delay(dt);
  stepmot.step(-stepsPerRevolution);
  delay(dt);


}

Stepper Motor Test 2 Sketch:

#include <Stepper.h>
const float STEPS_PER_REV=32; //see motor spec sheet
const float GEAR_RED = 64;
const float STEPS_PER_OUT_REV = STEPS_PER_REV * GEAR_RED;
int stepsReq;
int motSpeed=10;
int dt=500;
 
Stepper steppermotor(STEPS_PER_REV, 8,10,9,11);//see motor and driver specs

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);


}

void loop() {
  // put your main code here, to run repeatedly:
  steppermotor.setSpeed(1);
  stepsReq=4;
  steppermotor.step(stepsReq);
  delay(2000);

  stepsReq = STEPS_PER_OUT_REV / 2;
  steppermotor.setSpeed(100);
  steppermotor.step(stepsReq);
  delay(1000);


}

 

Crit1 Development: Experiment with Fan Motor and Light Sensor TEMT6000

With the TEMT6000 light sensor, I can turn on a motor based on input to the light sensor, similar to the one I had with the DHT-22.

With the TEMT6000, I used an analog write (instead of the digital write with the DHT-22) and the data arrives in float form. Several lines of code convert the reading into a percent. Data smoothing and millis () set up are the same as previous DHT-22 code.

#define lightPin A0 //Ambient light sensor reading 

//data smoothing
const int numReadings = 5;
float ligReadings [numReadings];
int ligReadIndex = 0;
float ligTotal = 0;
float ligAverage = 0;
int inputPin = lightPin; //put sensor pin here
//detecting sensor change
int last;
//response to sensor change

//for fan and motor
int speedPin = 5;
int dir1 = 4;
int dir2 = 3;
int mSpeed = 0;
unsigned long lastReadTime = 0;

void setup() {
  // put your setup code here, to run once:
  //pins for fan and motot
  pinMode(speedPin, OUTPUT);
  pinMode(dir1, OUTPUT);
  pinMode(dir2, OUTPUT);
  pinMode(lightPin, INPUT);

  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
    ligReadings[thisReading] = 0;
  }
  Serial.begin(9600);
  lastReadTime = millis();
}

void loop() {
  //delay(1000);

  if ((millis() - lastReadTime) >= 5000UL) {
    Serial.println(lastReadTime);
    Serial.println(millis());
    lastReadTime = millis();
    //light sensor read
    float light = analogRead(lightPin);
    float light_ratio = light / 1023.0;
    float light_percent = light_ratio * 100;

    ligTotal = ligTotal - ligReadings[ligReadIndex];
    ligReadings[ligReadIndex] = light_percent;
    ligTotal = ligTotal + ligReadings [ligReadIndex];
    ligReadIndex = ligReadIndex + 1;
    if (ligReadIndex >= numReadings) {
      ligReadIndex = 0;
    }
    ligAverage = ligTotal / numReadings;
    Serial.println(light_percent);
    Serial.println(ligAverage);

  }



  if (ligAverage > 25)
  {
    digitalWrite(dir1, HIGH);
    digitalWrite(dir2, LOW);
    analogWrite(speedPin, 225);
  }else
  {
    digitalWrite(dir1,HIGH);
    digitalWrite(dir2,LOW);
    analogWrite(speedPin, 0);
  }
}

Next, I plan to use the sensor to turn another motor (the stepper motor, which I think is a better fit for turning the plant turntable).

Crit1 Development: Experiment with Button and p5js

I ran into challenges with the previous DHT22 sensor to p5js assignment. I performed another test run using input from a button. With this, I was able to connect the p5.js web editor to the arduino, and have an image on the screen of the web editor change when the button input changed.

Arduino sketch below:

const int buttonPin = 2;
int buttonValue = 0; 

void setup() {
  // put your setup code here, to run once:
  pinMode(buttonPin, INPUT);
  Serial.begin(9600);

}

void loop() {
  // put your main code here, to run repeatedly:
  buttonValue = digitalRead(buttonPin);
  Serial.println(buttonValue);
  delay(10);

}

p5js sketch:

let serial; 
let newData = "waiting for data";

function setup() {
  createCanvas(windowWidth, windowHeight);
  serial = new p5.SerialPort();
  serial.list();
  serial.open('COM5');
  serial.on('connected', serverConnected);
  serial.on('list',gotList);
  serial.on('data',gotData);
  serial.on('error',gotError);
  serial.on('open',gotOpen);
  
}

function serverConnected() {
  print("Connected to Server");
}

function gotList(thelist) {
  print("List of Serial Ports:");
  
  for (let i=0; i < thelist.length; i++) {
    print(i + " " + thelist[i]);
    
  }
}

function gotOpen() {
  print("Serial Port is Open");
}

function gotClose() {
  print("Serial Port is Closed");
  newData = "Serial Port is Closed";
}

function gotError(theerror) {
  print(theerror);
}

function gotData() {
  let currentString = serial.readLine();
  trim(currentString);
  if (!currentString) return;
  console.log(currentString);
  newData = currentString;
}

function draw() {
  background(245,245,245);
  fill(50,50,200);
  text(newData,10,10);
  
  
  if (newData == 0) {
    rectMode(CENTER);
    rect(width/2,height/2,100,100);
  } else {
    ellipse(width/2,height/2,200,200);
    
  }
  
}

Next, for the Crit1 project, I want to be able to record or represent the actions of the plant turntable with p5js.

Resources and Demos: