257 lines
7.6 KiB
C++
257 lines
7.6 KiB
C++
#include "starcode.h"
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <esp_log.h>
|
|
#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<StarCodeEntry> star_codes;
|
|
|
|
static const char EMPTY_STAR_CODE_HEADER[] = " ";
|
|
|
|
esp_timer_handle_t starcode_delay_timer;
|
|
/// @brief `true` if we are delaying for a starcode
|
|
static volatile bool delaying_for_starcode;
|
|
static volatile StarCodeEntry* current_starcode = nullptr;
|
|
/// @brief `true` when we are handling user input for a starcode
|
|
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;
|
|
|
|
delaying_for_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();
|
|
}
|
|
|
|
|
|
void star_code_handle_keypad(uint16_t* just_pressed, uint16_t* just_released) {
|
|
if ((!delaying_for_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_DISPLAY_TEXT_MAX_LEN) {
|
|
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;
|
|
delaying_for_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 (delaying_for_starcode) {
|
|
if (current_starcode == nullptr) {
|
|
lcd_print(0, 0, "Invalid starcode ");
|
|
} else if (current_starcode->display_text != nullptr) {
|
|
char buf[21];
|
|
snprintf(buf, sizeof(buf), "%-20s", 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);
|
|
}
|
|
}
|
|
|
|
bool lcd_starcode_displaying_result() {
|
|
return delaying_for_starcode;
|
|
}
|