diff --git a/components/driver_bt/bt_app_sink.c b/components/driver_bt/bt_app_sink.c index 6175b138..56cd4ca3 100644 --- a/components/driver_bt/bt_app_sink.c +++ b/components/driver_bt/bt_app_sink.c @@ -64,18 +64,29 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param); static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param); static void volume_set_by_local_host(uint8_t volume); -static esp_a2d_audio_state_t s_audio_state = ESP_A2D_AUDIO_STATE_STOPPED; static const char *s_a2d_conn_state_str[] = {"Disconnected", "Connecting", "Connected", "Disconnecting"}; static const char *s_a2d_audio_state_str[] = {"Suspended", "Stopped", "Started"}; static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap; + static _lock_t s_volume_lock; static uint8_t s_volume = 0; static bool s_volume_notify; -static esp_avrc_playback_stat_t s_play_status = ESP_AVRC_PLAYBACK_STOPPED; -static uint8_t s_remote_bda[6]; +static bool s_playing = false; +static enum { AUDIO_IDLE, AUDIO_CONNECTED, AUDIO_ACTIVATED } s_audio = AUDIO_IDLE; + +static int s_sample_rate; static int tl; static bt_cmd_vcb_t cmd_handler_chain; -static char *s_artist, *s_album, *s_title; + +#define METADATA_LEN 128 + +static EXT_RAM_ATTR struct { + char artist[METADATA_LEN + 1]; + char album[METADATA_LEN + 1]; + char title[METADATA_LEN + 1]; + int duration; + bool updated; +} s_metadata; static void bt_volume_up(void) { // volume UP/DOWN buttons are not supported by iPhone/Android @@ -91,9 +102,8 @@ static void bt_volume_down(void) { } static void bt_toggle(void) { - if (s_play_status != ESP_AVRC_PLAYBACK_PLAYING) esp_avrc_ct_send_passthrough_cmd(tl++, ESP_AVRC_PT_CMD_PLAY, ESP_AVRC_PT_CMD_STATE_PRESSED); - else esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED); - //s_audio_state = ESP_A2D_AUDIO_STATE_STOPPED; + if (s_playing) esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED); + else esp_avrc_ct_send_passthrough_cmd(tl++, ESP_AVRC_PT_CMD_PLAY, ESP_AVRC_PT_CMD_STATE_PRESSED); } static void bt_play(void) { @@ -124,65 +134,68 @@ const static actrls_t controls = { bt_prev, bt_next, // prev, next }; -/* taking/giving audio system's control */ -void bt_master(bool on) { - if (on) actrls_set(controls, NULL); - else actrls_unset(); -} - /* disconnection */ void bt_disconnect(void) { displayer_control(DISPLAYER_SHUTDOWN); - esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED); - esp_a2d_sink_disconnect(s_remote_bda); + if (s_playing) esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED); actrls_unset(); ESP_LOGI(BT_AV_TAG, "forced disconnection"); } +/* update metadata if any */ +void update_metadata(bool force) { + if ((s_metadata.updated || force) && s_audio == AUDIO_ACTIVATED) { + (*bt_app_a2d_cmd_cb)(BT_SINK_PROGRESS, -1, s_metadata.duration); + (*bt_app_a2d_cmd_cb)(BT_SINK_METADATA, s_metadata.artist, s_metadata.album, s_metadata.title); + s_metadata.updated = false; + } else s_metadata.updated = force; +} + /* command handler */ static bool cmd_handler(bt_sink_cmd_t cmd, ...) { - bool chain = true, res = true; va_list args; va_start(args, cmd); + // handle audio event and stop if forbidden + if (!cmd_handler_chain(cmd, args)) { + va_end(args); + return false; + } + + // now handle events for display switch(cmd) { - case BT_SINK_CONNECTED: + case BT_SINK_AUDIO_STARTED: displayer_control(DISPLAYER_ACTIVATE, "BLUETOOTH"); break; - case BT_SINK_PLAY: - displayer_control(DISPLAYER_TIMER_RESUME); + case BT_SINK_AUDIO_STOPPED: + displayer_control(DISPLAYER_SUSPEND); break; + case BT_SINK_PLAY: + displayer_control(DISPLAYER_TIMER_RUN); + break; + case BT_SINK_STOP: + // not sure of difference between pause and stop for displayer case BT_SINK_PAUSE: displayer_control(DISPLAYER_TIMER_PAUSE); break; - case BT_SINK_STOP: - displayer_control(DISPLAYER_DISABLE); - break; - case BT_SINK_DISCONNECTED: - displayer_control(DISPLAYER_DISABLE); - break; case BT_SINK_METADATA: { char *artist = va_arg(args, char*), *album = va_arg(args, char*), *title = va_arg(args, char*); displayer_metadata(artist, album, title); - chain = false; break; } case BT_SINK_PROGRESS: { int elapsed = va_arg(args, int), duration = va_arg(args, int); displayer_timer(DISPLAYER_ELAPSED, elapsed, duration); - chain = false; break; } default: break; } - if (chain) res = cmd_handler_chain(cmd, args); - va_end(args); - return res; + return true; } /* callback for A2DP sink */ @@ -260,26 +273,40 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param) if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); (*bt_app_a2d_cmd_cb)(BT_SINK_DISCONNECTED); - actrls_unset(); } else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED){ - memcpy(s_remote_bda, bda, 6); esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); - if (!(*bt_app_a2d_cmd_cb)(BT_SINK_CONNECTED)){ - esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED); - esp_a2d_sink_disconnect(s_remote_bda); - } + (*bt_app_a2d_cmd_cb)(BT_SINK_CONNECTED); } break; } case ESP_A2D_AUDIO_STATE_EVT: { a2d = (esp_a2d_cb_param_t *)(p_param); ESP_LOGI(BT_AV_TAG, "A2DP audio state: %s", s_a2d_audio_state_str[a2d->audio_stat.state]); - s_audio_state = a2d->audio_stat.state; + if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) { - (*bt_app_a2d_cmd_cb)(BT_SINK_PLAY); + s_audio = AUDIO_CONNECTED; + + // verify that we can take control + if ((*bt_app_a2d_cmd_cb)(BT_SINK_AUDIO_STARTED, s_sample_rate)) { + // resynchronize events as¨PLAY might be sent before STARTED ... + s_audio = AUDIO_ACTIVATED; + + // send PLAY there, in case it was sent before AUDIO_STATE + if (s_playing) (*bt_app_a2d_cmd_cb)(BT_SINK_PLAY); + + // force metadata update + update_metadata(true); + + actrls_set(controls, NULL); + } else if (s_playing) { + // if decoder is busy but BT is playing, stop it (would be better to not ACK this command, but don't know how) + esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED); + } } else if (ESP_A2D_AUDIO_STATE_STOPPED == a2d->audio_stat.state || ESP_A2D_AUDIO_STATE_REMOTE_SUSPEND == a2d->audio_stat.state) { - (*bt_app_a2d_cmd_cb)(BT_SINK_STOP); + (*bt_app_a2d_cmd_cb)(BT_SINK_AUDIO_STOPPED); + s_audio = AUDIO_IDLE; + actrls_unset(); } break; } @@ -288,23 +315,23 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param) ESP_LOGI(BT_AV_TAG, "A2DP audio stream configuration, codec type %d", a2d->audio_cfg.mcc.type); // for now only SBC stream is supported if (a2d->audio_cfg.mcc.type == ESP_A2D_MCT_SBC) { - int sample_rate = 16000; + s_sample_rate = 16000; char oct0 = a2d->audio_cfg.mcc.cie.sbc[0]; if (oct0 & (0x01 << 6)) { - sample_rate = 32000; + s_sample_rate = 32000; } else if (oct0 & (0x01 << 5)) { - sample_rate = 44100; + s_sample_rate = 44100; } else if (oct0 & (0x01 << 4)) { - sample_rate = 48000; + s_sample_rate = 48000; } - (*bt_app_a2d_cmd_cb)(BT_SINK_RATE, sample_rate); + (*bt_app_a2d_cmd_cb)(BT_SINK_RATE, s_sample_rate); ESP_LOGI(BT_AV_TAG, "Configure audio player %x-%x-%x-%x", a2d->audio_cfg.mcc.cie.sbc[0], a2d->audio_cfg.mcc.cie.sbc[1], a2d->audio_cfg.mcc.cie.sbc[2], a2d->audio_cfg.mcc.cie.sbc[3]); - ESP_LOGI(BT_AV_TAG, "Audio player configured, sample rate=%d", sample_rate); + ESP_LOGI(BT_AV_TAG, "Audio player configured, sample rate=%d", s_sample_rate); } break; } @@ -347,12 +374,26 @@ void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_param { switch (event_id) { case ESP_AVRC_RN_TRACK_CHANGE: + ESP_LOGI(BT_AV_TAG, "Track changed"); bt_av_new_track(); (*bt_app_a2d_cmd_cb)(BT_SINK_PROGRESS, 0, 0); break; case ESP_AVRC_RN_PLAY_STATUS_CHANGE: ESP_LOGI(BT_AV_TAG, "Playback status changed: 0x%x", event_parameter->playback); - s_play_status = event_parameter->playback; + // re-synchronize events + s_playing = (event_parameter->playback == ESP_AVRC_PLAYBACK_PLAYING); + if (event_parameter->playback == ESP_AVRC_PLAYBACK_PLAYING && s_audio != AUDIO_IDLE) { + // if decoder is busy then stop (would be better to not ACK this command, but don't know how) + if (s_audio == AUDIO_CONNECTED || !(*bt_app_a2d_cmd_cb)(BT_SINK_PLAY)) { + esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED); + } else { + update_metadata(false); + } + } else if (event_parameter->playback == ESP_AVRC_PLAYBACK_PAUSED) (*bt_app_a2d_cmd_cb)(BT_SINK_PAUSE); + else if (event_parameter->playback == ESP_AVRC_PLAYBACK_STOPPED) { + (*bt_app_a2d_cmd_cb)(BT_SINK_PROGRESS, 0, -1); + (*bt_app_a2d_cmd_cb)(BT_SINK_STOP); + } bt_av_playback_changed(); break; case ESP_AVRC_RN_PLAY_POS_CHANGED: @@ -387,25 +428,13 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param) break; } case ESP_AVRC_CT_METADATA_RSP_EVT: { - char **p = NULL; ESP_LOGI(BT_RC_CT_TAG, "AVRC metadata rsp: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text); - if (rc->meta_rsp.attr_id == ESP_AVRC_MD_ATTR_TITLE) p = &s_title; - else if (rc->meta_rsp.attr_id == ESP_AVRC_MD_ATTR_ARTIST) p = &s_artist; - else if (rc->meta_rsp.attr_id == ESP_AVRC_MD_ATTR_ALBUM) p = &s_album; - else if (rc->meta_rsp.attr_id == ESP_AVRC_MD_ATTR_PLAYING_TIME) (*bt_app_a2d_cmd_cb)(BT_SINK_PROGRESS, -1, atoi((char*) rc->meta_rsp.attr_text)); - - // not very pretty, but this is bluetooth anyway - if (p) { - if (*p) free(*p); - *p = strdup((char*) rc->meta_rsp.attr_text); - - if (s_artist && s_album && s_title) { - (*bt_app_a2d_cmd_cb)(BT_SINK_METADATA, s_artist, s_album, s_title); - free(s_artist); free(s_album); free(s_title); - s_artist = s_album = s_title = NULL; - } - } + if (rc->meta_rsp.attr_id == ESP_AVRC_MD_ATTR_PLAYING_TIME) s_metadata.duration = atoi((char*) rc->meta_rsp.attr_text); + else if (rc->meta_rsp.attr_id == ESP_AVRC_MD_ATTR_TITLE) strncpy(s_metadata.title, (char*) rc->meta_rsp.attr_text, METADATA_LEN); + else if (rc->meta_rsp.attr_id == ESP_AVRC_MD_ATTR_ARTIST) strncpy(s_metadata.artist, (char*) rc->meta_rsp.attr_text, METADATA_LEN); + else if (rc->meta_rsp.attr_id == ESP_AVRC_MD_ATTR_ALBUM) strncpy(s_metadata.album, (char*) rc->meta_rsp.attr_text, METADATA_LEN); + update_metadata(true); free(rc->meta_rsp.attr_text); break; diff --git a/components/driver_bt/bt_app_sink.h b/components/driver_bt/bt_app_sink.h index 69ac64e0..6fc7b962 100644 --- a/components/driver_bt/bt_app_sink.h +++ b/components/driver_bt/bt_app_sink.h @@ -11,7 +11,7 @@ #include -typedef enum { BT_SINK_CONNECTED, BT_SINK_DISCONNECTED, BT_SINK_PLAY, BT_SINK_STOP, BT_SINK_PAUSE, +typedef enum { BT_SINK_CONNECTED, BT_SINK_DISCONNECTED, BT_SINK_AUDIO_STARTED, BT_SINK_AUDIO_STOPPED, BT_SINK_PLAY, BT_SINK_STOP, BT_SINK_PAUSE, BT_SINK_RATE, BT_SINK_VOLUME, BT_SINK_METADATA, BT_SINK_PROGRESS } bt_sink_cmd_t; typedef bool (*bt_cmd_vcb_t)(bt_sink_cmd_t cmd, va_list args); @@ -27,11 +27,6 @@ void bt_sink_init(bt_cmd_vcb_t cmd_cb, bt_data_cb_t data_cb); */ void bt_sink_deinit(void); -/** - * @brief * @brief do what's necessary when becoming in charge - */ -void bt_master(bool on); - /** * @brief force disconnection */ diff --git a/components/raop/raop.c b/components/raop/raop.c index 9cb892c6..1838af8a 100644 --- a/components/raop/raop.c +++ b/components/raop/raop.c @@ -661,8 +661,12 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock) kd_add(resp, "Audio-Jack-Status", "connected; type=analog"); kd_add(resp, "CSeq", kd_lookup(headers, "CSeq")); - if (success) buf = http_send(sock, "RTSP/1.0 200 OK", resp); - else buf = http_send(sock, "RTSP/1.0 500 ERROR", NULL); + if (success) { + buf = http_send(sock, "RTSP/1.0 200 OK", resp); + } else { + buf = http_send(sock, "RTSP/1.0 503 ERROR", NULL); + closesocket(sock); + } if (strcmp(method, "OPTIONS")) { LOG_INFO("[%p]: responding:\n%s", ctx, buf ? buf : ""); diff --git a/components/raop/raop_sink.c b/components/raop/raop_sink.c index 06e5cf22..2743b275 100644 --- a/components/raop/raop_sink.c +++ b/components/raop/raop_sink.c @@ -83,53 +83,49 @@ const static actrls_t controls = { * Command handler */ static bool cmd_handler(raop_event_t event, ...) { - bool chain = true, res = true; va_list args; va_start(args, event); + // handle audio event and stop if forbidden + if (!cmd_handler_chain(event, args)) { + va_end(args); + return false; + } + + // now handle events for display switch(event) { case RAOP_SETUP: + actrls_set(controls, NULL); displayer_control(DISPLAYER_ACTIVATE, "AIRPLAY"); break; case RAOP_PLAY: - displayer_control(DISPLAYER_TIMER_RESUME); + displayer_control(DISPLAYER_TIMER_RUN); break; case RAOP_FLUSH: displayer_control(DISPLAYER_TIMER_PAUSE); break; case RAOP_STOP: - displayer_control(DISPLAYER_DISABLE); + actrls_unset(); + displayer_control(DISPLAYER_SUSPEND); break; case RAOP_METADATA: { char *artist = va_arg(args, char*), *album = va_arg(args, char*), *title = va_arg(args, char*); displayer_metadata(artist, album, title); - chain = false; break; } case RAOP_PROGRESS: { int elapsed = va_arg(args, int), duration = va_arg(args, int); displayer_timer(DISPLAYER_ELAPSED, elapsed, duration); - chain = false; break; } default: break; } - if (chain) res = cmd_handler_chain(event, args); - va_end(args); - return res; -} - -/**************************************************************************************** - * Airplay taking/giving audio system's control - */ -void raop_master(bool on) { - if (on) actrls_set(controls, NULL); - else actrls_unset(); + return true; } /**************************************************************************************** diff --git a/components/raop/raop_sink.h b/components/raop/raop_sink.h index 825cbb27..a3cb87d4 100644 --- a/components/raop/raop_sink.h +++ b/components/raop/raop_sink.h @@ -32,11 +32,6 @@ void raop_sink_init(raop_cmd_vcb_t cmd_cb, raop_data_cb_t data_cb); */ void raop_sink_deinit(void); -/** - * @brief do what's necessary when becoming in charge - */ -void raop_master(bool on); - /** * @brief force disconnection */ diff --git a/components/services/display.c b/components/services/display.c index b90e70e4..6b6ab350 100644 --- a/components/services/display.c +++ b/components/services/display.c @@ -34,23 +34,23 @@ struct display_s *display; static const char *TAG = "display"; #define min(a,b) (((a) < (b)) ? (a) : (b)) +#define max(a,b) (((a) > (b)) ? (a) : (b)) #define DISPLAYER_STACK_SIZE 2048 #define SCROLLABLE_SIZE 384 #define HEADER_SIZE 64 #define DEFAULT_SLEEP 3600 - static EXT_RAM_ATTR struct { TaskHandle_t task; SemaphoreHandle_t mutex; int pause, speed, by; - enum { DISPLAYER_DISABLED, DISPLAYER_AUTO_DISABLE, DISPLAYER_ACTIVE } state; + enum { DISPLAYER_DOWN, DISPLAYER_IDLE, DISPLAYER_ACTIVE } state; char header[HEADER_SIZE + 1]; char string[SCROLLABLE_SIZE + 1]; int offset, boundary; char *metadata_config; - bool timer; + bool timer, refresh; uint32_t elapsed, duration; TickType_t tick; } displayer; @@ -109,56 +109,58 @@ void display_init(char *welcome) { */ static void displayer_task(void *args) { int scroll_sleep = 0, timer_sleep; - + while (1) { // suspend ourselves if nothing to do - if (displayer.state == DISPLAYER_DISABLED) { + if (displayer.state < DISPLAYER_ACTIVE) { + if (displayer.state == DISPLAYER_IDLE) display->line(2, 0, DISPLAY_CLEAR | DISPLAY_UPDATE, displayer.string); vTaskSuspend(NULL); + scroll_sleep = 0; display->clear(); display->line(1, DISPLAY_LEFT, DISPLAY_UPDATE, displayer.header); - scroll_sleep = 0; - } + } else if (displayer.refresh) { + // little trick when switching master while in IDLE and missing it + display->line(1, DISPLAY_LEFT, DISPLAY_CLEAR | DISPLAY_UPDATE, displayer.header); + displayer.refresh = false; + } // we have been waken up before our requested time if (scroll_sleep <= 10) { // something to scroll (or we'll wake-up every pause ms ... no big deal) if (*displayer.string && displayer.state == DISPLAYER_ACTIVE) { display->line(2, -displayer.offset, DISPLAY_CLEAR | DISPLAY_UPDATE, displayer.string); - xSemaphoreTake(displayer.mutex, portMAX_DELAY); scroll_sleep = displayer.offset ? displayer.speed : displayer.pause; displayer.offset = displayer.offset >= displayer.boundary ? 0 : (displayer.offset + min(displayer.by, displayer.boundary - displayer.offset)); xSemaphoreGive(displayer.mutex); - } else if (displayer.state == DISPLAYER_AUTO_DISABLE) { - display->line(2, DISPLAY_LEFT, DISPLAY_CLEAR | DISPLAY_UPDATE, displayer.string); - xSemaphoreTake(displayer.mutex, portMAX_DELAY); - displayer.state = DISPLAYER_DISABLED; - xSemaphoreGive(displayer.mutex); - } else scroll_sleep = DEFAULT_SLEEP; + } else { + scroll_sleep = DEFAULT_SLEEP; + } } - // handler timer + // handler elapsed track time if (displayer.timer && displayer.state == DISPLAYER_ACTIVE) { char counter[12]; TickType_t tick = xTaskGetTickCount(); uint32_t elapsed = (tick - displayer.tick) * portTICK_PERIOD_MS; if (elapsed >= 1000) { + xSemaphoreTake(displayer.mutex, portMAX_DELAY); displayer.tick = tick; displayer.elapsed += elapsed / 1000; + xSemaphoreGive(displayer.mutex); if (displayer.elapsed < 3600) sprintf(counter, "%2u:%02u", displayer.elapsed / 60, displayer.elapsed % 60); else sprintf(counter, "%2u:%2u:%02u", displayer.elapsed / 3600, (displayer.elapsed % 3600) / 60, displayer.elapsed % 60); display->line(1, DISPLAY_RIGHT, (DISPLAY_CLEAR | DISPLAY_ONLY_EOL) | DISPLAY_UPDATE, counter); timer_sleep = 1000; - } else timer_sleep = 1000 - elapsed; + } else timer_sleep = max(1000 - elapsed, 0); } else timer_sleep = DEFAULT_SLEEP; - // don't bother sleeping if we are disactivated - if (displayer.state == DISPLAYER_ACTIVE) { - int sleep = min(scroll_sleep, timer_sleep); - scroll_sleep -= sleep; - vTaskDelay( sleep / portTICK_PERIOD_MS); - } + // then sleep the min amount of time + int sleep = min(scroll_sleep, timer_sleep); + ESP_LOGD(TAG, "timers s:%d t:%d", scroll_sleep, timer_sleep); + scroll_sleep -= sleep; + vTaskDelay(sleep / portTICK_PERIOD_MS); } } @@ -203,9 +205,9 @@ void displayer_metadata(char *artist, char *album, char *title) { } // then copy token's content - if (strcasestr(q, "artist") && artist) strncat(string, p = artist, space); - else if (strcasestr(q, "album") && album) strncat(string, p = album, space); - else if (strcasestr(q, "title") && title) strncat(string, p = title, space); + if (!strncasecmp(q + 1, "artist", 6) && artist) strncat(string, p = artist, space); + else if (!strncasecmp(q + 1, "album", 5) && album) strncat(string, p = album, space); + else if (!strncasecmp(q + 1, "title", 5) && title) strncat(string, p = title, space); space = len - strlen(string); // flag to skip the data following an empty field @@ -224,6 +226,7 @@ void displayer_metadata(char *artist, char *album, char *title) { displayer.offset = 0; utf8_decode(displayer.string); + ESP_LOGI(TAG, "playing %s", displayer.string); displayer.boundary = display->stretch(2, displayer.string, SCROLLABLE_SIZE); xSemaphoreGive(displayer.mutex); @@ -252,9 +255,8 @@ void displayer_timer(enum displayer_time_e mode, int elapsed, int duration) { xSemaphoreTake(displayer.mutex, portMAX_DELAY); if (elapsed >= 0) displayer.elapsed = elapsed; - if (duration >= 0) displayer.duration = duration; - displayer.timer = true; - displayer.tick = xTaskGetTickCount(); + if (duration >= 0) displayer.duration = duration; + if (displayer.timer) displayer.tick = xTaskGetTickCount(); xSemaphoreGive(displayer.mutex); } @@ -267,10 +269,7 @@ void displayer_control(enum displayer_cmd_e cmd, ...) { va_start(args, cmd); xSemaphoreTake(displayer.mutex, portMAX_DELAY); - - displayer.offset = 0; - displayer.boundary = 0; - + switch(cmd) { case DISPLAYER_ACTIVATE: { char *header = va_arg(args, char*); @@ -278,18 +277,22 @@ void displayer_control(enum displayer_cmd_e cmd, ...) { displayer.header[HEADER_SIZE] = '\0'; displayer.state = DISPLAYER_ACTIVE; displayer.timer = false; + displayer.refresh = true; displayer.string[0] = '\0'; + displayer.elapsed = displayer.duration = 0; + displayer.offset = displayer.boundary = 0; vTaskResume(displayer.task); break; } - case DISPLAYER_DISABLE: - displayer.state = DISPLAYER_AUTO_DISABLE; + case DISPLAYER_SUSPEND: + // task will display the line 2 from beginning and suspend + displayer.state = DISPLAYER_IDLE; break; case DISPLAYER_SHUTDOWN: - displayer.state = DISPLAYER_DISABLED; - vTaskSuspend(displayer.task); + // let the task self-suspend (we might be doing i2c_write) + displayer.state = DISPLAYER_DOWN; break; - case DISPLAYER_TIMER_RESUME: + case DISPLAYER_TIMER_RUN: if (!displayer.timer) { displayer.timer = true; displayer.tick = xTaskGetTickCount(); diff --git a/components/services/display.h b/components/services/display.h index e00122e1..c8753254 100644 --- a/components/services/display.h +++ b/components/services/display.h @@ -18,6 +18,18 @@ #pragma once +/* + The displayer is not thread-safe and the caller must ensure use its own + mutexes if it wants something better. Especially, text() line() and draw() + are not protected against each other. + In text mode (text/line) when using DISPLAY_SUSPEND, the displayer will + refreshed line 2 one last time before suspending itself. As a result if it + is in a long sleep (scrolling pause), the refresh will happen after wakeup. + So it can conflict with other display direct writes that have been made during + sleep. Note that if DISPLAY_SHUTDOWN has been called meanwhile, it (almost) + never happens +*/ + #define DISPLAY_CLEAR 0x01 #define DISPLAY_ONLY_EOL 0x02 #define DISPLAY_UPDATE 0x04 @@ -33,7 +45,7 @@ enum display_font_e { DISPLAY_FONT_DEFAULT, DISPLAY_FONT_LINE_1, DISPLAY_FONT_LINE_2, DISPLAY_FONT_SEGMENT, DISPLAY_FONT_TINY, DISPLAY_FONT_SMALL, DISPLAY_FONT_MEDIUM, DISPLAY_FONT_LARGE, DISPLAY_FONT_HUGE }; -enum displayer_cmd_e { DISPLAYER_SHUTDOWN, DISPLAYER_ACTIVATE, DISPLAYER_DISABLE, DISPLAYER_TIMER_PAUSE, DISPLAYER_TIMER_RESUME }; +enum displayer_cmd_e { DISPLAYER_SHUTDOWN, DISPLAYER_ACTIVATE, DISPLAYER_SUSPEND, DISPLAYER_TIMER_PAUSE, DISPLAYER_TIMER_RUN }; enum displayer_time_e { DISPLAYER_ELAPSED, DISPLAYER_REMAINING }; // don't change anything there w/o changing all drivers init code diff --git a/components/squeezelite/decode_external.c b/components/squeezelite/decode_external.c index 37f75329..a79d4d16 100644 --- a/components/squeezelite/decode_external.c +++ b/components/squeezelite/decode_external.c @@ -113,19 +113,19 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args) if (cmd != BT_SINK_VOLUME) LOCK_O; switch(cmd) { - case BT_SINK_CONNECTED: + case BT_SINK_AUDIO_STARTED: + output.next_sample_rate = output.current_sample_rate = va_arg(args, u32_t); output.external = DECODE_BT; output.state = OUTPUT_STOPPED; output.frames_played = 0; _buf_flush(outputbuf); if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR; - bt_master(true); LOG_INFO("BT sink started"); break; - case BT_SINK_DISCONNECTED: + case BT_SINK_AUDIO_STOPPED: + // do we still need that? if (output.external == DECODE_BT) { output.state = OUTPUT_OFF; - bt_master(false); LOG_INFO("BT sink stopped"); } break; @@ -135,10 +135,12 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args) break; case BT_SINK_STOP: _buf_flush(outputbuf); - case BT_SINK_PAUSE: output.state = OUTPUT_STOPPED; LOG_INFO("BT sink stopped"); break; + case BT_SINK_PAUSE: + LOG_INFO("BT sink paused, just silence"); + break; case BT_SINK_RATE: output.next_sample_rate = output.current_sample_rate = va_arg(args, u32_t); LOG_INFO("Setting BT sample rate %u", output.next_sample_rate); @@ -239,7 +241,6 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args) output.external = DECODE_RAOP; output.state = OUTPUT_STOPPED; if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR; - raop_master(true); LOG_INFO("resizing buffer %u", outputbuf->size); break; case RAOP_STREAM: @@ -256,7 +257,6 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args) output.state = OUTPUT_OFF; output.frames_played = 0; raop_state = event; - raop_master(false); break; case RAOP_FLUSH: LOG_INFO("Flush", NULL);