Authors: Ben Aiken, Henry Feinstein, Johnathan Clementi, and Mariya Lupandina
Project Description
Vitalitree is a street tree health monitoring device that aims to increase the survival rate of street trees and provide more granular information on environmental conditions at the neighborhood level. The hope is to enable more efficient tree management practices and to make the public more aware of the environment around them since tree health is human health.
Project Motivation
In recent years, cities have made significant financial and manpower investments in tree planting programs to combat Climate Change and rising urban heat and to improve the quality-of-life of urban residents. For example, TreePhilly aims to reach 30% tree cover in every Philadelphia neighborhood – since, today, some Philadelphia neighborhoods have as little as 2% cover, according to a 2020 study.
However, while tree planting programs are essential, many are judged as successful solely based on the number of planted trees, even though compounding urban environmental stressors cause many of these trees to die within a couple years of being planted. As such, the average lifespan of a street tree is 7 to 20 years. Such a lifespan will do little to reach the goals set out by planting programs since the average tree will only begin providing substantial environmental services at around 40 years. Only at this maturity can it absorb a significant amount of water during rain events, effectively sequester CO2 and so on. Not only does this mean that street tree planting is not reaching its maximal efficacy, but the city is also wasting money since the average cost of planting a street tree is around $1,000.
As such, we believe that urban tree planting programs should incorporate improved tree monitoring practices to ensure that baby street trees experience optimal conditions in their early lives. Currently, Philly deploys Philadelphia Tree Checkers, which is a fleet of volunteers, once a year to measure street tree health and growth by hand. In addition, Seattle has a similar initiative within their park’s department, but just like many others, they have noted that their tree management would benefit from more granular information.
Project Goals
The goal of this project was to create a compact device that could be attached to a street tree and would sense several environmental factors that are a proxy for tree health. The device should also include a real-time display that communicates the tree’s condition (and the surrounding environmental conditions) to pedestrians. Finally, the device sends data to a web-based dashboard that displaces trends throughout the city.
With Vitalitree, cities would be able to improve monitoring practices by collecting specific information about each tree’s condition without needing more manpower and could get a better sense of the efficacy of tree planting programs. They then could assess the most common issue and create management plans to address them. As such, the city could use their resources more efficiently. In addition, monitoring environmental factors as a proxy of street tree health will increase resident awareness of the environmental conditions in their neighborhoods. Collecting data to support action is an important step for advocating for improved environmental conditions.
As can be seen from the diagram and picture above, the Vitalitree prototype is a small, heart-shaped unit which contains several sensors as well as two Arduino boards to process, display, and send the sensed data. The unit is mounted on the tree at approximately chest height so as to be readable by both adults and children. Some sensors are contained within the heart itself, while others are embedded in the soil or the foliage of the tree.
Environmental Proxies for Tree Health
Rather than focus on direct measures of tree health–which can be difficult to achieve in a way which is both effective and cheap–Vitalitree relies on sensing environmental conditions that serve as a proxy for whether the tree is likely to be healthy. This approach is easier than measuring tree health directly, and has the added benefit of providing granular insights into general environmental conditions for anyone who engages with the product. Given that one of Vitalitree’s primary goals is to increase awareness of urban nature among city dwellers, providing environmental information helps bridge the connections between tree health and human health in an accessible way.
Each Vitalitree unit is proposed to sense the following environmental conditions associated with tree health:
Soil moisture: Street trees, like any plant, require a certain balance of water in the soil to thrive. Along roads and in small soil boxes, that balance is more likely to be altered, so up-to-date moisture readings are important to keep track of.
Soil compaction: When soil is too densely packed, it inhibits a tree’s ability to grow roots and absorb water and nutrients at the proper rate. Soil compaction is one of the most significant challenges street trees face due to the weight of built structures and passing vehicles which slowly compress soil over time.
Soil pH: Soil pH serves as a proxy for the nutrients the tree is able to receive through the soil. In urban environments, many contaminants can alter the level of soil acidity, preventing the tree from accessing the nutrients it needs.
Soil salinity: Salinity is another measure of soil nutrition, with trees only able to survive with a certain amount of salt in the soil. This is a particular challenge in colder urban areas where salting the roads in the winter is common, as salt is frequently sprinkled or kicked into the soil street trees are planted in.
Temperature: Trees can typically sustain life across a large range of temperatures. However, some urban areas with few green spaces and a high prevalence of paved surfaces experience the “heat island effect” in summer months, with abnormally hot temperatures created by those heat-reflective conditions. Heat islands can seriously damage tree tissue, particularly in the foliage.
Air quality: Urban areas often have a high level of particulate matter in the air, created from pollution from vehicles, construction, and other sources. These particles can settle on leaves and impede the photosynthesis process, inhibiting tree health.
As can be seen from the above list, many of the environmental factors which threaten tree health are those very same stressors on human health in the city; for instance, heat islands heighten risk of heat stroke and particulate matter in the air can damage lungs. Measuring and publicizing these environmental conditions can help raise awareness of the connections between the lives of humans and trees in the city, hopefully leading to greater support for policies and investments which improve urban environments across the board.
Project Operations
Operational Diagram
The diagram above outlines the general operating flow for the Vitalitree unit. Environmental factors–as well as near-infrared light, a measure of foliage health–are measured using a series of sensors connected to the central unit (in the case of the prototype, a pair of Arduino boards). The data is then aggregated in the central unit to generate a “health score” for the tree. The score is determined using predefined ranges for each variable reflecting what ideal conditions for a healthy tree would be.
The data is then further processed in three ways. Firstly, it is sent to a display on the tree itself, which outputs an accessible message about the health of the tree, including a face to show the tree’s “mood” as well as information about the healthiness of the conditions. The display unit also includes RGB LEDs to display the status of each individual environmental variable. The data is also sent to a cloud-based database and displayed on a publicly-accessible dashboard for those users who are interested in more detailed information about the data collected by the program. Finally, the Parks and Recreation Department is alerted if any of the environmental variables are given “unhealthy” status so they can send a ranger to help alleviate the conditions the tree is experiencing.
Check out the Vimeo video below to see Vitalitree in action!
Dashboard Wireframe
The Vitalitree dashboard will allow cities and members of the public to quickly understand local and global trends of street tree health and environmental factors. There will be several views, including a city-wide, neighborhood-wide, and tree specific pages, shown below.
The city-wide view will show an aggregated view of tree health and tree planting efforts. The user can then click into a neighborhood and view neighborhood level trends.
Finally, users will be able to click into individual Vitalitree deployments within the neighborhood and see real-time environmental conditions at that tree and trends in those data over time.
Next Steps & Deploying Vitalitree at Scale
As it stands, the Vitalitree prototype achieves many of the project’s original goals. It is contained in a small, portable unit with an accessible display, measures several environmental variables effectively, and can send data to the cloud to be displayed on a public dashboard. However, several additional steps need to be taken before Vitalitree can be deployed at scale.
- Examine sensor quality and data fidelity: The sensors used in the prototype, while affordable, have some issues with durability and consistency of measurement. Other sensor choices should be explored to ensure that the data produced by the unit is as high-quality and consistent as possible over a long period of time.
- Weatherproof components and housing: Vitalitree units deployed in the city will have to withstand the elements for long stretches. The current prototype is not weatherproofed or durable, so work is required to increase the durability of the product before it can be used.
- Move to a larger micro-controller to decrease complexity: Due to the limited pin capacity of standard Arduino Uno boards, the prototype was limited in the number of components it could control. To deploy Vitalitree at scale, we would need to utilize an Arduino Mega, or switch to a custom printed circuit board (PCB) design.
- Deploy a robust LoRa Network, or Switch to Cellular: Our prototype utilized direct LoRa transmission, however to deploy Vitalitree at scale a large LoRa network of multiple LoRaWAN gateways would be needed to capture the data transmitted from each deployed Vitalitree. While LoRa does not need line-of-sight to be effective, it may not fully work when deployed in an urban environment where there are many obstructions. For this reason, it may be necessary to transition to cellular transmission.
The completion of these four steps would create a more robust Vitalitree prototype ready to test for a longer period of time–and ultimately, a final product which can help save both city resources and the lives of street trees around the country. By linking the distribution of Vitalitree units to tree-planting efforts as well as targeting neighborhoods with particularly notable environmental issues, this product can make a thorough and targeted impact on the wellbeing of trees and people in urban environments.
Technical Specifications
Parts
This setup utilizes two arduino uno boards, but would be far simpler if a larger board, like the mega for example, was used. Additionally, the following pages provide a more indepth walkthrough of different components: LCD display, soil moisture sensor, NIR sensor, and LoRa transmitting and receiving. Finally, Elegoo Lesson 16 provides helpful instructions for the 74HC595 power shifter.
The complete parts list is as follows:
Sensing:
- 1 x Arduino Uno Board
- 1 x DHT11 Temperature and Humidity module
- 1 x Adafruit STEMMA Capacitive Soil Moisture Sensor
- 1 x JST PH 2mm 4-pin to Male Header Cable/I2C STEMMA Cable, 200mm
- 1 x Qwiic AS7263 NIR Spectral Sensor
- 1 x Qwiic Shield for Arduino
- 1 x Qwiic Cable
- 1 x Grove Dust Sensor
- 1 x Grove Cable
- 3 x F-M wires
Display:
- 1 x Arduino Uno Board
- 1 x LCD1602 module
- 1 x Potentiometer (10k)
- 5 x RGB LEDs
- 1 x 74HC595 Power Shifter
- 10 x 220 Ohm Resistors
- 9 x M-M wires
- 30 x F-M wires
Transmitting:
- 2 x Heltec WiFi LoRa 32 V2 Boards
- 2 x M-M wires
- 2 x F-M wires
Shared:
- 1 x Small Breadboard
- 1 x USB Battery Pack
Wiring
Given the many components of the device, the wiring is quite complicated. Similar to the parts list, this section is split into three categories: sensing, display, and transmitting components. Wiring is far simpler if an Arduino Mega is used instead of two Uno boards.
Sensing:
The NIR Foliage Health Sensor uses a Qwiic connection and connects to the Sparkfun Qwiic shield directly, which should be on top of the sensing arduino.
The DHT11 Temperature and Humidity module has three relevant pins, from left to right:
- VDD: Connects to a power supply (3.5~5.5 V)
- DATA: Connects to a pin on the sensing arduino, in this case pin 2
- NC: An empty pin not used in this tutorial and not included on all models of the DHT11
- GND: Ground
The Grove 2.5 Dust Sensor has three relevant pins. The black wire connects to the ground, red to power, and yellow (data) to pin 3 of the sensing arduino.
Finally, the soil moisture sensor has four pins, from left to right:
- GND (black): Ground
- VDD (red): Connects to a power supply (5.5V)
- SCL (green): Connects to the A5 pin
- SDA (white): Connects to the A4 pin
Display:
The LCD1602 module has 16 pins and must be wired carefully. Orienting the LCD with the pins on top, from left to right:
- VSS: Connects to the ground
- VDD: Connects to a power supply (+5V)
- VO: Controls the contrast of the LCD. This is connected to the potentiometer.
- RS: Register Select, which connects to pin 7 in this tutorial and determines where the LCD memory is written.
- RW: Read/Write that selects read or writing mode, connected to the ground here.
- E: Enable, which directs the LCD module. Connected to pin 8.
- D0-7: Pins that read and write data. In this tutorial D4-D7 will be connected to pins 9-12 of the board.
- A: Controls the backlight, connected to the power supply.
- K: Controls the backlight, connected to the ground.
The 74HC595 power shifter powers 4 of the RGB LEDs with the following connections, from left to right:
- 1 (Top left): Power (5.5V)
- 2: Connects to an LED.
- 3: Clock Pin connects with pin 6 on the display arduino
- 4: Ground
- 5: Data Pin connects with pin 13 on the display arduino
- 6: Latch Pin connects with pin 5 on the display arduino
- 7: Power (5.5V)
- 8: Left empty
- Bottom row: The first seven of these pins connect (via resistor) to the LED leads. The eighth pin connects to the ground.
Since the power shifter can only power 4 LEDs, the fifth is powered through the board. It is important not to connect the LED to the board without a 220 ohm resistor for each lead. Since the device will only display red, green, or yellow (both red and green on together) the blue lead is not needed on any of the LEDs.
Transmitting:
The two arduinos and the LoRa are all connected to share the measurements taken by the sensors via a single row of the breadboard. The sensing board connects via the TX (transmit) pin and the display and LoRa connect via the RX (receive) pin (fourth from the bottom left of the LoRa). Additionally, the LoRa transmitter is grounded with the bottom left most pin. It is not possible to upload code while the boards are connected via the transmitting breadboard row is not possible, the boards must be disconnected first.
Wiring all of these pieces together is not easy and may require electrical tape to cover the loose pins and LED leads. Ultimately with everything connected it should look something like the image below.
Code
The following code takes measurements from the DHT, NIR, soil moisture, and Grove Dust sensor and sends the measurements to the display board. It should be loaded onto the sensing board.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
/* Vitalitree: Sense Street Tree Health Authors: Ben Aiken, Henry Feinstein, Johnathan Clementi, Mariya Lupandina 2022/04/28 This sketch controls the sensors and data collection of environmental conditions which are then sent to Vitalitree's sister Arduino Uno board. That sister board controls the visualization of those data. This sketch, running on the first Arduino Uno, power the sensors used by Vitalitree, and then periodically transmits those data via a Serial transmission. The first part of this sketch declares the variables and controls for the various sensors we are using. These sensors include: 1 x DHT11 Temperature and Humidity module 1 x Adafruit STEMMA Capacitive Soil Moisture Sensor 1 x JST PH 2mm 4-pin to Male Header Cable/I2C STEMMA Cable, 200mm 1 x Qwiic AS7263 NIR Spectral Sensor 1 x Qwiic Shield for Arduino 1 x Qwiic Cable 1 x Grove Dust Sensor 1 x Grove Cable In the loop, the sketch captures new data from each of the sensors. Once all of the readings have been captured, it serially transmits the data, bounded by '<' and '>' characters so that the transmission packet can be parsed on the read/display and LoRa transmission board. */ // Libraries #include "Adafruit_seesaw.h" //This library includes the functions and object definitions necessary to use the sensor #include "AS726X.h" //load the Sparkfun AS726X library #include <dht.h> //set up NDVI sensor AS726X ndvi_sensor; //create the sensor object byte GAIN = 0; //don't worry about this byte MEASUREMENT_MODE = 2; //turn on Mode 2 to get measurements in the desired S & V channels //define NDVI equation variables float a; float b; float NDVI_est; //set up DHT sensor dht DHT; // set DHT abbreviation int DHT11_PIN = 2; //set up Particulate Matter 2.5 sensor int aqi_pin = 3; unsigned long duration; // setting up variables used in calculation of air quality metrics unsigned long starttime; unsigned long sampletime_ms = 30000; //sample 30s unsigned long lowpulseoccupancy = 0; float ratio = 0; float concentration = 0; //set up moisture sensor Adafruit_seesaw moisture_sensor; //Initialize the ss ("seesaw") object, which represents the sensor int is_wet = 0; //Variable to indicate whether sensed environment is wet (1) or dry (0) void setup() { Serial.begin(115200); //Initialize the serial monitor at a 115,200 baud rate if (!moisture_sensor.begin(0x36)) { //Check to see if sensor is successfully connected to Arduino board while(1) delay(1); } else { //Serial.println(moisture_sensor.getVersion(), HEX); } Wire.begin(); ndvi_sensor.begin(); pinMode(aqi_pin,INPUT); starttime = millis(); //get the current time; } void loop() { //DHT SENSOR int chk = DHT.read11(DHT11_PIN); // Serial.print("Temperature: "); Serial.println(DHT.temperature); // Serial.print("Humidity: "); Serial.println(DHT.humidity); //AQI SENSOR duration = pulseIn(aqi_pin, LOW); // read input from sensor for this loop lowpulseoccupancy = lowpulseoccupancy+duration; // add to total duration of pulsing for this time interval //get particle concentration reading from AQI sensor if ((millis()-starttime) > sampletime_ms){//if the sample time == 30s ratio = lowpulseoccupancy/(sampletime_ms*10.0); // Integer percentage 0=>100 concentration = 1.1*pow(ratio,3)-3.8*pow(ratio,2)+520*ratio+0.62; // using spec sheet curve } lowpulseoccupancy = 0; // reset pulse ratio variable for next 30 second cycle starttime = millis(); // start the next time cycle // Serial.print("Particle Concentration: "); Serial.println(concentration); //MOISTURE SENSOR uint16_t capread = moisture_sensor.touchRead(0); //Get reading from capacitive moisture sensor if (capread > 400) { //Check averaged measurement to see if environment is wet, and adjust is_wet accordingly is_wet = 1; } else { is_wet = 0; } //Print resulting readings to the serial monitor //Serial.print("Capacitive Reading: "); Serial.println(capread); //Serial.print("Soil Moisture Status: "); //if (is_wet == 1) { Serial.println("Wet"); } else { Serial.println("Not Wet"); } //NDVI SENSOR ndvi_sensor.takeMeasurements(); //command for sensor to begin taking measurements if (ndvi_sensor.getVersion() == SENSORTYPE_AS7263) { //indicate which sensor is connected based on Sparkfun sample code NDVI_est = NDVI_eq(ndvi_sensor.getCalibratedV(), ndvi_sensor.getCalibratedS()); //run NDVI equation function with S (680 nm) & V channels' (810 nm) readings // Serial.print(" Reading: S["); //print raw S & V channel readings to the serial monitor // Serial.print(ndvi_sensor.getCalibratedS(), 2); // Serial.print("] V["); // Serial.print(ndvi_sensor.getCalibratedV(), 2); // Serial.println("]"); // Serial.print(" NDVI estimate = "); //print NDVI estimate to the serial monitor // Serial.println (NDVI_est); if (( NDVI_est > 0.2) && (NDVI_est < 0.8)) { // if NDVI estimate is between 0.2 & 0.8 this indicates healthy foliage // Serial.println(" HEALTHY"); } else { // Serial.println(" UNHEALTHY"); // if NDVI estimate is outside the above range, then foliage is unhealthy } // Serial.println(); //insert row between readings } capread = constrain(capread, 0, 999); Serial.print("<"); Serial.print(DHT.temperature); Serial.print(","); Serial.print(DHT.humidity); Serial.print(","); Serial.print(concentration); Serial.print(","); Serial.print(capread); Serial.print(","); Serial.print(NDVI_est); Serial.print(">"); delay(5000); //create 5 second delay between readings } float NDVI_eq(float a, float b) { //create function for NDVI equation float result; result = (a-b)/(a+b); //NDVI equation, where a = infrared wavelength reading & b = red wavelength reading return result; delay(1000); //Wait before taking next reading } |
The second set of code should be loaded onto the display board. It receives the measurements, turns on the corresponding LED to either red, yellow, or green depending on the value, and calculates a health score, which determines which face and statement are displayed on the LCD.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 |
/* Vitalitree: Recieve and Display Street Tree Health Authors: Ben Aiken, Henry Feinstein, Johnathan Clementi, Mariya Lupandina 2022/04/28 This sketch controls the receiving, parsing, and display of environmental conditions as sensed on Vitalitree's sister Arduino Uno board. That sister board controls the sensors and periodically serially transmits those data. This sketch, running on the second Arduino Uno, will receive those data, and control 5 RGB LED's and 1 LCD screen to indicate the status of each sensed environmental factor, as well as the overall 'health' of the tree as aggregated by our scoring system. The first part of this sketch declares custom characters used by the LCD screen to visualize a smiling, flat, frown, and dead face. We also initlize the variables for holding parsed sensor data and the score value for each sensed variable. Pins are initialized in the setup function. In the loop, the sketch is constantly listening for new data on the RX Serial port. Once new data are received, they are parsed, the sensor data are mapped to values that can be aggregated to create a health score, and the health score is calculated. The respective LED for each environmental factor is controlled in the mapping data step. The data parsing makes use of code from this Arduino Forum post: https://forum.arduino.cc/t/serial-input-basics-updated/382007 This page is SUPER useful for understanding how to transmit data serially and parse those data on the receiver side. */ // Libraries #include <LiquidCrystal.h> #include <Wire.h> // Define variables and pins LiquidCrystal lcd(7, 8, 9, 10, 11, 12); // LCD pins: BS E D4 D5 D6 D7 int face_time = 8000; // Time face is displayed int sentence_time = 2000; // Time statentment is displayed int latchPin = 5; // (5) ST_CP [RCK] on 74HC595 int clockPin = 6; // (6) SH_CP [SCK] on 74HC595 int dataPin = 13; // (13) DS [S1] on 74HC595 int GREEN = 3; int RED = 4; // Define eye byte arrangements (X is for the dangerous condition) byte eye[8] = {B01110, B11111, B11111, B11111, B11111, B01110, B00000, B00000}; byte X[8] = {B10001, B11011, B01110, B01110, B11011, B10001, B00000, B00000}; // Define mouth byte arrangements byte smile1[8] = {B00000, B00000, B00100, B00110, B00011, B00001, B00000, B00000}; byte smile2[8] = {B00000, B00000, B00000, B00000, B00000, B10000, B11111, B01111}; byte smile3[8] = {B00000, B00000, B00000, B00000, B00000, B00000, B11111, B11111}; byte smile4[8] = {B00000, B00000, B00000, B00000, B00000, B00001, B11111, B11110}; byte smile5[8] = {B00000, B00000, B00100, B01100, B11000, B10000, B00000, B00000}; byte flat[8] = {B00000, B00000, B00000, B00000, B00000, B11111, B11111, B00000}; byte frown1[8] = {B00000, B00000, B00000, B00000, B00001, B00011, B00110, B00100}; byte frown2[8] = {B00000, B00000, B01111, B11111, B10000, B00000, B00000, B00000}; byte frown3[8] = {B00000, B00000, B11111, B11111, B00000, B00000, B00000, B00000}; byte frown4[8] = {B00000, B00000, B11110, B11111, B00001, B00000, B00000, B00000}; byte frown5[8] = {B00000, B00000, B00000, B00000, B10000, B11000, B01100, B00100}; byte leds = 0; // Set up variables for sensed data calculations float m_score; float f_score; float a_score; float t_score; float h_score; float health; int moisture_reading; float NDVI_est; float dust_reading; float temp_reading; float humidity_reading; // Set up variables for data transmission const byte numChars = 32; char receivedChars[numChars]; // an array to store the received data boolean newData = false; void setup() { // Initialize LCD lcd.begin(16, 2); // Shift Register pinMode(latchPin, OUTPUT); pinMode(dataPin, OUTPUT); pinMode(clockPin, OUTPUT); pinMode(RED, OUTPUT); pinMode(GREEN, OUTPUT); digitalWrite(RED, LOW); digitalWrite(GREEN, LOW); Serial.begin(115200); Serial.println("<Arduino is ready>"); } void loop() { // check for new data from sensor board recvWithStartEndMarkers(); //if received new data, parse it and put new values in premade variables, then update displays if (newData == true) { parseData(); showParsedData(); // for debugging purposes only // calculate score for each reading calculateScores(); // calculate overall health score and write display accordingly calculateHealthScore(); newData = false; } } // This function comes from the Arduino forum post referenced in the header // It looks for a data packet bounded by '<' and '>'. These bounds are used // to mark the beginning and end of the data transmission to be parsed. void recvWithStartEndMarkers() { static boolean recvInProgress = false; static byte ndx = 0; char startMarker = '<'; char endMarker = '>'; char rc; while (Serial.available() > 0 && newData == false) { rc = Serial.read(); if (recvInProgress == true) { if (rc != endMarker) { receivedChars[ndx] = rc; ndx++; if (ndx >= numChars) { ndx = numChars - 1; } } else { receivedChars[ndx] = '\0'; // terminate the string recvInProgress = false; ndx = 0; newData = true; } } else if (rc == startMarker) { recvInProgress = true; } } } // This function is custom to our string of data to be parsed // It uses commas as delimiters to parse individual data chunks // and then passes those data to the global variables so the health scores // can be caluclated void parseData() { char * strtokIndx; // this is used by strtok() as an index strtokIndx = strtok(receivedChars,","); // get the first part - the string temp_reading = atof(strtokIndx); // convert this part to an float strtokIndx = strtok(NULL, ","); // this continues where the previous call left off humidity_reading = atof(strtokIndx); // convert this part to an float strtokIndx = strtok(NULL, ","); // this continues where the previous call left off dust_reading = atof(strtokIndx); // convert this part to an float strtokIndx = strtok(NULL, ","); // this continues where the previous call left off moisture_reading = atoi(strtokIndx); // convert this part to an integer strtokIndx = strtok(NULL, ","); // this continues where the previous call left off NDVI_est = atof(strtokIndx); // convert this part to an float } // This function is used for debugging on the Read/Display side of Vitalitree // It displays the received data on the Serial Monitor, allowing us to see if // the data are being properly received and parsed void showParsedData() { Serial.print("temp: "); Serial.println(temp_reading); Serial.print("humidity: "); Serial.println(humidity_reading); Serial.print("dust: "); Serial.println(dust_reading); Serial.print("moisture: "); Serial.println(moisture_reading); Serial.print("ndvi: "); Serial.println(NDVI_est); } // This function controls the data mapping for each of the sensed environmental factors. // After the data are mapped, the instructions for changing the color of the respective // RGB LED are contained within the function. // 4 of 5 LED's are controlled by the shift register, using the R/G but not B channels of // each LED, and the shift register has 8 output channels. The last LED is controlled directly // by the Arduino Uno. void calculateScores() { //Resetting position on shift register to rewrite LED colors leds = 0; updateShiftRegister(); //Moisture //calculate score if (moisture_reading > 400){ m_score = 10; //shine green bitSet(leds, 1); updateShiftRegister(); } else { m_score = 0; //shine red bitSet(leds, 0); updateShiftRegister(); } //Foliage //calculate score if (( NDVI_est > .2) && (NDVI_est < .8)) { // if NDVI estimate is between 0.2 & 0.8 this indicates healthy foliage f_score = 10; //shine green bitSet(leds, 3); updateShiftRegister(); } else { f_score = 0; //shine red bitSet(leds, 2); updateShiftRegister(); } //air (2.5 particulate matter) //calculate score a_score = constrain(dust_reading, 0, 10000); // a_score = map(a_score, 0, 10000, 0, 10); a_score = (10 - a_score); //shine red if(a_score < 3){ bitSet(leds, 4); updateShiftRegister(); } //shine yellow else if(a_score >= 3 && a_score <= 6){ bitSet(leds, 4); bitSet(leds, 5); updateShiftRegister(); } //shine green else if(a_score > 6){ bitSet(leds, 5); updateShiftRegister(); } //temp //calculate score if(temp_reading <= 25){ t_score = temp_reading; } else { t_score = (50 - temp_reading); } t_score = constrain(t_score, 0, 25); //eliminate negative values t_score = map(t_score, 0, 25, 0, 10); //adjust range to 0-1 //shine red if(t_score < 3){ bitSet(leds, 6); updateShiftRegister(); } //shine yellow else if(t_score >= 3 && t_score <= 6){ bitSet(leds, 6); bitSet(leds, 7); updateShiftRegister(); } //shine green else if(t_score > 6){ bitSet(leds, 7); updateShiftRegister(); } //humidity //calculate score if(humidity_reading <= 50){ h_score = humidity_reading; } else{ h_score = (100 - humidity_reading); } h_score = constrain(h_score, 0, 50); //eliminate negative values h_score = map(h_score, 0, 50, 0, 10); //adjust range to 0-1 //shine red if(h_score < 3){ digitalWrite(RED, HIGH); } //shine yellow else if(h_score >= 3 && h_score <= 6){ digitalWrite(RED, HIGH); digitalWrite(GREEN, HIGH); } //shine green else if(h_score > 6){ digitalWrite(GREEN, HIGH); } } // This function takes all of the mapped data from the sensors and creates and // aggregate health score. Based on that score, the LCD screen is updated with // the faces and text information. void calculateHealthScore() { //helath score health = (2 * m_score) + (2 * f_score) + (1.5 * a_score) + (t_score) + (h_score); //send to LCD if(health > 50){ ideal(); } else if(health > 30 && health <= 50){ decent(); } else if(health > 15 && health <= 30){ poor(); } else if(health <= 15){ dangerous(); } } // This function is called when the health score indicates that the environment is 'ideal' // It shines a smiling face and then text that says "This environment is ideal." void ideal() { // LCD only has memory for 8 custom character, must wipe and re-write memory for each face customclear(); // Create custom chracters lcd.createChar(0, eye); lcd.createChar(1, smile1); lcd.createChar(2, smile2); lcd.createChar(3, smile3); lcd.createChar(4, smile4); lcd.createChar(5, smile5); // Write smile face characters lcd.setCursor(6, 0); lcd.write(byte(0)); lcd.setCursor(9, 0); lcd.write(byte(0)); lcd.setCursor(5, 1); lcd.write(byte(1)); lcd.setCursor(6, 1); lcd.write(byte(2)); lcd.setCursor(7, 1); lcd.write(byte(3)); lcd.setCursor(8, 1); lcd.write(byte(3)); lcd.setCursor(9, 1); lcd.write(byte(4)); lcd.setCursor(10, 1); lcd.write(byte(5)); delay(face_time); lcd.clear(); // Print ideal sentence lcd.setCursor(0, 0); lcd.print("This environment"); lcd.setCursor(0, 1); lcd.print("is "); lcd.setCursor(4, 1); lcd.print("ideal."); delay(sentence_time); lcd.clear(); } // This function is called when the health score indicates that the environment is 'decent' // It shines a flat face and then text that says "This environment is decent." void decent() { // LCD only has memory for 8 custom character, must wipe and re-write memory for each face customclear(); // Create custom chracters lcd.createChar(0, eye); lcd.createChar(1, flat); // Write flat face characters lcd.setCursor(6, 0); lcd.write(byte(0)); lcd.setCursor(9, 0); lcd.write(byte(0)); lcd.setCursor(5, 1); lcd.write(byte(1)); lcd.setCursor(6, 1); lcd.write(byte(1)); lcd.setCursor(7, 1); lcd.write(byte(1)); lcd.setCursor(8, 1); lcd.write(byte(1)); lcd.setCursor(9, 1); lcd.write(byte(1)); lcd.setCursor(10, 1); lcd.write(byte(1)); delay(face_time); lcd.clear(); // Print decent sentence lcd.setCursor(0, 0); lcd.print("This environment"); lcd.setCursor(0, 1); lcd.print("is "); lcd.setCursor(4, 1); lcd.print("decent."); delay(sentence_time); lcd.clear(); } // This function is called when the health score indicates that the environment is 'poor' // It shines a frowning face and then text that says "This environment is poor." void poor() { // LCD only has memory for 8 custom character, must wipe and re-write memory for each face customclear(); // Create custom chracters lcd.createChar(0, eye); lcd.createChar(1, frown1); lcd.createChar(2, frown2); lcd.createChar(3, frown3); lcd.createChar(4, frown4); lcd.createChar(5, frown5); // Write frown face characters lcd.setCursor(6, 0); lcd.write(byte(0)); lcd.setCursor(9, 0); lcd.write(byte(0)); lcd.setCursor(5, 1); lcd.write(byte(1)); lcd.setCursor(6, 1); lcd.write(byte(2)); lcd.setCursor(7, 1); lcd.write(byte(3)); lcd.setCursor(8, 1); lcd.write(byte(3)); lcd.setCursor(9, 1); lcd.write(byte(4)); lcd.setCursor(10, 1); lcd.write(byte(5)); delay(face_time); lcd.clear(); // Print poor sentence lcd.setCursor(0, 0); lcd.print("This environment"); lcd.setCursor(0, 1); lcd.print("is "); lcd.setCursor(4, 1); lcd.print("poor."); delay(sentence_time); lcd.clear(); } // This function is called when the health score indicates that the environment is 'dangerous' // It shines a dead face and then text that says "This environment is dangerous." void dangerous() { // LCD only has memory for 8 custom character, must wipe and re-write memory for each face customclear(); // Create custom chracters lcd.createChar(0, X); lcd.createChar(1, frown1); lcd.createChar(2, frown2); lcd.createChar(3, frown3); lcd.createChar(4, frown4); lcd.createChar(5, frown5); // Write frown face characters with X eyes lcd.setCursor(6, 0); lcd.write(byte(0)); lcd.setCursor(9, 0); lcd.write(byte(0)); lcd.setCursor(5, 1); lcd.write(byte(1)); lcd.setCursor(6, 1); lcd.write(byte(2)); lcd.setCursor(7, 1); lcd.write(byte(3)); lcd.setCursor(8, 1); lcd.write(byte(3)); lcd.setCursor(9, 1); lcd.write(byte(4)); lcd.setCursor(10, 1); lcd.write(byte(5)); delay(face_time); lcd.clear(); // Print dangerous sentence lcd.setCursor(0, 0); lcd.print("This environment"); lcd.setCursor(0, 1); lcd.print("is "); lcd.setCursor(4, 1); lcd.print("dangerous."); delay(sentence_time); lcd.clear(); } void customclear() { // clears all custom chracter slots so new chracters can be defined byte blank[8] = {B00000, B00000, B00000, B00000, B00000, B00000, B00000, B00000}; for(int i = 0; i < 8; i++) { lcd.createChar(i, blank); } } void updateShiftRegister() { digitalWrite(latchPin, LOW); shiftOut(dataPin, clockPin, LSBFIRST, leds); digitalWrite(latchPin, HIGH); } |
Thank you!
Thank you for learning about Vitalitree! This has been a very rewarding process and we had a ton of fun working on this project. We’d like to thank Dr. Allison Lassiter, Logan Weaver, and our classmates for all of their help and guidance throughout the semester and with Vitalitree.
Works Cited:
- ELEGOO Lesson 11 – DHT11 Temperature and Humidity Sensor
- ELEGOO Lesson 14 – LCD Display
- ELEGOO Lesson 16 – Eight LED with 74HC595
- Custom Character memory clear function posted in the Arduino forum – https://forum.arduino.cc/t/why-the-8-character-limit-with-liquidcrystal/48650/4
- Daniels, T. (2014). Environmental planning handbook. American Planning Association.
- Detailed Digikey Article about the AS726X’s Design and Operation
- Development of NDVI Index from University of Bonn
- DHT Sensor library – https://www.circuitbasics.com/how-to-set-up-the-dht11-humidity-sensor-on-an-arduino/
- LCD custom character generator – https://maxpromer.github.io/LCD-Character-Creator/
- Marritz, L. (2014, July 9). Is average tree lifespan a meaningful number? Deeproot Blog.
- MacDonagh, L. (2014, September 14) 1 Million Trees: Vision or Nightmare?
- Minov’s Custom Character walkthrough – https://minov.in/how-to-create-custom-character-for-lcd/
- NASA Page Describing Near-Infrared’s Use in Determining Vegetation Health
- ScienceDaily. (2020, June 16). Study in Philadelphia links growth in tree canopy to decrease in human mortality. ScienceDaily.
- Sparksfun AS726X NIR/VIS Spectral Sensor Hookup Guide
- Adafruit STEMMA Soil Moisture Sensor User Guide