/*
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
// 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;
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;
}
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();
}
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);
}
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);
}