J Koger
Published © GPL3+

Googly Eye O-Watch

Inspired by Mike Mak's Eye Clock, now the O-Watch can show the time with two googly eyes: hours (left eye) and minutes (right eye).

BeginnerProtip1 hour828
Googly Eye O-Watch

Things used in this project

Hardware components

O Watch Base Kit
O Watch Base Kit
×1

Story

Read more

Code

google_eye_watch.ino

Arduino
Arduino code to display the Googly Eye Watch. Don't blink!
/*
 * Google Eye Watch
 *
 * Demonstrates the use of the... awwww, it's just a silly watch. Two big eyes
 * show the time: the position of the left eye's pupil shows the hour, and the right
 * eye's pupil shows the minute.
 * 
 * Inspired by Mike Mak's Eye Clock: http://www.mikemak.com/mike/main_2.php?id=11
 *
 * Uses Arduino Zero RTC library https://www.arduino.cc/en/Reference/RTC
 * Maintained by Arturo Guadalupi https://github.com/arduino-libraries/RTCZero
 *
 * This code is for the TinyScreen+ board by TinyCircuits used by O Watch http://tiny-circuits.com 
 *
 * This example is based on the Simple Watch code created by O Watch on 6 March 2016 http://theowatch.com
 * Modified to use RTC, to set the display brightness based on time of day, and to use __DATE__ and __TIME__
 * to preset the watch's date and time by J Koger 13 March 2016
 * Modified to also display a round analog clock by J Koger 14 March 2016
 * Modified to just display the hours and minutes as two googly eyes by J Koger 16 March 2016
 * 
*/

#include <TinyScreen.h> //include TinyScreen library
#include <RTCZero.h> //include the Arduino Zero's Real Time Clock library
#include <stdio.h>  // include the C++ standard IO library

/* Create an rtc object */
RTCZero rtc;

// We'll dynamically change these values to set the current initial time
// No need to change them here.
byte seconds = 0;
byte minutes = 45;
byte hours = 9;

// We'll dynamically change these values to set the current initial date
// The preset values are only examples.
byte days = 13;
byte months = 3;
byte years = 16;

int brightness = 15; // We'll set it, 3 - 15 based on time of day

TinyScreen display = TinyScreen(TinyScreenPlus); //Create the TinyScreen object

void setup()
{
  char s_month[5];
  int tmonth, tday, tyear, thour, tminute, tsecond;
  static const char month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";

  display.begin();                            //Initializes TinyScreen board
  display.setFlip(1);                         //Flips the TinyScreen rightside up for O Watch
  display.on();                               //Turns TinyScreen display on

  rtc.begin(); // initialize RTC
  
  // Set the time and date. Change this to your current date and time.
  // setTime(16,19,00,12,3,2016);    //values in the order hr,min,sec,day,month,year

  // Let's be lazy and let the compiler set the current date and time for us.
  // This will be a few seconds off due to the time it takes to compile the
  // .ino file and upload the app. But pretty close.

  // __DATE__ is a C++ preprocessor string with the current date in it.
  // It will look something like 'Mar  13  2016'.
  // So we need to pull those values out and convert the month string to a number.
  sscanf(__DATE__, "%s %d %d", s_month, &tday, &tyear);

  // Similarly, __TIME__ will look something like '09:34:17' so get those numbers.
  sscanf(__TIME__, "%d:%d:%d", &thour, &tminute, &tsecond);

  // Find the position of this month's string inside month_names, do a little
  // pointer subtraction arithmetic to get the offset, and divide the
  // result by 3 since the month names are 3 chars long.
  tmonth = (strstr(month_names, s_month) - month_names) / 3;

  months = tmonth + 1;  // The RTC library expects months to be 1 - 12.
  days = tday;
  years = tyear - 2000; // The RTC library expects years to be from 2000.
  hours = thour;
  minutes = tminute;
  seconds = tsecond;

  rtc.setTime(hours, minutes, seconds);
  rtc.setDate(days, months, years);
}

// Draw a circle. Slow but works. Taken from the O-Watch TinyScreen tutorial.
// http://www.theowatch.com/learn/o-watch-tinyscreen-programming/learn-tinyscreen-demo/
// 
void drawCircle(int x0, int y0, int radius, uint8_t color)
{
  int x = radius;
  int y = 0;
  int radiusError = 1-x;
 
  while(x >= y)
  {
    //drawPixel(x,y,color);//set pixel (x,y) to specified color. This is slow because we need to send commands setting the x and y, then send the pixel data.
    display.drawPixel(x + x0, y + y0, color);
    display.drawPixel(y + x0, x + y0, color);
    display.drawPixel(-x + x0, y + y0, color);
    display.drawPixel(-y + x0, x + y0, color);
    display.drawPixel(-x + x0, -y + y0, color);
    display.drawPixel(-y + x0, -x + y0, color);
    display.drawPixel(x + x0, -y + y0, color);
    display.drawPixel(y + x0, -x + y0, color);
    y++;
    if (radiusError<0)
    {
      radiusError += 2 * y + 1;
    }
    else
    {
      x--;
      radiusError += 2 * (y - x) + 1;
    }
  }
}


// We could draw a solid circle by drawing an outer circle,
// then one that's one pixel shorter radius inside of it,
// and another inside of it, etc.
//
// But instead we'll just draw lines between all the outline pixels
// we'd otherwise draw.
void drawSolidCircle(int x0, int y0, int radius, uint8_t color)
{
  int x = radius;
  int y = 0;
  int radiusError = 1-x;
 
  while(x >= y)
  {
    //drawPixel(x,y,color);//set pixel (x,y) to specified color. This is slow because we need to send commands setting the x and y, then send the pixel data.
    // display.drawPixel(x + x0, y + y0, color);
    // display.drawPixel(y + x0, x + y0, color);
    // display.drawPixel(-x + x0, y + y0, color);
    // display.drawPixel(-y + x0, x + y0, color);
    // display.drawPixel(-x + x0, -y + y0, color);
    // display.drawPixel(-y + x0, -x + y0, color);
    // display.drawPixel(x + x0, -y + y0, color);
    // display.drawPixel(y + x0, -x + y0, color);

    display.drawLine(x + x0, -y + y0 , x + x0, y + y0, color);
    display.drawLine(y + x0, -x + y0 , y + x0, x + y0, color);
    display.drawLine(-x + x0, -y + y0 , -x + x0, y + y0, color);
    display.drawLine(-y + x0, -x + y0 , -y + x0, x + y0, color);


    y++;
    if (radiusError<0)
    {
      radiusError += 2 * y + 1;
    }
    else
    {
      x--;
      radiusError += 2 * (y - x) + 1;
    }
  }
}


// Find the endpoint of a line from a given start point at a given angle for a given length.
//
// Don't use any floating point math such as the sine() function because it
// takes a long time to run and eats battery life on a computer without
// a floating point processor, like the Arduino Zero in the O-Watch.
// Instead, use a good old-fashioned hard-coded sine lookup table and
// a couple of integer math tricks.
//
// The underlying idea from a geometry standpoint is that we need to figure
// out the equivalent X,Y endpoint of a line that goes from the center of the
// circle (0,0) out to a length of radius 1.0 (the 'unit circle') at a given
// angle and multiply the length of our hand by that X,Y endpoint, then slide
// both the center of the circle and the enpoint over so that they match our actual.
// circle's coordinates.
// The sine and cosine of the unit circle are the X,Y endpoint that we need.
//
// Modified from: https://codebender.cc/example/glcd/clockFace#AnalogClock.cpp

void findRadiusEndpoint(int xStart, int yStart, int angle, int radius, int *xEnd, int *yEnd)

// xStart, yStart are starting point of hand (center of clock face)
// angle is location of hand on dial (0-60)
// radius is length of hand
// xEnd and yEnd are pointers to ints that will be filled in with the endpoint
{
  // Sines for hand positions from 0 to 15 minutes. All of the other
  // hand position sines can be determined by flipping x and y. These
  // are all multiplied by 256 so that we can use integers to store them.
  // Cosines can be also be determined by taking the sine of the angle minus
  // 15 degrees and flipping x and y.
  static int sine[16] = {0, 27, 54, 79, 104, 128, 150, 171, 190, 201, 221, 233, 243, 250, 254, 255};
  int quadrant, x_flip, y_flip;

  // calculate which quadrant the hand lies in
  quadrant = angle/15 ;

  // We could have a bigger table of sine values that handle every hand position from 0 to 60,
  // but we can avoid using that memory by doing some quick reflections and rotations.
  switch ( quadrant )
  {
    case 0 : x_flip = 1 ; y_flip = -1 ; break ;
    case 1 : angle = abs(angle-30) ; x_flip = y_flip = 1 ; break ;
    case 2 : angle = angle-30 ; x_flip = -1 ; y_flip = 1 ; break ;
    case 3 : angle = abs(angle-60) ; x_flip = y_flip = -1 ; break ; 
    default:  x_flip = y_flip = 1; // this should not happen
  }

  *xEnd = xStart;
  *yEnd = yStart;
  // OK, here's the tricky part. Look up the sine for the hand
  // angle, then multiply it by the hand length. Then, because
  // our sine table values are all pre-multiplied by 256 so we
  // can store them as integers, we need to divide by 256 to
  // correct for that. Which is, in integer math, the same as
  // saying that we'll shift the values right by 8 bit positions.
  // Doing a shift-right is faster than doing an integer divide by 256
  // (the compiler is probably smart enough to turn that
  // divide by 256 into a right shift anyway, but let's do
  // an explicit shift because it's cool).
  //
  // The equivalent floating-point call would look something like:
  // xEnd = sine(angle) * radius;
  *xEnd += (x_flip * (( sine[angle] * radius ) >> 8));
  *yEnd += (y_flip * (( sine[15-angle] * radius ) >> 8));
}


void displayTime()
{

  display.on();                               //Turns TinyScreen display on

 
 enum
 {
    leftEyeCenterX = 23,
    leftEyeCenterY = 32,
    rightEyeCenterX = 73, // 96 - 23
    rightEyeCenterY = 32,
    eyeRadius = 22,
    pupilRadius = 16,
    backgroundColor = TS_8b_Blue,
    eyeColor = TS_8b_White,
    pupilColor = TS_8b_Black,
    pupilRadialCenter = eyeRadius - pupilRadius
  };

  display.drawRect(0, 0, 96, 64, 1, backgroundColor); // Give our googly eyes a nice blue background. Cooookeeee!!!
  
  for (int i = 0; i < 2; i++) // Display for 2*5000 milliseconds (10 seconds), update/blink eyes each period
  {
    // The second time around, this full redraw will make the eyes look like they're blinking!
    
    drawSolidCircle(leftEyeCenterX, leftEyeCenterY, eyeRadius, eyeColor);
    drawSolidCircle(rightEyeCenterX, rightEyeCenterY, eyeRadius, eyeColor);

    drawCircle(leftEyeCenterX, leftEyeCenterY, eyeRadius, pupilColor);
    drawCircle(rightEyeCenterX, rightEyeCenterY, eyeRadius, pupilColor);

    // Get the hours and minutes, one for each eye.
    hours = rtc.getHours();
    minutes = rtc.getMinutes();

    // The hour is 0 - 23. But we need it
    // to be an 'angle' of 0 - 59. So
    // convert 24-hour time to 12 hour time,
    // and then multiply by 60 / 12 = 5.
    int hourAngle;
    if (hours >= 12)
      hourAngle = hours - 12;
    else
      hourAngle = hours;
    hourAngle *= 5;
    // To give the hour hand a real
    // analog-clock feel, it shouldn't point
    // straight at the hour, but should move between
    // the hours as the minute hand sweeps around.
    // So, let's nudge it forward a smidge by
    // the minutes scaled down to the space within 1 hour, or
    // divide them by 12.
    hourAngle += minutes / 12;

    int leftPupilCenterX, leftPupilCenterY, rightPupilCenterX, rightPupilCenterY;
    
    findRadiusEndpoint(leftEyeCenterX, leftEyeCenterY, hourAngle, pupilRadialCenter, &leftPupilCenterX, &leftPupilCenterY);
    findRadiusEndpoint(rightEyeCenterX, rightEyeCenterY, minutes, pupilRadialCenter, &rightPupilCenterX, &rightPupilCenterY);
    drawSolidCircle(leftPupilCenterX, leftPupilCenterY, pupilRadius, pupilColor);
    drawSolidCircle(rightPupilCenterX, rightPupilCenterY, pupilRadius, pupilColor);
    
    if (hours <= 12)
      brightness = hours + 3; // 0 hours = 3 brightness, noon = 15
    else if (hours >= 18)
      brightness = (24 - hours) * 2 + 2;  // 23 hours = 4 brightness, 18 hours = 14
    else
      brightness = 15; // full brightness all afternoon
    if (brightness < 3)
      brightness = 3;
    if (brightness > 15)
      brightness = 15;
    display.setBrightness(brightness);                  //Set display brightness 0 - 15


    delay(5000); //display for 5 seconds
  }

  display.off();                               //Turns TinyScreen display off
}

void loop()
{
   if (display.getButtons())
   {
      displayTime();
   }
}

Credits

J Koger

J Koger

10 projects • 4 followers
Thanks to Mike Mak.

Comments