{"id":2350,"date":"2022-03-03T19:37:51","date_gmt":"2022-03-04T00:37:51","guid":{"rendered":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/?p=2350"},"modified":"2022-03-03T19:37:51","modified_gmt":"2022-03-04T00:37:51","slug":"crit-1-motion-the-plant-rotater","status":"publish","type":"post","link":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/?p=2350","title":{"rendered":"Crit 1: Motion &#8211; The Plant Rotater"},"content":{"rendered":"<p>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)!<\/p>\n<p><img loading=\"lazy\" class=\"alignnone wp-image-2355 aligncenter\" src=\"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/03\/20220303_002328-225x300.jpg\" alt=\"\" width=\"385\" height=\"513\" srcset=\"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/03\/20220303_002328-225x300.jpg 225w, https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/03\/20220303_002328-768x1024.jpg 768w, https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/03\/20220303_002328-1152x1536.jpg 1152w, https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/03\/20220303_002328-1536x2048.jpg 1536w, https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/03\/20220303_002328-1200x1600.jpg 1200w, https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/03\/20220303_002328-scaled.jpg 1920w\" sizes=\"(max-width: 385px) 85vw, 385px\" \/><\/p>\n<p style=\"text-align: center\"><em>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.\u00a0<\/em><\/p>\n<p>A light sensor is placed near the plant, on the\u00a0side exposed to the most amount of light. It measures the light exposure at regular intervals, and when it has detected the &#8220;preferred total exposure&#8221; 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 &#8220;preferred total exposure&#8221; is reached, which allows for greater control.<\/p>\n<p>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:<\/p>\n<ol>\n<li>Lists the array of numbers sent from the Arduino<\/li>\n<li>Calculates &#8220;percent complete&#8221; 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.\u00a0 If the plant is exposed to a significant amount of light at once, the number could be over 100%.<\/li>\n<li>Illustrates the Total Light Exposure adjacent to the Preferred Total Exposure to provide a user with a visual.<\/li>\n<li>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.<\/li>\n<\/ol>\n<p>Arduino Sketch:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">\/\/motor and light sensor\r\n\r\n#define lightPin A0 \/\/Ambient light sensor reading \r\n#include &lt;Stepper.h&gt;\r\n\r\n\/\/data smoothing and light sensor\r\nconst int numReadings = 100;\r\nfloat ligReadings [numReadings];\r\nint ligReadIndex = 0;\r\nfloat ligTotal = 0;\r\nint inputPin = lightPin; \/\/put sensor pin here\r\nfloat ligPer=0;\r\nfloat light_percent=0;\r\nint prefLigExp=150;\r\n\r\n\/\/stepper motor\r\nint stepsPerRevolution = 2048; \/\/see motor spec sheet\r\nint motSpeed = 10;\r\nint dt = 500;\r\nStepper stepmot(stepsPerRevolution, 8, 10, 9, 11); \/\/see motor and driver specs\r\n\r\n\/\/for millis\r\nunsigned long lastReadTime = 0;\r\n\r\n\/\/for button\r\nconst int buttonPin = 2;\r\nint buttonValue = 0;\r\n\r\n\/\/p5jsturncount\r\nint numberOfTurns = 0;\r\n\r\nvoid setup() {\r\n  pinMode(lightPin, INPUT);\r\n  pinMode(buttonPin, INPUT_PULLUP);\r\n\r\n  for (int thisReading = 0; thisReading &lt; numReadings; thisReading++) {\r\n    ligReadings[thisReading] = 0;\r\n  }\r\n\r\n  Serial.begin(9600);\r\n  lastReadTime = millis();\r\n  stepmot.setSpeed(motSpeed);\r\n\r\n}\r\n\r\n\r\nvoid loop() {\r\n  \/\/millis and light sensor\r\n  if ((millis() - lastReadTime) &gt;= 5000UL) {\r\n    \/\/Serial.println(lastReadTime);\r\n    \/\/Serial.println(millis());\r\n    lastReadTime = millis();\r\n    \/\/light sensor read\r\n    float light = analogRead(lightPin);\r\n    float light_ratio = light \/ 1023.0;\r\n    float light_percent = light_ratio * 100;\r\n\r\n    ligTotal = ligTotal - ligReadings[ligReadIndex];\r\n    ligReadings[ligReadIndex] = light_percent;\r\n    ligTotal = ligTotal + ligReadings [ligReadIndex];\r\n    ligReadIndex = ligReadIndex + 1;\r\n\r\n\r\n  }\r\n\r\n  buttonValue = digitalRead(buttonPin);\r\n\r\n\r\n  if (buttonValue == LOW) {\r\n    stepmot.step(stepsPerRevolution \/ 4);\r\n    numberOfTurns=numberOfTurns+1;\r\n    Serial.print(ligTotal);\r\n    Serial.print(\",\");\r\n    Serial.print(prefLigExp);\r\n    Serial.print(\",\");\r\n    Serial.println(numberOfTurns);\r\n    ligTotal = 0;\r\n  }\r\n\r\n\r\n  if (ligTotal &gt;= prefLigExp) {\r\n    stepmot.step(stepsPerRevolution \/ 4);\r\n    numberOfTurns=numberOfTurns+1;\r\n    delay(3000);\r\n    Serial.print(ligTotal);\r\n    Serial.print(\",\");\r\n    Serial.print(prefLigExp);\r\n    Serial.print(\",\");\r\n    Serial.println(numberOfTurns);\r\n    \/\/make step stop rotating\r\n    ligTotal = 0;\r\n    \/\/ligReadIndex=0;\r\n  }\r\n\r\n  else {\r\n    stepmot.step(0);\r\n  }\r\n\r\n\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>p5js Sketch:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">\/\/smiley sketch: https:\/\/medium.com\/spidernitt\/introduction-to-digital-art-with-p5-js-d8ba63080b77\r\n\/\/serial, sensors, arrays, and p5js: https:\/\/www.youtube.com\/watch?v=feL_-clJQMs\r\n\r\nlet serial; \r\nlet sensors = [256,256,0];\r\nlet newData = \"waiting for data\";\r\nlet px, py;\r\nlet rad1;\r\nlet rad2;\r\nlet rad3;\r\nlet potDiameter;\r\n\r\nfunction setup() {\r\n  createCanvas(windowWidth, windowHeight);\r\n  stroke(255);\r\n  let radius = min(width,height)\/2;\r\n  let rad1=radius*0.7;\r\n  let rad2= radius*.5;\r\n  let rad3= radius*.25\r\n  px= width\/2;\r\n  py = height\/2;\r\n  potDiameter = radius*1.5\r\n  serial = new p5.SerialPort();\r\n  serial.list();\r\n  serial.open('COM5');\r\n  serial.on('connected', serverConnected);\r\n  serial.on('list',gotList);\r\n  serial.on('data',gotData);\r\n  serial.on('error',gotError);\r\n  serial.on('open',gotOpen);\r\n  \r\n}\r\n\r\nfunction serverConnected() {\r\n  print(\"Connected to Server\");\r\n}\r\n\r\nfunction gotList(thelist) {\r\n  print(\"List of Serial Ports:\");\r\n  \r\n  for (let i=0; i &lt; thelist.length; i++) {\r\n    print(i + \" \" + thelist[i]);\r\n    \r\n  }\r\n}\r\n\r\nfunction gotOpen() {\r\n  print(\"Serial Port is Open\");\r\n}\r\n\r\nfunction gotClose() {\r\n  print(\"Serial Port is Closed\");\r\n  newData = \"Serial Port is Closed\";\r\n}\r\n\r\nfunction gotError(theerror) {\r\n  print(theerror);\r\n}\r\n\r\nfunction gotData() {\r\n  let currentString = serial.readLine();\r\n  trim(currentString);\r\n  if (!currentString) return;\r\n  sensors=split(currentString, ',');\r\n  console.log(sensors);\r\n  newData = currentString;\r\n}\r\n\r\n\r\n\r\n\r\n\r\nfunction draw() {\r\n  background(255,255,255);\r\n  fill(50,25,0);\r\n  strokeWeight();\r\n  text(newData,10,10);\r\n  \r\n  \/\/numTurns=newData\r\n  \/\/text(numTurns,px,py)\r\n  \r\n  totExp=sensors[0];\r\n  prefExp=sensors[1];\r\n  numTurns=sensors[2];\r\n  \/\/strokeWeight(.5);\r\n  text(\"Turn:\"+ numTurns,px,py+75);\r\n  perc = (totExp)\/(prefExp)*100;\r\n  text(\"Percent Complete:\"+ perc,px,py+90);\r\n\r\n  text(\"Preferred\",px+60,py+20);\r\n  noStroke();\r\n  fill(200, 150, 0);\r\n  rect(px+55,py, 55,-sensors[1]);\r\n  \r\n  strokeWeight(1);\r\n  fill(50,25,0);\r\n  text(\"Received\",px,py+20);\r\n  \r\n  noStroke();\r\n  fill(200, 200, 0);\r\n  rect(px,py,55,-sensors[0]);\r\n  \r\n  if (perc &gt;=60 || perc &lt;=125) {\r\n    smiley(px, py-10, 30);\r\n  }\r\n  if (perc &lt;60 || perc &gt;125) {\r\n    frowny(px,py-10,30);\r\n  }\r\n  \r\n  \r\nfunction smiley(x, y, diameter) {\r\n  \/\/ Face\r\n  fill(255, 255, 0);                  \/\/fills the face with yellow color\r\n  stroke(0);                          \/\/outline in black color \r\n  strokeWeight(2);                    \/\/outline weight set to 2\r\n  ellipse(x, y, diameter, diameter);  \/\/creates the outer circle\r\n\r\n  \/\/ Smile\r\n  var startAngle = 0.1 * PI;          \/\/start angle of the arc\r\n  var endAngle = 0.9 * PI;            \/\/end angle\r\n  var smilediameter = 0.5 * diameter;  \r\n  arc(x, y, smilediameter, smilediameter, startAngle, endAngle);\r\n\r\n  \/\/ Eyes\r\n  var offset = 0.15 * diameter;\r\n  var eyediameter = 0.05 * diameter;\r\n  fill(0);\r\n  ellipse(x - offset, y - offset, eyediameter, eyediameter);\r\n  ellipse(x + offset, y - offset, eyediameter, eyediameter);\r\n}\r\n\r\n  function frowny(x, y, diameter) {\r\n  \/\/ Face\r\n  fill(255, 150, 0);                  \/\/fills the face with yellow color\r\n  stroke(0);                          \/\/outline in black color \r\n  strokeWeight(2);                    \/\/outline weight set to 2\r\n  ellipse(x, y, diameter, diameter);  \/\/creates the outer circle\r\n\r\n  var startAngle = 1.2 * PI;          \/\/start angle of the arc\r\n  var endAngle = -0.2 * PI;            \/\/end angle\r\n  var smilediameter = 0.5 * diameter;  \r\n  arc(x, y+8, smilediameter, smilediameter, startAngle, endAngle);\r\n\r\n  \/\/ Eyes\r\n  var offset = 0.15 * diameter;\r\n  var eyediameter = 0.05 * diameter;\r\n  fill(0);\r\n  ellipse(x - offset, y - offset, eyediameter, eyediameter);\r\n  ellipse(x + offset, y - offset, eyediameter, eyediameter);\r\n}\r\n\r\n\r\n  \r\n}<\/pre>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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 &hellip; <a href=\"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/?p=2350\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Crit 1: Motion &#8211; The Plant Rotater&#8221;<\/span><\/a><\/p>\n","protected":false},"author":28,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[13],"tags":[],"_links":{"self":[{"href":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/index.php?rest_route=\/wp\/v2\/posts\/2350"}],"collection":[{"href":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/index.php?rest_route=\/wp\/v2\/users\/28"}],"replies":[{"embeddable":true,"href":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2350"}],"version-history":[{"count":11,"href":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/index.php?rest_route=\/wp\/v2\/posts\/2350\/revisions"}],"predecessor-version":[{"id":2362,"href":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/index.php?rest_route=\/wp\/v2\/posts\/2350\/revisions\/2362"}],"wp:attachment":[{"href":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2350"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2350"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2350"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}