diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1dc66b2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,79 @@ +## ChangeLog + +### v1.0b1r3 (Beta) - 2018-08-28 + +**Enhancements** + +* Seeking support for Audible books +* Support for OffsetInMilliSeconds, ProgressReportDelayElapsed and ProgressReportIntervalElapsed +* LED patterns for various Alexa states as per Alexa UX design guidelines +* Playing Alerts/Timers, Reminders from received URL even after device reboot. Device used to play stored tunes if device rebooted in between setting the Alarm and its timeout +* Minute latency improvement where Recognize event is now being sent earlier upon WW detection + +**Bug Fixes** + +* No intermittent audio playback between multiple Speak directives of the same dialog (e.g "What's up") +* Few stability related fixes + +**Known Issues/Improvements** + +* Crashes are seen in Audible when segments >512 are received +* Only limited TuneIn radio stations are supported +* It is largely tested with internet and WiFi connectivity intact throughout its operation. Some issues are seen when device loses connectivity. +* TLS Certificate validation of Amazon and other streaming sites is yet to be done. + +### v1.0b1r2 (Beta) - 2018-08-14 + +**Enhancements** + +* Support for persistent Alerts/Timers, Reminders and Notifications + +**Bug Fixes** + +* Multiple race conditions fixed to improve the stability + +**Known Issues** + +* Regression in Audible support - crashes are seen with Audible +* Book and audio playback seeking is not yet supported +* Only limited TuneIn radio stations are supported +* It is largely tested with internet and WiFi connectivity intact throughout its operation. Some issues are seen when device loses connectivity. + +### v1.0b1r1 (Beta) - 2018-08-06 + +**Enhancements** + +* Amazon music support +* Alerts/Timers, Reminders, Notifications support +* Audible support +* Capabilities API integration +* AudioActivityTracker support + +**Bug Fixes** +* Overall stability improvements +* Fixed minor issues with Android App +* Amazon app is no more required on phone for authentication. App opens login page on device's browser if app isn't found. + +**Known Issues** + +* Alerts/Timers, Reminders and Notifications do not persist across device reboot +* Book and audio playback seeking is not yet supported +* Only limited TuneIn radio stations are supported +* It is largely tested with internet and WiFi connectivity intact throughout its operation. Some issues are seen when device loses connectivity. + +### v1.0a1r1 (Alpha) - 2018-07-09 + +* Complete C-Based SDK from ground-up with support for + * Basic conversation + * Multi-turn + * Audio playback - Saavn, TuneIn, Kindle + * Volume control (both physical and spoken) + * Tap-To-Talk +* Salvaged 75KB of internal memory as compared to CPP based port +* Phone app (Android) support for: + * Network configuration + * Alexa authentication + +### v0.15 + +* Port of Amazon's CPP SDK (v1.3) with additional optimizations for embedded target diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0c214df --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ + +ESPRESSIF MIT License + +Copyright (c) 2018 + +Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case, +it is free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..5273a91 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +## Overview + +The ESP-Alexa SDK provides an implementation of Amazon's Alexa Voice Service endpoint for ESP32 microcontroller. This facilitates the developers to evaluate ESP32 based Alexa integrated devices like speakers and IoT devices. Please refer to [Changelog](CHANGELOG.md) to track release changes and known-issues. + +### About SDK + +The SDK contains pre-built library of Alexa SDK along with sources of some of the utility components such as audio pipeline and connection manager. The SDK supports all major features of Alexa such as: +* Basic Alexa conversation +* Alexa dialogues and multi-turn +* Audio Streaming and Playback: Saavn, Amazon music, TuneIn (Only limited stations are supported as of now) +* Audio Book Support: Kindle, Audible +* Volume control via Alexa command +* Seek support for Audible +* Alerts/Timers, Reminders, Notifications + +For now, Tap-To-Talk is the only interaction mode supported on LyraT. + +## Supported Hardware + +Release supports following hardware platforms: +* [ESP32-LyraT](https://www.espressif.com/en/products/hardware/esp32-lyrat) + +The SDK can easily be extended to other ESP32 based audio platforms that have SPIRAM availability. + +## Getting started + +* When flashing the SDK for the first time, it is recommended to do `make erase_flash` to wipe out entire flash and start out fresh. +* Please refer to [LyraT README](examples/lyrat_alexa/README.md) to get started with flashing, provisioning and Alexa interactions. diff --git a/components/audio_board/audio_board.h b/components/audio_board/audio_board.h new file mode 100644 index 0000000..46c5ea9 --- /dev/null +++ b/components/audio_board/audio_board.h @@ -0,0 +1,74 @@ +/* + * ESPRESSIF MIT License + * + * Copyright (c) 2018 + * + * Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case, + * it is free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#ifndef __AUDIO_BOARD_H__ +#define __AUDIO_BOARD_H__ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * @brief returns i2s gpio config struct + * + * @param port_num i2s port number + * @param pf_i2s_pin i2s gpio init struct + * + */ +esp_err_t audio_board_i2s_pin_config(int port_num, i2s_pin_config_t *pf_i2s_pin); + +/* + * @brief returns i2c config struct with gpio pins intialization + * + *@param port_num i2c port number + *@param pf_i2c_pin i2c gpio init struct + * + */ +esp_err_t audio_board_i2c_pin_config(int port_num, i2c_config_t *pf_i2c_pin); + +/* + * @brief returns i2s default parameters init + * + *@param i2s_cfg_dft i2s param config structure + * + */ +esp_err_t audio_board_i2s_init_default(i2s_config_t *i2s_cfg_dft); + +/* + * @brief gpio initialization for button + * + *@param adc1_channel_t adc gpio selection from struct + * + */ +esp_err_t audio_board_button_config(adc1_channel_t *pf_button_pin); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/components/audio_board/component.mk b/components/audio_board/component.mk new file mode 100644 index 0000000..45432b7 --- /dev/null +++ b/components/audio_board/component.mk @@ -0,0 +1,3 @@ + +COMPONENT_ADD_INCLUDEDIRS := . + diff --git a/components/audio_pipeline/audio_pipeline.c b/components/audio_pipeline/audio_pipeline.c new file mode 100644 index 0000000..98b3ce6 --- /dev/null +++ b/components/audio_pipeline/audio_pipeline.c @@ -0,0 +1,626 @@ +/* + * ESPRESSIF MIT License + * + * Copyright (c) 2018 + * + * Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case, + * it is free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include +#include +#include + +#define ap_d(...) \ + ESP_LOGI("AudioPipeline", ##__VA_ARGS__) + +#define ap_e(...) \ + ESP_LOGE("AudioPipeline", ##__VA_ARGS__) + +static esp_err_t audio_pipe_event_cb(void *arg, int event, void *data) +{ + audio_pipe_t *p = (audio_pipe_t *) arg; + audio_stream_t *stream = NULL; + + if (event >= CODEC_EVENT_START_NUM) { + ap_d("Codec event %d", event); + } else { + stream = (audio_stream_t *) data; + assert(stream); + + if (stream->type == STREAM_TYPE_READER) { + ap_d("Reader stream event %d", event); + } else { + ap_d("Writer stream event %d", event); + } + } + + lock(p->lock); + p->old_state = p->state; + switch (event) { + case STREAM_EVENT_STARTED: + p->state = AUDIO_PIPE_STARTED; + break; + case STREAM_EVENT_STOPPED: + p->state = AUDIO_PIPE_STOPPED; + break; + case STREAM_EVENT_PAUSED: + p->state = AUDIO_PIPE_PAUSED; + break; + case CODEC_EVENT_SET_FREQ: { + unlock(p->lock); + if (p->event_func.func) { + p->event_func.func(p->event_func.arg, AUDIO_PIPE_CHANGE_FREQ, data); + } + lock(p->lock); + break; + } + case CODEC_EVENT_STARTED: + p->state = AUDIO_PIPE_STARTED; + break; + case CODEC_EVENT_STOPPED: + p->state = AUDIO_PIPE_STOPPED; + break; + case CODEC_EVENT_PAUSED: + p->state = AUDIO_PIPE_PAUSED; + break; + default: + break; + } + unlock(p->lock); + + // Send to event to application only if pipeline state changes + if ((p->old_state != p->state) && p->event_func.func) { + p->event_func.func(p->event_func.arg, (int) p->state, data); + } + + return ESP_OK; +} + +static audio_pipe_t *audio_pipe_alloc(const char *name) +{ + if (!name) { + return NULL; + } + + audio_pipe_t *p = calloc(1, sizeof(audio_pipe_t)); + assert(p); + + STAILQ_INIT(&p->pb); + + p->lock = xSemaphoreCreateMutex(); + assert(p->lock); + p->state = AUDIO_PIPE_INITED; + p->name = name; + return p; +} + +static void _create_insert_block(audio_pipe_t *p, void *cfg, block_type_t type, ringbuf_t *rb, size_t rb_size, bool head) +{ + audio_pipe_block_t *b = calloc(1, sizeof(audio_pipe_block_t)); + assert(b); + + b->block_cfg = cfg; + b->btype = type; + b->rb = rb; + b->rb_size = rb_size; + + lock(p->lock); + if (head) { + STAILQ_INSERT_HEAD(&p->pb, b, next); + } else { + STAILQ_INSERT_TAIL(&p->pb, b, next); + } + p->cnt++; + unlock(p->lock); + + return; +} + +static esp_err_t audio_pipe_replace_stream(audio_pipe_t *p, audio_pipe_block_t *b, audio_stream_t *new_stream) +{ + audio_stream_t *old_stream = (audio_stream_t *) b->block_cfg; + if (old_stream == new_stream) { + return ESP_OK; + } + + audio_io_fn_arg_t stream_io; + // Copy old stream configuration + if (old_stream->type == STREAM_TYPE_READER) { + memcpy(&stream_io, &old_stream->op.stream_output, sizeof(audio_io_fn_arg_t)); + } else { + memcpy(&stream_io, &old_stream->op.stream_input, sizeof(audio_io_fn_arg_t)); + } + + // Initialize new stream + if (audio_stream_init((audio_stream_t *) new_stream, "rstream", &stream_io, &old_stream->event_func) != ESP_OK) { + ap_d("Error initializing audio stream"); + unlock(p->lock); + return ESP_FAIL; + } + // Destroy old stream + audio_stream_destroy(old_stream); + + lock(p->lock); + b->block_cfg = new_stream; + unlock(p->lock); + + return ESP_OK; +} + +static esp_err_t audio_pipe_replace_codec(audio_pipe_t *p, audio_pipe_block_t *b, audio_codec_t *new_codec) +{ + audio_codec_t *old_codec = (audio_codec_t *) b->block_cfg; + if (old_codec == new_codec) { + return ESP_OK; + } + + // Initialize new codec with old configuration + if (audio_codec_init((audio_codec_t *) new_codec, "rcodec", &old_codec->codec_input, &old_codec->codec_output, + &old_codec->event_func) != ESP_OK) { + ap_d("Error initializing audio codec"); + unlock(p->lock); + return ESP_FAIL; + } + // Destroy old codec + audio_codec_destroy(old_codec); + lock(p->lock); + b->block_cfg = new_codec; + unlock(p->lock); + + return ESP_OK; +} + +static int _audio_pipe_stop(audio_pipe_t *p) +{ + audio_pipe_block_t *b; + + STAILQ_FOREACH(b, &p->pb, next) { + // Only stop first block in pipeline, stop gets propagated across pipeline + if (b->btype == STREAM_BLOCK) { + audio_stream_stop(b->block_cfg); + break; + } else if (b->btype == CODEC_BLOCK) { + audio_codec_stop(b->block_cfg); + break; + } + } + return ESP_OK; +} + +static int _audio_pipe_start(audio_pipe_t *p) +{ + audio_pipe_block_t *b; + + STAILQ_FOREACH(b, &p->pb, next) { + if (b->rb) { + rb_reset(b->rb); + } + if (b->btype == STREAM_BLOCK) { + audio_stream_start(b->block_cfg); + } else if (b->btype == CODEC_BLOCK) { + audio_codec_start(b->block_cfg); + } + } + return ESP_OK; +} + +static int _audio_pipe_pause(audio_pipe_t *p) +{ + audio_pipe_block_t *b; + + b = STAILQ_LAST(&p->pb, audio_pipe_block, next); + if (b) { + audio_stream_pause(b->block_cfg); + return ESP_OK; + } + return ESP_FAIL; +} + +static int _audio_pipe_resume(audio_pipe_t *p) +{ + audio_pipe_block_t *b; + + b = STAILQ_LAST(&p->pb, audio_pipe_block, next); + if (b) { + audio_stream_resume(b->block_cfg); + return ESP_OK; + } + return ESP_FAIL; +} + +static esp_err_t audio_state_machine(audio_pipe_t *p, audio_pipe_state_t next_state) +{ + int ret = ESP_OK; + + switch (p->state) { + case AUDIO_PIPE_INITED: + case AUDIO_PIPE_STOPPED: + if (next_state == AUDIO_PIPE_STARTED) { + _audio_pipe_start(p); + } else { + ret = ESP_FAIL; + } + break; + case AUDIO_PIPE_STARTED: + case AUDIO_PIPE_RESUMED: + if (next_state == AUDIO_PIPE_PAUSED) { + _audio_pipe_pause(p); + } else if (next_state == AUDIO_PIPE_STOPPED) { + _audio_pipe_stop(p); + } else { + ret = ESP_FAIL; + } + break; + case AUDIO_PIPE_PAUSED: + if (next_state == AUDIO_PIPE_RESUMED) { + _audio_pipe_resume(p); + } else { + ret = ESP_FAIL; + } + break; + default: + ap_d("Invalid state %d\n", p->state); + break; + } + + if (ret == ESP_FAIL) { + ap_d("Failed for state transition curr:%d next:%d", p->state, next_state); + } + return ret; +} + +esp_err_t audio_pipe_destroy(audio_pipe_t *p) +{ + if (p == NULL) { + return ESP_ERR_INVALID_ARG; + } + + // Wait for existing operations to finish + lock(p->lock); + unlock(p->lock); + + audio_pipe_block_t *b, *save; + STAILQ_FOREACH_SAFE(b, &p->pb, next, save) { + if (b->rb) { + rb_cleanup(b->rb); + } + if (b->btype == STREAM_BLOCK) { + audio_stream_destroy(b->block_cfg); + } else if (b->btype == CODEC_BLOCK) { + audio_codec_destroy(b->block_cfg); + } + free(b); + } + + vSemaphoreDelete(p->lock); + free(p); + return ESP_OK; +} + +esp_err_t audio_pipe_register_event_cb(audio_pipe_t *p, audio_event_fn_arg_t *event_func) +{ + if (!p || !event_func) { + return ESP_ERR_INVALID_ARG; + } + + lock(p->lock); + memcpy(&p->event_func, event_func, sizeof(audio_event_fn_arg_t)); + unlock(p->lock); + + return ESP_OK; +} + +esp_err_t audio_pipe_start(audio_pipe_t *p) +{ + lock(p->lock); + esp_err_t ret = audio_state_machine(p, AUDIO_PIPE_STARTED); + unlock(p->lock); + return ret; +} + +esp_err_t audio_pipe_pause(audio_pipe_t *p) +{ + lock(p->lock); + esp_err_t ret = audio_state_machine(p, AUDIO_PIPE_PAUSED); + unlock(p->lock); + return ret; +} + +esp_err_t audio_pipe_resume(audio_pipe_t *p) +{ + lock(p->lock); + esp_err_t ret = audio_state_machine(p, AUDIO_PIPE_RESUMED); + unlock(p->lock); + return ret; +} + +esp_err_t audio_pipe_stop(audio_pipe_t *p) +{ + lock(p->lock); + esp_err_t ret = audio_state_machine(p, AUDIO_PIPE_STOPPED); + unlock(p->lock); + return ret; +} + +static ssize_t rb_read_cb(void *h, void *data, int len, uint32_t wait) +{ + return rb_read((ringbuf_t *) h, data, len, wait); +} + +static ssize_t rb_write_cb(void *h, void *data, int len, uint32_t wait) +{ + if (len <= 0) { + rb_signal_writer_finished((ringbuf_t *) h); + return len; + } + return rb_write((ringbuf_t *) h, data, len, wait); +} + +audio_pipe_t *_audio_pipe_create(const char *name, audio_stream_t *istream, size_t rb1_size, + audio_io_fn_arg_t *io_cb, audio_codec_t *codec, size_t rb2_size, + audio_stream_t *ostream) +{ + ringbuf_t *rb1 = NULL, *rb2 = NULL; + audio_io_fn_arg_t stream_io; + audio_io_fn_arg_t codec_input, codec_output; + + audio_pipe_t *pipe = audio_pipe_alloc(name); + if (pipe == NULL) { + ap_e("failed to create audio pipe"); + return NULL; + } + + audio_event_fn_arg_t event_func = { + .func = audio_pipe_event_cb, + .arg = pipe + }; + + if (istream != NULL) { + rb1 = rb_init("rb1", rb1_size); + if (!rb1) { + ap_e("Error creating ring buffer"); + goto err; + } + + // Add input stream to pipeline + stream_io.func = rb_write_cb; + stream_io.arg = rb1; + if (audio_stream_init(istream, "ipstream", &stream_io, &event_func) != ESP_OK) { + ap_d("Error initializing audio stream"); + goto err; + } + _create_insert_block(pipe, istream, STREAM_BLOCK, rb1, rb1_size, true); + codec_input.func = rb_read_cb; + codec_input.arg = rb1; + } else { + // Add input callback to pipeline + codec_input.func = io_cb->func; + codec_input.arg = io_cb->arg; + _create_insert_block(pipe, istream, CUSTOM_BLOCK, NULL, rb1_size, true); + } + + // Add codec to audio pipeline + if (codec != NULL) { + rb2 = rb_init("rb2", rb2_size); + if (!rb2) { + ap_e("Error creating ring buffer"); + goto err; + } + + codec_output.func = rb_write_cb; + codec_output.arg = rb2; + if (audio_codec_init(codec, "codec", &codec_input, &codec_output, &event_func) != ESP_OK) { + ap_d("Error initializing audio codec"); + goto err; + } + _create_insert_block(pipe, codec, CODEC_BLOCK, rb2, rb2_size, false); + stream_io.func = rb_read_cb; + stream_io.arg = rb2; + } else { + stream_io.func = rb_read_cb; + stream_io.arg = rb1; + } + + // Add output stream to pipeline + if (audio_stream_init(ostream, "opstream", &stream_io, &event_func) != ESP_OK) { + ap_d("Error initializing audio stream"); + goto err; + } + _create_insert_block(pipe, ostream, STREAM_BLOCK, NULL, 0, false); + + return pipe; +err: + audio_pipe_destroy(pipe); + return NULL; +} + +audio_pipe_t *audio_pipe_create(const char *name, audio_stream_t *istream, size_t rb1_size, + audio_codec_t *codec, size_t rb2_size, audio_stream_t *ostream) +{ + if (name == NULL || istream == NULL || rb1_size == 0 || ostream == NULL) { + ap_e("Invalid argument/s"); + return NULL; + } + + return _audio_pipe_create(name, istream, rb1_size, NULL, codec, rb2_size, ostream); +} + +audio_pipe_t *audio_pipe_create_with_input_cb(const char *name, audio_io_fn_arg_t *io_cb, + audio_codec_t *codec, size_t rb2_size, audio_stream_t *ostream) +{ + if (name == NULL || io_cb == NULL || ostream == NULL) { + ap_e("Invalid argument/s"); + return NULL; + } + + return _audio_pipe_create(name, NULL, RINGBUF1_DEFAULT_SIZE, io_cb, codec, rb2_size, ostream); +} + +static audio_pipe_block_t *get_input_block(audio_pipe_t *p) +{ + lock(p->lock); + audio_pipe_block_t *b = STAILQ_FIRST(&p->pb); + unlock(p->lock); + return b; +} + +static audio_pipe_block_t *get_codec_block(audio_pipe_t *p) +{ + bool found = false; + audio_pipe_block_t *b; + + lock(p->lock); + STAILQ_FOREACH(b, &p->pb, next) { + if (b->btype == CODEC_BLOCK) { + found = true; + break; + } + } + unlock(p->lock); + return found ? b : NULL; +} + +audio_stream_identifier_t audio_pipe_get_ip_stream_type(audio_pipe_t *p) +{ + if (p == NULL) { + return ESP_ERR_INVALID_ARG; + } + + audio_pipe_block_t *b = get_input_block(p); + if (b != NULL) { + return (b->btype == CUSTOM_BLOCK) ? STREAM_TYPE_CALLBACK : audio_stream_get_identifier(b->block_cfg); + } else { + return -1; + } +} + +audio_codec_identifier_t audio_pipe_get_codec_type(audio_pipe_t *p) +{ + if (p == NULL) { + return ESP_ERR_INVALID_ARG; + } + + audio_pipe_block_t *b = get_codec_block(p); + if (b != NULL) { + return audio_codec_get_identifier(b->block_cfg); + } else { + return -1; + } +} + +esp_err_t audio_pipe_set_input_stream(audio_pipe_t *p, audio_stream_t *new_stream) +{ + if (p == NULL) { + return ESP_ERR_INVALID_ARG; + } + + if (p->state != AUDIO_PIPE_INITED && p->state != AUDIO_PIPE_STOPPED) { + ap_d("Invalid pipeline state %d", p->state); + return ESP_ERR_INVALID_STATE; + } + + esp_err_t ret = ESP_FAIL; + audio_pipe_block_t *b = get_input_block(p); + if (b && b->btype == STREAM_BLOCK) { + ret = audio_pipe_replace_stream(p, b, new_stream); + if (ret != ESP_OK) { + ap_d("Failed to replace stream %d\n", ret); + return ret; + } + } else { + b->rb = rb_init("rb1", b->rb_size); + if (!b->rb) { + ap_e("Error creating ring buffer"); + return ESP_ERR_NO_MEM; + } + + audio_event_fn_arg_t event_func = { + .func = audio_pipe_event_cb, + .arg = p + }; + + audio_io_fn_arg_t stream_io; + // Add input stream to pipeline + stream_io.func = rb_write_cb; + stream_io.arg = b->rb; + if (audio_stream_init(new_stream, "ipstream", &stream_io, &event_func) != ESP_OK) { + ap_d("Error initializing audio stream"); + return ESP_FAIL; + } + b->block_cfg = new_stream; + b->btype = STREAM_BLOCK; + audio_io_fn_arg_t io_cb = { .func = rb_read_cb, .arg = b->rb }; + b = get_codec_block(p); + if (b != NULL) { + audio_codec_modify_input_cb(b->block_cfg, &io_cb); + } + } + return ret; +} + +esp_err_t audio_pipe_set_codec(audio_pipe_t *p, audio_codec_t *new_codec) +{ + if (p == NULL) { + return ESP_ERR_INVALID_ARG; + } + + if (p->state != AUDIO_PIPE_INITED && p->state != AUDIO_PIPE_STOPPED) { + ap_d("Invalid pipeline state %d", p->state); + return ESP_ERR_INVALID_STATE; + } + + esp_err_t ret = ESP_FAIL; + audio_pipe_block_t *b = get_codec_block(p); + if (b != NULL) { + ret = audio_pipe_replace_codec(p, b, new_codec); + if (ret != ESP_OK) { + ap_d("Failed to replace codec %d\n", ret); + return ret; + } + } + return ret; +} + +esp_err_t audio_pipe_set_input_cb(audio_pipe_t *p, audio_io_fn_arg_t *io_cb) +{ + if (p == NULL || io_cb == NULL) { + return ESP_ERR_INVALID_ARG; + } + + if (p->state != AUDIO_PIPE_INITED && p->state != AUDIO_PIPE_STOPPED) { + ap_e("stream is running, cant modify"); + return ESP_ERR_INVALID_STATE; + } + + audio_pipe_block_t *b = get_input_block(p); + if (b->btype == STREAM_BLOCK) { + audio_stream_destroy(b->block_cfg); + rb_cleanup(b->rb); + b->block_cfg = NULL; + b->rb = NULL; + b->btype = CUSTOM_BLOCK; + } + + b = get_codec_block(p); + if (b != NULL) { + audio_codec_modify_input_cb(b->block_cfg, io_cb); + } + return ESP_OK; +} diff --git a/components/audio_pipeline/audio_pipeline.h b/components/audio_pipeline/audio_pipeline.h new file mode 100644 index 0000000..0521e05 --- /dev/null +++ b/components/audio_pipeline/audio_pipeline.h @@ -0,0 +1,154 @@ +/* + * ESPRESSIF MIT License + * + * Copyright (c) 2018 + * + * Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case, + * it is free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#ifndef __AUDIO_PIPELINE_H__ +#define __AUDIO_PIPELINE_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Default rinbuffer sizes */ +#define RINGBUF1_DEFAULT_SIZE (8 * 1024) +#define RINGBUF2_DEFAULT_SIZE (8 * 1024) + +/** Private members */ +typedef enum { + AUDIO_PIPE_INITED = 1, + AUDIO_PIPE_STARTED, + AUDIO_PIPE_STOPPED, + AUDIO_PIPE_PAUSED, + AUDIO_PIPE_RESUMED, + AUDIO_PIPE_CHANGE_FREQ, +} audio_pipe_event_t; + +typedef audio_pipe_event_t audio_pipe_state_t; + +typedef enum { + STREAM_BLOCK = 1, + CODEC_BLOCK, + CUSTOM_BLOCK, +} block_type_t; + +typedef struct audio_pipe_block { + block_type_t btype; + void *block_cfg; + ringbuf_t *rb; + size_t rb_size; + STAILQ_ENTRY(audio_pipe_block) next; +} audio_pipe_block_t; + +typedef struct { + const char *name; + audio_pipe_state_t state; + audio_pipe_state_t old_state; + int cnt; + audio_event_fn_arg_t event_func; + xSemaphoreHandle lock; + STAILQ_HEAD( , audio_pipe_block) pb; +} audio_pipe_t; + +#define lock(x) \ + xSemaphoreTake(x, portMAX_DELAY) +#define unlock(x) \ + xSemaphoreGive(x) + +/** Destroy audio pipeline + * + * @param[in] p Pipeline handle returned by \ref audio_pipe_create + * @return ESP_OK or ESP_FAIL + */ +esp_err_t audio_pipe_destroy(audio_pipe_t *p); + +/** Create audio player + * + * Create audio pipeline with input and output streams (codec is optional). + */ +audio_pipe_t *audio_pipe_create(const char *name, audio_stream_t *istream, size_t rb1_size, + audio_codec_t *codec, size_t rb2_size, audio_stream_t *ostream); + +/** Create audio player with input callback + * + * Create audio pipeline with input callback (user defined). + */ +audio_pipe_t *audio_pipe_create_with_input_cb(const char *name, audio_io_fn_arg_t *io_cb, + audio_codec_t *codec, size_t rb2_size, audio_stream_t *ostream); + +/** Register event handler with pipeling + * + * @param[in] p Pipeline handle + * @param[in] cb event handler callback + * @return ESP_OK or ESP_FAIL + */ +esp_err_t audio_pipe_register_event_cb(audio_pipe_t *p, audio_event_fn_arg_t *event_func); + +/** Get input stream type from pipeline */ +audio_stream_identifier_t audio_pipe_get_ip_stream_type(audio_pipe_t *p); + +/** Get input codec type from pipeline */ +audio_codec_identifier_t audio_pipe_get_codec_type(audio_pipe_t *p); + +/** Set/update input stream/callback with new_stream in pipeline + * + * Pipeline should either be in inited or stopped state for this API to work. Old stream (if any) in pipeline + * will be destroyed and new stream will be initialzed and plugged in. + */ +esp_err_t audio_pipe_set_input_stream(audio_pipe_t *pipe, audio_stream_t *new_stream); + +/** Set/update codec with new_codec in pipeline (if multiple codecs, then primary would be updated) + * + * Pipeline should either be in inited or stopped state for this API to work. Old codec (if any) in pipeline + * will be destroyed and new codec will be initialized and plugged in. + */ +esp_err_t audio_pipe_set_codec(audio_pipe_t *pipe, audio_codec_t *new_codec); + +/** Set/update input stream/callback with io_cb + * + * Pipeline should either be in inited or stopped state for this API to work. Old stream (if any) in pipeline + * will be destroyed and callback will be plugged in. + */ +esp_err_t audio_pipe_set_input_cb(audio_pipe_t *pipe, audio_io_fn_arg_t *io_cb); + +/* Asynchronous control APIs, waiting for appropriate event is required */ +esp_err_t audio_pipe_start(audio_pipe_t *t); +esp_err_t audio_pipe_stop(audio_pipe_t *t); +esp_err_t audio_pipe_pause(audio_pipe_t *t); +esp_err_t audio_pipe_resume(audio_pipe_t *t); + +#ifdef __cplusplus +} +#endif + +#endif /* __AUDIO_PIPELINE_H__ */ diff --git a/components/audio_pipeline/component.mk b/components/audio_pipeline/component.mk new file mode 100644 index 0000000..7267d5f --- /dev/null +++ b/components/audio_pipeline/component.mk @@ -0,0 +1,3 @@ +COMPONENT_SRCDIRS := . + +COMPONENT_ADD_INCLUDEDIRS := . diff --git a/components/button/Kconfig b/components/button/Kconfig new file mode 100644 index 0000000..8125118 --- /dev/null +++ b/components/button/Kconfig @@ -0,0 +1,6 @@ +menu "Button" + config IO_GLITCH_FILTER_TIME_MS + int "IO glitch filter timer ms (10~100)" + range 10 100 + default 50 +endmenu \ No newline at end of file diff --git a/components/button/button/button.c b/components/button/button/button.c new file mode 100644 index 0000000..7adb0da --- /dev/null +++ b/components/button/button/button.c @@ -0,0 +1,363 @@ +/* + * ESPRESSIF MIT License + * + * Copyright (c) 2018 + * + * Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case, + * it is free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define IOT_CHECK(tag, a, ret) if(!(a)) { \ + ESP_LOGE(tag,"%s:%d (%s)", __FILE__, __LINE__, __FUNCTION__); \ + return (ret); \ + } +#define ERR_ASSERT(tag, param) IOT_CHECK(tag, (param) == ESP_OK, ESP_FAIL) +#define POINT_ASSERT(tag, param, ret) IOT_CHECK(tag, (param) != NULL, (ret)) + +typedef enum { + BUTTON_STATE_IDLE = 0, + BUTTON_STATE_PUSH, + BUTTON_STATE_PRESSED, +} button_status_t; + +typedef struct button_dev button_dev_t; +typedef struct btn_cb button_cb_t; + +struct btn_cb{ + TickType_t interval; + button_cb cb; + void* arg; + uint8_t on_press; + TimerHandle_t tmr; + button_dev_t *pbtn; + button_cb_t *next_cb; +}; + +struct button_dev{ + uint8_t io_num; + uint8_t active_level; + uint32_t serial_thres_sec; + uint8_t taskq_on; + QueueHandle_t taskq; + QueueHandle_t argq; + button_status_t state; + button_cb_t tap_short_cb; + button_cb_t tap_psh_cb; + button_cb_t tap_rls_cb; + button_cb_t press_serial_cb; + button_cb_t* cb_head; +}; + +#define BUTTON_GLITCH_FILTER_TIME_MS CONFIG_IO_GLITCH_FILTER_TIME_MS +static const char* TAG = "button"; + +static void button_press_cb(xTimerHandle tmr) +{ + button_cb_t* btn_cb = (button_cb_t*) pvTimerGetTimerID(tmr); + button_dev_t* btn = btn_cb->pbtn; + // low, then restart + if (btn->active_level == gpio_get_level(btn->io_num)) { + btn->state = BUTTON_STATE_PRESSED; + if (btn->taskq != NULL && btn->argq != NULL && btn->taskq_on && !btn_cb->on_press) { + void *tmp = btn_cb->cb; + xQueueOverwrite(btn->taskq, &tmp); + xQueueOverwrite(btn->argq, &btn_cb->arg); + } else if (btn_cb->cb) { + btn_cb->cb(btn_cb->arg); + } + } +} + +static void button_tap_psh_cb(xTimerHandle tmr) +{ + button_cb_t* btn_cb = (button_cb_t*) pvTimerGetTimerID(tmr); + button_dev_t* btn = btn_cb->pbtn; + xTimerStop(btn->tap_rls_cb.tmr, portMAX_DELAY); + int lv = gpio_get_level(btn->io_num); + + if (btn->active_level == lv) { + // True implies key is pressed + btn->state = BUTTON_STATE_PUSH; + if (btn->press_serial_cb.tmr) { + xTimerChangePeriod(btn->press_serial_cb.tmr, btn->serial_thres_sec*1000 / portTICK_PERIOD_MS, portMAX_DELAY); + xTimerReset(btn->press_serial_cb.tmr, portMAX_DELAY); + } + if (btn->tap_psh_cb.cb) { + btn->tap_psh_cb.cb(btn->tap_psh_cb.arg); + } + } else { + // 50ms, check if this is a real key up + if (btn->tap_rls_cb.tmr) { + xTimerStop(btn->tap_rls_cb.tmr, portMAX_DELAY); + xTimerReset(btn->tap_rls_cb.tmr, portMAX_DELAY); + } + } +} + +static void button_tap_rls_cb(xTimerHandle tmr) +{ + button_cb_t* btn_cb = (button_cb_t*) pvTimerGetTimerID(tmr); + button_dev_t* btn = btn_cb->pbtn; + xTimerStop(btn->tap_rls_cb.tmr, portMAX_DELAY); + if (btn->active_level == gpio_get_level(btn->io_num)) { + + } else { + // high, then key is up + button_cb_t *pcb = btn->cb_head; + while (pcb != NULL) { + if (pcb->tmr != NULL) { + xTimerStop(pcb->tmr, portMAX_DELAY); + } + pcb = pcb->next_cb; + } + if (btn->taskq != NULL && btn->argq != NULL && btn->taskq_on && uxQueueMessagesWaiting(btn->taskq) != 0 && btn->state != BUTTON_STATE_IDLE) { + void (*task)(void*); + void *arg; + xQueueReceive(btn->taskq, &task, 0); + xQueueReceive(btn->argq, &arg, 0); + task(arg); + } + if (btn->press_serial_cb.tmr && btn->press_serial_cb.tmr != NULL) { + xTimerStop(btn->press_serial_cb.tmr, portMAX_DELAY); + } + if (btn->tap_short_cb.cb && btn->state == BUTTON_STATE_PUSH) { + btn->tap_short_cb.cb(btn->tap_short_cb.arg); + } + if(btn->tap_rls_cb.cb && btn->state != BUTTON_STATE_IDLE) { + btn->tap_rls_cb.cb(btn->tap_rls_cb.arg); + } + btn->state = BUTTON_STATE_IDLE; + } +} + +static void button_press_serial_cb(xTimerHandle tmr) +{ + button_dev_t* btn = (button_dev_t*) pvTimerGetTimerID(tmr); + if (btn->press_serial_cb.cb) { + btn->press_serial_cb.cb(btn->press_serial_cb.arg); + } + xTimerChangePeriod(btn->press_serial_cb.tmr, btn->press_serial_cb.interval, portMAX_DELAY); + xTimerReset(btn->press_serial_cb.tmr, portMAX_DELAY); +} + +static void button_gpio_isr_handler(void* arg) +{ + button_dev_t* btn = (button_dev_t*) arg; + portBASE_TYPE HPTaskAwoken = pdFALSE; + int level = gpio_get_level(btn->io_num); + if (level == btn->active_level) { + if (btn->tap_psh_cb.tmr) { + xTimerStopFromISR(btn->tap_psh_cb.tmr, &HPTaskAwoken); + xTimerResetFromISR(btn->tap_psh_cb.tmr, &HPTaskAwoken); + } + + button_cb_t *pcb = btn->cb_head; + while (pcb != NULL) { + if (pcb->tmr != NULL) { + xTimerStopFromISR(pcb->tmr, &HPTaskAwoken); + xTimerResetFromISR(pcb->tmr, &HPTaskAwoken); + } + pcb = pcb->next_cb; + } + } else { + // 50ms, check if this is a real key up + if (btn->tap_rls_cb.tmr) { + xTimerStopFromISR(btn->tap_rls_cb.tmr, &HPTaskAwoken); + xTimerResetFromISR(btn->tap_rls_cb.tmr, &HPTaskAwoken); + } + } + if(HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR(); + } +} + +static void button_free_tmr(xTimerHandle* tmr) +{ + if (tmr && *tmr) { + xTimerStop(*tmr, portMAX_DELAY); + xTimerDelete(*tmr, portMAX_DELAY); + *tmr = NULL; + } +} + +esp_err_t iot_button_delete(button_handle_t btn_handle) +{ + POINT_ASSERT(TAG, btn_handle, ESP_ERR_INVALID_ARG); + button_dev_t* btn = (button_dev_t*) btn_handle; + gpio_set_intr_type(btn->io_num, GPIO_INTR_DISABLE); + gpio_isr_handler_remove(btn->io_num); + + button_free_tmr(&btn->tap_rls_cb.tmr); + button_free_tmr(&btn->tap_psh_cb.tmr); + button_free_tmr(&btn->tap_short_cb.tmr); + button_free_tmr(&btn->press_serial_cb.tmr); + + button_cb_t *pcb = btn->cb_head; + while (pcb != NULL) { + button_cb_t *cb_next = pcb->next_cb; + button_free_tmr(&pcb->tmr); + free(pcb); + pcb = cb_next; + } + free(btn); + return ESP_OK; +} + +button_handle_t iot_button_create(gpio_num_t gpio_num, button_active_t active_level) +{ + IOT_CHECK(TAG, gpio_num < GPIO_NUM_MAX, NULL); + button_dev_t* btn = (button_dev_t*) calloc(1, sizeof(button_dev_t)); + POINT_ASSERT(TAG, btn, NULL); + btn->active_level = active_level; + btn->io_num = gpio_num; + btn->state = BUTTON_STATE_IDLE; + btn->taskq_on = 0; + btn->taskq = xQueueCreate(1, sizeof(void*)); + btn->argq = xQueueCreate(1, sizeof(void *)); + btn->tap_rls_cb.arg = NULL; + btn->tap_rls_cb.cb = NULL; + btn->tap_rls_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_PERIOD_MS; + btn->tap_rls_cb.pbtn = btn; + btn->tap_rls_cb.tmr = xTimerCreate("btn_rls_tmr", btn->tap_rls_cb.interval, pdFALSE, + &btn->tap_rls_cb, button_tap_rls_cb); + btn->tap_psh_cb.arg = NULL; + btn->tap_psh_cb.cb = NULL; + btn->tap_psh_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_PERIOD_MS; + btn->tap_psh_cb.pbtn = btn; + btn->tap_psh_cb.tmr = xTimerCreate("btn_psh_tmr", btn->tap_psh_cb.interval, pdFALSE, + &btn->tap_psh_cb, button_tap_psh_cb); + gpio_install_isr_service(0); + gpio_config_t gpio_conf; + gpio_conf.intr_type = GPIO_INTR_ANYEDGE; + gpio_conf.mode = GPIO_MODE_INPUT; + gpio_conf.pin_bit_mask = (1 << gpio_num); + gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&gpio_conf); + gpio_isr_handler_add(gpio_num, button_gpio_isr_handler, btn); + return (button_handle_t) btn; +} + +esp_err_t iot_button_rm_cb(button_handle_t btn_handle, button_cb_type_t type) +{ + button_dev_t* btn = (button_dev_t*) btn_handle; + button_cb_t* btn_cb = NULL; + if (type == BUTTON_CB_PUSH) { + btn_cb = &btn->tap_psh_cb; + } else if (type == BUTTON_CB_RELEASE) { + btn_cb = &btn->tap_rls_cb; + } else if (type == BUTTON_CB_TAP) { + btn_cb = &btn->tap_short_cb; + } else if (type == BUTTON_CB_SERIAL) { + btn_cb = &btn->press_serial_cb; + } + btn_cb->cb = NULL; + btn_cb->arg = NULL; + btn_cb->pbtn = btn; + button_free_tmr(&btn_cb->tmr); + return ESP_OK; +} + +esp_err_t iot_button_set_serial_cb(button_handle_t btn_handle, uint32_t start_after_sec, TickType_t interval_tick, button_cb cb, void* arg) +{ + button_dev_t* btn = (button_dev_t*) btn_handle; + btn->serial_thres_sec = start_after_sec; + if (btn->press_serial_cb.tmr == NULL) { + btn->press_serial_cb.tmr = xTimerCreate("btn_serial_tmr", btn->serial_thres_sec*1000 / portTICK_PERIOD_MS, + pdFALSE, btn, button_press_serial_cb); + } + btn->press_serial_cb.arg = arg; + btn->press_serial_cb.cb = cb; + btn->press_serial_cb.interval = interval_tick; + btn->press_serial_cb.pbtn = btn; + xTimerChangePeriod(btn->press_serial_cb.tmr, btn->serial_thres_sec*1000 / portTICK_PERIOD_MS, portMAX_DELAY); + return ESP_OK; +} + +esp_err_t iot_button_set_evt_cb(button_handle_t btn_handle, button_cb_type_t type, button_cb cb, void* arg) +{ + POINT_ASSERT(TAG, btn_handle, ESP_ERR_INVALID_ARG); + button_dev_t* btn = (button_dev_t*) btn_handle; + if (type == BUTTON_CB_PUSH) { + btn->tap_psh_cb.arg = arg; + btn->tap_psh_cb.cb = cb; + btn->tap_psh_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_RATE_MS; + btn->tap_psh_cb.pbtn = btn; + xTimerChangePeriod(btn->tap_psh_cb.tmr, btn->tap_psh_cb.interval, portMAX_DELAY); + } else if (type == BUTTON_CB_RELEASE) { + btn->tap_rls_cb.arg = arg; + btn->tap_rls_cb.cb = cb; + btn->tap_rls_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_RATE_MS; + btn->tap_rls_cb.pbtn = btn; + xTimerChangePeriod(btn->tap_rls_cb.tmr, btn->tap_psh_cb.interval, portMAX_DELAY); + } else if (type == BUTTON_CB_TAP) { + btn->tap_short_cb.arg = arg; + btn->tap_short_cb.cb = cb; + btn->tap_short_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_RATE_MS; + btn->tap_short_cb.pbtn = btn; + } else if (type == BUTTON_CB_SERIAL) { + iot_button_set_serial_cb(btn_handle, 1, 1000 / portTICK_RATE_MS, cb, arg); + } + return ESP_OK; +} + +esp_err_t iot_button_add_on_press_cb(button_handle_t btn_handle, uint32_t press_sec, button_cb cb, void* arg) +{ + POINT_ASSERT(TAG, btn_handle, ESP_ERR_INVALID_ARG); + IOT_CHECK(TAG, press_sec != 0, ESP_ERR_INVALID_ARG); + button_dev_t* btn = (button_dev_t*) btn_handle; + button_cb_t* cb_new = (button_cb_t*) calloc(1, sizeof(button_cb_t)); + POINT_ASSERT(TAG, cb_new, ESP_FAIL); + cb_new->on_press = 1; + cb_new->arg = arg; + cb_new->cb = cb; + cb_new->interval = press_sec * 1000 / portTICK_PERIOD_MS; + cb_new->pbtn = btn; + cb_new->tmr = xTimerCreate("btn_press_tmr", cb_new->interval, pdFALSE, cb_new, button_press_cb); + cb_new->next_cb = btn->cb_head; + btn->cb_head = cb_new; + return ESP_OK; +} + +esp_err_t iot_button_add_on_release_cb(button_handle_t btn_handle, uint32_t press_sec, button_cb cb, void* arg) +{ + POINT_ASSERT(TAG, btn_handle, ESP_ERR_INVALID_ARG); + IOT_CHECK(TAG, press_sec != 0, ESP_ERR_INVALID_ARG); + button_dev_t* btn = (button_dev_t*) btn_handle; + button_cb_t* cb_new = (button_cb_t*) calloc(1, sizeof(button_cb_t)); + POINT_ASSERT(TAG, cb_new, ESP_FAIL); + btn->taskq_on = 1; + cb_new->arg = arg; + cb_new->cb = cb; + cb_new->interval = press_sec * 1000 / portTICK_PERIOD_MS; + cb_new->pbtn = btn; + cb_new->tmr = xTimerCreate("btn_press_tmr", cb_new->interval, pdFALSE, cb_new, button_press_cb); + cb_new->next_cb = btn->cb_head; + btn->cb_head = cb_new; + return ESP_OK; +} + diff --git a/components/button/button/button_obj.cpp b/components/button/button/button_obj.cpp new file mode 100644 index 0000000..8a1f287 --- /dev/null +++ b/components/button/button/button_obj.cpp @@ -0,0 +1,64 @@ +/* + * ESPRESSIF MIT License + * + * Copyright (c) 2017 + * + * Permission is hereby granted for use on ESPRESSIF SYSTEMS products only, in which case, + * it is free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include +#include + +CButton::CButton(gpio_num_t gpio_num, button_active_t active_level) +{ + m_btn_handle = iot_button_create(gpio_num, active_level); +} + +CButton::~CButton() +{ + iot_button_delete(m_btn_handle); + m_btn_handle = NULL; +} + +esp_err_t CButton::set_evt_cb(button_cb_type_t type, button_cb cb, void* arg) +{ + return iot_button_set_evt_cb(m_btn_handle, type, cb, arg); +} + +esp_err_t CButton::set_serial_cb(button_cb cb, void* arg, TickType_t interval_tick, uint32_t start_after_sec) +{ + return iot_button_set_serial_cb(m_btn_handle, start_after_sec, interval_tick, cb, arg); +} + +esp_err_t CButton::add_on_press_cb(uint32_t press_sec, button_cb cb, void* arg) +{ + return iot_button_add_on_press_cb(m_btn_handle, press_sec, cb, arg); +} + +esp_err_t CButton::add_on_release_cb(uint32_t press_sec, button_cb cb, void* arg) +{ + return iot_button_add_on_release_cb(m_btn_handle, press_sec, cb, arg); +} + +esp_err_t CButton::rm_cb(button_cb_type_t type) +{ + return iot_button_rm_cb(m_btn_handle, type); +} diff --git a/components/button/button/component.mk b/components/button/button/component.mk new file mode 100644 index 0000000..a98f634 --- /dev/null +++ b/components/button/button/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/components/button/button/include/iot_button.h b/components/button/button/include/iot_button.h new file mode 100644 index 0000000..287959b --- /dev/null +++ b/components/button/button/include/iot_button.h @@ -0,0 +1,281 @@ +/* + * ESPRESSIF MIT License + * + * Copyright (c) 2018 + * + * Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case, + * it is free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + + +#ifndef _IOT_BUTTON_H_ +#define _IOT_BUTTON_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +typedef void (* button_cb)(void*); +typedef void* button_handle_t; + +typedef enum { + BUTTON_ACTIVE_HIGH = 1, /*!