#include <Stepper.h>
#include <WiFi.h>
#include <Adafruit_NeoPixel.h>
#include <ArduinoJson.h>
#include <UUID.h>
#include <queue>
#include <ctime>
#include <sstream>
#include <string>
#include <chrono>
#include <algorithm>
#include <random>
#include "time.h"
#include "timer.h"
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 0;
const int daylightOffset_sec = 3600;
const int stepsPerRevolution = 200;
#define IN1_1 23
#define IN1_2 22
#define IN1_3 4
#define IN1_4 2
#define IN2_1 21
#define IN2_2 19
#define IN2_3 18
#define IN2_4 5
#define LED_PIN 33
// update the time every 1000 miliseconds
#define RESOLUTION_TIME 1000
#define RESOLUTION_TIMER 20
int lastTimeUpdate = 0;
int lastTimerUpdate = 0;
Stepper stepperMin(stepsPerRevolution, IN2_4, IN2_3, IN2_2, IN2_1);
Stepper stepperHour(stepsPerRevolution, IN1_4, IN1_3, IN1_2, IN1_1);
int rotationMin = 0;
int rotationHour = 0;
tm lastTime;
tm currentTime;
// When we setup the NeoPixel library, we tell it how many pixels, and which pin to use to send signals.
// Note that for older NeoPixel strips you might need to change the third parameter--see the strandtest
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(61, LED_PIN, NEO_GRB + NEO_KHZ800);
void printLocalTime()
{
getTime();
Serial.println(¤tTime, "%m %d %Y / %H:%M:%S");
}
void getTime() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
Serial.println("Failed to obtain time");
} else {
currentTime = timeinfo;
}
}
void setup() {
// set the speed at 20 rpm for one stepper, 90 rpm for another:
stepperMin.setSpeed(120);
stepperHour.setSpeed(120);
// initialize the serial port:
Serial.begin(9600);
//connect to WiFi
Serial.printf("Connecting to %s ", ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(" CONNECTED");
//init and get the time
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
resetClock();
printLocalTime();
pixels.begin();
}
void resetClock() {
rotationMin = 0;
rotationHour = 0;
Serial.println("Done A Reset... in theory");
}
void incrementTime() {
float stepAngle = (float)360 / (float)stepsPerRevolution;
getTime();
int hour = currentTime.tm_hour;
int minute = currentTime.tm_min;
int second = currentTime.tm_sec;
// normalise the hour to 12 hour hand clock
if (hour > 11) {
hour = hour - 12;
}
// angle of hour hand
float hourAngle = ((float) 360 / (float) 12) * (float) hour;
hourAngle += ((float) 360 / (float) 12) * ((float) minute / (float) 60);
int targetStepsHour = std::round(hourAngle / stepAngle);
if (targetStepsHour < rotationHour) {
targetStepsHour += stepsPerRevolution;
}
//move hour hand
int stepsToMoveHour = targetStepsHour - rotationHour;
stepperHour.step(stepsToMoveHour);
rotationHour = targetStepsHour;
float minuteAngle = ((float) 360 / (float) 60) * (float) minute;
minuteAngle += ((float) 360 / (float) 60) * ((float) second / (float) 60);
int targetStepMinute = std::round(minuteAngle / stepAngle);
//move minute hand
if (targetStepMinute < rotationMin) {
targetStepMinute += stepsPerRevolution;
}
int stepsToMoveMin = targetStepMinute - rotationMin;
stepperMin.step(stepsToMoveMin);
rotationMin = targetStepMinute;
if (rotationMin >= 200) {
rotationMin = rotationMin - stepsPerRevolution;
}
if (rotationHour >= 200) {
rotationHour = rotationHour - stepsPerRevolution;
}
}
// Create a priority queue for storing the timers
std::priority_queue<Timer> timers;
// Define a function to generate a random UUID
UUID generate_uuid() {
UUID uuid;
return uuid;
}
// Define a function to add a timer with a given name and UTC time string
void add_timer_from_utc(std::string utc_time, std::string name) {
// Generate a UUID for the new timer
UUID uuid = generate_uuid();
// Convert the UTC time string to an end time
struct tm tm = {};
strptime(utc_time.c_str(), "%Y-%m-%dT%H:%M:%SZ", &tm);
time_t end_time_t = mktime(&tm);
unsigned long end_time = end_time_t * 1000;
// Create a new timer with the specified end time, name, and UUID
Timer new_timer = { end_time, name, uuid };
// Add the new timer to the priority queue
timers.push(new_timer);
}
// Define a function to add a timer with a given name and relative offset
void add_timer_from_offset(int hours, int minutes, int seconds, std::string name) {
// Generate a UUID for the new timer
UUID uuid = generate_uuid();
// Calculate the end time based on the current time and the relative offset
unsigned long end_time = millis() + (hours * 3600000) + (minutes * 60000) + (seconds * 1000);
// Create a new timer with the specified end time, name, and UUID
Timer new_timer = { end_time, name, uuid };
// Add the new timer to the priority queue
timers.push(new_timer);
}
void rgb2hsb(uint8_t r, uint8_t g, uint8_t b, float hsb[3]) {
float r_norm = r / 255.0;
float g_norm = g / 255.0;
float b_norm = b / 255.0;
float cmax = max(r_norm, max(g_norm, b_norm));
float cmin = min(r_norm, min(g_norm, b_norm));
float delta = cmax - cmin;
float hue;
if (delta == 0) {
hue = 0;
} else if (cmax == r_norm) {
hue = fmod((g_norm - b_norm) / delta, 6);
} else if (cmax == g_norm) {
hue = ((b_norm - r_norm) / delta) + 2;
} else {
hue = ((r_norm - g_norm) / delta) + 4;
}
hue = hue * 60;
if (hue < 0) {
hue += 360;
}
float saturation = (cmax == 0) ? 0 : (delta / cmax);
float brightness = cmax;
hsb[0] = hue;
hsb[1] = saturation;
hsb[2] = brightness;
}
void hsb2rgb(float hue, float saturation, float brightness, uint8_t rgb[3]) {
float c = brightness * saturation;
float x = c * (1 - fabs(fmod(hue / 60, 2) - 1));
float m = brightness - c;
float r, g, b;
if (hue < 60) {
r = c;
g = x;
b = 0;
} else if (hue < 120) {
r = x;
g = c;
b = 0;
} else if (hue < 180) {
r = 0;
g = c;
b = x;
} else if (hue < 240) {
r = 0;
g = x;
b = c;
} else if (hue < 300) {
r = x;
g = 0;
b = c;
} else {
r = c;
g = 0;
b = x;
}
rgb[0] = static_cast<uint8_t>((r + m) * 255);
rgb[1] = static_cast<uint8_t>((g + m) * 255);
rgb[2] = static_cast<uint8_t>((b + m) * 255);
}
uint32_t hash_uuid(const UUID& uuid) {
// Get a pointer to the UUID bytes
const uint8_t* uuid_bytes = reinterpret_cast<const uint8_t*>(&uuid);
// Hash the UUID using the FNV-1a algorithm
const uint32_t FNV_OFFSET_BASIS = 0x811c9dc5;
const uint32_t FNV_PRIME = 0x01000193;
uint32_t hash = FNV_OFFSET_BASIS;
for (size_t i = 0; i < sizeof(UUID); i++) {
hash ^= static_cast<uint32_t>(uuid_bytes[i]);
hash *= FNV_PRIME;
}
return hash;
}
// Define a function to generate a pastel RGB color value based on a UUID
uint32_t get_pastel_color_for_uuid(const UUID& uuid, float saturation = 0.5, float brightness = 0.9) {
uint32_t hash = hash_uuid(uuid);
// Convert the hash to an HSB color
float hsb[3] = { static_cast<float>(hash % 360), saturation, brightness };
// Convert the HSB color to an RGB color
uint8_t rgb[3];
hsb2rgb(hsb[0], hsb[1], hsb[2], rgb);
// Convert the RGB color to a 32-bit color
uint32_t color = (static_cast<uint32_t>(rgb[0]) << 16) | (static_cast<uint32_t>(rgb[1]) << 8) | static_cast<uint32_t>(rgb[2]);
return color;
}
void parseTimerOffset(StaticJsonDocument<300> doc) {
// get hours/mins/seconds until timer finishes and build end time.
int hoursLeft = 0;
if (doc["payload"]["hours"]) {
hoursLeft = doc["payload"]["hours"];
}
int minsLeft = 0;
if (doc["payload"]["minutes"]) {
minsLeft = doc["payload"]["minutes"];
}
int secsLeft = 0;
if (doc["payload"]["seconds"]) {
secsLeft = doc["payload"]["seconds"];
}
const char* name = "";
if (doc["payload"]["name"]) {
name = doc["payload"]["name"].as<const char*>();
}
// Calculate the end time based on the current time and the relative offset
unsigned long end_time = millis() + (hoursLeft * 3600000) + (minsLeft * 60000) + (secsLeft * 1000);
add_timer_from_offset(hoursLeft, minsLeft, secsLeft, name);
}
void parseTimerAbsolute(StaticJsonDocument<300> doc) {
const char* absTime = "1970-01-01T00:00:00Z";
if (doc["payload"]["time"]) {
absTime = doc["payload"]["time"].as<const char*>();
}
const char* name = "";
if (doc["payload"]["name"]) {
name = doc["payload"]["name"].as<const char*>();
}
add_timer_from_utc(absTime, name);
}
void parseCommand(String commandString) {
StaticJsonDocument<300> doc;
DeserializationError error = deserializeJson(doc, commandString);
// Test if parsing succeeds.
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
return;
}
if (doc["command"] == "setTimerOffset") {
Serial.println("Set Timer Offset");
parseTimerOffset(doc);
} else if (doc["command"] == "setTimerAbsolute") {
Serial.println("Set Timer Absolute");
parseTimerAbsolute(doc);
}
}
void catchCommand() {
if (Serial.available()) { // if there is data coming
String command = Serial.readStringUntil('\n');
if (command.substring(0, 4) == "CMD ") {
Serial.println("RECEIVED - " + command);
parseCommand(command.substring(4));
}
}
}
void set_timer_leds(int seq, Timer timer) {
int now = millis();
int millis_left = timer.end_time - now;
int secs_left = round(millis_left / 1000);
uint32_t color = get_pastel_color_for_uuid(timer.uuid);
if (seq == 0) {
color = pixels.Color(255, 255, 255);
}
// less than a minute left
if ( secs_left < 61 ) {
// 60 = 0
set_leds_for_timer(secs_left, color);
if (seq == 0) {
set_all_leds_timers(secs_left, color);
}
}
// more than a minute, but less than an hour
if ( secs_left > 60 && secs_left < (60 * 60 + 1)) {
int mins_left = ceil(secs_left / 60);
set_leds_for_timer(mins_left, color);
if (seq == 0) {
set_all_leds_timers(mins_left, color);
}
}
// more than an hour, but less than 60 hours
if ( secs_left > 60 * 60 && secs_left < (60 * 60 * 60 + 1)) {
int hours_left = ceil(secs_left / (60 * 60));
set_leds_for_timer(hours_left, color);
}
// more than 60 hours
if ( secs_left > 60 * 60 * 60) {
set_leds_for_timer(60, pixels.Color(255, 0, 0));
}
}
void set_leds_for_timer(int count, uint32_t color) {
int pixel_id = count - 1;
if (count == 60) {
pixel_id = 0;
}
pixels.setPixelColor(pixel_id, color);
}
void set_all_leds_timers(int count, uint32_t color) {
for (int j = 0; j < count; j++) {
int pixel_id = j;
if (j == 60) {
pixel_id = 0;
}
pixels.setPixelColor(pixel_id, color);
}
}
void evaluateTimers() {
// Get current time
unsigned long now = millis() / 1000;
// Check if the top timer has expired
if (!timers.empty() && timers.top().end_time <= millis()) {
// Remove the top timer and print a message
Timer t = timers.top();
timers.pop();
Serial.print("Timer expired: ");
Serial.println(t.name.c_str());
endTimerLeds();
}
std::priority_queue<Timer> temp = timers;
int timer_seq = 0;
pixels.clear();
while (!temp.empty()) {
Timer t = temp.top();
temp.pop();
set_timer_leds(timer_seq, t);
timer_seq++;
}
pixels.show();
}
void fadeToColor(uint32_t color, int fadeTime) {
int steps = 60;
int r = (color >> 16) & 0xFF;
int g = (color >> 8) & 0xFF;
int b = color & 0xFF;
for (int i = 0; i < steps; i++) {
int fadeVal = i * (255 / steps);
for (int j = 0; j < (pixels.numPixels() - 1); j++) {
pixels.setPixelColor(j, pixels.Color((r * fadeVal) / 255, (g * fadeVal) / 255, (b * fadeVal) / 255));
}
pixels.show();
delay(fadeTime / steps * 2);
}
for (int i = 0; i < steps; i++) {
int fadeVal = 255 - (i * (255 / steps));
for (int j = 0; j < (pixels.numPixels() - 1); j++) {
pixels.setPixelColor(j, pixels.Color((r * fadeVal) / 255, (g * fadeVal) / 255, (b * fadeVal) / 255));
}
pixels.show();
delay(fadeTime / steps * 2);
}
}
void endTimerLeds() {
for (int i = 0; i < 3; i++) {
fadeToColor(pixels.Color(255, 255, 255), 1000);
}
}
void loop() {
if (millis() > (lastTimeUpdate + RESOLUTION_TIME)) {
getTime();
lastTimeUpdate = millis();
printLocalTime();
incrementTime();
}
if (millis() > (lastTimerUpdate + RESOLUTION_TIMER)) {
getTime();
lastTimerUpdate = millis();
evaluateTimers();
}
catchCommand();
}Loading
epaper-2in9
epaper-2in9