2023 Group 11 – White Rabbit Top Hat

Group Members

Our brief was to make a ‘user-interactive theatrical prop’ that an actor can use to interject life into the scene and engage with the audience. Using an Arduino micro-controller, sensors and actuators, we wanted to design and make a wearable prop that could be used to better convey an emotion or action on stage.

Project Planning

project planning chart

Maker Manual

Materials used:

  • A broken umbrella
  • A baby jumpsuit/second hand fluffy material of your choice
  • A top hat
  • String
  • Wooden dow

Tools used:

  • Glue gun
  • Stanley knife
  • Wire cutters
  • Wood file

Recycled components:

  • The umbrella (utilising from a broken/unusable product)
  • The top hat (found second hand)
  • The material used for the ears – was a baby jumpsuit found in a charity shop
the part of the umbrella we used
where we got our material from

Circuit components needed:

  • Wires x 20
  • Micro servo SG90-HV x2
  • 6V cell x1
  • Push-button x4
  • 220 ohm resistor x4
  • Arduino and breadboard x1

Arduino breadboard layout:

Building Instructions

Making the bunny ear mechanism

  • – Locate hinge mechanism from umbrella (this can be seen in the picture above).
  • – Using wire cutters cut the mechanism so you have two pieces of metal connected by the hinge between them.
  • – Where the hinge is there should be a small hole which you can tie string through. This enables the hinge to be pulled via the string.
  • – Then get a wooden dowel and cut it to roughly 1 inch long.
  • – Use a file to make a notch in the dowel (slightly larger than the width of the string) and secure it at the bottom of the mechanism (with hot glue or duct tape). The string connected at the hinge should be behind the dowel, so the system acts as pulley.
  • – Now add two bits of card roughly 1.5 inches thick and 1 inch in height above and below the hinge. This will act as the frame which will retain the shape of the bunny ear underneath the fabric.
  • – Cut out 2 bunny ear shaped patterns that match the size of the mechanism and width of the cardboard.
  • – Glue these two bits of bunny ear together and fit it over the mechanism like a sleeve.
  • – Repeat this process again for the other ear.

Attaching the bunny ear to hat

  • – Cut a hole for the bunny ear to be pushed into the rim of the hat to maintain its position.
  • – Now with the bit of cardboard acting as the ear frame, cut along the ear where it would protrude sideways so that it can be seen.
  • – Cut on the hat where this cardboard lines up against it, and then slip the cardboard into the hat.
  • – Inside the hat cut along the seam of the cardboard and secure it with duct tape or glue.
  • – Do the same process for the other ear.

Attaching servo to bunny ear mechanism

  • – Locate the servo and attach the string from the bunny ear to the pivot.
  • – Make sure the string is pulled taught to allow for the string to move the hinge when pulled.
  • – Where the string is taught, mark the location of the servo and cut out a rectangle that matches the size of the servo (slightly smaller to ensure a secure fit).
  • – Place the servo inside the hole so that it stays in place.
  • – Brace the servo with duct tape so It doesn’t get pulled out under the tension of the string.
  • – Apply same process for the other servo/ear.

Building the circuit

  • Follow the breadboard layout shown above and attach the wires, buttons, and resistors to their correlating position on the breadboard (avoid connecting the servo wires to the board yet).

Attaching circuit to hat

  • – When the Arduino board is all connected attach it to the back of the hat with duct tape.
  • – Then get the cell and position it on the top of the hat and also fasten it with duct tape.
  • – Connect the Arduino board to a computer with a cable and run the code provided below then disconnect the cable.
  • – Connect the cell to the Arduino board.
  • – Pierce two holes in the hat for the servo wires to come out from and then connect these wires to the bread board (the wires are colour coded so match them to the correlating colour).

The Code

//creates two servo objects for the ear controls

#include <Servo.h>

Servo rightEar;

Servo leftEar;

//variables to save positions of servos

int pos = 0;

int leftPos = 0;

int rightPos = 0;

//button pin numbers

int defButt = 2;

int surpButt = 3;

int sadButt  = 4;

int confButt = 5;

//code setup

void setup(){

  //opens the serial line for testing

  Serial.begin(9600);

  //connects the servos to their pin

  rightEar.attach(9);

  leftEar.attach(10);

  //button pin setup

  pinMode(defButt, INPUT);

  pinMode(surpButt, INPUT); 

  pinMode(sadButt, INPUT); 

  pinMode(confButt, INPUT);

}

//main code loop

void loop() {

  //if statement setups, depending on the button pressed

  //each button correlates to an emotion

  if(digitalRead(defButt) == LOW){

    // Serial.println(“DEFAULT”);

    defaultEmote();

  }

  if(digitalRead(surpButt) == LOW){

    //Serial.println(“SURPRISED”);

    surprised();

  }

  if(digitalRead(sadButt) == LOW){

    // Serial.println(“SAD”);

    sad();

  }

  if(digitalRead(confButt) == LOW){

    // Serial.println(“CONFUSED”);

    confused();

  }

}

//the “default” position which the actor would set the ears to

void defaultEmote(){

  //the servo angle that pulls the ears raised

  pos = 145;

  //writing that angle to the servos so they move to the correct angle

  leftEar.write(pos);  

  rightEar.write(pos);             

}

//fully raised ears, showing surprise

void surprised(){

  pos = 270;

  leftEar.write(pos);  

  rightEar.write(pos); 

}

//ears all the way down, showing sadness

void sad(){

  pos = 50;

  leftEar.write(pos);  

  rightEar.write(pos); 

}

//one ear goes up and the other down in a small cycle, showing confusion

void confused(){

  //separate angles for left and right ear

  int rightPos = 0;

  int leftPos = 270;

  //for loop that cycles one ear up and down twice as a small animation

  for(int i = 0; i < 2; i++){

    leftEar.write(rightPos);  

    rightEar.write(leftPos);

    //pause between ear changes

    delay(1500);

    leftEar.write(leftPos);  

    rightEar.write(rightPos); 

    delay(1500);

  }

}

Design And Making Process

When deciding what we wanted to make for out theater prop we all recalled pantomimes, stories and comic book cliches which would be familiar and wildly recognised as well as fun and interactive. We thought of birds circling ones head when being dizzy or knocked out, a glowing, beating or broken heart, a mask that would have LEDs or moving parts, a ratatouille movie inspired chef hat, and an iconic Alice in wonderland top hat. We all got excited by the idea of decorating a mad hatter/white rabbit prop and having folding bunny ears that would correspond with the emotion of the actor on stage.

The sketches of our different initial ideas
Here were the different emotions we wanted to portray by the ears with the top hat.
original mechanism design

Our first idea for the mechanism was to have four servos, two per ear. The ones on the base of the hat would move the hat left and right, while the ones in the middle of the ear would fold up-and-down.

Creating And Testing The Mechanism

When looking at the servos which we wanted to use, and considering the weight of them, the power that would be needed by the base servo and the internal supporting structure of the ear to stand up right with the second servo would be reasonably large and unpractical. We therefore adapted the design shown in the sketch above to have one servo to control the entire ear.

This is what pushed us to start considering a different mechanism using strings and pulleys. We looked into different hinges and found that the folding system of an umbrella worked very well with our desired mechanism.

the pulley system pulled manually

We attached the string from this mechanism to a servo and with simple code were able to test that the mechanism would work when combined with Arduino.

the pulley system connected to the servo

We moved onto adding the ear material to the mechanism skeleton. After prototyping with scrap fabric we were confident that the servo had enough power to support the added weight, and therefore moved onto using a fluffy white material, which we recycled from baby jumpsuit found in a charity shop, to begin making.

The combined sock and mechanism connected with the servo

We found that the bunny ear sock lost its shape with only the thin metal mechanism inside and so we incorporated a cardboard skeleton to retain the ear form despite movement.

How we connected the mechanism to the top hat

As well as the bunny ear material, we were also able to find a top hat from a charity shop. Here is Carl modelling it for us.

Carl with the top hat

First, we marked on the brim of the hat where we wanted to position each ear and made a small hole to poke the metal skeleton of the mechanism into. At this point, we also added a small wooden dow with a notch in it at the bottom of the metal ear component to regulate the movement of the string when it was being pulled.

Next, we added the material component of the ear and braced it to the side of the top har to add further support and to make sure the ear wouldn’t be pulled by the force of the servo, since if the ear moved it would compromise the tension of the string operating the pulley. We did this by cutting a slit through the material, revealing the cardboard frame we had implemented earlier, and, using a Stanley knife, cut through the hat to thread this cardboard into the main body of the hat where we then secured it with gorilla tape.

The cardboard in this photo both acts as a frame for the ear shape and is used to brace the ear and mechanism to the side of the hat.

Once the ear was firmly attached to the hat, we connected the servo to the Arduino board and marked where we should secure it to the hat in order to best ensure complete and reliable movement of the ear. We then cut the shape of the servo out of the hat with the Stanley knife and fitted the servo inside.

The tension of the string connecting the servo and the ear was the most important factor regarding the mechanism performing to how we wanted. We experimented to find the best string length which, when at full extension, did not have a force large enough to break the servo. Even when the pivoting limb of the servo did not pop off under the tension of the string, the main body of the servo would begin to be pulled out of the hat and even distort the shape of the hat. When wearing it, the contracting hat would apply pressure to the user and so we considering different techniques of bracing the hat in order to maintain its shape and to make it more comfortable.

At first we tried using criss-crossing wooden dowels inside the hat to add some internal support to the hat, not only did it fail to solve the issue with the servo, but the dowels itself were distorting the geometry of the hat and made it sit uncomfortably and awkwardly on the head. Since having a self contained prop which the actor can actually wear on stage was biggest priority while making we did not use the criss-crossing dowels. Instead we tried securing the servo differently to the hat. We glued the servo to be adjacent to the hat instead of perpendicular, allowing there to be a balancing force against the tension of the string. This stopped the servo being pulled as much as it had been before, however it did not remove all issues with the servo pivot popping off under extreme tension, and so we had to be careful not to overuse the mechanism at one go and increase the delay of the servo moving.

sketch of how we braced the servo
Diagram presenting how we braced the servos by securing them differently

Attaching the arduino board and the battery to the hat

Since we wanted to make this prop self-contained, we removed our laptops as the power source and replaced it with a battery pack. To do this we had to solder the connecting cables between the cell and the arduino board to elongate the wire so it could reach .

where we had to solder

We used gorilla tape to secure the breadboard at the back of the hat so that it wouldn’t be seen from the front and placed the battery pack on the top. Ideally we would have wanted to hide the breadboard and the battery pack so create an overall sleeker design, but due to the lack of time and the size of the hat we were working with we were limited with how we could conceal these components, and is something we would change in a future iteration.

Mapping the buttons to the emotions

Once everything was attached to the hat we focussed our attention on adding signifiers on the buttons to match the corresponding emotions. We considered the position, colour and symbol on each button to enhance the intuitive interaction of the sensors.

Consideration of Position:

Assuming most users will be right handed, we placed the default, most used button furthest to the right in order for it to be in easiest reach for the user behind their head. We then ranked the emotions according to what we assumed would be most to least used emotion from right to left. The most left button, furthest out of reach would then be the least used while the most used button will be easiest to access.

Consideration of Colour:

Mapping colour and emotion can be reasonably controversial. Having ‘sad’ as blue was the most obvious choice. We decided on yellow for ‘surprised’ as it is a bright, strong colour for a sudden and strong emotional response. For ‘confused’ we chose green, and for ‘default’ we chose black.

Consideration of Symbols:

We did not assign default a symbol. Surprised had an exclamation mark, ‘!’. Confused was a question mark ‘?’, and sad was ‘:(‘.

Summary of testing conducted

When testing the hinge mechanism, we played with different powers of servo to accommodate the strength needed to pull the string. We had a very large servo initially but then opted for the smaller servo that comes in the Arduino kit as its reduced size allowed for the hat to be worn more comfortably whilst still providing the power we required.

Additionally, the tension of the string between the ear and servo was changed so that the hinge could be fully extended and closed. Furthermore, with the different emotions we gave different angles of rotation to display different emotions more clearly.

Shortcomings

Our aim with this project was to make the hat wearable as well as expressive for it to be a functional theatre prop. While we were able to do this, the arduino board including all the wires remain exposed and so take away from the fully automated, ‘Wizard of Oz’ magic which can be impressive. The user has to reach to the back of the hat in order to initiate a change in the ear movement, this action may hinder their performance and be a distraction to the audience. Ideally we would want wires that could run down the actor’s sleeve to buttons where the ears can be quietly controlled in their hand. Since the buttons are also out of sight from the user, even though the buttons are labelled, they would have to memorise the positioning of each emotion, and since these emotions have no natural cognitive mapping this would require memory work and could be a point of failure.

The ears of the bunny are also not completely straight and angle inwards. This was to do with the positioning of the ear against the hat. When the ‘confused’ function would be activated the ears would sometimes clash and prevent movement.

Also over time the servos began to get weaker and struggled to pull the mechanism efficient. A potential solution to this would be using more robust, powerful servos. This could be implemented into a next iteration where the hat would be bigger and we would have more space to conceal a bigger servo.

2023 Group 10 – Bo The Bear

Ayana Bazzy, Riva Lakkadi, and Safiya Menk

Project Overview:
No child loves anything more than havign a personal stuffed animal. Most stuffed animals are inanimate, but Bo the Bear is an interactive teddy bear that engaged with its owner to make them feel loved and happy. Bo is a comfort best friend to a designated child, providing them with loyal protection and support in times of need!

Project Brief:
Bo the Bear comes with two key tags, a blue round tag and a white card. The blue tag is the friendly tag and the white tag is the enemy tag. The child owner of the bear keeps the blue tag. When they scan the blue tag to the bear’s chest, the eyes will light up green, happy music will play, and the bear’s right arm will move. Since the child keeps the blue tag, if anyone else tries to play with the bear by using the white tag, the eyes will turn red, angry music will play, and both arms will flap around.

Our Aims:
We intended to create a product that would engage with the child through three primary interactions: audio, visual, and movement. These are some of the interactions that children tend to recognize and understand best. Some light colors have universal meanings that children can understand, and lights also appeal to a child’s eye. Children also love hearing sound and are often attracted to it, so we wanted Bo to also play music. Lastly, movement is a form of interaction that will make the child believe that the bear is alive and real, so they will truly feel as if the bear is a real-life friend! We also wanted Bo to only be loyal to its owner, so any other person who tries to interact with Bo will receive the same feedback, but it won’t be friendly.

Target Audience:
Bo is created for children ages 3-10. This is the age range where children typically engage with stuffed animals. Children get emotional support from Bo, a play toy, a sleep companion, and unconditional love from Bo.

Project Planning:

Project Task List:

Ayana BazzyPsuedocode for Speakers
Arduino Board 
Bought Teddy Bear
Inserted components into bear
Tested Code
Safiya MenkUpdated document with project progress/updates
Pseudcode for Servos
Arduino Board
Commented code
Tested Code
Finalized Code
Riva LakkadiPseudcode for RFID Tag and LED
Ordered components 
Soldiered wires into RFID Card Reader and RGB LEDs
Arduino Board
Tested Code
GANTT Chart

Brainstorming & Low-fi Prototyping:

Brainstorm

Lo-Fi Prototype

Research:

Because we’ve worked with most of the parts utilized in the project in class, all we had to do was follow the circuit diagram for each part (servo, RGB LEDs, speaker). Linked below is the circuitry for each part:

RGB LED: https://www.instructables.com/How-to-use-an-RGB-LED-Arduino-Tutorial/

Servo: https://learn.adafruit.com/experimenters-guide-for-metro/circ04-wiring

Speaker: https://learn.adafruit.com/experimenters-guide-for-metro/circ06-wiring

However, one of our most difficult struggles was getting the RGB LED to display the appropriate colors. Although we tried to make sure the wiring was okay, we had to take apart the circuitry multiple times to get it to work. We used this video along with following the Professor’s advice to get the RGB LED to work properly

Video to Help Set Up RGB LED

Writing 0 (analogue) to turn a pin on and 255 to turn it on helped us get the appropriate colors to show.

RFID Tag

Because the RFID MFRC522 was unfamiliar to all of us, we had to look at multiple resources detailing how to set it up.

https://www.instructables.com/Use-MFRC522-RFID-Reader-With-Arduino/

Design Decisions:

  • RGB LEDs: We wanted to provide visual feedback to the user as children are easily able to recognize visual signals. We chose to use an RGB LED because it is the best way to be able to display multiple colors onto one LED. We can display red, green, blue, purple, no colors, etc. As green is a universal symbol of friendliness, approachability, and acceptance, we purposely chose to display green to the user when they scan the friendly tag. The bright color will make the child feel welcome, and they will be able to recognize green as a sign of goodness. On the other hand, red is a universal symbol of evilness or anger. Therefore, when someone who is not the child owner tries to scan with the white tag, the eyes will turn red. The person scanning the tag will understand that the bear is angry due to the color of the eyes.
  • Speaker: We wanted to provide auditory feedback to the user as children tend to also interact with sound. We chose to order a speaker from Amazon instead of using the Piezo as our speaker provided more quality noise. When the friendly tag is scanned, happy music plays. The child recognizes that this is a good tune, and they will feel welcomed by the bear. On the other hand, when the enemy tag is scanned, a deeper angry tune is played, which signifies to the user that the bear does not welcome them and that the bear does not want to interact with them.
  • Servo: We wanted to provide motion feedback to the user as motion signals are an additional way of conveying emotion. We chose to use servos as they were the one motion actuators we were familiar with, and we thought they would get the job done. Unfortunately, one of our failures was that we didn’t make it so that the servo would move the entire arm/hand of the bear. We assumed that the servo would be powerful enough to move just the hand, but after inserting it, we realized there was very limited motion. There was also not enough time left to find a way to be able to increase the motion. We thought of attaching something to the bear’s hand so it moves with the sensor, but we weren’t able to carry this action out in time with the available materials. We could improve this by adapting our code to make the servos more powerful and make the motion more visible.

Principles of Interactive Design

  • Feedback: As mentioned above, we provided various forms of feedback to the user to convey the intentions and purpose of Bo.
  • Visibility: Each aspect of the project is visible to the user. The RGB LEDs are inserted into the eyes, the card tags come with the product, and the user can see the speaker and servos. Although they may not be familiar with the speaker and servo, once they start up the product, they will understand what each part does
  • Learnability: Our product is extremely easy to learn to use. We provide instructions with the product that state which key tag belongs to the owner, and we notify them that all they have to do is tap their tag on the bear’s chest. It is also easy for children to just tap a tag onto a bear, so there is no difficulty in executing the action.
  • Efficiency: The user only needs to take one step to make the bear work. All they have to do is scan the tag, and the bear takes over from there

Day By Day Blog Updates

November 15-16:
Group members decided that the Interactive Teddy Bear is our top choice for the final project

November 17-21:
Did background research on the project and decided that the three main functionalities will be the teddy bear’s eyes lighting up (RBG LEDs), the hands of the teddy bear waving (servo motors), and audio feedback (speaker). We found that we could used recycled items for the servo motors, wires, resistors, and RGBs, but we had to buy all the other components.

November 20:
We created a brainstorm diagram that took into consideration the material, aesthetic, consumers, function, cost, safety, environment, and size of our project. We also created a lo-fi prototype in the form of a storyboard detailing the two different states the teddy bear can be in (friendly and unfriendly).

November 25-27:
We bought the RFID tags and speaker from amazon after researching what models would work best with our project

December 4:
We started working with the RBG LEDs but had some difficulty getting the correct colors to show up. After lots of trial and error, we went to the professor for advice and did more research online.

December 5:
The RGB code started working and all the correct colors were lighting up. We also soldered long wires to both RGBs because they needed to reach the board from all the way inside of the teddy bear’s eyes. Next, we got the servo motors and speaker to work in both a friendly mode and an unfriendly mode. For the friendly servo, we moved the right motor back and forth to simulate a hand waving. The unfriendly servos had both motors moving in a frenzy to show that the bear would be upset. For the speaker, we chose a happy tune for the friendly mode and a lower register dark tune for the unfriendly mode.

December 6:
We soldered 8 long wires to the RFID scanner so that it could sit inside of the bear and still reach the board. This took a long time because at first, all the soldered wires were touching each other and the scanner was not working, After going back and forth trying to fix the soldering, we finally got the RFID tag to recognize and scan both the blue and white tags.

December 7-9:
We spend these last few days testing and finalizing our code before the presentation. We initially tested each component separately, so we finally combined the RBG, servo, and speaker code and got rid of any bugs we found.

December 10:
We put the RBG LEDs inside the eyes of the bear, the servo motors inside the arms of the bear, and the RFID tag in the chest of the bear. We decided to leave the speaker outside of the bear so that the user could hear the audio more clearly.

December 11:
We presented our final presentation to the class and demonstrated our interactive teddy bear to the instructors.

Maker Manual:

Tools & Supplies:

  • 1 Arduino Uno
  • 2 Breadboards
  • 6 Resistors 220Ω (Red-Red-Brown)
  • 2 RGB LED lights
  • 2 Servo Motors (X-shaped horns)
  • 1 Speaker (3 Watts)
  • 1 MFRC-RC522 RFID Reader with 2 Tags
  • Wires
  • USB Cable
  • Stuffed Teddy Bear
  • Soldering Iron
  • Scissors
  • Cutting Knife
  • Duck tape

Circuit Diagram:

Building the Final Product:

  1. Soldering the Wires:
    – Solder wires into RFID card reader
    – Solder longer wires for the LEDs to fit into the bear

2. Build the Circuit According to the Diagram:
– the RFID reader should activate the correct LEDs, music and servo motions

3. Stuffing the Bear:
– Slit the back of the bear using a knife cutter/scissors
– Cut small holes into the bear’s eyes for the LEDs to fit in
– Remove the stuffing from the body, arms and most of the head
– Cut small holes into the bear’s hands for the servos to fit in
– Slide each servo into the right arm and attach the horn from the outside through the hole cut
– Restuff the bear’s arms to keep the motors in place
– Carefully slide each LED light into the correct hole and restuff the head to secure the position
– Use duck tape to stick the RFID reader on the bear’s belly and restuff to secure its position

Our Final Product:

Complete Code:

//libraries for the RFID Tag
#inlcude <SPI.h>
#inlcude <MFRC522.h>

//library for the servo
#include <Servo.h>

//create Servo objects for the arms of the teddy bear
Servo rightArm;
Servo leftArm;

//variable for speaker pin
int speakerPin = 6;

//Pins 9 and 10 communicate the RFID tags to the Arduino Board
#define SS_PIN 10
#define RST_PIN 9
MFRC522 rfid(SS_PIN, RST_PIN); // Instance of the class

MFRC522::MIFARE_Key key;

// Init array that will store new NUID
byte nuidPICC[4];

//utilized a built in Arduino file called NUID that when you scan tags, it returns the UID of both tags.
String happy_user = “F0 76 80 10”;
String angry_user = “C3 64 1A 03”;
//variable for which user is logged in. there are two options: the user the bear recognizes or the user the bear doesn’t
int loggedIn = 0;

void setup() {
//setupRFID
Serial.begin(9600);
SPI.begin(); // Init SPI bus
rfid.PCD_Init(); // Init MFRC522
for (byte i = 0; i < 6; i++) {
key.keyByte[i] = 0xFF;
}

//setupRGBLEDs
//left eye
pinMode(A0,OUTPUT);
pinMode(A1,OUTPUT);
pinMode(A2,OUTPUT);
//right eye
pinMode(A3,OUTPUT);
pinMode(A4,OUTPUT);
pinMode(A5,OUTPUT);

//setupServo
//left arm
leftArm.attach(3);
//right arm
rightArm.attach(5);

//setupSpeaker
pinMode (speakerPin, OUTPUT);
}

void loop() {
//initialize the state of the bear to neutral. no lights should be on
neutralLights();

//all of this code is used to verify the presence of the correct RFID Tags
// Reset the loop if no new card present on the sensor/reader. This saves the entire process when idle.
if ( ! rfid.PICC_IsNewCardPresent()) //only execute the code if a different card is presented
return;

// Verify if the NUID has been readed
if ( ! rfid.PICC_ReadCardSerial())
return;

Serial.print(F(“PICC type: “));
MFRC522::PICC_Type piccType = rfid.PICC_GetType(rfid.uid.sak);
Serial.println(rfid.PICC_GetTypeName(piccType));

// Check is the PICC of Classic MIFARE type
if (piccType != MFRC522::PICC_TYPE_MIFARE_MINI &&
piccType != MFRC522::PICC_TYPE_MIFARE_1K &&
piccType != MFRC522::PICC_TYPE_MIFARE_4K) {
Serial.println(F(“Your tag is not of type MIFARE Classic.”));
return;
}

//write whatever is scanned in hex form to the currentState variable. this variable is used later to compare against actual tag name
String currentState = “”;

if (rfid.uid.uidByte[0] != nuidPICC[0] ||
rfid.uid.uidByte[1] != nuidPICC[1] ||
rfid.uid.uidByte[2] != nuidPICC[2] ||
rfid.uid.uidByte[3] != nuidPICC[3] ) {
Serial.println(F(“A new card has been detected.”));

// Store NUID into nuidPICC array
for (byte i = 0; i < 4; i++) {
nuidPICC[i] = rfid.uid.uidByte[i];
}

//Feedback to the serial monitor to ensure that the RFID Tag is scanning
Serial.println(F(“The NUID tag is:”));
Serial.print(F(“In hex: “));
printHex(rfid.uid.uidByte, rfid.uid.size);
Serial.println();
Serial.print(F(“In dec: “));
printDec(rfid.uid.uidByte, rfid.uid.size);
Serial.println();

//takes in the UID read and concatenates each index to the currentState string
for (byte i = 0; i < rfid.uid.size; i++) {
currentState.concat(String(rfid.uid.uidByte[i] < 0x10 ? ” 0″ : ” “));
currentState.concat(String(rfid.uid.uidByte[i], HEX));
}
currentState.toUpperCase();
}
else {Serial.println(F(“Card read previously.”));
// Halt PICC
rfid.PICC_HaltA();
// Stop encryption on PCD
rfid.PCD_StopCrypto1();
}

//compare the currentState against the tag UID and play the appropriate bear mood based on that
if(currentState.substring(1) == happy_user){
Serial.println(“Inside happy state”);
int y = 0;
while (y <2) {
happyLights();
happyMusic();
happyArms();
y+=1;
delay(100);
}
}

else if(currentState.substring(1) == angry_user){
//if bear recognizes user, play happy music, lights, and wave nicely
Serial.println(“Inside angry state”);
//use a loop to increase duration of angry state
int x = 0;
while (x<2) {
Serial.println(x);
angryLights();
angryArms();
angryMusic();
x+=1;
delay(100);
}
}
else {
//this means a tag was not scanned. the bear can remain in a neutral state
Serial.println(“inside neutral state”);
neutralLights();
}
}

// Helper routine to dump a byte array as hex values to Serial.
void printHex(byte *buffer, byte bufferSize) {
for (byte i = 0; i < bufferSize; i++) {
Serial.print(buffer[i] < 0x10 ? ” 0″ : ” “);
Serial.print(buffer[i], HEX); }
}

// Helper routine to dump a byte array as dec values to Serial.
void printDec(byte *buffer, byte bufferSize) {
for (byte i = 0; i < bufferSize; i++) {
Serial.print(buffer[i] < 0x10 ? ” 0″ : ” “);
Serial.print(buffer[i], DEC); }
}

void printLogin(String id){
Serial.println(“You are logged in as “+id+” .”);
}

//SERVO

void angryArms() {
Serial.println(“Angry arms”);
for (int pos = 0; pos <= 90; pos +=15) { // goes from 0 degrees to 90 degrees in steps of 15 degrees
rightArm.write(pos); // write both arms to the position
leftArm.write(pos);
delay(25); // waits 25ms for the servo to reach the position
}
for (int pos = 90; pos >= 0; pos -= 15) { // goes from 90 degrees to 0 degrees
rightArm.write(pos); // write both arms to the position
leftArm.write(pos);
delay(25); // waits 25ms for the servo to reach the position
}
}

void happyArms() {
Serial.println(“Happy arms”);
//only move the right arm to wave
for (int pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees in steps of 1 degree
rightArm.write(pos); // tell servo to go to position in variable ‘pos’
delay(20); // waits 20ms for the servo to reach the position
}
delay(1000);
for (int pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
rightArm.write(pos); // tell servo to go to position in variable ‘pos’
delay(20); // waits 20ms for the servo to reach the position
}
}

//SPEAKER

//function that plays a certain tone
void playTone(int frequency, int duration) {
tone(speakerPin, frequency, duration);
delay(duration); // Wait for the tone to finish before playing the next one
noTone(speakerPin);
}

//function2 that plays a certain tone
void playNote(char note, int duration) {
char names[] = { ‘C’, ‘D’, ‘E’, ‘F’, ‘G’, ‘A’, ‘B’ };
int tones[] = { 1915, 1700, 1519, 1432, 1275, 1136, 1014 };
// play the tone corresponding to the note name
for (int i = 0; i < 7; i++) {
if (names[i] == note) {
tone(speakerPin, tones[i], duration);
}
}
}

//function that plays happy music in happy state
void happyMusic(){
int length = 20; // the number of notes
char notes[] = “EDCDEEEDDEFGGGFEEDDD “; // a space represents a rest
int beats[] = { 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 4 };
int tempo = 300;
for (int i = 0; i < length; i++) {
if (notes[i] == ‘ ‘) {
delay(beats[i] * tempo); // rest
} else {
playNote(notes[i], beats[i] * tempo);
}
// pause between notes
delay(tempo / 2);
}
}

//functiont that plays angry music in angry state
void angryMusic(){
playTone(220, 300); // A4
playTone(220, 300); // A4
playTone(220, 300); // A4
playTone(185, 300); // F4
delay(100);
playTone(196, 300); // G4
playTone(196, 300); // G4
playTone(196, 300); // G4
playTone(174, 300); // B4
delay(500); // pause before playing again
}

//LEDs

void happyLights(){
Serial.println(“Initializing happy lights…”);
//left eye
analogWrite(A0,255);
analogWrite(A1,0);
analogWrite(A2,255);
//right eye
analogWrite(A3,255);
analogWrite(A4,0);
analogWrite(A5,255);
}

void angryLights(){
Serial.println(“Initializing angry lights…”);
//left eye
analogWrite(A0,0);
analogWrite(A1,255);
analogWrite(A2,255);
//right eye
analogWrite(A3,0);
analogWrite(A4,255);
analogWrite(A5,255);
}

void neutralLights(){
//right eye
//Serial.println(“Initializing neutral lights…”);
analogWrite(A0, 255);
analogWrite(A1,255);
analogWrite(A2,255);
//left eye
analogWrite(A3,255);
analogWrite(A4,255);
analogWrite(A5,255);
}

Limitations

  • All of the wires and board stick out of the bear, so the bear isn’t cuddly friendly
  • Aesthetic shortcomings due to the wiring
  • Limited motion due to ineffective attachment of the servos. We could have placed the servos more effectively or tried one of various methods in order to make it so that the servo moves the entire bear’s hands/arms.

Suggestions

  • Rip out a bit of the bear’s paw and attach it to the Servo so the actual hands move
  • Get a bigger bear so that all of the wiring and the boards can fit inside
  • Add more features such as talking, dancing, and customization to the child

2023 Group 9 – SmartLeaf

Project by Benjamin Gaunt, Jessica Malek, Catriona Gholami

Selected Project Brief:

Our brief was on the basis of monitoring your plants, by which we must create an automatic irrigation system with sensors for plant conditions. 

You care about your plants, right? Then, why not try Arduino to help you monitor and water them? This project should include a soil humidity sensor that alerts (through some beeping sound or LCD display) when the humidity is too low or too high. It also has to include some automatic irrigation system (see some examples here: https://all3dp.com/2/arduino-watering-system-plant-irrigation-project/). You can also add a light sensor for the amount of light available and an indicator to the user (the carer) in case they need to change the position of the plant accordingly, among other sensors you think would help to keep your plant healthy. Design for providing long term behaviours like the plant thanking you if you have been treating them well (for example, by watering them the last couple of days) and then showing a “Thank you!” message in the display.

Design Process

Based on our selected project brief, we researched the market and audience and how such a product would be beneficial in creation of an interactive plant watering system. We recognize the importance of maintenance of optimal conditions in aiding plant growth and decided to focus our project on the development of an intelligent monitoring system for plant care with feedback to the user.

Plant wellfare is intricately tied to numerous biotic and abiotic factors which consist of light exposure, soil moisture, temperature and just general conditions of the surrounding environment. Our solution to this brief is the creation of an integrated approach and sense of autonomy in plant care systems, to ensure conditions can remain optimum with minumum effort on the client/user based side; meaning the plant can be kept well and alive, even if the user has forgotten about it. Traditional plant care relies solely on periodic human interaction and intervention of conditioning, meaning the parameters of care can be fluctuating and inconsistent, becoming easily overseen. This is where our idea of providing physical and realtime feedback of interaction stemmed from; aiming to notify the user of watering system activations and how the plant is holding up.

The main aspects of our design choices were to ensure effective usability and reliability, interaction (some kind of user experience aspect that makes it fun), and feedback to the user,.

Selection of components:

We ordered the kit giving us things necessary for the completion of the system such as a capacitive soil moisture sensor, a 1-channel 5V relay module, a mini water pump, and vinyl tubing. This integration reduces the complexity of sourcing individual components, ensuring compatibility amongst the system components and functions:

Capacitive soil moisture sensor: Accurately measuring soil moisture levels; more reliable sense compared to a resistive one meaning consistent reading can be obtained throughout.

5V Relay module: Controling of both devices i.e the water pump and the arduino microcontroller; simplifying the interface for safe, efficient operation

Water pump: The specific water pump we ordered consisted of a submersible design. This submersible nature meant that all conditions would be suitable and enabled the use of a water reservoir submersion, so that water can be efficiently delivered directly to the plant as needed for our irrigation system. Has a brushless DC, meaning energy efficiency is sourced.

Vinyl tubing: Water transportation to the plant; flexibility for streamlining the component connection and adjustable to design and system architecture.

Graphical Display Selection:

We were unsure of whether to select an LCD graphical display or an OLED graphical display for our system, but we decided to go with an OLED graphical display module. The OLED would enable us to provide our visual feedback to the user and due to the properties of an OLED it means we would be able to display graphics with a fast response and contrast/brightness compared to an LCD. It ensures accessibility at all times even in lower light conditions where the user may not be able to see well. The response time is also much faster, allowing us to display our feedback in a swift manner; which is important particularly if a change of an optimum condition is drastic and urgent action or notification to the user is necessary.

Component Trials:

We wanted to get started despite not having all our components arrived, so we began through testing the things we did have. Our first experiments began with the DHT sensor (detection of humidity and temperature). We tested the functionality of the sensor and how it responds to different conditions, different rooms etc. The wiring was as below:

Wiring and testing of the humidity and temperature sensor

Our initial wiring was incorrect and the sensor was heating up due to a large voltage and current being passed through which resulted us in implementing a resistor. This was our initial attempt of setting up the sensor; which we later adjusted and corrected the wiring to no longer require a resistor.

To ensure accurate readings, calibration was required and we tested to ensure that it could provide reliable data by changing conditions. This allowed us to begin setting up our arduino environment for the final hard coding of the system.

Configuration of the DHT sensor:

#include "DHT.h"
#define DHTPIN 4
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);

To allow the sensor to work we had to include the DHT library in arduino; providing pre-written code for this specific component and allowing functionality to be activated; abstracting the communication protocols and readings. Inclusion of this library means that we can instantise functions such as ‘readTemperature()’ and ‘readHumiditity()’ in our code testing. We then assigned it to a digital pin (in this picture, it was 7, although in later testing we switched to 4 to accomodate our other components). The sensor type we were using was established as DHT11.

Library reference: https://www.arduino.cc/reference/en/libraries/dht11/

Setup function:

void setup() {
  Serial.begin(9600); // Initialize serial communication
  dht.begin();
  }
}

Initialising the serial communication for the DHT sensor to activate outputs and confirm readings are viable and reliable.

Loop function:

void loop() {
  ReadSoil();     // Reads soil moisture
  ReadHumidity(); // Reads humidity and temperature
}

Calls the functions which we shall define to be in a loop, so that measurements can be consistently taken and updated; to make sure system is in real-time.

ReadHumidity function:

void ReadHumidity(){
  // read humidity
  float humi  = dht.readHumidity();
  // read temperature as Celsius
  float tempC = dht.readTemperature();
  Serial.println();
  // check if any reads failed
  if (isnan(humi) || isnan(tempC)) {
    Serial.println("Failed to read from DHT sensor!");
  } else {
    Serial.print("Humidity: ");
    Serial.print(humi);
    Serial.print("%");

    Serial.print("  |  ");

    Serial.print("Temperature: ");
    Serial.print(tempC);
    Serial.print("°C ~ ");
  }

}

In this function we use the pre-defined functions from the DHT library to obtain the readings from the sensor. We print out the results in the serial monitor so that we can track the changes. By making sure we include the isnan() function, we check to make sure the readings are number values that can be printed and reflected from the physical environment – allowing a route for display of error messages and communication. This means the system can be aware of potential issues with the reading, ensuring reliability of the future monitoring system.

ReadSoil function:

void ReadSoil(){
  int sensorSoilValue = analogRead(soilSensorPin); // Read soil analog value
  float moisturePercentage = map(sensorSoilValue, 0, 1023, 0, 100); // Map to percentage
  //code to print moisture level to LED sensor

  delay(1000); // Wait for a second before the next reading

}

ReadSoil represents the part of the water pump system responsible for the soil moisture level. We decided to use the analog value and conversion of it through mapping into a percentage for better readability and more intuitive representation of soil moisture to the user. This is the basis of the main code which we will later use to make the irrigation system activate when soil moisture falls below a certain threshold (where optimum conditions of the plant can no longer be satisfied). It is also necessary to incorporate a delay so that there is space between consecutive readings, to allow adjustments of soil as required.

Visual feedback experimentation:

After setting up the hardware and phsyical architecture of pump and system, we decided to experiment with the sensor detections and how we can map them onto visual feedback for example an RGB LED.

RGB feedback of plant soil humidity

This is the code we had to add to activate the functionality of our feedback RGB LED light. In our void setup function we added:

for(int i = 0; i < 3; i++){
    //  set the 3 pins as outputs
    pinMode(ledDigitalOne[i], OUTPUT);

We established the threshold of humidity in which feedback must be shown. The choice of humidity values are relative to the plant optimum conditions required. A response will be triggered when the humidity is too low. Within our ReadHumidity function we added:

 if (humi > 80 || humi < 40) {
    setColor(ledDigitalOne, RED);
  } else {
    setColor(ledDigitalOne, GREEN);
  }

setColor function:

Allows seting up of the LEDs by iteration of the respective pins to visually indicate the conditions like the soil humidity detection

void setColor(int* led, const boolean* color) {
  for(int i = 0; i < 3; i++){
    digitalWrite(led[i], color[i]);
  }
}

Main system architecture design:

For the design we decided to use all recycled materials; with our thought process being resembling of a smal garden made with an eco-conscious approach. The garden design aimed to create a visual aesthetic/appeal for the user, furthermore aligning with the purpose and intention of the project – a familiar and pleasant environment for interactivity and accessibility. By creating a miniature representation of a garden with the automatic plant irrigation system, we foster a sense of engagement and enjoyment; a wholesome experience for a user when making use of the system.

As for the base/foundation of all the components we decided to use a recycled tin foil tray for stability and mounting all our required components on. The shallow edges of the tray also ensured for any potential spill-offs to be caught and overall organizing the appearance of our structure.

We also used scrap paper to create decorative elements on this foil tray resembling of flowers and plants to enhance the aesthetic appeal of the system, likewise with the water tray looking like a watering bucket or can adding a thematic touch to the whole system.

Combining components and feedbacks and testing:

After combining all the elements which we worked on/tested individually beforehand, we had to figure out which feedback would be most approapriate for our system.

Initially we incorporated and additional blue LED in the system, which would dim/brighten in response/correlation with the light sensor detections. However we did not proceed with this as it was rather redundant as this data is already displayed on the OLED screen and isn’t such a drastic influential factor to the irrigation system. This also allowed more simplicity and only the necessary feedback/most useful remained.

Addition of RGB LED:

As a visual feedback choice we enhance the user experience with an RGB LED providing immediate visual feedback without the need for users to wait for the values/confirmation on the OLED screen. This means users instantly gauge the conditions of the plant by the colouring of the LED. This allows for intuitive color recognition and real-time monitoring, making the overall experience rather user-friendly. It is complementary to the OLED display which allows for a comprehensive visual feedback system.

OLED Screen testing:

The OLED screen is our main instant visual feedback that the user can rely on; displaying swift feedback about the plant conditions in an intuitive way. The real-time values are presented (converted to percentages) alongside the temperature in Celsius. We created three separate display screens that cycle through to show to the user.

Add to our void loop:

 //cycles through the 3 different displays
  if (displaynum == 1){
    DisplayFace();
    displaynum = 2;
  }
  else if(displaynum == 2){
    DisplayText();
    displaynum=3;
  }
  else if(displaynum == 3){
    DisplayGoodBad();
    displaynum = 1;
  }

Coding the display of the values:

void DisplayText(){
  display.setTextSize(1); //set the size of the text
  display.setTextColor(WHITE); //color setting
  display.setCursor(0,0); //The string will start at 0,0 (x,y)
  display.clearDisplay(); //Erase the previous display on the screen
  display.print("Soil Moisture: ");
  display.print(100 - soilPercentage); //displays the inverse of the soil percentage as reading a high percentage as water increases is more human friendly
  display.println("%");
  display.print("Humidity: ");
  display.print(humi);
  display.println("%");
  display.print("Temperature: ");
  display.print(tempC);
  display.println("C");
  display.print("Light: ");
  display.print(100 - lightPercentage);
  display.println("%");
  display.display(); //send the text to the screen
}

In another representation we have the numerical values stripped and “Good” or “Bad” messages streamlining communication the user on the plant conditions. This enhances the user-friendly environment by which especially users without technical expertise can grasp the plant healthy without the need of delving into specific numerical readings. Due to this, the system aligns with the goal of making it accessible in many ways to a wide range of users. If a user is not familiar with plant metrics this is ideal and the user will not need to be overwhelmed by any technical details. The indicators are clear and concise and along with the piezo buzzer notification too make it much more inclusive.

Coding the display of good/bad indicators:

/This display shows if the values on the previous display are good or bad, cycling through the facefactors array to see if they are 1(good) or 0(bad)
void DisplayGoodBad(){
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.clearDisplay();
  display.print("Soil Moisture: ");
  if(FaceFactors[0] == 1){
    display.println("Good");
  }
  else if(FaceFactors[0] == 0){
    display.println("Bad");
  }
  display.print("Humidity: ");
  if(FaceFactors[1] == 1){
    display.println("Good");
  }
  else if(FaceFactors[1] == 0){
    display.println("Bad");
  }
  display.print("Temperature: ");
  if(FaceFactors[2] == 1){
    display.println("Good");
  }
  else if(FaceFactors[2] == 0){
    display.println("Bad");
  }
  display.print("Light: ");
  if(FaceFactors[3] == 1){
    display.println("Good");
  }
  else if(FaceFactors[3] == 0){
    display.println("Bad");
  }
  display.display(); //send the text to the screen

}

The user will be alerted with the piezo buzzer, and the OLED display which will display and tell the user if the watering can is empty and if it need to be refilled in order for the plant to be watered once again/ for the automatic irrigation system to continue.

We added a Smiley/Sad face display on the OLED as a graphical representation of plant status/ if optimum conditions are reached or not. This enables the user to understand the status at a quick glance without needing to interpret any of the numerical values if they do not want to. This also in a way humanizes the interaction with the plant system, making users more likely to care for and engage with the plant when presented with a communication of a friendly and approachable manner. A smiley face provides a positive reinforcement for users to continue engaging in the plant care and highlights the idea of the users efforts contributing to their plant well-being.

Coding the display of the faces:

void DisplayFace(){
  //check how many of the 4 factors for plant growth are good(1)/bad(0)
  int counter = 0; //sets a counter as a local variable, which gets incremented by 1 as per each factor is good
  for(int i = 0; i <=3; i++){
    if(FaceFactors[i] == 1){ //loops through all 4 items in the global array named facefactors
      counter+=1;
    }
  }

  display.clearDisplay();
  //face
  display.drawCircle(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 30, WHITE); // circle drawn at midpoint of screen with radius 30
  //eyes
  display.drawCircle(SCREEN_WIDTH / 2 - 10, SCREEN_HEIGHT / 2 - 5, 5, WHITE);
  display.drawCircle(SCREEN_WIDTH / 2 + 10, SCREEN_HEIGHT / 2 - 5, 5, WHITE);

  //smily face drawn when all 4 factors are good
  if(counter == 4){
    display.fillCircle(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2 + 7, 6, WHITE);
    display.fillCircle(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2 + 6, 6, BLACK); // black circle drawn just above the mouth circle to leave a smily face
  }
  //midface drawn when 1 or 2 factors are bad
  else if (counter > 1 && counter <=3){
    display.fillRect(SCREEN_WIDTH / 2 - 15, SCREEN_HEIGHT / 2 + 10, 30, 3, WHITE);
  }
  //sadface drawn when 3 or 4 factors are bad
  else if(counter <=1){
    display.fillCircle(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2 + 9, 6, WHITE);
    display.fillCircle(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2 + 11, 6, BLACK); //black circle drawn just below the mouth circle to leave a frowny face
  }
  display.display();
}

Finalising and putting it all together:

We also decided to implement a controlled watering mechanism, where the water pump only dispenses the water in limited bursts with frequency adjusted based on the plant current conditions. The water pump is limited to only two pumps to prevent the soil from becoming waterlogged and that there is sufficient moisture throughout in this automatic irrigation system; preventing the risk of overwatering and excessing water accumulation which tends to be a frequent cause in plant struggle under manual human care. An automatic irrigation system also avoids water stress on a plant; the adaptive nature means the soil moisture is monitored and if failed and remains dry after the initial watering, will be watered again or detected that the watering can is empty.

Final wiring/arduino breadboard and component layout
Watering/ Irrigation system entering plant
OLED Technical Display version with real-time statistics
Final Design of system

Project Task List

Week 9– selection of topic for project
– creation of low fidelity design prototype
– project objectives and scope definition alongside research
– finalising selection of approapriate components
-All members
-Ben, Cat
-All members
-Ben, Jess
Week 10– ordered all components necessary
– sorting out code structure and logic
– designing overall system architecture and arduino code
– planning layout of physical components for when they arrive
– testing of separate functions for the different components used
-Ben, Jess
-Cat
-All members
-All members
-Ben, Jess
Week 11– design of final outcome and ensuring functionalities
– finalising and organisation of code + comments
– blog creation to compile all previous documentation together
– blog design
– video final product
– final product design/aesthetics
– debugging
-All members
-Ben
-Jess
-Jess
-Ben
-Cat
-Ben, Cat
Timeline and Task management/assignment

Maker Manual

Overview

Happy plant 🙂
Good conditions!
Shows functioning of system (and how light will change depending on conditions i.e. holding DHT sensor to change condition to show the feedback and response to mimic a bad plant condition)

SmartLeaf! Automatic Irrigation, arduino-based system combining technology and commitment to sustainability. A user-friendly graphical interface with an automatic irrigation system designed to revolutionize plant care.

Tools and Supplies

  • Tin foil tray (base of project) – recycled material
  • Scrap paper – recycled material
  • Sellotape
  • Scissors
  • Plastic cups – recycled material
  • Plant in a pot
  • Soil
  • Arduino board
  • Wires
  • Resistors (x4)
  • Piezo buzzer
  • RGB LED
  • Soil moisture sensor
  • DHT (humidity and temperature sensor)
  • Light sensor
  • Water pump
  • Relay module
  • OLED Display
  • Laptop/USB as power supply

Circuit Diagram

Arduino breadboard layout and circuit diagram:

Building System

In crafting the physical aspects of the system we prioritized sustainability through opting to use recyclable materials to pose emphasise on the eco-friendly approach of the project. For the base structure and foundation we used a tin foil tray; a recyclable material providing a foundation for the components and structural integrity. Scrap paper also used for decorative purposes of paper plants/flowers alongside plastic cups to hold the water.

Code

#include "DHT.h"
#define DHTPIN 4
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
const int soilSensorPin = A0; // Analog input pin for the sensor
const int relayPin = 2;  // Digital output pin connected to the relay module
int ledDigitalOne[] = {6, 3, 7};
const boolean RED[] = {LOW, LOW, HIGH};  //sets the red and green colours for rgb led
const boolean GREEN[] = {LOW, HIGH, LOW};
int lightPin = A1;
int speakerPin = 5;
int displaynum = 1;
int sensorSoilValue ; //global values are created for sensor values so they can read throughout
int FaceFactors[] = {1,1,1,1}; //good(1)/bad(0) for {moisture,humidity,temp,light}
float humi;
float tempC;
int lightLevel;
float lightPercentage;
float soilPercentage;

//sets up screen
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for SSD1306 display connected using software SPI (default case):
#define OLED_MOSI   9
#define OLED_CLK   10
#define OLED_DC    11
#define OLED_CS    12
#define OLED_RESET 13
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,
  OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);


void setup() {
  Serial.begin(9600); // Initialize serial communication
  dht.begin(); //starts humidity/temp sensor
  for(int i = 0; i < 3; i++){
    //  set the 3 pins as outputs on the RGB LED
    pinMode(ledDigitalOne[i], OUTPUT);
  }
  pinMode(relayPin, OUTPUT);
  digitalWrite(relayPin, HIGH);
  //screen
  if(!display.begin(SSD1306_SWITCHCAPVCC)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

}


void loop() {
  //lightcode
  ReadLight();
  //soil code
  ReadSoil();
  //humidity and temperature code
  ReadHumidityTemp();
  delay(1000); // Wait for a second before the next reading

  //cycles through the 3 different displays
  if (displaynum == 1){
    DisplayFace();
    displaynum = 2;
  }
  else if(displaynum == 2){
    DisplayText();
    displaynum=3;
  }
  else if(displaynum == 3){
    DisplayGoodBad();
    displaynum = 1;
  }
}


void DisplayFace(){
  //check how many of the 4 factors for plant growth are good(1)/bad(0)
  int counter = 0; //sets a counter as a local variable, which gets incremented by 1 as per each factor is good
  for(int i = 0; i <=3; i++){
    if(FaceFactors[i] == 1){ //loops through all 4 items in the global array named facefactors
      counter+=1;
    }
  }

  display.clearDisplay();
  //face
  display.drawCircle(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 30, WHITE); // circle drawn at midpoint of screen with radius 30
  //eyes
  display.drawCircle(SCREEN_WIDTH / 2 - 10, SCREEN_HEIGHT / 2 - 5, 5, WHITE);
  display.drawCircle(SCREEN_WIDTH / 2 + 10, SCREEN_HEIGHT / 2 - 5, 5, WHITE);

  //smily face drawn when all 4 factors are good
  if(counter == 4){
    display.fillCircle(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2 + 7, 6, WHITE);
    display.fillCircle(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2 + 6, 6, BLACK); // black circle drawn just above the mouth circle to leave a smily face
  }
  //midface drawn when 1 or 2 factors are bad
  else if (counter > 1 && counter <=3){
    display.fillRect(SCREEN_WIDTH / 2 - 15, SCREEN_HEIGHT / 2 + 10, 30, 3, WHITE);
  }
  //sadface drawn when 3 or 4 factors are bad
  else if(counter <=1){
    display.fillCircle(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2 + 9, 6, WHITE);
    display.fillCircle(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2 + 11, 6, BLACK); //black circle drawn just below the mouth circle to leave a frowny face
  }
  display.display();
}


void DisplayText(){
  display.setTextSize(1); //set the size of the text
  display.setTextColor(WHITE); //color setting
  display.setCursor(0,0); //The string will start at 0,0 (x,y)
  display.clearDisplay(); //Erase the previous display on the screen
  display.print("Soil Moisture: ");
  display.print(100 - soilPercentage); //displays the inverse of the soil percentage as reading a high percentage as water increases is more human friendly
  display.println("%");
  display.print("Humidity: ");
  display.print(humi);
  display.println("%");
  display.print("Temperature: ");
  display.print(tempC);
  display.println("C");
  display.print("Light: ");
  display.print(100 - lightPercentage);
  display.println("%");
  display.display(); //send the text to the screen
}

//This display shows if the values on the previous display are good or bad, cycling through the facefactors array to see if they are 1(good) or 0(bad)
void DisplayGoodBad(){
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.clearDisplay();
  display.print("Soil Moisture: ");
  if(FaceFactors[0] == 1){
    display.println("Good");
  }
  else if(FaceFactors[0] == 0){
    display.println("Bad");
  }
  display.print("Humidity: ");
  if(FaceFactors[1] == 1){
    display.println("Good");
  }
  else if(FaceFactors[1] == 0){
    display.println("Bad");
  }
  display.print("Temperature: ");
  if(FaceFactors[2] == 1){
    display.println("Good");
  }
  else if(FaceFactors[2] == 0){
    display.println("Bad");
  }
  display.print("Light: ");
  if(FaceFactors[3] == 1){
    display.println("Good");
  }
  else if(FaceFactors[3] == 0){
    display.println("Bad");
  }
  display.display(); //send the text to the screen

}

void ReadLight(){
  lightLevel = analogRead(lightPin);  
  lightPercentage = map(lightLevel, 0, 1023, 0, 100); //maps the lightlevel to a percentage
  if(lightPercentage > 78){ //If the percentage of light is less than 22%(Value on OLED), then thats bad
    FaceFactors[3] = 0;
  }
  else{
    FaceFactors[3] = 1;
  }
}

void ReadHumidityTemp(){
  // read humidity
  humi  = dht.readHumidity();  //uses dht library
  // read temperature as Celsius
  tempC = dht.readTemperature();
  Serial.println();
  // check if any reads failed
  if (isnan(humi) || isnan(tempC)) { //if not a number returned, alert developer that the dht sensor is not working
    Serial.println("Failed to read from DHT sensor!");
  } else {
  }
  if (humi > 80 || humi < 40) {  //led set to let the user know if the humidity is good or bad
    setColor(ledDigitalOne, RED);
    FaceFactors[1] = 0;
  } else {
    setColor(ledDigitalOne, GREEN); //calls set colour with the green boolean array created
    FaceFactors[1] = 1;
  }
    if (tempC<15 || tempC >25) {
    FaceFactors[2] = 0;
  } else {
    FaceFactors[2] = 1;
  }
}


void setColor(int* led, const boolean* color) {
  for(int i = 0; i < 3; i++){ //cycles though the LED pins in the rgb led and sets based on the values written in the colour array specified
    digitalWrite(led[i], color[i]);
  }
}


//function to acitivate the pump
void activatePump(){
  for(int i = 0; i <2;i++){ //pump lets out 2 squirts of water
    digitalWrite(relayPin, LOW);  // Turn on the relay, which activates the pump
    delay(1000);      
    digitalWrite(relayPin, HIGH);   // Turn off the relay, turning off the pump
    delay(1000);
  }

  sensorSoilValue = analogRead(soilSensorPin);
  soilPercentage = map(sensorSoilValue, 200, 600, 0, 100);
  if (soilPercentage >50) { //after reading the moisture sensor again after watering, it lets you know if its still dry(water can is empty)
    tone(speakerPin, 1600, 2000); //annoying noise is played to annoy the user into filling the can
    display.setTextSize(2); //makes larger text
    display.setCursor(0, 0);
    display.clearDisplay();
    display.print("Watering  Can is    empty!");
    FaceFactors[0] = 0; //sets moisture to bad
    display.display();
  }
  else{ // it was successfully watered, shown by a melody
    tone(speakerPin, 1400, 700); //1400 pitch at a duration of 700
    delay(150);
    tone(speakerPin, 1800, 300);
    delay(150);
    tone(speakerPin, 1600, 700);
    delay(150);
  }
}


void ReadSoil(){
  sensorSoilValue = analogRead(soilSensorPin); // Read soil analog value
  soilPercentage = map(sensorSoilValue, 200, 600, 0, 100);// the readings we found were between 200 and 600 so we mapped this to a percentage
  FaceFactors[0] = 1;
  if (soilPercentage >45) {  //if the soil moisture is less than 55%(print screen) then water the plant
    // Activate the water pump using the relay
    activatePump();
  }
  else{
    delay(2000);
  }

}

Testing and Shortcomings

As seen in the design blog, there was many testing conducted of the different components and their effectivity or redundancy in demonstrating our project

  • Single threshold for humidity: different types of plants have varying optimal humidity conditions therefore system may not be adequate or suitable for all the kinds of plants available.
    • One way this could be overcome in further development is by the creation of a more rigorous algoerithm with customisable thresholds for the possible types of plants used.
  • Power dependency: the system relies on a usb connection to a computer for example, meaning it isnt completely versatile and limits the ability of outdoor location use.
    • Could be overcome by design of waterproof enclosure for the physical electronic components (like the power supply); meaning system can be used outdoors and protected from natural elemental conditions.
  • Scalability: the system is designed for a singular area of plant/soil based on the detectors and sensore therefore it is limited for larger setups.

2023 Group 8 – Ball Maze Game

Group Members:

  • Emir Savas
  • Logan Shalloe
  • Matthew Su

Project Brief

Our chosen brief was to create an ‘escape room puzzle’, with ‘interactive game design.’ We chose to go with a ball maze puzzle game, where you use the joystick to tilt the maze, to try and get the ball from the start to the finish, avoiding the holes in the ground. After completing the puzzle, the maze reveals a code which you use to escape the room.


Project Planning


Design Process

Low-fidelity prototypes: 

In the idea stage, we made a spin puzzle and concluded it would be too simple to solve, and difficult to detect when it is solved using electronic components that we had access to.

This model shows the first maze idea, where a ball will move from point A to point B with the user’s input. There will be potholes to make the game more challenging. We have variation in difficulty by setting two routes by which the user can undergo, one is more difficult and the other is easier to go through, but both lead to the same finish.

We chose to go with a square maze, as the symmetry will help with the balance of the servos, the maze is a 12×12 grid with gaps of at least a half square.

The design of the maze includes multiple routes to the end, the easier and harder ways marked out, the easier ways have larger gaps to allow more space, with the harder ways made tighter with more frequent holes in the ground.

The first concept uses two servos, stuck to each other to allow for movements in both directions. This was going to be placed underneath the centre of the maze. The second concept, which we followed through with is one that uses four servos, two on each level controlling the x or y direction tilt.

Started the making of the final maze.

Extending joystick length for greater control, attaching it to wooden base, hiding wiring with black cloth.


Maker Manual

Materials:

  • 4mm thick plywood
  • 3mm thick plywood
  • 4mm diameter metal ball bearing
  • 8mm diameter wood dowel
  • spare 5V USB cable

Circuit components:

  • 5x Micro servo SG90-HV
  • 1x Joystick
  • 1x PNP transistor

Tools:

  • Hand saw
  • Sandpaper
  • Glue gun
  • Hand drill

Sizes of wooden pieces:

Pieces cut from 4mm plywood:

  • 180x180mm (x2)
  • 180x8mm (x2)
  • 180x41mm (x2)
  • 188x45mm (x2)

Breadboard Layout:

A – Connect each one to a bare wire, which are placed at the ending of the maze close enough that the ball bearing will connect them

B – Joystick component.

  • purple wire to GND
  • blue to +5V
  • green to VRx
  • yellow to VRy

C – Connect to 5V USB cable for additional power. Connect the red and black wires from within the cable to the like-coloured wires plugged into the breadboard.

The Code

#include <Servo.h>

//transistor jumpstart

#define JMPSTART 11

//joystick

#define JOY_X A1

#define JOY_Y A2

//x servos

#define X_SERVO1 1

#define X_SERVO2 2

//y servos

#define Y_SERVO1 3

#define Y_SERVO2 4

//winning

#define WIN_SENSOR 7

#define WIN_SERVO 9

Servo xServo1;

Servo xServo2;

Servo yServo1;

Servo yServo2;

Servo winServo;

void setup() {

  pinMode(WIN_SENSOR, INPUT_PULLUP);

  pinMode(JMPSTART, OUTPUT);

  xServo1.attach(X_SERVO1);

  xServo2.attach(X_SERVO2);

  yServo1.attach(Y_SERVO1);

  yServo2.attach(Y_SERVO2);

  winServo.attach(WIN_SERVO);

  delay(20);

  digitalWrite(JMPSTART, HIGH);

  delay(20);

  digitalWrite(JMPSTART, LOW);

}

void loop() {

  if (digitalRead(WIN_SENSOR) == LOW) {

    winServo.write(130);

  } else {

    winServo.write(0);

  }

  int joyX = analogRead(JOY_X);

  int joyY = analogRead(JOY_Y);

  int xVal = map(joyX, 0, 1023, 0, 50);

  int yVal = map(joyY, 0, 1023, 0, 50);

  xServo1.write(xVal);

  xServo2.write(180 – xVal);

  yServo1.write(yVal);

  yServo2.write(180 – yVal);

}

2023 Group 7 – SecuroBag

Chosen Project Brief

“Being able to check on the security of your home, family members and belongings while away and in various locations around the world is of interest to you not only from the security perspective but also to ensure safety and peace of mind. The camera is the most component device that is used nowadays to ensure you capture any abnormalities or intruders but recently radio signals, voice and also heat have been used to complement or replace the camera. For this project, we encourage you to start thinking of a simple, low-effective home security system with the camera not being the centre of it. You could use microphones, infra-red sensors and also wireless shields to be able to detect movements, presence and also changes in the home environment from a distance through a website or phone app. Remember to be creative but also realistic considering the time available and budget.


Project Overview

Team Members:

  • Run Lin Liu
  • Justin Smith
  • Yesh Chandiramani

Product Description:
In today’s bustling and chaotic world, our attention is constantly pulled in multiple directions, making us more susceptible to theft. The hustle and bustle of crowded places, coupled with attention-grabbing stimuli, has created an environment where unsuspecting individuals become easy targets for thieves. Recognizing this growing concern, we introduce SecuroBag – the innovative backpack equipped with a built-in alarm system designed to not only protect your belongings but also alert you and those nearby of potential criminal activity.

SecuroBag addresses two primary issues: the prevention of pickpocketing (the unauthorized removal of items from an unattended bag) and bag theft (theft of the entire bag). The key to its effectiveness lies in two crucial components: a smart zipper that senses when the bag is opened and a Bluetooth module that notifies the user when the bag is taken out of their immediate vicinity.

Operating SecuroBag is simple yet highly secure. With just a touch of an RFID tag, a user can easily arm and disarm their bag. When armed, any attempt to open the bag without authorization triggers a loud alarm, immediately deterring thieves and preventing further action. Additionally, through Bluetooth technology, SecuroBag sends real-time notifications to the user when the bag has moved away from their immediate surroundings. This feature empowers users to confront and thwart potential thieves, ensuring the safety of their valuables at all times.

Final Product
User Guide

Project Planning

Project Planning Chart

Project Task List


Design Process

Ask (Nov 13 – Nov 15)

In the design process that we embarked on, we started the asking phase by asking each other what problems we have faced in our day-to-day lives. As we shared a list of our problems, there was one mutual problem among us: when we are alone in a cafe or library, we take all our belongings with us to avoid leaving them unattended and end up losing our seats. This problem has bothered all of us for years, and this was an opportunity for us to devise a solution together.

Research (Nov 15 – Nov 20)

After settling on the problem and deciding that university students like us would be our intended audience, we decided to dissect all the pain points and concerns generated by leaving our bags unattended while we were in the bathroom. We came up with the following list:

  • someone stealing your entire bag
  • someone stealing items from your bag (noticeable and unnoticeable)
  • someone opening your bag to slip something inside

We discussed these pain points and concerns among ourselves and friends who have experienced these problems. We realized that we don’t have to make the bag impossible to steal from but need to have some protective measures in place to deter people from tampering with the bag. This insight and listing out the pain points and concerns helped us better understand the problem and our solution.

Imagine (Nov 21 – Nov 22)

With the pain points and concerns in mind, we imagined and discussed some potential solutions. One solution was to create a bag with a GPS tracker (like an Apple air tag) and make the zipper impossible to open by anyone besides the backpack owner. This was the most obvious solution, but it would have been too complex for us to build. The other solution we arrived at was to add an alarm to the bag’s zipper, and it would go off when the zipper was unzipped. This solution fits our needs almost perfectly because it would allow us to know if someone has stolen and/or slipped something from/into the bag, and the alarm would draw the attention of bystanders, causing the thief to be caught in the act. Unfortunately, having an alarm on the bag would not help if the entire bag is stolen, so we came up with the solution of keeping track of when the bag was last in range of the owner and relying on bystanders and security cameras to assist with finding the thief who stole the bag. By stitching the idea of keeping the bag in range and an alarm for the zipper, we settled on an idea for our solution that met the needs of our intended audience.

Storyboards of the Idea
Plan (Nov 22 – Nov 24)

Now that we had a potential solution in mind, we had to plan out how to check if the bag was out of the range of the owner and trigger an alarm when the zipper became unzipped.

Checking if the bag was out of range of the owner
Before we could consider what hardware components could solve our problem, we needed to define what out-of-range means and what assumptions we could make to assist us in prototyping our solution rapidly. Since the problem originated with going to the bathroom in a cafe or library, we decided that our maximum distance in the range had to be more than 20 meters but less than 100 meters. Also, our intended users are university students, so we can assume that they almost always have their phones with them. We hypothesized that a Bluetooth connection between the bag and the owner’s phone would fit our requirements. To validate our hypothesis, one of us wore a pair of Bluetooth earphones connected to a phone that was playing music and went into the bathroom of a local cafe. Despite being 60 meters away, the music continued playing, which meant a Bluetooth connection would suffice for our solution’s use case.

Trigger an alarm when the zipper is unzipped
To implement this idea, we had to break the concept into two components: detecting the zipper’s state and sounding the alarm. To sound the alarm, we decided to use a piezo buzzer because that is a readily available component in our experimenter’s kit. All that was left was to figure out how to detect the zipper’s state. Since the zipper of the backpack we were going to use is metallic, we decided to use that to our advantage and create a circuit that would close when the zipper is unzipped. To make a circuit that closes when the zipper is unzipped, we had to find an area of the zipper track that the zip would pass through when unzipping the bag. Since the zipper track was connected to a nylon material and that circuit must be robust, we decided that sowing conductive thread to the end of the zipper track would be best suited for accomplishing the task.

RFID Authentication
After developing the technical solutions needed to bring our idea to life, we realized that we didn’t have a way to identify if the bag was armed and should sound the alarm when the zipper was unzipped or disarmed and function like a normal bag. After discussing the problem, our initial solution was to use the Bluetooth connection to send arm and disarm commands to the bag’s microcontroller. This solution would have worked fine, but it is not the most convenient or intuitive to our target users, university students, because this process mapped to arming/disarming a house’s security system, and that is not a standard mapping for university students. Therefore, we decided to map the arming/disarming process to the locking/unlocking of one’s car, a process a university student would be much more familiar with. To achieve the mapping of unlocking/locking one’s car, we decided that hovering an authorized RFID token over an RFID reader would accomplish the job.

Prototype Plan
The plan for the prototype was broken down into three distinct components that each team member would be responsible for building:

  • Bluetooth Connectivity (Yesh)
  • RFID Authentication (Justin)
  • Zipper Alarm (Run Lin)
Create (Nov 27 – Dec 6)
Lo-Fi Prototypes (Nov 27 – Dec 1)

Bluetooth Connectivity:
The Lo-Fi prototype for Bluetooth Connectivity was to get the HC05 Bluetooth Module to spawn up an interface named SecuroBag, and read messages sent from the phone to the Arduino. The messages that were sent from the phone were done by using the Android Bluetooth Serial Terminal app. The purpose of this prototype was to identify how to create a Bluetooth connection and communicate between the phone and Arduino. This prototype was a success!

The code running on the Arduino is as follows:

#include <SoftwareSerial.h>

// Set up a Serial interface to between the Arduino and HC05 Bluetooth Module using Pins 2 & 3 for Receiving and Transmitting data
SoftwareSerial myBluetooth (2,3);

void setup() {
  // put your setup code here, to run once:
  // Start the Serial Monitor in the Arduino IDE with a 9600 Baud Rate
  Serial.begin(9600);
  // Start the Serial interface between the Arduino and HC05 Bluetooth module on the 9600 Baud Rate
  myBluetooth.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
  // Check if there is data being sent from the Bluetooth Module to the Arduino (Bluetooth Module got the data from the Phone its connected to)
  if (myBluetooth.available()) {
    // Read the available data as a String
    String data = myUart.readString();
    // Print the data to the Serial Monitor in the Arduino IDE
    Serial.println(data);
  }
}

RFID Authentication
The LoFi prototype for the RFID Authentication component was setting up a basic circuit that read an RFID tag and dumped the information about the RFID tag to the Serial Monitor in the Arduino IDE The Arduino code used for this prototype was provided by the MFRC522 Library’s example code. The purpose of this prototype was to identify how the RFID reader should be set up and the experience of reading the RFID tag.

This prototype was particularly tough to get working because we had issues with the wiring and connectivity between the RFID Reader component and Arduino pins. Thankfully, our persistence prevailed, and Justin was able to successfully prototype the RFID Authentication.

Justin Soldering the RFID Reader

Zipper Alarm
The LoFi prototype for the Zipper Alarm was to create a circuit using conductive thread sown to an old pencil case’s zipper track that lights up an LED when the zipper is being unzipped. The purpose of this prototype was to identify how to use the conductive thread on a zipper track to check the state of the zipper and the experience of zipping/unzipping the pencil case with the conductive thread sewn on the zipper track. This prototype was also successfully built!

Run Lin Soldering
Run Lin demonstrating the pencil case prototype

The code for the prototype:

int ledPin = 9;

void setup() {
  pinMode(ledPin, OUTPUT);
}

void loop() {
  // Light up when the circuit is closed (aka when the zip is being unzipped)
  digitalWrite(ledPin, HIGH);
}
HiFi Prototype Design Decisions (Dec 1 – Dec 6)

Using the experience from our LoFi prototypes, we implemented our respective components into one Arduino and the backpack.

External View of the Prototype:
We wanted the prototype to resemble to look of an ordinary bag. We did not entirely achieve this because the Alarm and State LED dangles on the outside of the bag, which is a bit odd. However, we needed to have the Alarm and State LED dangle on the outside of the bag because we needed the visual and audio feedback to be clear to the user. The current external view of the prototype looks more or less like an ordinary bag, which is good enough for a prototype.

Wiring Inside the Bag:
The wiring is mostly contained within the backpack, and the Arduino is powered by a 5V power bank that is in the backpack (second image). The only pieces that are sticking out are the Alarm and State LED (last image).

RFID Reader:
The RFID reader is taped close to the zipper track on the inside of the backpack, which allows the user to easily wave their RFID token to arm or disarm the bag. Since the RFID reader is not perceived at first glance by the user, we intentionally placed a piece of tape on the outside of the bag to signify this hidden affordance. The feedback from the interaction the user may have with the RFID reader is conveyed through sounds produced by the piezo buzzer (the Alarm). We have programmed the buzzer to have three distinct sounds: arming/disarming error, arming success, and disarming success. The error sound starts from a high pitch and progresses to a lower pitch, which maps to the user’s conceptual model of error sounds from games like Mario. Similarly, the success sounds maintain a high pitch and map to the user’s conceptual model of success sounds from games.

Bluetooth Connectivity:
The connection state of Bluetooth has no visible feedback except if the user checks their phone. To combat this, we rely on the piezo buzzer (the Alarm) to play a sound to relay the feedback on the connection state of the Bluetooth module. This fits the conceptual model that a university student may already have when they connect their phone to a Bluetooth audio device.

State LED:
The state LED helps indicate to the user if the bag is currently armed (on) or disarmed (off). The feedback from the LED is crucial for the user because they can quickly understand if their interaction with the bag was successful or not. Moreover, we deliberately used a red LED because people have a mapping of the color red with danger. We want to leverage the idea of danger to deter people from tampering with the user’s bag.

Conductive Thread:
As seen in the first image on the last row, the conductive thread is sewn onto the end of the zipper track. We picked a darkish green color to blend into the dark color of the bag but still be visible to the user upon careful inspection so that they know what position the zipper needs to be in for the bag to be armed.

HiFi Prototype Logic

Since the HiFi prototype has three individual components that need to be operating together, we decided to implement the logic for the prototype through the lens of a Finite State Machine (FSM). The FSM perspective allowed us to easily integrate all the individual components and constrain the available actions with the prototype. By constraining the available actions, we are able to minimize human error.

Our process of devising the state machine logic was as follows:

  1. Identify all possible states for the prototype
  2. At each state, think about all possible actions that can occur for the state and decide if those actions are necessary
  3. For all necessary actions, draw an error from the start state to the end state and label the action and its effects on the prototype
  4. After all the actions for all the states have been outlined, go through different scenarios of using the prototype and operate the logic using the state machine diagram

Final State Machine Diagram:

An example of minimizing human error by limiting the actions to only the necessary actions is only allowing the bag to be armed when the Bluetooth connection to the owner’s device is established because the Bluetooth connection is core to the thesis of our prototype— having theft detection mechanisms in place will deter people from tampering with one’s bag.

Testing (Dec 7 – Dec 8)

Our testing methodology for the HiFi prototype involved comprehensive run-throughs of the bag’s workflow in accordance with the state machine diagram. These workflows were designed to validate the prototype’s efficacy in safeguarding users’ belongings.

HiFi Prototype Testing
Testing Evaluation

The primary emphasis was placed on the alarm system, with the objective of ensuring it is activated only when necessary. The goal was to prevent any unwarranted alarms that could potentially induce panic without cause. Through repeated simulations, we meticulously verified that the alarm functioned precisely as intended, activating only in response to genuine security threats. This meticulous approach aimed to eliminate any possibility of false alarms, thereby providing users with a reliable and stress-free experience. The video above demonstrated the capabilities of the HiFi Prototype and everything we set out to achieve with respect to functionality was successful!

Known Shortcomings & Improvements (Dec 9 – Dec 10)

The primary drawback we identified pertains to the out-of-range notification, where Bluetooth disconnection was found to be less reliable in preventing unwarranted alarms. After careful consideration, we opted not to trigger the alarm solely based on Bluetooth disconnectivity, recognizing its limitations in ensuring accurate alerts.

To address this concern and enhance the reliability of the out-of-range notification, several viable solutions were explored. One option involves upgrading to a more advanced Bluetooth chip to improve signal stability. Additionally, integrating a GPS module directly into the bag emerged as a potential solution, allowing for precise distance detection from the user. Another consideration involved implementing an accelerometer to discern the movement of the bag, providing an additional layer of context to the notification system.

Ultimately, a combination of these solutions could be explored to create a robust and comprehensive approach to the out-of-range notification feature. By adopting such enhancements, we aim to deliver a more dependable and effective SecuroBag, ensuring users receive accurate notifications without compromising on the reliability of the alarm system.


Maker Manual

Tools & Supplies

In the creation of our backpack, the tools and supplies used were:

  • Arduino & Breadboard
  • Resistors
  • Wires
  • Piezo Buzzer
  • Red LED 
  • RC522 RFID Reader & Tag
  • HC05 Bluetooth Transmitter
  • Conductive Thread
  • Soldering Iron
  • Wire Strippers
  • Backpack
  • Android smartphone & Serial Bluetooth Terminal App

Circuit Layout

A circuit diagram of the Arduino board setup

Build Section

The backpack consists of 7 main physical components. The zipper sensor, the Alarm, the State LED, the RFID reader, a Bluetooth module, an Arduino microcontroller for logic, and, of course, the backpack itself. Execution of each component is as follows:

Smart Zipper

The smart zipper aims to detect if a bag has been opened. We do this by creating two contact points using conductive thread to detect if a zipper is in a certain position. The contact points act as a switch for the Arduino, closing a circuit with the zipper when the bag is closed.

The Alarm and State LED

The Alarm:
The alarm is composed of a piezo speaker that sits outside of the bag. When ringing, the piezo speaker sounds intermittently, alerting people nearby to the theft taking place. The piezo also serves to provide audio feedback when connecting the Bluetooth module of the bag and when arming the bag.

The State LED:
The State LED is connected to the Arduino and runs to the outside of the backpack using copper wires. We currently leave a small gap in the zipper for the LED and speaker. A more elegant solution would be to create a small hole in the bag for the LED and speaker to be put through and sewn into place, but as we are using one of our own bags for prototyping, we will use the zipper gap workaround to avoid permanent damage to the bag.

RFID Authorization

For authorization, the RFID reader is placed near the top of the bag for ease of access. This allows the user to easily tap to arm and disarm the bag. For added security, the RFID reader is housed inside the bag to prevent any potential thieves from tampering with the reader.

Bluetooth Connectivity

The Bluetooth module allows the user to connect their smartphone to the bag and monitor its whereabouts. This module is connected directly to the Arduino’s breadboard. Similar to the RFID reader, the module is placed within the bag.

Putting it All Together

Connecting the digital components together is the Arduino and breadboard, secured to the bottom of the backpack.

The backpack houses everything needed to secure itself.

Code

Sound.ino
const int piezoPin = 5;
const int tempo = 200;

void Sound_Setup() {
  pinMode(piezoPin, OUTPUT);
  digitalWrite(piezoPin, HIGH);
}

// Sound that is played with there is an error with arming/disarming
void Sound_Error(){
  char notes[]="gbba";
  for (int i = 0; i < 6; i++) {
    _playNote(notes[i], 800);
    delay(tempo);
  }
}

// Sound that is played when the bag is successfully disarmed
void Sound_DisarmSucc(){
  char notes[]="fga";
  for (int i = 0; i < 6; i++) {
    _playNote(notes[i], 800);
    delay(tempo);
  }
}

// Sound that is played when the bag is successfully armed
void Sound_ArmingSucc(){
  char notes[]="edc";
  for (int i = 0; i < 6; i++) {
    _playNote(notes[i], 800);
    delay(tempo);
  }
}

// Sound that is played to signify that Bluetooth is connected
void Sound_BTConnected(){
  char notes[]="ecd";
  for (int i = 0; i < 6; i++) {
    _playNote(notes[i], 800);
    delay(tempo);
  }
}

// Sound that is played to signify that Bluetooth is disconnected
void Sound_BTDisconnected(){
  char notes[]="dce";
  for (int i = 0; i < 6; i++) {
    _playNote(notes[i], 800);
    delay(tempo);
  }
}

// The alarm sound to signal that the bag's security has been breached
void Sound_Alarm(){
  char notes[]="aa";
  for (int i = 0; i < 6; i++) {
    _playNote(notes[i], 800);
    delay(tempo);
  }
  delay(tempo);
  for (int i = 0; i < 6; i++) {
    _playNote(notes[i], 800);
    delay(tempo);
  }
} 

// Private helper function to play a note
void _playNote(char note, int duration) {
  char names[] = { 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C',};
  int tones[] = { 1915, 1700, 1519, 1432, 1275, 1136, 1014, 956};
  // play the tone corresponding to the note name
  for (int i = 0; i < 8; i++) {
    if (names[i] == note) {
      tone(piezoPin, tones[i], duration);
    }
  }
}
Zipper.ino
const int zipperPin = 4;
const int ledPin = 8;

void Zipper_Setup() {
  pinMode(zipperPin, INPUT);
  pinMode(ledPin, OUTPUT);
  Zipper_SetLEDTo(LOW);
}

bool Zipper_IsClosed() {
  // Zipper circuit is open, which means zipper is closed
  return digitalRead(zipperPin) == LOW;
}

void Zipper_SetLEDTo(int highOrLow) {
  // Write the given value to the LED pin
  digitalWrite(ledPin, highOrLow);
}
RFID.ino
#include <SPI.h>
#include <MFRC522.h>

#define SS_PIN 10
#define RST_PIN 9
MFRC522 mfrc522(SS_PIN, RST_PIN);  // Create MFRC522 instance.

void RFID_Setup() {
  SPI.begin();         // Initiate  SPI bus
  mfrc522.PCD_Init();  // Initiate MFRC522
}

RFIDResult RFID_Check() {
  // Look for new cards
  if (!mfrc522.PICC_IsNewCardPresent()) {
    return NOSCAN;
  }
  // Select one of the cards
  if (!mfrc522.PICC_ReadCardSerial()) {
    return NOSCAN;
  }

  // Get RFID content
  String content = "";
  for (byte i = 0; i < mfrc522.uid.size; i++) {
    content.concat(String(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " "));
    content.concat(String(mfrc522.uid.uidByte[i], HEX));
  }
  content.toUpperCase();

  //change here the UID of the card/cards that you want to give access
  if (content.substring(1) == "A3 E7 00 0D" || content.substring(1) == "04 8E 55 1A 4B 3F 80") 
  {
    // Serial.println("Access granted.");
    // Serial.println();
    return AUTH;
  } else {
    return UNAUTH;
  }
}
BT.ino
#include <SoftwareSerial.h>

#define BT_RX 2
#define BT_TX 3

const int BT_BAUD_RATE = 9600;
const int BT_STATE_PIN = 6;

// Create Bluetooth Serial Interface using port 2 for receiving data and port 3 for transmitting data
SoftwareSerial btSerial(BT_RX, BT_TX);

// Bluetooth connection state
int connState = 0;

// Begin Bluetooth Serial Interface with a 9600 Baud Rate
void BT_Setup() {
  // Start interface at 9600 baud rate
  btSerial.begin(BT_BAUD_RATE);
  Serial.println("Bluetooth is running");
  // Print bluetooth connection state
  pinMode(BT_STATE_PIN, INPUT);
}

// Send message to device through bluetooth
void BT_SendMsg(String msg) {
  btSerial.println(msg);
}

// Read message from device through bluetooth
String BT_ReadMsg() {
  if (btSerial.available()) {
    String msg = btSerial.readString();
    msg.trim();
    return msg;
  }
  return "";
}

// Check if bluetooth module is connected
bool BT_IsConnected() {
  int currState = digitalRead(BT_STATE_PIN);
  if (currState != connState) {
    connState = currState;
    _printConnectState();
  }
  return connState;
}

// Private helper function to print BT connection state
void _printConnectState() {
  if (connState == 1) {
    Serial.println("Bluetooth Connected");
  } else {
    Serial.println("Bluetooth Disconnected");
  }
}
Main.ino (uses the functions from all the other sketches)
// SecuroBag State Machine States (refer to diagram)
enum State {
  DisarmedBTDisconnected,
  DisarmedBTConnected,
  ArmedBTConnected,
  ArmedBTDisconnected,
  Alarm,
};

// Status of RFID module
enum RFIDResult {
  AUTH,
  UNAUTH,
  NOSCAN
};

// Keep track of State Machine's current state
State currState = DisarmedBTDisconnected;
// Track how many unauthorized RFID scans occurred during armed state
int errCount = 0;

// State Machine initilization process
void initStateMachine() {
  currState = DisarmedBTDisconnected;
  errCount = 0;
}

void setup() {
  Serial.begin(9600);
  // Setup bluetooth module
  BT_Setup();
  // Setup RFID module
  RFID_Setup();
  // Setup Zipper circuit
  Zipper_Setup();
  // Setup Sound modules
  Sound_Setup();
  // Initialize state machine
  initStateMachine();
  // Ready for action!
  Serial.println("SecuroBag is ready");
  // DEBUG: Print current state
  printState();
}

void loop() {
  // DEBUG: log state
  State prevState = currState;

  // Get status of RFID module
  RFIDResult scanResult = RFID_Check();

  // Execute actions based on the current state of the State Machine
  switch (currState) {
    case DisarmedBTDisconnected:
      // Reset error count
      errCount = 0;

      // Check if any RFID is being scanned
      if (scanResult != NOSCAN) {
        Serial.println("Play Arming Error Sound");
        Sound_Error();
      }
      // Check if Bluetooth is connected
      else if (BT_IsConnected()) {
        currState = DisarmedBTConnected;
        Serial.println("Play Bluetooth Connected Sound");
        Sound_BTConnected();
        // Send connected message to device via BT
        BT_SendMsg("Connected to SecuroBag");
      }
      break;
    case DisarmedBTConnected:
      // Reset error count
      errCount = 0;
      // Recheck if RFID is scanned
      scanResult = RFID_Check();
      // Check if bag is trying to be armed
      if (scanResult == UNAUTH || (scanResult == AUTH && !Zipper_IsClosed())) {
        if (scanResult == UNAUTH) {
          Serial.println("unauth");
        } else {
          Serial.println("zipper not closed");
        }
        Serial.println("Play Arming Error Sound");
        Sound_Error();
      }
      // Bag is successfully armed
      else if (Zipper_IsClosed() && (scanResult == AUTH || BT_ReadMsg() == "arm")) {
        currState = ArmedBTConnected;
        Serial.println("Play Armed Sound && Turn On LED");
        Sound_ArmingSucc();
        Zipper_SetLEDTo(HIGH);
        // Send msg to device through BT about state change
        BT_SendMsg(stateToString());
      }
      // Check if Bluetooth disconnected
      else if (!BT_IsConnected()) {
        currState = DisarmedBTDisconnected;
        Serial.println("Play Bluetooth Disconnected Sound");
        Sound_BTDisconnected();
      }
      break;
    case ArmedBTConnected:
      // Check if bag is trying to be disarmed
      if (scanResult == UNAUTH) {
        Serial.println("Play Disarming Error Sound");
        Sound_Error();
        errCount++;
      }
      // Bag is successfully disarmed
      else if (scanResult == AUTH || BT_ReadMsg() == "disarm") {
        currState = DisarmedBTConnected;
        Serial.println("Play Disarmed Sound");
        Sound_DisarmSucc();
        Zipper_SetLEDTo(LOW);
        // Send msg to device through BT about state change
        BT_SendMsg(stateToString());
      }
      // Check if Bluetooth disconnected
      else if (!BT_IsConnected()) {
        currState = ArmedBTDisconnected;
        Serial.println("Play Bluetooth Disconnected Sound");
        Sound_BTDisconnected();
      }

      // Always check if zipper is not closed or error count == 3
      if (!Zipper_IsClosed() || errCount == 3) {
        currState = Alarm;
        // Send msg to device through BT about state change
        BT_SendMsg(stateToString());
      }
      break;
    case ArmedBTDisconnected:
      // Check if bag is trying to be disarmed
      if (scanResult == UNAUTH) {
        Serial.println("Play Disarming Error Sound");
        Sound_Error();
        errCount++;
      }
      // Bag is successfully disarmed
      else if (scanResult == AUTH) {
        Serial.println("Play Disarmed Sound");
        Sound_DisarmSucc();
        Zipper_SetLEDTo(LOW);
      }

      // Always check if zipper is not closed or error count == 3
      if (!Zipper_IsClosed() || errCount == 3) {
        currState = Alarm;
      }
      break;
    case Alarm:
      // Make sure sound has on and off intervals to do other checks
      Serial.println("Play Alarm Sound");
      Sound_Alarm();

      // Recheck if RFID is scanned
      scanResult = RFID_Check();

      // Bag has been successfully disarmed
      if (scanResult == AUTH || BT_ReadMsg() == "disarm") {
        Zipper_SetLEDTo(LOW);
        if (BT_IsConnected()) {
          currState = DisarmedBTConnected;
          // Send msg to device through BT about state change
          BT_SendMsg(stateToString());
        } else {
          currState = DisarmedBTDisconnected;
        }
        Sound_DisarmSucc();
        delay(500);
      }
      break;
  }

  // DEBUG: print state changes
  if (prevState != currState) {
    Serial.print("State changed to: ");
    printState();
  }
}

// Helper function to create message to be sent through BT about SecuroBag state
String stateToString() {
  switch (currState) {
    case DisarmedBTDisconnected:
    case DisarmedBTConnected:
      return "Status: Disarmed";
    case ArmedBTConnected:
    case ArmedBTDisconnected:
      return "Status: Armed";
    case Alarm:
      return "Status: Bag has been breached!";
  }
}

// DEBUG: Print SecuroBag State Machine states for debugging purposes
void printState() {
  switch (currState) {
    case DisarmedBTDisconnected:
      Serial.println("Disarmed, Bluetooth Disconnected");
      break;
    case DisarmedBTConnected:
      Serial.println("Disarmed, Bluetooth Connected");
      break;
    case ArmedBTConnected:
      Serial.println("Armed, Bluetooth Connected");
      break;
    case ArmedBTDisconnected:
      Serial.println("Armed, Bluetooth Disconnected");
      break;
    case Alarm:
      Serial.println("ALARM");
      break;
  }
}

2023 Group 6: Apres-ski Party Hat

design blog

Project goals

Project Brief

Dance your cares away…

‘Create an e-textile with interactive LEDs that are set to flash to the beats of the music.’

Purpose

Skiers love to party and what is an après ski party without a light-up hat? Our warm, cosy hat is perfect for partying in the cold, snowy mountains and it lights up to the beat of the music! Wearing this hat will make you the envy of the slopes.

Requirements And Objectives

  • Create an e-textile (t-shirt, bag, skirt, etc) with interactive LEDs that can be set to either flash to the music’s beats or the wearer’s movement
  • Fit in a skiing environment
  • Interact with different types of music and beats
  • Program reactions using sensors and actuators (LEDs) with Arduino
  • Ensure physical components (such as Arduino Uno) do not make the user uncomfortable while wearing the hat
  • Consider sound for lighting schemes

Target audience

Skiers who love to party! The target demographic is skiers of all ages. The hat fits all sizes so any user can put it on and become the life of the party at any event.

Market Research

Product 1: Music Sync LED Lights Strip, RGB
Colour Changing LED Lights

Product 2: RGB Light Bars, Rechargeable Colourful Ambient LED Light Rhythm Light Bar, 32 Bit Sound Activated Lights, Voice Activated Pickup Music Rhythm Lights.

design decisions and processes

Brainstorming and Lofi prototypes

Brainstorming LED placement

Paper models

Design decisions

Colour – the colour changes based on volume of sound

Patterns – the patterns change based on the volume (colour)

  • Quiet: Blue and turquoise – LEDs change to this colour (they just turn on)
  • Medium: Orange and yellow – LEDs have a flashing dissolving pattern
  • Loud: Red – LEDs have a dissolving loop pattern

Brightness – the brightness changes based on the volume (colour)

  • Blue: 10
  • Green/blue: 20
  • Turquoise: 30
  • Green: 40
  • Yellow: 60
  • Orange: 80
  • Red: 100
Hifi Prototypes and Testing

First, we tested the LED strip and the sound sensor separately:

Testing the LED strip from the recycling box in the Electronics Lab. Initially, our LED strip wouldn’t work, it was resetting every 10 seconds. Then, we added the capacitor and resistor and it worked

Testing the sound sensor by using the serial monitor (code)

Testing the sound sensor by using the serial monitor (serial monitor output)

The circuit was shorting due to the previous soldering so we decided to resolder the wires

Re-soldering the wires

Re-soldered wires

Testing the patterns on the LED strip after we re-soldered them – they worked!

LED strip with basic loop animations
The LED pattern before adding the dissolve and loop animations
The LED pattern after adding the dissolve and loop animations

final product

Making
Re-soldering wires
Finished re-soldered wires
Sewing the LED strip
Final product

Project planning

GANTT Chart

makers manual

Components
  • Recycled WS2812B RGB LED strip (the main component of the project) x1
  • Arduino IDE x1
  • Breadboard x1
  • DAOKAI 5PCS High Sensitivity Microphone Sensor x1
  • Capacitor x1
  • Resistor x1
  • Wires x9
  • Fur hat x1
  • Battery pack x1
  • Power wire to attach Arduino IDE to battery pack x1
Tools
  • Soldering machine
  • Sewing kit
Breadboard Layout
How To Build
Building the circuit
  • Follow the circuit diagram above, attaching each component to their appropriate position
  • Ensure the sound sensor and the LEDs work before continuing to the next step. Also check that the wires on the LED are soldered correctly and are not touching to make sure there is no short circuiting
Attaching the circuit to the hat
  • Cut a hole at the top of the hat for the Arduino and the breadboard. The Arduino and the breadboard should fit into the seam of the hat so it cannot be seen on the outside or be touching the users head. Sew this in place
  • Attach the battery to Arduino Uno
  • Sew the LED strip around the front of the hat
  • Cut a hole on the side of the hat for the sound sensor to be attached. Sew the sound sensor in place so the sensor is on the outside of the hat
  • Attach the battery pack to the back of the hat

project evaluation

Shortcomings
  • The sensor in itself is quite basic, it measures the amplitude but not the frequency. 
  • After rigorous testing, we discovered that the range of values that it can output is around 5 variations. For example, ranging from 40 to 45, where 40 would be quiet and 45 would be loud. This did not give us a lot of space to be able to respond to the many variations of tones/frequencies/loudness of music. Before we had wanted to output darker, bluer colours for low frequencies and lighter, redder colours for higher frequencies, now we could only do so with amplitude and not frequency.
  • Improve aesthetics by improving the precision of sewing and incisions on the hat.
  • The sound sensor is exposed meaning the hat is not waterproof and also anything that touches the sensor will interfere with it.
Future Considerations
  • Incorporate gesture control with ultrasonic sound sensors that could provide an extra interactive layer.
  • Enabling users to customise patterns or animation with varying amplitude levels.
Conclusion

While the current sound sensor setup has its limitations in measuring only amplitude and providing a narrow range of values, the use of smaller Arduino and ultrasonic sound sensors could enhance the product by adding gesture control and customisable sound profiles and there is potential to overcome current constraints and responsive aspects of the project.

code

#include <FastLED.h>

#define SENSOR_PIN A0
#define LED_PIN 7
#define NUM_LEDS 47

// millis setup
unsigned long startTime;
unsigned long interval = 100;
unsigned long soundDetectionInterval = 1000;

// colours 
const CRGB red = CRGB(255, 0, 0);
const CRGB orange = CRGB(255, 128, 0);
const CRGB greenBlue = CRGB(0, 102,102);
const CRGB yellow = CRGB(255, 255, 0);
const CRGB green = CRGB(0, 153, 0);
const CRGB blue = CRGB(0, 0, 255);
const CRGB purple = CRGB(51, 0, 102);
const CRGB turqouise = CRGB(0, 102, 102);
const CRGB white = CRGB(255, 255, 255);

// default colour
CRGB colour = CRGB(0,0,0);
// initialise leds array
CRGB leds[NUM_LEDS];
// choose brightness
int brightness = 50;

void setup()
{
  pinMode(SENSOR_PIN, INPUT);
  FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);
  Serial.begin(9600);
  // turn off/reset LEDs
  clearLeds();
  fill_solid(leds, NUM_LEDS, red);
  FastLED.show(); // lights up all LEDs at once
}


void loop()
{ 
  delay(2);
  lightUp();
}

void lightUp() {
  CRGB colourDetermined = determineColour();
  FastLED.setBrightness(brightness);
  fill_solid(leds, NUM_LEDS, colourDetermined);
  FastLED.show(); // lights up all LEDs at once
}

CRGB determineColour(){
  unsigned long currentTime = millis();
  // read volume
  String volume = readSound();
  // choose which colour and pattern to display based on the volume
  if (currentTime - startTime >= interval) {
    if (volume == "ppp") {
      colour = blue;
      brightness = 10;
    }
    else if (volume == "pp") {
      colour = greenBlue;
      brightness = 20;
    }
    else if (volume == "p") {
      colour = turqouise;
      brightness = 30;
    }
    else if (volume == "mp") {
      colour = green;
      brightness = 40;
    }
    else if (volume == "mf") {
      colour = yellow;
      brightness = 60;
      dissolve(10, 10, 10);
    }
    else if (volume == "f") {
      colour = orange;
      brightness = 80;
      dissolve(10, 10, 10);
    }
    else if (volume == "ff") {
      colour = red;
      brightness = 100;
      displayLoopPattern(colour, 7);
      dissolve(10, 10, 7);
    }
    startTime = currentTime;
  }
  return colour;
}

void displayLoopPattern(CRGB patternColor, int patternSize) {
  FastLED.setBrightness(brightness);
  for (int i = 0; i < NUM_LEDS; i += patternSize) {
    for (int j = 0; j < patternSize && i + j < NUM_LEDS; j++) {
      leds[i + j] = patternColor;
    }
    FastLED.show();
    FastLED.delay(20);
    clearLeds();
  }
}

// turn off/reset LEDs
void clearLeds() {
  for (int i = 0; i < NUM_LEDS; i++) {
    leds[i] = CRGB::Black;
  }
}

// read the volue of the sensor
String readSound()
{
  int inputVolume = analogRead(SENSOR_PIN);
  String dynamicMarking = "";

  if (inputVolume < 42) {
    Serial.println("|");
    dynamicMarking =  "ppp";
  }
  else if (inputVolume == 42) {
    Serial.println("||");
    dynamicMarking =  "pp";
  }
  else if (inputVolume == 43) {
    Serial.println("|||");
    dynamicMarking =  "mf";
  }
  else if (inputVolume == 44 ) {
    Serial.println("|||||");
    dynamicMarking =  "f";
  }
  else if (inputVolume > 44) {
    Serial.println("||||||||||||||||||||||||||||||");
    dynamicMarking = "ff";
  }
  return dynamicMarking;
}

// Random dissolve colors
void dissolve(int simultaneous, int cycles, int speed){
  //delay(10);
  for(int i=0; i<cycles; i++){
    for(int j=0; j<simultaneous; j++){
      int idx = random(NUM_LEDS);
      leds[idx] = CRGB::Black;
    }
    FastLED.show();
    delay(speed);
  }
  allColor(CRGB::Black);
}

// Changes all LEDS to given color
void allColor(CRGB c){
  for(int i=0; i<NUM_LEDS; i++){
    leds[i] = c;
  }
  FastLED.show();
}

2023 Group 5 – Fairy Wings

Design blog

A holistic view was adopted to select the materials we used to ensure user friendliness, efficient functioning of the circuit and keeping in mind the stage setup, lighting and the impact of the wings on the performance.

  • Iridescent paper – it reflects well on the stage where the spotlight will only focus on the actors and its pitch dark all around. It stands out well. This also means it reflects the led lights better
    It is also light weight.
  • Craft wire – they are light weight and allow to mimic the realistic veins of the wings. Also they are light weight so, the actor doesn’t need to carry much weight and they still uphold the structure of the wings.
  • Buttons – In order to disguise the buttons, we have put them as a pendant. This way, the buttons are easily accessible to the user and also seems like a part of the accessories a fairy would wear.
  • 6-inch servo elongated arms – since the wings are quite large and wide, the servo arms of the SG90 servo cannot cover much surface area of the wings to make them flutter effectively. 6-inch servo elongated arms.
  • Small breadboard – To allow the user to control the wings and the LED strip, a connection had to be established between the back where the Arduino board is and around their neck. Instead of soldering the buttons to the wires directly, using a smaller breadboard allows the buttons to be mounted safely and securely and also ensures that the user won’t be harmed if the wires were soldered to the buttons directly rather than the other way around.
  • Potentiometer – Potentiometer allows to map a range of values between 0 and 1023 based on the analog input read. This helps to transition between a large range of colors smoothly. The twisting feature of the potentiometer is also quite intuitive for the user to use
  • Buttons – Instead of smaller buttons, we got larger buttons that could also pass as gems on the necklace and are easier to interact with instead of the small ones, making it more user friendly.
  • 9V Battery – required to power the circuit and specifically the LED strip.

How does the user interact?

The Arduino board is mounted behind the user and is connected to the wings and LED strips. As a result, another breadboard that is accessible to them, simple to operate, and doesn’t get in the way of their performance has to be given to them. We thought of three ways for the user to interact with the system:

  • Idea 1 – the user interacts via a bracelet around their wrist.
  • Idea 2 – the user interacts via the necklace around their neck.
  • Idea 3 – the user interacts via a staff or a wand in their hand.

All of these ideas can be easily disguised as accessories a fairy would wear hence, they fit well with the costume. Considering the frequent hand movements during the performance, we decided to go with the necklace around the user’s neck. This way, it’s easily reachable, won’t interfere with their movements and ensures that the connection between the Arduino board at the back and the necklace remains secure. Hence, to ensure the functionality isn’t compromised while the performance, the necklace idea was chosen in the end.

Design principles

Visibility – he user can quickly identify which controls to use owing to the necklace they wear during performances. Similar sequins have been stuck to the button and potentiometer used to control the lights, making it evident how functionally related they are.

Affordance – Using buttons instead of any other input device is to use familiarity of the user with the object to make it easy for the user to understand how it works. People are aware how buttons are clickable.

Feedback -Every user interaction generates feedback. By pressing the blue button, the RGB led strip can be turned on or off to provide visual feedback. The sound of the servo arms moving provides haptic and auditory input when the wings begin to flutter upon pushing the red button. Additionally, when the potentiometer is twisted, the RGB led strip’s colors shift visually.


Sketches and prototypes

Prototype 1

Idea behind the functionality:

  • The wires are connected to the breadboard
  • Wires extend all the over the shoulder and mimic as a necklace as a user interface – the buttons acts as gems on it
  • Press the button to flutter the wings
  • Press the button to turn on and off the LED strip
  • The potentiometer is also allows controlling the different colors of the LED lights.
LED lights testing

We decided to test each functionality and circuit individually first and then put them all together for the prototype. This was to make sure that all the functionalities are coded and that they are not dependent on other functionality.

We first started working with the LED strip. Testing out the code and working out whether we can control it with button. Installed an additional library, FastLED for coding the LED strip and then completed the circuit by connecting the LED to a 330 ohm blue resistor and a capacitor.

Servo testing

Making the first quick prototype to test out the capacity of the servo to move the small size wings. This is also to test out how the servo can be connected to the wings.

Prototype 2

Changing the size of the wings to full size to test out the fluttering with the servos. Sizing up the wings needed 2 servos instead of 1. A single servo cannot handle the weight of the big wings and establishes 2 points of contact with the wing.

Final Prototype

Craft wire wings

While we are working on the functionality of the wings and the LED lights, we start working on the final design of the wings made of craft wire.

The wire was quite flimsy alone, we chose a weaker wire so that it would be easier to work with, also for the lightweight property, however this also meant that it wasn’t so strong or immune to damage. Adding all the details inside provided some structure and made the initial wing frame much stronger. To connect each piece of wire to another, I coiled one around the other – this also provided more structure and made it much more sturdy.

Cellophane

The cellophane seemed quite thick initially, we wanted the wire to show through in the middle section as this was a key aesthetic feature in the design – the wings should mimic those of a butterfly. We realised the cellophane could be split into two pieces which made it thinner. We did this for both wings so the wire details came through as intended.

After we taped the entire wire wing and stuck it to the cellophane, we pinched the paper around the cellophane to secure it. In come places, this made it rip in some places so we had to remove the paper and stick it down less taught. This was difficult as we had to get the right ratio of having the cellophane paper loose enough so that it wouldn’t rip but also not too loose otherwise wrinkles would appear in the wing. The wrinkles were not pleasing at all, aesthetically, so we used trial and error before we were happy with how the cellophane looked and stuck it properly.

After this the paper was cut to size with about 5-8 cm extra around the edges. We then used a lighter to seal the edges, since there was so much extra cellophane it wasn’t burning correctly and morphed in a strange way. We then cut again leaving only 1-2cm. Since it was now too short the cellophane was sticking to the wire with a gap in between and the two pieces weren’t sticking to each other. After pinching the cellophane together first and then applying heat, the cellophane sheets moulded into each other and then the wire as initially intended.

Servo testing with the final wings

Trying out the MG996R servo for the final wings. The bigger servo was stronger and could easily move the wings to make them flutter.

Another iteration from the previous carboard wings was to attach long arms to the servo arms to cover more surface area on the wings and help them flutter better.

Soldering

We needed the LED lights on both the wings, so had to cut the the LED strip into 2 pieces and solder 3 pin female header on the ends of it.

Potentiometer Testing

testing out the code for the potentiometer independent of the buttons to test out its independent functionality.

Back support

U – shaped back support created with the metal craft wire. The U-shaped wire is slotted inside the waistband user wears to wear the wings. The servos are attached to this support.

This was an unsuccessful iteration because the wire that is attached to the wings restricted the movement of the wings when fluttering. Also because

Final support

Project Task List

Making the first quick prototype to test out the capacity of the servo to move the small size wings. This is also to test out how the servo can be connected to the wings. Sizing up on the wings

As an overview, the workload was divided based on everyone’s strengths.

  • Amrina – responsible for the making of the final metal wings and back support
  • Halak & Jess – responsible for prototyping, circuit and testing out the functionality with code

Maker Manual

Contents

  • Overview
  • Tools
  • Supplies
  • Circuit Diagram
  • Building instructions
  • Code
  • Testing
  • Possible improvements

Overview

A stunning set of wearable wings suitable for any fairy to step into their own spotlight!

​By using the costume-coherent attached amulet, the lights can be discretely toggled and colour-changed in real time by the wearer without breaking immersion. The same control system can also be used to toggle wing flapping to further enhance the performance. 

The straps are tied around the torso so that the wings can be worn, and the amulet can be worn around the neck. The Arduino board is mounted to the back to minimize its visibility to the audience. The amulet and its strings can be decorated to match the costume. The cellophane foil covering the wire is lightweight and light-reflective to allow for easier servo operation whilst standing out in a theatrical environment. 

​ 

Tools

  • Pliers 
  • Lighter
  • Wire strippers 
  • Pen/pencil 
  • Scissors 
  • Tape: consider masking tape, double sided and clear cellotape 
  • Screwdriver 
  • Soldering supplies 

Supplies

  • Craft wire 
  • Iridescent cellophane 
  • 330 ohm blue resistor
  • 25v capacitor
  • SG90 servos × 4 
  • 6-inch servo elongated arms (3D printed by Dave) 
  • RGB strip WS2812B – 30 or 60 LEDs per metre is fine – 1.5-2m of strip is enough 
  • Female 3-pin header x 2 (for soldering) 
  • Female single pin headers 
  • Roll wires for the “amulet” 
  • Plenty of pin (male-male) wires 
  • Large buttons × 2 
  • Potentiometer × 1 
  • Arduino board × 1 (including breadboard) 
  • Mini breadboard × 1 
  • 9V battery and snap connector 
  • Cardboard  (made out of old recycled boxes)
  • old birthday decorations (repurposed)
  • Optional embellishments on wires/buttons (such as sticky sequins and ribbons) 

Circuit diagram and Layout

​ 

​ 

​ add the circuit pic above 🙂

Building Instructions

  1. Draw out one wing on the cardboard and cut it out. Use this as a stencil for the wire. 
  2. Cut around 1m of wire and make the outline of the top section of the wing using the cardboard as a guide. 
  3. Using around 0.5m of wire create the bottom section of the wing. 
  4. Attach the top and bottom by spiralling a piece of wire around the section where they touch. The spiral also makes the wire sturdier. 
  5. Using the cardboard sketch as a guide, create the inside details of the wing. Again, attach each piece by creating a spiral of wire. 
  6. Use double sided tape to cover all the wire – small gaps are fine, but the wire should be at least 80% covered 
  7. Measuring the length of one wing, top to bottom, cut two pieces of cellophane 
  8. Place the taped wing carefully onto one piece of cellophane. Place the other piece of cellophane on top, ensure there are minimal wrinkles but that it is not too taught as you need room to pinch it around the wire. 
  9. Softly, pinch the cellophane around the outside of the wing before pinching around each of the wires. Do not pinch too hard as the cellophane will rip. 
  10. Turn the wing over and repeat this on the other side to make sure the cellophane is stuck to the wire outline on both sides. 
  11. Cut around the cellophane leaving around 1-2cm past the wire 
  12. To seal the cellophane around the wire, pinch it together then carefully use a lighter to heat it up, so it burns and moulds together around the wire. Make sure the cellophane is just above the flame and doesn’t actually touch it i.e. you’re applying heat not setting it on fire, a flame is used since it helps focus the heat over a small area. (Take extra care of surroundings and make sure your hand isn’t close to any open flame. 
    • Alternatively, if the cellophane is cut to size, cello tape can be used to seal it over the wire however, this can look messy and using a lighter gives a cleaner finish. 
  13. Repeat steps 2-12 to make the second wing. 
  14. Attaching the longer servo arms to the servos, position two servos along the inside of each wing, then stick the arms to the wings. The servo arms should be parallel. 
  15. Using wire, connect the two servos on both sides to reinforce them – do this on both wings. 
  16. Then connect the wings by using wire to connect the wire supports that were made in step 15. The connections should run horizontally, one at the top, and one at the bottom to create a square structure. 
  17. To strengthen the support, add connections from the top horizontal wire support down to the bottom in three, equally spaced points. The support should be strong enough to support the servos and keep the wings in place, but not so strong that the wings are rigid and stiff – they should still be able to flutter elegantly. 
  18. Cut the RGB strip to the desired length. 
  19. The 3-pin headers must then be soldered to the side of the strip indicated by the arrow.  
  20. Remove the backing and stick it down. If sticking along the outside frame, cello tape may be needed to reinforce it as the strip may not stay in place along the edge of the wire. 
  21. Wire up the mini breadboard to the two buttons and potentiometer 
  22. Use the longer leads to connect the components of the mini breadboard back to the Arduino Uno board 
  23. Using the recycled gold strips, wrap the leads tightly together to give it the appearance of a necklace. 
  24. Add gems to the buttons and potentiometer to also disguise them as a pendant in the necklace. 
  25. Connect the servos and RGB strips to the Arduino board and breadboard.  
  26. Create/stick the cardboard box to the wire support between the wings. Make sure this is on the correct side (the side that won’t be touching the wearer’s back). 
  27. Place the Arduino board, carefully, into the box along with all wiring. The ‘necklace’ (mini breadboard does not go inside so naturally some wiring will not fit fully inside the box – this is okay). 
  28. Add the strings to the top of the wire support – these are so that the wings can be worn by tying them around the wearer’s arms. 
  29. Add embellishments as desired. 

Code

// code by Halak & Jes :))))
#include <Servo.h>
#include <FastLED.h>
#define LED_PIN 5
#define NUM_LEDS 99  // we have 99 leds

Servo myservo;  // create servo object to control a servo         
Servo myservo2;
Servo myservo3;
Servo myservo4;

int pos = 0;
bool servoMoving = false;
bool lightOn = false;


const int potPin = A0;      // potentiometer pin (analog)
const int lightBtnPin = 2;  // the button to turn on lights
const int flapBtnPin = 3;  // button to control servo

// variable for reading the pushbutton status 
int buttonStateL = 0;  // for lights
int buttonStateS = 0;  // for servos
int potVal = 0;

int pressesL = 0;
int pressesS = 0;
CRGB leds[NUM_LEDS];

// global r,g,b values approach
int red = 255;
int blue = 0;
int green = 200;

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

  FastLED.addLeds<WS2812, LED_PIN, RGB>(leds, NUM_LEDS);
  FastLED.setMaxPowerInVoltsAndMilliamps(5, 500);
  FastLED.clear();
  FastLED.show();

// pinMode(lightBtnPin, INPUT_PULLUP);

  pinMode(lightBtnPin, INPUT);
  pinMode(flapBtnPin, INPUT);
  // Potentiometer doesn't need definition, just analogRead();

  myservo.attach(10);  // attaches the servo on pin 9 to the servo object
  myservo2.attach(11);
  myservo3.attach(12);  // attaches the servo on pin 9 to the servo object
  myservo4.attach(13);

  // interrupt
  //pinMode(INTERRUPT_PIN, INPUT_PULLUP);
  pinMode(flapBtnPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(flapBtnPin), interruptServo, FALLING); // trigger when button pressed, but not when released.
  //attachInterrupt(digitalPinToInterrupt(lightBtnPin), interruptLight, FALLING);
}


void interruptServo() {
  Serial.println("interrupted");

  if(servoMoving == true){
    servoMoving = false;
  }
}

// void interruptLight() {
//   Serial.println("interrupted");
//   updateLights();
// }


void turnAtoB(int a, int b, int speed) {  // a to b, a < b
  for (pos = a; pos <= b; pos += 1) {
    myservo.write(pos);  // tell servo to go to position in variable 'pos'
    myservo2.write(pos);
    myservo3.write(pos);
    myservo4.write(pos);
    delay(speed);  // waits 15ms for the servo to reach the position
  }
}

void reverseAtoB(int a, int b, int speed) {  // a to b, a > b
  for (pos = a; pos >= b; pos -= 1) {
    myservo.write(pos);  // tell servo to go to position in variable 'pos'
    myservo2.write(pos);
    myservo3.write(pos);
    myservo4.write(pos);
    delay(speed);  // waits 15ms for the servo to reach the position
  }
}


void colourStrip(int g, int r, int b) {
  for (int i = 0; i < NUM_LEDS; i++) {
    leds[i] = CRGB(g, r, b);  //
    FastLED.show();
  }
}


void dialcol(int in) {  // input should be potentiometer value
  // logically, colour cycles are just shifting the distribution of RGB between two at a time.
  // see: R=255,B=0 -> R=0, B=255 goes through red, pink, purple, blue (keeping last value 0 to keep it light);
  // we do this for RB, BG and GR: shifting 255 values 3 times, so x of 765 "points" to map onto 0-1024

  // first off, map where the potentiometer is on its cycle
  int mapped = map(in, 0, 1023, 0, 765);

  // now work out: is it RB[0], BG[1] or GR[2]?
  int phase = mapped / 255;  // int divide
  int rem = mapped % 255;    // modulus: this is how far in we are (first val of pair)

  if (phase == 0) {  // RG
    red = rem;
    green = 255 - rem;
    blue = 0;
  } else if (phase == 2) {  // GB
    green = rem;
    blue = 255 - rem;
    red=0;
  } else {  //BR
    blue = rem;
    red = 255 - rem;
    green=0;
  }
  // reload lights if they're on
  if(pressesL %2 != 0){
    colourStrip(green, red, blue);
  }
}

void updateLights() {
  if(lightOn ==true) {
    colourStrip(green, red, blue);
  } else {
    colourStrip(0,0,0);
  }
}

void loop() {

  delay(100);
  buttonStateL = digitalRead(lightBtnPin);
  delay(100);
  buttonStateS = digitalRead(flapBtnPin);
  
  potVal = analogRead(potPin);
  dialcol(potVal);

  // check if the pushbutton is pressed.
  //   if it is, the buttonState is HIGH:
  // of buttonstate is on - then only read the potentiometer and change the colour
  // if (buttonStateL == LOW) {
  //   pressesL++;
  //   if (pressesL % 2 != 0) {
  //     Serial.println("led on");
  //     colourStrip(green, red, blue);
  //   } 
  //   else {
  //     Serial.println("led is turned off");
  //     lightOn = false; 
  //     colourStrip(0,0,0);
  //   }
  // }
  colourStrip(green, red, blue);

  if (buttonStateS == LOW) {
    pressesS++;
    if (pressesS % 2 == 0) { // even: turn off
      Serial.println("its even, so the servo is off");
      servoMoving = false;
    } else {
      Serial.println("its odd, so the servo is on");
      servoMoving = true;
    }
  }

  if(servoMoving ==true) {
    Serial.println("servoMoving");
    int min = 0;
    int max = 100;
    int spd = 15;
    turnAtoB(90, 140, spd);
    reverseAtoB(140, 90, spd);
    // i think (the first two servos) need to go 180 to 140
  }
  //updateLights();
}

​ Circ 07, Circ 04 were used as a reference for the code for the push buttons and the servos. FastLED library was used for the coding the addressable LED strip

Testing

Testing was conducted at every stage of the building process. Firstly, the RGB strips were programmed to output a single colour to ensure all LEDs were functional; they were also tested after being soldered to the headers to ensure the 3-pin header had been joined properly, and then again when the button input had been wired. 

The mini breadboard was wired to connect two buttons and a potentiometer to the main arduino. We used a sketch that printed different outputs to the serial monitor when it detected each button press, and kept a record of previous state to ensure buttons were togglable. We also printed the value of the potentiometer at regular intervals. When we were certain all input methods were responsive and wired correctly, we connected the RGB strip to the arduino and debugged a sketch which took inputs from the potentiometer and mapped them to an RGB output. 

As the servos were attached to the wings, they were tested using a simple servo movement sketch to ensure both that the wings moved in the right direction (away from the intended user) and that a pair along the wing would move in sync. Once all servos were installed, we ensured they could be activated and deactivated with button presses before assembling to the frame.  

Issues that came up during code testing were mainly to do with button responsiveness. Even when wired properly, both buttons appeared to not register response or lag when turning off the servo or RGB strip. After re-working the order of checking for buttons within loop(), we introduced an interrupt to the servo button pin which prioritised checking the state of the servos when the button was pressed. This reduced the severity of the issue, but there is still a short delay between either button press and actual stopping of the RGB strip and/or servo. 

​ 

Possible Improvements

Hardware improvements
  • The RGB strip would go around the entire wing.  
  • Customisable pendant: buttons can be changed so colours fit the character/scene better, further blending the pendant into the costume. 
Coding improvements
  • Different light sequences could be programmed and used. For example, different patterns such as blinking or running, and colours could be cycled. There could also be the option to adjust the brightness of the lights. 
  • Fluttering could have a speed adjustment feature – allows for more dramatic effects on stage. 
  • Allow to change the colour on the led strip with the potentiometer without turning on the lights as well.

Group members

2023 Group 4 – Angel Hat and Glove Set

Created by Nimat Choudhury, Sahra Yusuf, Shahd Ahmed

Do you wish to make heads turn everytime you step in the room? Well, we’ve got you covered with our newest release: The Angelic Hat and Magic Glove set! With a cute design and unique never-before-seen features, this hat will quickly become the most prized item in your wardrobe. Interactions such as moving your head to flap the wings or triggering a colour change with a single touch are sure to make everyone jealous of the power you’ll possess after owning this set.

Motion Tracking Fashionable Wearable

We chose to create a motion-tracking fashionable wearable, in the form of a hat and glove, syncing wirelessly. Our brief was to design a garment capable of tracking body movement and provide outputs based on collected data, whilst also focusing on being practical for everyday use and maintaining aesthetics. Our project revolves around a hat with wings and LEDs, which are controlled by movement, and the wearer pressing sensors on a glove. The sensors control the LED colour and light sequence, and head movement makes the wings flap. A hat perfect for making heads turn!

Design Blog

Week 7

Brainstorming

Brainstorming Session Notes

After choosing our brief, we began brainstorming and expanding on initial ideas.

We came up with numerous ideas such as a dress, a skirt, a glove, a hat, and glasses. After discussion, we decided to use a hat as our project base. Designs included a costume-style hat, similar to Captain Hook, with large feathers and suitable for dress-up parties, a ship’s captain-style hat with matching gloves, and various styles of hats with wings.

A sketch of the finalised idea

Ultimately, we went with a casual hat suitable for everyday wear. We also wanted to combine a scrapped idea with this hat, so we brought back the glove. The glove, along with the addition of wings, will communicate with the hat wirelessly through hand gestures. The end product would give the wearer an accessory to wear on any occasion, whilst having unique features, adding interactive elements, as well as customisation.

Week 8

Components Research

After completing the design of our project, we researched components that we would utilise to develop a better understanding of how they work and how to implement them with Arduino.

Wireless Module

We researched wireless modules and how they can be used with Arduino, so we can use them to make our hat and glove communicate with each other. We ended up ordering a NRF24L01, a good module for wireless communication between 2 Arduinos with close proximity.

Gyroscope

A gyroscope to be used to detect movement in angles, which will determine the movements of the wings on the hat. Specified angle ranges would result in different wing movements. We decided the MPU6050 module would be most suitable to meet our task’s goals as it can detect angles and acceleration.

Mini Servo

We saw mini servos as the best way to make the wings move and most suitable for the movements we wanted to implement. They would also be small enough to fit into the hat while also managing to hold up the wings.

NeoPixel Ring 16

We wanted to add LEDs placed in the shape of a heart on our hat and glove, but instead we found that a NeoPixel Ring would be much more efficient to use as it would connect to one pin, as opposed to multiple LEDs which would be a hassle to keep control of. The NeoPixel can have various colours and different light animations, which would work well with our project and can be controlled with sensors in the glove.

GSR Sensor

We knew we wanted additional features that would add a small health aspect to our product. Because of this, we also chose to incorporate a GSR sensor which measures the amount of sweat produced. Along with being able to indicate if someone has just done exercise, the amount of sweat can also correspond to the user’s emotional state. We plan to use this to manage change in hues of the NeoPixel on the glove.

Week 9

Prototype

Bottom – first prototype, Middle – second prototype, Top – final wing, chosen.

We began prototyping the wings first, in order to attach them to servos and test their movement. The wings were crocheted by Sahra, and originally were white and light blue. The next wing prototype was a larger size, to better match the hat. After considering our hat’s aesthetics and asking a couple stakeholders on their preference, we decided to use wings with brown stitching instead of light blue, to better match the colour scheme.

Testing the gyroscope with servo motors

Week 10

After prototyping, we started to implement the real functionalities of the final product. To ensure a proper functionality of each component, each was tested separately and then each functionality was combined with another until all functionalities are working out simultaneously. Unfortunately, not all features worked well with the wireless module. The servo and animation feature would not send from transmitter to receiver so we had to compromise by limiting servo movements to the gyroscope and switching animations from being triggered by the glove sensors to being activated by a button on the hat.

Testing the wireless communication between the force sensor and the ring
Testing the mood ring with the stress measuring sensor
Testing the wireless communication between the force sensor and the ring Testing multitasking of the microcontroller with two features: wirelessly changing colour with force sensors and altering animation by a button

Week 11

We were working on combining multiple features and debugging errors we came across. Further investigation of enhancing the code and using more comprehensive commands has been done to enable the better functionality performance.

Combining wireless colour changing feature with wings flapping controlled by servos and gyroscope, making sure they can function sumultaneously
Testing all functionalities together

It was hard to get many videos of the hat working using batteries because the Arduino drained them so quickly. Here we manage to show the hat’s light animation and servo feature working with the Arduino inside of the hat and a battery powering it. As you can see, pressing the button flicks through a variety of animations, while moving your head causes the wings attached to the servos to move.

Project Task List

WeekTaskMember(s)
7Topic selection
Project brainstorm, exploring ideas
All members
8Research components:
Wireless module
Gyroscope/Servo
Galvanic sensors, LDR
All members
Shahd
Sahra
Nimat
9Prototypes
Order components
Hard coding, testing separate functions and components
All members
10Assembling circuits
Making wings
Coding functions
Order components
Plan/organise portfolio
All members
Sahra
All members
Shahd
Nimat
11Debugging
Further assembling of components
Combining materials to components
Portfolio/design blog
Testing
Sahra, Shahd
All members
Sahra
Nimat
Sahra, Shahd
Task List
Gantt Chart

Maker Manual

Overview

Tools and Components

Glove (Transmitter)

  • 1 NeoPixel Ring 16 
  • 1 NRF24L01 (Wireless module) 
  • 1 10uF Capacitor
  • 1 GSR sensor 
  • 2 round force sensitive resistors 
  • 4 AA 1.5V batteries 
  • 1 Arduino Uno 
  • 1 Breadboard 
  • Wires 

Hat (Receiver)

  • 1 NeoPixel Ring 16 
  • 1 NRF24L01 (Wireless module) 
  • 1 10uF Capacitor
  • 1 MPU6050 (Gyroscope/Accelerometer) 
  • 2 mini servos 
  • 1 button 
  • 4 AA 1.5V batteries 
  • 1 Arduino Uno 
  • 1 Breadboard 
  • Wires 

Circuit Layout

Transmitter

Receiver

How To Build

Code

Transmitter

// Code 1: Sending Text (Transmitter)
#include <SPI.h>
#include <RF24.h> //Wireless Module Libraries
#include <nRF24L01.h>
#include <Adafruit_NeoPixel.h> //RGB 16 LED Ring Library
RF24 radio(9, 8); //Pins for wireless module (CE, CSN)
const byte address [10] = "ADDRESS01"; //Address for radio communication
char color [][7] = {"C0", "C1", "C2", "C3", "C4", "C5"}; //Array of colour numbers to be sent to Receiver
const int FSR_THUMB = A0; //Pin for force sensor at the thumb
const int FSR_RING = A1; //Pin for force sensor at the ring finger
int thumbReading = 0; //Reading from thumb's force sensor
int ringReading = 0; //Reading from ring finger's force sensor
int countColour = 0; //Pointer for color array


const int GSR = A5; //Pin for sweat sensor
int sensorValue; //Reading from sweat sensor
int LED_PIN = 6; //NeoPixel Pin
int LED_COUNT = 16; //Number of LEDs on the NeoPixel
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800); //Declare NeoPixel strip object
int brightness = 20; //brightness of NeoPixel
int ledColour; //Colour of NeoPixels
int blueColour = 0; //Blue hue of NeoPixel
int greenColour = 60; //Green hue of NeoPixel
int redColour = 60; //Red hue of NeoPixel
unsigned long timer = 0; //Variable used with millis() when tracking GSR reading


void setup() {
  Serial.begin(9600);
  radio.begin();
  radio.openWritingPipe(address); //Set destination address
  radio.setPALevel (RF24_PA_MIN); //Adjust power level to minimum as modules will be close together
  radio.stopListening(); //The Transmitter does not receive data
  strip.begin();
}
void loop(){
  sweatBlue(); //Call function for using GSR sensor
  sensorTapping(); //Call function for reading force sensors and sending data to receiver
}
void showColour(uint32_t color, int brightness) { //Function to show a given colour and brightness
  for (int i = 0; i < strip.numPixels(); i++) {
    uint8_t red = (color >> 16) & 0xFF;
    uint8_t green = (color >> 8) & 0xFF;
    uint8_t blue = color & 0xFF;


    strip.setPixelColor(i, strip.Color(red * brightness / 255, green * brightness / 255, blue * brightness / 255));
    strip.show();
  }
}
void sweatBlue() { //Function to change colour of NeoPixel on glove to display level of sweat
  if (millis() - timer >= 100) { //This should happen every 100ms
    sensorValue=analogRead(GSR);
    blueColour = map(sensorValue, 0, 800, 255, 0); //Map the sweat sensor value to be used as the blue hue for the NeoPixel's colour
    Serial.println(sensorValue);
    ledColour = strip.Color(redColour, greenColour, blueColour);
    showColour(strip.Color(redColour, greenColour, blueColour), brightness); //Put the new blue value into the NeoPixel
    timer = millis(); //Update timer
  }
}
void sensorTapping() { //Function to check if ring finger and thumb are pressed together
  thumbReading = analogRead(FSR_THUMB);
  ringReading = analogRead(FSR_RING);
  Serial.println("thumb" + String(thumbReading)); //Used for testing if sensors are being read correctly
  Serial.println("ring" + String(ringReading));
  delay(1000); //Delay of 1 second to make sure one finger tap isnt registered as multiple taps
  if ((thumbReading > 400) && (ringReading > 400)) { //Colour change when force exceeds 400
    if (countColour < 5) {
      countColour++; //Increment pointer in color array
    } 
    else {
      countColour = 0; //reset pointer to index 0 when it exceeds index 4
    }
    radio.write(&color[countColour], sizeof(color[countColour])); //Send the new colour to Receiver
    Serial.print("Text: ");
    Serial.println(color[countColour]); //Used to test if pointer at color array has been changed
    delay(500); //Delay of 0.5 seconds to make sure one finger tap isnt registered as multiple taps
  }
}

Receiver

// Code 1: Sending Text (Receiver)
// Library: TMRh20/RF24 (https://github.com/tmrh20/RF24/)
#include <SPI.h>
#include <RF24.h> //Wireless Module Libraries
#include <nRF24L01.h>
#include <Adafruit_NeoPixel.h>//RGB 16 LED Ring Library
#ifdef __AVR__
#include <avr/power.h> //Required for 16 MHz Adafruit Trinket
#endif
#define LED_PIN    6 //NeoPixel pin
#define LED_COUNT 16 //Number of LEDs on the NeoPixel
#define BUTTON_PIN   7 //Pin for button
#include <Servo.h> //Servo Library
#include <MPU6050_tockn.h> //Gyroscope/Accelerometer module
#ifdef __AVR__
 #include <avr/power.h> //Required for 16 MHz Adafruit Trinket
#endif
bool oldState = HIGH; //Variable to store the button's old state
int showType = 0; //Variable for the animation number used to as an argument for startshow function


Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800); //Declare NeoPixel strip object


int pos = 0; //Initialise position of servo
int brightness = 50; //Initialise brightness of NeoPixel
RF24 radio (9, 8); //CE and CSN pins
const byte address [10] = "ADDRESS01"; //Variable for the host and receiver
int animation_count = 0; //Pointer for animation array
int countColour = 0; //Pointer for colours array
uint32_t colours[] = { //Array of colours to be 
  strip.Color(0, 255, 0), //Green
  strip.Color(255, 0, 0), //Red
  strip.Color(0, 0, 255), //Blue
  strip.Color(255, 255, 0), //Yellow
  strip.Color(255, 0, 255), //Magenta
  strip.Color(0, 0, 0) //Off
};


MPU6050 mpu6050(Wire); //Creating MPU6050 object
float x; //x rotation value from MPU6050 module
float y; //y rotation value from MPU6050 module
float z; //z rotation value from MPU6050 module
float prevX; //Saving the x rotation value from the previous 1/2 second
float prevY; //Saving the y rotation value from the previous 1/2 second
float prevZ; //Saving the z rotation value from the previous 1/2 second
float xDiff; //Difference in x rotation within 1 second
float zDiff; //Difference in z rotation within 1 second
unsigned long timer = 0; 
unsigned long timer2 = 0; //Variables to be used when implementing millis() for multitasking
unsigned long timer3 = 0;


//Class for multitasking servos reused from https://learn.adafruit.com/multi-tasking-the-arduino-part-1/overview


class Sweeper { //Declaring a class for the servos to work with multitasking


  Servo servo; //The servo 
  int pos; //Current servo position  
  int increment; //Increment to move for each interval 
  int  updateInterval; //Interval between updates 
  unsigned long lastUpdate; //Last update of position 


public:
  Sweeper(int interval, int inputIncrement) { //Method to create instance of Sweeper class
    updateInterval = interval;
    increment = inputIncrement;
  } 


  void Attach(int pin) { //Method that causes wings to move
    servo.attach(pin); //Attach servo in order to move
  } 


  void Detach() { //Method that causes wings to stop moving
    servo.detach(); //Detach servo in order to halt it
  } 


  void Update() {  //Increment servo using the argument
    if((millis() - lastUpdate) > updateInterval) { //Using millis() to allow for multitasking
      lastUpdate = millis(); 
      pos += increment; //Move wing forwards until it reaches 80 degrees
      servo.write(pos); 
      if ((pos >= 80) || (pos <= 0)) { //Wing moves 80 degrees backwards
        increment = -increment; //Reverse direction 
      } 
    } 
  } 
};


Sweeper sweeper1(5, 5); //Left wing will move 5 degrees every 5 milliseconds
Sweeper sweeper2(5, 5); //Right wing will move 5 degrees every 5 milliseconds


void setup() {
  pinMode(BUTTON_PIN, INPUT); //Set button pin mode to input
  Serial.begin(9600);
  radio.begin();
  radio.openReadingPipe(0, address); //Set up radio to listen on pipe 0 with address "ADDRESS01"
  radio.setPALevel(RF24_PA_MIN); //Adjust power level to minimum as modules will be close together
  radio.startListening(); //The Transmitter does not receive data
  Wire.begin();
  mpu6050.begin();
  mpu6050.calcGyroOffsets(true); //Used to remove the other forces acting on the gryoscope
  sweeper1.Attach(3); //Attach left servo to pin 3
  sweeper2.Attach(5); //Attach right servo to pin 5
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
  clock_prescale_set(clock_div_1);
#endif
  strip.begin();
  strip.show();
  showColour(strip.Color(255, 255, 255), 50); //Start program by setting colour to white and brightness to 50
  strip.setBrightness(50); //Set the LEDs brightness to 50
}
void loop() {
  changecolor(); //Call function that changes colour when a message is sent from the transmitter
  gyroServo(); //Call function that combines MPU6050 and servos
  animationButton(); //Call function that uses a button to change light animations
}
void changecolor() { //Function to change the colour of light when message is sent from the transmitter
  while (radio.available()) { //While the transmitter and receiver are connected
    char txt[4] = ""; //Create a blank array of characters where the message sent from the transmitter will be stored
    radio.read(&txt, sizeof(txt)); //Read the message being sent from transmitter and store it in the txt array
    Serial.println(txt); //Display to see if anything is being sent from the transmitter
    if (txt[0] == 'C') { //If a colour change is being initiated...
      if (txt[1] == '1') { //(Number corresponds to how many times user has pressed force sensor)
        Serial.print("text: "); 
        Serial.print(txt[0]); //Display to see if what's being sent from the transmitter is being received
        Serial.println(txt[1]);
        showColour(colours[0], 50); //Display the appropriate colour from the array according to the number that's being received
      } else if (txt[1] == '2') {
        Serial.print("text: ");
        Serial.print(txt[0]); //Display to see if what's being sent from the transmitter is being received
        Serial.println(txt[1]);
        showColour(colours[1], 50); //Display the appropriate colour from the array according to the number that's being received
      } else if (txt[1] == '3') {
        Serial.print("text: ");
        Serial.print(txt[0]); //Display to see if what's being sent from the transmitter is being received
        Serial.println(txt[1]);
        showColour(colours[2], 50); //Display the appropriate colour from the array according to the number that's being received 
      } else if (txt[1] == '4') {
        Serial.print("text: ");
        Serial.print(txt[0]); //Display to see if what's being sent from the transmitter is being received
        Serial.println(txt[1]);
        showColour(colours[3], 50); //Display the appropriate colour from the array according to the number that's being received
      } else if (txt[1] == '5') {
        Serial.print("text: ");
        Serial.print(txt[0]); //Display to see if what's being sent from the transmitter is being received
        Serial.println(txt[1]);
        showColour(colours[4], 50); //Display the appropriate colour from the array according to the number that's being received
      } else if (txt[1] == '0') {
        Serial.print("text: ");
        Serial.print(txt[0]); //Display to see if what's being sent from the transmitter is being received
        Serial.println(txt[1]);
        showColour(colours[5], 50); //Display the appropriate colour from the array according to the number that's being received
      }
    }
  }
}
void showColour(uint32_t color, int brightness) { //Function to show a given colour and brightness
  for (int i = 0; i < strip.numPixels(); i++) {
    uint8_t red = (color >> 16) & 0xFF;
    uint8_t green = (color >> 8) & 0xFF;
    uint8_t blue = color & 0xFF;
    strip.setPixelColor(i, strip.Color(red * brightness / 255, green * brightness / 255, blue * brightness / 255));
    strip.show();
  }
}
void gyroServo() { //Function to move servos according to the gyroscope movements
  sweeper1.Update(); 
  sweeper2.Update(); //Update state of servos everytime the gyroscope is checked
  mpu6050.update();  //Update gyroscope to check position of the board(hat)
  x = mpu6050.getAngleX(); //Read rotation on x axis
  y = mpu6050.getAngleY(); //Read rotation on y axis
  z = mpu6050.getAngleZ(); //Read rotation on z axis
  if (millis() - timer > 500) { //Every 1/2 second, record the gyroscope position
    prevX = x;
    prevZ = z;
    timer = millis();
  }
  if (millis() - timer2 > 1000) { //Every second, record the difference between the previous gyroscope position and current one
    xDiff = prevX - x;
    zDiff = prevZ - z;
    timer2 = millis();
  }
  if (zDiff>15 || zDiff<-15) { //Move both wings when the hat is moved at least 15 degrees on the z axis in a second
    sweeper1.Attach(3);
    sweeper2.Attach(5);
    Serial.println(zDiff);
  } else if (xDiff>20 || xDiff<-20) { //Move both wings when the hat is moved at least 15 degrees on the z axis in a second
    sweeper1.Attach(3);
    sweeper2.Attach(5);
    Serial.println(xDiff);
  } else if (y>30) { //Rotating hat past 30 degrees to the right on the y axis causes left wing to move
    sweeper1.Attach(3);
  } else if (y<-30) { //Rotating hat past 30 degrees to the left on the y axis causes right wing to move
    sweeper2.Attach(5);
  } else { //If none of these conditions are met, wings do not move
    sweeper1.Detach();
    sweeper2.Detach();
  }
  if (millis() - timer3 > 3000) { //Used to see if gyroscope is responding and working correctly
    Serial.print("angleX : ");Serial.print(x);
    Serial.print("  angleY : ");Serial.print(y);
    Serial.print("  angleZ : ");Serial.print(z);
    Serial.println("");
    timer3 = millis(); //millis() used instead of delay() to not interfere with multitasking features
  } 
}
void animationButton() { //Function that changes animation when button is pressed
  bool newState = digitalRead(BUTTON_PIN); //Get current button state
  if (newState == LOW && oldState == HIGH) { //Check if button is pressed
    delay(20); //Short delay to debounce button
    newState = digitalRead(BUTTON_PIN); //Check if button is still low after debounce
    if (newState == LOW) { //If button is pressed, increment variable that keeps track of the animation type
      showType++; 
      if (showType > 9) //Theres 9 animations, so reset variable to 0
        showType=0;
      startShow(showType);
    }
  }
  oldState = newState; //Set the last button state to the old state
}
void startShow(int i) { //Function used to display different animations
  switch(i){
    case 0: colorWipe(strip.Color(0, 0, 0), 50); //Off
            break;
    case 1: colorWipe(strip.Color(255, 0, 0), 50); //Red
            break;
    case 2: colorWipe(strip.Color(0, 255, 0), 50); //Green
            break;
    case 3: colorWipe(strip.Color(0, 0, 255), 50); //Blue
            break;
    case 4: theaterChase(strip.Color(127, 127, 127), 50); //White
            break;
    case 5: theaterChase(strip.Color(127,   0,   0), 50); //Red
            break;
    case 6: theaterChase(strip.Color(  0,   0, 127), 50); //Blue
            break;
    case 7: rainbow(20);
            break;
    case 8: theaterChaseRainbow(50);
            break;
  }
}


//Animation function - Reused from Adafruit NeoPixel example code 'strandtest'


void colorWipe(uint32_t color, int wait) { //Animation 1
  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);
  }
}
void theaterChase(uint32_t color, int wait) { //Animation 2
  for(int a=0; a<10; a++) {  // Repeat 10 times...
    for(int b=0; b<3; b++) { //'b' counts from 0 to 2...
      strip.clear(); //Set all pixels in RAM to 0 (off)
      //'c' counts up from 'b' to end of strip in steps of 3...
      for(int c=b; c<strip.numPixels(); c += 3) {
        strip.setPixelColor(c, color); //Set pixel 'c' to value 'color'
      }
      strip.show(); //Update strip with new contents
      delay(wait);  //Pause for a moment
    }
  }
}
void rainbow(int wait) { //Animation 3
  for(long firstPixelHue = 0; firstPixelHue < 5*65536; firstPixelHue += 256) {
    strip.rainbow(firstPixelHue);
    strip.show(); //Update strip with new contents
    delay(wait);
  }
}
void theaterChaseRainbow(int wait) { //Animation 4
  int firstPixelHue = 0; //First pixel starts at red (hue 0)
  for(int a=0; a<30; a++) { //Repeat 30 times...
    for(int b=0; b<3; b++) { //'b' counts from 0 to 2...
      strip.clear(); //Set all pixels in RAM to 0 (off)
      //'c' counts up from 'b' to end of strip in increments of 3...
      for(int c=b; c<strip.numPixels(); c += 3) {
        //hue of pixel 'c' is offset by an amount to make one full
        //revolution of the color wheel (range 65536) along the length
        //of the strip (strip.numPixels() steps):
        int      hue   = firstPixelHue + c * 65536L / strip.numPixels();
        uint32_t color = strip.gamma32(strip.ColorHSV(hue)); //hue -> RGB
        strip.setPixelColor(c, color); //Set pixel 'c' to value 'color'
      }
      strip.show(); //Update strip with new contents
      delay(wait); //Pause for a moment
      firstPixelHue += 65536 / 90; //One cycle of color wheel over 90 frames
    }
  }
}
uint32_t Wheel(byte WheelPos) { //Animation 4
  WheelPos = 255 - WheelPos;
  if(WheelPos < 85) {
   return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else if(WheelPos < 170) {
    WheelPos -= 85;
   return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  } else {
   WheelPos -= 170;
   return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  }
}

Testing & Shortcomings

Multitasking with Arduino

Servo Rotation

Initially we utilized For loops in order to make the servos move. However, this proved to not work very well in the grand scheme of the project, as we had many features that required multitasking. In order to combat this issue, we added a Sweeper class that used millis() in one of the methods. Using millis(), the servo is incremented by 5 degrees every 5 milliseconds after creating 2 Sweeper objects such that Sweeper sweeper1(5, 5) and Sweeper sweeper2(5, 5).

Screenshot 1 code snippet with for loops()

Animation Control

Each animation is a sequence of LEDs with changing colors. There is a delay for each sequence to be displayed. We tried to use millis(), but it could not break out of the while or for loops when the button is triggered. To overcome this limitation, we opted to reduce the animation duration instead of endlessly looping, in order to allow the button to trigger a change in the animation sequence. The animation functions are called in switch cases instead of while loops.

Screenshot 1 code snippet with While loops
Screenshot 2 code snippet with switch case

Wireless Communication

The NRF24L01 transceiver disconnected while testing. The two microcontrollers for the host and transmitter had to be reset before starting to send texts. When another functionality was implemented to change the color animation based on a different text message, the receiver could not configure it. The communication lagging happened due to various reason such as noise interference and short distance between the host and receiver.

For future improvements, ESP-01 WiFi or HC-12 SI4463 is recommended to establish the wireless communication because of the high noise prevention. Additionally, instead of altering animations using a button, another force sensor can be implemented on the glove so that with thumb and index tapping, the ring displays various animations.

Components and Batteries Assembly:

The weight of components is not convenient for a hat-glove set, so it is recommended to design a PCB that has all components mounted to it. The glove would also benefit if an Arduino Nano was used instead of an Uno, but our Nano had compatibility issues which is why we stuck to using an Uno. The batteries assembly is unsafe to be right next to the wearer’s head or hand as they might explode. For safety, a couple of fuses or battery switches should implemented.

2023 Group 3 – Aladdin Genie Lamp

User-interactive Theatrical Props

By: Emma Gonzalez, Liyana Iqbal, Deepika Nimmala

Maker Manual

Final Product – watch with full volume

Tools And Supplies

  1. Servo
  2. LEDs (blue x1, red x6)
  3. Micro SD card module & SD card
  4. Mini Speaker
  5. Ultrasonic sensor
  6. Buttons x3
  7. Wood
  8. Cardboard
  9. Cotton balls
  10. Soldering kit
  11. Glue gun

Breadboard Layout Diagrams

Ultrasonic sensor, SD card and speaker breadboard layout
Genie servo motion and LEDs animation

Build Section

Arduino boards:

On one breadboard we built the audio and ‘rubbing’ interactions. First the SD card and mini speaker were wired up to play the noise of the genie ‘waking up’ and then activate the dialogue of the genie. The MP3 player is only activated once the sliding mechanism exposes the ultrasonic sensor. The slider is gently slid back onto the ultrasonic sensor to ensure that the audio does not replay, imitating the rubbing of the lamp. On another breadboard we built the genie mechanism and LED animation to create a magical aesthetic for the lamp. By pressing the on button for the LEDs, the LEDs will light up one by one in a loop of 10 rounds. This animation should be activated first so that it occurs whilst the sound effects are played in the background. Then, pressing the on button for the genie mechanism will allow the servo to spin out of the lamp, turn on the LED and reveal the genie mechanism. This can be left as is during the duration of the scene, then when it is time for the genie to exit the stage, the off button can be pressed to turn the servo back into the lamp, hiding the mechanism.

Making of slider mechanism
Ultrasonic sensor sliding mechanism
Buttons for LED animation and servo

3D High fidelity Prototype

Draw the outline of the lamp shape onto wood then cut this out. Cut out a square base and glue these pieces together using a glue gun, or use pins/screws to attach them together. Place the breadboards inside the lamp firmly, securing it using tape. Cover the gaps and build the side faces of the lamp using cardboard since this is more flexible therefore easier to bend into shape.

Complete Code

Code for LED animation and servo genie mechanism:

#include <Servo.h>
Servo myservo;
int pos= 0;
int ledPin = 11;      // select the pin for the LED

int buttonPin1 = 12;
int buttonPin2 = 13;
int ledPins[] = {2,3,4,5,6,7}; 
int buttonPin3 = 8;


void setup() {
  // put your setup code here, to run once:
myservo.attach(10);
pinMode(buttonPin1, INPUT);
pinMode(buttonPin2, INPUT);
pinMode(ledPin, OUTPUT);
Serial.begin(9600);
 
  pinMode(buttonPin3, INPUT);
  //Set each pin connected to an LED to output mode (pulling high (on) or low (off)
 for(int i = 0; i < 6; i++){         //this is a loop and will repeat 6 times
     pinMode(ledPins[i],OUTPUT); //we use this to set each LED pin to output
  }                                   //the code this replaces is below
 
}

void loop() {


  // put your main code here, to run repeatedly:
if(digitalRead(buttonPin1) == LOW){
  Serial.println("button 1 works");
  for(pos = 150; pos>=0; pos-=1){
  myservo.write(pos);
  delay(15);
}
  digitalWrite(ledPin, LOW); // turn LED ON
  delay(100);

}
else if (digitalRead(buttonPin2) == LOW){
    Serial.println("button 2 works");
  for(pos = 0; pos<=150; pos+=1){
    myservo.write(pos);
    delay(15);
  }

  digitalWrite(ledPin, HIGH); // turn LED ON
  delay(100);
}
  else if(digitalRead(buttonPin3)==LOW){
     oneAfterAnotherLoop();  
 //does the same as oneAfterAnotherNoLoop but with 
                             //much less typin
  }

}

 void oneAfterAnotherLoop(){
  int delayTime = 100; //the time (in milliseconds) to pause between LEDs
                       //make smaller for quicker switching and larger for slower
 for(int i = 0; i < 10; i++){  
//Turn Each LED on one after another
  for(int i = 0; i <= 5; i++){
    digitalWrite(ledPins[i], HIGH);  //Turns on LED #i each time this runs i
    delay(delayTime);                
  }                                  
                                     
//Turn Each LED off one after another
  for(int i = 5; i >= 0; i--){  //same as above but rather than starting at 0 and counting up
                                
    digitalWrite(ledPins[i], LOW);  //Turns off LED #i each time this runs i
    delay(delayTime);                
  }                                  
 }                                    
                                     
}

Code for ultrasonic sensor and SD card & mini speaker:

#include "SD.h"          // Include the SD library for working with SD cards
#define SD_ChipSelectPin 10  // Define the chip select pin for the SD card
#include "TMRpcm.h"      // Include the TMRpcm library for playing audio
#include "SPI.h"         // Include the SPI library for communication with SD card

TMRpcm tmrpcm;            // Create an instance of the TMRpcm class

const int trigPin = 5;     // Define the trigger pin for the ultrasonic sensor
const int echoPin = 6;     // Define the echo pin for the ultrasonic sensor

long duration;             // Variable to store the duration of the ultrasonic pulse
int distance;              // Variable to store the calculated distance from the ultrasonic sensor
bool audioPlayed = false;  // Flag to track whether audio has played

void setup() {
  tmrpcm.speakerPin = 9;   // Set the speaker pin for audio output
  pinMode(trigPin, OUTPUT); // Set the trigger pin as an output
  pinMode(echoPin, INPUT);  // Set the echo pin as an input
  Serial.begin(9600);       // Start serial communication with a baud rate of 9600

  if (!SD.begin(SD_ChipSelectPin)) { // Initialize the SD card and check for failure
    Serial.println("SD fail");
    return;
  }
}

void loop() {
  digitalWrite(trigPin, LOW);          // Set the trigger pin to low
  delayMicroseconds(2);                  // Wait for 2 microseconds

  digitalWrite(trigPin, HIGH);         // Generate a 10-microsecond pulse on the trigger pin
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);

  duration = pulseIn(echoPin, HIGH);    // Measure the duration of the pulse on the echo pin
  distance = duration * 0.034 / 2;      // Calculate the distance based on the speed of sound

  Serial.print("Distance: ");
  Serial.println(distance);              // Print the calculated distance to the serial monitor

  if (distance > 160 && !audioPlayed) { // If the distance is greater than 160 and audio has not played
    tmrpcm.setVolume(6);                 // Set the volume for audio playback
    tmrpcm.play("test.wav");             // Play the audio file "test.wav"
    audioPlayed = true;                  // Set the flag to true after playing audio

  } else if (distance <= 5) {            // If the distance is less than or equal to 5
    // Reset flags if distance is continuously under 10
    audioPlayed = false;                 // Reset the audio played flag
    delay(15);                            // Introduce a delay to avoid rapid flag toggling
  }
}

Testing and Shortcomings

As displayed in the design blog below, we conducted testing by running the codes for each component separately to identify any errors before combining the codes together to develop our interactions. This enabled us to understand what each line of code would do and order the functions accurately to initiate interactions in the order we want. A shortcoming that we have faced in this project is the sound of the audio outputted by the mini speaker being too quiet. We researched different ways to amplify the audio but soon realised that this would mean we need to order new components which we did not have enough time for. When we initially tried the ‘rubbing’ effect using a motion sensor, we discovered that it was too sensitive therefore would always stay on. If we correlated the motion sensor to the SD card and speaker in the code, this would mean that the audio plays repeatedly. Therefore, we made the decision to use an ultrasonic sensor instead to activate the audio.

Project Task List

TasksMember(s)
Brainstorming concepts Liyana and Deepika
Coding and WiringDeepika and Liyana
DocumentationLiyana
Creating 3D prototypes + Final productDeepika
Updating the blogLiyana and Emma

Design Blog

13/11/2023 – Brainstorming session:

After confirming that we will be working on the ‘user-interactive theatrical props’ project, we researched and made a list of popular theatre shows where we believed interactive props are essential to the storyline. We developed a list of different theatrical props and shows:
1. Magic Lamp (Aladdin)
2. Rocking chain (Women in Black)
3. Broomstick (Harry Potter)
After a group discussion, we decided that we will move forward with developing the magic lamp because we felt this was a prop where we could showcase the most creativity and include more interaction using a combination of different actuators and sensors. We also planned out what sort of interactions we would want before attempting to code and prototype, this included sketches. 

LINK : https://www.youtube.com/watch?v=GI27mX1BHvk

We took inspiration from this scene in Aladdin, specifically from this clip, extracting the audio between 00:37 – 00:57 seconds in the video. This is the audio that will be saved onto the SD card and played from the speaker.

20/11/2023 – Prototyping Session 1: liyana + deepika

Parts:
+ Servo
+ Speaker
+ Blue LED
+ Buttons x2
+ Crocodile clips x2
+ Resistors x3
+ Wires
For our first prototype, we decided to test out the following interactions using different sensors and actuators:
1. Pressing on and off buttons to activate and deactivate the magic lamp.
2. Spinning a servo back and forth when the buttons are pressed to illustrate the genie escaping the lamp and then returning back inside.
3. An LED turns on when the lamp is activated to symbolise the genie’s escape.
4. Using a speaker to play notes when the lamp is activated and deactivated.
After experimenting with the different components on the breadboard, we made a rough 2D model of the magic lamp to understand the positioning of the servo.

The code:

Notes on the current code:
Currently, we have an on-and-off button that turns a blue LED on and off and also plays music when the LED is turned on. It also turns the servo from 0 – 150 when the on button is pressed and then turns it from 150-0 when the off button is pressed. 
Currently, we have two buttons that make the servo spin from 0 degrees to 150 degrees when one is pressed and then from 150 degrees to 0 degrees when the second button is pressed. As a result of the servo spinning to 150 degrees, the LED will switch on and when the servo spins back to 0 degrees, the LED will switch off. This motion will be used to signify that the magic lamp has been activated, and the genie is exiting the lamp. We have also included two different notes to indicate when the LED turns on and when it turns off. 
Our next steps are to figure out how to replace the buttons with a motion sensor as this will be easier to use when the Arduino is placed inside the magic lamp. A motion sensor will enable our lamp to be more user-interactive because it will replicate the ‘rubbing’ of the magic lamp to awaken the genie. We also need to figure out how to upload and use the interaction without connecting the Arduino board to a computer. Over the next few days, we must make a list of components that we need to order.
Things to order/do:
+ Lipo battery to avoid connecting the Arduino board to the computer (We realised that our interactions will not require a high voltage therefore it’ll be more appropriate to use a power bank and USB.)
+ A motion sensor
+ Small crocodile clips (It will be more efficient to solder the wires to the LEDs rather than use crocodile clips because they are less reliable and connections may be lost.)
+ An SD card 
+ An MP3 player

** Note: Before beginning to code, we researched pre-existing tutorials that demonstrate the wiring and primary code for using a motion sensor and SD card:

https://www.arduino.cc/reference/en/libraries/sd/

27/11/2023- Motion sensor testing:

Before coding, we researched which SD card + mp3 player is most suitable for our project.

Motion sensor attempt code:
When we first attempted the motion sensor interaction, the serial monitor would only print motion detected and not print motion stopped despite us stopping the motion ourselves. We identified that the motion sensor was far too sensitive for our project, and was responding to movement within a 3m radius of the sensor. Our interactions require far less sensitivity, with the sensor responding to motion only within a 10cm radius. Therefore, we decided to replace this sensor with an ultrasonic sensor that would allow us to activate the servo and LEDs to reinforce the activation of the magic lamp. 
New improved code based on the seen results:

29/12/2023 – LOW FIDELITY PROTOTYPE

This small scale 3D model was made to understand how the lamp should look as a prop. This will act as a reference for when we create our final product.

02/12/2023 – WORKING SD CARD CODE: deepika

Code that checks if SD CARD works and shoes what files are on the SD card on the serial monitor

SD CARD AND SPEAKER CODE

Simplified code that reads SD card adapter chip pin, identifies audio test.wav which is the required genie audio and plays it from speaker.

Although the SD card successfully transfers information to the MP3 player which then allows it to play an audio, the audio is quite quiet. We tried to figure out a way to amplify the audio but soon realised that there were too many components requiring more resistance which meant our audio was quieter.

03/12/2023- Combining the SD card and Ultrasonic Sensor

After getting the MP3 player to play the audio on the SD card, we combined the ultrasonic sensor code with the SD card code. This would mean that when the ultrasonic sensor is activated, the audio would play. In practice, this would initiate the sound effects and dialogue of the genie when the lamp is ‘rubbed’ by interacting with the ultrasonic sensor.

04/12/2023 – Separating components & soldering (liyana + deepika)

After adding the SD card and speaker to the breadboard, we decided that wiring all of our components onto just one breadboard was overcrowding it, therefore, we decided to split the code up into 2 breadboards. Today we individually tested out the servo being activated and deactivated by 2 buttons and then 6 LEDs lighting up one after the other to add the aesthetics of the magic lamp.

Serial Output
6 led animation code:
This code runs through an array of LEDs and turns them on one after another in a loop to create an animation that creates the illusion of the magic lamp being activated before the actor of the genie enters the stage. 
After testing the array of LEDs and the servo separately, we then combined both codes into one.
After that, LEDs were soldered to the wires to allow us to position them better in the lamp structure rather than having the LEDs in a row which does not create an exciting animation. If the LEDs are positioned all over the lamp rather than in a row in one area, everyone in the audience will be able to see that the magic lamp is activated, considering the audience is not always seated directly in front of the stage at theatre shows. 
Soldering

08/12/2023- Constructing the Lamp : deepika

Before making a 3D prototype, we sketched out what the lamp should look like and the different elements required in its structure and construction. By creating a 3D prototype using cardboard, we were also able to determine how users could access our interactions, for example, how they could use the ultrasonic sensor without the audience seeing it or without difficulty.

After creating a 3D prototype out of cardboard to understand where to place the 2 breadboards, we decided to create the final product out of a combination of recycled wood and cardboard.

Recycled scrap wood
Final product
Video showing how slider mechanism works – watch with full volume.
Backside of the prop
Genie fluff
Genie mechanism

Front of prop

2023 Group 1 – Trifecta

PROJECT BRIEF

Escape rooms require you to solve a series of puzzles within a time limit to escape. Create a puzzle for participants to solve using the skills you’ve learnt. Your puzzle might include a sequence of things to solve, should offer some help if participants get stuck, whilst also rewarding them on its completion. You are asked to design an Arduino based circuit that could be used as part of an escape room

Purpose

To entertain children with a fun interactive puzzle which keeps them engrossed. Various components like potentiometer, buttons and keypad will be used as a source of input from the user and a visual or auditory would be provided as an output.

Target Audience

The targeted demographic is older children between the age of 8-13 which are looking for an interesting puzzle/ game. Puzzles like these could be really fun for them and can help improve short term memory, fine motor skills and mental speed.

Market Research

1. Talking Tables Escape Room Kyoto Edition

With a playtime of 60 minutes, it’s a fun-filled quick game for an evening with friends. So hosts and players can gather their teams, sharpen their minds, and get ready to embark on an unforgettable adventure through the streets of Kyoto. Will you be able to solve the puzzles and escape before time runs out?

2. Cluebox “The Trial of Camelot”

This is a portable Escape Room game which has a playtime of 60 – 90 minutes. This involves various engaging puzzles you need to do to complete the game.

3. Professor Puzzle Escape Room Game

In this game you’ll move through the hotel solving the series of interactive puzzles to release you from each room. The ultimate quest being to uncover the dark, hidden secret of The Grand Hotel.

Conclusion

All these above games are really interesting and look really fun to play, but these games take really long to finish and only consist of visual cues. The targeted audience has a really short attention span so we can’t expect them to sit still and complete an hour long game. So our game would be fun and comparatively quick to play, will also involve playing around with sound.

INSTRUCTION MANUAL

Phase 1

Match the lights with corresponding button, three levels of increasing difficulty

Phase 2

Work out the answer to each equation and input the final digit of each into the pin pad once all figured out

Phase 3

Match the user’s tone to the original using three potentiometers

PROJECT TASK LIST

Plan Chart

Task Distribution

AdibulJamesRahul
Phase 3 Code and Testing
I2C Communication
Assembly
Phase 1 Code and Testing
Phase 2 Code and Testing
Production of box
Documentation
Assembly
Documentation
Design and CAD model of the box
Production of box
Assembly

Timeline

Week 9 – Discussed and chose our project
– Brainstormed possible ideas
– Discussed possible functions
– Created a design for the escape room.
– Created a low fidelity prototype of the design.
Week 10 – Ordered the components
– Completed phase 1 & 2 code
– Changed our design for phase 3
– Started with phase 3 code
– Started documentation and completed blog design
Week 11 – Design the exterior of escape room
– Created 3D CAD model of the design
– Started making the box for the escape room
– Completed phase 3 code
– Completed making the box for the escape room
– Connect phase 1 & 2 with 3 using I2C communication
Week 12 – Solder all parts
– Assemble and attach all parts onto the frame of the box

DESIGN CHOICES

Box Design:

For the box we decided to go for a triangle shape because our game consisted of 3 phases, so we thought triangle is best way to represent that.

For the material we chose MDF board because it is cost-effective and easily available while also being stable, durable and sustainable.

Additionally it is very consistent throughout, so the cut edges will appear smooth and won’t have voids or splinters.

We used square dowel sticks as the border for the front panel because it shows a really nice contrast between the rest of the box and the border while also partitioning all 3 phases.

Layout of the potentiometers and LEDs is in that particular way to reduce negative space in their respective partition.

Extra space for Phase 2 is given compared to Phase 1 & 3 not just because LCD screen and key pad require more phase, we wanted it to be the centre of focus. This is because LCD screen was planned to display hints for other phases as well.

Phase 1:

For this phase we decided to use LEDs, piezo element and buttons to create an interactive game because we believed LEDs work very as a visual cue for the user and buttons can work well as the user input. In this the user will have to match the sequence of blinking LEDs with the button of corresponding colour. Additionally we also used piezo element to produce different sounds for pressing the button, completing the levels and completing the entire phase. The sounds are really important to make the game more engaging and letting the user whether they have completed a level or the entire phase. Also at the end of each level and the entire phase all LEDs would start blinking. Finally to complete the first phase, user has to complete three levels of increased difficulty.

Phase 2:

For the second phase we decided to use a screen to display the equations and keypad for the user input. Being a escape room, how can we not put a puzzle which represents opening a safe. That’s exactly what were trying to go for with the keypad and screen. In this the user would be given four simple math equations on the screen and the last digit of each answer of the equations is the 4 digit code to reach next phase.

Phase 3:

In the last phase we decided to use the potentiometer as the user input and piezo element as the output. We went for potentiometers as the input because we wanted this phase to represent opening a vault kind of a puzzle where the user has to rotate a dial. For the output we went for piezo element, this works a audio cue for the user, this design decision was also backed by the market research which showed there are no available escape room puzzles in the marker with auditory cues. In this phase the user would have to match the music sequence consisting of 3 notes coming out of the piezo element which is random every time it is played, using the 3 potentiometers which work as dials. We also decided to keep different coloured LEDs to differentiate the sound provided to the user by the game and the ones they are testing. Additionally, we limited the range of frequencies provided in this level between 0Hz to 1000Hz to reduce the difficulty of this phase while also providing the user a bit of extra leverage by accepting the input plus or minus 50Hz to the original value given by the game as the correct answer. When all 3 notes are matched the user wins the game and it is represented by the blinking red and green lights in this phase.

MAKER MANUAL

Early Brainstorming

Low Fidelity Prototype

Production stages

  • Take a MDF piece and cut it according to the dimensions provided on the engineering drawing using a band saw
  • Sand uneven edges using disc sander
  • Attach the bottom triangle piece with two side pieces using hot glue
  • Drill holes of different dimensions(refer engineering drawing for dimensions) for the potentiometer, LEDs and buttons using hand drill
  • For the slot for the LCD screen drill a hole in 4 corners and cut the rectangle using a coping saw.
  • For the slot for the keypad wiring, drill onto to the two ends of the required slot and cut the piece using a coping saw.
  • Cut square dowel rods using a hand saw
  • Sand the imperfections using a disc sander
  • Stick the cut pieces on the edges of the existing frame as per the image using hot glue.
  • Solder all the parts and wires
  • Fit all the soldered parts(LEDs, potentiometer, buttons) in their respective slots
  • Reassemble the circuit with the soldered wire and parts
  • Put the LCD screen in its respective slot
  • Stick the keypad onto the wood
  • Place the front wooden panel in the box

Final Product

Dimensions and CAD Model

Engineering Drawing

Final Model

Phases 1, 2 & 3 (order is left to right on the panel)

Demo of Model

Parts used

Level 1 + 2:

  • 1 x Arduino Uno
  • 3×4 Keypad
  • 1 x Breadboard
  • 1 x Piezo Buzzer
  • 4 x LED’s (White, Blue, Red, Green)
  • 4 x Pushbuttons (White, Blue, Red, Green)
  • 4 x 10k Ohm Resistor
  • 4 x 560 Ohm Resistor
  • 39 x Wires

Level 3:

  • 1 x Arduino Uno
  • 1 x Breadboard
  • 1 x Piezo Buzzer
  • 3 x Potentiometer
  • 2 x 560 Ohm Resistor
  • 1 x 10k Ohm resistor
  • 2 x LEDs (Red and Green)
  • 14 x Wires

Connection for Arduinos (I2C):

  • 3 x Wires

Box:

  • MDF Board
  • Wooden Square Dowel Rods

Tools used

  • Hand Saw
  • Band Saw
  • Coping Saw
  • Hand Drill
  • Hot glue
  • Disc Sander

Breadboard Diagram

Recycle & Reuse

LCD screen and the Keypad we used for our final product was used from the box of reusable parts in the lab.

We reused square dowel rods which were put in the wood scraps in the maker space for the border of the front panel.

Shortcoming

  • In Phase 2 of the escape room, moving from input on the serial monitor to using the keypad was a struggle as the keypad is a new way to code. Some different iterations were made but the final working version is that the four equations are displayed and then the pin is inputted.
  • Where we wanted three levels, using one Arduino wouldn’t be enough for this due to the lack of pins. Therefore, we tried to use a multiplex to increase the amount of pins, this didn’t work. So therefore we have decided to go with using two Arduinos.
  • For the Phase 2, LCD screen didn’t work as planned. We tried to code the LCD screen in different ways but nothing seemed to work also with the help of the professor and demonstrators in the computer lab. Apparently there was a technical issue with the LCD screen provided and there wasn’t enough time to order a new one. So we had to shift everything which was supposed to display onto the LCD screen to the serial monitor, which primarily consisted of the equations for this phase and also we were planning to give hints for other phases that also couldn’t be done.
  • The buzzer in Phase 3 was very sensitive due to using potentiometers, we overcame this by allowing a range for the potentiometers to be in, instead of being exactly correct which was very hard to do. This allowed the user to complete the level as before it was nearly impossible due to the potentiometers changing value from any small knock.
  • Due to the time constraint we had to rush a lot of things which included the making of the final model, if we had more time to work on the final product it could have looked much better.

Code

Level 1 & 2

// Include Arduino Wire library for I2C
#include <Wire.h>

// Define Slave I2C Address
#define SLAVE_ADDR 9



//For level 1
int ledPin[4] = { 2, 3, 4, 5 };
int buttonPin[4] = { 8, 9, 10, 11 };

long randNumber;

int random1;
int random2;
int random3;
int random4;
int random5;

bool winlevelone;
bool winleveltwo;
bool winlevelthree;

int speakerPin = 12;  //speaker pin

int length = 2;
int lengthding = 3;
int lengthwin = 8;

char notes[] = { "ce" };
char notesding[] = { "bge" };
char noteswin[] = { "Cbagfedc" };

int beats[] = { 1, 1 };
int beatsding[] = { 1, 1, 1 };
int beatswin[] = { 1, 1, 1, 1, 1, 1, 1, 1 };

int tempo = 150;


//For level 2
int number1 = 10;
int number2 = 10;
int number3 = 10;
int number4 = 10;

bool wongame2;


#include <Keypad.h>

const byte ROWS = 4;  //four rows
const byte COLS = 3;  //four columns
//define the symbols on the buttons of the keypads

char hexaKeys[ROWS][COLS] = {
  { '1', '2', '3' },
  { '4', '5', '6' },
  { '7', '8', '9' },
  { '*', '0', '#' }
};
byte rowPins[ROWS] = { 7, 6, 13, A3 };  //connect to the row pinouts of the keypad
byte colPins[COLS] = { A2, A1, A0 };    //connect to the column pinouts of the keypad

//initialize an instance of class NewKeypad
Keypad customKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);

int level2start = 0;

int click1 = 10;
int click2 = 10;
int click3 = 10;
int click4 = 10;

bool bclick1;
bool bclick2;
bool bclick3;
bool bclick4;



void setup() {
  Wire.begin();
  //for level 1
  Serial.begin(9600);
  randomSeed(analogRead(7));

  for (int i = 0; i < 4; i++) {
    pinMode(ledPin[i], OUTPUT);
  }
  for (int i = 0; i < 4; i++) {
    pinMode(buttonPin[i], INPUT);
  }
}



void loop() {
  if (wongame2 == true) {
    //ADIBULS GAME

    // Write a charatre to the Slave
    Wire.beginTransmission(SLAVE_ADDR);
    Wire.write(5);
    Wire.endTransmission();
  
  }

  if (winlevelthree == true) {
    if (wongame2 == false) {
      game2();
      return (0);
    }
  }

  if (winlevelone == false) {
    gamestart1();
  }
  levelone();

  if (winlevelone == true) {
    gamestart2();
  }
  leveltwo();

  if (winleveltwo == true) {
    gamestart3();
  }
  levelthree();
}


void gamestart1() {
  randNumber = random(0, 4);
  //Serial.println(randNumber);
  random1 = randNumber;
  digitalWrite(ledPin[randNumber], HIGH);
  delay(400);
  digitalWrite(ledPin[randNumber], LOW);
  delay(200);
}


void levelone() {
  while (digitalRead(buttonPin[random1]) == HIGH) {
    waiting();
  }
  if (digitalRead(buttonPin[random1]) == LOW) {
    digitalWrite(ledPin[random1], HIGH);
    noise();
    delay(300);
    digitalWrite(ledPin[random1], LOW);
    winning();
    winlevelone = true;
  }
}


void gamestart2() {

  for (int i = 0; i < 3; i++) {
    randNumber = random(0, 4);
    //Serial.println(randNumber);
    if (i == 0) {
      random1 = randNumber;
    } else if (i == 1) {
      random2 = randNumber;
    } else {
      random3 = randNumber;
    }

    digitalWrite(ledPin[randNumber], HIGH);
    delay(400);
    digitalWrite(ledPin[randNumber], LOW);
    delay(200);
  }
}


void leveltwo() {

  while (digitalRead(buttonPin[random1]) == HIGH) {
    waiting();
  }

  if (digitalRead(buttonPin[random1]) == LOW) {
    digitalWrite(ledPin[random1], HIGH);
    noise();
    delay(300);
    digitalWrite(ledPin[random1], LOW);

    while (digitalRead(buttonPin[random2]) == HIGH) {
      waiting();
    }

    if (digitalRead(buttonPin[random2]) == LOW) {
      digitalWrite(ledPin[random2], HIGH);
      noise();
      delay(300);
      digitalWrite(ledPin[random2], LOW);

      while (digitalRead(buttonPin[random3]) == HIGH) {
        waiting();
      }

      if (digitalRead(buttonPin[random3]) == LOW) {
        digitalWrite(ledPin[random3], HIGH);
        noise();
        delay(300);
        digitalWrite(ledPin[random3], LOW);
        winning();
        winleveltwo = true;
      }
    }
  }
}


void gamestart3() {

  for (int i = 0; i < 5; i++) {
    randNumber = random(0, 4);
    //Serial.println(randNumber);
    if (i == 0) {
      random1 = randNumber;
    } else if (i == 1) {
      random2 = randNumber;
    } else if (i == 2) {
      random3 = randNumber;
    } else if (i == 3) {
      random4 = randNumber;
    } else {
      random5 = randNumber;
    }

    digitalWrite(ledPin[randNumber], HIGH);
    delay(400);
    digitalWrite(ledPin[randNumber], LOW);
    delay(200);
  }
}


void levelthree() {


  while (digitalRead(buttonPin[random1]) == HIGH) {
    waiting();
  }

  if (digitalRead(buttonPin[random1]) == LOW) {
    digitalWrite(ledPin[random1], HIGH);
    noise();
    delay(300);
    digitalWrite(ledPin[random1], LOW);

    while (digitalRead(buttonPin[random2]) == HIGH) {
      waiting();
    }

    if (digitalRead(buttonPin[random2]) == LOW) {
      digitalWrite(ledPin[random2], HIGH);
      noise();
      delay(300);
      digitalWrite(ledPin[random2], LOW);

      while (digitalRead(buttonPin[random3]) == HIGH) {
        waiting();
      }

      if (digitalRead(buttonPin[random3]) == LOW) {
        digitalWrite(ledPin[random3], HIGH);
        noise();
        delay(300);
        digitalWrite(ledPin[random3], LOW);

        while (digitalRead(buttonPin[random4]) == HIGH) {
          waiting();
        }

        if (digitalRead(buttonPin[random4]) == LOW) {
          digitalWrite(ledPin[random4], HIGH);
          noise();
          delay(300);
          digitalWrite(ledPin[random4], LOW);

          while (digitalRead(buttonPin[random5]) == HIGH) {
            waiting();
          }

          if (digitalRead(buttonPin[random5]) == LOW) {
            digitalWrite(ledPin[random5], HIGH);
            noise();
            delay(300);
            digitalWrite(ledPin[random5], LOW);
            winning2();
            winlevelthree = true;
          }
        }
      }
    }
  }
}


void waiting() {
  if (digitalRead(buttonPin[0]) == LOW) {
    digitalWrite(ledPin[0], HIGH);
  } else {
    digitalWrite(ledPin[0], LOW);
  }

  if (digitalRead(buttonPin[1]) == LOW) {
    digitalWrite(ledPin[1], HIGH);
  } else {
    digitalWrite(ledPin[1], LOW);
  }

  if (digitalRead(buttonPin[2]) == LOW) {
    digitalWrite(ledPin[2], HIGH);
  } else {
    digitalWrite(ledPin[2], LOW);
  }

  if (digitalRead(buttonPin[3]) == LOW) {
    digitalWrite(ledPin[3], HIGH);
  } else {
    digitalWrite(ledPin[3], LOW);
  }
}


void winning() {
  noiseding();
  for (int i = 0; i < 4; i++) {
    digitalWrite(ledPin[i], HIGH);
    delay(100);
  }
  delay(300);
  digitalWrite(ledPin[0], LOW);
  digitalWrite(ledPin[1], LOW);
  digitalWrite(ledPin[2], LOW);
  digitalWrite(ledPin[3], LOW);
  delay(300);
}


void winning2() {
  noisewin();
  noisewin();
  for (int i = 0; i < 3; i++) {
    for (int i = 0; i < 4; i++) {
      digitalWrite(ledPin[i], HIGH);
      delay(100);
    }
    for (int j = 0; j < 4; j++) {
      digitalWrite(ledPin[j], LOW);
      delay(100);
    }
  }
}


void playNote(char notewin, int durationwin) {
  char nameswin[] = { 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C' };
  int toneswin[] = { 1915, 1700, 1519, 1432, 1275, 1136, 1014, 956 };
  for (int i = 0; i < 8; i++) {
    if (nameswin[i] == notewin) {
      tone(speakerPin, toneswin[i], durationwin);
    }
  }
}


void noise() {
  for (int i = 0; i < length; i++) {
    if (notes[i] == ' ') {
      delay(beats[i] * tempo);
    } else {
      playNote(notes[i], beats[i] * tempo);
    }
    delay(tempo / 2);
  }
}

void noiseding() {
  for (int i = 0; i < lengthding; i++) {
    if (notesding[i] == ' ') {
      delay(beatsding[i] * tempo);
    } else {
      playNote(notesding[i], beatsding[i] * tempo);
    }
    delay(tempo / 2);
  }
}


void noisewin() {
  for (int i = 0; i < lengthwin; i++) {
    if (noteswin[i] == ' ') {
      delay(beatswin[i] * tempo);
    } else {
      playNote(noteswin[i], beatswin[i] * tempo);
    }
    delay(tempo / 2);
  }
}



void game2() {
  if (level2start == 0) {
    for (int i = 0; i < 4; i++) {
      if (number1 < 10) {
        if (number2 < 10) {
          if (number3 < 10) {
            if (number4 < 10) {
              winning();
            }
          }
        }
      }

      int num1 = random(5, 10);
      int num2 = random(1, 5);
      int num3 = random(1, 10);
      int answer = (num1 - num2) * num3;
      Serial.print("(");
      Serial.print(num1);
      Serial.print("-");
      Serial.print(num2);
      Serial.print(")");
      Serial.print("*");
      Serial.println(num3);

      if (i == 0) {
        number1 = answer % 10;
      }
      if (i == 1) {
        number2 = answer % 10;
      }
      if (i == 2) {
        number3 = answer % 10;
      }
      if (i == 3) {
        number4 = answer % 10;
      }

      //Serial.println(answer);
      // Serial.println(number1);
      // Serial.println(number2);
      // Serial.println(number3);
      // Serial.println(number4);
      delay(4000);
    }
    level2start = 1;
  }
  wingame2();
}


void wingame2() {
  level2start = 1;
  if (bclick1 == true) {
    if (bclick2 == true) {
      if (bclick3 == true) {
        if (bclick4 == true) {
          passwordenter();
        }
      }
    }
  }

  char customKey = customKeypad.getKey();
  if (customKey) {
    if (click1 == 10) {
      bclick1 = true;
      Serial.print("Pin 1 is = ");
      Serial.println(customKey);
      click1 = customKey;
      return (0);
    }
  }
  if (customKey) {
    if (click2 == 10) {
      bclick2 = true;
      Serial.print("Pin 2 is = ");
      Serial.println(customKey);
      click2 = customKey;
      return (0);
    }
  }
  if (customKey) {
    if (click3 == 10) {
      bclick3 = true;
      Serial.print("Pin 3 is = ");
      Serial.println(customKey);
      click3 = customKey;
      return (0);
    }
  }
  if (customKey) {
    if (click4 == 10) {
      bclick4 = true;
      Serial.print("Pin 4 is = ");
      Serial.println(customKey);
      click4 = customKey;
      return (0);
    }
  }
}

void passwordenter() {
  if (click1 - 48 == number1) {
    if (click2 - 48 == number2) {
      if (click3 - 48 == number3) {
        if (click4 - 48 == number4) {
          Serial.println("YAY YOU WIN");
          wongame2 = true;
          delay(2000);
        } else {
          Serial.println("TRY AGAIN PLEASE");
          bclick1 = false;
          bclick2 = false;
          bclick3 = false;
          bclick4 = false;
          click1 = 10;
          click2 = 10;
          click3 = 10;
          click4 = 10;
        }
      } else {
        Serial.println("TRY AGAIN PLEASE");
        bclick1 = false;
        bclick2 = false;
        bclick3 = false;
        bclick4 = false;
        click1 = 10;
        click2 = 10;
        click3 = 10;
        click4 = 10;
      }
    } else {
      Serial.println("TRY AGAIN PLEASE");
      bclick1 = false;
      bclick2 = false;
      bclick3 = false;
      bclick4 = false;
      click1 = 10;
      click2 = 10;
      click3 = 10;
      click4 = 10;
    }
  } else {
    Serial.println("TRY AGAIN PLEASE");
    bclick1 = false;
    bclick2 = false;
    bclick3 = false;
    bclick4 = false;
    click1 = 10;
    click2 = 10;
    click3 = 10;
    click4 = 10;
  }
}

Level 3

// Include Arduino Wire library for I2C
#include <Wire.h>

// Define Slave I2C Address
#define SLAVE_ADDR 9

int rd;

int ledPin = 3;
int ledPin2 = 4;
int answerTones[3];
int tones[3];
int speakerPin1 = 9;
int speakerPin2 = 10;
int length = 3;        // the number of notes
char notes[] = "cde";  // a space represents a rest
int beats[] = {4,4,1};
int tempo = 800;
int sensorPins[] = {A3,A2,A1};                    // select the input pins for the potentiometers
int sensorValues[3];  // variables to store the value coming from the sensor

void setup() {
  // Initialize I2C communications as Slave
  Wire.begin(SLAVE_ADDR);
  
  // Function to run when data received from master
  Wire.onReceive(receiveEvent);
  
  randomSeed(analogRead(8));
  Serial.begin(9600);
  pinMode(speakerPin1, OUTPUT);
  pinMode(speakerPin2, OUTPUT);
  pinMode(ledPin, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  for (int i = 0; i < length; i++) {
    tones[i] =  random(0,1000);
  }
}

void loop() {
  // read the value from the sensor:
Serial.println(rd);
 if (rd == 5){
    for (int i = 0; i < length; i++) {
      answerTones[i] = map(analogRead(sensorPins[i]), 0, 1023, 0, 1000);
      
    }
    // Serial.println(answerTones[0]);
    // Serial.println(answerTones[1]);
    // Serial.println(answerTones[2]);
    playMelody();

    delay(3000);
    playMelody2();

    delay(3000);

    if (answerTones[0] < tones[0]-25 || answerTones[0] > tones[0]+25) {
      Serial.println("Error: Unexpected sensor reading for tone 0");
    }
    if ((answerTones[0] > tones[0]-25) && (answerTones[0] < tones[0]+25)) {
      Serial.println("expected sensor reading for tone 0");
      if ((answerTones[1] > tones[1]-25) && (answerTones[1] < tones[1]+25)) {
        Serial.println("expected sensor reading for tone 1");
        if ((answerTones[2] > tones[2]-25) && (answerTones[2] < tones[2]+25)) {
          Serial.println("YOU WIN!!");
          for (int i = 0; i < 7; i++){
            wingame();
          }
          delay(2000000000000);
        }
      }
    }
 }
}

void playNote(char note, int duration) {
  char names[] = { 'c', 'd', 'e' };

  // play the tone corresponding to the note name
  for (int i = 0; i < 3; i++) {
    if (names[i] == note) {
      tone(speakerPin1, tones[i], duration);
    }
  }
}

void playMelody() {
  digitalWrite(ledPin, LOW);
  digitalWrite(ledPin2, HIGH);
  for (int i = 0; i < length; i++) {
    // Serial.println(tones[i]);
    if (notes[i] == ' ') {
      delay(beats[i] * tempo);  // rest
    } else {
      playNote(notes[i], beats[i] * tempo);
    }
    Serial.println(tones[i]);
    // pause between notes
    delay(tempo / 2);
  }
}


void playNote2(char note, int duration) {
  char names[] = { 'c', 'd', 'e' };
  int tones[] = { answerTones[0], answerTones[1], answerTones[2]};
  //play the tone corresponding to the note name
  for (int i = 0; i < 3; i++) {
    if (names[i] == note) {
      tone(speakerPin1, tones[i], duration);
    }
  }
}

void playMelody2() {
  digitalWrite(ledPin, HIGH);
  digitalWrite(ledPin2, LOW);
  for (int i = 0; i < length; i++) {
    if (notes[i] == ' ') {
      delay(beats[i] * tempo);  // rest
    } else {
      playNote2(notes[i], beats[i] * tempo);
    }
    Serial.println(answerTones[i]);

    // pause between notes
    delay(tempo / 2);
  }
}

void wingame() { 
  digitalWrite(ledPin, HIGH);
  digitalWrite(ledPin2, HIGH);
  delay(250);
  digitalWrite(ledPin,LOW);
  digitalWrite(ledPin2, LOW);
  delay(250);
  digitalWrite(ledPin, HIGH);
  digitalWrite(ledPin2, HIGH);
  delay(250);
  digitalWrite(ledPin,LOW);
  digitalWrite(ledPin2, LOW);
  delay(250);
  digitalWrite(ledPin, HIGH);
  digitalWrite(ledPin2, HIGH);
  delay(250);
  digitalWrite(ledPin,LOW);
  digitalWrite(ledPin2, LOW);
  delay(250);
  digitalWrite(ledPin, HIGH);
  digitalWrite(ledPin2, HIGH);
}


void receiveEvent() {
  // read one character from the I2C
  rd = Wire.read();
  // Print value of incoming data
  Serial.println(rd);
    
}

2023 Group 2 – KEITH Plant Monitor


dESIGN BLOG

November 13, 2023

Today we made our final decision on what our first and back up choices are, “Monitoring your plants” and “Your Own Talking and loving Teddy Bear” respectively. We also did some brainstorming of some ideas for each project:

I also emailed Laurissa to confirm our group members and our first and backup choices. In the evening I received an email confirmation from Laurissa that the “Monitoring your plants” project is ours to work on.
Atanas

November 17, 2023

After it was confirmed that we can start working on the plant monitor project, I came up with a starting idea that we could try and prototype on Monday during the lab. 

A single container that would contain the Arduino board, the sensors/actuators, the water container and pump and that would have a round space in the middle for the user to insert their plastic pot and plant. We will also need to decide how to power Keith, our plant monitor, with a USB cable, AC adapter or batteries.
Atanas

November 20, 2023

During today’s lab, we started to create prototypes based on my starting design to see how it might function and look. I researched the components we will need. We need to add them to our order today so we can get them early and start putting them together.
A list of what we need:

  • an OLED screen
  • a humidity sensor
  • an easier to use potentiometer and button than the one provided with the Arduino kit
  • Velcro tape
  • plant
  • a large selection of wires – male-to-male, male-to-female, female-to-female that come with enough red and black wires for connecting voltage and ground
  • a light sensor(reuse from Arduino kit)
  • soil moisture sensor
  • water pump
  • relay
  • water container
  • tubing
  • resistors (reuse from Arduino kit)

While Bryle was researching for parts and components, Stanley and I started working on prototypes. 

Prototype of the rough physical appearance of the plant monitor:

This gave us an idea of what each part of the casing would be dedicated to. And how the user would interact with the monitor.

OLED screen user interface prototype:

We wanted to provide the users of our monitor to be able to get in depth analysis of the their plant’s immediate surrounding area so they can take better care of it. Different bars on the screen would represent the temperature, light, humidity and soil moisture. Each bar would also have indicators of what the current value of that bar is and what is the optimal condition of the plant. So if there wasn’t enough light in the room, the user would have the move their plant pot and monitor somewhere else. Or if the humidity was really low they would get a humidifier or give their plant a few spritzes with a mister.

The bars would also be accompanied by a flower character that would provide additional indication of how the plant is doing.

We also had the idea that the plant monitor could be adapted by the user so they can take care of any plant with it, by adjusting the optimal levels for each bar through a potentiometer and a button. 

The user would use the potentiometer to go to the setting levels option and press confirm with the button. Then they would scroll through each bar which would be indicated with an icon displayed over the flower character. Pressing the button again would confirm their choice and they will be able to set the relevant bar to the required level and once again press the button to confirm their choice.

After the prototypes were we all collectively started researching components and by the end of the day had found the majority we required and had them ordered. The most difficult one to decide on was the OLED screen because there were so many options, but it was difficult to decide which would work with our Arduino and which library they would require. We also had to consider the number of pixels we would need, as the bigger screens that we originally thought we would use, it turned out would eat up most of our budget as they cost over £40-£50.

Atanas

November 27, 2023

Today, we received our components that we had ordered for Keith, so I tested them to make sure that they worked and how they would work.

First thing I tested was the light sensor.

The light sensor used one analog pin to work, outputting a value representing the ambient light level. It has a LDR soldered on at the end of the circuit board to read the light level.

The temperature and humidity sensor needed one digital pin along with the two pins for voltage and ground to work. Using the DHT11 library for the Arduino, I was able to get readings for both the humidity and temperature.

These two sensors will be recording the ambient factors surrounding the plant, so they needed to be exposed to correctly record values.

The next sensor I tested was the soil moisture sensor.

This came with the components for the irrigation system (water pump, relay etc). This didn’t need any libraries so I could use standard Arduino code to get readings from the sensor. The readings for this sensor worked where the drier the soil, the higher the reading which would be important to keep in mid when coding. It uses one analog pin for reading the value into the code.

This sensor would be placed in the soil of our plant to get its readings.

The next component I tested was the OLED screen. It has a 128×64 resolution and is 2.42 inches.

The Driver IC (Intergrated Circuit Chip) in the OLED screen is 1309, so I used the ‘u8g2’ library to run code that would display images on the screen. The image above is the standard coding test of outputting ‘Hello World’ using example code that came with the library. Using an example code file called ‘Graphics Test’, I tested what kind of shapes the screen could output. Below is a bitmap example from the code.

The screen required 7 pins to work. 5 pins were needed for the screen to work, along with the power and ground wires.

Bryle

28 November, 2023

Following some of the component testing from yesterday, Stanley and Atanas began building the circuit in order to get a feel for how the components would need to be wired together on the Arduino. While they did this, I tested the relay and water pump to make sure that they worked since they were essential for the irrigation system of the project.

Top Left: Relay
Bottom Right: Water Pump

The relay was used in conjunction with the water pump and would be activated using code with digitalWrite, going between high and low. The relay uses a digital pin in order to receive the high or low signal to run the pump. For the irrigation system, we’ll be using the reading from the soil moisture sensor in an if statement to check whether the plant needs water.

Bryle

1 December, 2023

Today, I began coding the program we would be using for the final product.

I started with making sure that all readings that I need from the sensors can be read in. For the temperature and humidity sensor, I used the DHT Sensor library by Adafruit to read the values. Even though only one digital pin is used to connect the sensor to the Arduino, the library allows for the humidity and temperature to be pulled separately and assigns to different variables. The variables would be defined at the top of the program, outside of setup, and then the value would be taken in every time the code in the loop section is run, so that the readings are constantly refreshed.

For the light sensor and the soil moisture sensor, I didn’t need to install any libraries so I used standard Arduino code. Both these sensors used one analog pin each to take a reading. Same as the temperature and humidity, they were defined at the top of the program and then constantly refresh the reading in the loop section.

For the soil moisture sensor, I did have to write a function in order to read a value. This was because the sensor requires power to take a reading so the function essentially sends a high digital signal to power the sensor on, then takes a reading and assigns it to a variable, and then sends a low digital signal to power the sensor off. It returns the reading from the sensor.

Bryle

3 December, 2023

Today me and Stanley tested the pump and relay system and ran into an issue where the relay LEDs were turning on, but the pump was not doing anything. We ordered a new pump and relay with next day delivery to make sure this is sorted out as soon as possible.

Atanas

4 December, 2023

I continued coding the program while Stanley and Atanas begin assemblying the final product.

Stanley and Atanas used this final concept for the product.

Stanley needed to do some work on the watering can pot to fit in the water pump’s pipes so that it goes through the spout of the watering can.

Stanley and Atanas tested the new pump and relays that arrived today and thankfully they were both working successfully. They were then able to add the pump into the watering can to ensure that it works, being successful in doing so.

Regarding the code, I was able to add the if statement block, responsible for checking the soil moisture and activating the pump when the moisture is too low.

Bryle

8 December, 2023

Today, the assembly of the product involving the pots and Arduino was finished.

The product will run using a battery, ensuring that it doesn’t require a mains plug to work, meaning it isn’t restricted to where it can be placed.

Assembly Finished
10 December, 2023

Today, the coding aspect of the project was wrapped up.

The screen feedback was done today.

The first part of the screen feedback was a show of the current plant factors. Graphics were used to create bars that represent the current state of each factor that needs to be observed when taking of plants. The bars representing the current reading of each factor was done by mapping the readings into a certain range that would allow it to be displayed correctly on the screen. There was also text added below to ensure that the user knew which bar represented which factor.

Plant Factors

The second part of the screen feedback is a flower representing a general summary of the plant’s current status. It has three states:

  • Full Flower: A flower with all of its petals, representing that all the factors for the plant are optimal.
  • Decaying Flower: A flower that has lost some of its petals, representing that one or two of the factors are not optimal.
  • Wilted Flower: A flower that has lost all of its petals, representing that all three factors are not optimal for the plant.
Decayed Flower state

These states only depended on three of the factors (temperature, humidity and light) as the irrigation system handled the soil moisture.

The state changing based on the three factors was done using booleans that would check whether the factor’s reading was within a certain range, setting the boolean to true, otherwise being false if it is outside said range. It would then check all three booleans to determine the state of the plant and what is shown to the user.

I also changed the library used for the humidity and temperature sensor, from the adafruit one we were using to the official Arduino one, EduIntro. The Adafruit library we were using stopped working with a sensor so that is what necessitated the change.

Bryle

11 December, 2023

Before the presentation, we uploaded the code into the Arduino and inserted it into the finished product. We did some final testing, making sure everything was working as intended for the demonstration. We also finalised the slides within our group, ready for the presentation.

Below is our final product.

Finished Keith Care

Bryle

project task list

Task List

Group Members: Atanas Ilinov, Bryle Inandan, Stanley Parker

TaskMember/s
Choose Two Briefs – First Choice, Backup ChoiceEveryone
Confirm Group Members, First Choice, Backup ChoiceAtanas
BrainstormingEveryone
Initial Design ConceptsAtanas, Stanley
Lo-fi PrototypingAtanas, Stanley
Researching Circuitry ComponentsEveryone
Ordering Components and MaterialsEveryone
Testing ComponentsBryle
Assembling CircuitAtanas, Stanley
Modifying/Building CasingStanley
CodingBryle
AssemblyAtanas, Stanley
Testing and Bug-fixingEveryone
Create PresentationEveryone
BlogEveryone

Project Plan

maker manual

Overview

Are you tired of forgetting to water your plants? Tired of them being too hot or too cold? Tired of wilting due to a lack of light? Well, we have a solution for you, and his name is KEITH!

KEITH is a must have for any houseplant parent, he gives you critical information about your plant, including the temperature, humidity and light level of it’s surroundings, he even keeps track of the soil moisture content and waters it for you!

KEITH is the perfect caretaker for all of your green, leafy children, simply hook his moisture sensor up to the soil and adjust his watering hose to the position you need it in and let him get to work! KEITH‘s small module is non-intrusive and can be placed in most areas of your home with ease.

KEITH is perfect for casual hobbyists or more experienced plant owners due to the variety of information he can give you about your plant, and he’s so easy to set up, once he’s hooked up and you’ve filled his reservoir he’ll begin watering your plant whenever it needs it.

KEITH isn’t like those other “self-watering” plant pots you see that rely solely on the plant’s ability absorb water, KEITH will keep your plant watered no matter the conditions, even if your plant is struggling KEITH will be right there with it helping you nurture and care for it.

Tools and Supplies

Tools
Supplies
  • Textual Breakdown of Components:
    • Arduino UNO
    • Miniature submersible water pump
    • Arduino 1 channel 5V relay module
    • Velcro Tape
    • 2.42 inch 128X64 OLED Display Module IIC I2C Serial Peripheral Interface
    • 8.6mm PVC pipe
    • M-F, M-M and F-F cables
    • Capacitive Soil Moisture Sensor
    • DHT11 3 Pin Module
    • Epoxy Putty
    • Ceramic Pot + Watering Can
    • Arduino Light Sensor Module
    • 9V battery
    • 9V battery clip with 2.1*5mm Male Plug
    • Fake Grass

Layout and Circuit Diagrams

Breadboard Layout Diagram

Unfortunately the software we used to make the breadboard diagram (Fritzing) was a little outdated, so instead of a water pump image I had to use a motor image. They also did not have a photoresistor module, so we just implemented a regular photoresistor. This is also reflected in the circuit diagram below.

Circuit Diagram

Relay here is treated as a relay module. The diagram above only shows how the module itself is connected to the circuit. Some of the pins on some of the components have also been rearranged for better readability.

How to Build

Code

// libraries
#include <EduIntro.h>
#include <Arduino.h>
#include <U8g2lib.h>
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

// dht11 library code for temperature and humidity sensor
DHT11 dht11(D3);

// soil moisture and light pins
const int soilMoistRead = A0;
const int soilMoistPower = 4;
const int lightPin = A1;

// relay connected to pin 2, HIGH will pump water out
int pumpPin = 2;

// potentiometer to scroll between screens
const int scrollPin = A2;

// variables for plant needs
float soilMoist;
int humidity;
int temperature;
float currentLight;

// booleans for flower face
bool optimalTemp = true;
bool optimalHumid = true;
bool optimalLight = true;

// pins for screen
// GND = Ground, VCC = 3/5V, SDL/Clock = 13, SDA/Data = 11, RES/Reset = 8, DC = 9, CS = 10

// constructor for screen
U8G2_SSD1309_128X64_NONAME2_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);

// used to change between the flower faces and statistics
uint8_t page = 0;

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

       // power on the screen and prepare everything
       u8g2.begin();
       u8g2_prepare();

       // assign pin modes
       pinMode(soilMoistPower, OUTPUT);
       pinMode(pumpPin, OUTPUT);

       // default state of pump - off
      digitalWrite(soilMoistPower, LOW);
} //end setup

void loop() {
      // Serial.println();

      // updates the temperature and humidity reading every loop for updated reading
      dht11.update();

      humidity = dht11.readHumidity();
      // Serial.print("Humidity (%): ");
      // Serial.println(humidity);

      temperature = dht11.readCelsius();
      // Serial.print("Temperature (celsius): ");
      // Serial.println(temperature);

      // sensor value closer to 0 the more wet it is
      soilMoist = readSensor();
      // Serial.print("Soil Moisture: ");
      // Serial.println(soilMoist);

      currentLight = analogRead(lightPin);
     // Serial.print("Light Level: ");
      // Serial.println(currentLight);

      // drawing the plant stats and current status
      u8g2.clearBuffer();
      plantStatus();
      u8g2.sendBuffer();

      // checks for optimal conditions
      // soil moisture not included as irrigation system is automatic
      if(temperature >= 21 && temperature <= 30){
          optimalTemp = true;
      } else{
          optimalTemp = false;
      } // end if else

      if(humidity >= 40 && humidity <= 70){
          optimalHumid = true;
      } else{
          optimalHumid = false;
      } // end if else

      if(currentLight >= 40 && currentLight <= 70){
          optimalLight = true;
      } else{
          optimalLight = false;
      } // end if else

      // runs pump after checking the soil moisture
      waterPlant(soilMoist);

      // automatically flip between the two pages of flower stats
      page++;

      if(page > 1){
          page = 0;
      } // end if

      delay(2000);
} // end loop

// soil moisture reading
int readSensor(){
     
digitalWrite(soilMoistPower, HIGH);
      delay(10);
      int val = analogRead(soilMoistRead);
      digitalWrite(soilMoistPower, LOW);
     
return val;
} // end readSensor

// sensor value closer to 0 the more wet it is
// checks water and activates pump when low on water
void waterPlant(float moisture){
      if(moisture > 400){
          digitalWrite(pumpPin, HIGH);
      } else {
          digitalWrite(pumpPin, LOW);
      }
} // end waterPlant

// prepare screen to be used
void u8g2_prepare() {
      u8g2.setFont(u8g2_font_5x8_tf);
      u8g2.setFontRefHeightExtendedText();
      u8g2.setDrawColor(1);
      u8g2.setFontPosTop();
      u8g2.setFontDirection(0);
} // end function

// draw status bar of each plant need
void plantStatusBar(){
      int mapSoilMoist = map(soilMoist, 350, 600, 0, 40);
      int mapHumid = map(humidity, 20, 90, 0, 40);
      int mapTemp = map(temperature, 0, 50, 0, 40);
      int mapLight = map(currentLight, 1023, 0, 0, 40);

      u8g2.drawStr(10, 50, "Water");
      u8g2.drawBox(19, 8+(40-mapSoilMoist), 8, mapSoilMoist); // map values into range of 0 - 40
      u8g2.drawFrame(17, 6, 12, 44);
      u8g2.drawStr(37, 50, "Humid");
      u8g2.drawBox(45, 8+(40-mapHumid), 8, mapHumid);
      u8g2.drawFrame(43, 6, 12, 44);
      u8g2.drawStr(65, 50, "Temp");
      u8g2.drawBox(70, 8+(40-mapTemp), 8, mapTemp);
      u8g2.drawFrame(68, 6, 12, 44);
      u8g2.drawStr(88, 50, "Light");
      u8g2.drawBox(95, 8+(40-mapLight), 8, mapLight);
      u8g2.drawFrame(93, 6, 12, 44);
} // end plantStatusBar

void flowerFace(){
      if(!optimalTemp || !optimalHumid || !optimalLight){
          if(!optimalTemp && !optimalHumid && !optimalLight){
              // wilted flower
              u8g2.drawStr(35, 0, "Wilted Flower");
              u8g2.drawStr(30, 10, "Stats no good...");
              u8g2.drawDisc(64, 40, 9); // main body
          } else {
              // slightly decayed floweru8g2.drawStr(30, 0, "Decayed Flower");
              u8g2.drawStr(35, 10, "Check stats!");
              u8g2.drawDisc(64, 40, 9); // main body
              u8g2.drawCircle(54, 40, 5); // left petal
              // u8g2.drawCircle(74, 40, 5); // right petal
              // u8g2.drawCircle(64, 50, 5); // bottom petal
              u8g2.drawCircle(64, 30, 5); // top petal
              // u8g2.drawCircle(58, 45, 5); // bottom left petal
              u8g2.drawCircle(70, 45, 5); // bottom right petal
              u8g2.drawCircle(58, 35, 5); // top left petal
              // u8g2.drawCircle(70, 35, 5); // top right petal
          }
      } else {
          // full flower
          u8g2.drawStr(35, 0, "Full Flower");
          u8g2.drawStr(30, 10, "All stats good!");
          u8g2.drawDisc(64, 40, 9); // main body
          u8g2.drawCircle(54, 40, 5); // left petal
          u8g2.drawCircle(74, 40, 5); // right petal
          u8g2.drawCircle(64, 50, 5); // bottom petal
          u8g2.drawCircle(64, 30, 5); // top petal
          u8g2.drawCircle(58, 45, 5); // bottom left petal
          u8g2.drawCircle(70, 45, 5); // bottom right petal
          u8g2.drawCircle(58, 35, 5); // top left petal
          u8g2.drawCircle(70, 35, 5); // top right petal
      } // end if else
} // end flowerFace

void plantStatus(){
      u8g2_prepare();

      switch(page){
          case 0: plantStatusBar(); break;
          case 1: flowerFace(); break;
      }
} // end plantStatus

Testing and Shortcomings

Testing

We Tested KEITH’s water pump and soil moisture reading mapping with several different threshold values to try and make sure the range we selected was realistic for the home environment in order to optimise the display to make it more readable for the user as well as to optimise the amount of water KEITH pumps to the plant as there is slight latency in the system.
We rigorously tested all our sensors with both expected and extreme values in order to make sure they were all working well, in the process we discovered some sensors were faulty and replaced them.

Shortcomings

Our aim with KEITH was to make a household friendly plant caretaker. Unfortunately due to time limitations we didn’t get to implement a manual varying of thresholds to allow the user to fine tune KEITH and instead we set more general thresholds which will be suitable for most houseplants but may not be suitable for more exotic or unusual varieties.
KEITH is not weatherproof and is only suitable for indoor use.
KEITH is also limited on his modularity, he is only suitable for one plant at any given time due to how the system is set up.