Skip to content

Final Project

TimeFlow
It’s a clock and timer, with an intuitive design. Flip the clock like you would a hourglass and it turns into a timer. No switches, no buttons, just one dial to control it all. Additionally, it’s a smart device capable of fetching data off the net. It could be the price of a stock, bitcoin or simply the weather. I chose to get Islamic Prayer timings, as they change daily and would perfectly fit the theme of time keeping.

Brain-storming

Many ideas came to mind for the final project, but I set out to select a project that allows me to mix 3D printing with electronics components and programming, and then be able to offer all the schematics and STL files for others to create this project at home. After going through my ideas I decided on this one: TimeFlow.

Some other functionality ideas I had was to incorporate in the TimeFlow was a particulate sensor or air quality sensor or Humidity/Temperature sensor to give an overall air quality rating in an institutive way.

Research and Planning

Micro-Processor

SeeedStudio Xiao S3

I went through a few board but ended up using the SeeedStudio Xiao S3 as it is tiny, powerful and inexpensive. Other boards used were:

  • Arduino UNO R3
  • Arduino Nano
  • Arduino MKR1010

Time Keeping

DS3231 Real Time Clock (RTC) Module

An arduino or even a computer, is unable to track time when it is powered off and unplugged. Therefor most computer motherboards house a small battery on them to keep track of time even when unplugged. This is why we need an RTC module for our project, to be able to keep track of time even when we unplug it.

Best of all, the battery is estimated to stay anywhere from 5 to 10 years depending on the storing conditions!

Motion Detection

Tilt Switch

Initially I started with a GY-521 MPU-6050 Gyro + 3 Axis Accelerometer Module. I also managed to get it working, however someone at Fablab gave a comment, “why not use a tilt switch?”. I couldn’t agree more, it is simpler to add to our circuit, much lighter to program and less costly. I love hearing what others have to say because you can get valuable advice like this. The only thing we have to consider is how we layout the tilt switch during our soldering phase, and the debounce logic in the programming phase.

Display

MAX7219 Dot Matrix Module

I think I found a display that is perfect for this project, and I have not seen many people use it in their own projects! This display combines four 8x8 dot matrix displays in one and includes the MAX7219 chip, significantly reducing the need to do soldering work, making everything less bulk. I am surprised that less students use this great screen. The only drawback is it does draw a bit of power and also comes in single colors only (Red, Green or Blue)

Controls

I was initially planning to mount multiple buttons, but then I though that a rotary encoder will do it all and would look more elegant. It is also intuitive to use, as no labels or instructions are needed on multiple buttons, it’s just turn and press.

Casing

3D Printing

I knew that I wanted to 3D design a case so anyone can print it and try it out. It also allows me to exactly fit all the parts inside the case. It also allows us to print the case in many colors or materials.

Dock

CNC Cutting

I wanted to design an elegant looking dock, that looks like a high end piece of furniture. I also wanted the ability to dock the Time-Flow in horizontal and vertical orientations.

Why doesn’t it have a Battery?

This is probably the most common question I get, so, here is the reasoning.

To add a battery system in the project we will also need to add:

  • Well, a battery, I found a suitable one 3,700 mah 3.7v (added a bit of weight which was acceptable), 2,000 mah also works
  • The selected display operates on 5V, initial testing shows it worked with 3.3V with significantly reduced brightness and alot of random flickering
  • A charging circuit is also needed, or alternatively a board with a charging circuit (Out of all the boards I used only the MKR1010 and Xaio S3 have that)
  • A power switch for the user to turn the clock on and off (takes away from the simplicity concept)
  • A charging mechanism which again adds to the size of the device (I explored charging via prongs and wireless charging, and even had them on hand)
  • Finally, my goal is to create something that others can make at home. The above points adds more parts and restrictions on the boards we can use

As you can see, adding a battery has a lot of constraints that overall was not worth the tradeoffs. Given the two weeks time constraints, and my goal to make something simple and visually appealing, I opted to drop the battery.

Materials

Qty Description Price Link Notes
1 SeeedStudio Xiao S3 9.9 BHD https://www.bitware.store/product/seeed-studio-xiao-esp32c3-esp32s3-s3-c3-s3-with-camera/ S3 without the camera
1 MAX7219 4 in 1 module 4.4 BHD https://www.bitware.store/product/max7219-dot-matrix-module-compatible-with-arduino/
1 Tilt Switch 0.33 BHD www.bitware.store
1 DS3231 Real Time Clock (RTC) Module 3.3 BHD https://www.bitware.store/product/ds3231-at24c32-high-precision-clock-module-iic-module/
1 Screws 0 $ Found laying around
1 USB Type C Cable 0 $ Found laying around

Total is under BHD 18 for the parts!

Electronic Circuit

My initial circuit was tested with an Arduino Nano, an RTC Module without a battery and the Matrix Dot Display all on a breadboard. The test was a success.

The RTC Module is designed to charge the button battery, however I am using a CR3032 battery so the charging circuit needs to be disabled. We just need to remove a single resistor and that should work.

Next, I added a rotary encoder and a gyro module, along with resistors for the i2c module. I was able to get either the Gyro or RTC module to work, as they were interfering with each other. I looked at the resistance with the a multimeter to ensure the pull-up resistance was in spec, I also made sure there were no address clashes and some other basic checks, however I was still facing issues with the Gyro module, but everything else was working together nicely.

At this point, I shifted the circuit to a MKR1010 and briefly tested if a battery would power the circuit. Everything powered up and the battery charges via the USB. However the MAX7219 did’t really like operating on 3.3V, and was dim and flickering. Additionally as discussed in the Research and Planning portion, there were far too many drawbacks, just to add the portability to a device that would mostly stay stationary.

Shortly after I got the recommendation to see if a tilt switch works. It worked perfectly. I also wanted to put together an initial prototype on a vero-board, this was a bit of a mistake as we will see.

So after I put things together i realized that the rotary knob doesn’t really work in such a position! Learning from my mistake I laid all the parts out on one of my early 3D prints and figured out how to mount each electronic circuit. I also noticed that the MKR1010 is long and quite tall as it has its own headers already.

I once again changed board, this time it was the SeeedStudio Xiao S3 running off 5V USB power. Now, let’s try this again.

Here is the initial layout, MUCH better! The dot matrix display and rotary encoder would mount on the 3D case itself.

Let’s prepare the board, as it comes without any headers installed

Let us put the whole circuit together again on a vero-board

Final results

Quite compact, I loved it!

Wiring Diagram

Rotary Encoder:

  • GND>GNC
  • VCC>VCC
  • SW>D1
  • DT> D3
  • CLK> D2

Tilt Switch:

  • GND>GND
  • VCC>D0

Real-Time Clock (i2c):

  • GND>GNC
  • VCC>VCC
  • SDA>D4
  • SCL>D5

Dot Matrix Display (SPI):

  • VCC>VCC
  • GND>GNC
  • DIN>D10(MOSI)
  • SCK>D8(CLK)
  • SS>D7(CS)

Unused Pins for adding future functionality:

  • D6
  • D9

3D Design and Printing

I used fusion to design my 3D Case, and downloaded a the 3D Model of the Dot Matrix Display and Rotary encoder off grabcad to be able to align the parts in the software.

The idea was to put squares infront of the display, to give the pixels a more square shape, as the bare LEDs are circular. My first test for the Dot Matrix Display actually turned out nearly perfect as shown in this image!

I also tested many materials infront of the display, but ended up opting for printing a thin flat surface infront of the display. Here is an image of the bare LED’s going through the grid I printed with a piece of paper infront of it all.

Here I was done with the initial layout, but I needed to figure out how to keep things in place.

I started to then test many mechanisms to fit the display and rotary encoder in place, I ended up printing 6 versions! The knob also had many iterations, but the first one I printed worked perfectly, and it was mainly cosmetic changes to make the knob sit more flush, and feel better to use.

One of my early attempts was to make a long post that would fit in the screw holes, but they were extremely fragile, the reasons were: . The post is too long, so force on the top part, exudes a much greater force on the bottom part . The only thing supporting the whole post structure is the bottom part with a diameter of 1mm, which is just not strong enough

Creating shorter posts, with a chamfer to the bottom, greatly strengthened the posts. However they still were not going to hold out with wear and tear.

We reached 4 versions by this point

I also added in a clip in the middle, which also worked perfectly from the first try. The clips also have an extention ontop that acts as a rim for the back cover!

I failed to account that the clip would obstruct the circuit we made, so I split the clips to the left and right, this ended up being more secure and also provided better support for the back cover.

We now have a screw mountable rotary switch! We just need to use the printed pilot holes and a wood screw to prepare them for our smaller screws.

Here is a picture of me putting it all together

And the screws after installation

Done! I love how the circuit is nearly organized inside, the back cover press fits in, and has a small hole for the cable to run out too

I made my final knob significantly bigger, which made it much easier to use

Here are all the versions

and the knob versions, prior to the last design

CNC Cutting

The concept behind the shape of the dock was inspired by the bottom of an hourglass, as this is a digital version of that. Once the size of the case was locked in, I got started working on the CNC machine to create a wooden dock. We found a nice natrual wood block laying around in the workshop, and we opted to go with it. Before that, we started out testing on an MDF board.

After that first cut, I realized that we turn the device, it didn’t have much support, so I decided to add a 2nd indent in the design for the vertical position, this would be completely invisible in both horizontal and vertical positions!

Next it was time to cut the actual board.

Midway, I also made sure everything fits. The back cover was not needed as it fits inside the main body.

Done!

Next comes the sanding!

The block was sanded in this order 120 > 320 > 400 > 800 > 1000

The edges were also sanded in a way to round it nicely, removing any sharp edges.

For the finish I chose a Beewax / Orange Oil made for natural woods.

The final round of sanding was done during the oil application for a better seal.

The dock ended up looking very professionally done!

Project Code

Research

Using the MD_Parola and MD_MAX72XX libraries I was quickly able to get the time to display on the screen. However, I needed to find a more compressed font for the numbering as the time format can be as long as “12:59PM” which wouldn’t fit on the screen. Additionally I had to create many custom characters that we will later see.

The tilt mechanism needed was tuned by positioning the tilt switch in a way that triggers at the right time, and by adding a de-bounce to the flag that switches modes.

You can go through my code below with all the in-line comments explaining everything, along with sources for all the code reuse.

Custom Characters

To create the custom characters I used this web-based editor: https://pjrp.github.io/MDParolaFontEditor

For the Clock / Prayer Timings:

For the Timer, I had to create a set of characters but flipped, these characters were also created from scratch:

Code

/* TimeFlow - Clock / Prayer Timings / Timer - Project by Ahmed AlKooheji
 * MAX7219 Code from how2electronics.com (Modified)
 * Code for rotary encoder by Dejan Nedelkovski howtomechatronics.com (Modified)
 * Prayer time JSON parsing done by "DIY Usthad" (Almost Stock, modified to create a prayer table array for UI)
 * dotnetlovers.com had a custom font for the MAX7219 which came in handy. Further modifications were done on some characters, also added Arabic Prayer Names and AM PM signs (Modified)
 * Code snippet for 24 to 12 hour time conversion was from Adafruit, it was modified to also add the AM and PM sign (Modified)*/

//MAX7219 Screen Libraries
#include <MD_Parola.h>
#include <MD_MAX72xx.h>

//To connect to the MAX7129 Screen
#include <SPI.h>

//RTC Module Libraries
#include <RTClib.h>
#include <Wire.h>

//Wifi Library
#include "WiFi.h"

//Athan Parsing libraries (Using HTTP to connect, JSON for data structure)
#include <HTTPClient.h>
#include <ArduinoJson.h>

//Define new RTC
RTC_DS3231 rtc;

int leftTilt; //Flag for detecting tilting of the FlowTime
int mode=2; //2 is clock, 1 is timer
int clockMode=1; //1 is standard clock, 2 is prayer clock

//If you are having issues with reversed characters or glitching, try the other hardware type
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
//#define HARDWARE_TYPE MD_MAX72XX::GENERIC_HW

#define MAX_DEVICES 4
#define CS_PIN D7

//Defning the display
MD_Parola myDisplay = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);

//The following are the declarations for the Rotary Encoder controls
// Rotary Encoder Inputs
#define CLK D2
#define DT D3
#define SW D1
#define TS D0

//Flag for 24 Hour mode.
#define TIME_24_HOUR false

int counter = 0; //Counter for the timer, logic currently treats it as seconds for demo purposes
int aState; //Current Rotation State
int aLastState; //Last Rotation State
unsigned long lastButtonPress = 0; //to save the time when the button was clicked
unsigned long trackTime=0; //To track the time for the counter once it is started
unsigned long lastTilt = 0; //to save the tilt switch was clicked for debouncing
String ap=""; //AM PM characters. - is AM and + is PM
int hour=0;
String currentDir ="";

// Replace with your network credentials
const char* ssid = "BSC-GF";
const char* password = "bsc@2030";

// Athan Constants, not all of them are used in this code
const char* data_timings_Fajr ;
const char* data_timings_Sunrise ;
const char* data_timings_Dhuhr ;
const char* data_timings_Asr ;
const char* data_timings_Sunset ;
const char* data_timings_Maghrib ;
const char* data_timings_Isha ;
const char* data_timings_Imsak ;
const char* data_timings_Midnight ;

//Defining the prayer timings array, for Ramadhan we can add Imsak timings.
String athanTimings[5][2]={
{"Fajr", ""},
{"Dhuhr", ""},
{"Asr", ""},
{"Maghrib", ""},
{"Isha", ""}
};

MD_MAX72XX::fontType_t clockFont[] PROGMEM = 
{
    0,  // 0        
    0,  // 1        
    0,  // 2        
    0,  // 3        
    0,  // 4        
    0,  // 5        
    0,  // 6        
    0,  // 7        
    0,  // 8        
    0,  // 9        
    0,  // 10        
    0,  // 11        
    0,  // 12        
    0,  // 13        
    0,  // 14        
    0,  // 15        
    0,  // 16        
    0,  // 17        
    0,  // 18        
    0,  // 19        
    0,  // 20        
    0,  // 21        
    0,  // 22        
    0,  // 23        
    0,  // 24        
    0,  // 25        
    0,  // 26        
    0,  // 27        
    0,  // 28        
    0,  // 29        
    0,  // 30        
    0,  // 31        
    1, 0,   // 32         
    0,  // 33        
    0,  // 34        
    0,  // 35        
    0,  // 36        
    5, 152, 88, 32, 208, 200,   // 37         
    0,  // 38        
    0,  // 39        
    0,  // 40        
    0,  // 41        
    0,  // 42        
5, 254, 10, 238, 96, 224,   // 43                                                            
    0,  // 44        
6, 254, 18, 254, 224, 64, 224,  // 45     
    0,  // 46        
    5, 64, 32, 16, 8, 4,    // 47        
    5, 124, 162, 146, 138, 124,     // 48         
    5, 136, 132, 254, 128, 128,     // 49         
    5, 132, 194, 162, 146, 140,     // 50         
    5, 68, 146, 146, 146, 108,  // 51        
    5, 48, 40, 36, 254, 32,     // 52         
    5, 78, 138, 138, 138, 114,  // 53        
    5, 124, 146, 146, 146, 100,     // 54         
    5, 2, 226, 18, 10, 6,   // 55         
    5, 108, 146, 146, 146, 108,     // 56         
    5, 76, 146, 146, 146, 124,  // 57        
    1, 40,  // 58        
    0,  // 59        
    0,  // 60        
    0,  // 61        
    3, 68, 40, 16,  // 62           
    0,  // 63        
    0,  // 64        
    5, 252, 18, 18, 18, 252,    // 65        
    5, 254, 146, 146, 146, 108,     // 66         
    5, 124, 130, 130, 130, 68,  // 67        
    5, 254, 130, 130, 130, 124,     // 68         
    5, 254, 146, 146, 146, 130,     // 69         
    5, 254, 18, 18, 18, 2,  // 70        
    5, 124, 130, 130, 162, 100,     // 71         
    5, 254, 16, 16, 16, 254,    // 72        
    3, 130, 254, 130,   // 73         
    5, 64, 130, 130, 130, 126,  // 74        
    5, 254, 16, 16, 40, 198,    // 75        
    5, 254, 128, 128, 128, 128,     // 76         
    5, 254, 4, 8, 4, 254,   // 77         
    5, 254, 8, 16, 32, 254,     // 78         
    5, 124, 130, 130, 130, 124,     // 79         
    5, 254, 18, 18, 18, 12,     // 80         
    5, 124, 130, 162, 194, 252,     // 81         
    5, 254, 18, 18, 18, 236,    // 82        
    5, 76, 146, 146, 146, 100,  // 83        
    5, 2, 2, 254, 2, 2,     // 84         
    5, 126, 128, 128, 128, 126,     // 85         
    5, 62, 64, 128, 64, 62,     // 86         
    5, 254, 64, 32, 64, 254,    // 87        
    5, 198, 40, 16, 40, 198,    // 88        
    5, 6, 8, 240, 8, 6,     // 89         
    5, 194, 162, 146, 138, 134,     // 90         
24, 0, 0, 0, 128, 128, 64, 48, 16, 16, 16, 20, 18, 82, 20, 24, 16, 16, 16, 28, 20, 21, 28, 0, 0,    // 91          
    0,  // 92        
24, 0, 0, 0, 128, 128, 192, 112, 16, 16, 24, 20, 18, 26, 20, 24, 16, 16, 16, 31, 20, 21, 28, 0, 0,  // 93           
24, 0, 0, 0, 128, 128, 64, 48, 16, 28, 16, 24, 20, 18, 18, 20, 24, 16, 16, 30, 18, 18, 18, 0, 0,    // 94                         
24, 0, 0, 56, 32, 32, 160, 32, 32, 184, 128, 64, 48, 16, 28, 20, 21, 28, 16, 16, 24, 20, 24, 0, 0,  // 95                                                         
    0,  // 96        
    5, 64, 168, 168, 168, 248,  // 97        
    5, 254, 144, 136, 136, 112,     // 98         
    5, 112, 136, 136, 136, 64,  // 99        
    5, 112, 136, 136, 144, 254,     // 100         
    5, 112, 168, 168, 168, 48,  // 101        
    4, 16, 252, 18, 2,  // 102        
    5, 16, 168, 168, 168, 120,  // 103        
    5, 254, 16, 8, 8, 240,  // 104        
    3, 136, 250, 128,   // 105         
    3, 128, 136, 122,   // 106         
    4, 254, 32, 80, 136,    // 107        
    3, 130, 254, 128,   // 108         
    5, 248, 8, 240, 8, 240,     // 109         
    5, 248, 8, 8, 8, 240,   // 110         
    5, 112, 136, 136, 136, 112,     // 111         
    5, 248, 40, 40, 40, 16,     // 112         
    5, 16, 40, 40, 40, 248,     // 113         
    5, 248, 16, 8, 8, 16,   // 114         
    5, 144, 168, 168, 168, 72,  // 115        
    5, 4, 254, 132, 128, 64,    // 116        
    5, 120, 128, 128, 128, 248,     // 117         
    5, 24, 96, 128, 96, 24,     // 118         
    5, 120, 128, 96, 128, 120,  // 119        
    5, 136, 72, 112, 144, 136,  // 120        
    5, 136, 144, 96, 32, 24,    // 121        
    5, 136, 200, 168, 152, 136,     // 122         
  10, 12, 6, 50, 155, 91, 91, 155, 50, 6, 12,   // 123             
  1, 255, // 124        
    0,  // 125        
  24, 0, 0, 32, 56, 40, 40, 0, 63, 32, 32, 32, 32, 56, 34, 57, 34, 56, 32, 32, 56, 40, 40, 0, 0,    // 126   
    0,  // 127        
    0,  // 128        
    0,  // 129        
    0,  // 130        
    0,  // 131        
    0,  // 132        
    0,  // 133        
    0,  // 134        
    0,  // 135        
    0,  // 136        
    0,  // 137        
    0,  // 138        
    0,  // 139        
    0,  // 140        
    0,  // 141        
    0,  // 142        
    0,  // 143        
    0,  // 144        
    0,  // 145        
    0,  // 146        
    0,  // 147        
    0,  // 148        
    0,  // 149        
    0,  // 150        
    0,  // 151        
    0,  // 152        
    0,  // 153        
    0,  // 154        
    0,  // 155        
    0,  // 156        
    0,  // 157        
    0,  // 158        
    0,  // 159        
    0,  // 160        
    0,  // 161        
    0,  // 162        
    0,  // 163        
    0,  // 164        
    0,  // 165        
    0,  // 166        
    0,  // 167        
    0,  // 168        
    0,  // 169        
    0,  // 170        
    0,  // 171        
    0,  // 172        
    0,  // 173        
    0,  // 174        
    0,  // 175        
    0,  // 176        
    0,  // 177        
    0,  // 178        
    0,  // 179        
    0,  // 180        
    0,  // 181        
    0,  // 182        
    0,  // 183        
    0,  // 184        
    0,  // 185        
    4, 12, 18, 18, 12,  // 186        
    0,  // 187        
    0,  // 188        
    0,  // 189        
    0,  // 190        
    0,  // 191        
    0,  // 192        
    0,  // 193        
    0,  // 194        
    0,  // 195        
    0,  // 196        
    0,  // 197        
    0,  // 198        
    0,  // 199        
    0,  // 200        
    0,  // 201        
    0,  // 202        
    0,  // 203        
    0,  // 204        
    0,  // 205        
    0,  // 206        
    0,  // 207        
    0,  // 208        
    0,  // 209        
    0,  // 210        
    0,  // 211        
    0,  // 212        
    0,  // 213        
    0,  // 214        
    0,  // 215        
    0,  // 216        
    0,  // 217        
    0,  // 218        
    0,  // 219        
    0,  // 220        
    0,  // 221        
    0,  // 222        
    0,  // 223        
    0,  // 224        
    0,  // 225        
    0,  // 226        
    0,  // 227        
    0,  // 228        
    0,  // 229        
    0,  // 230        
    0,  // 231        
    0,  // 232        
    0,  // 233        
    0,  // 234        
    0,  // 235        
    0,  // 236        
    0,  // 237        
    0,  // 238        
    0,  // 239        
    0,  // 240        
    0,  // 241        
    0,  // 242        
    0,  // 243        
    0,  // 244        
    0,  // 245        
    0,  // 246        
    0,  // 247        
    0,  // 248        
    0,  // 249        
    0,  // 250        
    0,  // 251        
    0,  // 252        
    0,  // 253       
    0,  // 254       
    0,  // 255
};

MD_MAX72XX::fontType_t timerFont[] PROGMEM = 
{
    0,  // 0                
    0,  // 1                
    0,  // 2                
    0,  // 3                
    0,  // 4                
    0,  // 5                
    0,  // 6                
    0,  // 7                
    0,  // 8                
    0,  // 9                
    0,  // 10                
    0,  // 11                
    0,  // 12                
    0,  // 13                
    0,  // 14                
    0,  // 15                
    0,  // 16                
    0,  // 17                
    0,  // 18                
    0,  // 19                
    0,  // 20                
    0,  // 21                
    0,  // 22                
    0,  // 23                
    0,  // 24                
    0,  // 25                
    0,  // 26                
    0,  // 27                
    0,  // 28                
    0,  // 29                
    0,  // 30                
    0,  // 31                
    0,  // 32                 
    8, 126, 145, 145, 145, 157, 129, 129, 126,  // 33                        
    0,  // 34                
    0,  // 35                
    0,  // 36                
    0,  // 37                 
    0,  // 38                
    0,  // 39                
    0,  // 40                
    0,  // 41                
    0,  // 42                
    0,  // 43                                                                    
    0,  // 44                
    0,  // 45             
    0,  // 46                
    0,  // 47               
    8, 60, 66, 98, 82, 74, 70, 66, 60,  // 48                            
    8, 16, 48, 80, 16, 16, 16, 16, 124,     // 49                                
    8, 0, 56, 68, 8, 16, 32, 64, 124,   // 50                     
    8, 60, 4, 4, 4, 28, 4, 4, 60,   // 51                            
    8, 8, 24, 40, 72, 124, 8, 8, 8,     // 52                                 
    8, 124, 64, 64, 120, 4, 4, 68, 56,  // 53                                                   
    8, 56, 64, 64, 64, 124, 68, 68, 56,     // 54                                                        
    8, 126, 2, 2, 4, 8, 16, 16, 16,     // 55                                                      
    8, 60, 66, 66, 60, 66, 66, 66, 60,  // 56                                                         
    8, 60, 66, 66, 66, 60, 2, 2, 60,    // 57                                                                               
    0,  // 58                                         
    0,  // 59                
    0,  // 60                
    0,  // 61                
    0,  // 62                
    0,  // 63                   
    0,  // 64               
8, 1, 3, 7, 78, 220, 248, 240, 96,  // 65                
8, 0, 2, 6, 78, 220, 248, 240, 96,  // 66                                                        
8, 0, 0, 4, 76, 220, 248, 240, 96,  // 67             
8, 0, 0, 0, 72, 216, 248, 240, 96,  // 68                                                                      
8, 0, 0, 0, 64, 208, 240, 240, 96,  // 69                                                               
8, 0, 0, 0, 64, 192, 224, 224, 96,  // 70                                                                  
8, 0, 0, 0, 64, 192, 192, 192, 64,  // 71                           
8, 0, 0, 0, 0, 128, 128, 128, 0,    // 72                                       
    0,  // 73                    
    0,  // 74                
    0,  // 75                 
    0,  // 76             
    0,  // 77                     
    0,  // 78                        
    0,  // 79                 
    0,  // 80                     
    0,  // 81                       
    0,  // 82                      
    0,  // 83                    
    0,  // 84                  
    0,  // 85                
    0,  // 86                   
    0,  // 87                   
    0,  // 88                
    0,  // 89                   
    0,  // 90                    
    0,  // 91                  
    0,  // 92                  
    0,  // 93                
    0,  // 94                
    0,  // 95                
    0,  // 96                
    0,  // 97                
    0,  // 98                    
    0,  // 99              
    0,  // 100                 
    0,  // 101                      
    0,  // 102                   
    0,  // 103                 
    0,  // 104                      
    0,  // 105                
    0,  // 106                    
    0,  // 107                       
    0,  // 108                    
    0,  // 109                      
    0,  // 110                      
    0,  // 111                 
    0,  // 112                
    0,  // 113                    
    0,  // 114                
    0,  // 115                   
    0,  // 116                   
    0,  // 117              
    0,  // 118                     
    0,  // 119              
    0,  // 120                     
    0,  // 121                     
    0,  // 122                 
    0,  // 123                        
    1, 255,     // 124                         
    2, 24, 126,     // 125                                                                        
    0,  // 126                
    0,  // 127                
    0,  // 128                
    0,  // 129                
    0,  // 130                
    0,  // 131                
    0,  // 132                
    0,  // 133                
    0,  // 134                
    0,  // 135                
    0,  // 136                
    0,  // 137                
    0,  // 138                
    0,  // 139                
    0,  // 140                
    0,  // 141                
    0,  // 142                
    0,  // 143                
    0,  // 144                
    0,  // 145                
    0,  // 146                
    0,  // 147                
    0,  // 148                
    0,  // 149                
    0,  // 150                
    0,  // 151                
    0,  // 152                
    0,  // 153                
    0,  // 154                
    0,  // 155                
    0,  // 156                
    0,  // 157                
    0,  // 158                
    0,  // 159                
    0,  // 160                
    0,  // 161                
    0,  // 162                
    0,  // 163                
    0,  // 164                
    0,  // 165                
    0,  // 166                
    0,  // 167                
    0,  // 168                
    0,  // 169                
    0,  // 170                
    0,  // 171                
    0,  // 172                
    0,  // 173                
    0,  // 174                
    0,  // 175                
    0,  // 176                
    0,  // 177                
    0,  // 178                
    0,  // 179                
    0,  // 180                
    0,  // 181                
    0,  // 182                
    0,  // 183                
    0,  // 184                
    0,  // 185                
    0,  // 186                
    0,  // 187                  
    0,  // 188                
    0,  // 189                
    0,  // 190                
    0,  // 191                
    0,  // 192                
    0,  // 193                
    0,  // 194                
    0,  // 195                
    0,  // 196                
    0,  // 197                
    0,  // 198                
    0,  // 199                
    0,  // 200                
    0,  // 201                
    0,  // 202                
    0,  // 203                
    0,  // 204                
    0,  // 205                
    0,  // 206                
    0,  // 207                
    0,  // 208                
    0,  // 209                
    0,  // 210                
    0,  // 211                
    0,  // 212                
    0,  // 213                
    0,  // 214                
    0,  // 215                
    0,  // 216                
    0,  // 217                
    0,  // 218                
    0,  // 219                
    0,  // 220                
    0,  // 221                
    0,  // 222                
    0,  // 223                
    0,  // 224                
    0,  // 225                
    0,  // 226                
    0,  // 227                
    0,  // 228                
    0,  // 229                
    0,  // 230                
    0,  // 231                
    0,  // 232                
    0,  // 233                
    0,  // 234                
    0,  // 235                
    0,  // 236                
    0,  // 237                
    0,  // 238                
    0,  // 239                
    0,  // 240                
    0,  // 241                
    0,  // 242                
    0,  // 243                
    0,  // 244                
    0,  // 245                
    0,  // 246                
    0,  // 247                
    0,  // 248                
    0,  // 249                
    0,  // 250                
    0,  // 251                
    0,  // 252                
    0,  // 253                
    0,  // 254               
    0,  // 255
};

void initWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi ..");
  myDisplay.setFont(clockFont);
  myDisplay.setTextAlignment(PA_CENTER);
  myDisplay.print("{ Wifi"); //Will display the custom wifi icon as it connects.
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(1000);
  }
  Serial.println();
  Serial.println(WiFi.localIP());
}

void fetchAthanData()
{ 
  //code mostly unmodified, changed location to Bahrain.
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;
    Serial.print("[HTTP] begin...\n");
    if (http.begin("https://api.aladhan.com/v1/timingsByCity/07-07-2024?city=Manama&country=Bahrain&method=4")) {  // API Request, you can change the country, city and method here
      Serial.print("[HTTP] GET...\n");
      // start connection and send HTTP header
      int httpCode = http.GET();
      // httpCode will be negative on error
      if (httpCode > 0) {
        // HTTP header has been send and Server response header has been handled
        Serial.printf("[HTTP] GET... code: %d\n", httpCode);
        // file found at server
        if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
          String payload = http.getString();
          DynamicJsonDocument doc(2048);
          deserializeJson(doc, payload);
          JsonObject data = doc["data"];
          JsonObject data_timings = data["timings"];
          data_timings = data["timings"];
          data_timings_Fajr = data_timings["Fajr"]; // "05:13"
          data_timings_Sunrise = data_timings["Sunrise"]; // "06:31"
          data_timings_Dhuhr = data_timings["Dhuhr"]; // "12:29"
          data_timings_Asr = data_timings["Asr"]; // "15:52"
          data_timings_Sunset = data_timings["Sunset"]; // "18:26"
          data_timings_Maghrib = data_timings["Maghrib"]; // "18:26"
          data_timings_Isha = data_timings["Isha"]; // "19:56"
          data_timings_Imsak = data_timings["Imsak"]; // "05:03"
          data_timings_Midnight = data_timings["Midnight"]; // "00:29"

          //Added this code to load up the Prayer timings array
          athanTimings[0][1]=data_timings_Fajr;
          athanTimings[1][1]=data_timings_Dhuhr;
          athanTimings[2][1]=data_timings_Asr;
          athanTimings[3][1]=data_timings_Maghrib;
          athanTimings[4][1]=data_timings_Isha;

          Serial.println(data_timings_Fajr);
          Serial.println(data_timings_Dhuhr);
          Serial.println(data_timings_Asr);
          Serial.println(data_timings_Maghrib);
          Serial.println(data_timings_Isha);

        }
      } else {
        Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
      }
      http.end();
    } else {
      Serial.printf("[HTTP} Unable to connect\n");
    }
  }
}

void setup() {


    // Set encoder pins as inputs
    pinMode(CLK,INPUT);
    pinMode(DT,INPUT);
    pinMode(SW, INPUT_PULLUP);
  pinMode(TS, INPUT_PULLUP);
  // Reads the initial state of the outputA
  aLastState = digitalRead(CLK);  

  //Prepare Matrix Display
  myDisplay.begin();
  myDisplay.setIntensity(8);
  myDisplay.displayClear();


  Wire.begin();
  rtc.begin();

  //Enable the code below to set the time, you will need to immediately upload a new code with this line commented out, otherwise the clock will keep resetting to the last build time
  //rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));

  // Setup Serial Monitor
    Serial.begin(9600);

  // Set WiFi to station mode and disconnect from an AP if it was previously connected
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();

  delay(100);

  initWiFi();
  fetchAthanData();

  // Disconnect after loading up the prayer timings table, as we don't need an active connection for this project
  WiFi.disconnect();

}

void showClock()

{
  // Read the button state
    int btnState = digitalRead(SW);

    if (btnState == LOW) 
    {if (millis() - lastButtonPress > 50) //the 50 millisecond delay is to avoid the button triggering multiple times
        if(clockMode==1) // Logic to switch the alternative mode
          clockMode=2; // prayer timings
        else
          clockMode=1; // clock mode
    lastButtonPress=millis();}

  if(clockMode==1) // Clock Mode
  {DateTime now = rtc.now();
  hour=now.hour();

  // Do 24 hour to 12 hour format conversion when required.
  if (!TIME_24_HOUR) {
    ap="-";
    // Handle when hours are past 12 by subtracting 12 hours.
    if (hour > 12) {
      hour -= 12;
      ap="+";
    }
    else if (hour == 12) //Added this as 12, is PM
      ap="+";
    // Handle hour 0 (midnight) being shown as 12.
    else if (hour == 0)
      hour += 12;
  }

  String timeNow = String(hour);

  if(now.minute()<10) // Logic to add a 0 if the minutes are smaller than 10, this is just for a more easy to read layout
    timeNow += ":0"+String(now.minute())+ap;
  else
    timeNow += ":"+String(now.minute())+ap;

    myDisplay.print(timeNow);

  }

  else if(clockMode==2) //Prayer Timings Mode
  { 
    //These are the special characters added to the font to show arabic words
    //[ Fajer | ] Dhuhr | ^ Asr | _ Magreb | ~ Isha 

    //Code can be enhanced to use millis rather than delay, and add a button press detection to stop the process
    myDisplay.print("[");
    delay(1000);
    myDisplay.print(athanTimings[0][1]);
    delay(2000);
    myDisplay.print("]");
    delay(1000);
    myDisplay.print(athanTimings[1][1]);
    delay(2000);
    myDisplay.print("^");
    delay(1000);
    myDisplay.print(athanTimings[2][1]);
    delay(2000);
    myDisplay.print("_");
    delay(1000);
    myDisplay.print(athanTimings[3][1]);
    delay(2000);
    myDisplay.print("~");
    delay(1000);
    myDisplay.print(athanTimings[4][1]);
    delay(2000);
    clockMode=1;
  }
}

void showTimer()
{   
    // Read the button state
    int btnState = digitalRead(SW);
    //clockMode=1; //reset back to normal clock

    //If we detect LOW signal, button is pressed
    if (btnState == LOW) {
        //if 50ms have passed since last LOW pulse, it means that the
        //button has been pressed, released and pressed again
        if (millis() - lastButtonPress > 50) {
            Serial.println("Button pressed!");


      int timerPosition=0;
      long blockTime; //length of time in millis for each block
      int flag=0; //Flag for counter
      String progress="}|||||||||||||||"; //Progress bar
      trackTime=0;
      blockTime=(counter*1000)/16;
      myDisplay.print(progress); //display on screen
      myDisplay.setTextAlignment(PA_RIGHT);

      while (flag==0) //loop till flag is marked
        {
        if(millis() - trackTime > blockTime)
          {
          progress.remove(1,1);
          trackTime=millis();
          timerPosition++;
          myDisplay.print(progress); //display on screen
          if(timerPosition==16)
            {flag=1;
            myDisplay.setTextAlignment(PA_CENTER);}
          }
        }

        //can be coded as a loop, this is the tick animation
        myDisplay.print("H"); 
        delay(30);
        myDisplay.print("G");
        delay(30);
        myDisplay.print("F");
        delay(30);
        myDisplay.print("E");
        delay(30);
        myDisplay.print("D");
        delay(30);
        myDisplay.print("C");
        delay(30);
        myDisplay.print("B");
        delay(30);
        myDisplay.print("A");
        delay(1000);

        }
        // Remember last button press event
        lastButtonPress = millis();
    }

   aState = digitalRead(CLK); // Reads the "current" state of the outputA
   // If the previous and the current state of the outputA are different, that means a Pulse has occured
   if (aState != aLastState){     
     // If the outputB state is different to the outputA state, that means the encoder is rotating clockwise
     if (digitalRead(DT) != aState) { 
       counter ++;
     } else {
       if (counter>0)
        counter --;
     }
     Serial.print("Position: ");
     Serial.println(counter);
   } 
   aLastState = aState; // Updates the previous state of the outputA with the current state

    String counterString=String(counter);

    // Reverse the string, this is needed if you decide to make your clock stand vertically the other way
    //String reversedString = "";
    // for (int i = counterString.length() - 1; i >= 0; i--) {
    //     reversedString += counterString.charAt(i);
    // }

    if(counter>99) //if the number is bigger than 99, make more space by removing the clock
      //myDisplay.print(reversedString);
      myDisplay.print(counterString);
    else
      //myDisplay.print(reversedString+"!");
      myDisplay.print("!"+counterString);

    }

void loop() {
  leftTilt=digitalRead(TS); //read the state of the tilt switch
  //Serial.println(leftTilt);

  if(mode==2) //if running in Clock Mode
    showClock();

  else if (mode==1) //if running in Timer Mode
    showTimer();

  if((leftTilt==LOW)&&(millis() - lastTilt > 500)) //if tilted back flat for more than 0.5 second
  {mode=1;
  lastTilt= millis();
  myDisplay.setFont(timerFont); //load up the vertical font
  myDisplay.setTextAlignment(PA_LEFT);}

  else if((leftTilt==HIGH)&&(millis() - lastTilt > 500))  //if tilted up for more than 0.5 second
  {mode=2;
  lastTilt= millis();
  myDisplay.setFont(clockFont); //load up the standard clock fonts
  myDisplay.setTextAlignment(PA_CENTER);
  } 

  //Put in a slight delay to help debounce the readings
  delay(1);
}

3D Models

And here is the outcome

Final Files

Fusion 360 Design

Dock DXF

Knob STL

Main Body STL

Back Cover STL

Hope you enjoyed going through my documentation!


Last update: July 16, 2024