/main/keyboard.c
#include "esp_system.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "u8g2.h"

#include "keyboard.h"
#include "gfx.h"
#include "fonts.h"

int key_init() {
	const char TAG[] = "key_init";
	esp_err_t ret;

	gpio_config_t conf = {
		pin_bit_mask: 1ull << 25 | 1ull << 26 | 1ull << 27,
		mode: GPIO_MODE_OUTPUT,
		pull_up_en: GPIO_PULLUP_DISABLE,
		pull_down_en: GPIO_PULLDOWN_DISABLE,
		intr_type: GPIO_INTR_DISABLE,
	};
	ret = gpio_config(&conf);
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not configure keyboard GPIO: %s", esp_err_to_name(ret));
		return 0;
	}

	conf.pin_bit_mask = 1ull << 34 | 1ull << 35 | 1ull << 36 | 1ull << 39;
	conf.mode = GPIO_MODE_INPUT;
	ret = gpio_config(&conf);
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not configure keyboard GPIO: %s", esp_err_to_name(ret));
		return 0;
	}

	return 1;
}

static int cols[] = { GPIO_NUM_25, GPIO_NUM_26, GPIO_NUM_27 };
static int rows[] = { GPIO_NUM_34, GPIO_NUM_35, GPIO_NUM_36, GPIO_NUM_39 };
key_state_t key_state, pressed, released;
TickType_t last_seen[KEY_COUNT];

void key_scan() {
	key_state_t new_state = 0;

	for (int c = 0; c < 3; c++) {
		gpio_set_level(cols[c], 1);
		for (int r = 0; r < 4; r++) {
			new_state = new_state | (gpio_get_level(rows[r]) << (r * 3 + c));
		}
		gpio_set_level(cols[c], 0);
	}

	pressed =   new_state & ~key_state;
	released = ~new_state &  key_state;
	TickType_t now = xTaskGetTickCount();
	for (int i = 0; i < KEY_COUNT; i++) {
		if (now - last_seen[i] < DEBOUNCE_TIMEOUT) {
			// We've already seen this one, remove it from our set
			pressed &= ~(1 << i);
			released &= ~(1 << i);
			continue;
		}
		if (((pressed | released) >> i) & 1) {
			last_seen[i] = now;
		}
	}

	key_state &= ~released;
	key_state |= pressed;

	if (pressed | released) {
		printf("pressed: %04X, released: %04X\n", pressed, released);
	}
}

key_event_t key_get_event(const TickType_t ticksToWait) {
	TickType_t ticksStart = xTaskGetTickCount();

	while (!(pressed | released)) {
		key_scan();
		if (pressed | released) {
			break;
		}
		if (ticksToWait == 0 || xTaskGetTickCount() - ticksStart > ticksToWait) {
			key_event_t e = {0, KEY_NO_EVENT};
			return e;
		}
		vTaskDelay(50 / portTICK_PERIOD_MS);
	}

	if (pressed) {
		for (int i = 0; i < KEY_COUNT; i++) {
			if ((pressed >> i) & 1) {
				// Clear this event
				pressed &= ~(1 << i);
				key_event_t e = {i, KEY_PRESSED};
				return e;
			}
		}
	} else if (released) {
		for (int i = 0; i < KEY_COUNT; i++) {
			if ((released >> i) & 1) {
				// Clear this event
				released &= ~(1 << i);
				key_event_t e = {i, KEY_RELEASED};
				return e;
			}
		}
	}

	key_event_t e = {0, KEY_NO_EVENT};
	return e;
}

keycode_t key_get_pressed() {
	key_event_t ke;
	do {
		ke = key_get_event(portMAX_DELAY);
	} while (ke.type != KEY_PRESSED);

	return ke.code;
}

void key_test() {
	// We do our own drawing, so disable the gfx thread
	gfx_set_enabled(false);

	printf("key_test\n");
	u8g2_SetFont(&u8g2, maximum_overdrift);

	while (true) {
		key_scan();

		printf("Key State: ");
		for (int i = 0; i < KEY_COUNT; i++) {
			printf("%d", (key_state >> (KEY_COUNT - i - 1)) & 1);
		}
		printf("\n");

		if (key_state == (BIT(KEY_OFF) | BIT(KEY_BAND))) {
			break;
		}

		u8g2_ClearBuffer(&u8g2);

		if (key_state & BIT(KEY_4)) {
			u8g2_DrawBox(&u8g2, 0, 0, 7, 7);
		}
		u8g2_SetDrawColor(&u8g2, !(key_state & BIT(KEY_4)));
		u8g2_DrawStr(&u8g2, 1, 7, "4");

		if (key_state & BIT(KEY_BAND)) {
			u8g2_DrawBox(&u8g2, 48, 0, 25, 7);
		}
		u8g2_SetDrawColor(&u8g2, !(key_state & BIT(KEY_BAND)));
		u8g2_DrawStr(&u8g2, 49, 7, "BAND");

		if (key_state & BIT(KEY_5)) {
			u8g2_DrawBox(&u8g2, 96, 0, 7, 7);
		}
		u8g2_SetDrawColor(&u8g2, !(key_state & BIT(KEY_5)));
		u8g2_DrawStr(&u8g2, 97, 7, "5");

		if (key_state & BIT(KEY_1)) {
			u8g2_DrawBox(&u8g2, 0, 8, 7, 7);
		}
		u8g2_SetDrawColor(&u8g2, !(key_state & BIT(KEY_1)));
		u8g2_DrawStr(&u8g2, 1, 15, "1");

		if (key_state & BIT(KEY_3)) {
			u8g2_DrawBox(&u8g2, 48, 8, 7, 7);
		}
		u8g2_SetDrawColor(&u8g2, !(key_state & BIT(KEY_3)));
		u8g2_DrawStr(&u8g2, 49, 15, "3");

		if (key_state & BIT(KEY_2)) {
			u8g2_DrawBox(&u8g2, 96, 8, 7, 7);
		}
		u8g2_SetDrawColor(&u8g2, !(key_state & BIT(KEY_2)));
		u8g2_DrawStr(&u8g2, 97, 15, "2");

		if (key_state & BIT(KEY_ON)) {
			u8g2_DrawBox(&u8g2, 0, 16, 13, 7);
		}
		u8g2_SetDrawColor(&u8g2, !(key_state & BIT(KEY_ON)));
		u8g2_DrawStr(&u8g2, 1, 23, "ON");

		if (key_state & BIT(KEY_SNOOZE)) {
			u8g2_DrawBox(&u8g2, 48, 16, 37, 7);
		}
		u8g2_SetDrawColor(&u8g2, !(key_state & BIT(KEY_SNOOZE)));
		u8g2_DrawStr(&u8g2, 49, 23, "SNOOZE");

		if (key_state & BIT(KEY_OFF)) {
			u8g2_DrawBox(&u8g2, 96, 16, 19, 7);
		}
		u8g2_SetDrawColor(&u8g2, !(key_state & BIT(KEY_OFF)));
		u8g2_DrawStr(&u8g2, 97, 23, "OFF");

		if (key_state & BIT(KEY_TUNE_PLUS)) {
			u8g2_DrawBox(&u8g2, 0, 24, 29, 7);
		}
		u8g2_SetDrawColor(&u8g2, !(key_state & BIT(KEY_TUNE_PLUS)));
		u8g2_DrawStr(&u8g2, 1, 31, "TUNE+");

		if (key_state & BIT(KEY_ENTER)) {
			u8g2_DrawBox(&u8g2, 48, 24, 31, 7);
		}
		u8g2_SetDrawColor(&u8g2, !(key_state & BIT(KEY_ENTER)));
		u8g2_DrawStr(&u8g2, 49, 31, "ENTER");

		if (key_state & BIT(KEY_TUNE_MINUS)) {
			u8g2_DrawBox(&u8g2, 96, 24, 29, 7);
		}
		u8g2_SetDrawColor(&u8g2, !(key_state & BIT(KEY_TUNE_MINUS)));
		u8g2_DrawStr(&u8g2, 97, 31, "TUNE-");

		u8g2_SendBuffer(&u8g2);
	}

	gfx_set_enabled(true);
}