An ambient device thats sits on your night stand and creates a soothing environment to help you fall asleep as soon as you hit the bed
An ambient device that sits on your nightstand and creates a soothing environment to help you fall asleep as soon as you hit the bed. 60 million people in the United States suffer from insomnia and have difficulty falling asleep, including myself. Most of us search for tips and tricks which will alleviate the anxiety. I came up with a nighttime routine which worked but was lengthy and required effort. The routine involves deep breathing exercises, listening to music and drinking chamomile tea (I find the aroma extremely soothing).
On most days, I come back home quite late and don’t have the energy or time to go through the routine and end up sleeping poorly. Slumber was the solution to my night anxiety problem, with Pillo as it's sidekick!
Slumber and Pillo are 2 halves of an IoT ecosystem. Pillo acts as a sensor in your pillow or under your mattress which tracks your movement and relays information to Slumber about whether you are awake or asleep.
Pillo
Slumber
Lola gets back from a busy day of work, tired and drained. She's had a few nights of insomnia in a row and it's starting to have visible effects. She prepares a quick dinner for herself and skims through what's on her docket for the next day. She has an important meeting and needs her sleep but doesn't have the time or energy for her complete nighttime routine - which doesn't always work. She needs something, which at the touch of a button, will calm her and allow her to get a good night's sleep.
Main Flow
Alternate Flow
Through this project, I wanted to gain a nuanced understanding of how to create a good user experience using various outputs. A few of the consideration while designing were:
The following table is a list of the materials used in the project. Find a detailed list here.
This device will reside in a tiny box inside the pillow and use an accelerometer to track my movement on the bed. As I lie down, Pillo will sense my movements and actuate the Slumber device. Once I'm asleep and stop moving, Pillo will tell Slumber to turn off the various outputs. Through the night, Pillo is in listening mode and will continue to track my movements. If my movement passes a threshold, it will signal Slumber to start a subtle version of the various outputs.
/*
Project: Pillo
Discription: This device will create a soothing environment to help those that suffer from insomnia and night anxiety, to fall asleep.
Author: Ammani
Date: 5/12/2017
Version: 2.0
Sourced from: //https://anasdalintakam.blogspot.in/
ADXL335
note:vcc-->5v ,but ADXL335 Vs is 3.3V
The circuit:
5V: VCC
analog 1: x-axis
analog 2: y-axis
analog 3: z-axis
*/
//------ VARIABLES FOR THE INPUTS ------
const int xpin = A3; // x-axis of the accelerometer
const int ypin = A2; // y-axis
const int zpin = A1; // z-axis (only on 3-axis models)
const int numReadings = 10;
//------ VARIABLES FOR THE X-AXIS ------
int xreadings[numReadings]; // the readings from the analog input
int xreadIndex = 0; // the index of the current reading
int xtotal = 0; // the running total
int xaverage = 0; // the average
int readingX = 0;
int prevreadingX = 0;
int changeX = 0;
//------ VARIABLES FOR THE Y-AXIS ------
int yreadings[numReadings]; // the readings from the analog input
int yreadIndex = 0; // the index of the current reading
int ytotal = 0; // the running total
int yaverage = 0; // the average
int readingY = 0;
int prevreadingY = 0;
int changeY = 0;
//------ VARIABLES FOR THE Z-AXIS ------
int zreadings[numReadings]; // the readings from the analog input
int zreadIndex = 0; // the index of the current reading
int ztotal = 0; // the running total
int zaverage = 0; // the average
int readingZ = 0;
int prevreadingZ = 0;
int changeZ = 0;
//this code is for sensing ambient light using sparkfuns temt6000 ambient light sensor and arduino
int timeElapsed = -1;
void setup()
{
// initialize the serial communications:
Serial.begin(9600);
for (int thisReading = 0; thisReading < numReadings; thisReading++) {
xreadings[thisReading] = 0;
}
for (int thisReading = 0; thisReading < numReadings; thisReading++) {
yreadings[thisReading] = 0;
}
for (int thisReading = 0; thisReading < numReadings; thisReading++) {
zreadings[thisReading] = 0;
}
}
void loop()
{
int x = analogRead(xpin); //read from xpin
int y = analogRead(ypin); //read from ypin
int z = analogRead(zpin); //read from zpin
float zero_G = 512.0; //ADC is 0~1023 the zero g output equal to Vs/2
//ADXL335 power supply by Vs 3.3V
float scale = 102.3; //ADXL335330 Sensitivity is 330mv/g
//330 * 1024/3.3/1000
prevreadingX = readingX;
prevreadingY = readingY;
prevreadingZ = readingZ;
readingX = ((float)x - 331.5)/65*9.8;
readingY = ((float)y - 329.5)/68.5*9.8;
readingZ = ((float)z - 340)/68*9.8;
Serial.print( Time.timeStr());
Serial.print(",");
Serial.print(abs(xaverage)); //print x value on serial monitor
Serial.print(",");
Serial.print(abs(yaverage)); //print y value on serial monitor
Serial.print(",");
Serial.print(abs(zaverage)); //print z value on serial monitor
Serial.println("");
changeX = readingX - prevreadingX;
changeY = readingY - prevreadingY;
changeZ = readingZ - prevreadingZ;
//light = light_value * 0.0976;// percentage calculation
// x readings
xtotal = xtotal - xreadings[xreadIndex]; // subtract the last reading:
xreadings[xreadIndex] = changeX; // read from the sensor:
xtotal = xtotal + xreadings[xreadIndex]; // add the reading to the total:
xreadIndex = xreadIndex + 1; // advance to the next position in the array:
if (xreadIndex >= numReadings) { // if we're at the end of the array...
xreadIndex = 0; // ...wrap around to the beginning:
}
xaverage = xtotal / numReadings; // calculate the average:
// y readings
ytotal = ytotal - yreadings[yreadIndex]; // subtract the last reading:
yreadings[yreadIndex] = changeY; // read from the sensor:
ytotal = ytotal + yreadings[yreadIndex]; // add the reading to the total:
yreadIndex = yreadIndex + 1; // advance to the next position in the array:
if (yreadIndex >= numReadings) { // if we're at the end of the array...
yreadIndex = 0; // ...wrap around to the beginning:
}
yaverage = ytotal / numReadings; // calculate the average:
// z readings
ztotal = ztotal - zreadings[zreadIndex]; // subtract the last reading:
zreadings[zreadIndex] = changeZ; // read from the sensor:
ztotal = ztotal + zreadings[zreadIndex]; // add the reading to the total:
zreadIndex = zreadIndex + 1; // advance to the next position in the array:
if (zreadIndex >= numReadings) { // if we're at the end of the array...
zreadIndex = 0; // ...wrap around to the beginning:
}
zaverage = ztotal / numReadings; // calculate the average:
// Checks to see if the person is moving a lot and publishes an event
if (timeElapsed + 10000 < millis() )
{
if (abs(xaverage) > 5 or abs(yaverage) > 5 or abs(zaverage) > 5)
{
Particle.publish( "ammani/movement", "yes");
Serial.println("publishing");
delay(5000);
timeElapsed = millis();
}
}
delay(100);
}
Click to Expand
When activated, Slumber will play calming music, simulate a breathing exercise with a light pattern and fill the room with a soothing chamomile aroma. Slumber contains an on/off switch and is primarily activated by the Pillo. There is also a secondary sensor on the device itself, the PIR sensor, which will allow me to activate or deactivate the device by choice. By simply waving my hand over the top of the device, Slumber will get activated if it's in listening mode and vice-versa.
The led strip requires the neopixel library which can be found here. The neopixel.cpp and neopixel.h files need to be in the same folder as the main code file.
/*
Project: Slumber
Discription: This device will create a soothing environment to help those that suffer from insomnia and night anxiety, to fall asleep.
Author: Ammani
Date: 5/12/2017
Version: 2.0
*/
//------DEFINITIONS------
#include "neopixel.h"
#define PIXEL_PIN D0
#define PIXEL_COUNT 30
#define PIXEL_TYPE SK6812RGBW
// Currently, I think this is what is sent to the serial as output verification data. Haven't deleted it because I'm sure
// To understand more about this, look at DFplayer_basic_sample.ino in the audio folder
Adafruit_NeoPixel ring = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);
// These are used by the DF Player
# define Start_Byte 0x7E
# define Version_Byte 0xFF
# define Command_Length 0x06
# define End_Byte 0xEF
# define Acknowledge 0x00 //Returns info with command 0x41 [0x01: info, 0x00: no info]
// These definitions are the various states the device will run through
# define STATE_OFF 0
# define STATE_WELCOME 1 // When you switch on device
# define STATE_LISTENING 2 // When the device is monitering your movement and hand gesture
# define STATE_PRIMARY 3 // The state in which the device is actuating the various outputs for the first time or when it's consciously activated by the user
# define STATE_SECONDARY 4 // When the device detects you might be waking up and responds minimally to put you back to sleep
//------- VARIABLES, INPUTS & OUTPUTS -------
int onSwitch = A0; // Input for the button
int aromapin = D1; // Output to diffuser
int Command; // Input for the DF Player
int Parameter1; // Input for the DF Player
int Parameter2; // Input for the DF Player
int state = 0; //
int pirState = D5;
int pirSense = 0;
int head = 0;
int tail = -10; // Index of first 'on' and 'off' pixels
void setup()
{
Serial.begin(9600);
Serial1.begin(9600); // Input for the DF Player
pinMode(aromapin, OUTPUT);
pinMode(onSwitch, INPUT);
Particle.subscribe( "ammani/movement" , callingMovement );
pinMode(pirState, INPUT);
ring.begin();
ring.show();
Particle.variable("pir", &pirSense, INT);
delay(30);
execute_CMD(0x3F, 0, 0); //Initialises the MP3 player
execute_CMD(0x06, 0, 30); // Initialize sound to very low volume. Adapt according used speaker and wanted volume
execute_CMD(0x16, 0 , 1);
}
void loop()
{
// This command switches between all the states of the device
switch( state )
{ case STATE_OFF:
doOffState();
break;
case STATE_WELCOME:
Serial.println("Welcome State");
doWelcomeState();
break;
case STATE_LISTENING:
//Serial.println("Listening State");
doListeningState();
break;
case STATE_PRIMARY:
// Serial.println("Primary State");
doPrimaryState();
break;
case STATE_SECONDARY:
Serial.println("Secondary State");
doSecondaryState();
break;
}
}
// ------ VARIABLES USED FOR TIMING AND TO INITIATE STATES ------
long lastDebounceTime = 0;
long debounceDelay = 5000;
long lastMovementCalled = 0;
bool primaryStateOn = FALSE;
bool movement = FALSE;
long enteredWelcomeStateAt = -1;
bool secondaryStateOn = FALSE;
bool isPrimaryStatePlaying = false;
bool aromaOn = false;
long startedPlayingAt = -1;
int primaryStateDuration = 1000 * 30;
int aromaStartTime = -1;
int aromaTime = 1000 * 10;
bool isSecondaryStatePlaying = false;
long startedSecondaryPlayingAt = -1;
int secondaryStateDuration = 1000 * 30;
// ------ FUNCTIONS FOR ALL THE STATES ------
void doOffState(){
off();
execute_CMD(0x16, 0 ,1);
if (analogRead(onSwitch) < 100){
state = STATE_WELCOME;
}
}
void doWelcomeState()
{
if( enteredWelcomeStateAt == -1 ) enteredWelcomeStateAt = millis();
star();
if (analogRead(onSwitch) > 100){
off();
enteredWelcomeStateAt = -1;
lastDebounceTime = millis();
head = 0;
tail = -10;
state = STATE_OFF;
}
else if( enteredWelcomeStateAt + 8000 < millis() )
{
lastDebounceTime = millis();
enteredWelcomeStateAt = -1;
head = 0;
tail = -10;
changeColor(ring.Color(10,160,0,0));
Serial.println("listeningstate");
state = STATE_LISTENING;
}
}
void doListeningState()
{
pirSense = digitalRead(pirState);
if (analogRead(onSwitch) > 100){
off();
state = STATE_OFF;
}
else if (movement == TRUE )
{
movement == FALSE;
secondaryStateOn = TRUE;
state = STATE_SECONDARY;
}
else if ( lastDebounceTime + debounceDelay < millis())
{if (pirSense == 1)
{
lastDebounceTime = millis();
primaryStateOn = TRUE;
state = STATE_PRIMARY;
}
}
}
void doPrimaryState(){
pirSense = digitalRead(pirState);
Serial.println("primarystate");
Serial.println(millis()-aromaStartTime);
yellowcolor();
if (primaryStateOn == TRUE and isPrimaryStatePlaying == FALSE)
{
Serial.println("2nd loop");
startedPlayingAt = millis();
aromaStartTime = millis();
aromaOn = true;
execute_CMD(0x01,0,1); //Plays the next track in SD card once timer is started
digitalWrite(aromapin,HIGH);
isPrimaryStatePlaying = true;
Serial.println("aroma on");
}
if (aromaOn)
{
if (aromaStartTime + aromaTime < millis())
{
digitalWrite(aromapin, LOW);
aromaOn = false;
Serial.println("aroma off");
}
}
else
if ( aromaStartTime + 20000 < millis())
{
digitalWrite(aromapin,HIGH);
Serial.println("aroma on");
aromaStartTime = millis();
}
if( isPrimaryStatePlaying && startedPlayingAt + primaryStateDuration < millis() )
{
primaryStateOn = FALSE;
isPrimaryStatePlaying = FALSE;
changeColor(ring.Color(10,160,0,0));
Serial.println("3rdloop");
digitalWrite(aromapin, LOW);
Serial.println("aroma off");
Serial.println("listeningstate");
aromaStartTime = -1;
startedPlayingAt = -1;
state = STATE_LISTENING;
}
else
{
//return; //
}
if (analogRead(onSwitch) > 100){
off();
state = STATE_OFF;
}
else if (lastDebounceTime + debounceDelay < millis())
{
if (pirSense == 1)
{
Serial.println("The Pir break");
lastDebounceTime = millis();
execute_CMD(0x16,0,1);
primaryStateOn = FALSE;
isPrimaryStatePlaying = FALSE;
digitalWrite(aromapin, LOW);
Serial.println("aroma off");
changeColor(ring.Color(10,160,0,0));
state = STATE_LISTENING;
}
}
}
void doSecondaryState(){
pirSense = digitalRead(pirState);
Serial.println("secondary state");
Serial.println(millis()-aromaStartTime);
orangecolor();
if (secondaryStateOn == TRUE and isSecondaryStatePlaying == FALSE)
{
Serial.println("2nd loop");
startedSecondaryPlayingAt = millis();
aromaStartTime = millis();
aromaOn = true;
execute_CMD(0x01,0,1);
digitalWrite(aromapin,HIGH);
isSecondaryStatePlaying = true;
Serial.println("aroma on");
}
if (aromaOn)
{
if (aromaStartTime + aromaTime < millis())
{
digitalWrite(aromapin, LOW);
aromaOn = false;
Serial.println("aroma off");
}
}
else
if ( aromaStartTime + 20000 < millis())
{
digitalWrite(aromapin,HIGH);
Serial.println("aroma on");
aromaStartTime = millis();
}
if( isSecondaryStatePlaying && startedSecondaryPlayingAt + secondaryStateDuration < millis() )
{
secondaryStateOn = FALSE;
isSecondaryStatePlaying = FALSE;
changeColor(ring.Color(10,160,0,0));
Serial.println("3rdloop");
digitalWrite(aromapin, LOW);
Serial.println("aroma off");
Serial.println("listeningstate");
aromaStartTime = -1;
startedSecondaryPlayingAt = -1;
state = STATE_LISTENING;
}
else
{
//return; //
}
if (analogRead(onSwitch) > 100){
off();
state = STATE_OFF;
}
else if (lastDebounceTime + debounceDelay < millis())
{
if (pirSense == 1)
{
Serial.println("The Pir break");
lastDebounceTime = millis();
execute_CMD(0x16,0,1);
secondaryStateOn = FALSE;
isSecondaryStatePlaying = FALSE;
digitalWrite(aromapin, LOW);
Serial.println("aroma off");
changeColor(ring.Color(10,160,0,0));
state = STATE_LISTENING;
}
}
}
// ------ FUNCTION FOR THE DF PLAYER ------
void execute_CMD(byte CMD, byte Par1, byte Par2) // Excecute the command and parameters
{
// Calculate the checksum (2 bytes)
int16_t checksum = -(Version_Byte + Command_Length + CMD + Acknowledge + Par1 + Par2);
// Build the command line
byte Command_line[10] = { Start_Byte, Version_Byte, Command_Length, CMD, Acknowledge, Par1, Par2, checksum >> 8, checksum & 0xFF, End_Byte};
//Send the command line to the module
for (byte k=0; k<10; k++)
{
Serial1.write( Command_line[k]);
}
}
// ------ FUNCTIONS FOR THE LED STRIP ------
uint32_t color = 0x00ff08; // 'On' color (starts red)
void star(){
ring.setPixelColor(head, color); // 'On' pixel at head
ring.setPixelColor(tail, 0); // 'Off' pixel at tail
ring.show(); // Refresh strip
delay(5); // Pause 20 milliseconds (~50 FPS)
if(++head >= ring.numPixels()) { // Increment head index. Off end of strip?
head = 0; // Yes, reset head index to start
/*if((color >>= 8) == 0) // Next color (R->G->B) ... past blue now?
color = 0xFF0000; // Yes, reset to red*/
}
if(++tail >= ring.numPixels()) tail = 0; // Increment, reset tail index
}
void yellowcolor(){
for(int i = 0; i <= 10; i += 5) {
changeColor(ring.Color(i,i*13,0,0));
delay(50);
}
delay(500);
for(int i = 10; i >= 0; i -= 5) {
changeColor(ring.Color(i,i*13,0,0));
delay(50);
}
}
void orangecolor(){
for(int i = 0; i <= 10; i += 5) {
changeColor(ring.Color(i,i*10,0,0));
delay(50);
}
delay(500);
for(int i = 10; i >= 0; i -= 5) {
changeColor(ring.Color(i,i*10,0,0));
delay(50);
}
}
void off() {
changeColor(ring.Color(0, 0, 0, 0));
}
void changeColor(uint32_t color) {
for(uint16_t i=0; i < ring.numPixels(); i++) {
ring.setPixelColor(i, color);
ring.show();
}
}
// ------ FUNCTION TO RESPOND TO THE PILLOW SENSOR ------
void callingMovement(const char *event, const char *data)
{
Serial.println("Reached Calling Movement Handler");
if (data == "yes"){
moving();
}
}
void moving()
{
if ( lastMovementCalled + 20000 < millis() )
{
lastMovementCalled = millis();
movement = TRUE;
}
}
Click to Expand
The primary purpose of this eco-system is to track one's sleep and simulate a soothing environment. In the future, the device could also incorporate an alarm system. The alarm could be designed in such a way to calmly wake me up by slowly increasing the intensity of the light and playing music.
An ambient device thats sits on your night stand and creates a soothing environment to help you fall asleep as soon as you hit the bed
May 14th, 2017