From 0999d439dde65761713b4558d93aaaa9a7e31b18 Mon Sep 17 00:00:00 2001 From: Philippe G Date: Fri, 5 Jun 2020 16:46:38 -0700 Subject: [PATCH] knob-only navigation --- components/services/audio_controls.c | 64 +++++++++++++++++++++++++--- components/squeezelite/controls.c | 43 +++++++++++-------- 2 files changed, 83 insertions(+), 24 deletions(-) diff --git a/components/services/audio_controls.c b/components/services/audio_controls.c index f2447993..85ecd1ec 100644 --- a/components/services/audio_controls.c +++ b/components/services/audio_controls.c @@ -11,11 +11,13 @@ #include #include #include +#include "freertos/FreeRTOS.h" +#include "freertos/timers.h" #include "esp_system.h" #include "esp_log.h" #include "cJSON.h" #include "buttons.h" -#include "platform_config.h" +#include "config.h" #include "accessors.h" #include "audio_controls.h" @@ -35,6 +37,7 @@ static esp_err_t actrls_process_action (const cJSON * member, actrls_config_t *c static esp_err_t actrls_init_json(const char *profile_name, bool create); static void control_rotary_handler(void *client, rotary_event_e event, bool long_press); +static void rotary_timer( TimerHandle_t xTimer ); static const actrls_config_map_t actrls_config_map[] = { @@ -70,6 +73,9 @@ static actrls_ir_handler_t *default_ir_handler, *current_ir_handler; static struct { bool long_state; bool volume_lock; + TimerHandle_t timer; + bool click_pending; + int left_count; } rotary; static const struct ir_action_map_s{ @@ -132,8 +138,16 @@ esp_err_t actrls_init(const char *profile_name) { if ((p = strcasestr(config, "A")) != NULL) A = atoi(strchr(p, '=') + 1); if ((p = strcasestr(config, "B")) != NULL) B = atoi(strchr(p, '=') + 1); if ((p = strcasestr(config, "SW")) != NULL) SW = atoi(strchr(p, '=') + 1); - if ((p = strcasestr(config, "volume")) != NULL) rotary.volume_lock = true; - if ((p = strcasestr(config, "longpress")) != NULL) longpress = 1000; + if ((p = strcasestr(config, "knobonly")) != NULL) { + p = strchr(p, '='); + int double_press = p ? atoi(p + 1) : 350; + rotary.timer = xTimerCreate("knobTimer", double_press / portTICK_RATE_MS, pdFALSE, NULL, rotary_timer); + longpress = 500; + ESP_LOGI(TAG, "single knob navigation %d", double_press); + } else { + if ((p = strcasestr(config, "volume")) != NULL) rotary.volume_lock = true; + if ((p = strcasestr(config, "longpress")) != NULL) longpress = 1000; + } // create rotary (no handling of long press) err = create_rotary(NULL, A, B, SW, longpress, control_rotary_handler) ? ESP_OK : ESP_FAIL; @@ -222,17 +236,42 @@ static void control_rotary_handler(void *client, rotary_event_e event, bool long switch(event) { case ROTARY_LEFT: - if (rotary.long_state) action = ACTRLS_PREV; + if (rotary.timer) { + if (rotary.left_count) { + action = KNOB_LEFT; + // need to add a left button the first time + if (rotary.left_count == 1) (*current_controls[KNOB_LEFT])(true); + } + xTimerStart(rotary.timer, 20 / portTICK_RATE_MS); + rotary.left_count++; + } + else if (rotary.long_state) action = ACTRLS_PREV; else if (rotary.volume_lock) action = ACTRLS_VOLDOWN; else action = KNOB_LEFT; break; case ROTARY_RIGHT: - if (rotary.long_state) action = ACTRLS_NEXT; + if (rotary.timer) { + if (rotary.left_count == 1) { + action = ACTRLS_PAUSE; + rotary.left_count = 0; + xTimerStop(rotary.timer, 0); + } else action = KNOB_RIGHT; + } + else if (rotary.long_state) action = ACTRLS_NEXT; else if (rotary.volume_lock) action = ACTRLS_VOLUP; else action = KNOB_RIGHT; break; case ROTARY_PRESSED: - if (long_press) rotary.long_state = !rotary.long_state; + if (rotary.timer) { + if (long_press) action = ACTRLS_PLAY; + else if (rotary.click_pending) { + action = BCTRLS_LEFT; + xTimerStop(rotary.timer, 0); + } + else xTimerStart(rotary.timer, 20 / portTICK_RATE_MS); + rotary.click_pending = !rotary.click_pending; + } + else if (long_press) rotary.long_state = !rotary.long_state; else if (rotary.volume_lock) action = ACTRLS_TOGGLE; else action = KNOB_PUSH; break; @@ -243,6 +282,19 @@ static void control_rotary_handler(void *client, rotary_event_e event, bool long if (action != ACTRLS_NONE) (*current_controls[action])(pressed); } +/**************************************************************************************** + * + */ +static void rotary_timer( TimerHandle_t xTimer ) { + if (rotary.click_pending) { + (*current_controls[KNOB_PUSH])(true); + rotary.click_pending = false; + } else if (rotary.left_count) { + if (rotary.left_count == 1) (*current_controls[KNOB_LEFT])(true); + rotary.left_count = 0; + } +} + /**************************************************************************************** * */ diff --git a/components/squeezelite/controls.c b/components/squeezelite/controls.c index 7c1ff03a..fb6c63ae 100644 --- a/components/squeezelite/controls.c +++ b/components/squeezelite/controls.c @@ -7,7 +7,7 @@ */ #include "squeezelite.h" -#include "platform_config.h" +#include "config.h" #include "audio_controls.h" static log_level loglevel = lINFO; @@ -49,6 +49,7 @@ struct IR_header { static in_addr_t server_ip; static u16_t server_hport; static u16_t server_cport; +static int cli_sock = -1; static u8_t mac[6]; static void (*chained_notify)(in_addr_t, u16_t, u16_t); static bool raw_mode; @@ -243,38 +244,44 @@ const actrls_t LMS_controls = { * */ static void cli_send_cmd(char *cmd) { - char packet[64]; - struct sockaddr_in addr; - socklen_t addrlen = sizeof(addr); - int len, sock = socket(AF_INET, SOCK_STREAM, 0); + char packet[96]; + int len; - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = server_ip; - addr.sin_port = htons(server_cport); - - if (connect(sock, (struct sockaddr *) &addr, addrlen) < 0) { - LOG_ERROR("unable to connect to server %s:%hu with cli", inet_ntoa(server_ip), server_cport); - return; - } - len = sprintf(packet, "%02x:%02x:%02x:%02x:%02x:%02x %s\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], cmd); LOG_DEBUG("sending command %s at %s:%hu", packet, inet_ntoa(server_ip), server_cport); - if (send(sock, packet, len, 0) < 0) { + if (send(cli_sock, packet, len, MSG_DONTWAIT) < 0) { LOG_WARN("cannot send CLI %s", packet); } - - closesocket(sock); + + // need to empty the RX buffer otherwise we'll lock the TCP/IP stack + len = recv(cli_sock, packet, 96, MSG_DONTWAIT); } /**************************************************************************************** * Notification when server changes */ static void notify(in_addr_t ip, u16_t hport, u16_t cport) { + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + server_ip = ip; server_hport = hport; server_cport = cport; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = server_ip; + addr.sin_port = htons(server_cport); + + // close existing CLI connection and open new one + if (cli_sock >= 0) closesocket(cli_sock); + cli_sock = socket(AF_INET, SOCK_STREAM, 0); + + if (connect(cli_sock, (struct sockaddr *) &addr, addrlen) < 0) { + LOG_ERROR("unable to connect to server %s:%hu with cli", inet_ntoa(server_ip), server_cport); + cli_sock = -1; + } + LOG_INFO("notified server %s hport %hu cport %hu", inet_ntoa(ip), hport, cport); if (chained_notify) (*chained_notify)(ip, hport, cport); @@ -303,4 +310,4 @@ void sb_controls_init(void) { chained_notify = server_notify; server_notify = notify; -} +} \ No newline at end of file