Sunday, February 21, 2010

The Brushduino

Ever have an idea that people tell you "you should TOTALLY patent that!" Well, I did, and they did, and I thought about it... but instead I decided to open source the whole thing. It seemed like kind of an obvious idea to me and going after a patent made me feel kind of greedy. Besides, all of my prototyping was done with open source tools!

Anyways, I hope someone can improve upon this. I think it's a useful thing.

The Story

My daughters don't brush their teeth long enough, if left to their own devices. When my wife or I brush their teeth, we do a nice thorough job that takes a little while but when the girls do it themselves, they tend to cut it short. Timing them doesn't help, as they just stand there and chit-chat until the timer goes off. It's not that they don't brush their teeth LONG enough, it's that they don't brush their teeth WELL enough.

It occurred to me that if could make something that monitored how WELL they brush their teeth---and maybe even guided them through the process a little---then they'd eventually establish better habits for the future. I started thinking about what I could build that would help them and settled on the notion that an accelerometer was the perfect sort of thing to keep tabs on what constitutes a brush "stroke" while they're in there brushing their teeth. All I'd need to do is come up with a little hardware and software to make it happen.

About the Hardware

For my birthday last year, I got myself an Arduino starter kit from www.adafruit.com and hadn't used it for anything yet. I was curious about the Arduino platform and have read a lot about it. I figured it was time to build something on it and this toothbrush thing seemed like the perfect fit.



I started looking for a low-cost accelerometer I could base my project on. I found one with a breakout board on Adafruit and one on Sparkfun but I didn't want to spend that much. Plus, it needed to be small---I wanted this thing to fit on the end of a toothbrush. I continued scouring the web and found this article over on Hack a Day for Digital Dice. This was based around the Freescale MMA7260QT and he fearlessly did all of the fine soldering work himself. I figured I could do that too. I ordered up a couple of MMA7260QT's from Mouser and a couple of the MMA7368LT's (BGA package version) and set to work with 30 AWG tynar coated solid core wire. This stuff is usually used for wire-wrapping, but I like to use it for low-current point to point stuff and general purpose project wire.

I wired one of the MMA7368LT's up to a .1" pin header so I could start experimenting.



It's pretty fiddly work and requires lots of swearing. I drink a lot of caffeine, which makes it even harder. I recommend finding great inner peace before you begin. Some solder paste also helps.




I got everything going with the Arduino just jumpering things around and with a bit of breadboard. It all went very quickly compared thanks to the Arduino environment which took care of a lot of the usual setup I normally go through (soldering up a voltage regulator, breaking out the SPI pins for programming AVR chips, setting fuses, burn-and-test-and-burn-and-test, etc.) It was nice!

If the Arduino is the brain of this project and the accelerometer is the eyes, then the Adafruit ProtoShield is the heart. It's only like $13 and it's brilliant.


It's basically all LED's. There are 6 white LED's that correspond to the six regions of the mouth that I want to highlight, there are four green LED's for showing progress and there's a speaker I pulled out of some broken toy that's there to entertain the children while they brush. The multi-strand wire connected to the three analog inputs on the Arduino also supplies 3.3v and ground to the accelerometer. The cable itself is an old ADB keyboard cable from my old Mac SE/30 from back in the day. It's been laying in my junk box for ages. There are also some current limiting resistors for the LED's and a bunch more of that lovely AWG30 solid core wire I like so much.

At the toothbrush end of the cable, I encased the whole thing in hot glue to keep it safe from water and from yanking by children:

This "puck" on the end was meant to be attached to the toothbrush handle via some mechanism (I ended up using rubber bands, which was only somewhat successful and I hope to improve upon) and held in the user's hands.

For an enclosure, I used a cool plastic enclosure I got from PAC TEC a few years ago as a free sample. It was perfect for housing the Arduino with attached ProtoShield and had a 9 Volt Battery compartment too. I had to modify the battery compartment and I added a barrel connector so I didn't have to solder to the Arduino at all.

To allow the indicator lights to show through the case to the front and still remain waterproof, I used some more hot glue. I drilled holes roughly the size of the LED's directly above where they would fall once installed in the case, filled the holes with hot glue, and snipped off the tops and bottoms to be flush against the case on the top side and flush against the LED's on the bottom side. For the reset button, I used the same technique, but lubricated the hole so the hot glue piece can slide up and down. It all proved to be pretty water tight and while the hot glue is a little opaque, it's clear enough to transfer the light up to the surface where they can see it. It's a sort of a "light pipe" type of thing.

Above you can see the barrel connector for the arduino peeking out and the SPST slide switch I installed in the side of the case.

I repurposed some orange suction cups from a cheap nerf hoop knockoff toy I got as a Christmas present one year to stick the enclosure to the girls' mirror in the bathroom to keep it at eye level and keep it out of the water.

Here it is installed over their sink:


When you fire it up, it plays the opening bars of the Super Mario Brothers theme song while randomly flashing the white LED's. Then it starts instructing on what area of the mouth to brush.


I draw a mouth on a piece of paper and glue-stick it to the front. That way I can rotate the pictures out and keep them funny/interesting. The green LED that's lit rotates around between the four as each "stroke" is counted. A "stroke" is defined as hard acceleration in one direction followed by hard acceleration in the opposite direction within a specified time window. I tweaked the parameters of what defines a "stroke" until I got pretty good results. It still picks up false strokes when they're moving the toothbrush around but it does a pretty good job. At least---good enough for this purpose.


Each time a section of the mouth has been brushed sufficiently (stroke count was based on experimentation and dentist recommendations) a few more bars of the Super Mario Brothers theme song is played (along with light show) and then the next white LED starts flashing. It's easier to see the pattern than it is to describe it. The section they're supposed to be brushing at any point in time is blinking with the already-brushed portions of the mouth lit fully on.

Here's a video of the whole process with me fake-brushing my way through it. Pardon the nausea-cam photography, I couldn't find my little tabletop tripod.


video

It works pretty well! And my first Arduino project was a smashing success. Let's hear it for easy floating point number support on microcontrollers.

My next step is to replace that puck of hot glue on the end with a Sugru "cup" that they stick on the end of the toothbrush. I was lucky enough to get some Sugru for Christmas (actually, after Christmas) and have been itching to use it for something electronic. For those not familiar with Sugru, it's an air-curing silicone putty made by a snazzy little outfit in the UK.


I'll post again once I have the Sugru version going. I just ripped out of few of the wires to the accelerometer when cutting the hot glue off so I have to go back to the workshop for a while. :-/

Eventually I'll replace the Arduino with a couple of voltage regulators, a pushbutton and an ATmega328 and make this piece of hardware fully "legit." Or maybe I won't.

About the Software

Coding for Arduino is easy-cheesy compared to working directly with WinAVR or Atmel's AVR Studio. For one thing, you get printf() which is HUGE! I used LED indicators to debug previously. Being able to log output to a scrollback is an immense boon. It's really pretty luxurious! The Arduino environment is basically a front end for a WinAVR backend and it also gives you some pretty awesome libraries to use straight out of the box.

For the software, I needed to light pretty lights, play pretty music and most importantly count brush strokes.

For those of you who have worked with three axis accelerometers, you know that gravity is always represented in the reading. To get a good sense of movement around three space, you need to subtract out the gravity component from the readings you're getting. This is harder than it sounds if you consider that in my case the toothbrush could be oriented any which way and therefore the gravity could be registering along any axis or any combination of axes.

To solve this problem, I turned to a cheap-o solution which is to use a sort of bargain basement "high pass filter" to remove the gravity component. Since gravity is going to be constant, it stands to reason that whatever elements of motion that the accelerometer is registering, the ones that have been going on for a long time (low frequency data) should be ignored while the recent and rapid changes (high frequency data) should be allowed to go through for analysis. I did a little searching around and found a small Objective C code snippet in the iPhone developers' realm that did just the thing I needed... simulated a high pass filter:


// Read the 3 axes analog values from the accelerometer
int rawX = analogRead(analogXPin) - 512;    // read the value from the sensor
int rawY = analogRead(analogYPin) - 512;    // read the value from the sensor
int rawZ = analogRead(analogZPin) - 512;    // read the value from the sensor

// Our High Pass filter works better with signed floating point values
float floatX = (float)rawX,
floatY = (float)rawY,
floatZ = (float)rawZ;

// Low pass filter
float kFilteringFactor = 0.1f;

// We're keeping 90% of the rolling value and adding in 10% of the new value each
// time. This filters out recent values and retains historical values.
rollingX = (floatX * kFilteringFactor) + (rollingX * (1.0 - kFilteringFactor));
rollingY = (floatY * kFilteringFactor) + (rollingY * (1.0 - kFilteringFactor));
rollingZ = (floatZ * kFilteringFactor) + (rollingZ * (1.0 - kFilteringFactor));

// High pass filter

// Now we remove the historical values to emphasize current values more.  The high
// pass filter helps us remove gravity from the reading so we can get orientation-
// independent acceleration values from the device
accelX = floatX - rollingX;
accelY = floatY - rollingY;
accelZ = floatZ - rollingZ;



My processed ( accelX, accelY, accelZ ) vector now represents interesting accelerometer values to feed into a little stroke counter routine.

For the music side of the project, I found the Arduino Playground "freqout()" function, which I modified to handle rests and a wider range of note values. I then found someone's sheet music transcription of the Super Mario Brothers theme song and converted it to data.

Here's the full source code of the Arduino sketch. This was compiled against arduino-0017 but should work with the latest too. Full source after the break below:



//
// Brushduino
//

//////////////////////////////////////////////////////////////////////////////
// Pin Assignments
//
int analogXPin = 0;
int analogYPin = 1;
int analogZPin = 2;

// Arduino indicator LED
int ledPin = 13;

// Activity LED Assignments
int activityLED1 = 6;
int activityLED2 = 7;
int activityLED3 = 8;
int activityLED4 = 9;

// Audio Pin
int audioPWM = 11;

int regionLEDs[] = {3, 10, 4, 5, 12, 2};

//////////////////////////////////////////////////////////////////////////////
// High Pass Filter Variables
//

float rollingX = 0; 
float rollingY = 0;
float rollingZ = 0;

float accelX, accelY, accelZ;

//////////////////////////////////////////////////////////////////////////////
// Brushing Parameters
//
boolean strokeTop = false;
boolean strokeBottom = false;

int elapsed = 0;
int lastReading = 0;
int strokeCount = 0;
float strokeMin = 10;
float strokeMax = 50;
int maxStrokeDuration = 250;
int minStrokeDuration = 50;

// Number of counted strokes for each "region" of the mouth
int strokesPerRegion = 60;

// Iterations through the main loop
unsigned int loopIterations = 0;

// The rate we flash the current region
int flashRate = 1000;

// The region we're brushing in
int curRegion = 0;


//////////////////////////////////////////////////////////////////////////////
// Music Definitions
//
int tempoSlow = 100;
int tempoFast = 50;

//////////////////////////////////////////////////////////////////////////////
// setup()
//
void setup() 
{
  Serial.begin(115200);
  
  pinMode(ledPin, OUTPUT);  // declare the ledPin as an OUTPUT

  analogReference(EXTERNAL);
  
  // Activity LED's
  pinMode(activityLED1, OUTPUT);
  pinMode(activityLED2, OUTPUT);
  pinMode(activityLED3, OUTPUT);
  pinMode(activityLED4, OUTPUT);
  
  // Audio out
  pinMode(audioPWM, OUTPUT);

  // Mouth Region LED's
  pinMode(regionLEDs[0], OUTPUT);
  pinMode(regionLEDs[1], OUTPUT);
  pinMode(regionLEDs[2], OUTPUT);
  pinMode(regionLEDs[3], OUTPUT);
  pinMode(regionLEDs[4], OUTPUT);
  pinMode(regionLEDs[5], OUTPUT);
  
  loopIterations = 0;
  curRegion = 0;
  
  // After init, play the one bar intro to the song
  PlaySong(0, tempoSlow);
}

//////////////////////////////////////////////////////////////////////////////
// UpdateCount()
//
// Do accelerometer stroke counting
//
void UpdateCount()
{
  // A single stroke requires going below and then above a threshhold within a certain amount of time
  //
  elapsed = millis();
  
  // Magnitude of 3d accelerometer reading
  float val = sqrt(accelX * accelX + accelY * accelY + accelZ * accelZ);
  
//  Serial.println(val);
  
  // See it's been at least enough time to try and count a stroke
  if (elapsed > lastReading + minStrokeDuration)
  {
    // See if we've got a stroke top
    if (val > strokeMax)
    {
      strokeTop = true;
      lastReading = elapsed;
    }
  
    // See if we've found the stroke bottom
    if (val < strokeMin)
    {
      strokeBottom = true;
      lastReading = elapsed;
    }
  }
  
  // See if we failed to identify a stroke inside the maximum time window
  if (elapsed > lastReading + maxStrokeDuration)
  {
    // No stroke found, reset our stroke tracking and start again
    strokeTop = false;
    strokeBottom = false;
    lastReading = elapsed;
  }
  else     // We haven't timed out our stroke yet, see if we've identified one
  if (strokeTop & strokeBottom)
  {
    // Yes, both top and bottom of stroke have been found, we count this
    strokeCount++;
    
    // Reset stroke tracking
    strokeTop = false;
    strokeBottom = false;
    lastReading = elapsed;
    
    // Flash the led and pop the audio indicating we detected something
    digitalWrite(audioPWM, HIGH); 
    digitalWrite(ledPin, HIGH);  // turn the ledPin on
    delay(30);                  // stop the program for some time
    digitalWrite(ledPin, LOW);   // turn the ledPin off
    digitalWrite(audioPWM, LOW);
  } 
}

//////////////////////////////////////////////////////////////////////////////
// loop()
//
// Arduino main loop
//
void loop() 
{
  boolean flashOn = (loopIterations % flashRate) > (flashRate / 3);

  // Read the 3 axes analog values from the accelerometer
  int rawX = analogRead(analogXPin) - 512;    // read the value from the sensor
  int rawY = analogRead(analogYPin) - 512;    // read the value from the sensor
  int rawZ = analogRead(analogZPin) - 512;    // read the value from the sensor
  
  // Our High Pass filter works better with signed floating point values
  float floatX = (float)rawX, 
        floatY = (float)rawY, 
        floatZ = (float)rawZ;
 
  // Low pass filter
  float kFilteringFactor = 0.1f;

  // We're keeping 90% of the rolling value and adding in 10% of the new value each
  // time. This filters out recent values and retains historical values.
  rollingX = (floatX * kFilteringFactor) + (rollingX * (1.0 - kFilteringFactor));
  rollingY = (floatY * kFilteringFactor) + (rollingY * (1.0 - kFilteringFactor));
  rollingZ = (floatZ * kFilteringFactor) + (rollingZ * (1.0 - kFilteringFactor));

  // High pass filter
  
  // Now we remove the historical values to emphasize current values more.  The high
  // pass filter helps us remove gravity from the reading so we can get orientation-
  // independent acceleration values from the device
  accelX = floatX - rollingX;
  accelY = floatY - rollingY;
  accelZ = floatZ - rollingZ;
  
  // We have 4 "crawling" LED's to show progress towards each mouth region's stroke
  // count
  int interval = 1;
  int val = strokeCount % (interval * 4);

  // Stroke LED's are turned on to indicate progress towards goal
  if (val >= interval * 0 && val < interval * 1) digitalWrite(activityLED1, HIGH); else digitalWrite(activityLED1, LOW);
  if (val >= interval * 1 && val < interval * 2) digitalWrite(activityLED2, HIGH); else digitalWrite(activityLED2, LOW);
  if (val >= interval * 2 && val < interval * 3) digitalWrite(activityLED3, HIGH); else digitalWrite(activityLED3, LOW);
  if (val >= interval * 3 && val < interval * 4) digitalWrite(activityLED4, HIGH); else digitalWrite(activityLED4, LOW);

  // Region LED's are turned on to tell the user which area of the mouth to brush
  // at any point in time.  We start with region 1 and are done when all 6 have
  // been on long enough.
  
  // Iterate through regions
  for (int region = 0; region <= 6; region++)
  {
    if (strokeCount > strokesPerRegion * region)
    {
      // Flash the currently active region
      digitalWrite(regionLEDs[region], (flashOn || (region < curRegion-1)) ? HIGH : LOW);
      
      // See if we're ready to move to the next region
      if (region == curRegion)
      {
        if (region > 0)
        {
          PlaySong(region + 1, tempoSlow);
        }
        curRegion++;
      }
    }
    
    // Turn off leds for all regions not yet handled
    if (region >= curRegion)
    {
      digitalWrite(regionLEDs[region], LOW);
    }
  }
 
  // All finished?  Have a little "you're all done" party
  if (curRegion == 7)
  {
      curRegion++;
      
      PlaySong(1, tempoFast);
      PlaySong(2, tempoFast);
      PlaySong(3, tempoFast);
      PlaySong(4, tempoFast);
      PlaySong(5, tempoFast);
      PlaySong(6, tempoFast);
  
      int i;
      int j;
      for (j=0; j<6; j++)
      {
        for (int i=0; i<6; i++)
        {    
          digitalWrite(regionLEDs[i], HIGH);
        }
  
        delay(250);

        for (int i=0; i<6; i++)
        {
          digitalWrite(regionLEDs[i], LOW);
        }
        delay(250);      
      }
  }

  // Count brush strokes 
  UpdateCount();
  
  // Count loop iterations for flashing things
  loopIterations++;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// freqout borrowed from Paul Badger, thanks Paul!
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////////

/*  freqout(freq, t)  // freq in hz, t in ms
*   Paul Badger 2007
*   a simple tone generation function
*   generates square waves of arbitrary frequency and duration
*   program also includes a top-octave lookup table & transposition function
*/

#include   // requires an Atmega168 chip 

#define outpin audioPWM   // audio out to speaker or amp

// note values for two octave scale
// divide them by powers of two to generate other octaves
float A     = 14080;
float AS    = 14917.2;
float B     = 15804.3;
float C     = 16744;
float CS    = 17739.7;
float D     = 18794.5;
float DS    = 19912.1;
float E     = 21096.2;
float F     = 22350.6;
float FS    = 23679.6;
float G     = 25087.7;
float GS    = 26579.5;
float A2    = 28160;
float A2S   = 29834.5;
float B2    = 31608.5;
float C2    = 33488.1;
float C2S   = 35479.4;
float D2    = 37589.1;
float D2S   = 39824.3;
float E2    = 42192.3;
float F2    = 44701.2;
float F2S   = 47359.3;
float G2    = 50175.4;
float G2S   = 53159;
float A3    = 56320;
float AS3   = 59669;
float B3    = 63217;
float C3    = 66976.2;

float REST  = 0;

// freqout
//
// Generate the tone
//
void freqout(int freq, int t)  // freq in hz, t in ms
{  
  // Added special handling for rests (avoids pops)
  if (freq == 0)
  {
    delay(t);
    return;
  }
 
  int hperiod;                               //calculate 1/2 period in us
  long cycles, i;

  pinMode(outpin, OUTPUT);                   // turn on output pin
  hperiod = (500000 / freq) - 7;             // subtract 7 us to make up for digitalWrite overhead
  cycles = ((long)freq * (long)t) / 1000;    // calculate cycles
  
  for (i=0; i<= cycles; i++)
  {
      // play note for t ms 
      digitalWrite(outpin, HIGH); 
      delayMicroseconds(hperiod);
      digitalWrite(outpin, LOW); 
      delayMicroseconds(hperiod - 1);     // - 1 to make up for digitaWrite overhead
  }
  pinMode(outpin, INPUT);                // shut off pin to avoid noise from other operations
}

float EIGHTH = 1;
float DOTTED_EIGHTH = 1.5;
float QUARTER = 2;
float DOTTED_QUARTER =3;
float HALF = 4;
float WHOLE = 8;
float ETERNITY =-1;

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// My Mario Music
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////////

int cNumSongs = 7;

float song0[] = 
  {
    E2,EIGHTH, E2,QUARTER, E2,EIGHTH, REST,EIGHTH, C2,EIGHTH, E2,QUARTER, G2,HALF, G,HALF, 
    REST,ETERNITY
  };
float song1[] = 
  {
    C2,DOTTED_QUARTER, G,EIGHTH, REST,QUARTER, E,QUARTER, REST,EIGHTH, A2,QUARTER, B2,EIGHTH, REST,EIGHTH, A2S,EIGHTH, A2,QUARTER,
    G,DOTTED_EIGHTH, E2,DOTTED_EIGHTH, G2,EIGHTH, A3,QUARTER, F2,EIGHTH, G2,EIGHTH, REST,EIGHTH, E2,QUARTER, C2,EIGHTH, D2,EIGHTH, B2,DOTTED_QUARTER,
    REST,ETERNITY  
  };
float song2[] = 
  {
    C2,DOTTED_QUARTER, G,EIGHTH, REST,QUARTER, E,QUARTER, REST,EIGHTH, A2,QUARTER, B2,EIGHTH, REST,EIGHTH, A2S,EIGHTH, A2,QUARTER,
    G,DOTTED_EIGHTH, E2,DOTTED_EIGHTH, G2,EIGHTH, A3,QUARTER, F2,EIGHTH, G2,EIGHTH, REST,EIGHTH, E2,QUARTER, C2,EIGHTH, D2,EIGHTH, B2,DOTTED_QUARTER,
    REST,ETERNITY  
  };
float song3[] = 
  {
    REST,QUARTER, G2,EIGHTH, F2S,EIGHTH, F2,EIGHTH, D2S,QUARTER, E2,EIGHTH, REST,EIGHTH, GS,EIGHTH, A2,EIGHTH, C2,EIGHTH, REST,EIGHTH, A2,EIGHTH, C2,EIGHTH, D2,EIGHTH,
    REST,QUARTER, G2,EIGHTH, F2S,EIGHTH, F2,EIGHTH, D2S,QUARTER, E2,EIGHTH, REST,EIGHTH, C3,QUARTER, C3,EIGHTH, C3,HALF,
    REST,ETERNITY  
  };
float song4[] = 
  {
    REST,QUARTER, G2,EIGHTH, F2S,EIGHTH, F2,EIGHTH, D2S,QUARTER, E2,EIGHTH, REST,EIGHTH, GS,EIGHTH, A2,EIGHTH, C2,EIGHTH, REST,EIGHTH, A2,EIGHTH, C2,EIGHTH, D2,EIGHTH,
    REST,QUARTER, D2S,QUARTER, REST,EIGHTH, D2,DOTTED_QUARTER, C2,HALF, REST,HALF,
    REST,ETERNITY  
  };
float song5[] = 
  {
    C2,EIGHTH, C2,QUARTER, C2,EIGHTH, REST,EIGHTH, C2,EIGHTH, D2,QUARTER, E2,EIGHTH, C2,QUARTER, A2,EIGHTH, G,HALF,
    C2,EIGHTH, C2,QUARTER, C2,EIGHTH, REST,EIGHTH, C2,EIGHTH, D2,EIGHTH, E2,EIGHTH, REST,WHOLE,
    REST,ETERNITY  
  };
float song6[] = 
{
    C2,EIGHTH, C2,QUARTER, C2,EIGHTH, REST,EIGHTH, C2,EIGHTH, D2,QUARTER, E2,EIGHTH, C2,QUARTER, A2,EIGHTH, G,HALF,
    E2,EIGHTH, E2,QUARTER, E2,EIGHTH, REST,EIGHTH, C2,EIGHTH, E2,QUARTER, G2,QUARTER, REST, HALF,
    REST,ETERNITY  
};

float* songs[] = 
{
  song0,
  song1,
  song2,
  song3,
  song4,
  song5,
  song6,
};

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// PlaySong()
//
// Just a little function to play  music and light LED's
//
void PlaySong(int songIndex, int tempo)
{
  
  if (songIndex < 0 || songIndex >= cNumSongs)
  {
    return;
  }
  
//  Serial.println("PlaySong");
  
  float* song = songs[songIndex];
  
  int x;
  for(x= 0; x<10000; x=x+2)
  {
    int noteval = (int)(song[x] / 64.0f);
    int dur = (int)((float)tempo * song[x+1]);
    
    if(dur < 0)
      break;
    
    freqout(noteval, dur);
 
    for (int i=0; i<6; i++)
    {
      boolean ledOn = (i == (millis() % 6));
      digitalWrite(regionLEDs[i], ledOn ? HIGH : LOW);
    }

    delay(10);
  }
}

4 comments:

Zoe said...

long page of programming there, Dad.

gubs said...

the program is too long but correct.

Thomas & Betts

Eric said...

Very cool, thanks for sharing!

hamsolo474 said...

that was pretty cool, im looking forward to seeing more projects