#include <Arduino.h>
#include <U8g2lib.h>
#include <SPI.h>
#include <Wire.h>
#include <Timezone.h>
//settings
const int DISPLAY_CLOCK_PIN = 5;
const int DISPLAY_DATA_PIN = 4;
const int SHIFT_REG_DATA_PIN = 7;
const int SHIFT_REG_CLOCK_PIN = 8;
const int SHIFT_REG_LATCH_PIN = 9;
const int SPEAKER_PIN = 6;
const uint8_t* FONT = u8g2_font_ncenB08_tr;
const int CLOCK_REFRESH_WAIT_TIME = 1000;//milliseconds ie 1 second
int auto_sleep = 5;//seconds
int BOOT_UTC_MINUTE = 17;
int BOOT_UTC_HOUR = 17;//24 hour time
int BOOT_UTC_MONTH = 9;
int BOOT_UTC_DAY = 11;
int BOOT_UTC_YEAR = 2022;
//advanced settings
const int BOUNCE_COMPENSATION = 20;//20 milliseconds
//hardware
const int DISPLAY_WIDTH = 128;
const int DISPLAY_HEIGHT = 64;
//library call for esp 32
U8G2_SSD1306_128X64_NONAME_F_HW_I2C graphics(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ DISPLAY_CLOCK_PIN, /* data=*/ DISPLAY_DATA_PIN); // ESP32 Thing, HW I2C with pin remapping
bool sleeping = false;
int time_last_button_press;
char local_str[] = "local:";
char no_colon[] = "%s %02d %02d %02d %s";
char colon[] = "%s %02d:%02d:%02d %s";
char AM_str[3] = "AM";
char PM_str[3] = "PM";
TimeChangeRule usEDTRULE = {"EDT", Second, Sun, Mar, 2, -240};//EDT
Timezone US_EDT(usEDTRULE);
void init_speaker(){
ledcSetup(0, 8000, 8);//setting channel 0 at 8000 hz with 8 bit resolution for speaker
pinMode(SPEAKER_PIN, OUTPUT);
}
void init_display(){
graphics.begin();
graphics.setFont(FONT);
}
void init_clock(){
setTime(BOOT_UTC_HOUR,BOOT_UTC_MINUTE,00,BOOT_UTC_MONTH,BOOT_UTC_DAY,BOOT_UTC_YEAR);
}
void start_threads(){
xTaskCreate(render_thread, "render thread", 4096, NULL, 1, NULL);
xTaskCreate(input_thread, "input thread", 4096, NULL, 1, NULL);
}
void init_shift_reg(){
pinMode(SHIFT_REG_DATA_PIN, INPUT);
pinMode(SHIFT_REG_CLOCK_PIN, OUTPUT);
pinMode(SHIFT_REG_LATCH_PIN, OUTPUT);
}
void setup(void) {
Serial.begin(9600);//serial speed in bps
init_speaker();
init_display();
init_clock();
init_shift_reg();
time_last_button_press = millis();
start_threads();
}
void beep(){
tone(SPEAKER_PIN, 1000,20);
}
unsigned int get_shift_reg_int(int clock_pin,int latch_pin,int data_pin){
int out = 0;
digitalWrite(latch_pin, LOW);
digitalWrite(latch_pin, HIGH);
for (int i = 0; i < 16; i++) {
out <<=1;
out |= digitalRead(data_pin) == HIGH;
digitalWrite(clock_pin, HIGH);
digitalWrite(clock_pin, LOW);
}
return out;
}
int bits_old_state = 0;
bool button_pressed[12];
bool was_button_pressed(int i){//read if button was pressed
bool temp = button_pressed[i];
button_pressed[i] = false;
return temp;
}
void updateButtons(){
while(true){
//gather bits and store into button variables
unsigned int all_bits = get_shift_reg_int(SHIFT_REG_CLOCK_PIN,SHIFT_REG_LATCH_PIN,SHIFT_REG_DATA_PIN);
for(int i = 0;i < 12;i++){
int bit_choice = 1<<i;
bool pressed = ((all_bits & bit_choice) != 0) && ((bit_choice & bits_old_state) == 0);
if(pressed){
sleeping = false;
time_last_button_press = millis();
beep();
}
button_pressed[i] |= pressed;
}
bits_old_state = all_bits;
//
/*
for(int i = 0;i<12;i++){
if(was_button_pressed(i)){
Serial.print("button: ");
Serial.println(i);
}
}
*/
delay(5);
}
}
const int MENU_ITEM_SPACEING = 6;
struct Menu{
char** m_options = nullptr;
int m_num_options;
int m_filled = 0;
Menu(int num_options){
m_options = new char*[num_options];
m_num_options = num_options;
}
void add_option(char* option_str){
m_options[m_filled] = option_str;
m_filled ++;
}
~Menu(){
if(m_options != nullptr){
delete[] m_options;
}
}
void paint_menu(unsigned int selected){
int letter_height = graphics.getAscent();
graphics.clearBuffer();
graphics.setDrawColor(1);
for(int i = 0;i<m_num_options;i++){
graphics.drawStr(1,(letter_height+MENU_ITEM_SPACEING)*(i+1)-MENU_ITEM_SPACEING,m_options[i]);
int line_y = (letter_height+MENU_ITEM_SPACEING)*(i+1)-MENU_ITEM_SPACEING/2;
graphics.drawHLine(0,line_y,DISPLAY_WIDTH);
}
graphics.setDrawColor(2);
graphics.drawBox(0,selected*(letter_height+MENU_ITEM_SPACEING),DISPLAY_WIDTH,letter_height);
graphics.sendBuffer();
}
char* open_menu(){//returns pointer of selected item
unsigned int selected = 0;
paint_menu(selected);
bool selecting = true;
while(selecting){
Serial.println("in menu");
boolean up_pressed = was_button_pressed(10);
boolean down_pressed = was_button_pressed(2);
boolean select_pressed = was_button_pressed(8);
bool button_pressed = up_pressed | down_pressed | select_pressed;
if(down_pressed){
selected++;
}else if(up_pressed){
if(selected == 0) selected = m_num_options-1;
else selected--;
}else if(select_pressed){
selecting = false;
}
if(button_pressed && !select_pressed){
selected = selected % m_num_options;
paint_menu(selected);
}
delay(50);
}
return m_options[selected];
}
};
bool display_off = false;
void main_menu(){
Menu menu(3);
char settings[16] = "settings >";
menu.add_option(settings);
char apps[8] = "apps >";
menu.add_option(apps);
char back[8] = "< back";
menu.add_option(back);
menu.open_menu();
}
void render_thread(void *pvParameters) {
Serial.println("starting render thread");
while (true) {
main_menu();
/*
if(sleeping){
if(!display_off) graphics.clearDisplay();
display_off = true;
delay(500);
}else{
display_off = false;
graphics.clearBuffer();
char time[32];
char date[32];
char* chosen;
char* AM_PM;
if(second()%2 == 0){
chosen = colon;
}else{
chosen = no_colon;
}
if(hour()>12){
AM_PM = PM_str;
}else{
AM_PM = AM_str;
}
time_t utc = now();
time_t shift = US_EDT.toLocal(utc);
sprintf(time,chosen,local_str,hour(shift)%12,minute(shift),second(shift),AM_PM);
graphics.drawStr(0,font_height*2,time);
graphics.drawHLine(0, font_height*2+2, 128);
graphics.sendBuffer();
Serial.println("rendered clock");
delay(CLOCK_REFRESH_WAIT_TIME);
}
if(millis()-time_last_button_press > 1000*auto_sleep) sleeping = true;
delay(50);
*/
}
}
void input_thread(void *pvParameters) {
Serial.println("starting input thread");
while (1) {
updateButtons();
delay(5);
}
}
void loop(){delay(10000);}//thread is not used