John Bradnam
Published © GPL3+

Power Brick Kitchen Timer & Alarm Clock

A simple Kitchen timer with an Internet Alarm Clock built inside a power brick.

IntermediateFull instructions provided12 hours320
Power Brick Kitchen Timer & Alarm Clock

Things used in this project

Hardware components

ESP32 Development Kit
30 pin variant
×1
DF Player Mini
×1
TM1637 0.36in 4 Digital 7 Segment Display Module
×1
Rotary Encoder with Push-Button
Rotary Encoder with Push-Button
Spline shaft
×1
28mm 2W 8 ohm speaker
×1
240VAC to 5VDC 700mA module
×1
3 x 10K 1/8W resistors, 1 x 0.1uF axial capacitor
×1
2 way Screw PCB mount Terminal Barrier Block
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

STL Files

Files for 3D printing

Schematics

Schematic

PCB

Eagle files

Schematic & PCB in Eagle format

Code

Kitchen_timer_with_clock_V2.ino

Arduino
/*----------------------------------------------
 * Kitchen timer with clock
 * 
 * Board: Node32s
 * 
 * 2023-12-25 V1 jlb
 *  Code base imported from R2D2 Clock V6
 *  Removed LED code as it isn't used
 * 2023-12-26 V2 jlb
 *  Added volume control setup
 *  Added brightness control setup
 *--------------------------------------------*/  

#include "WiFiManager.h" //https://github.com/tzapu/WiFiManager WiFi Configuration Magic
#include "ezTime.h" // Time Library - get your POSIX Timezone 'string' to replace TZ/GMT if needed, below, here: https://support.cyberdata.net/index.php?/Knowledgebase/Article/View/438/10/posix-timezone-strings
#include "SevenSegmentExtended.h"
#include "SevenSegmentTM1637.h"
#include "SevenSegmentFun.h" // For Text readouts
#include "EspSoftwareSerial.h"
#include "DFMiniMp3.h"
#include <EEPROM.h>

//========================USEFUL VARIABLES=============================
const char* ssid     = "PUT_CLOCKS_OWN_WIFI_NAME_HERE";   //  KITCHEN Access point network name (to connect to KITCHEN, to change/add your WiFi details)
const char* password = "SET_A_PASSWORD"; //  KITCHEN Access point password
const char* ntpServer = "pool.ntp.org"; // Your chosen TimeServer
const String timezone_posix_string = "AEST-10AEDT,M10.2.0/3:00:00,M4.3.0/2:00:00";  // Insert your own POSIX Timezone string here. See link above for info.

//=====================================================================
// Avoid changing below here...

// Call WiFi Manager
WiFiManager wifiManager;

// Call a Timezone
Timezone TZ;

#define CLK 25
#define DT 26
#define SW 27

enum SETUP { SHOW_TIME, TIMER, TIME_FORMAT, ALARM_ONOFF, ALARM_HOUR, ALARM_MIN, VOLUME, BRIGHTNESS };
SETUP setupMode = SHOW_TIME;
enum SHOW {HOURS,MINUTES};

#define NOTIFICATION_VOLUME 25  //  Speaker volume
#define DISPLAY_BACKLIGHT 40;   //  4 Digit Display brightness when operating normally (outside of initialisation)

//Uncomment to reset EEPROM data
//#define RESET_EEPROM
#define EEPROM_ADDRESS 0
#define EEPROM_MAGIC 0x0DAD0BAD
typedef struct {
  uint32_t magic;
  bool timeFormat;
  bool alarmOn;
  int8_t alarmHour;
  int8_t alarmMinute;
  uint16_t volume;
  int brightness;
} EEPROM_DATA;

EEPROM_DATA EepromData;         //Current EEPROM settings

bool set24 = false;             //Time format being set
bool setAO = false;             //Alarm on/off being set
int8_t setAH = 0;               //Alarm Hour being set
int8_t setAM = 0;               //Alarm Minute being set
uint16_t setVolume = 0;         //Volumne being set
int setBrightness = 0;          //Brightness being set
bool alarmCancelled = false;    //Set true if user cancelled alarm
bool colon = true;              //Colon to flash every second

float counter = 0;
int currentStateCLK;
int lastStateCLK;
String currentDir = "";
unsigned long lastButtonPress = 0;

// timer
int timer_secs = 0;
int timer_mins = 0;

class Mp3Notify
{
  public:
    static void PrintlnSourceAction(DfMp3_PlaySources source, const char* action)
    {
      if (source & DfMp3_PlaySources_Sd)
      {
        Serial.print("SD Card, ");
      }
      if (source & DfMp3_PlaySources_Usb)
      {
        Serial.print("USB Disk, ");
      }
      if (source & DfMp3_PlaySources_Flash)
      {
        Serial.print("Flash, ");
      }
      Serial.println(action);
    }
    static void OnError(uint16_t errorCode)
    {
      // see DfMp3_Error for code meaning
      Serial.println();
      Serial.print("Com Error ");
      Serial.println(errorCode);
    }
    static void OnPlayFinished(DfMp3_PlaySources source, uint16_t track)
    {
      Serial.print("Play finished for #");
      Serial.println(track);
    }
    static void OnPlaySourceOnline(DfMp3_PlaySources source)
    {
      PrintlnSourceAction(source, "online");
    }
    static void OnPlaySourceInserted(DfMp3_PlaySources source)
    {
      PrintlnSourceAction(source, "inserted");
    }
    static void OnPlaySourceRemoved(DfMp3_PlaySources source)
    {
      PrintlnSourceAction(source, "removed");
    }
};

SevenSegmentExtended      blue1(21, 22); // CLK, DIO
SevenSegmentFun           words(21, 22); // CLK, DIO
SoftwareSerial            secondarySerial(18, 19); // TX, RX
DFMiniMp3<SoftwareSerial, Mp3Notify> mp3(secondarySerial);

void configModeCallback (WiFiManager *myWiFiManager) 
{
  Serial.println("Entered WiFi Manager config mode..");
  Serial.println(WiFi.softAPIP());

  Serial.println(myWiFiManager->getConfigPortalSSID());
}

//flag for saving data
bool shouldSaveConfig = false;

//callback notifying us of the need to save config
void saveConfigCallback () 
{
  Serial.println("Should save config");
  shouldSaveConfig = true;
}

void setup() 
{
  words.setBacklight(80);
  words.begin();
  words.scrollingText("CLOCK START", 2);
  
  pinMode(CLK, INPUT);
  pinMode(DT, INPUT);
  pinMode(SW, INPUT_PULLUP);

  Serial.begin(115200);
    
  //set custom ip for portal
  wifiManager.setAPStaticIPConfig(IPAddress(192, 168, 2, 1), IPAddress(192, 168, 2, 1), IPAddress(255, 255, 255, 0));
  WiFiManagerParameter custom_text("<p>Welcome to Kitchen Timer With Clock: Please connect to WiFi using this portal</p>");
  wifiManager.addParameter(&custom_text);
  wifiManager.setAPCallback(configModeCallback);
  //first parameter is name of access point, second is the password
  wifiManager.autoConnect(ssid, password);
  words.scrollingText("CONNECT TO CLOCK WIFI", 4);
  wifiManager.setSaveConfigCallback(saveConfigCallback);
  wifiManager.setConfigPortalTimeout(600); // Hold for 10 mins if unable to connect.

  blue1.setBacklight(EepromData.brightness);
  blue1.begin();
  mp3.begin();
  mp3.setVolume(EepromData.volume);
  uint16_t count = mp3.getTotalTrackCount(DfMp3_PlaySource_Sd);
  Serial.print("files ");
  Serial.println(count);

  Serial.print("IP assigned by DHCP is ");
  Serial.println(WiFi.localIP());
  delay (2000);

  Serial.println("\n Should be connected: Waiting for time sync");
  waitForSync();

  // Read the initial state of CLK
  lastStateCLK = digitalRead(CLK);
  TZ.setPosix(timezone_posix_string);
  Serial.println("UTC: " + UTC.dateTime());
  Serial.println("Local Time: " + TZ.dateTime("G") + ":" + TZ.dateTime("i"));

  //Read the alarm status
  EEPROM.begin(sizeof(EEPROM_DATA));
  readEepromData();
  blue1.setBacklight(EepromData.brightness);
  mp3.setVolume(EepromData.volume);

  showTime();
}

//----------------------------------------------------------------------
// Main program loop
//----------------------------------------------------------------------

void loop() 
{
  if (minuteChanged()) 
  {
    colon = !colon;
    showTime();
    
    //Test if alarm gone off
    Serial.println("Time " + String(TZ.hour()) + ":" + String(TZ.minute()));
    Serial.println("alarmOn=" + String(EepromData.alarmOn) + ", alarmCancelled=" + String(alarmCancelled));
    Serial.println("alarmTime=" + String(EepromData.alarmHour) + ":" + String(EepromData.alarmMinute));
    
    if (alarmCancelled && (TZ.hour() != EepromData.alarmHour || TZ.minute() != EepromData.alarmMinute))
    {
      alarmCancelled = false;
    }
    else if (EepromData.alarmOn && !alarmCancelled && TZ.hour() == EepromData.alarmHour && TZ.minute() == EepromData.alarmMinute)
    {
      Serial.println("*** ALARM ON ****");
      turnOnAlarm();
      alarmCancelled = true;
    }
  }
  else if (secondChanged())
  {
    colon = !colon;
    showTime();
  }

  //If we detect LOW signal, button is pressed
  if (digitalRead(SW) == 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("Timer Button pressed!");
      bool repeatLoop = true;
      while (repeatLoop)
      {
        setupMode = (setupMode == BRIGHTNESS) ? SHOW_TIME : (SETUP)((int)setupMode + 1);
        switch (setupMode)
        {
          case SHOW_TIME: 
            showTime();
            repeatLoop = false; 
            break;
            
          case TIMER: 
            repeatLoop = setupTimer();
            if (!repeatLoop)
            {
              setupMode = SHOW_TIME;
              showTime();
            }
            break;
  
          case TIME_FORMAT:
            //Store settings so we can detect if they changed
            set24 = EepromData.timeFormat;
            setAO = EepromData.alarmOn;
            setAH = EepromData.alarmHour;
            setAM = EepromData.alarmMinute;
            setVolume = EepromData.volume;
            setBrightness = EepromData.brightness;
            setTimeFormat();
            break;
          
          case ALARM_ONOFF: 
            setAlarmState(); break;
            break;
            
          case ALARM_HOUR: 
            setAlarmHour(); 
            break;
            
          case ALARM_MIN: 
            setAlarmMinute(); 
            break;

          case VOLUME:
            setClockVolume();
            break;

          case BRIGHTNESS:
            setClockBrightness();
            //Test if alarm status has changed
            if (setAO != EepromData.alarmOn || setAH != EepromData.alarmHour || setAM != EepromData.alarmMinute || set24 != EepromData.timeFormat || setVolume != EepromData.volume || setBrightness != EepromData.brightness)
            {
              //Write changes back to EEPROM
              EepromData.timeFormat = set24;
              EepromData.alarmOn = setAO;
              EepromData.alarmHour = setAH;
              EepromData.alarmMinute = setAM;
              EepromData.volume = setVolume;
              EepromData.brightness = setBrightness;
              writeEepromData();
              blue1.setBacklight(EepromData.brightness);
              mp3.setVolume(EepromData.volume);
            }
            break;
          
        }
      }
    }

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

  delay(1);
}

//----------------------------------------------------------------------
// Display the current time
//  24hr format has leading zeros, 12hr format doesn't
//----------------------------------------------------------------------

void showTime()
{
  if (EepromData.timeFormat)
  {
    showPartialTime(HOURS, TZ.dateTime("H").toInt(), true, true, colon);
  }
  else
  {
    showPartialTime(HOURS, TZ.dateTime("g").toInt(), false, true, colon);
  }
  showPartialTime(MINUTES, TZ.dateTime("i").toInt(), true, true, colon);
}

//---------------------------------------------------------------------
// Write two time digits to display
//  s - SHOW constant
//  num - (0 to 99) 
//  leadingZeros - true to have leading zeros
//  on - true to show digit, false to show blank
//  c - true to show colon
//---------------------------------------------------------------------

void showPartialTime(SHOW s, int num, bool leadingZeros, bool on, bool c)
{
  num = max(min(num, 99), 0);
  int b = (s == HOURS) ? 0 : 2;
  for (int i = 0; i < 2; i++)
  {
    if (on && (num > 0 || i == 0 || leadingZeros))
    {
      blue1.setColonOn(c);
      blue1.printRaw(blue1.encode((int16_t)(num % 10)), b + 1-i);
    }
    else
    {
      blue1.printRaw((uint8_t)TM1637_CHAR_SPACE, b + 1-i);
    }
    num = num / 10;
  }
}

//----------------------------------------------------------------------
// Show/Change the time format
//  - Rotary  Encoder changes alarm state
//----------------------------------------------------------------------

void setTimeFormat()
{
  Serial.println("In time format!");
  showTimeFormat();
  mp3.playMp3FolderTrack(2);
  delay(500);
  while (digitalRead(SW) == HIGH) 
  {
    switch (readRotaryEncoder())
    {
      case 1: set24 = true; showTimeFormat(); break;
      case -1: set24 = false; showTimeFormat(); break;
      default: break;
    }
    delay(1);
  }
  Serial.println("Exit time format!");
}

//----------------------------------------------------------------------
// Show the current time format
//----------------------------------------------------------------------

void showTimeFormat()
{
  blue1.printRaw(TM1637_CHAR_T, 0);
  blue1.printRaw(TM1637_CHAR_f, 1);
  if (set24)
  {
    blue1.printRaw(TM1637_CHAR_2, 2);
    blue1.printRaw(TM1637_CHAR_4, 3);
  }
  else
  {
    blue1.printRaw(TM1637_CHAR_1, 2);
    blue1.printRaw(TM1637_CHAR_2, 3);
  }
}

//----------------------------------------------------------------------
// Show/Change alarm state
//  - Rotary  Encoder changes alarm state
//----------------------------------------------------------------------

void setAlarmState()
{
  Serial.println("In alarm state!");
  showAlarmState();
  mp3.playMp3FolderTrack(2);
  delay(500);
  while (digitalRead(SW) == HIGH) 
  {
    switch (readRotaryEncoder())
    {
      case 1: setAO = true; showAlarmState(); break;
      case -1: setAO = false; showAlarmState(); break;
      default: break;
    }
    delay(1);
  }
  Serial.println("Exit alarm State!");
}

//----------------------------------------------------------------------
// Show the current alarm state
//----------------------------------------------------------------------

void showAlarmState()
{
  blue1.printRaw(TM1637_CHAR_A, 0);
  blue1.printRaw((uint8_t)TM1637_CHAR_SPACE, 1);
  blue1.printRaw(TM1637_CHAR_o, 2);
  blue1.printRaw((setAO) ? TM1637_CHAR_n : TM1637_CHAR_f, 3);
}

//----------------------------------------------------------------------
// Show/Change alarm hours
//----------------------------------------------------------------------

void setAlarmHour()
{
  Serial.println("In alarm hour!");
  showAlarmHour();
  mp3.playMp3FolderTrack(2);
  delay(500);
  while (digitalRead(SW) == HIGH) 
  {
    switch (readRotaryEncoder())
    {
      case 1: setAH = (setAH + 1) % 24; showAlarmHour(); break;
      case -1: setAH = (setAH + 23) % 24; showAlarmHour(); break;
      default: break;
    }
    delay(1);
  }
  Serial.println("Exit alarm hour!");
}

//----------------------------------------------------------------------
// Show the current alarm hour
//----------------------------------------------------------------------

void showAlarmHour()
{
  uint8_t buffer[2];
  
  blue1.printRaw(TM1637_CHAR_A, 0);
  blue1.printRaw(TM1637_CHAR_h, 1);
  buffer[0] = blue1.encode(int16_t(setAH / 10));
  buffer[1] = blue1.encode(int16_t(setAH % 10));
  blue1.printRaw(buffer, 2, 2);
}

//----------------------------------------------------------------------
// Clock is in ALARM_MIN mode
//----------------------------------------------------------------------

void setAlarmMinute()
{
  Serial.println("In alarm minute!");
  showAlarmMinute();
  mp3.playMp3FolderTrack(2);
  delay(500);
  while (digitalRead(SW) == HIGH) 
  {
    switch (readRotaryEncoder())
    {
      case 1: setAM = (setAM + 1) % 60; showAlarmMinute(); break;
      case -1: setAM = (setAM + 59) % 60; showAlarmMinute(); break;
      default: break;
    }
    delay(1);
  }
  Serial.println("Exit alarm minute!");
}

//----------------------------------------------------------------------
// Show the current alarm hour
//----------------------------------------------------------------------

void showAlarmMinute()
{
  uint8_t buffer[2];
  
  blue1.printRaw(TM1637_CHAR_A, 0);
  blue1.printRaw(TM1637_CHAR_n, 1);
  buffer[0] = blue1.encode(int16_t(setAM / 10));
  buffer[1] = blue1.encode(int16_t(setAM % 10));
  blue1.printRaw(buffer, 2, 2);
}

//----------------------------------------------------------------------
// Clock is in VOLUME mode
//----------------------------------------------------------------------

void setClockVolume()
{
  Serial.println("In volume!");
  showClockVolume();
  mp3.playMp3FolderTrack(2);
  delay(500);
  while (digitalRead(SW) == HIGH) 
  {
    switch (readRotaryEncoder())
    {
      case 1: 
        if (setVolume < 99) 
        {
          setVolume++; 
        }
        showClockVolume(); 
        break;
        
      case -1:
        if (setVolume > 0) 
        {
          setVolume--; 
        }
        showClockVolume(); 
        break;
      
      default: 
        break;
    }
    delay(1);
  }
  Serial.println("Exit volume!");
}

//----------------------------------------------------------------------
// Show the current volume
//----------------------------------------------------------------------

void showClockVolume()
{
  uint8_t buffer[2];
  
  blue1.printRaw(TM1637_CHAR_V, 0);
  blue1.printRaw(TM1637_CHAR_o, 1);
  buffer[0] = blue1.encode(int16_t(setVolume / 10));
  buffer[1] = blue1.encode(int16_t(setVolume % 10));
  blue1.printRaw(buffer, 2, 2);
  mp3.setVolume(setVolume);
  mp3.playMp3FolderTrack(2);
}

//----------------------------------------------------------------------
// Clock is in BRIGHTNESS mode
//----------------------------------------------------------------------

void setClockBrightness()
{
  Serial.println("In brightness!");
  showClockBrightness();
  mp3.playMp3FolderTrack(2);
  delay(500);
  while (digitalRead(SW) == HIGH) 
  {
    switch (readRotaryEncoder())
    {
      case 1: 
        if (setBrightness < 90) 
        {
          setBrightness = setBrightness + 10; 
        }
        showClockBrightness(); 
        break;
        
      case -1:
        if (setBrightness > 10) 
        {
          setBrightness = setBrightness - 10; 
        }
        showClockBrightness(); 
        break;
      
      default: 
        break;
    }
    delay(1);
  }
  Serial.println("Exit brightness!");
}

//----------------------------------------------------------------------
// Show the current brightness
//----------------------------------------------------------------------

void showClockBrightness()
{
  uint8_t buffer[2];
  
  blue1.printRaw(TM1637_CHAR_b, 0);
  blue1.printRaw(TM1637_CHAR_r, 1);
  buffer[0] = blue1.encode(int16_t(setBrightness / 10));
  buffer[1] = blue1.encode(int16_t(setBrightness % 10));
  blue1.printRaw(buffer, 2, 2);
  blue1.setBacklight(setBrightness);
}

//---------------------------------------------------------------
// Sound the alarm
//  This will play for one minute or until the rotary switch is pressed
//---------------------------------------------------------------

void turnOnAlarm()
{
  while (digitalRead(SW) == HIGH && !minuteChanged()) 
  {
    mp3.playMp3FolderTrack(5);
    uint32_t timeout = millis() + 30000;
    while (digitalRead(SW) == HIGH && !minuteChanged() && millis() < timeout)
    {
      delay(10);
    }
    mp3.stop();
  }
  while (digitalRead(SW) == LOW)
  {
    //Wait until button released so it doesn't trigger setup
  }
}

//----------------------------------------------------------------------
// Handle timer
//  - Rotary Encoder changes seconds
//  - Switch starts timer or changes mode if timer is at zero
//  Returns true if timer not set
//----------------------------------------------------------------------

bool setupTimer() 
{
  Serial.println("In Timer State!");
  blue1.printTime(88, 88, false);
  mp3.playMp3FolderTrack(2);
  delay(500);
  while (digitalRead(SW) == HIGH) 
  {
    counter += float(readRotaryEncoder() * 10);
    if (counter < 0)
    {
      counter = 0;
    }

    timer_mins = counter / 60;
    timer_secs =  ((counter / 60) - timer_mins) * 60;
    blue1.printTime(timer_mins, timer_secs, false);

    delay(1);
  }
  bool timerNotUsed = (counter == 0);
  if (counter > 0)
  {
    Countdown(counter);
  }
  Serial.println("Exit Timer State!");
  return timerNotUsed;
}

//----------------------------------------------------------------------
// Handle timer
//  - Countdown the time and play sound when done
//----------------------------------------------------------------------

void Countdown (float timer_counter) 
{
  mp3.playMp3FolderTrack(2);
  delay(1000);
  
  while (digitalRead(SW) == HIGH)
  {
    for (int i = 10 ; i > 0; i--) 
    {
      timer_counter = timer_counter - 0.1;
      timer_mins = timer_counter / 60;
      timer_secs =  ((timer_counter / 60) - timer_mins) * 60;
      blue1.printTime(timer_mins, timer_secs, false);
      delay(100);
    }

    if (timer_counter <= 0) 
    {
      mp3.playMp3FolderTrack(1);
      break;
    };
  }
  while (digitalRead(SW) == LOW)
  {
    //Wait until button released so it doesn't trigger setup
  }
  counter = 0;
}

//----------------------------------------------------------------------
// Read the rotary encoder
//  - Returns 0, -1 or 1
//----------------------------------------------------------------------

int readRotaryEncoder()
{
  int result = 0;
  // Read the current state of CLK
  currentStateCLK = digitalRead(CLK);
  // If last and current state of CLK are different, then pulse occurred
  // React to only 1 state change to avoid double count
  if (currentStateCLK != lastStateCLK  && currentStateCLK == 1) 
  {

    // If the DT state is different than the CLK state then
    // the encoder is rotating CCW so decrement
    if (digitalRead(DT) != currentStateCLK) 
    {
      result = -1;
      currentDir = "CCW";
    } 
    else 
    {
      // Encoder is rotating CW so increment
      result = 1;
      currentDir = "CW";
    }

    Serial.print("Direction: ");
    Serial.print(currentDir);
    Serial.print(" | Counter: ");
    Serial.println(counter);
  }

  // Remember last CLK state
  lastStateCLK = currentStateCLK;
  return result;  
}

//----------------------------------------------------------------------
// Wait a number of milliseconds while updating MP3 player
//  msWait - time to wait in mS
//----------------------------------------------------------------------

void waitMilliseconds(uint16_t msWait) 
{
  uint32_t start = millis();

  while ((millis() - start) < msWait && digitalRead(SW) == HIGH)
  {
    // calling mp3.loop() periodically allows for notifications
    // to be handled without interrupts
    mp3.loop();
    delay(1);
  }
}

//---------------------------------------------------------------
// Write the EepromData structure to EEPROM
//---------------------------------------------------------------

void writeEepromData()
{
  //This function uses EEPROM.update() to perform the write, so does not rewrites the value if it didn't change.
  EEPROM.put(EEPROM_ADDRESS,EepromData);
  EEPROM.commit();
}

//---------------------------------------------------------------
//Read the EepromData structure from EEPROM, initialise if necessary
//---------------------------------------------------------------

void readEepromData()
{
  //Eprom
  EEPROM.get(EEPROM_ADDRESS,EepromData);
  #ifndef RESET_EEPROM
  if (EepromData.magic != EEPROM_MAGIC)
  #endif
  {
    EepromData.magic = EEPROM_MAGIC;
    EepromData.timeFormat = false;
    EepromData.alarmOn = false;
    EepromData.alarmHour = 6;
    EepromData.alarmMinute = 0;
    EepromData.volume = NOTIFICATION_VOLUME;
    EepromData.brightness = DISPLAY_BACKLIGHT;
    writeEepromData();
    EEPROM.commit();
  }
}

mp3.zip

Arduino
MP3 files to unzip onto your SD card
No preview (download only).

arduino-tm1637-master.zip

Arduino
Required Arduino library
No preview (download only).

DFPlayer_Mini_Mp3_by_Makuna.zip

Arduino
Required Arduino library
No preview (download only).

EspSoftwareSerial.zip

Arduino
Required Arduino library
No preview (download only).

ezTime.zip

Arduino
Required Arduino library
No preview (download only).

Credits

John Bradnam

John Bradnam

141 projects • 170 followers
Thanks to Juraj and jeje95.

Comments