Gesture Clock Program Includes and  Defines

/*

  GestureClock.ino. Description:

  This is for the squarish wood box with a display in the middle and a sensor underneat it.

  It is a clock controlled by this sensor and a 3-way switch on the back.

  It contains:

  1. An 8-character red 14-segment display individually addressable with ASCII code via I2C.

  2. An APDS9660 optical sensor on a brealout module.

     This sensor has 2 active IR capabilities which are Proximity and Gesture.

     It has a third. passive, capability which measures Color.

     The sensor is always powered and draws 0.85 MA.

  3. An Arduino Mini-Pro 5V, 16 MHz microcontroller.

  4. A DS3231 real-time clock-calendar on a breakout module with a CR1220 backup battery.

  5. A size 18650 LiIon battery rated 2600 MAH.

  6. An Adafruit Power-Boost 500 Charger module

      This provides for charging the battery and provides 5V power.

  7. A circuit board for conditionung the 5V enable and measuring battery Volyage.

*/

/*

  Power Operation:

  The 3-way switch has overriding control control:

  UP - Always ON.

  Center - controlled by the Proximty Detector, and the software.

  DOWN - Always OFF.

  The 5V output is active-high enabled from any of 3 sources:

  1. The switch is UP.

  2. A high asserted from the port designated keepAlivePort.

  3. An active-low interrupt output from the sensor.

*/


/*

   Display Modes:        Controlled by RGB Color LED remote

   Display = true. Default  Selected in Setting mode by blue pulse

   Setting = false Selected in Display Mode by red pulse

*/


/*

   Display Mode options: controlled by LEFT / RIGHT swipe

   Left Swipe: Battery Voltage display = 0.

   Right swipe: cycles through these options:

   Time = 1 default.

   Date = 2.

   Year = 3.


   Display Mode options: controlled by UP / DOWN

   DOWN - power OFF

   UP reset timeout

*/


/*

  Setting Mode options: controlled by LEFT / RIGHT

  Left Swipe: Adjust hour = 0. default

  Right swipe: cycles through these options

  Adjust Minute = 1.

  Adjust  Date = 2.

  Adjust. Month = 3

  Adjust. Year = 4

  Adjust. Day of week = 5.


  Setting Mode options: controlled by UP / DOWN

  DOWN -decrease

  UP increase.


  Setting Mode options: controlled by Color

  green pulse: Save changes

  blue pulse: return to Display mode


  John Saunders 4/11.2024

*/


#include <Wire.h>                     // The RTC and the display use i2C

#include "SegDisp.h"

#include <ds3231.h>

#include "Adafruit_APDS9960.h"


#define messageSpeed 200

#define proxyLimit 35

#define keepAlivePort 12

#define battEnPort 11

#define refEnPort 10

#define battMeasPort A1

#define refMeasPort A2

#define redOffset 10

#define greenOffset 10

#define blueOffset 10

Gesture Clock Program Global Variables


// const float refVolt = 1234;  //By measurement mv

const float refVolt = 1270;  //For correction mv


//create the APDS9960 object

Adafruit_APDS9960 apds;

const uint8_t timeoutInterval = 3;      //minutes


ts now; //ts is a struct findable in ds3231.h


const char *fixed[20] = {

  //    0          1             2          3           4          5           6           7          8

  "This was", "made by ", "John    ", "Saunders", "in 2024 ", "        ", "  /  /  ", "   20   ", "BAT=   v",

  //   9        10           11          12           13           14         15         16          17

  "Mins=   ", "Hours=  ", "Date=   ", "Year=   ", "Month=  ", "Setup ON", " Saved  ", "Timeout ", "Manual  ",

  //   18       19

  "Shutdown", "Day=    "

};


// RTC time & date structures:

typedef struct {

  byte loc;                         // label index

  uint8_t val;                       // Value in adj

  byte ul;                          // Maximum item value

  byte ll;                          // Minimum item value

} adj_t;


adj_t adjList[] = {

  //   hour                     min                  date                 month                     year              weekday

  { 10, 0, 23, 0}, {9, 0, 59, 1}, {11, 0, 31, 1}, {13, 0, 12, 1},  {12, 0, 99, 24}, {19, 0, 7, 1}

};

bool displayMode; //true  = display, false = settings

unsigned long timeoutLimit;

int dispIndex;

int setIndex;

uint16_t rVal;

uint16_t gVal;

uint16_t bVal;

uint16_t cVal;

uint16_t cValBase;

uint8_t proxy;

uint8_t proxyBase;

Gesture Clock Program Utility Functions

void getAdjVals(void) {

  adjList[0].val = now.hour;

  adjList[1].val = now.min;

  adjList[2].val = now.mday;

  adjList[3].val = now.mon;

  adjList[4].val = now.year_s;

  adjList[5].val = now.wday;

}


void setAdjVals(void) {

  now.hour = adjList[0].val;

  now.min = adjList[1].val;

  now.mday = adjList[2].val;

  now.mon = adjList[3].val;

  now.year_s = adjList[4].val;

  now.wday = adjList[5].val;

  DS3231_set(now);

}


int getBattVolt(void) {

  int rawRefVal, rawBattVal;

  unsigned long battVal;

  digitalWrite(refEnPort, HIGH);

  digitalWrite(battEnPort, HIGH);

  delay(100);

  rawRefVal = analogRead(refMeasPort);

  rawBattVal = analogRead(battMeasPort);

  digitalWrite(battEnPort, LOW);

  digitalWrite(refEnPort, LOW);

  battVal = (rawBattVal * refVolt) / (5 * rawRefVal);

  return (int)battVal;

}

Gesture Clock  Display Functions


void dispFixed(int index, int dVal = 1000, bool writeout = false) {

  int addr = 0;

  for (int j = 0; j < 9; j++) {

    SegDisp_drawAscii(addr++, fixed[index][j], false);

  }

  if (writeout) {

    SegDisp_writeAlpha();

  }

  delay(dVal);

}


void displayWelcome(void) {                                 // At setup for personalisation

  for (int i = 0; i < 5; i++) {

    dispFixed(i);

    SegDisp_writeAlpha();

    delay(messageSpeed );

  }

}

const char weekdays[] = {"   MONTUEWEDTHUFRISATSUN"};


void displayWeekday(uint8_t loc) {    // Value in timeDate is 3

  char c;

  uint8_t addr = loc;

  uint8_t val = now.wday;

  if (val > 7) {

    val = 0;

  }

  for (byte i = 0; i < 3; i++)  {

    c = weekdays[((3 * val) + i)];

    SegDisp_drawAscii(addr++, c, false);

  }

}


const char months[] = {"   JANFEBMARAPRMAYJUNJLYAUGSEPOCTNOVDEC"};


void displayMonth(uint8_t loc) {

  char c;

  uint8_t addr = loc;

  uint8_t val = now.mon;

  if (val > 12) {

    val = 0;

  }

  for (byte i = 0; i < 3; i++)  {

    c = months[((3 * val) + i)];

    SegDisp_drawAscii(addr++, c, false);

  }

}

void displayNum(uint8_t loc, int val, bool leadZero = false, int dpLoc = 0) {

  byte c, r = 0;

  uint8_t addr = loc;

  bool dp = false;

  if (val < 0) {

    SegDisp_drawAscii(addr++, 0x2D, dp);

    val = abs(val);

  }

  if (dpLoc == 3) {

    dp = true;

  }

  if (val >= 1000) {

    c = (val / 1000) + 0x30;

    if ((leadZero == true) && (c == 0x30)) {

      SegDisp_drawAscii(addr++, 0x20, dp);

    }

    else {

      SegDisp_drawAscii(addr++, c, dp);

    }

    val %= 1000;

  }

  dp = false;

  if (dpLoc == 2) {

    dp = true;

  }

  if (val > 99) {

    c = (val / 100) + 0x30;

    if ((leadZero == true) && (c == 0x30)) {

      SegDisp_drawAscii(addr++, 0x20, dp);

    }

    else {

      SegDisp_drawAscii(addr++, c, dp);

    }

  }

  dp = false;

  if (dpLoc == 1) {

    dp = true;

  }

  r = val % 100;

  c = (r / 10) + 0x30;

  if ((leadZero == true) && (c == 0x30) && (val < 100)) {

    SegDisp_drawAscii(addr++, 0x20, false);

  }

  else {

    SegDisp_drawAscii(addr++, c, false);

  }

  if (dpLoc == 1) {

    SegDisp_drawAscii(addr++, 0x2E, false);

  }

  c = (val % 10) + 0x30;

  SegDisp_drawAscii(addr, c, false);

}


void displayTime(void) {

  dispFixed(6, 0);

  displayNum( 0, now.hour, false);

  displayNum( 3, now.min, false);

  displayNum( 6, now.sec, false);

  SegDisp_writeAlpha();

}


void displayDayMonth(void) {

  dispFixed(5, 0);

  displayWeekday(0);

  displayMonth(5);

  SegDisp_writeAlpha();

}


void displayDateYear(void) {

  dispFixed(7, 0);

  displayNum( 0, now.mday, false);

  displayNum( 5, now.year_s, false);

  SegDisp_writeAlpha();

}


void displayBatteryVolt(int volt) {

  dispFixed(8, 0);

  displayNum(4, volt, true, 2);

  SegDisp_writeAlpha();

}

Gesture Clock Program Setup


void setup() {

  pinMode(keepAlivePort, OUTPUT);

  digitalWrite(keepAlivePort, HIGH);

  pinMode(battEnPort, OUTPUT);

  pinMode(refEnPort, OUTPUT);

  Serial.begin(9600);

  Wire.begin(); //start i2c (required for connection)

  SegDisp_init();

  DS3231_init(DS3231_INTCN); //register the ds3231 (DS3231_INTCN is the default address of ds3231)


  // No leading zeroes. sec, mins, hours, date, month (jan = 1), year (YYYY), day (Monday = 1, Sunday = 7)

  now = {0, 21, 14, 2, 4, 2024, 4};

  //       DS3231_set(now);


  if (!apds.begin()) {

    Serial.println("failed to initialize apds device! Please check your wiring.");

  }

  else {

    Serial.println("apds initialzed");

  }


  apds.setLED(APDS9960_LEDDRIVE_50MA , APDS9960_LEDBOOST_100PCNT);

  apds.enableProximity(true);

  apds.setProxPulse(APDS9960_PPULSELEN_32US, 25);

  apds.enableGesture(true);

  apds.enableColor(true);

  apds.disableColorInterrupt();

  apds.disableProximityInterrupt();

  while (!apds.colorDataReady()) {

    Serial.print('-');

    delay(50);

  }

  apds.getColorData(&rVal, &gVal, &bVal, &cVal);

  cValBase = cVal + 5;

  Serial.println(cValBase);

  displayMode = true;

  dispIndex = 1;

  setIndex = 1;

  timeoutLimit =  60000 * timeoutInterval;

  proxy = apds.readProximity();

  proxyBase = proxy + 10;

  Serial.print("proximity =");

  Serial.println(proxy);

  apds.setGestureProximityThreshold(proxyBase);

  apds.setGestureOffset(10, 0, 10, 10);

}

Gesture Clock Program  Loop 


void loop() {

  static long cumBattVolt = 0;

  int battVolt;

  uint8_t gesture;

  static int loopCount = 60;


  proxy = apds.readProximity();

  if (proxy > proxyBase) {

    Serial.print("proximity =");

    Serial.println(proxy);

  }

  DS3231_get(&now);

  delay(10);



  // Exit on timeout in display mode

  if (millis() > timeoutLimit) {

    dispFixed(16, 1600, true);

    dispFixed(18, 2000, true);

    digitalWrite(keepAlivePort, LOW);

  }


  //Calculate Battery Voltage as average

  if (loopCount > 0) {

    loopCount--;

    cumBattVolt += getBattVolt();

  }

  else {

    battVolt = cumBattVolt / 60;

    cumBattVolt = 0;

    loopCount = 60;

  }

  apds.enableGesture();

  delay(100);

  gesture = apds.readGesture();

  delay(10);


  if (gesture > 0) {

    Serial.print("Gesture=");

    Serial.println(gesture);

  }


  if (displayMode) {

    if (gesture == APDS9960_DOWN) {

      dispFixed(17, 1500, true);

      dispFixed(18, 3000, true);

      digitalWrite(keepAlivePort, LOW);

    }

    if (gesture == APDS9960_UP) {

      timeoutLimit += (60000 * timeoutInterval);

      displayWelcome();

    }

    if (gesture == APDS9960_LEFT) {

      dispIndex = 0;

    }

    if (gesture == APDS9960_RIGHT) {

      dispIndex++;

      if (dispIndex > 3) {

        dispIndex = 1;

      }

    }


    switch (dispIndex) {

      case 0:

        displayBatteryVolt(battVolt);

        break;

      case 1:

        displayTime();

        break;

      case 2:

        displayDayMonth();

        break;

      case 3:

        displayDateYear();

        break;

    }

  }

  else {

    if (gesture > 0 ) {

      timeoutLimit = millis() + (60000 * timeoutInterval);

    }

    dispFixed(adjList[setIndex].loc, 0);

    displayNum(6, adjList[setIndex].val);

    SegDisp_writeAlpha();

    if (gesture == APDS9960_DOWN) {

      if (adjList[setIndex].val > adjList[setIndex].ll) {

        adjList[setIndex].val--;

      }

    }

    if (gesture == APDS9960_UP) {

      if (adjList[setIndex].val < adjList[setIndex].ul) {

        adjList[setIndex].val++;

      }

    }

    if (gesture == APDS9960_LEFT) {

      setIndex = 0;

    }

    if (gesture == APDS9960_RIGHT) {

      if (setIndex < 5) {

        setIndex++;

      }

      else {

        setIndex = 1;

      }

    }

  }

  apds.enableGesture(false);

  apds.getColorData(&rVal, &gVal, &bVal, &cVal);

  if (cVal > cValBase) {

    Serial.print("Color=");

    Serial.println(cVal);

  }

  if ((rVal > (gVal + bVal + redOffset)) && displayMode && (cVal > cValBase)) {                 //red

    displayMode = false;                                      //Go to seting mode

    setIndex = 1;

    dispFixed(14, 2000, true);

    getAdjVals();

  }

  if ((gVal > rVal) && (gVal > bVal) && !displayMode && (cVal > cValBase)) {                 //green not so strong

    dispFixed(15, 1000, true);                                          //Save changes to the Ds3231

    setAdjVals();

  }

  if ((bVal > (gVal + rVal + blueOffset)) && !displayMode && (cVal > cValBase)) {                 //blue

    displayMode = true;

    dispIndex = 1;

  }

  delay(50);

}