#include "star_code.h" #include #include #include #include #include #include "drivers/bottom_half.h" #include "char_lcd.h" #include "esp_timer.h" static const char* TAG = "star_code"; volatile bool handling_new_starcodes = false; static volatile bool system_initialized = false; // TODO: use the semaphore, convert to RWLock? static volatile SemaphoreHandle_t star_codes_sem; static std::vector star_codes; static const char EMPTY_STAR_CODE_HEADER[] = " "; esp_timer_handle_t starcode_delay_timer; static volatile bool processing_starcode; static volatile StarCodeEntry* current_starcode = nullptr; static volatile bool doing_starcode = false; static uint16_t starcode_waiting_on_release; static char current[STARCODE_MAX_LEN + 1]; static size_t current_idx; static void starcode_trigger_cb(void* arg) { (void) arg; processing_starcode = false; if (current_starcode != nullptr) { if (current_starcode->triggered_sem != nullptr) xSemaphoreGive(current_starcode->triggered_sem); if (current_starcode->callback != nullptr) (current_starcode->callback)(); current_starcode = nullptr; } // TODO: rename star code everywhere to starcode lcd_print_header_star_code(); } void star_code_handle_keypad(uint16_t* just_pressed, uint16_t* just_released) { if ((!processing_starcode) && handling_new_starcodes && (*just_pressed & (1 << KeypadKey::star))) { current_idx = 0; current[current_idx] = '\0'; doing_starcode = true; } if (doing_starcode) { // If we get a press while handling a starcode, we also want to capture the release of that key. starcode_waiting_on_release |= *just_pressed; KeypadKey key; while (take_key(&key, just_pressed)) { if (key == KeypadKey::star) { current_idx = 0; current[current_idx] = '\0'; } else if (key == KeypadKey::pound) { doing_starcode = false; if (current_idx != 0) { trigger_star_code(current); } } else { // shift the digits left if neccesary if (current_idx >= STARCODE_MAX_LEN) { for (int i = 1; i < current_idx; i++) { current[i-1] = current[i]; } current_idx--; } // append the character current[current_idx++] = char_of_keypad_key(key); current[current_idx] = '\0'; } lcd_print_header_star_code(); } } // capture any releases from starcodes uint16_t new_just_released = (*just_released) & (~starcode_waiting_on_release); starcode_waiting_on_release = starcode_waiting_on_release & (~*just_released); *just_released = new_just_released; } void init_star_code_system() { star_codes_sem = xSemaphoreCreateBinary(); xSemaphoreGive(star_codes_sem); const esp_timer_create_args_t timer_args = { .callback = &starcode_trigger_cb, .arg = nullptr, .dispatch_method = ESP_TIMER_TASK, .name = "starcode_trigger", .skip_unhandled_events = false, }; ESP_ERROR_CHECK(esp_timer_create(&timer_args, &starcode_delay_timer)); handling_new_starcodes = true; system_initialized = true; } /// Checks if a triggered code matches an expected code. /// @return true iff the codes match, where '*'s in the expected code can match any character in the triggered code static bool check_code_match(const char* triggered, const char* expected) { size_t triggered_len = strlen(triggered); size_t match_len = strlen(triggered); if (triggered_len != match_len) return false; for (int i = 0; i < triggered_len; i++) { if (!(expected[i] == '*' || expected[i] == triggered[i])) { return false; } } return true; } bool add_star_code(StarCodeEntry code) { ESP_LOGI(TAG, "Adding starcode: %s", code.code); if (code.code == nullptr || strlen(code.code) > STARCODE_MAX_LEN) { ESP_LOGW(TAG, "invalid code"); return false; } if (code.display_text != nullptr && strlen(code.display_text) > STARCODE_MAX_LEN + 1) { ESP_LOGW(TAG, "invalid display_text"); return false; } // check for a existing entry auto it = std::find_if(star_codes.begin(), star_codes.end(), [&](const StarCodeEntry& other) { return check_code_match(code.code, other.code); }); if (it != star_codes.end()) { // existing star code found! ESP_LOGW(TAG, "Duplicate starcode %s", code.code); return false; } star_codes.push_back(code); return true; } bool add_star_codes(const StarCodeEntry* codes, size_t len) { bool success = true; for (int i = 0; i < len; i++) { if (!add_star_code(codes[i])) { success = false; } } return success; } bool rm_star_code(const char* code) { ESP_LOGI(TAG, "Removing starcode: %s", code); auto it = std::find_if(star_codes.begin(), star_codes.end(), [&](const StarCodeEntry& star_code) { return strcmp(code, star_code.code) == 0; }); if (it == star_codes.end()) { ESP_LOGW(TAG, "Failed to remove star code %s", code); return false; } star_codes.erase(it); return true; } bool rm_star_codes(const StarCodeEntry* codes, size_t len) { bool success = true; for (int i = 0; i < len; i++) { if (!rm_star_code(codes[i].code)) { success = false; } } return success; } bool rm_star_codes_str(const char** codes, size_t len) { bool success = true; for (int i = 0; i < len; i++) { if (!rm_star_code(codes[i])) { success = false; } } return success; } void clear_star_codes() { star_codes.clear(); } bool trigger_star_code(const char* code) { auto it = std::find_if(star_codes.begin(), star_codes.end(), [&](const StarCodeEntry& other) { return check_code_match(code, other.code); }); uint64_t delay_us = 2'000'000; processing_starcode = true; if (it != star_codes.end()) { current_starcode = &*it; delay_us = current_starcode->delay_us; } ESP_ERROR_CHECK(esp_timer_start_once(starcode_delay_timer, delay_us)); return current_starcode != nullptr; } void pause_star_code_system() { doing_starcode = false; handling_new_starcodes = false; } void resume_star_code_system() { handling_new_starcodes = system_initialized; } void lcd_print_header_star_code() { if (!lcd_header_enabled()) return; // TODO: consider upping the display text size to be able to overwrite the game_state area. if (processing_starcode) { if (current_starcode == nullptr) { lcd_print(0, 0, "Invalid "); } else if (current_starcode->display_text != nullptr) { char buf[STARCODE_MAX_LEN + 2]; snprintf(buf, sizeof(buf), "%-10s", current_starcode->display_text); lcd_print(0, 0, buf); } else { lcd_print(0, 0, EMPTY_STAR_CODE_HEADER); } } else if (doing_starcode) { char buf[STARCODE_MAX_LEN + 2]; snprintf(buf, sizeof(buf), "*%-9s", current); lcd_print(0, 0, buf); } else { lcd_print(0, 0, EMPTY_STAR_CODE_HEADER); } }