Khansa's Enchanted Sketchbook

Made by Langley Vogt, Swapnil Amritkar and Ussama Naveed

Found in DIoT - Augmented Objects · UNLISTED (SHOWN IN POOLS)

Khansa's Enchanted Sketchbook connects her with her husband across the world through short, endearing audio messages that play out of her glowing sketchbook.

0

Problem Statement

With the advent of different voice and video calling apps, we are able to easily connect with each other. However, these applications and platforms (i.e. texts or video call) don’t necessarily help communicate emotions genuinely. For example, one may want to tell their significant other or close friend who is away an endearing message like “I love you”, or “I miss you, hope your day is going well”. Texting or video calling may not translate the message to be as special as it was intended. This is where our Enchanted Book helps communicate short, endearing messages to one's significant other or close friends/family in such a way that lifts the receiver's spirits. 

This was the problem faced by one of our users interviewed. Khansa is currently located in Pakistan, separated from her husband who is studying at Carnegie Mellon University. When interviewed about home-objects that she would want to be enchanted (connected to the internet to display ambient information), she was drawn to her sketchbook. Both her and her husband sketch daily. With a simple object she uses frequently, enchanting this could help the distance seem less. Our problem statement was as follows:

How might we enchant Khansa's notebook to connect with her significant other abroad?

0

Goal

We plan to enchant this everyday object, a sketchbook, to give it an additional ability to send and receive meaningful, short, endearing messages when she interacts with it on the norm, while still maintaining its basic functionality. We also wanted to emphasize the ambience of the device for Khansa so it wouldn't be obtrusive or distracting, but rather catch her eye when there was a message.

A list of key functionalities:

  • Receive a message and the book glows
  • Play message
  • Save message
  • Record and send message
  • Interchange any book into this sleeve housing
0

Storyboard

Below depicts how Khansa would use her enchanted book as she goes about her day, along with the interaction with her husband.

0

Process


First Iteration

 The first iteration of this enchanted book was prototyped with cardboard for form and had the basic functionalities working: Glowing when a message was received and playing a tone from a piezometer when the book was opened. This prototype helped us understand how the circuit and the form would come together and also provided a lot of opportunities for improvement. 

Below are pictures of the first prototyped circuit (Note: the DFPlayer Mini does not work in this iteration, the piezometer takes its place)

0

Final Iteration

In this iteration, we incorporated classmates feedback and tailored it more towards Khansa instead of marketing it to anyone. It was a big jump from our initial prototype. We added additional features we believed were necessary to really make this enchanted book as usable as possible where it gets the full experience across.

We added:

  • Red button for recording
  • Green button to send the recorded message
  • Yellow button for saving the message that uses IFTTT to save to the Google Drive
  • Blue button for playing the message
  • DFPlayer Mini plays an audio message through speaker
  • Polished the form to fit Khansa
0

Components

Main Circuit:

(All features)

  • 1 large breadboard
  • Particle Argon
  • x5 1k resistors
  • x4 LEDs (red, green, yellow, blue)
  • x4 Push Buttons  (red, green, yellow, blue)
  • Flex Sensor
  • WS2812 Neopixel  (7 Pixel Count)

Recording and Sending Circuit

(Record and send features)

  • 1 large breadboard
  • Particle Argon
  • x2 1k resistors
  • x2 LEDs (red, green)
  • x2 Push Buttons (red, green)

Speaker Circuit

(Play the audio message when called)

  • 1 large breadboard
  • Particle Argon
  • DFPlayer Mini
  • MicroSD Card, Kingston 64 GB (from Amazon)
  • 1 small speaker that could be powered by 3.3V


0
Whatsapp image 2019 11 25 at 8.39.21 pm %282%29 Primary Circuit
0
20191126 171633 Recording and Sending Circuit
0
20191126 171650 Speaker Circuit
0

Outcome

We successfully enchanted Khansa's sketchbook to glow when she receives a message from her husband, with the ability to save the message, and then proceed to record and send a message. The following demonstration shows the functionality of the three circuits together and each of the buttons in the book.

0
0

Code

Provided is the Fritzing diagram that would hypothetically be used for both of the books, where these two circuits would be in the back cover of the book and have the record, send, save and play features. The three codes that were used to demonstrate this idea are provided below, as well. 

0
Circuit Fritzing Circuit Diagram
0
Primary Circuit
//Created by Langley V, Swapnil A, and Ussama N on 11/17/2019
//Creative Project II

//Last edited: 11-26-2019

// This #include statement was automatically added by the Particle IDE.
#include <DFPlayer.h>

DFPlayer dfPlayer;


int folder = 0;
int volume = 0;
bool powerOn = false;

//********************************************************************
//Neopixel
//********************************************************************

// This #include statement was automatically added by the Particle IDE.
#include <neopixel.h>

//Set pixel pin, number of pins, and type
#define PIXEL_PIN D2
#define PIXEL_COUNT 7
#define PIXEL_TYPE WS2812

const uint8_t KEYFRAMES[]  = {
  // Rising
  20, 21, 22, 24, 26, 28, 31, 34, 38, 41, 45, 50, 55, 60, 66, 73, 80, 87, 95,
  103, 112, 121, 131, 141, 151, 161, 172, 182, 192, 202, 211, 220, 228, 236,
  242, 247, 251, 254, 255,

  // Falling
  254, 251, 247, 242, 236, 228, 220, 211, 202, 192, 182, 172, 161, 151, 141,
  131, 121, 112, 103, 95, 87, 80, 73, 66, 60, 55, 50, 45, 41, 38, 34, 31, 28,
  26, 24, 22, 21, 20,
  20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 
};

int keyframePointer = 0;
int numKeyframes = sizeof(KEYFRAMES) - 1;

//define neopixel strip
Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);

//Global variable delcaration
//int timeDuration;

//Declare booleans that go along with the message received from the sender
bool displayNotif = false; //calendar event boolean
bool isRunning = false;
long timeStartedAt = -1; 
int timeElapsed = -1;


//Define colorOff: 0,0,0
uint32_t colorOff = strip.Color( 0, 0, 0 );

//********************************************************************
//Publishing Events
//********************************************************************
// This value will store the last time we published an event
long lastPublishedAt = 0;

int last_published = -1;


//********************************************************************
//Flex Sensor
//********************************************************************
int flexPin = A0;
int flexReading = 0;

//********************************************************************
//LED indicator
//********************************************************************
//int ledPin = A1;

int RedPin = D5; //To identify the state of the RED Button
int GreenPin = D3; //To identify the state of the GREEN Button
int YellowPin = D4; //To identify the state of the YELLOW Button
int BluePin = D6; //To identify the state of the BLUE Button

int RedButton = A1; //For recording the Message
int GreenButton = A2; //For sending the Message
int YellowButton = A3; //For saving the Message
int BlueButton = A4; //For listening to the Message

int publishAfter = 10000;

//High  = 1;
int RedButtonState = 0;
int GreenButtonState = 0;
int YellowButtonState = 0;
int BlueButtonState = 0;

bool messageExists = false;
int PreviousRBS = 0;
bool messageHeared = false;

int here = 0;


void setup() {
    
    //another event starts, stop current event and then reset timer, then start over.
    Particle.function("startNotif", startNotif);
    Particle.function("endNotif", endNotif);

    //Code initialization
    strip.begin();
    strip.show();
    //strip.setBrightness(30); //dim lights
    
    //get time particle io started at
    timeStartedAt = millis(); 

    //initial color, Yellow glow
    uint32_t c = strip.Color(0, 255, 255);

    //reset all the colors
    for (int i = 0; i < strip.numPixels(); i++) {
        strip.setPixelColor(i, c);
        delay(100);
    }
    strip.show();
    
    for (int i = 0; i < strip.numPixels(); i++) {
        strip.setPixelColor(i, colorOff);
        delay(100);
    }
    strip.show();
    
//********************************************************************
    
    //Declare pin modes for flex, speaker and LED
    pinMode(flexPin, INPUT);

    pinMode( RedButton , INPUT_PULLUP ); // sets pin as input
    pinMode( GreenButton , INPUT_PULLUP); // sets pin as input
    pinMode( YellowButton , INPUT_PULLUP); // sets pin as input
    pinMode( BlueButton , INPUT_PULLUP); // sets pin as input
    
    pinMode( RedPin , OUTPUT ); // sets pin as output
    pinMode( GreenPin , OUTPUT ); // sets pin as output
    pinMode( YellowPin , OUTPUT ); // sets pin as output
    pinMode( BluePin , OUTPUT ); // sets pin as output
    
    dfPlayer.setLogging(true);
    volume = dfPlayer.getVolume();
    
    delay(5000);

    Particle.variable("force", flexReading);
    Particle.subscribe("swapnil/2019/enchantedBook/" , handleSharedEvent);
    Particle.subscribe("langley/2019/iot/enchantedBookMessage/" , handleSharedEventSpeaker);

    Particle.variable("RedState", RedButtonState);
    Particle.variable("PreviousRBS", PreviousRBS);
    Particle.variable("GreenState", GreenButtonState);
    Particle.variable("BlueButtonState", BlueButtonState);
    Particle.variable("Here", here);
    
    
    Particle.variable("force", flexReading);
    Particle.variable("folder", folder);
    Particle.variable("volume", volume);
    Particle.variable("power", powerOn);
    Particle.function("setVolume", setVolume);
    Particle.function("playFolder", playFolder);
    Particle.function("setPower", setPower);
  
    setVolume("20");
    setPower("true");
    folder = 1;
    delay(200);
    
    //dfPlayer.playTrack(1);
    PlayMessage();
    delay(1000);
    //playFolder(String(folder));

}

void loop() {
    
    //Get flex sensor reading 
    flexReading = analogRead(flexPin);
    //delay(100);

    RedButtonState = digitalRead( RedButton );
    GreenButtonState = digitalRead( GreenButton );
    YellowButtonState = digitalRead( YellowButton );
    BlueButtonState = digitalRead( BlueButton );

    //Recording the Message
    
    if( PreviousRBS == HIGH && RedButtonState == LOW)
    {
        //Start recording 
        digitalWrite( RedPin, HIGH); // turn the LED On
        PreviousRBS = RedButtonState;
        messageExists = true;
    }
    else if(PreviousRBS == LOW && RedButtonState == HIGH)
    {
        //Stop recording 
        digitalWrite( RedPin, LOW); // turn the LED Off
    }
    PreviousRBS = RedButtonState;
    
    //Sending the Message
    if( GreenButtonState == LOW && messageExists == true )
    {
        digitalWrite(GreenPin, HIGH);
        delay(100);
        digitalWrite(GreenPin, LOW);
        
        SendMessage();
        delay(2000);
        // delay for a bit
        messageExists = false;

    }
    else
    {
        digitalWrite( GreenPin, LOW);
    }
    

    
    //If there is a message waiting, go into this loop
    if(displayNotif) {
        
        //CASE 1: GLOW
        //If there is a message and the flex sensor is bent (book is closed)
        if(flexReading > 700) {
        
            for (int i = 0; i < strip.numPixels(); i++) {
                uint8_t color = (127 * KEYFRAMES[keyframePointer]) / 256;
                strip.setPixelColor(i, 0, 0, color);
            }
            strip.show();   
            
            // Increment the keyframe pointer.
            if (++keyframePointer > numKeyframes) {
              // Reset to 0 after the last keyframe.
              keyframePointer = 0;
            }   
   
        }
        
        //CASE 2: PLAY MESSAGE 
        //There is a notification and the book is opened
        else if(flexReading < 700) {
            
            //LED glows because there is a message
            digitalWrite( BluePin, HIGH);

            //delay(1000);
            
            // delay(100);
            
            // PlayMessage();
            
            // delay(4000);
            
            // digitalWrite( BluePin, LOW);

            //Play message
            if(BlueButtonState == LOW )
            {
                Particle.publish("bluebutton", "low-prev-high");
                here = 1;
                PlayMessage();
                
                delay(100);
                
                displayNotif = false;
                
                messageHeared = true;
                
                digitalWrite( BluePin, LOW);


            }


 
            //Set notification back to false, message has been heard
            //displayNotif = false;
            isRunning = false;

            for (int i = 0; i < strip.numPixels(); i++) {
                strip.setPixelColor(i, 0, 0, 0);
            }
            strip.show();   

            
            //digitalWrite( BluePin, LOW);

        }
        
        else {
            //do nothing
            //return;
        }
    }
    
    // NO message, do nothing
    else {
        //do nothing
    }
    
       //Save the Message
    if(YellowButtonState == LOW && messageHeared == true)
    {
        // store the time when you last published
        int last_published = -1;
        
        digitalWrite(YellowPin, HIGH);
        delay(500);
        
        SaveMessage();
        
        messageHeared = false;
       
    }
    else
    {
        digitalWrite( YellowPin, LOW);
    }
    
    delay(500);
}

//Particle Functions that change boolean value to true or false
//For now, have console "send the message" to this particle board that there is a message
//Next steps: Use another particle board to publish an event and use this particle board to receive it
bool startNotif(String val) {
    
    //if it is not running then start it
    if (!isRunning) {
        //timeStartedAt = millis(); //reset time 
        displayNotif = true;
        isRunning = true;

        //goes into loop and runs light code
    }
    //A calendar event ambient notification is already running
    else {
        //Stop what is happening 
        //turn all off 
        for (int i = 0; i < strip.numPixels(); i++) {
            strip.setPixelColor(i, colorOff);
            delay(50);
        }
        strip.show();
    }
    
}

//End the message notification
bool endNotif(String val) {

    //turn all off 
    for (int i = 0; i < strip.numPixels(); i++) {
        strip.setPixelColor(i, colorOff);
        delay(50);
    }
    strip.show();
        
    //no ambient notification
    displayNotif = false;
    //code is not running
    isRunning = false;
    
}

void SendMessage()
{
  // Remember that a device can publish at rate of about 1 event/sec,
  // with bursts of up to 4 allowed in 1 second.
  // Back to back burst of 4 messages will take 4 seconds to recover.
  // So we want to limit the amount of publish events that happen.

  // check that it's been 10 secondds since our last publish
  if( lastPublishedAt + publishAfter < millis() )
  {
      // Remember our subscribe is matching  "db2018/paired/"
      // We'll append the device id to get more specific
      // about where the event came from

      // System.deviceID() provides an easy way to extract the device
      // ID of your device. It returns a String object of the device ID,
      // which is used to identify your device.

      String eventName = "langley/2019/iot/sendenchantedBookMessage/" + System.deviceID();

      // now we have something like "diot/2019/paired/0123456789abcdef"
      // and that corresponds to this devices info

      // then we share it out
      Particle.publish( eventName, "Message_Sent" );

      // And this will get shared out to all devices using this code

      // we just pubished so capture this.
      lastPublishedAt = millis();
  }

}

//Notify other person 
void ReceivedMessage()
{
  // Remember that a device can publish at rate of about 1 event/sec,
  // with bursts of up to 4 allowed in 1 second.
  // Back to back burst of 4 messages will take 4 seconds to recover.
  // So we want to limit the amount of publish events that happen.

  // check that it's been 10 secondds since our last publish
  if( lastPublishedAt + publishAfter < millis() )
  {
      // Remember our subscribe is matching  "db2018/paired/"
      // We'll append the device id to get more specific
      // about where the event came from

      // System.deviceID() provides an easy way to extract the device
      // ID of your device. It returns a String object of the device ID,
      // which is used to identify your device.

      String eventName = "langley/2019/iot/receivedenchantedBookMessage/" + System.deviceID();

      // now we have something like "diot/2019/paired/0123456789abcdef"
      // and that corresponds to this devices info

      // then we share it out
      Particle.publish( eventName, "Message_Sent" );

      // And this will get shared out to all devices using this code

      // we just pubished so capture this.
      lastPublishedAt = millis();
  }

}


//Write data to spreadsheet
void SaveMessage() {

  // check if 1 minute has elapsed
	if( last_published + 10000 < millis() ){
		Particle.publish( "messageLogged", String("Hi-I-love-youuuu") );
		last_published = millis();
	}
}

//
int playFolder(String buffer) {
    folder = atoi(buffer);
    
    // the following commands play 6 tracks that were stored in the root folder of the TFCard
    // the dfPlayer.playTrack method uses DFPlayer command 0x03
    // The tracks were named 001 - 006.
    // The tracks were added to the TFCard in the following sequence 001, 002, 003, 005, 004, and 006
    // This code plays the tracks in order 001 - 006
    delay(3000);
    dfPlayer.playTrack(1);
    delay(3000);
    dfPlayer.playTrack(2);
    delay(7000);

    return 0;
}

void PlayMessage()
{
  // Remember that a device can publish at rate of about 1 event/sec,
  // with bursts of up to 4 allowed in 1 second.
  // Back to back burst of 4 messages will take 4 seconds to recover.
  // So we want to limit the amount of publish events that happen.

  // check that it's been 10 second  since our last publish
  if( lastPublishedAt + publishAfter < millis() )
  {
      // Remember our subscribe is matching  "db2018/paired/"
      // We'll append the device id to get more specific
      // about where the event came from

      // System.deviceID() provides an easy way to extract the device
      // ID of your device. It returns a String object of the device ID,
      // which is used to identify your device.

      String eventName = "langley/2019/iot/playMessage/" + System.deviceID();

      // now we have something like "diot/2019/paired/0123456789abcdef"
      // and that corresponds to this devices info

      // then we share it out
      Particle.publish( eventName, "Message_Sent" );

      // And this will get shared out to all devices using this code

      // we just pubished so capture this.
      lastPublishedAt = millis();
  }

}


void handleSharedEvent(const char *event, const char *data)
{
    // Now we're getting ALL events published using "db2018/paired/"
    // This includes events from this device.
    // So we need to ignore any events that we sent.

    // Let's check the event name
    String eventName = String( event ); // convert to a string object
    // This gives us access to a bunch of built in methods
    // Like indexOf()
    // Locates a character or String within another String.
    // By default, searches from the beginning of the String,
    // but can also start from a given index,
    // allowing for the locating of all instances of the character or String.
    // It Returns: The index of val within the String, or -1 if not found.

    // We can use this to check if our event name contains the
    // id of this device

    String deviceID = System.deviceID();

    // device id = 0123456789abcdef
    // event = "diot/2019/paired/0123456789abcdef"

    if( eventName.indexOf( deviceID ) != -1 ){
      // if we get anything other than -1
      // the event came from this device.
      // so stop doing stuff
      return;
    }

    displayNotif = true;
    delay(1000);
    
    ReceivedMessage();
}


void handleSharedEventSpeaker(const char *event, const char *data)
{
    // Now we're getting ALL events published using "db2018/paired/"
    // This includes events from this device.
    // So we need to ignore any events that we sent.

    // Let's check the event name
    String eventName = String( event ); // convert to a string object
    // This gives us access to a bunch of built in methods
    // Like indexOf()
    // Locates a character or String within another String.
    // By default, searches from the beginning of the String,
    // but can also start from a given index,
    // allowing for the locating of all instances of the character or String.
    // It Returns: The index of val within the String, or -1 if not found.

    // We can use this to check if our event name contains the
    // id of this device

    String deviceID = System.deviceID();

    // device id = 0123456789abcdef
    // event = "diot/2019/paired/0123456789abcdef"

    if( eventName.indexOf( deviceID ) != -1 ){
      // if we get anything other than -1
      // the event came from this device.
      // so stop doing stuff
      return;
    }

    digitalWrite(GreenPin, HIGH);
    delay(200);
    digitalWrite(GreenPin, LOW);
    delay(200);
    
}


// setVolume is a Particle method that can be used to adjust volume via the Particle Console
int setVolume(String buffer) {
    volume = atoi(buffer);
    dfPlayer.setVolume(volume);
    return 0;
}

// setPower is a Particle method that that plays a gong when power is toggled false, and chirping birds when power is toggned true 
bool setPower(String buffer) {
    if ((buffer[0] == 'f') && (powerOn == true))  {
        powerOn = false;
        dfPlayer.playFolderTrack(1,2);
    }
    if ((buffer[0] == 't') && (powerOn == false)) {
        powerOn = true;
        dfPlayer.playFolderTrack(1,1);
    }
    return powerOn;
}
Click to Expand
0
Recording and Sending Circuit
//For Receiving and Sending Circuit

int RedPin = D4; //To identify the state of the RED Button
int GreenPin = D5; //To identify the state of the GREEN Button

int RedButton = A5; //For recording the Message
int GreenButton = A0; //For sending the Message

long lastPublishedAt = 0; //The last time an event was published
int publishAfter = 10000;// Time delay before publishing a new event

//Initial state of the button
int RedButtonState=1;
int GreenButtonState=1;

int PreviousRBS = 1;

bool MessageExists = false;
   
void setup()
{
    // Set Button pins as input
    pinMode( RedButton , INPUT_PULLUP ); 
    pinMode( GreenButton , INPUT_PULLUP); 

    Particle.variable("Message",RedButtonState); //to verify the state of the red button used for recording message
    
    
    // Set LED pins as output
    pinMode( RedPin , OUTPUT ); 
    pinMode( GreenPin , OUTPUT ); 
    
    //Subscribe to Langley's event to get feedback if my published event has been received
    Particle.subscribe(  "langley/2019/iot/receivedenchantedBookMessage/" , handleSharedEvent );
}

void loop()
{
    //Read the state of the button
    RedButtonState = digitalRead( RedButton );
    GreenButtonState = digitalRead( GreenButton );
    
    //Recording the Message
    if( PreviousRBS == HIGH && RedButtonState == LOW)
    {
        //Start recording 
        digitalWrite( RedPin, HIGH); // turn the LED On
        //message = "This is a Sample Message"; //Sample text to simulate voice recording
        MessageExists = true;
    }
    else if(PreviousRBS == LOW && RedButtonState == HIGH)
    {
        //Stop recording 
        digitalWrite( RedPin, LOW); // turn the LED Off
    }
    
    //Sending the Message
    if( GreenButtonState == LOW && MessageExists == true)
    {
        publishMyEvent(); 
        delay(100);
        digitalWrite(GreenPin, HIGH);
        MessageExists = false;
    }
    else
    {
        digitalWrite(GreenPin, LOW);
    }
    
    //saving the message
    
    PreviousRBS = RedButtonState; 
}


void publishMyEvent()
{
  // Remember that a device can publish at rate of about 1 event/sec,
  // with bursts of up to 4 allowed in 1 second.
  // Back to back burst of 4 messages will take 4 seconds to recover.
  // So we want to limit the amount of publish events that happen.

  // check that it's been 10 secondds since our last publish
  if( lastPublishedAt + publishAfter < millis() )
  {
      // Remember our subscribe is matching  "db2018/paired/"
      // We'll append the device id to get more specific
      // about where the event came from

      // System.deviceID() provides an easy way to extract the device
      // ID of your device. It returns a String object of the device ID,
      // which is used to identify your device.

      String eventName = "swapnil/2019/enchantedBook/" + System.deviceID();

      // now we have something like "diot/2019/paired/0123456789abcdef"
      // and that corresponds to this devices info

      // then we share it out
      Particle.publish( eventName, "Swapnil_Message" );

      // And this will get shared out to all devices using this code

      // we just pubished so capture this.
      lastPublishedAt = millis();
  }

}

// Our event handlde requires two bits of information
// This gives us:
// A character array that consists of the event name
// A character array that contains the data published in the event we're responding to.
void handleSharedEvent(const char *event, const char *data)
{
    // Now we're getting ALL events published using "db2018/paired/"
    // This includes events from this device.
    // So we need to ignore any events that we sent.

    // Let's check the event name
    String eventName = String( event ); // convert to a string object
    // This gives us access to a bunch of built in methods
    // Like indexOf()
    // Locates a character or String within another String.
    // By default, searches from the beginning of the String,
    // but can also start from a given index,
    // allowing for the locating of all instances of the character or String.
    // It Returns: The index of val within the String, or -1 if not found.

    // We can use this to check if our event name contains the
    // id of this device

    String deviceID = System.deviceID();

    // device id = 0123456789abcdef
    // event = "diot/2019/paired/0123456789abcdef"

    if( eventName.indexOf( deviceID ) != -1 ){
      // if we get anything other than -1
      // the event came from this device.
      // so stop doing stuff
      return;
    }

    // otherwise do your stuff to respond to
    // the paired device here

    digitalWrite( GreenPin, HIGH);
    delay(500);
    digitalWrite( GreenPin, LOW); 
    delay(500);
    digitalWrite( GreenPin, HIGH);
    delay(500);
    digitalWrite( GreenPin, LOW);
    delay(500);
}
Click to Expand
0
Speaker Circuit
//For DFPlayer ONLY
//Plays audio file on it

#include "DFPlayer.h"

    // global variables
DFPlayer dfPlayer;

int folder = 0;
int volume = 0;
bool powerOn = false;

bool playMessage = false;


// This value will store the last time we published an event
long lastPublishedAt = 0;
// this is the time delay before we should publish a new event
// from this device
int publishAfter = 10000;

void setup() {
    Particle.subscribe("langley/2019/iot/playMessage/" , handleSharedEvent );

    Particle.variable("playMessage", playMessage);
    Particle.variable("folder", folder);
    Particle.variable("volume", volume);
    Particle.variable("power", powerOn);
    Particle.function("setVolume", setVolume);
    Particle.function("playFolder", playFolder);
    Particle.function("setPower", setPower);
    
    Serial.begin(115200);
    Serial1.begin(9600);
    delay(5000);


    // the following command tells the program to display data on the serial console.
    // logging can be turned on and off at will throughout the user's code
    dfPlayer.setLogging(true);
    volume = dfPlayer.getVolume();
    
    setVolume("20");
    setPower("true");
    folder = 1;
    delay(200);
    //playFolder(String(folder));

    delay(1000);

    dfPlayer.playTrack(1);
    
}

void loop() {

    if( playMessage == true ){
            // otherwise do your stuff to respond to
        // the paired device here
        //playFolder(String(folder));
        dfPlayer.playTrack(1);
        publishMyEvent();
        
        playMessage = false;
        delay( 5000 );
    }
    
}
// playFolder is a Particle Function ... that can be run from the Particle Console ..

int playFolder(String buffer) {
    folder = atoi(buffer);
    
    // the following commands play 6 tracks that were stored in the root folder of the TFCard
    // the dfPlayer.playTrack method uses DFPlayer command 0x03
    // The tracks were named 001 - 006.
    // The tracks were added to the TFCard in the following sequence 001, 002, 003, 005, 004, and 006
    // This code plays the tracks in order 001 - 006
    delay(3000);
    dfPlayer.playTrack(1);
    delay(3000);

    return 0;
}

// setVolume is a Particle method that can be used to adjust volume via the Particle Console
int setVolume(String buffer) {
    volume = atoi(buffer);
    dfPlayer.setVolume(volume);
    return 0;
}

// setPower is a Particle method that that plays a gong when power is toggled false, and chirping birds when power is toggned true 
bool setPower(String buffer) {
    if ((buffer[0] == 'f') && (powerOn == true))  {
        powerOn = false;
        dfPlayer.playFolderTrack(1,2);
    }
    if ((buffer[0] == 't') && (powerOn == false)) {
        powerOn = true;
        dfPlayer.playFolderTrack(1,1);
    }
    return powerOn;
}




// Our event handlde requires two bits of information
// This gives us:
// A character array that consists of the event name
// A character array that contains the data published in the event we're responding to.
void handleSharedEvent(const char *event, const char *data)
{
    // Now we're getting ALL events published using "db2018/paired/"
    // This includes events from this device.
    // So we need to ignore any events that we sent.

    // Let's check the event name
    String eventName = String( event ); // convert to a string object
    // This gives us access to a bunch of built in methods
    // Like indexOf()
    // Locates a character or String within another String.
    // By default, searches from the beginning of the String,
    // but can also start from a given index,
    // allowing for the locating of all instances of the character or String.
    // It Returns: The index of val within the String, or -1 if not found.

    // We can use this to check if our event name contains the
    // id of this device

    String deviceID = System.deviceID();

    // device id = 0123456789abcdef
    // event = "diot/2019/paired/0123456789abcdef"

    if( eventName.indexOf( deviceID ) != -1 ){
      // if we get anything other than -1
      // the event came from this device.
      // so stop doing stuff
      return;
    }
    
    
    playMessage = true;

    
}


void publishMyEvent()
{
  // Remember that a device can publish at rate of about 1 event/sec,
  // with bursts of up to 4 allowed in 1 second.
  // Back to back burst of 4 messages will take 4 seconds to recover.
  // So we want to limit the amount of publish events that happen.

  // check that it's been 10 secondds since our last publish
  if( lastPublishedAt + publishAfter < millis() )
  {
      // Remember our subscribe is matching  "db2018/paired/"
      // We'll append the device id to get more specific
      // about where the event came from

      // System.deviceID() provides an easy way to extract the device
      // ID of your device. It returns a String object of the device ID,
      // which is used to identify your device.

      String eventName = "langley/2019/iot/enchantedBookMessage/" + System.deviceID();

      // now we have something like "diot/2019/paired/0123456789abcdef"
      // and that corresponds to this devices info

      // then we share it out
      Particle.publish( eventName, "From_Langley" );

      // And this will get shared out to all devices using this code

      // we just pubished so capture this.
      lastPublishedAt = millis();
  }

}
Click to Expand
0

Next Steps

The next steps and future scope would include the following:

  • Record, send, save and retrieve audio files using an external server instead of using a DFPlayer Mini and pre-recorded audio to demonstrate audio
  • Make two enchanted book prototypes
  • Reduce the size of the book
  • Look into connectivity over the internet to encrypt and transmit audio messages


0

Reflection

There were many challenges that arose in the code. 

Firstly, the DFPlayer would not play the single audio track on the microSD card, even though everything was wired correctly for an Particle Argon (Note - there is code for a DFPlayer, but it is either for Arduino or a Particle Photon). It turns out that we were using a faulty breadboard that did not connect the speaker correctly to the Argon. We also discovered that using only the 3.3V power supply on the Argon would not power the DFPlayer, Neopixels, LED's and pushbuttons. Hence, the use of three separate circuits that communicated through Particle.publish events. Later on, the speaker itself had very weak connections (however, no more small speakers were available for replacements), and only worked in certain positions.

 Secondly, we attempted to code the logic for the case that if the book was already open and the user receives a message. We ran into multiple issues trying to use previous and current state and a time duration variable and it taking 10 seconds per if-statement to decide what to do for each loop. We overcame this issue by having the book owner press the blue button when they want to hear the message.

Conclusions: Our team was able to complete a working prototype with three Particle boards communicating with each other seamlessly. We are satisfied with what we accomplished. We were able to share a video demonstration with Khansa herself, and she really loved what we put together. 

0

Resources:

None of the original resources worked for the DFPlayer Mini. We relied on a customized diagram provided by our professor that is incorporated into the Fritzing diagrams above.

Tutorials on: https://diotlabs.daraghbyrne.me/  


Good reference anyways - For a Particle Photon Board and using no libraries

https://community.particle.io/t/a-great-very-cheap-mp3-sound-module-without-need-for-a-library/20111/4  

x
Share this Project

This project is only listed in this pool. Be considerate and think twice before sharing.


Courses

49713 Designing for the Internet of Things

· 16 members

A hands-on introductory course exploring the Internet of Things and connected product experiences.


Focused on
About

Khansa's Enchanted Sketchbook connects her with her husband across the world through short, endearing audio messages that play out of her glowing sketchbook.

Created

November 25th, 2019