This patch adds handling send and response of AT command. Note that we always wait for AT command response before sending next command, however user can fill hfp_hf with more than one command. All the commands are queued and send one by one. --- src/shared/hfp.c | 175 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/shared/hfp.h | 5 ++ 2 files changed, 180 insertions(+) diff --git a/src/shared/hfp.c b/src/shared/hfp.c index d61d76d..1833461 100644 --- a/src/shared/hfp.c +++ b/src/shared/hfp.c @@ -70,6 +70,10 @@ struct hfp_hf { struct ringbuf *read_buf; struct ringbuf *write_buf; + bool writer_active; + struct queue *cmd_queue; + bool command_in_progress; + struct queue *event_handlers; hfp_debug_func_t debug_callback; @@ -101,6 +105,14 @@ struct hfp_hf_result { unsigned int offset; }; +struct cmd_response { + char *prefix; + hfp_response_func_t resp_cb; + struct hfp_hf_result *response; + char *resp_data; + void *user_data; +}; + struct event_handler { char *prefix; void *user_data; @@ -868,12 +880,64 @@ static void destroy_event_handler(void *data) free(handler); } +static bool hf_can_write_data(struct io *io, void *user_data) +{ + struct hfp_hf *hfp = user_data; + ssize_t bytes_written; + + bytes_written = ringbuf_write(hfp->write_buf, hfp->fd); + if (bytes_written < 0) + return false; + + if (ringbuf_len(hfp->write_buf) > 0) + return true; + + return false; +} + +static void hf_write_watch_destroy(void *user_data) +{ + struct hfp_hf *hfp = user_data; + + hfp->writer_active = false; +} + static void hf_skip_whitespace(struct hfp_hf_result *result) { while (result->data[result->offset] == ' ') result->offset++; } +static bool is_response(const char *msg, enum hfp_result *result) +{ + if (strcmp(msg, "OK") == 0) { + *result = HFP_RESULT_OK; + return true; + } + + if (strcmp(msg, "ERROR") == 0) { + *result = HFP_RESULT_ERROR; + return true; + } + + return false; +} + +static void hf_wakeup_writer(struct hfp_hf *hfp) +{ + if (hfp->writer_active) + return; + + if (!ringbuf_len(hfp->write_buf)) + return; + + if (!io_set_write_handler(hfp->io, hf_can_write_data, + hfp, hf_write_watch_destroy)) + return; + + hfp->writer_active = true; +} + static void hf_call_prefix_handler(struct hfp_hf *hfp, const char *data) { struct event_handler *handler; @@ -904,6 +968,52 @@ static void hf_call_prefix_handler(struct hfp_hf *hfp, const char *data) lookup_prefix[pref_len] = '\0'; result_data.offset += pref_len + 1; + if (hfp->command_in_progress) { + struct cmd_response *cmd; + enum hfp_result result; + + cmd = queue_peek_head(hfp->cmd_queue); + if (!cmd) + return; + + if (is_response(lookup_prefix, &result)) { + cmd->resp_cb(result, cmd->response, cmd->user_data); + + if (cmd->resp_data) + free(cmd->resp_data); + + if (cmd->response) + free(cmd->response); + + queue_remove(hfp->cmd_queue, cmd); + free(cmd); + + if (!queue_isempty(hfp->cmd_queue)) { + hf_wakeup_writer(hfp); + return; + } + + hfp->command_in_progress = false; + + return; + } + /* + * Check if unsolicited result is the response for ongoing + * command. If not we try to find registered handler for it + * later. + */ + if (strcmp(lookup_prefix, &cmd->prefix[2]) == 0 && + !cmd->resp_data) { + /* Store response and wait for OK */ + cmd->resp_data = strdup(result_data.data); + + cmd->response = new0(struct hfp_hf_result, 1); + cmd->response->offset = result_data.offset; + cmd->response->data = cmd->resp_data; + return; + } + } + handler = queue_find(hfp->event_handlers, match_handler_event_prefix, lookup_prefix); @@ -1033,6 +1143,19 @@ struct hfp_hf *hfp_hf_new(int fd) return NULL; } + hfp->cmd_queue = queue_new(); + if (!hfp->cmd_queue) { + io_destroy(hfp->io); + ringbuf_free(hfp->write_buf); + ringbuf_free(hfp->read_buf); + queue_destroy(hfp->event_handlers, NULL); + free(hfp); + return NULL; + } + + hfp->writer_active = false; + hfp->command_in_progress = false; + if (!io_set_read_handler(hfp->io, hf_can_read_data, hfp, read_watch_destroy)) { queue_destroy(hfp->event_handlers, @@ -1143,6 +1266,58 @@ bool hfp_hf_set_close_on_unref(struct hfp_hf *hfp, bool do_close) return true; } +bool hfp_hf_send_command(struct hfp_hf *hfp, hfp_response_func_t resp_cb, + void *user_data, const char *format, ...) +{ + va_list ap; + char *fmt; + int len; + const char *separators = ";?=\0"; + uint8_t prefix_len; + struct cmd_response *cmd; + + if (!hfp || !format || !resp_cb) + return false; + + if (asprintf(&fmt, "%s\r", format) < 0) + return false; + + cmd = new0(struct cmd_response, 1); + if (!cmd) + return false; + + va_start(ap, format); + len = ringbuf_vprintf(hfp->write_buf, fmt, ap); + va_end(ap); + + free(fmt); + + if (len < 0) { + free(cmd); + return false; + } + + prefix_len = strcspn(format, separators); + cmd->prefix = strndup(format, prefix_len); + cmd->resp_cb = resp_cb; + cmd->user_data = user_data; + + if (!queue_push_tail(hfp->cmd_queue, cmd)) { + ringbuf_drain(hfp->write_buf, len); + free(cmd); + return false; + } + + if (hfp->command_in_progress) + return true; + + hfp->command_in_progress = true; + + hf_wakeup_writer(hfp); + + return true; +} + bool hfp_hf_register(struct hfp_hf *hfp, hfp_hf_result_func_t callback, const char *prefix, void *user_data, diff --git a/src/shared/hfp.h b/src/shared/hfp.h index 85037b1..773d827 100644 --- a/src/shared/hfp.h +++ b/src/shared/hfp.h @@ -83,6 +83,9 @@ typedef void (*hfp_command_func_t)(const char *command, void *user_data); typedef void (*hfp_disconnect_func_t)(void *user_data); +typedef void (*hfp_response_func_t)(enum hfp_result result, + struct hfp_hf_result *resp, + void *user_data); struct hfp_gw; struct hfp_hf; @@ -146,3 +149,5 @@ bool hfp_hf_register(struct hfp_hf *hfp, hfp_hf_result_func_t callback, const char *prefix, void *user_data, hfp_destroy_func_t destroy); bool hfp_hf_unregister(struct hfp_hf *hfp, const char *prefix); +bool hfp_hf_send_command(struct hfp_hf *hfp, hfp_response_func_t resp_cb, + void *user_data, const char *format, ...); -- 1.8.4 -- To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html