#include #include #include #include #include #include "Font5x7FixedMono.h" #include "alarm_sound.h" #include "control.h" #include "display.h" #include "gc9a01.h" #include "macros.h" #include "lwip/arch.h" #include "multicore_events.h" #include "pins.h" #include "smart_alarm.h" #include "pico/multicore.h" #include "pico/time.h" #include "sound.h" #include "timezones.h" #include "hardware/gpio.h" #include "bell.h" #include "link.h" bool secondary_button_override = false; struct configuration { char* text; void (*set_text)(const configuration* self); void (*update)(configuration* self,bool hard); uint32_t local_conf; // can be used as diferent data types uint8_t additional_data; bool override_secondary_button; }; void timezone_set_text(const configuration* self) {sprintf(self->text, "TIMEZONE: %s", timezones[self->local_conf].zone_name);} void timezone_update(configuration* self,bool hard) { uint16_t selected_option = get_knob_percentage(); self->additional_data %= std::size(timezones)/100; selected_option+= self->additional_data*100; self->local_conf = selected_option >= std::size(timezones) ? std::size(timezones)-1 : selected_option; if (hard) { timezone_index = self->local_conf; // todo store to flash } } void volume_set_text(const configuration* self) {sprintf(self->text, "VOLUME: %0.1f", *(float*)&self->local_conf);} void volume_update(configuration* self,bool hard) { *(float*)&self->local_conf = static_cast(get_knob_percentage())/10.0f; if (hard) { volume_multiplier = *(float*)&self->local_conf; // todo store to flash } } [[noreturn]] void config_page() { char volume_str[17]; char timezone_str[41]; std::array options = {{{timezone_str,&timezone_set_text,&timezone_update,timezone_index, 0,true}, {volume_str,&volume_set_text,&volume_update,*(uint32_t*)&volume_multiplier, 0, false}}}; const GFXfont *font = &Font5x7FixedMono; draw_ui_circle(RED); multicore_fifo_drain(); uint8_t selected_option; bool configuring = false; while (true) { for (uint8_t i = 0; i < ARRAY_LENGTH(options); i++) { options[i].set_text(&options[i]); } if (!configuring) { selected_option = std::min( static_cast((get_knob_percentage() / 100.0f) * options.size()), static_cast(options.size() - 1)); } // Find the actual maximum text width that will be rendered uint16_t max_text_width_pixels = 0; for (uint8_t i = 0; i < options.size(); i++) { uint16_t text_width = strlen(options[i].text) * 5; if (text_width > max_text_width_pixels) { max_text_width_pixels = text_width; } } // Add proper padding to ensure text doesn't touch cyan lines const uint8_t horizontal_padding = 15; // Increased padding const uint8_t half_boundary_width = (max_text_width_pixels + (2 * horizontal_padding)) / 2; constexpr uint8_t center_pos = (MAX_X / 2); // Calculate cyan line positions with proper spacing uint16_t left_cyan_x = center_pos - half_boundary_width; uint16_t right_cyan_x = center_pos + half_boundary_width; // Get vertical boundaries std::array circle_y_pos = get_ui_circle_vertical_pos(right_cyan_x); uint16_t y_offset = circle_y_pos[0] + 2; // right gc9a01_vline(&display, right_cyan_x, y_offset, (circle_y_pos[1] - 1) - y_offset, CYAN); // left gc9a01_vline(&display, left_cyan_x, y_offset, (circle_y_pos[1] - 1) - y_offset, CYAN); const uint8_t half_y_font = static_cast( roundf(static_cast(7) / static_cast(2))); y_offset += 20; // Render text with proper horizontal centering for (uint8_t i = 0; i < options.size(); i++) { uint16_t text_width_pixels = strlen(options[i].text) * 5; // Center each text line horizontally within the cyan boundaries uint16_t x_pos = (center_pos - (text_width_pixels / 2)); // Ensure text stays within cyan line boundaries if (x_pos < left_cyan_x + horizontal_padding) { x_pos = left_cyan_x + horizontal_padding; } else if (x_pos + text_width_pixels > right_cyan_x - horizontal_padding) { x_pos = right_cyan_x - horizontal_padding - text_width_pixels; } gc9a01_text_gfx_buffered(&display, font, options[i].text, x_pos-10 /*MAGIC IDK WHY*/, y_offset, i == selected_option ? configuring ? RED : GREEN : WHITE, BLACK); y_offset += half_y_font; // Draw separator line within cyan boundaries gc9a01_hline(&display, left_cyan_x + 2, y_offset, (right_cyan_x - left_cyan_x) - 4, WHITE); y_offset += half_y_font * 2; } switch (static_cast(multicore_fifo_pop_blocking())) { case PRIMARY_BUTTON_PRESSED: if (configuring) { options[selected_option].update(&options[selected_option], true); secondary_button_override = false; } else { if (options[selected_option].override_secondary_button) { secondary_button_override = true; } gc9a01_fill_rect(&display,left_cyan_x,circle_y_pos[0]+1,right_cyan_x-left_cyan_x,(circle_y_pos[1]-circle_y_pos[0])-2,BLACK); options[selected_option].update(&options[selected_option], false); } configuring = !configuring; break; case SECONDARY_BUTTON_PRESSED: options[selected_option].additional_data++; gc9a01_fill_rect(&display,left_cyan_x,circle_y_pos[0]+1,right_cyan_x-left_cyan_x,(circle_y_pos[1]-circle_y_pos[0])-2,BLACK); options[selected_option].update(&options[selected_option], false); break; case KNOB_CHANGE: if (configuring) { gc9a01_fill_rect(&display,left_cyan_x,circle_y_pos[0]+1,right_cyan_x-left_cyan_x,(circle_y_pos[1]-circle_y_pos[0])-2,BLACK); options[selected_option].update(&options[selected_option], false); } break; } multicore_fifo_drain(); // prevent event stucking } } [[noreturn]] void time_page() { draw_ui_circle(MAGENTA); print_time(true); while (true) { multicore_fifo_drain(); sleep_ms(500); print_time(); } } 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}; [[noreturn]] void alarm_gone_off_page() { printf("alarm went off\n"); clear_display(); page_selected = 1; secondary_button_override = false; draw_ui_circle(ORANGE); gc9a01_blit_buffer(&display,120-(bell_image.width/2),180-(bell_image.height/2),bell_image.width,bell_image.height,const_cast(bell_image.pixel_data),std::size(bell_image.pixel_data)); gpio_put(LED_PIN, true); play_alarm_audio(); print_time(true); multicore_fifo_drain(); while (true) { { uint32_t event; if (multicore_fifo_pop_timeout_us(500000,&event)) { if (event == PRIMARY_BUTTON_PRESSED) { stop_alarm_audio(); gpio_put(LED_PIN, false); clear_display(); if (get_core_num()) { asm volatile ( "ldr r3, %[stack_top] \n\t" "mov sp, r3 \n\t" : : [stack_top] "m" (__StackOneTop) : "r3" );// clear the stack to prevent stack overflow from circular function calling time_page(); } else { multicore_launch_core1(&time_page); } } } multicore_fifo_drain(); } print_time(); } }