49713 Designing for the Internet of Things
· 16 members
A hands-on introductory course exploring the Internet of Things and connected product experiences.
Found in DioT 2019: Connected Communities
We embarked to design for the CMU Playschool Community to augment the children's experience of play at the school playground through IoT connectivity and a sprinkle of magic!
Polly the playful playground is the playground that comes to life when children play with it. As more children interact with Polly, it gets happier and expresses itself by lighting up the entire playground. This project is meant to encourage children to play with each other. Polly’s main attraction is a path that circles the playground and lights up a path when stepped on. As more and more children interact with it, the entire path begins to light up with joy. In order to measure children’s interactions, an indicator consisting of a cartoon face was also made and connected to the playground, so that it can be placed anywhere. As more children play with Polly and with each other, the face gets happier.
Our Project focuses on the children’s playground at CMU. Although the playground is meant to allow children to play with each other and promote movement, many children were found to play alone and remain within one area for long periods of time. We decided to target the need for bringing children together and creating more movement around the playground.
We also want to create a more playful and interactive atmosphere around what children currently enjoy by having this environment respond and react to children based on how they use it. That way, the playground itself comes to life.
We were inspired by precedents that were playful, magical, and eye-catching. We wanted our design to exhibit those three features to attract children to our playground. There were three references that stood out to us as we were ideating and inspired our design:
1. Twinkly lights [1]
An installation that uses lights to lure people to a certain direction and change their behavior.
2. Hopscotch for Geniuses [2]
A playful and interactive take on hopscotch, which encourages children to learn about shapes.
3. The Wizarding World of Harry Potter (Universal Studios) [3]
An interactive wand that performs actions when waved.
We chose to design around the community of children instead of any single child. And so we began thinking of ways to bring all the children together. Currently, the children’s playground has a jungle gym, benches, a slide and a sandbox. But it’s most striking feature was a track that ran around the park. This track could be used by all and in it we saw our opportunity. Our Project is a magical track that dances, sparkles and twirls as children gather to play. As more children join, it synchronizes a brilliant light show, almost as if inviting more participation. At its crescendo the track lights up in a dazzling Supernova in celebration of all the children playing together. The amount of participation is displayed by a cartoon face that gets happier and happier as more children play with the track.
Based on our community and inspiration gathered from precedents, we generated some ideas that could be implemented in the playground such as a talking snowman, a swing that encourages sharing, and a responsive hopscotch floor.
We decided to expand on the hopscotch idea by creating an interactive and responsive floor. We settled on the use of lights as our form of interaction and response. By using FSRs, a neopixel light strip, and neopixel ring, we created a track that lights up when stepped on.
We encountered some challenges in scaling the design to a complete track as we could not use more than 5 FSR sensors. Thus, we decided to create a path with 5 FSR sensors that people can interact with. Additionally, we created a simulation of what it would look like when 3, 6, and 10 or more kids were playing on the track. Once 10 or more kids are playing, the track creates a light show in response to signal its excitement.
We also used a servo and separate particle device that connect to the playground track and respond as more children interact with the track. This particle and servo were used to create an indicator of how many kids are playing on the track.
// This is the code for the activity indicator
#include <Adafruit_PWMServoDriver.h>
// This #include statement was automatically added by the Particle IDE.
#include <Adafruit_PWMServoDriver.h>
// This #include statement was automatically added by the Particle IDE.
#include <Adafruit_PWMServoDriver.h>
int servoPin = A3;
Servo myServo;
int servoPos = 0;
void setup() {
// attaches the servo on the A3 pin to the servo object
myServo.attach( A3 );
// Keep a cloud variable for the current position
Particle.variable( "servoPos" , &servoPos , INT );
// Basically this will match any event that starts with 'db2018/paired/'
// This is a feature we'll useto figure out if our event comes from
// this device or another (see publishMyEvent below)
Particle.subscribe( "sixkids/2019/paired/" , sixKidsPublish);
Particle.subscribe( "threekids/2019/paired/" , threeKidsPublish );
Particle.subscribe( "tenkids/2019/paired/" , tenKidsPublish );
}
// 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 threeKidsPublish(const char *event, const char *data)
{
int newPoz = 40;
servoPos = constrain (newPoz, 0, 180);
// Set the servo
myServo.write( servoPos );
delay(10000);
servoPos = constrain (20, 0, 180);
}
void sixKidsPublish(const char *event, const char *data)
{
int newPoz = 105;
servoPos = constrain (newPoz, 0, 180);
// Set the servo
myServo.write( servoPos );
}
void tenKidsPublish(const char *event, const char *data)
{
int newPoz = 160;
servoPos = constrain (newPoz, 0, 180);
// Set the servo
myServo.write( servoPos );
}
Click to Expand
// This is the code for the playground
#include <neopixel.h>
// define pin for strip lights
#define PIXEL_PIN D3
#define PIXEL_COUNT 50
#define PIXEL_TYPE WS2811
//define pin for center neoxpixel
#define PIXEL_PIN2 D5
#define PIXEL_COUNT2 24
#define PIXEL_TYPE2 WS2812
// 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 = 5000;
//strip lights
Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);
//neopixel
Adafruit_NeoPixel strip2 = Adafruit_NeoPixel(PIXEL_COUNT2, PIXEL_PIN2, PIXEL_TYPE2);
//set strip lights to off
// initial color status variables
uint32_t e = strip.Color(0, 0, 0);
//for i
uint32_t c = strip.Color(0, 0, 0);
uint32_t d= strip.Color(0, 0, 0);
//for p
uint32_t f = strip.Color( 0, 0, 0);
uint32_t a= strip.Color (0, 0, 0);
//for b
uint32_t g = strip.Color( 0, 0, 0);
uint32_t h= strip.Color (0, 0, 0);
// for j
uint32_t k = strip.Color (0, 0, 0);
uint32_t m = strip.Color (0, 0, 0);
// for n
uint32_t o = strip.Color (0, 0, 0);
uint32_t q = strip.Color (0, 0, 0);
// for r
uint32_t s = strip.Color (0, 0, 0);
uint32_t t = strip.Color (0, 0, 0);
//define fsr sensor pins and initialize readings
int fsrPin = A0;
int fsrPin2 = A1;
int fsrPin3 = A2;
int fsrPin4 = A3;
int fsrPin5 = A4;
int fsrReading = 0;
int fsrReading2 = 0;
int fsrReading3 = 0;
int fsrReading4 = 0;
int fsrReading5 = 0;
// define blinking variables
int LED=0;
int led2= 100;
//define buttons
int buttonBlue = D7;
int buttonYellow = D8;
int buttonRed = D6;
// set status of buttons
int yellowStatus = LOW;
int blueStatus = LOW;
int redStatus = LOW;
// for ten kids event
int rand_delay = rand() % 200;
int rand_light = rand() % 255;
int rand_red = rand()%255;
int rand_green = rand()%255;
int rand_blue = rand()%255;
//rand_light = 255;
int shooting_star =0;
void setup() {
//show strip lights
strip.begin();
strip.show();
// show neopixel lights
strip2.begin();
strip2.show();
//define variables for each fsr
Particle.variable("force", &fsrReading, INT);
Particle.variable("force2", &fsrReading2, INT);
Particle.variable("force3", &fsrReading3, INT);
Particle.variable("force4", &fsrReading4, INT);
Particle.variable("force5", &fsrReading5, INT);
pinMode(buttonBlue, INPUT_PULLUP);
pinMode(buttonYellow, INPUT_PULLUP);
pinMode(buttonRed, INPUT_PULLUP);
}
void loop() {
yellowStatus = digitalRead(buttonYellow);
blueStatus = digitalRead(buttonBlue);
redStatus = digitalRead(buttonRed);
// publish my event
// you'll want some more complex stuff here
if(yellowStatus == 0){
pushThreeKids();
publishThreeKids();
// delay for a bit
delay(100);
}
else if(blueStatus == 0){
pushSixKids();
publishSixKids();
//delay for a bit
delay(100);
}
else if(redStatus == 0){
publishTenKids();
pushTenKids();
//delay for a bit
delay(100);
}
else {
pushFsrShow();
}
}
void pushFsrShow() {
// assign fsrReading to fsr actual reading
fsrReading = analogRead(fsrPin);
fsrReading2 = analogRead(fsrPin2);
fsrReading3 = analogRead(fsrPin3);
fsrReading4 = analogRead(fsrPin4);
fsrReading5 = analogRead(fsrPin5);
c = strip.Color(0,0,0);
for( int i = 6; i<15; i++) //set the whole neopixel span
{
strip.setPixelColor(i, c);
}
for( int i = 29; i<49; i++) //set the whole neopixel span
{
strip.setPixelColor(i, c);
}
for( int i = 0; i<PIXEL_COUNT2; i++) //set the whole neopixel span
{
c = strip2.Color(0, 0, 0);
strip2.setPixelColor(i, c);
}
// set lights to turn on and breathe when fsr num. 5 is pressed
if (fsrReading5 > 1000){
//make step glow up
c = strip.Color(0, 255, 255);
strip.setPixelColor(32, c);
// make front and back lights breathe
if (LED < 100){
d = strip.Color(0, LED ,LED);
strip.setPixelColor(33, d);
strip.setPixelColor(31, d);
LED=LED+5;
delay (100);
}
if (led2>0 && LED == 100){
d= strip.Color(0, led2, led2);
strip.setPixelColor(33,d);
strip.setPixelColor(31,d);
led2= led2-5;
delay (100);
}
if (led2 == 0) {
LED = 0;
led2 = 100;
}
}
// set lights to turn on and breathe when fsr num. 4 is pressed
if (fsrReading4 > 1000){
c = strip.Color(0, 255,255);
strip.setPixelColor(36, c);
if (LED < 100){
d = strip.Color(0, LED ,LED);
strip.setPixelColor(35, d);
strip.setPixelColor(37, d);
LED=LED+5;
delay (100);
}
if (led2>0 && LED == 100){
d= strip.Color(0, led2, led2);
strip.setPixelColor(35,d);
strip.setPixelColor(37,d);
led2= led2-5;
delay (100);
}
if (led2 == 0) {
LED = 0;
led2 = 100;
}
}
// set lights to turn on and breathe when fsr num. 3 is pressed
if (fsrReading3 > 1000){
c = strip.Color(0, 255,255);
strip.setPixelColor(34, c);
if (LED < 100){
d = strip.Color(0, LED ,LED);
strip.setPixelColor(33, d);
strip.setPixelColor(35, d);
LED=LED+5;
delay (100);
}
if (led2>0 && LED == 100){
d= strip.Color(0, led2, led2);
strip.setPixelColor(33,d);
strip.setPixelColor(35,d);
led2= led2-5;
delay (100);
}
if (led2 == 0) {
LED = 0;
led2 = 100;
}
}
// set lights to turn on and breathe when fsr num. 2 is pressed
if (fsrReading2 > 1000){
c = strip.Color(0, 255,255);
strip.setPixelColor(35, c);
if (LED < 100){
d = strip.Color(0, LED ,LED);
strip.setPixelColor(36, d);
strip.setPixelColor(34, d);
LED=LED+5;
delay (100);
}
if (led2>0 && LED == 100){
d= strip.Color(0, led2, led2);
strip.setPixelColor(36,d);
strip.setPixelColor(34,d);
led2= led2-5;
delay (100);
}
if (led2 == 0) {
LED = 0;
led2 = 100;
}
}
// set lights to turn on and breathe when fsr num. 1 is pressed
if (fsrReading > 1000){
c = strip.Color(0, 255,255);
strip.setPixelColor(33, c);
if (LED < 100){
d = strip.Color(0, LED ,LED);
strip.setPixelColor(32, d);
strip.setPixelColor(34, d);
LED=LED+5;
delay (100);
}
if (led2>0 && LED == 100){
d= strip.Color(0, led2, led2);
strip.setPixelColor(32,d);
strip.setPixelColor(34,d);
led2= led2-5;
delay (100);
}
if (led2 == 0) {
LED = 0;
led2 = 100;
}
}
strip.show();
strip2.show();
}
void pushThreeKids() {
//set all colors to zero (initialize)
for( int i = 6; i<15; i++) //set the whole neopixel span
{
c = strip.Color(0, 0, 0);
strip.setPixelColor(i, c);
}
for( int i = 29; i<49; i++) //set the whole neopixel span
{
c = strip.Color(0, 0, 0);
strip.setPixelColor(i, c);
}
// respond to paired device here
for( int i = 7, p = 30, b = 43; i<15 && p<35 && b>35; i++, p++, b--) //set specific path to light up
{
// for i --first kid
c = strip.Color(0, 255,255);
strip.setPixelColor(i, c);
strip.setPixelColor(i-2,e);
// for p --second kid
a= strip.Color(255, 255, 0);
strip.setPixelColor(p, a);
strip.setPixelColor(p-2,e);
// for b --third kid
g= strip.Color(255, 0, 255);
strip.setPixelColor(b, g);
strip.setPixelColor(b+2, e);
for (int l = 0; l<100; l++)
{
//for i
d= strip.Color(0,l,l);
strip.setPixelColor(i-1,d);
strip.setPixelColor(i+1,d);
// for p
f = strip.Color(l,l,0);
strip.setPixelColor(p-1,f);
strip.setPixelColor(p+1,f);
//for b
h = strip.Color( l, 0, l);
strip.setPixelColor(b+1, h);
strip.setPixelColor(b-1, h);
strip.show();
}
strip.show();
delay(500);
}
}
void pushSixKids() {
//set all colors to zero (initialize)
for( int i = 6; i<15; i++) //set the whole neopixel span
{
c = strip.Color(0, 0, 0);
strip.setPixelColor(i, c);
}
for( int i = 29; i<49; i++) //set the whole neopixel span
{
c = strip.Color(0, 0, 0);
strip.setPixelColor(i, c);
}
// respond to paired device here
for( int i = 7, p = 29, b = 43, j = 49, n=11, r=34; i<11 && p<33 && b>39 && j>45 && n<15 && r<39; i++, p++, b--, j--, n++, r++) //set the whole neopixel span
{
// for i
c = strip.Color(0, 255,255);
strip.setPixelColor(i, c);
strip.setPixelColor(i-2,e);
// for p
a= strip.Color(255, 255, 0);
strip.setPixelColor(p, a);
strip.setPixelColor(p-2,e);
// for b
g= strip.Color(255, 0, 255);
strip.setPixelColor(b, g);
strip.setPixelColor(b+2, e);
// for j
k= strip.Color(255, 0, 0);
strip.setPixelColor(j, k);
strip.setPixelColor(j+2, e);
// for n
o= strip.Color(0, 255, 0);
strip.setPixelColor(n, o);
strip.setPixelColor(n-2, e);
// for r
s= strip.Color(0, 0, 255);
strip.setPixelColor(r, s);
strip.setPixelColor(r-2, e);
for (int l = 0; l<100; l++)
{
//for i
d= strip.Color(0,l,l);
strip.setPixelColor(i-1,d);
strip.setPixelColor(i+1,d);
// for p
f = strip.Color(l,l,0);
strip.setPixelColor(p-1,f);
strip.setPixelColor(p+1,f);
//for b
h = strip.Color( l, 0, l);
strip.setPixelColor(b+1, h);
strip.setPixelColor(b-1, h);
strip.show();
// for j
m = strip.Color(l, 0, 0);
strip.setPixelColor(j+1, m);
strip.setPixelColor(j-1, m);
// for n
q = strip.Color(0, l, 0);
strip.setPixelColor(n+1, q);
strip.setPixelColor(n-1, q);
// for r
t = strip.Color(0, 0, l);
strip.setPixelColor(r+1, t);
strip.setPixelColor(r-1, t);
strip.show();
}
strip.show();
delay(500);
}
}
void pushTenKids() {
for( int i = 6; i<15; i++) //set the whole neopixel span
{
int rand_red = rand()%255;
int rand_green = rand()%255;
int rand_blue = rand()%255;
c = strip.Color(rand_red, rand_green, rand_blue);
strip.setPixelColor(i, c);
strip.show();
delay(100);
}
for( int i = 31; i<49; i++) //set the whole neopixel span
{
int rand_red = rand()%255;
int rand_green = rand()%255;
int rand_blue = rand()%255;
c = strip.Color(rand_red, rand_green, rand_blue);
strip.setPixelColor(i, c);
strip.show();
delay(100);
}
for( int i = 29; i<31; i++) //set the whole neopixel span
{
int rand_red = rand()%255;
int rand_green = rand()%255;
int rand_blue = rand()%255;
c = strip.Color(rand_red, rand_green, rand_blue);
strip.setPixelColor(i, c);
strip.show();
delay(100);
}
for( int i = 0; i<PIXEL_COUNT2; i++) //set the whole neopixel span
{
int rand_red = rand()%255;
int rand_green = rand()%255;
int rand_blue = rand()%255;
c = strip2.Color(rand_red, rand_green, rand_blue);
strip2.setPixelColor(i, c);
strip2.show();
delay(100);
}
for( int i = 0; i<PIXEL_COUNT2; i++) //set the whole neopixel span
{
c = strip2.Color(255, 0, 0);
strip2.setPixelColor(i, c);
}
strip2.show();
delay(200);
for( int i = 0; i<PIXEL_COUNT2; i++) //set the whole neopixel span
{
c = strip2.Color(0, 255, 0);
strip2.setPixelColor(i, c);
}
strip2.show();
delay(200);
for( int i = 0; i<PIXEL_COUNT2; i++) //set the whole neopixel span
{
c = strip2.Color(0, 0, 255);
strip2.setPixelColor(i, c);
}
strip2.show();
delay(200);
for( int i = 0; i<PIXEL_COUNT2; i++) //set the whole neopixel span
{
c = strip2.Color(255, 255, 255);
strip2.setPixelColor(i, c);
}
strip2.show();
delay(200);
for(int i = 0;i<60;i++)
{
int rand_delay = rand() % 200;
int rand_light = rand() % 255;
int rand_red = rand()%255;
int rand_green = rand()%255;
int rand_blue = rand()%255;
//rand_light = 255;
for( int i = 6; i<15; i++) //set the whole neopixel span
{
c = strip.Color(rand_red, rand_green, rand_blue);
strip.setPixelColor(i, c);
}
for( int i = 29; i<49; i++) //set the whole neopixel span
{
c = strip.Color(rand_red, rand_green, rand_blue);
strip.setPixelColor(i, c);
}
if (shooting_star < 49)
{
c = strip.Color(0,0,0);
strip.setPixelColor(shooting_star-1, c);
c = strip.Color(50,50,50);
strip.setPixelColor(shooting_star, c);
}
else if (shooting_star == 49)
{
c = strip.Color(0,0,0);
strip.setPixelColor(shooting_star-1, c);
}
else if (shooting_star > 150)
{
shooting_star = 0;
}
strip.show();
////////////////////////////////////////////////////TWINKLE MIDDLE NEOPIXEL
//delay(1000);
shooting_star = shooting_star + 1;
int big_light = rand_light % 255;
int little_light = rand_light % 255 ;
int delta = rand_light % 20;
for( int i = 0; i<PIXEL_COUNT2; i++) //set the whole neopixel span
{
c = strip2.Color(big_light, big_light +delta, big_light);
strip2.setPixelColor(i, c);
}
strip2.show();
delay(rand_delay);
}
}
void publishThreeKids()
{
// 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 5 seconds 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 = "threekids/2019/paired/" + 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, "Three Kids");
// And this will get shared out to all devices using this code
// we just pubished so capture this.
lastPublishedAt = millis();
}
}
void publishSixKids()
{
// 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 seconds 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 = "sixkids/2019/paired/" + 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, "Six Kids");
// And this will get shared out to all devices using this code
// we just pubished so capture this.
lastPublishedAt = millis();
}
}
void publishTenKids()
{
// 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 seconds 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 = "tenkids/2019/paired/" + 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, "Ten Kids");
// And this will get shared out to all devices using this code
// we just pubished so capture this.
lastPublishedAt = millis();
}
}
Click to Expand
On our final demo day, we asked our reviewers, “What would you do to expand this concept?” They recommended that we start to explore the materials used to build this in real life. We should start considering what it would be like to work with translucent concrete, for example.
They also recommended that we start to think about expanding the light animations to be seasonal: halloween lights in October; christmas lights in December. They suggested that we envision little pleasant surprises into the animations. So every time a child returns to the park, it does something new. They recommended we explore other senses like sound. When children step on the lighted path, it could surprise them with the sound of a spring going ‘boing!’ or an ‘ow! That hurts!’.
They mentioned how children may have been our initial target, but adults would equally enjoy our concept. Imagine if downtown Dallas had, in the center of the city, a play area for all the bar hoppers. They would get to experience the joy that adults rarely give themselves permission to.
Precedents:
A hands-on introductory course exploring the Internet of Things and connected product experiences.
We embarked to design for the CMU Playschool Community to augment the children's experience of play at the school playground through IoT connectivity and a sprinkle of magic!
December 13th, 2019