Add bluetooth mode, streaming reads thread and test
+static uint8_t* btn_list[] = {
+ btn_1_bits,
+ btn_2_bits,
+ btn_3_bits,
+ btn_4_bits,
+ btn_5_bits,
+};
idf_component_register(
- SRCS "main.c" "keyboard.c" "u8g2_esp32_hal.c" "io.c" "fonts.c" "pm.c" "gfx.c" "audio.c" "bt.c" "battery.c"
+ SRCS "main.c"
+ "keyboard.c"
+ "u8g2_esp32_hal.c"
+ "io.c"
+ "fonts.c"
+ "pm.c"
+ "gfx.c"
+ "audio.c"
+ "bt.c"
+ "battery.c"
+ "play.c"
+ "file.c"
INCLUDE_DIRS "."
)
#include "audio.h"
#include "bt.h"
#include "gfx.h"
+#include "play.h"
void _bt_set_mode(bt_mode m);
bt_mode mode = BT_INACTIVE;
esp_bd_addr_t pairing_peer = {0};
char bt_addr_buf[18];
-esp_avrc_playback_stat_t playback_state = ESP_AVRC_PLAYBACK_STOPPED;
+bt_playback_state playback_state = {0};
char* bt_addr_str(esp_bd_addr_t addr) {
snprintf(bt_addr_buf, 18, "%02x:%02x:%02x:%02x:%02x:%02x", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
return s;
}
+int max(int a, int b) {
+ return a > b ? a : b;
+}
+
void set_last_connected(esp_bd_addr_t* bda) {
const char TAG[] = "set_last_connected";
nvs_handle_t h;
case ESP_A2D_CONNECTION_STATE_CONNECTED:
event_str = "Connected to";
set_last_connected(&p->remote_bda);
+ play_set_media_type(PLAY_MEDIA_BLUETOOTH);
break;
case ESP_A2D_CONNECTION_STATE_CONNECTING:
event_str = "Connecting to";
}
}
+void grab_metadata() {
+ esp_err_t ret;
+
+ ret = esp_avrc_ct_send_metadata_cmd(1, ESP_AVRC_MD_ATTR_TITLE | ESP_AVRC_MD_ATTR_ARTIST | ESP_AVRC_MD_ATTR_PLAYING_TIME);
+ if (ret != ESP_OK) {
+ ESP_LOGE("BT", "Could not send metadata request: %s", esp_err_to_name(ret));
+ }
+}
+
void a2dp_data_callback(const uint8_t *data, uint32_t len) {
audio_send((uint16_t*)data, len / 2);
}
const char TAG[] = "avrc_callback";
char *text;
esp_err_t ret;
+ int len;
switch (event) {
case ESP_AVRC_CT_CONNECTION_STATE_EVT:
ESP_LOGI(TAG, "connection state connected:%d addr:%s", param->conn_stat.connected, bt_addr_str(param->conn_stat.remote_bda));
+ if (param->conn_stat.connected) {
+ grab_metadata();
+ }
break;
case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT:
// param->psth_rsp
// param->meta_rsp
text = arr_to_str(param->meta_rsp.attr_text, param->meta_rsp.attr_length);
ESP_LOGI(TAG, "metadata response attr_id:%d text:%s", param->meta_rsp.attr_id, text);
+ gfx_data_t* gd = gfx_acquire_data(SCENE_NO_CHANGE);
+ switch(param->meta_rsp.attr_id) {
+ case ESP_AVRC_MD_ATTR_TITLE:
+ len = max(param->meta_rsp.attr_length, META_STR_MAX);
+ memcpy(gd->playing_meta.title, param->meta_rsp.attr_text, len);
+ gd->playing_meta.title[META_STR_MAX - 1] = 0;
+ break;
+ case ESP_AVRC_MD_ATTR_ARTIST:
+ len = max(param->meta_rsp.attr_length, META_STR_MAX);
+ memcpy(gd->playing_meta.artist, param->meta_rsp.attr_text, len);
+ gd->playing_meta.artist[META_STR_MAX - 1] = 0;
+ break;
+ case ESP_AVRC_MD_ATTR_PLAYING_TIME:
+ gd->playing_meta.track_length = atoi(text) / 1000;
+ break;
+ }
+ gfx_release_data();
free(text);
+
break;
case ESP_AVRC_CT_PLAY_STATUS_RSP_EVT:
// ???
break;
}
ESP_LOGI(TAG, "playback status changed:%s", text);
+ grab_metadata();
- playback_state = param->change_ntf.event_parameter.playback;
+ playback_state.state = param->change_ntf.event_parameter.playback;
ret = esp_avrc_ct_send_register_notification_cmd(1, ESP_AVRC_RN_PLAY_STATUS_CHANGE, 0);
if (ret != ESP_OK) {
case ESP_AVRC_RN_TRACK_CHANGE:
ESP_LOGI(TAG, "track change:");
print_hex(param->change_ntf.event_parameter.elm_id, 8);
+ printf("\n");
+ grab_metadata();
+
+ ret = esp_avrc_ct_send_register_notification_cmd(3, ESP_AVRC_RN_TRACK_CHANGE, 0);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "Could not register track change");
+ }
break;
case ESP_AVRC_RN_PLAY_POS_CHANGED:
ESP_LOGI(TAG, "play position change:%d", param->change_ntf.event_parameter.play_pos);
ESP_LOGI(TAG, "volume change not supported");
}
+ if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, events, ESP_AVRC_RN_TRACK_CHANGE)) {
+ ret = esp_avrc_ct_send_register_notification_cmd(3, ESP_AVRC_RN_TRACK_CHANGE, 0);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "Could not register track change");
+ }
+ ESP_LOGI(TAG, "track change notification registered");
+ } else {
+ ESP_LOGI(TAG, "track change not supported");
+ }
+
break;
case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT:
// param->set_volume_rsp
esp_avrc_ct_send_passthrough_cmd(1, cmd, state);
}
-esp_avrc_playback_stat_t bt_get_playback_state() {
- return playback_state;
+bt_playback_state* bt_get_playback_state() {
+ return &playback_state;
}
BT_DISCOVERABLE,
} bt_mode;
+
+typedef struct {
+ esp_avrc_playback_stat_t state;
+} bt_playback_state;
+
int bt_init();
bt_mode bt_get_mode();
void bt_set_mode(bt_mode m);
void bt_confirm_pair(int accept);
void bt_send_key(esp_avrc_pt_cmd_t cmd, esp_avrc_pt_cmd_state_t state);
-esp_avrc_playback_stat_t bt_get_playback_state();
+bt_playback_state* bt_get_playback_state();
#endif // __BT_H
+#include <stdio.h>
+#include <stdbool.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/queue.h"
+#include "freertos/semphr.h"
+#include "freertos/stream_buffer.h"
+#include "esp_log.h"
+
+#include "file.h"
+
+#define BUFSIZE 4096
+#define TAG "file reader"
+
+typedef enum {
+ FILE_READER_OPEN,
+ FILE_READER_SUBSCRIBE,
+ FILE_READER_START,
+ FILE_READER_STOP,
+ FILE_READER_SEEK,
+ FILE_READER_CLOSE,
+} file_reader_command_t;
+
+typedef struct {
+ long offset;
+ int whence;
+} file_reader_seek_args_t;
+
+typedef struct {
+ file_reader_command_t command;
+ union {
+ const char* open_filename;
+ StreamBufferHandle_t subscribe_sb;
+ file_reader_seek_args_t seek;
+ };
+} file_reader_command_args_t;
+
+QueueHandle_t file_reader_command_queue = NULL;
+TaskHandle_t file_reader_task_handle = NULL;
+StreamBufferHandle_t file_reader_streambuffer = NULL;
+SemaphoreHandle_t seek_sync_sem = NULL;
+
+void file_reader_task(void* pvParameters) {
+ BaseType_t err;
+ bool reading = false;
+ size_t bytes_read = 0, bytes_sent = 0;
+ FILE* f = NULL;
+
+ uint8_t *buf = (uint8_t *) malloc(BUFSIZE);
+ if (buf == NULL) {
+ ESP_LOGE(TAG, "Could not allocate read buffer");
+ return;
+ }
+
+ ESP_LOGI(TAG, "Reader task started");
+
+ while (1) {
+ file_reader_command_args_t a;
+ err = xQueueReceive(file_reader_command_queue, &a, reading ? 0 : portMAX_DELAY);
+ if (err == pdTRUE) {
+ ESP_LOGI(TAG, "Received command %d", a.command);
+ switch (a.command) {
+ case FILE_READER_OPEN:
+ if (f) {
+ fclose(f);
+ }
+ f = fopen(a.open_filename, "r");
+ if (f == NULL) {
+ ESP_LOGE(TAG, "Could not open %s", a.open_filename);
+ }
+ break;
+ case FILE_READER_SUBSCRIBE:
+ file_reader_streambuffer = a.subscribe_sb;
+ break;
+ case FILE_READER_START:
+ reading = true;
+ break;
+ case FILE_READER_STOP:
+ reading = false;
+ break;
+ case FILE_READER_SEEK:
+ if (f == NULL) {
+ ESP_LOGE(TAG, "Cannot seek without file");
+ break;
+ }
+ fseek(f, a.seek.offset, a.seek.whence);
+ bytes_sent = bytes_read = 0;
+ if (file_reader_streambuffer) {
+ err = xStreamBufferReset(file_reader_streambuffer);
+ if (err != pdPASS) {
+ ESP_LOGE(TAG, "Could not reset streambuffer during seek");
+ }
+ }
+ xSemaphoreGive(seek_sync_sem);
+ break;
+ case FILE_READER_CLOSE:
+ fclose(f);
+ f = NULL;
+ reading = false;
+ bytes_sent = bytes_read = 0;
+ break;
+ }
+ }
+
+ if (reading) {
+ ESP_LOGI(TAG, "%d sent %d read", bytes_sent, bytes_read);
+ if (bytes_sent == bytes_read) {
+ if (f == NULL) {
+ ESP_LOGE(TAG, "FILE pointer null while reading");
+ reading = false;
+ } else if (feof(f)) {
+ ESP_LOGI(TAG, "end of file");
+ fclose(f);
+ f = NULL;
+ } else {
+ bytes_sent = 0;
+ bytes_read = fread(buf, 1, BUFSIZE, f);
+ ESP_LOGI(TAG, "read %d bytes", bytes_read);
+ }
+ }
+ bytes_sent += xStreamBufferSend(
+ file_reader_streambuffer,
+ buf + bytes_sent,
+ bytes_read - bytes_sent,
+ 500 / portTICK_PERIOD_MS
+ );
+ }
+ }
+
+ vTaskDelete(NULL);
+}
+
+int file_init() {
+ BaseType_t err;
+
+ seek_sync_sem = xSemaphoreCreateBinary();
+ file_reader_command_queue = xQueueCreate(1, sizeof(file_reader_command_args_t));
+
+ err = xTaskCreate(file_reader_task, "file reader", 4096, NULL, 2, &file_reader_task_handle);
+ if (err != pdPASS) {
+ ESP_LOGE(TAG, "could not initialize file reader task: %d", err);
+ return 0;
+ }
+
+ return 1;
+}
+
+void file_reader_open(const char* filename) {
+ file_reader_command_args_t a;
+ a.command = FILE_READER_OPEN;
+ a.open_filename = filename;
+ xQueueSend(file_reader_command_queue, &a, portMAX_DELAY);
+}
+
+void file_reader_subscribe(StreamBufferHandle_t bh) {
+ file_reader_command_args_t a;
+ a.command = FILE_READER_SUBSCRIBE;
+ a.subscribe_sb = bh;
+ xQueueSend(file_reader_command_queue, &a, portMAX_DELAY);
+}
+
+void file_reader_start() {
+ file_reader_command_args_t a;
+ a.command = FILE_READER_START;
+ xQueueSend(file_reader_command_queue, &a, portMAX_DELAY);
+}
+
+void file_reader_stop() {
+ file_reader_command_args_t a;
+ a.command = FILE_READER_STOP;
+ xQueueSend(file_reader_command_queue, &a, portMAX_DELAY);
+}
+
+void file_reader_seek(long offset, int whence) {
+ file_reader_command_args_t a;
+ a.command = FILE_READER_SEEK;
+ a.seek.offset = offset;
+ a.seek.whence = whence;
+ xQueueSend(file_reader_command_queue, &a, portMAX_DELAY);
+ xSemaphoreTake(seek_sync_sem, portMAX_DELAY);
+}
+
+void file_reader_close() {
+ file_reader_command_args_t a;
+ a.command = FILE_READER_CLOSE;
+ xQueueSend(file_reader_command_queue, &a, portMAX_DELAY);
+}
+#pragma once
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/stream_buffer.h"
+
+int file_init();
+void file_reader_open(const char* filename);
+void file_reader_subscribe(StreamBufferHandle_t bh);
+void file_reader_start();
+void file_reader_stop();
+void file_reader_seek(long offset, int whence);
+void file_reader_close();
key_get_pressed();
}
-void draw_play(play_scene_t *data) {
+void draw_play(gfx_data_t *data) {
struct tm localtime;
- char buf[10];
- int len;
+ int len, x;
+ char buf[68];
+ uint64_t t = esp_timer_get_time();
u8g2_ClearBuffer(&u8g2);
+ u8g2_DrawHLine(&u8g2, 0, 20, 59);
+ u8g2_DrawLine(&u8g2, 59, 19, 68, 10);
+ u8g2_DrawHLine(&u8g2, 69, 9, 59);
+
+ if (data->play.play_state == PLAY_STATE_PLAYING) {
+ x = (t / 4000) % 256;
+ if (x < 128) {
+ u8g2_SetDrawColor(&u8g2, 0);
+ u8g2_DrawBox(&u8g2, x, 9, 13, 12);
+ u8g2_SetDrawColor(&u8g2, 1);
+ }
+ }
+
+ u8g2_DrawXBM(&u8g2, 2, 0, btn_rtrk_1_width, btn_rtrk_1_height, btn_rtrk_1_bits);
+ u8g2_DrawXBM(&u8g2, 13, 0, btn_rrev_2_width, btn_rrev_2_height, btn_rrev_2_bits);
+ if (data->play.play_state == PLAY_STATE_PLAYING) {
+ u8g2_DrawXBM(&u8g2, 22, 0, btn_play_3_width, btn_play_3_height, btn_play_3_bits);
+ } else {
+ u8g2_DrawXBM(&u8g2, 22, 0, btn_pause_3_width, btn_pause_3_height, btn_pause_3_bits);
+ }
+ u8g2_DrawXBM(&u8g2, 31, 0, btn_ffwd_4_width, btn_ffwd_4_height, btn_ffwd_4_bits);
+ u8g2_DrawXBM(&u8g2, 40, 0, btn_ftrk_5_width, btn_ftrk_5_height, btn_ftrk_5_bits);
+
u8g2_SetFont(&u8g2, hyperspace_7n);
- gmtime_r(&data->elapsed, &localtime);
+ gmtime_r(&data->playing_meta.track_position, &localtime);
strftime(buf, sizeof(buf), "%M:%S", &localtime);
len = u8g2_GetStrWidth(&u8g2, buf);
u8g2_DrawStr(&u8g2, 58 - len, 18, buf);
- gmtime_r(&data->total, &localtime);
+ gmtime_r(&data->playing_meta.track_length, &localtime);
strftime(buf, sizeof(buf), "%M:%S", &localtime);
u8g2_DrawStr(&u8g2, 70, 19, buf);
- u8g2_DrawHLine(&u8g2, 0, 20, 59);
- u8g2_DrawLine(&u8g2, 59, 19, 68, 10);
- u8g2_DrawHLine(&u8g2, 69, 9, 59);
+ // TODO: actual font
+ u8g2_SetFont(&u8g2, u8g2_font_6x10_tf);
+ if (data->playing_meta.artist[0]) {
+ snprintf(buf, 68, "%s - %s", data->playing_meta.artist, data->playing_meta.title);
+ } else {
+ snprintf(buf, 68, "%s", data->playing_meta.title);
+ }
- u8g2_SetFont(&u8g2, maximum_overdrift);
- u8g2_DrawStr(&u8g2, 2, 30, data->title);
+ u8g2_uint_t w = u8g2_GetStrWidth(&u8g2, buf);
+ if (w > 128) {
+ int l = w - 128;
+ int x = (t / 100000) % l;
+ u8g2_DrawStr(&u8g2, -x, 30, buf);
+ } else {
+ u8g2_DrawStr(&u8g2, 0, 30, buf);
+ }
}
void draw_bt(bt_info_t* info) {
draw_menu(&gfx_data.menu);
break;
case SCENE_PLAY:
- draw_play(&gfx_data.play);
+ draw_play(&gfx_data);
break;
case SCENE_BT:
draw_bt(&gfx_data.bt);
#include "battery.h"
#include "bt.h"
+#include "play.h"
+
+#define META_STR_MAX 32
typedef enum {
SCENE_CLOCK,
} scene_t;
typedef struct {
+ char title[META_STR_MAX];
+ char artist[META_STR_MAX];
+ time_t track_position;
+ time_t track_length;
+} playing_metadata_t;
+
+typedef struct {
const char *label;
void (*func)();
} menu_def_t;
} menu_scene_t;
typedef struct {
- char *title;
- time_t elapsed;
- time_t total;
+ play_state play_state;
} play_scene_t;
typedef struct {
scene_t scene;
bt_info_t bt;
battery_info_t battery;
+ playing_metadata_t playing_meta;
union {
clock_scene_t clock;
menu_scene_t menu;
#include "audio.h"
#include "battery.h"
#include "bt.h"
+#include "file.h"
#include "fonts.h"
+#include "play.h"
#include "sprites.h"
#include "keyboard.h"
#include "gfx.h"
return total_roll;
}
-typedef struct {
- const char * filename;
- StreamBufferHandle_t sb;
- int eof;
-} read_task_parameter_t;
-
-void read_task(void *pvParameters) {
- read_task_parameter_t *params = (read_task_parameter_t *) pvParameters;
-
- uint8_t *buf = (uint8_t *) malloc(BUFSIZE / 2);
- if (buf == NULL) {
- ESP_LOGE("read_task", "Could not allocate read buffer");
- return;
- }
-
- FILE *f = fopen(params->filename, "r");
- if (f == NULL) {
- ESP_LOGE("read_task", "Could not open %s", params->filename);
- return;
- }
-
- while (!feof(f)) {
- size_t n = fread(buf, 1, BUFSIZE / 2, f);
- ESP_LOGI("read_task", "read %d bytes", n);
- xStreamBufferSend(params->sb, buf, n, portMAX_DELAY);
- }
-
- params->eof = 1;
-
- vTaskDelete(NULL);
-}
-
+/*
void play_mp3() {
const char TAG[] = "play_mp3";
BaseType_t xerr;
ESP_LOGI(TAG, "wrote %d samples", bytes_written);
}
}
+*/
void print_task_list(void *pvParameters) {
while (1) {
do_menu("TEST", 5, menu);
}
-void play_mode() {
- TaskHandle_t decoder_task;
- key_event_t ke;
- struct timeval tv;
- float now, t;
-
- FATFS *fs = sd_card_mount();
- if (fs == NULL) {
- u8g2_ClearBuffer(&u8g2);
- u8g2_DrawStr(&u8g2, 51, 19, "NO SD");
- u8g2_SendBuffer(&u8g2);
- vTaskDelay(1000 / portTICK_PERIOD_MS);
- return;
- }
-
- int ret = audio_open();
- if (!ret) {
- gfx_message("NO AUDIO");
- return;
- }
-
- SemaphoreHandle_t waiter = xSemaphoreCreateBinary();
- xTaskCreate(play_flac, "flac", 4096, &waiter, 5, &decoder_task);
-
- gettimeofday(&tv, NULL);
- now = (float)tv.tv_sec + (float)tv.tv_usec / 1000000.0;
-
- while (ke = key_get_event(0), !(ke.type == KEY_PRESSED && ke.code == KEY_BAND)) {
- gettimeofday(&tv, NULL);
- t = (float)tv.tv_sec + (float)tv.tv_usec / 1000000.0;
- gfx_data_t *gd = gfx_acquire_data(SCENE_PLAY);
- gd->play.title = "FLAC";
- gd->play.elapsed = (time_t) (t - now);
- gd->play.total = (time_t) 341;
- gfx_release_data();
-
- vTaskDelay(100 / portTICK_PERIOD_MS);
- }
-
- // Kill the decoder
- xTaskNotifyGive(decoder_task);
-
- xSemaphoreTake(waiter, portMAX_DELAY);
-
- sd_card_unmount();
-
- audio_close();
-}
-
void bt_config_mode() {
key_event_t ke;
}
}
} else {
- if (ke.code == KEY_SNOOZE && ke.type != KEY_NO_EVENT) {
- if (bt_get_playback_state() == ESP_AVRC_PLAYBACK_PLAYING) {
- bt_send_key(ESP_AVRC_PT_CMD_PAUSE, ke.type - 1);
- } else {
- bt_send_key(ESP_AVRC_PT_CMD_PLAY, ke.type - 1);
- }
- }
-
if (ke.type == KEY_PRESSED) {
switch (ke.code) {
case KEY_1:
void main_menu() {
menu_def_t menu[] = {
- {"PLAY", play_mode},
+ {"PLAY", play_controller},
{"TEST", test_mode},
{"BT", bt_config_mode},
};
esp_err_t ret;
gpio_config_t conf = {
- pin_bit_mask: GPIO_SEL_13 | GPIO_SEL_15,
+ pin_bit_mask: 1ull << 13 | 1ull << 15,
mode: GPIO_MODE_INPUT,
pull_up_en: GPIO_PULLUP_ENABLE,
pull_down_en: GPIO_PULLDOWN_DISABLE,
return 0;
}
- conf.pin_bit_mask = GPIO_SEL_14;
+ conf.pin_bit_mask = 1ull << 14;
conf.mode = GPIO_MODE_OUTPUT;
conf.pull_up_en = GPIO_PULLUP_DISABLE;
conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
return 1;
}
+void print_hexdump_line(uint32_t addr, uint8_t* buf) {
+ printf("%08x: ", addr);
+ for (int j = 0; j < 16; j++) {
+ printf("%02x", buf[j]);
+ if (j % 4 == 3) {
+ printf(" ");
+ }
+ }
+ printf(" ");
+ for (int j = 0; j < 16; j++) {
+ if (buf[j] >= 0x20 && buf[j] < 0x7F) {
+ putc(buf[j], stdout);
+ } else {
+ putc('.', stdout);
+ }
+ }
+ printf("\n");
+}
+
+void stream_test() {
+ const char TAG[] = "stream_test";
+ uint8_t* buf = malloc(256);
+ StreamBufferHandle_t h = xStreamBufferCreate(256, 1);
+ uint32_t addr = 0;
+
+ sd_card_mount();
+
+ ESP_LOGI(TAG, "-------- start --------");
+
+ file_reader_open("/sd/rock.gcode");
+ file_reader_subscribe(h);
+ file_reader_start();
+ for (int i = 0; i < 256 / 16; i++) {
+ addr += xStreamBufferReceive(h, buf, 16, portMAX_DELAY);
+ print_hexdump_line(addr, buf);
+ }
+ file_reader_seek(0, SEEK_SET);
+ ESP_LOGI(TAG, "--------- seek --------");
+ for (int i = 0; i < 256 / 16; i++) {
+ addr += xStreamBufferReceive(h, buf, 16, portMAX_DELAY);
+ print_hexdump_line(addr, buf);
+ }
+ file_reader_close();
+
+ ESP_LOGI(TAG, "--------- end ---------");
+}
+
void app_main(void) {
nvs_init();
key_init();
io_init();
audio_init();
- sdspi_init();
gfx_init();
+ sdspi_init();
+ file_init();
battery_init();
bt_init();
//xTaskCreate(print_task_list, "task list", 4096, NULL, 1, NULL);
- //play_mp3();
- //xTaskCreate(play_flac, "flac", 4096, NULL, 5, NULL);
- //play_mod();
+ stream_test();
clock_mode();
}
+#include <sys/time.h>
+#include "freertos/FreeRTOS.h"
+#include "esp_log.h"
+
+#include "bt.h"
+#include "gfx.h"
+#include "keyboard.h"
+#include "play.h"
+
+play_media_type media_type = PLAY_MEDIA_NONE;
+
+void play_set_media_type(play_media_type t) {
+ media_type = t;
+}
+
+play_state get_play_state() {
+ switch (media_type) {
+ case PLAY_MEDIA_BLUETOOTH:
+ return (play_state) bt_get_playback_state()->state;
+ default:
+ ESP_LOGE("PLAY", "Unfinished media type: %d", media_type);
+ return PLAY_STATE_ERROR;
+ }
+}
+
+void play_send_command(play_command c) {
+ switch (media_type) {
+ case PLAY_MEDIA_BLUETOOTH: {
+ esp_avrc_pt_cmd_t button;
+ switch (c) {
+ case PLAY_COMMAND_STOP:
+ button = ESP_AVRC_PT_CMD_STOP;
+ break;
+ case PLAY_COMMAND_PLAY:
+ button = ESP_AVRC_PT_CMD_PLAY;
+ break;
+ case PLAY_COMMAND_PAUSE:
+ button = ESP_AVRC_PT_CMD_PAUSE;
+ break;
+ case PLAY_COMMAND_RTRK:
+ button = ESP_AVRC_PT_CMD_BACKWARD;
+ break;
+ case PLAY_COMMAND_FTRK:
+ button = ESP_AVRC_PT_CMD_FORWARD;
+ break;
+ case PLAY_COMMAND_RREV:
+ button = ESP_AVRC_PT_CMD_REWIND;
+ break;
+ case PLAY_COMMAND_FFWD:
+ button = ESP_AVRC_PT_CMD_FAST_FORWARD;
+ break;
+ default:
+ return;
+ }
+ bt_send_key(button, ESP_AVRC_PT_CMD_STATE_PRESSED);
+ bt_send_key(button, ESP_AVRC_PT_CMD_STATE_RELEASED);
+
+ break;
+ }
+ default:
+ ESP_LOGE("PLAY", "Unfinished media type: %d", media_type);
+ }
+}
+
+void play_controller() {
+ key_event_t ke;
+
+ while (ke = key_get_event(0), !(ke.type == KEY_PRESSED && ke.code == KEY_BAND)) {
+ play_state ps = get_play_state();
+ if (ke.type == KEY_PRESSED) {
+ switch (ke.code) {
+ case KEY_SNOOZE:
+ if (ps == PLAY_STATE_PLAYING) {
+ play_send_command(PLAY_COMMAND_PAUSE);
+ } else {
+ play_send_command(PLAY_COMMAND_PLAY);
+ }
+ break;
+ case KEY_1:
+ play_send_command(PLAY_COMMAND_RTRK);
+ break;
+ case KEY_2:
+ play_send_command(PLAY_COMMAND_RREV);
+ break;
+ case KEY_3:
+ if (ps == PLAY_STATE_PLAYING) {
+ play_send_command(PLAY_COMMAND_PAUSE);
+ } else {
+ play_send_command(PLAY_COMMAND_PLAY);
+ }
+ break;
+ case KEY_4:
+ play_send_command(PLAY_COMMAND_FFWD);
+ break;
+ case KEY_5:
+ play_send_command(PLAY_COMMAND_FTRK);
+ break;
+ default:
+ break;
+ }
+ }
+
+ gfx_data_t* gd = gfx_acquire_data(SCENE_PLAY);
+ gd->play.play_state = ps;
+ gfx_release_data();
+
+ vTaskDelay(50 / portTICK_PERIOD_MS);
+ }
+}
+
+/*
+void play_mode() {
+ TaskHandle_t decoder_task;
+ key_event_t ke;
+ struct timeval tv;
+ float now, t;
+
+ FATFS *fs = sd_card_mount();
+ if (fs == NULL) {
+ u8g2_ClearBuffer(&u8g2);
+ u8g2_DrawStr(&u8g2, 51, 19, "NO SD");
+ u8g2_SendBuffer(&u8g2);
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ return;
+ }
+
+ int ret = audio_open();
+ if (!ret) {
+ gfx_message("NO AUDIO");
+ return;
+ }
+
+ SemaphoreHandle_t waiter = xSemaphoreCreateBinary();
+ xTaskCreate(play_flac, "flac", 4096, &waiter, 5, &decoder_task);
+
+ gettimeofday(&tv, NULL);
+ now = (float)tv.tv_sec + (float)tv.tv_usec / 1000000.0;
+
+ while (ke = key_get_event(0), !(ke.type == KEY_PRESSED && ke.code == KEY_BAND)) {
+ if (ke.code == KEY_SNOOZE && ke.type != KEY_NO_EVENT) {
+ if (state == ESP_AVRC_PLAYBACK_PLAYING) {
+ bt_send_key(ESP_AVRC_PT_CMD_PAUSE, ke.type - 1);
+ } else {
+ bt_send_key(ESP_AVRC_PT_CMD_PLAY, ke.type - 1);
+ }
+ }
+
+ vTaskDelay(100 / portTICK_PERIOD_MS);
+ }
+
+ // Kill the decoder
+ xTaskNotifyGive(decoder_task);
+
+ xSemaphoreTake(waiter, portMAX_DELAY);
+
+ sd_card_unmount();
+
+ audio_close();
+}
+*/
+#pragma once
+
+typedef enum {
+ PLAY_MEDIA_NONE,
+ PLAY_MEDIA_BLUETOOTH,
+ PLAY_MEDIA_FLAC,
+ PLAY_MEDIA_MP3,
+} play_media_type;
+
+typedef enum {
+ PLAY_STATE_STOPPED,
+ PLAY_STATE_PLAYING,
+ PLAY_STATE_PAUSED,
+ PLAY_STATE_FFWD,
+ PLAY_STATE_RRWD,
+ PLAY_STATE_ERROR,
+} play_state;
+
+typedef enum {
+ PLAY_COMMAND_STOP,
+ PLAY_COMMAND_PLAY,
+ PLAY_COMMAND_PAUSE,
+ PLAY_COMMAND_FFWD,
+ PLAY_COMMAND_RREV,
+ PLAY_COMMAND_FTRK,
+ PLAY_COMMAND_RTRK,
+} play_command;
+
+void play_set_media_type(play_media_type t);
+void play_send_command(play_command c);
+void play_controller();
#define btn_5_height 7
static uint8_t btn_5_bits[] = {
0x1F, 0x11, 0x1D, 0x11, 0x17, 0x11, 0x1F, };
+#define btn_ffwd_4_width 7
+#define btn_ffwd_4_height 7
+static uint8_t btn_ffwd_4_bits[] = {
+ 0x0F, 0x15, 0x35, 0x71, 0x37, 0x17, 0x0F, };
+#define btn_ftrk_5_width 9
+#define btn_ftrk_5_height 7
+static uint8_t btn_ftrk_5_bits[] = {
+ 0x2F, 0x00, 0x51, 0x00, 0xBD, 0x00, 0x71, 0x01, 0xB7, 0x00, 0x51, 0x00,
+ 0x2F, 0x00, };
+#define btn_pause_3_width 7
+#define btn_pause_3_height 7
+static uint8_t btn_pause_3_bits[] = {
+ 0x36, 0x22, 0x2E, 0x22, 0x2E, 0x22, 0x36, };
+#define btn_play_3_width 7
+#define btn_play_3_height 7
+static uint8_t btn_play_3_bits[] = {
+ 0x0F, 0x03, 0x2F, 0x63, 0x2F, 0x03, 0x0F, };
+#define btn_rrev_2_width 7
+#define btn_rrev_2_height 7
+static uint8_t btn_rrev_2_bits[] = {
+ 0x78, 0x44, 0x5E, 0x47, 0x76, 0x44, 0x78, };
+#define btn_rtrk_1_width 9
+#define btn_rtrk_1_height 7
+static uint8_t btn_rtrk_1_bits[] = {
+ 0xE8, 0x01, 0xB4, 0x01, 0xBA, 0x01, 0xBD, 0x01, 0xBA, 0x01, 0xB4, 0x01,
+ 0xE8, 0x01, };
static uint8_t* btn_list[] = {
btn_1_bits,
btn_2_bits,