From 955a8ce303a9f8fd6a34009934e3d7aaeff3ec17 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Tue, 1 Aug 2023 12:13:48 +1000 Subject: Basic nvs init + bluetooth in the build --- src/drivers/CMakeLists.txt | 3 +- src/drivers/bluetooth.cpp | 11 ++++++ src/drivers/include/bluetooth.hpp | 19 ++++++++++ src/drivers/include/nvs.hpp | 27 +++++++++++++++ src/drivers/nvs.cpp | 73 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 src/drivers/bluetooth.cpp create mode 100644 src/drivers/include/bluetooth.hpp create mode 100644 src/drivers/include/nvs.hpp create mode 100644 src/drivers/nvs.cpp (limited to 'src/drivers') diff --git a/src/drivers/CMakeLists.txt b/src/drivers/CMakeLists.txt index 40cd0c4f..9774f80b 100644 --- a/src/drivers/CMakeLists.txt +++ b/src/drivers/CMakeLists.txt @@ -5,6 +5,7 @@ idf_component_register( SRCS "touchwheel.cpp" "i2s_dac.cpp" "gpios.cpp" "battery.cpp" "storage.cpp" "i2c.cpp" "spi.cpp" "display.cpp" "display_init.cpp" "samd.cpp" "relative_wheel.cpp" "wm8523.cpp" + "nvs.cpp" "bluetooth.cpp" INCLUDE_DIRS "include" - REQUIRES "esp_adc" "fatfs" "result" "lvgl" "span" "tasks") + REQUIRES "esp_adc" "fatfs" "result" "lvgl" "span" "tasks" "nvs_flash" "bt") target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) diff --git a/src/drivers/bluetooth.cpp b/src/drivers/bluetooth.cpp new file mode 100644 index 00000000..9748522f --- /dev/null +++ b/src/drivers/bluetooth.cpp @@ -0,0 +1,11 @@ +#include "bluetooth.hpp" + +#include "esp_bt.h" + +namespace drivers { + +auto Bluetooth::Enable() -> Bluetooth* { + return nullptr; +} + +} // namespace drivers diff --git a/src/drivers/include/bluetooth.hpp b/src/drivers/include/bluetooth.hpp new file mode 100644 index 00000000..f3a4b2ac --- /dev/null +++ b/src/drivers/include/bluetooth.hpp @@ -0,0 +1,19 @@ + +#pragma once + +#include + +namespace drivers { + +class Bluetooth { + public: + static auto Enable() -> Bluetooth*; + Bluetooth(); + ~Bluetooth(); + + struct Device {}; + auto Scan() -> std::vector; + private: + }; + +} diff --git a/src/drivers/include/nvs.hpp b/src/drivers/include/nvs.hpp new file mode 100644 index 00000000..be783583 --- /dev/null +++ b/src/drivers/include/nvs.hpp @@ -0,0 +1,27 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include "esp_err.h" +#include "nvs.h" + +namespace drivers { + +class NvsStorage { + public: + static auto Open() -> NvsStorage*; + + auto SchemaVersion() -> uint8_t; + + explicit NvsStorage(nvs_handle_t); + ~NvsStorage(); + + private: + nvs_handle_t handle_; +}; + +} // namespace drivers \ No newline at end of file diff --git a/src/drivers/nvs.cpp b/src/drivers/nvs.cpp new file mode 100644 index 00000000..a2de9518 --- /dev/null +++ b/src/drivers/nvs.cpp @@ -0,0 +1,73 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "nvs.hpp" +#include + +#include +#include + +#include "esp_log.h" +#include "nvs.h" +#include "nvs_flash.h" + +namespace drivers { + +static constexpr char kTag[] = "nvm"; +static constexpr uint8_t kSchemaVersion = 1; + +static constexpr char kKeyVersion[] = "ver"; + +auto NvsStorage::Open() -> NvsStorage* { + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_LOGW(kTag, "partition needs initialisation"); + nvs_flash_erase(); + err = nvs_flash_init(); + } + if (err != ESP_OK) { + ESP_LOGE(kTag, "failed to init nvm"); + return nullptr; + } + + nvs_handle_t handle; + if ((err = nvs_open("tangara", NVS_READWRITE, &handle)) != ESP_OK) { + ESP_LOGE(kTag, "failed to open nvs namespace"); + return nullptr; + } + + std::unique_ptr instance = std::make_unique(handle); + if (instance->SchemaVersion() < kSchemaVersion) { + ESP_LOGW(kTag, "namespace needs downgrading"); + nvs_erase_all(handle); + nvs_set_u8(handle, kKeyVersion, kSchemaVersion); + err = nvs_commit(handle); + if (err != ESP_OK) { + ESP_LOGW(kTag, "failed to init namespace"); + return nullptr; + } + } + + ESP_LOGI(kTag, "nvm storage initialised okay"); + return instance.release(); +} + +NvsStorage::NvsStorage(nvs_handle_t handle) : handle_(handle) {} + +NvsStorage::~NvsStorage() { + nvs_close(handle_); + nvs_flash_deinit(); +} + +auto NvsStorage::SchemaVersion() -> uint8_t { + uint8_t ret; + if (nvs_get_u8(handle_, kKeyVersion, &ret) != ESP_OK) { + return UINT8_MAX; + } + return ret; +} + +} // namespace drivers -- cgit v1.2.3 From 3511852f39cd5023ec8e6d0b94cc69f34e9201ed Mon Sep 17 00:00:00 2001 From: jacqueline Date: Thu, 3 Aug 2023 15:32:28 +1000 Subject: Add very limited resampling (it's slow as shit) --- src/drivers/bluetooth.cpp | 2 +- src/drivers/i2s_dac.cpp | 1 - src/drivers/include/bluetooth.hpp | 19 ++++++++++--------- src/drivers/include/i2s_dac.hpp | 6 ++---- 4 files changed, 13 insertions(+), 15 deletions(-) (limited to 'src/drivers') diff --git a/src/drivers/bluetooth.cpp b/src/drivers/bluetooth.cpp index 9748522f..a02fa620 100644 --- a/src/drivers/bluetooth.cpp +++ b/src/drivers/bluetooth.cpp @@ -5,7 +5,7 @@ namespace drivers { auto Bluetooth::Enable() -> Bluetooth* { - return nullptr; + return nullptr; } } // namespace drivers diff --git a/src/drivers/i2s_dac.cpp b/src/drivers/i2s_dac.cpp index c835fb1f..885321d1 100644 --- a/src/drivers/i2s_dac.cpp +++ b/src/drivers/i2s_dac.cpp @@ -161,7 +161,6 @@ auto I2SDac::Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate) word_length = 0b10; break; case BPS_32: - // TODO(jacqueline): Error on this? It's not supported anymore. slot_config_.data_bit_width = I2S_DATA_BIT_WIDTH_32BIT; slot_config_.ws_width = 32; word_length = 0b11; diff --git a/src/drivers/include/bluetooth.hpp b/src/drivers/include/bluetooth.hpp index f3a4b2ac..22b58c8b 100644 --- a/src/drivers/include/bluetooth.hpp +++ b/src/drivers/include/bluetooth.hpp @@ -6,14 +6,15 @@ namespace drivers { class Bluetooth { - public: - static auto Enable() -> Bluetooth*; - Bluetooth(); - ~Bluetooth(); + public: + static auto Enable() -> Bluetooth*; + Bluetooth(); + ~Bluetooth(); - struct Device {}; - auto Scan() -> std::vector; - private: - }; + struct Device {}; + auto Scan() -> std::vector; -} + private: +}; + +} // namespace drivers diff --git a/src/drivers/include/i2s_dac.hpp b/src/drivers/include/i2s_dac.hpp index 06c0dc16..889ba68c 100644 --- a/src/drivers/include/i2s_dac.hpp +++ b/src/drivers/include/i2s_dac.hpp @@ -51,14 +51,12 @@ class I2SDac { BPS_32 = I2S_DATA_BIT_WIDTH_32BIT, }; enum SampleRate { - SAMPLE_RATE_11_025 = 11025, - SAMPLE_RATE_16 = 16000, - SAMPLE_RATE_22_05 = 22050, + SAMPLE_RATE_8 = 8000, SAMPLE_RATE_32 = 32000, SAMPLE_RATE_44_1 = 44100, SAMPLE_RATE_48 = 48000, + SAMPLE_RATE_88_2 = 88200, SAMPLE_RATE_96 = 96000, - SAMPLE_RATE_192 = 192000, }; auto Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate) -> void; -- cgit v1.2.3 From 520ec6d98a761e1d96b5bea299819c096ce08ac3 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Tue, 8 Aug 2023 17:04:34 +1000 Subject: Add skeleton of bluetooth FSM --- src/drivers/CMakeLists.txt | 2 +- src/drivers/bluetooth.cpp | 246 +++++++++++++++++++++++++++++++++++++- src/drivers/include/bluetooth.hpp | 98 ++++++++++++++- 3 files changed, 338 insertions(+), 8 deletions(-) (limited to 'src/drivers') diff --git a/src/drivers/CMakeLists.txt b/src/drivers/CMakeLists.txt index 9774f80b..a64495f0 100644 --- a/src/drivers/CMakeLists.txt +++ b/src/drivers/CMakeLists.txt @@ -7,5 +7,5 @@ idf_component_register( "spi.cpp" "display.cpp" "display_init.cpp" "samd.cpp" "relative_wheel.cpp" "wm8523.cpp" "nvs.cpp" "bluetooth.cpp" INCLUDE_DIRS "include" - REQUIRES "esp_adc" "fatfs" "result" "lvgl" "span" "tasks" "nvs_flash" "bt") + REQUIRES "esp_adc" "fatfs" "result" "lvgl" "span" "tasks" "nvs_flash" "bt" "tinyfsm") target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) diff --git a/src/drivers/bluetooth.cpp b/src/drivers/bluetooth.cpp index a02fa620..f9ab4e95 100644 --- a/src/drivers/bluetooth.cpp +++ b/src/drivers/bluetooth.cpp @@ -1,11 +1,253 @@ #include "bluetooth.hpp" +#include + +#include +#include +#include + +#include "esp_a2dp_api.h" +#include "esp_avrc_api.h" #include "esp_bt.h" +#include "esp_bt_device.h" +#include "esp_bt_main.h" +#include "esp_gap_bt_api.h" +#include "esp_log.h" +#include "esp_mac.h" +#include "tinyfsm/include/tinyfsm.hpp" namespace drivers { -auto Bluetooth::Enable() -> Bluetooth* { - return nullptr; +static constexpr char kTag[] = "bluetooth"; + +static std::atomic sStream; + +auto gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t* param) -> void { + tinyfsm::FsmList::dispatch( + bluetooth::events::internal::Gap{.type = event, .param = param}); +} + +auto avrcp_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t* param) + -> void { + tinyfsm::FsmList::dispatch( + bluetooth::events::internal::Avrc{.type = event, .param = param}); +} + +auto a2dp_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t* param) -> void { + tinyfsm::FsmList::dispatch( + bluetooth::events::internal::A2dp{.type = event, .param = param}); +} + +auto a2dp_data_cb(uint8_t* buf, int32_t buf_size) -> int32_t { + if (buf == nullptr || buf_size <= 0) { + return 0; + } + StreamBufferHandle_t stream = sStream.load(); + if (stream == nullptr) { + return 0; + } + return xStreamBufferReceive(stream, buf, buf_size, 0); +} + +Bluetooth::Bluetooth() { + tinyfsm::FsmList::start(); +} + +auto Bluetooth::Enable() -> bool { + tinyfsm::FsmList::dispatch( + bluetooth::events::Enable{}); + + return !bluetooth::BluetoothState::is_in_state(); +} + +auto Bluetooth::Disable() -> void { + tinyfsm::FsmList::dispatch( + bluetooth::events::Disable{}); +} + +auto DeviceName() -> std::string { + uint8_t mac[8]{0}; + esp_efuse_mac_get_default(mac); + std::ostringstream name; + name << "TANGARA " << std::hex << mac[0] << mac[1]; + return name.str(); +} + +namespace bluetooth { + +static bool sIsFirstEntry = true; + +void Disabled::entry() { + if (sIsFirstEntry) { + // We only use BT Classic, to claw back ~60KiB from the BLE firmware. + esp_bt_controller_mem_release(ESP_BT_MODE_BLE); + sIsFirstEntry = false; + return; + } + + esp_bluedroid_disable(); + esp_bluedroid_deinit(); + esp_bt_controller_disable(); +} + +void Disabled::react(const events::Enable&) { + esp_bt_controller_config_t config = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + if (esp_bt_controller_init(&config) != ESP_OK) { + ESP_LOGE(kTag, "initialize controller failed"); + return; + } + + if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != ESP_OK) { + ESP_LOGE(kTag, "enable controller failed"); + return; + } + + if (esp_bluedroid_init() != ESP_OK) { + ESP_LOGE(kTag, "initialize bluedroid failed"); + return; + } + + if (esp_bluedroid_enable() != ESP_OK) { + ESP_LOGE(kTag, "enable bluedroid failed"); + return; + } + + // Enable Secure Simple Pairing + esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE; + esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO; + esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t)); + + // Set a reasonable name for the device. + std::string name = DeviceName(); + esp_bt_dev_set_device_name(name.c_str()); + + // Initialise GAP. This controls advertising our device, and scanning for + // other devices. + esp_bt_gap_register_callback(gap_cb); + + // Initialise AVRCP. This handles playback controls; play/pause/volume/etc. + // esp_avrc_ct_init(); + // esp_avrc_ct_register_callback(avrcp_cb); + + // Initialise A2DP. This handles streaming audio. Currently ESP-IDF's SBC + // encoder only supports 2 channels of interleaved 16 bit samples, at + // 44.1kHz, so there is no additional configuration to be done for the + // stream itself. + esp_a2d_source_init(); + esp_a2d_register_callback(a2dp_cb); + esp_a2d_source_register_data_callback(a2dp_data_cb); + + // Don't let anyone interact with us before we're ready. + esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); + + transit(); } +static constexpr uint8_t kDiscoveryTimeSeconds = 10; +static constexpr uint8_t kDiscoveryMaxResults = 0; + +void Scanning::entry() { + ESP_LOGI(kTag, "scanning for devices"); + esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, + kDiscoveryTimeSeconds, kDiscoveryMaxResults); +} + +void Scanning::exit() { + esp_bt_gap_cancel_discovery(); +} + +auto OnDeviceDiscovered(esp_bt_gap_cb_param_t* param) -> void { + ESP_LOGI(kTag, "device discovered"); +} + +void Scanning::react(const events::internal::Gap& ev) { + switch (ev.type) { + case ESP_BT_GAP_DISC_RES_EVT: + OnDeviceDiscovered(ev.param); + break; + case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: + if (ev.param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) { + ESP_LOGI(kTag, "still scanning"); + esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, + kDiscoveryTimeSeconds, kDiscoveryMaxResults); + } + break; + case ESP_BT_GAP_MODE_CHG_EVT: + // todo: mode change. is this important? + ESP_LOGI(kTag, "GAP mode changed"); + break; + default: + ESP_LOGW(kTag, "unhandled GAP event: %u", ev.type); + } +} + +void Connecting::entry() { + ESP_LOGI(kTag, "connecting to device"); + esp_a2d_source_connect(nullptr); +} + +void Connecting::exit() {} + +void Connecting::react(const events::internal::Gap& ev) { + switch (ev.type) { + case ESP_BT_GAP_AUTH_CMPL_EVT: + // todo: auth completed. check if we succeeded. + break; + case ESP_BT_GAP_PIN_REQ_EVT: + // todo: device needs a pin to connect. + break; + case ESP_BT_GAP_CFM_REQ_EVT: + // todo: device needs user to click okay. + break; + case ESP_BT_GAP_KEY_NOTIF_EVT: + // todo: device is telling us a password? + break; + case ESP_BT_GAP_KEY_REQ_EVT: + // todo: device needs a password + break; + case ESP_BT_GAP_MODE_CHG_EVT: + // todo: mode change. is this important? + break; + default: + ESP_LOGW(kTag, "unhandled GAP event: %u", ev.type); + } +} + +void Connecting::react(const events::internal::A2dp& ev) { + switch (ev.type) { + case ESP_A2D_CONNECTION_STATE_EVT: + // todo: connection state changed. we might be connected! + break; + default: + ESP_LOGW(kTag, "unhandled A2DP event: %u", ev.type); + } +} + +void Connected::react(const events::internal::A2dp& ev) { + switch (ev.type) { + case ESP_A2D_CONNECTION_STATE_EVT: + // todo: connection state changed. we might have dropped + break; + case ESP_A2D_AUDIO_STATE_EVT: + // todo: audio state changed. who knows, dude. + break; + default: + ESP_LOGW(kTag, "unhandled A2DP event: %u", ev.type); + } +} + +void Connected::react(const events::internal::Avrc& ev) { + switch (ev.type) { + case ESP_AVRC_CT_CONNECTION_STATE_EVT: + // todo: avrc connected. send our capabilities. + default: + ESP_LOGW(kTag, "unhandled AVRC event: %u", ev.type); + } +} + +} // namespace bluetooth + } // namespace drivers + +FSM_INITIAL_STATE(drivers::bluetooth::BluetoothState, + drivers::bluetooth::Disabled) diff --git a/src/drivers/include/bluetooth.hpp b/src/drivers/include/bluetooth.hpp index 22b58c8b..2b5e6a8d 100644 --- a/src/drivers/include/bluetooth.hpp +++ b/src/drivers/include/bluetooth.hpp @@ -1,20 +1,108 @@ #pragma once +#include #include +#include +#include +#include "esp_a2dp_api.h" +#include "esp_avrc_api.h" +#include "esp_gap_bt_api.h" +#include "tinyfsm.hpp" +#include "tinyfsm/include/tinyfsm.hpp" + namespace drivers { +/* + * A handle used to interact with the bluetooth state machine. + */ class Bluetooth { public: - static auto Enable() -> Bluetooth*; Bluetooth(); - ~Bluetooth(); - struct Device {}; - auto Scan() -> std::vector; + auto Enable() -> bool; + auto Disable() -> void; + + auto SetSource(StreamBufferHandle_t) -> void; +}; + +namespace bluetooth { + +namespace events { +struct Enable : public tinyfsm::Event {}; +struct Disable : public tinyfsm::Event {}; + +namespace internal { +struct Gap : public tinyfsm::Event { + esp_bt_gap_cb_event_t type; + esp_bt_gap_cb_param_t* param; +}; +struct A2dp : public tinyfsm::Event { + esp_a2d_cb_event_t type; + esp_a2d_cb_param_t* param; +}; +struct Avrc : public tinyfsm::Event { + esp_avrc_ct_cb_event_t type; + esp_avrc_ct_cb_param_t* param; +}; +} // namespace internal +} // namespace events + +class BluetoothState : public tinyfsm::Fsm { + public: + virtual ~BluetoothState(){}; + + virtual void entry() {} + virtual void exit() {} + + virtual void react(const events::Enable& ev){}; + virtual void react(const events::Disable& ev) = 0; + + virtual void react(const events::internal::Gap& ev) = 0; + virtual void react(const events::internal::A2dp& ev) = 0; + virtual void react(const events::internal::Avrc& ev){}; +}; + +class Disabled : public BluetoothState { + void entry() override; + + void react(const events::Enable& ev) override; + void react(const events::Disable& ev) override{}; + + void react(const events::internal::Gap& ev) override {} + void react(const events::internal::A2dp& ev) override {} +}; + +class Scanning : public BluetoothState { + void entry() override; + void exit() override; + + void react(const events::Disable& ev) override; - private: + void react(const events::internal::Gap& ev) override; + void react(const events::internal::A2dp& ev) override; }; +class Connecting : public BluetoothState { + void entry() override; + void exit() override; + + void react(const events::Disable& ev) override; + void react(const events::internal::Gap& ev) override; + void react(const events::internal::A2dp& ev) override; +}; + +class Connected : public BluetoothState { + void entry() override; + void exit() override; + + void react(const events::Disable& ev) override; + void react(const events::internal::Gap& ev) override; + void react(const events::internal::A2dp& ev) override; + void react(const events::internal::Avrc& ev) override; +}; + +} // namespace bluetooth + } // namespace drivers -- cgit v1.2.3 From 592f231627843bc44ebaaa4506aec26da1f56499 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Tue, 8 Aug 2023 17:11:13 +1000 Subject: Improve sd card errors --- src/drivers/storage.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'src/drivers') diff --git a/src/drivers/storage.cpp b/src/drivers/storage.cpp index db257dee..f253a79a 100644 --- a/src/drivers/storage.cpp +++ b/src/drivers/storage.cpp @@ -63,7 +63,7 @@ auto SdStorage::Create(IGpios* gpio) -> cpp::result { // Will return ESP_ERR_INVALID_RESPONSE if there is no card esp_err_t err = sdmmc_card_init(host.get(), card.get()); if (err != ESP_OK) { - ESP_LOGW(kTag, "Failed to read, err: %d", err); + ESP_LOGW(kTag, "Failed to read, err: %s", esp_err_to_name(err)); return cpp::fail(Error::FAILED_TO_READ); } @@ -74,7 +74,21 @@ auto SdStorage::Create(IGpios* gpio) -> cpp::result { // Mount right now, not on first operation. FRESULT ferr = f_mount(fs, "", 1); if (ferr != FR_OK) { - ESP_LOGW(kTag, "Failed to mount, err: %d", ferr); + std::string err_str; + switch (ferr) { + case FR_DISK_ERR: + err_str = "FR_DISK_ERR"; + break; + case FR_NOT_READY: + err_str = "FR_NOT_READY"; + break; + case FR_NO_FILESYSTEM: + err_str = "FR_NO_FILESYSTEM"; + break; + default: + err_str = std::to_string(ferr); + } + ESP_LOGW(kTag, "Failed to mount, err: %s", err_str.c_str()); return cpp::fail(Error::FAILED_TO_MOUNT); } -- cgit v1.2.3