“Shine” Toothbrush Grip

Jiaxin Wu
Final Documentation, Course “Internet of Things – In Depth”

When parents tell their children to brush their teeth, do they really want clean teeth?

Project Goal

When parents tell their children to brush their teeth, do they really want clean teeth?
Under the notion that in addition to preventing cavities, parents want their children to learn to take the responsibility of caring for themselves, I designed and prototyped a toothbrush grip named Shine, that with the complemented webpage, can help with the problem.
Shine toothbrush grip creates a gamified solution for children to learn and follow the proper process of teeth-brushing and help them persevere through boring but necessary tasks of life.

Market Research

User needs

Existing Competitors

Aquasonic brush monster

The brush monster uses an app to guide the teethbrushing process. The use cartoon monster character displayed on phone attracts the children and keeps then engaged through the brushing process.


Colgate uses a result-oriented rather than a process-oriented method to inspect the cleanliness of the user’s teeth. It uses blue light to identify dirt and similarly uses an app to show the spots on a picture of the teeth. The user will have to brush until there are no spots showing up.

OralB IO

OralB’s new toothbrush installs sensors at the tip of the toothbrush. With the sensors, the toothbrush will be able to know not only the direction of the brush, but also the force. It similarly uses an app to give feedback to the users.


Autobrush is a Y-shaped electric brush that uses a friendly-tasting foam instead of toothpaste to facilitate the teeth-brushing process. The child holds the brush in his mouth, and the brush plays music while the high-frequency vibration gets rid of the dirt. This minimizes the children’s efforts to simply holding the brush to their mouth.

The dentist

Dentist clinics have been a nightmare for many children because of the uncomfortable experiences there. Many clinics now adopt a colorful and bright environment, installed with a playing corner with toys and books. The interior of clinic rooms may include cartoon characters, and dentists may let them play with some toys to distract them and prevent crying or moving during the examination.


  • Many existing solutions use displays, especially apps on smartphones.
  • Cleanliness is the major target. Few focus on the process.
  • There is no current gamification, though Brush Monster does use cartoon characters.
  • Children generally hate brushing their teeth and are afraid of dentists.
  • Many products sell an entire brush that is usually expensive.


X Fancy brush with fancy price
X Sophisticated sensors
X Rarely read data log
✓ Simple grip applicable to any brush
✓ Ergonomically child-friendly
✓ Process-oriented

Plan and Path

The Original Plan

The original proposal. The core idea is a toothbrush that tracks the movement, and provides corresponding display that either applauds and encourages the child to continue to perform well, or hints and nudges him to follow the correct process.

Data Analysis

I did some research on how to utilize sample data to guide setting the thresholds and identification.

Detecting Movement

  • First-order difference: This is a technique used by an article about 3D-modeling toothbrush movements. The idea is to treat continuous data as discrete variables, and calculate the slope between each data point. In this case, since the distance (time interval) between data points is fixed, the problem simplifies into calculating the difference of the data. This did not test accurate in my project. A likely reason is that the duration / interval of each movement is not fixed and hard to define. Thus, the acceleration level read may be in different phases of the movement and cannot aggregate to form a pattern.
  • Variance This tested well in my project. A likely reason is that it measures the vigorance of brushing in each direction, so it is less sensitive to the length of intervals between readings.
  • Motion Direction: This is sensitive to the case of teeth-brushing. The activity does not permit movement in many directions other than along the axises. In addition to whether the grip is idle or moving, a simple comparison of which direction has larger acceleration variation.


  • Exponential Smoothing: This technique can show the trend while eliminating significant outliers.
    S0= x0
    St = αxt-1 + (1-α)st-1, t > 0
    Apply: store the last calculated variances and smooth the current one out.
  • Moving Average Smoothing: This is an extension of exponential smoothing.
    Apply: Store the last few calculated variances and smooth the current one out. However, I think it will not work as well as exponential smoothing, because there is less weight on the current value.
  • Double Exponential Smoothing: This takes into account the amount of change since the last value.
    St = At + Bt , t = 1,2,3,…, N
    Where, At = axt + (1- a) St-1 0< a <= 1
    Bt = b (At – At-1) + (1 – b ) Bt-1 0< b <= 1
    Apply: Create a variable to calculate and store the difference from the last value.


Break down to phases

The teeth-brushing is broken down into 3 major phases:

  1. Brush vertically, along the wall of the teeth (the front that shows).
  2. Brush horizontally, along the crown of the teeth (where you chew).
  3. Brush the back of the teeth.
    The movement required for each phase is tied to readings and results of the accelerometer.


In each loop, I read the accelerometer along 3 axises into an array. I computed the variance along each axis to determine how violently the brush was moving along that axis. Judgement of whether the brush is idle, and how it is moving, it based on the computed results:

  • Horizontal
    avgVx>100 && avgVx>avgVy
    The variance along the x axis is larger than 100, and variance along the x axis is larger than y axis.
  • Vertical avgVy>100 && avgVy>avgVx
    The variance along the x axis is larger than 100, and variance along the x axis is larger than y axis.
  • Idle
    There is no identification for brushing the back of the teeth, because the user research discovered that movement for such varies a lot. The requirement is simply not idle in this phase, or: avgVy>100 || avgVx>100.


Since children normally do not use brushes as we might anticipate them to, I simplified the phase breakdown and lowered the threshold to allow for more tolerance for the children’s use. This helps ensure the experience is not too rigid and discouraging.

Summary of Pivots

I made several pivots during the progress and I am glad that I did. This does not mean that I failed at doing something, but only more focused on essential functions and adapted to any abstacles along the way.

  • Forego the physical design of the grip
    The grip should ergonomically fit a child’s hand, be easy to grasp, easy to clean, and water proof. I had wanted this to be part of the work, at least make a prototype out of clay to portray the ergonomic shape. But eventually, I realized that because I am building the prototype with Particle Argon, the circuit board is unnecessarily big. Making a shape of the grip big enough to hold the current circuit would be over-engineering, and waste a lot of time. Since the water-proof requirement is very feasible, just not at this point, I skipped working t=on this part altogether.
  • Screen Since children usually do not have their own tablet or smart phone, I wanted to show displays on a separate screen so it also does not burden the parents financially or raise concerns about screen time. However, the screen was not working easily, so I decided to use a webpage instead. This actually has several benefits to it:
    • Allowing users to control from the display.
    • Allowing flexibility in content and future development. Users will be able to easily choose from multiple themes for their favorite display, and accessing other features (if any) without having to download.
  • Mesh and Wifi connections
    With the screen solution, I wanted to use mesh connection to build a local network and save energy and eliminate the need for Wifi. Transitioning to using a web page, I was forced to use Wifi to communicate parameters between devices.
  • Toothbrush Holder
    Originally I had wanted to make a toothbrush holder that senses the toothbrush being taken out and starting the entire process, and also integrated with the screen. This was compromised when the screen changed into the website. The user will be able to control the start, pause, stop and restart from the website, leaving little value for the toothbrush holder.

Code and Circuit

Circuit Diagram

After several pivots of the project, the circuit boils down to just one sensor (3-axis accelerometer) attached to a processor (I used Particle Argon).

Actual circuit board.

Actual Circuit Board

Circuit diagram

Circuit diagram

Code for grip

// This #include statement was automatically added by the Particle IDE.
#include <SparkFunMMA8452Q.h>
#include <Particle.h>
#include <Wire.h> // Must include Wire library for I2C

Argon Alex
1) Reads data on 3-axis accelrometer;
2) Performs data analysis;
3) Determines which phase;
4) Posts data as variables for the display page to get;
5) starts and stops by control of buttons of the display page. 


MMA8452Q accel;//set up accelerometer

//acc readings
int accx=0;
int accy=0;
int accz=0;
//recording of past 10 readings per axis
int X[10];
int Y[10];
int Z[10];
// count reading
int n=0;

// smoothing //store past 7 values of variance
int Vx[7]={0,0,0,0,0,0,0};
int Vy[7]={0,0,0,0,0,0,0};
volatile int avgVx = 0; 
volatile int avgVy = 0;

//time management
int starttime=0;
int last_published=0;
int startctrl=NULL; 

    int avgX=0;
    int avgY=0;    
    int diffX=0;
    int diffY=0;
    int varX=0;
    int varY=0;

int count =0;//running

//respective strokes
int stroke_horizontal = 0;
int stroke_vertical = 0;
int stroke_back = 0;
int idle=1;
int phase=0;

void setup() {
    accel.begin(SCALE_2G, ODR_1); //initiate accelerometer
    Particle.function("control", control); //be controlled by the display page    
    Particle.variable("stroke_horizontal", &stroke_horizontal, INT);
    Particle.variable("stroke_vertical", &stroke_vertical, INT);
    Particle.variable("stroke_back", &stroke_back, INT);
    Particle.variable("idle", &idle, INT);
    Particle.variable("phase", &phase, INT);
    Particle.variable("startctrl", &startctrl, INT);

void loop() {
    if (start==0)
        Mesh.publish("time", millis());
    if (startctrl!=NULL)
        for (n=0;n<10;n++)
            if (accel.available())
            }// Update accelerometer data
            X[n]=accel.x;//assign into array
            // log the time
            Serial.print( Time.timeStr() );
            // print out data in order
            Serial.print( accx ); 
            Serial.print( accy ); 
            Serial.print( accz ); 

            //start a new line
            Serial.println( " " );
        //for test
        Serial.println( String(phase) );
        Serial.println( "Unactivated" );
    if (count >1000) 
        count =0;
    //define phase
    //when idle >1, is idle
    if (startctrl ==1)
        if (millis()-starttime<=5000)//use time
            phase = 1; //welcome
        else if (stroke_vertical<5 && phase ==1  )//use time
            phase = 2; //vertical instructions
        else if (stroke_vertical<30 && idle ==0 )
            phase = 3; // v good
        else if ( stroke_vertical<30 && idle ==1 )
            phase = 4; // v idle
        else if (stroke_horizontal<20 && stroke_vertical>30 )
            phase = 5; // h instructions
        else if (stroke_vertical>30 && stroke_horizontal<20 && idle ==0  && phase <=7)
            phase = 6; // h good
        else if (stroke_vertical>30 && stroke_horizontal<20 && idle==1  && phase <=7)
            phase = 7; // h idle
        else if (stroke_vertical>30 && stroke_horizontal>20 && stroke_horizontal<23 && phase <=10)
            phase = 8; // h change
        else if (stroke_vertical>30 && stroke_horizontal>23 && stroke_horizontal<43 && idle==0 && phase <=10) 
            phase = 9; // h good
        else if (stroke_vertical>30 && stroke_horizontal>23 && stroke_horizontal<43 && idle ==1 && phase <=10)
            phase =10; // h idle
            //stroke_back =0;//re-initialize
        else if (stroke_vertical>30 && stroke_horizontal>40 && stroke_back<5) 
            phase = 11; //behind teeth
        else if (stroke_vertical>30 && stroke_horizontal>60 && stroke_back>20)
            phase = 12; //idle
        phase = 1;


void analyze() //motion data analysis
    //get average of X and Y axis acceleration in past 10 readings
    for (int i=0; i<10; i++)
    // compute respective variance
    for (int j=0; j<10; j++)
        diffX = X[j]-avgX;
        diffY = Y[j]-avgY;
    //update cell

    // horizontal
    if (avgVx>100 && avgVx>avgVy && phase <=10) // variance along the axis is significant and larger than other
        Particle.publish("shine/movement", "vertical");
        Mesh.publish("shine/movement", "vertical");
        last_published = millis();
        idle =0;
    else if (avgVy>100 && avgVy>avgVx && phase <=10) //horizontal
        Particle.publish("shine/movement", "horizontal");
        Mesh.publish("shine/movement", "horizontal");
        last_published = millis();
        idle =0;
    else if (phase>=9 && max(avgVy, avgVx)>100)
        Particle.publish("shine/movement", "back");
        Mesh.publish("shine/movement", "back");
        Particle.publish("shine/movement", "no");
        Mesh.publish("shine/movement", "no");
        idle=min(100/avgVx, 100/avgVy);
    send();    // send raw data 

int control( String datastr)
    //String dataStr = String( data);
    //Serial.printlnf("event=%s data=%s", event, data ? data : "NULL");
    if (datastr=="1")
        startctrl = 1;
        stroke_back =0;
        stroke_horizontal =0;
        stroke_vertical =0;
        startctrl = 0;
        stroke_back =0;
        stroke_horizontal =0;
        stroke_vertical =0;
    //startctrl = datastr.toInt();
    return 0;

void smooth()
    for (int k=0; k<7; k++)

void send() //send raw data
    Mesh.publish("shine/data/accx", String(accx));
    Mesh.publish("shine/data/accy", String(accy));
    Mesh.publish("shine/data/accz", String(accz));
    Mesh.publish("shine/data/variX", String(avgVx));
    Mesh.publish("shine/data/variY", String(avgVy));

void print()
    // log the time
    Serial.print( Time.timeStr() );
    // print out data in order
    Serial.print( accx ); 
    Serial.print( accy ); 
    Serial.print( accz ); 

    //start a new line
    Serial.println( " " );

Code for website

<pre class="wp-block-syntaxhighlighter-code"><!doctype html>
<html class="no-js" lang="en" dir="ltr">
   <meta charset="utf-8">
   <meta http-equiv="x-ua-compatible" content="ie=edge">
   <meta http-equiv="refresh" content="30">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/foundation-sites@6.4.3/dist/css/foundation.min.css" integrity="sha256-GSio8qamaXapM8Fq9JYdGNTvk/dgs+cMLgPeevOYEx0= sha384-wAweiGTn38CY2DSwAaEffed6iMeflc0FMiuptanbN4J+ib+342gKGpvYRWubPd/+ sha512-QHEb6jOC8SaGTmYmGU19u2FhIfeG+t/hSacIWPpDzOp5yygnthL3JwnilM7LM1dOAbJv62R+/FICfsrKUqv4Gg==" crossorigin="anonymous">
   <!-- ADD your APP TITLE HERE -->
   <title>Shine toothbrush grip display</title>

 	<!--top row-->
   <div class="row">
   	<div style="background-color:#4c0080" class="title-bar"> 
   		<div class="title-bar-left">
   			<span class="title-bar-title">Shine toothbrush grip display</span>
   		<div class="title-bar-right"></div>

   <!--display instructions--> 
   <p>Click on the buttons to start, stop or start over!</p>
   	<div class="secondary">
   	    <a class="button" style="background-color:#4c0080" onclick="startIt( )" >Let's Start Now  ^-^ </a>
   	    <a class="button" style="background-color:#4c0080" onclick="stopIt( )">I have to stop 😦 </a>
   	    <a class="button" style="background-color:#4c0080" onclick="restart( )">Start over </a>
   <p>Follow the instructions and receive clean teeth :)</p>
   <img src= "file:///C:/CMU/IOT-depth/display/1.JPG" width="240" id="shineDisplay">


   <!--Create some space at the botton-->
   <div style="margin-top:500px"></div>	

   <a href="https://code.jquery.com/jquery-3.3.1.min.js">https://code.jquery.com/jquery-3.3.1.min.js</a>

   <!-- Compressed JavaScript -->
   <a href="https://cdn.jsdelivr.net/npm/foundation-sites@6.4.3/dist/js/foundation.min.js">https://cdn.jsdelivr.net/npm/foundation-sites@6.4.3/dist/js/foundation.min.js</a>
   		<style>.gist table { margin-bottom: 0; }</style><div class="gist-oembed" data-gist="bc983aec2e7010046ab9f383cf08c807.json" data-ts="8"></div>

   	//access and fuction to get variables from particle
   	var deviceID    = "e00fce68f645bb07172afa34"; // Argon alex
   	var accessToken = "5975537948b5425fcf31b9df8a26c333d6dc742a"; // token for Argon Alex
   	//var event1 = "shine/movement";
   	var trackedEventName = "shine/phase";
   	var functionURL = "https://api.particle.io/v1/devices/" + deviceID + "/";

   	//time control
   	var starttime = new Date().getTime; 
   	var currenttime = new Date().getTime;

   	//prepare for interaction
   	$( document ).ready(function() {
   	    $("#deviceIDInput").val(  deviceID );
   	    $("#accessTokenInput").val(  accessToken );

   	// phase control
   	var phase=0; 

   	function doPhaseControl( phase )
   			case 1: //welcome 
   			$('#shineDisplay').attr( "src", 'file:///C:/CMU/IOT-depth/display/1.JPG' );
   			case 2: //vertical instructions
   			$('#shineDisplay').attr( "src", 'file:///C:/CMU/IOT-depth/display/2.JPG' );
   			case 3: //vertical good job
   			$('#shineDisplay').attr( "src", 'file:///C:/CMU/IOT-depth/display/3.GIF' );
   			case 4: //vertical try harder
   			$('#shineDisplay').attr( "src", 'file:///C:/CMU/IOT-depth/display/4.JPG' );
   			case 5: //horizontal instructions
   			$('#shineDisplay').attr( "src", 'file:///C:/CMU/IOT-depth/display/5.JPG' );
   			case 6: //horizontal good job
   			$('#shineDisplay').attr( "src", 'file:///C:/CMU/IOT-depth/display/6.GIF' );
   			case 7: //horizontal try harder 
   			$('#shineDisplay').attr( "src", 'file:///C:/CMU/IOT-depth/display/7.JPG' );
   			case 8: //change side instructions
   			$('#shineDisplay').attr( "src", 'file:///C:/CMU/IOT-depth/display/8.JPG' );
   			case 9: //horizontal good job
   			$('#shineDisplay').attr( "src", 'file:///C:/CMU/IOT-depth/display/9.GIF' );
   			case 10: //horizontal try harder
   			$('#shineDisplay').attr( "src", 'file:///C:/CMU/IOT-depth/display/10.JPG' );
   			case 11: //behind teeth
   			$('#shineDisplay').attr( "src", 'file:///C:/CMU/IOT-depth/display/pacman.gif' );
   			case 12: //ending
   			$('#shineDisplay').attr( "src", 'file:///C:/CMU/IOT-depth/display/12.JPG' );
   			default: //welcome page
   			$('#shineDisplay').attr( "src", 'file:///C:/CMU/IOT-depth/display/1.JPG' );

   	function startIt()
   			1) send a signal to Argon Alex to start running the code;
   			2) note down the time of running; 
   			3) update time spent on phases
   	    var functionName = "control"; // name of function
   		var passedParameter = "1"; // data to send

   		var functionURL = "https://api.particle.io/v1/devices/" + deviceID + "/";

   		// don't change this!
   		$.post( functionURL + functionName + "/", { params: passedParameter , access_token: accessToken });
   	function stopIt()
   			1) send a signal to Argon Alex to stop;
   			2) reset
   			Note: the numbers do not reset to 0 at this point. 
   	    var functionName = "control"; // name of function
   		var passedParameter = "0"; // data to send
   		var functionURL = "https://api.particle.io/v1/devices/" + deviceID + "/";
   		// don't change this!
   		$.post( functionURL + functionName + "/", { params: passedParameter , access_token: accessToken });
   	function restart(){
   			Basically it is stopping and then starting again.
   			Call those functions. 

   	function loadVariables(){ //get
   		var phaseRequest = $.get( functionURL + "phase", { access_token: accessToken }, function( data ) {
   			console.log( data );
   			console.log("loaded variables");
   			var phase = parseInt( data.result );

   			$( "#phase" ).val( phase ); //value stored in data.result

   			doPhaseControl(  phase ); // get the integer
               setTimeout(loadVariables, 2000); //reload variables every 1 sec
   		}).fail(function() {
   		    alert( "The phase of the teethbrushing process cannot be received. Make sure it is powered on." );


   	function handleParticleEvents()
   		var source = new EventSource("https://api.particle.io/v1/devices/" + deviceID + "/events?access_token=" + accessToken );
       	if (typeof(EventSource) !== "undefined") 
           	console.log("SSE is Supported!");
       	} else 
           	console.log("SSE is NOT Supported");
       	source.addEventListener( trackedEventName , function (e) 
           	var obj = jQuery.parseJSON(e.data);
   				var newListItem = $( "<li>" + obj.data + " <span>" + obj.published_at + "</span> </li>" );
           	$("#dataLog ul").append( newListItem );
       	}, false);

Feedback and Future Steps

Overall, my idea has received good feedback during the demo in how it integrates necessary tasks with games. This might be something worthy of carrying on, though the task may not be limited to teethbrushing.
Some other suggestions for next steps include:

Use the orientation offered by the accelerometer and break down the process further

  • I had thought of this, but it was more complicated and does not really contribute to the demo of the idea. The current 3 phases is enough for the demo, but further splitting it up is definitely feasible.
  • There is a bit concern about whether breaking down too much will put too much burden on the children. They should be around 3-6 years old. Also, if we use the same game for same movement but different direction, there may be some perceived redundancy.

Movement Correspondance

For the Flappy Bird game, some users thought that they should move the brush up only when the bird should go up. This is some confusion that can be solved by creating a better game to match.

Real games instead of GIFs

Some suggested pairing the movement with real games, which means that instead of using GIFs to represent the status, they will actually controlling the game. Actually I don’t see a significant difference here. Perhaps this is still related to the correspondance of the movement and the display. This should definitely be worked on the remove any confusion for younger children.

Dash board for dentists.

As this only focuses on the process, the data collected may be of little use for dentists, but perhaps help parents learn about how well their children are performing, and intervene or reinforce early.

More themes

To avoid boredom and appeal to a larger range of users. Having moved the display from an attached screen to a website, it is easier to enable the user to choose what “game” he’d like to play but simply adding themes to choose from before starting the process.


I want to express my thanks to Daragh Bryne who has provided help and guidance throughout the project, including the overall idea and coding details.
I appreciate this opportunity to explore the domain ofconsumer electronics in the form of a project that focuses on the education of responsibility of younger children.

Marketing Poster

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s