diff --git a/CMakeLists.txt b/CMakeLists.txt index 9151e8a..5d2c20c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,7 @@ add_executable(smart_alarm smart_alarm.cpp sound.cpp ui.cpp fonts/Font5x7FixedMono.c + alarm.cpp ) pico_set_program_name(smart_alarm "smart_alarm") diff --git a/alarm.cpp b/alarm.cpp new file mode 100644 index 0000000..f2dce61 --- /dev/null +++ b/alarm.cpp @@ -0,0 +1,125 @@ +#include +#include +#include +#include +#include "alarm.h" + + + + alarm::alarm(uint8_t hours, uint8_t minutes, const std::array &days_enabled, bool every_other_week, bool even_week){ + this->minutes_store = minutes; + + // Initialize scheduled field + this->scheduled = 0; + + // Set bits 0-6 for days of the week + for (int i = 0; i < 7; i++) { + if (days_enabled[i]) { + this->scheduled |= (1 << i); + } + } + + // Set bit 7 for every other week functionality + if (every_other_week) { + this->scheduled |= (1 << 7); + } + + // Initialize hours field + this->hours_and_stuff_store = 0; + + // Set bit 0 for even/odd week (only relevant if every_other_week is true) + if (every_other_week && even_week) { + this->hours_and_stuff_store |= (1 << 0); + } + + // Set bit 1 as disabled flag + this->hours_and_stuff_store &= ~(1 << 1); + + // Bit 2 is reserved (left as 0) + + // Set bits 3-7 with actual hours value (shift left by 3) + this->hours_and_stuff_store |= (hours & 0x1F) << 3; // 0x1F masks to 5 bits (0-31) + }; + +uint8_t alarm::minutes() const { + return minutes_store; +} + +uint8_t alarm::hours() const { + return (hours_and_stuff_store >> 3) & 0x1F; // Extract bits 3-7 and mask to 5 bits +} + +std::array alarm::days_enabled() const { + std::array days; + for (int i = 0; i < 7; i++) { + days[i] = (scheduled & (1 << i)) != 0; + } + return days; +} + +bool alarm::every_other_week() const { + return (scheduled & (1 << 7)) != 0; +} + +bool alarm::even_week() const { + return (hours_and_stuff_store & (1 << 0)) != 0; +} + +bool alarm::enabled() const { + return (hours_and_stuff_store & (1 << 1)) != 0; +} + +// Enable/disable methods +void alarm::enable() { + hours_and_stuff_store |= (1 << 1); // Set bit 1 +} + +void alarm::disable() { + hours_and_stuff_store &= ~(1 << 1); // Clear bit 1 +} + +void alarm::set_state(bool enabled) { + if (enabled) { + enable(); + } else { + disable(); + } +} + +void alarm::set_minutes(uint8_t minutes) { + this->minutes_store = minutes; +} + +void alarm::set_hours(uint8_t hours) { + this->hours_and_stuff_store &= ~(0x1F << 3); // Clear bits 3-7 + this->hours_and_stuff_store |= (hours & 0x1F) << 3; // Set new hours +} + +void alarm::set_days_enabled(const std::array& days_enabled) { + this->scheduled &= (1 << 7); // Keep only bit 7 (every_other_week) + + // Set new day bits + for (int i = 0; i < 7; i++) { + if (days_enabled[i]) { + this->scheduled |= (1 << i); + } + } +} + +void alarm::set_every_other_week(bool every_other_week) { + if (every_other_week) { + this->scheduled |= (1 << 7); // Set bit 7 + } else { + this->scheduled &= ~(1 << 7); // Clear bit 7 + } +} + +void alarm::set_even_week(bool even_week) { + if (even_week) { + this->hours_and_stuff_store |= (1 << 0); // Set bit 0 + } else { + this->hours_and_stuff_store &= ~(1 << 0); // Clear bit 0 + } +} + +std::vector alarms; \ No newline at end of file diff --git a/alarm.h b/alarm.h new file mode 100644 index 0000000..1c90454 --- /dev/null +++ b/alarm.h @@ -0,0 +1,36 @@ +#ifndef SMART_ALARM_PERSISTANCE_H +#define SMART_ALARM_PERSISTANCE_H +#include +#include +#include + +struct alarm +{ +private: + uint8_t scheduled; // BITs used to mark days 1 ring on that day 0 dosn't ring on that day last bit marks if it should rind only every other week, if all BITs are 0 it will ring only once + uint8_t hours_and_stuff_store; // if every other week is selected it will check first bit of this if it is 1 it will ring on even weeks and if 0 it will ring on odd week next 1 is enabled flags, next BIT is reserved rest is hours + uint8_t minutes_store; +public: + alarm(uint8_t hours, uint8_t minutes, const std::array &days_enabled, bool every_other_week, bool even_week); + + [[nodiscard]] uint8_t minutes() const; + [[nodiscard]] uint8_t hours() const; + [[nodiscard]] std::array days_enabled() const; + [[nodiscard]] bool every_other_week() const; + [[nodiscard]] bool even_week() const; + [[nodiscard]] bool enabled() const; + + // Enable/disable methods + void enable(); + void disable(); + void set_state(bool enabled); + + void set_minutes(uint8_t minutes); + void set_hours(uint8_t hours); + void set_days_enabled(const std::array& days_enabled); + void set_every_other_week(bool every_other_week); + void set_even_week(bool even_week); +}; + +extern std::vector alarms; +#endif //SMART_ALARM_PERSISTANCE_H \ No newline at end of file diff --git a/display.cpp b/display.cpp index 2f4518a..5fc966b 100644 --- a/display.cpp +++ b/display.cpp @@ -10,11 +10,17 @@ #include "pico/time.h" #include "spi.h" #include +#include #include #include "Font5x7FixedMono.h" +#include "alarm.h" +#include "control.h" +#include "macros.h" +#include "multicore_events.h" #include "hardware/rtc.h" +#include "pico/multicore.h" int32_t t_fine; uint16_t dig_T1; @@ -74,18 +80,25 @@ gc9a01_GC9A01_obj_t create_lcd() { gc9a01_GC9A01_obj_t display; -void draw_ui_circle(uint16_t color) -{ - for (int i = 720; i > 0; i--) { - int x = 120 + (int)(119.0 * sin((180.0 + i / 2.0) * M_TWOPI / 360.0)); - int y = 120 + (int)(119.0 * cos((180.0 + i / 2.0) * M_TWOPI / 360.0)); +void draw_circle(int16_t x_center, int16_t y_center, int16_t r, + uint16_t color) { + for (uint16_t i = 720; i > 0; i--) { + uint8_t x = x_center + + static_cast(static_cast(r) * + sin((180.0 + i / 2.0) * M_TWOPI / 360.0)); + uint8_t y = y_center + + static_cast(static_cast(r) * + cos((180.0 + i / 2.0) * M_TWOPI / 360.0)); gc9a01_draw_pixel(&display, x, y, color); } } +void draw_ui_circle(uint16_t color) { draw_circle(120, 120, 119, color); } -std::array get_ui_circle_vertical_pos(uint16_t x, uint16_t center_x, uint16_t center_y, uint16_t radius) -{ +std::array get_ui_circle_vertical_pos(uint16_t x, + uint16_t center_x, + uint16_t center_y, + uint16_t radius) { // Ctalculate horizontal distance from point to center uint16_t dx = x - center_x; uint16_t dx_squared = dx * dx; @@ -93,7 +106,7 @@ std::array get_ui_circle_vertical_pos(uint16_t x, uint16_t center_x, // Check if point's x-coordinate is within circle bounds if (dx_squared >= radius_squared) { - return {0,0}; // Outside circle - no vertical space + return {0, 0}; // Outside circle - no vertical space } // Calculate vertical distance from center to circle edge at this x @@ -103,14 +116,12 @@ std::array get_ui_circle_vertical_pos(uint16_t x, uint16_t center_x, uint16_t y_top = center_y - (uint16_t)vertical_offset; uint16_t y_bottom = center_y + (uint16_t)vertical_offset; - std::array positions ={y_top,y_bottom}; + std::array positions = {y_top, y_bottom}; return positions; } -void clear_display() { - gc9a01_fill(&display, BLACK); -} +void clear_display() { gc9a01_fill(&display, BLACK); } void display_start() { char buf[10]; @@ -121,31 +132,158 @@ void display_start() { sleep_ms(100); clear_display(); - draw_ui_circle(MAGENTA); - - } void print_time(const bool force_refresh) { datetime_t t; if (rtc_get_datetime(&t)) { - const GFXfont* font = &VGA1_16x32; + const GFXfont *font = &VGA1_16x32; char buf[11]; - sprintf(buf,"%02d:%02d:%02d", t.hour, t.min, t.sec); - gc9a01_text(&display,font,buf,120-((16*8)/2),120-16,WHITE, BLACK); + sprintf(buf, "%02d:%02d:%02d", t.hour, t.min, t.sec); + gc9a01_text(&display, font, buf, 120 - ((16 * 8) / 2), 120 - 16, WHITE, + BLACK); static uint8_t last_day = UINT8_MAX; if (last_day != t.day || force_refresh) { font = &Font5x7FixedMono; - sprintf(buf,"%02d.%02d.%04d",t.day,t.month,t.year); - gc9a01_text_gfx_buffered(&display,font,buf,120-((6*10)/2),((120-16)+32)+7,WHITE,BLACK); + sprintf(buf, "%02d.%02d.%04d", t.day, t.month, t.year); + gc9a01_text_gfx_buffered(&display, font, buf, 120 - ((6 * 10) / 2), + ((120 - 16) + 32) + 7, WHITE, BLACK); last_day = t.day; } } else { - const char* rtc_fail= "NO SYNC!"; - const GFXfont* font = &Font5x7FixedMono; - gc9a01_text_gfx_buffered(&display,font,rtc_fail,120-((5*8)/2),120-((7*1)/2),RED,BLACK); + const char *rtc_fail = "NO SYNC!"; + const GFXfont *font = &Font5x7FixedMono; + gc9a01_text_gfx_buffered(&display, font, rtc_fail, 120 - ((5 * 8) / 2), + 120 - ((7 * 1) / 2), RED, BLACK); } - +} + + +void print_alarm(alarm *alarm, selected_t selected, uint8_t x, u_int8_t y) { + bool setting_sub = false; + uint8_t selected_item = UINT8_MAX; + do + { + uint8_t item_index = 0; + gc9a01_rect(&display, x, y, ALARM_BOX_WIDTH, ALARM_BOX_HEIGHT, selected); + char buf[3]; + sprintf(buf, "%02d", alarm->hours()); + if (selected_item == item_index) { + gc9a01_fill_rect(&display,((x + ALARM_BOX_WIDTH / 2) - ((strlen(buf) * 5) / 2)) - 2,y + 3,(5*2)+1,7,BLACK); + } + gc9a01_text_gfx_buffered(&display, &Font5x7FixedMono, buf,((x + ALARM_BOX_WIDTH / 2) - ((strlen(buf) * 5) / 2)) - 2, y + 10, selected_item == item_index ? setting_sub ? SETTING : SELECTED : WHITE,BLACK); + item_index++; + sprintf(buf, ":"); + gc9a01_text_gfx_buffered(&display, &Font5x7FixedMono, buf,(((x + ALARM_BOX_WIDTH / 2) - ((strlen(buf) * 5) / 2)) - 2)+(5*2)-(5/2), y + 10, WHITE,BLACK); + sprintf(buf, "%02d", alarm->minutes()); + if (selected_item == item_index) { + gc9a01_fill_rect(&display,(((x + ALARM_BOX_WIDTH / 2) - ((strlen(buf) * 5) / 2)) - 2)+5*3,y + 3,(5*2)+1,7,BLACK); + } + gc9a01_text_gfx_buffered(&display, &Font5x7FixedMono, buf,(((x + ALARM_BOX_WIDTH / 2) - ((strlen(buf) * 5) / 2)) - 2)+5*3, y + 10, selected_item == item_index ? setting_sub ? SETTING : SELECTED : WHITE,BLACK); + item_index++; + + gc9a01_fill_rect(&display, x+ALARM_BOX_WIDTH-7, + y+2,5,7,alarm->enabled() ? GREEN : RED); + if (selected_item == item_index) { + gc9a01_rect(&display, x+ALARM_BOX_WIDTH-7, + y+2,5,7,alarm->enabled() ? RED : GREEN); + } + item_index++; + + u_int8_t x_text_abrivs = x + (5 / 2); + { + std::array days_enabled = alarm->days_enabled(); + + for (uint8_t i = 0; i < days_enabled.size(); i++) { + gc9a01_fill_rect(&display, x_text_abrivs, + (y + ALARM_BOX_HEIGHT) - 10,5,7,days_enabled[i] ? GREEN : RED); + if (selected_item == item_index) { + gc9a01_rect(&display, x_text_abrivs, + (y + ALARM_BOX_HEIGHT) - 10,5,7,days_enabled[i] ? RED : GREEN); + } + item_index++; + x_text_abrivs += 5 + 5 / 2; + } + } + if (alarm->every_other_week()) { + char c = '='; + if (selected_item == item_index) { + gc9a01_fill_rect(&display, x_text_abrivs,(y + ALARM_BOX_HEIGHT) - 7, 5, 1, BLACK); + } + gc9a01_text_gfx_buffered(&display, &Font5x7FixedMono,&c,x_text_abrivs,(y + ALARM_BOX_HEIGHT) - 3,selected_item == item_index ? SELECTED : MAGENTA,BLACK); + item_index++; + x_text_abrivs += 5 + 5 / 2; + c = alarm->even_week() ? '2' : '1'; + if (selected_item == item_index && c == '1') { + gc9a01_fill_rect(&display, x_text_abrivs+4,(y + ALARM_BOX_HEIGHT) - 9,1,2,BLACK); + } + gc9a01_text_gfx_buffered(&display, &Font5x7FixedMono,&c,x_text_abrivs,(y + ALARM_BOX_HEIGHT) - 3,selected_item == item_index ? SELECTED : alarm->even_week() ? MAGENTA : CYAN,BLACK); + item_index++; + } else { + constexpr char c = '-'; + if (selected_item == item_index) { + gc9a01_fill_rect(&display, x_text_abrivs,(y + ALARM_BOX_HEIGHT) - 8, 5, 3, BLACK); + } + gc9a01_text_gfx_buffered(&display, &Font5x7FixedMono,&c,x_text_abrivs,(y + ALARM_BOX_HEIGHT) - 3,selected_item == item_index ? SELECTED : CYAN,BLACK); + item_index++; + } + if (selected == selected_t::SETTING) { + switch (static_cast(multicore_fifo_pop_blocking())) { + case PRIMARY_BUTTON_PRESSED: + if (setting_sub) { + setting_sub = false; + break; + } + switch (selected_item) { + case 0: // hours + case 1: // minutes + setting_sub = true; + break; + case 2: // enable/disable + alarm->set_state(!alarm->enabled()); + break; + case 10: // every other week + alarm->set_every_other_week(!alarm->every_other_week()); + break; + case 11: // even week + alarm->set_even_week(!alarm->even_week()); + break; + default: // days of the week + std::array days_enabled = alarm->days_enabled(); + days_enabled[selected_item-3] = !days_enabled[selected_item-3]; + alarm->set_days_enabled(days_enabled); + } + break; + case SECONDARY_BUTTON_PRESSED: + goto exit_print_alarm; + case KNOB_CHANGE: { + if (!setting_sub) + {selected_item = std::min(static_cast(static_cast(get_knob_percentage())/100.0f*static_cast(item_index)),static_cast(item_index-1));} else { + switch (selected_item) { + case 0: + alarm->set_hours(std::min(static_cast(static_cast(get_knob_percentage())/100.0f*static_cast(24)),static_cast(23))); + break; + case 1: + alarm->set_minutes(std::min(static_cast(static_cast(get_knob_percentage())/100.0f*static_cast(60)),static_cast(59))); + break; + default: + printf("ERROR: invalid option selected!!!\n"); + }} + } + break; + } + } + } while (selected == selected_t::SETTING); + exit_print_alarm: + return; // fix waring about C23 extension +} + +void print_add_button(uint8_t center_x, uint8_t center_y, uint8_t w, uint16_t color) { + draw_circle(center_x, center_y, w/2, color); + const uint8_t plus_length = w - 7; + const uint8_t half_length = plus_length / 2; + gc9a01_hline(&display, center_x - half_length, center_y, plus_length, color); + gc9a01_vline(&display, center_x, center_y - half_length, plus_length, color); } diff --git a/display.h b/display.h index a038736..4ded42d 100644 --- a/display.h +++ b/display.h @@ -3,6 +3,7 @@ #include #include +#include "alarm.h" #include "gc9a01.h" void display_start(); void print_time(bool force_refresh = false); @@ -12,6 +13,16 @@ std::array get_ui_circle_vertical_pos(uint16_t x, uint16_t center_x extern gc9a01_GC9A01_obj_t display; +enum selected_t { + NOT_SELECTED = WHITE, + SELECTED = GREEN, + SETTING = RED +}; + +void print_alarm(alarm* alarm, selected_t selected, uint8_t x,u_int8_t y); +void print_add_button(uint8_t center_x, uint8_t center_y,uint8_t w, uint16_t color); #define MAX_X 240 #define MAX_Y 240 +#define ALARM_BOX_WIDTH 70 +#define ALARM_BOX_HEIGHT 30 #endif //SMART_ALARM_DISPLAY_H \ No newline at end of file diff --git a/fonts/SOURCE b/fonts/SOURCE new file mode 100644 index 0000000..1ab417c --- /dev/null +++ b/fonts/SOURCE @@ -0,0 +1 @@ +https://github.com/robjen/GFX_fonts diff --git a/gen_sound.sh b/gen_sound.sh new file mode 100755 index 0000000..b56a8ed --- /dev/null +++ b/gen_sound.sh @@ -0,0 +1,4 @@ +#!/bin/bash +xxd -i alarm_sound.wav > alarm_sound.h +sed -i 's/unsigned/inline constexpr unsigned/g' alarm_sound.h +sed -i '1i\#pragma once\' alarm_sound.h \ No newline at end of file diff --git a/pins.h b/pins.h index ca159cc..0fe5666 100644 --- a/pins.h +++ b/pins.h @@ -8,4 +8,6 @@ #define BUTTON_PRIMARY_IN 8 #define BUTTON_SECONDARY_PW 7 -#define BUTTON_SECONDARY_IN 6 \ No newline at end of file +#define BUTTON_SECONDARY_IN 6 + +#define LED_PIN 5 \ No newline at end of file diff --git a/smart_alarm.cpp b/smart_alarm.cpp index 3468884..2532543 100644 --- a/smart_alarm.cpp +++ b/smart_alarm.cpp @@ -117,6 +117,8 @@ int main() { setup_pwm_audio(); timezone_index = 353; // todo load from flash + gpio_init(LED_PIN); + gpio_set_dir(LED_PIN, GPIO_OUT); // sign of life cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, true); @@ -173,7 +175,7 @@ int main() { if (!secondary_button_override) { page_selected = - page_selected + 1 >= page_functions.size() ? 0 : page_selected + 1; + page_selected + 1 >= page_functions.size() ? 0 : page_selected + 1; multicore_reset_core1(); clear_display(); multicore_launch_core1(page_functions[page_selected]); diff --git a/ui.cpp b/ui.cpp index 8a13782..801a4ff 100644 --- a/ui.cpp +++ b/ui.cpp @@ -171,7 +171,67 @@ void time_page() { } } -void alarm_set_page() { draw_ui_circle(BLUE); } +static uint16_t get_alarm_x(uint8_t index) { + return (index % 2 == 0) ? 40 : (MAX_X - 40 - ALARM_BOX_WIDTH); +} + +static uint8_t get_alarm_y(uint8_t index) { + return 40 + (index / 2) * (ALARM_BOX_HEIGHT + 5); +} + + +[[noreturn]] void alarm_set_page() { + draw_ui_circle(BLUE); + full_redraw: + multicore_fifo_drain(); + uint8_t y_pos = 40; + + uint8_t selected_index = static_cast(static_cast(get_knob_percentage())/100.0f*static_cast(alarms.size())); + for (uint8_t i = 0; i < alarms.size(); i++) { + print_alarm(&alarms[i], i == selected_index ? SELECTED : NOT_SELECTED, !(i % 2) ? 40 : MAX_X-40-ALARM_BOX_WIDTH,y_pos); + if (i % 2) { + y_pos += ALARM_BOX_HEIGHT+5; + } + } + print_add_button(MAX_X/2,MAX_Y-14,20,selected_index == alarms.size() ? SELECTED : NOT_SELECTED); + + while (true) { + switch (static_cast(multicore_fifo_pop_blocking())) { + case PRIMARY_BUTTON_PRESSED: + if (selected_index == alarms.size()) { + alarms.emplace_back(0, 0, std::array{false, false, false, false, false, false, false}, false, false); + goto full_redraw; + } else { + secondary_button_override = true; + print_alarm(&alarms[selected_index], SETTING,get_alarm_x(selected_index),get_alarm_y(selected_index)); + secondary_button_override = false; + goto full_redraw; + } + break; + case SECONDARY_BUTTON_PRESSED: + break; + case KNOB_CHANGE: { + uint8_t new_index = static_cast(static_cast(get_knob_percentage())/100.0f*static_cast(alarms.size())); + if (new_index != selected_index) { + if (selected_index < alarms.size()) { + print_alarm(&alarms[selected_index], NOT_SELECTED,get_alarm_x(selected_index),get_alarm_y(selected_index)); + } else { + print_add_button(MAX_X/2,MAX_Y-14,20,NOT_SELECTED); + } + if (new_index >= alarms.size()) { + print_add_button(MAX_X/2,MAX_Y-14,20,SELECTED); + } else { + print_alarm(&alarms[new_index], SELECTED,get_alarm_x(new_index),get_alarm_y(new_index)); + } + selected_index = new_index; + } + break; + } + } + multicore_fifo_drain(); // prevent event stucking + } + +} std::array page_functions = {config_page, time_page, alarm_set_page};