summaryrefslogtreecommitdiff
path: root/src/drivers
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2024-09-09 15:15:00 +1000
committerjacqueline <me@jacqueline.id.au>2024-09-09 15:15:00 +1000
commit2b1a01705d62d08cefd6816ba108c5cae48a50ac (patch)
tree20ba16a6259ffc335dbcded84fa6bcbe327e9d84 /src/drivers
parent9475d10d1000c7e21a7ea311b0c8ee6a72ef46c4 (diff)
parentacdc9789c90ed6f083d054cd07930e020123457f (diff)
downloadtangara-fw-2b1a01705d62d08cefd6816ba108c5cae48a50ac.tar.gz
Merge branch 'main' into jqln/tts
Diffstat (limited to 'src/drivers')
-rw-r--r--src/drivers/CMakeLists.txt2
-rw-r--r--src/drivers/bluetooth.cpp303
-rw-r--r--src/drivers/display.cpp9
-rw-r--r--src/drivers/i2s_dac.cpp4
-rw-r--r--src/drivers/include/drivers/bluetooth.hpp115
-rw-r--r--src/drivers/include/drivers/bluetooth_types.hpp7
-rw-r--r--src/drivers/include/drivers/nvs.hpp18
-rw-r--r--src/drivers/include/drivers/pcm_buffer.hpp8
-rw-r--r--src/drivers/include/drivers/samd.hpp13
-rw-r--r--src/drivers/include/drivers/touchwheel.hpp3
-rw-r--r--src/drivers/nvs.cpp134
-rw-r--r--src/drivers/pcm_buffer.cpp17
-rw-r--r--src/drivers/samd.cpp74
-rw-r--r--src/drivers/spi.cpp2
-rw-r--r--src/drivers/test/test_samd.cpp4
-rw-r--r--src/drivers/touchwheel.cpp8
16 files changed, 520 insertions, 201 deletions
diff --git a/src/drivers/CMakeLists.txt b/src/drivers/CMakeLists.txt
index 33d25894..fea5780f 100644
--- a/src/drivers/CMakeLists.txt
+++ b/src/drivers/CMakeLists.txt
@@ -8,5 +8,5 @@ idf_component_register(
"samd.cpp" "wm8523.cpp" "nvs.cpp" "haptics.cpp" "spiffs.cpp" "pcm_buffer.cpp"
INCLUDE_DIRS "include"
REQUIRES "esp_adc" "fatfs" "result" "lvgl" "nvs_flash" "spiffs" "bt"
- "tasks" "tinyfsm" "util" "libcppbor")
+ "tasks" "tinyfsm" "util" "libcppbor" "driver")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})
diff --git a/src/drivers/bluetooth.cpp b/src/drivers/bluetooth.cpp
index acb38ce4..3da5dd0c 100644
--- a/src/drivers/bluetooth.cpp
+++ b/src/drivers/bluetooth.cpp
@@ -4,6 +4,7 @@
#include <algorithm>
#include <atomic>
+#include <iterator>
#include <mutex>
#include <ostream>
#include <sstream>
@@ -116,93 +117,111 @@ IRAM_ATTR auto a2dp_data_cb(uint8_t* buf, int32_t buf_size) -> int32_t {
return buf_size;
}
-Bluetooth::Bluetooth(NvsStorage& storage, tasks::WorkerPool& bg_worker) {
+Bluetooth::Bluetooth(NvsStorage& storage,
+ tasks::WorkerPool& bg_worker,
+ EventHandler cb)
+ : nvs_(storage) {
sBgWorker = &bg_worker;
- bluetooth::BluetoothState::Init(storage);
+ bluetooth::BluetoothState::Init(storage, cb);
}
-auto Bluetooth::Enable() -> bool {
- auto lock = bluetooth::BluetoothState::lock();
- tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch(
- bluetooth::events::Enable{});
+auto Bluetooth::enable(bool en) -> void {
+ if (en) {
+ auto lock = bluetooth::BluetoothState::lock();
+ tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch(
+ bluetooth::events::Enable{});
+ } else {
+ // FIXME: the BT tasks unfortunately call back into us while holding an
+ // internal lock, which then deadlocks with our fsm lock.
+ // auto lock = bluetooth::BluetoothState::lock();
+ tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch(
+ bluetooth::events::Disable{});
+ }
+}
+auto Bluetooth::enabled() -> bool {
+ auto lock = bluetooth::BluetoothState::lock();
return !bluetooth::BluetoothState::is_in_state<bluetooth::Disabled>();
}
-auto Bluetooth::Disable() -> void {
- // FIXME: the BT tasks unfortunately call back into us while holding an
- // internal lock, which then deadlocks with our fsm lock.
- // auto lock = bluetooth::BluetoothState::lock();
+auto Bluetooth::sources(OutputBuffers* src) -> void {
+ auto lock = bluetooth::BluetoothState::lock();
+ if (src == sStreams) {
+ return;
+ }
+ sStreams = src;
tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch(
- bluetooth::events::Disable{});
+ bluetooth::events::SourcesChanged{});
}
-auto Bluetooth::IsEnabled() -> bool {
- auto lock = bluetooth::BluetoothState::lock();
- return !bluetooth::BluetoothState::is_in_state<bluetooth::Disabled>();
+auto Bluetooth::softVolume(float f) -> void {
+ sVolumeFactor = f;
}
-auto Bluetooth::IsConnected() -> bool {
+auto Bluetooth::connectionState() -> ConnectionState {
auto lock = bluetooth::BluetoothState::lock();
- return bluetooth::BluetoothState::is_in_state<bluetooth::Connected>();
+ if (bluetooth::BluetoothState::is_in_state<bluetooth::Connected>()) {
+ return ConnectionState::kConnected;
+ } else if (bluetooth::BluetoothState::is_in_state<bluetooth::Connecting>()) {
+ return ConnectionState::kConnecting;
+ }
+ return ConnectionState::kDisconnected;
}
-auto Bluetooth::ConnectedDevice() -> std::optional<bluetooth::MacAndName> {
+auto Bluetooth::pairedDevice() -> std::optional<bluetooth::MacAndName> {
auto lock = bluetooth::BluetoothState::lock();
- if (!bluetooth::BluetoothState::is_in_state<bluetooth::Connected>()) {
- return {};
- }
- return bluetooth::BluetoothState::preferred_device();
+ return bluetooth::BluetoothState::pairedDevice();
}
-auto Bluetooth::KnownDevices() -> std::vector<bluetooth::Device> {
+auto Bluetooth::pairedDevice(std::optional<bluetooth::MacAndName> dev) -> void {
auto lock = bluetooth::BluetoothState::lock();
- std::vector<bluetooth::Device> out = bluetooth::BluetoothState::devices();
- std::sort(out.begin(), out.end(), [](const auto& a, const auto& b) -> bool {
- return a.signal_strength < b.signal_strength;
- });
- return out;
+ bluetooth::BluetoothState::pairedDevice(dev);
}
-auto Bluetooth::SetPreferredDevice(std::optional<bluetooth::MacAndName> dev)
- -> void {
- auto lock = bluetooth::BluetoothState::lock();
- auto cur = bluetooth::BluetoothState::preferred_device();
- if (dev && cur && dev->mac == cur->mac) {
- return;
- }
- ESP_LOGI(kTag, "preferred is '%s' (%u%u%u%u%u%u)", dev->name.c_str(),
- dev->mac[0], dev->mac[1], dev->mac[2], dev->mac[3], dev->mac[4],
- dev->mac[5]);
- bluetooth::BluetoothState::preferred_device(dev);
- tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch(
- bluetooth::events::PreferredDeviceChanged{});
+auto Bluetooth::knownDevices() -> std::vector<bluetooth::MacAndName> {
+ return nvs_.BluetoothNames();
}
-auto Bluetooth::PreferredDevice() -> std::optional<bluetooth::MacAndName> {
- auto lock = bluetooth::BluetoothState::lock();
- return bluetooth::BluetoothState::preferred_device();
+auto Bluetooth::forgetKnownDevice(const bluetooth::mac_addr_t& mac) -> void {
+ nvs_.BluetoothName(mac, {});
}
-auto Bluetooth::SetSources(OutputBuffers* src) -> void {
+auto Bluetooth::discoveryEnabled(bool en) -> void {
auto lock = bluetooth::BluetoothState::lock();
- OutputBuffers* cur = bluetooth::BluetoothState::sources();
- if (src == cur) {
- return;
- }
- bluetooth::BluetoothState::sources(src);
- tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch(
- bluetooth::events::SourcesChanged{});
+ bluetooth::BluetoothState::discovery(en);
}
-auto Bluetooth::SetVolumeFactor(float f) -> void {
- sVolumeFactor = f;
+auto Bluetooth::discoveryEnabled() -> bool {
+ auto lock = bluetooth::BluetoothState::lock();
+ return bluetooth::BluetoothState::discovery();
}
-auto Bluetooth::SetEventHandler(std::function<void(bluetooth::Event)> cb)
- -> void {
- auto lock = bluetooth::BluetoothState::lock();
- bluetooth::BluetoothState::event_handler(cb);
+auto Bluetooth::discoveredDevices() -> std::vector<bluetooth::MacAndName> {
+ std::vector<bluetooth::Device> discovered;
+ {
+ auto lock = bluetooth::BluetoothState::lock();
+ discovered = bluetooth::BluetoothState::discoveredDevices();
+ }
+
+ // Show devices with stronger signals first, since they're more likely to be
+ // physically close (and therefore more likely to be what the user wants).
+ std::sort(discovered.begin(), discovered.end(),
+ [](const auto& a, const auto& b) -> bool {
+ return a.signal_strength < b.signal_strength;
+ });
+
+ // Convert to the right format.
+ std::vector<bluetooth::MacAndName> out;
+ out.reserve(discovered.size());
+ std::transform(discovered.begin(), discovered.end(), std::back_inserter(out),
+ [&](const bluetooth::Device& dev) {
+ return bluetooth::MacAndName{
+ .mac = dev.address,
+ .name = {dev.name.data(), dev.name.size()},
+ };
+ });
+
+ return out;
}
static auto DeviceName() -> std::pmr::string {
@@ -255,6 +274,10 @@ auto Scanner::StopScanningNow() -> void {
}
}
+auto Scanner::enabled() -> bool {
+ return enabled_;
+}
+
auto Scanner::HandleGapEvent(const events::internal::Gap& ev) -> void {
switch (ev.type) {
case ESP_BT_GAP_DISC_RES_EVT:
@@ -347,16 +370,18 @@ NvsStorage* BluetoothState::sStorage_;
Scanner* BluetoothState::sScanner_;
std::mutex BluetoothState::sFsmMutex{};
-std::map<mac_addr_t, Device> BluetoothState::sDevices_{};
-std::optional<MacAndName> BluetoothState::sPreferredDevice_{};
-std::optional<MacAndName> BluetoothState::sConnectingDevice_{};
+std::map<mac_addr_t, Device> BluetoothState::sDiscoveredDevices_{};
+std::optional<MacAndName> BluetoothState::sPairedWith_{};
+std::optional<MacAndName> BluetoothState::sConnectingTo_{};
int BluetoothState::sConnectAttemptsRemaining_{0};
std::function<void(Event)> BluetoothState::sEventHandler_;
-auto BluetoothState::Init(NvsStorage& storage) -> void {
+auto BluetoothState::Init(NvsStorage& storage, Bluetooth::EventHandler cb)
+ -> void {
sStorage_ = &storage;
- sPreferredDevice_ = storage.PreferredBluetoothDevice();
+ sEventHandler_ = cb;
+ sPairedWith_ = storage.PreferredBluetoothDevice();
tinyfsm::FsmList<bluetooth::BluetoothState>::start();
}
@@ -364,68 +389,85 @@ auto BluetoothState::lock() -> std::lock_guard<std::mutex> {
return std::lock_guard<std::mutex>{sFsmMutex};
}
-auto BluetoothState::devices() -> std::vector<Device> {
- std::vector<Device> out;
- for (const auto& device : sDevices_) {
- out.push_back(device.second);
- }
- return out;
+auto BluetoothState::pairedDevice() -> std::optional<MacAndName> {
+ return sPairedWith_;
}
-auto BluetoothState::preferred_device() -> std::optional<MacAndName> {
- return sPreferredDevice_;
-}
+auto BluetoothState::pairedDevice(std::optional<MacAndName> dev) -> void {
+ auto cur = sPairedWith_;
+ if (dev && cur && dev->mac == cur->mac) {
+ return;
+ }
+ if (dev) {
+ ESP_LOGI(kTag, "pairing with '%s' (%u%u%u%u%u%u)", dev->name.c_str(),
+ dev->mac[0], dev->mac[1], dev->mac[2], dev->mac[3], dev->mac[4],
+ dev->mac[5]);
+ }
+ sPairedWith_ = dev;
+ std::invoke(sEventHandler_, SimpleEvent::kDeviceDiscovered);
-auto BluetoothState::preferred_device(std::optional<MacAndName> addr) -> void {
- sPreferredDevice_ = addr;
+ tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch(
+ bluetooth::events::PairedDeviceChanged{});
}
-auto BluetoothState::sources() -> OutputBuffers* {
- return sStreams;
+auto BluetoothState::discovery() -> bool {
+ return sScanner_->enabled();
}
-auto BluetoothState::sources(OutputBuffers* src) -> void {
- sStreams = src;
+auto BluetoothState::discovery(bool en) -> void {
+ if (en) {
+ if (!sScanner_->enabled()) {
+ sDiscoveredDevices_.clear();
+ }
+ sScanner_->ScanContinuously();
+ } else {
+ sScanner_->StopScanning();
+ }
}
-auto BluetoothState::event_handler(std::function<void(Event)> cb) -> void {
- sEventHandler_ = cb;
+auto BluetoothState::discoveredDevices() -> std::vector<Device> {
+ std::vector<Device> out;
+ for (const auto& device : sDiscoveredDevices_) {
+ out.push_back(device.second);
+ }
+ return out;
}
auto BluetoothState::react(const events::DeviceDiscovered& ev) -> void {
- bool is_preferred = false;
- bool already_known = sDevices_.contains(ev.device.address);
- sDevices_[ev.device.address] = ev.device;
+ bool is_paired = false;
+ bool already_known = sDiscoveredDevices_.contains(ev.device.address);
+ sDiscoveredDevices_[ev.device.address] = ev.device;
- if (sPreferredDevice_ && ev.device.address == sPreferredDevice_->mac) {
- is_preferred = true;
+ if (sPairedWith_ && ev.device.address == sPairedWith_->mac) {
+ is_paired = true;
}
- if (sEventHandler_ && !already_known) {
- std::invoke(sEventHandler_, SimpleEvent::kKnownDevicesChanged);
+ if (!already_known) {
+ std::invoke(sEventHandler_, SimpleEvent::kDeviceDiscovered);
}
- if (is_preferred && sPreferredDevice_) {
- connect(*sPreferredDevice_);
+ if (is_paired && sPairedWith_) {
+ connect(*sPairedWith_);
}
}
auto BluetoothState::connect(const MacAndName& dev) -> bool {
- if (sConnectingDevice_ && sConnectingDevice_->mac == dev.mac) {
+ if (sConnectingTo_ && sConnectingTo_->mac == dev.mac) {
sConnectAttemptsRemaining_--;
} else {
sConnectAttemptsRemaining_ = 3;
}
if (sConnectAttemptsRemaining_ == 0) {
+ sConnectingTo_ = {};
return false;
}
- sConnectingDevice_ = dev;
+ sConnectingTo_ = dev;
ESP_LOGI(kTag, "connecting to '%s' (%u%u%u%u%u%u)", dev.name.c_str(),
dev.mac[0], dev.mac[1], dev.mac[2], dev.mac[3], dev.mac[4],
dev.mac[5]);
- if (esp_a2d_source_connect(sConnectingDevice_->mac.data()) != ESP_OK) {
+ if (esp_a2d_source_connect(sConnectingTo_->mac.data()) != ESP_OK) {
ESP_LOGI(kTag, "Connecting failed...");
if (sConnectAttemptsRemaining_ > 1) {
ESP_LOGI(kTag, "Will retry.");
@@ -489,7 +531,7 @@ void Disabled::react(const events::Enable&) {
// Set a reasonable name for the device.
std::pmr::string name = DeviceName();
- esp_bt_dev_set_device_name(name.c_str());
+ esp_bt_gap_set_device_name(name.c_str());
// Initialise GAP. This controls advertising our device, and scanning for
// other devices.
@@ -553,36 +595,31 @@ void Disabled::react(const events::Enable&) {
esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
ESP_LOGI(kTag, "bt enabled");
- if (sPreferredDevice_) {
- ESP_LOGI(kTag, "connecting to preferred device '%s'",
- sPreferredDevice_->name.c_str());
- connect(*sPreferredDevice_);
+ if (sPairedWith_) {
+ ESP_LOGI(kTag, "connecting to paired device '%s'",
+ sPairedWith_->name.c_str());
+ connect(*sPairedWith_);
} else {
- ESP_LOGI(kTag, "scanning for devices");
transit<Idle>();
}
}
void Idle::entry() {
ESP_LOGI(kTag, "bt is idle");
- sScanner_->ScanContinuously();
+ std::invoke(sEventHandler_, SimpleEvent::kConnectionStateChanged);
}
void Idle::exit() {
- sScanner_->StopScanning();
+ std::invoke(sEventHandler_, SimpleEvent::kConnectionStateChanged);
}
void Idle::react(const events::Disable& ev) {
transit<Disabled>();
}
-void Idle::react(const events::PreferredDeviceChanged& ev) {
- bool is_discovered = false;
- if (sPreferredDevice_ && sDevices_.contains(sPreferredDevice_->mac)) {
- is_discovered = true;
- }
- if (is_discovered) {
- connect(*sPreferredDevice_);
+void Idle::react(const events::PairedDeviceChanged& ev) {
+ if (sPairedWith_) {
+ connect(*sPairedWith_);
}
}
@@ -604,36 +641,32 @@ void Connecting::entry() {
sTimeoutTimer = xTimerCreate("bt_timeout", pdMS_TO_TICKS(15000), false, NULL,
timeoutCallback);
xTimerStart(sTimeoutTimer, portMAX_DELAY);
-
- sScanner_->StopScanning();
- if (sEventHandler_) {
- std::invoke(sEventHandler_, SimpleEvent::kConnectionStateChanged);
- }
}
void Connecting::exit() {
xTimerDelete(sTimeoutTimer, portMAX_DELAY);
-
- if (sEventHandler_) {
- std::invoke(sEventHandler_, SimpleEvent::kConnectionStateChanged);
- }
}
void Connecting::react(const events::ConnectTimedOut& ev) {
ESP_LOGI(kTag, "timed out awaiting connection");
- esp_a2d_source_disconnect(sConnectingDevice_->mac.data());
- if (!connect(*sConnectingDevice_)) {
+ esp_a2d_source_disconnect(sConnectingTo_->mac.data());
+ if (!connect(*sConnectingTo_)) {
transit<Idle>();
}
}
void Connecting::react(const events::Disable& ev) {
- // TODO: disconnect gracefully
+ esp_a2d_source_disconnect(sConnectingTo_->mac.data());
transit<Disabled>();
}
-void Connecting::react(const events::PreferredDeviceChanged& ev) {
- // TODO. Cancel out and start again.
+void Connecting::react(const events::PairedDeviceChanged& ev) {
+ esp_a2d_source_disconnect(sConnectingTo_->mac.data());
+ if (sPairedWith_) {
+ connect(*sPairedWith_);
+ } else {
+ transit<Idle>();
+ }
}
void Connecting::react(events::internal::Gap ev) {
@@ -704,29 +737,41 @@ void Connected::entry() {
ESP_LOGI(kTag, "entering connected state");
transaction_num_ = 0;
- connected_to_ = sConnectingDevice_->mac;
- sPreferredDevice_ = sConnectingDevice_;
- sConnectingDevice_ = {};
+ connected_to_ = sConnectingTo_->mac;
+ sPairedWith_ = sConnectingTo_;
+
+ sStorage_->BluetoothName(sConnectingTo_->mac, sConnectingTo_->name);
+ std::invoke(sEventHandler_, SimpleEvent::kKnownDevicesChanged);
+
+ sConnectingTo_ = {};
auto stored_pref = sStorage_->PreferredBluetoothDevice();
- if (!stored_pref || (sPreferredDevice_->name != stored_pref->name ||
- sPreferredDevice_->mac != stored_pref->mac)) {
- sStorage_->PreferredBluetoothDevice(sPreferredDevice_);
+ if (!stored_pref || (sPairedWith_->name != stored_pref->name ||
+ sPairedWith_->mac != stored_pref->mac)) {
+ sStorage_->PreferredBluetoothDevice(sPairedWith_);
}
+
+ std::invoke(sEventHandler_, SimpleEvent::kConnectionStateChanged);
+
+ // TODO: if we already have a source, immediately start playing
}
void Connected::exit() {
ESP_LOGI(kTag, "exiting connected state");
esp_a2d_source_disconnect(connected_to_.data());
+
+ std::invoke(sEventHandler_, SimpleEvent::kConnectionStateChanged);
}
void Connected::react(const events::Disable& ev) {
transit<Disabled>();
}
-void Connected::react(const events::PreferredDeviceChanged& ev) {
- sConnectingDevice_ = sPreferredDevice_;
- transit<Connecting>();
+void Connected::react(const events::PairedDeviceChanged& ev) {
+ transit<Idle>();
+ if (sPairedWith_) {
+ connect(*sPairedWith_);
+ }
}
void Connected::react(const events::SourcesChanged& ev) {
diff --git a/src/drivers/display.cpp b/src/drivers/display.cpp
index efc9df93..7321f20b 100644
--- a/src/drivers/display.cpp
+++ b/src/drivers/display.cpp
@@ -242,6 +242,7 @@ void Display::SendInitialisationSequence(const uint8_t* data) {
spi_device_release_bus(handle_);
}
+IRAM_ATTR
void Display::SendCommandWithData(uint8_t command,
const uint8_t* data,
size_t length) {
@@ -249,17 +250,20 @@ void Display::SendCommandWithData(uint8_t command,
SendData(data, length);
}
+IRAM_ATTR
void Display::SendCmd(const uint8_t* data, size_t length) {
SendTransaction(COMMAND, data, length);
}
+IRAM_ATTR
void Display::SendData(const uint8_t* data, size_t length) {
SendTransaction(DATA, data, length);
}
+IRAM_ATTR
void Display::SendTransaction(TransactionType type,
- const uint8_t* data,
- size_t length) {
+ const uint8_t* data,
+ size_t length) {
// TODO(jacqueline): What's sending this?
if (length == 0) {
return;
@@ -290,6 +294,7 @@ void Display::SendTransaction(TransactionType type,
ESP_ERROR_CHECK(spi_device_transmit(handle_, &sTransaction));
}
+IRAM_ATTR
void Display::OnLvglFlush(const lv_area_t* area, uint8_t* color_map) {
// Swap the pixel byte order first, since we don't want to do this whilst
// holding the SPI bus lock.
diff --git a/src/drivers/i2s_dac.cpp b/src/drivers/i2s_dac.cpp
index 4e2e171a..46bf8e80 100644
--- a/src/drivers/i2s_dac.cpp
+++ b/src/drivers/i2s_dac.cpp
@@ -46,12 +46,12 @@ extern "C" IRAM_ATTR auto callback(i2s_chan_handle_t handle,
if (event == nullptr || user_ctx == nullptr) {
return false;
}
- if (event->data == nullptr || event->size == 0) {
+ if (event->dma_buf == nullptr || event->size == 0) {
return false;
}
assert(event->size % 4 == 0);
- uint8_t* buf = *reinterpret_cast<uint8_t**>(event->data);
+ uint8_t* buf = reinterpret_cast<uint8_t*>(event->dma_buf);
auto* src = reinterpret_cast<OutputBuffers*>(user_ctx);
BaseType_t ret1 = src->first.receive(
diff --git a/src/drivers/include/drivers/bluetooth.hpp b/src/drivers/include/drivers/bluetooth.hpp
index eaecfb2b..99c71e52 100644
--- a/src/drivers/include/drivers/bluetooth.hpp
+++ b/src/drivers/include/drivers/bluetooth.hpp
@@ -3,6 +3,7 @@
#include <array>
#include <atomic>
+#include <functional>
#include <map>
#include <mutex>
#include <optional>
@@ -25,28 +26,68 @@
namespace drivers {
/*
- * A handle used to interact with the bluetooth state machine.
+ * A handle used to interact with the bluetooth state machine. This is the main
+ * API that the rest of the system should use to interact with Bluetooth.
*/
class Bluetooth {
public:
- Bluetooth(NvsStorage& storage, tasks::WorkerPool&);
+ /*
+ * Callback invoked when an event is generated by the Bluetooth stack. This
+ * callback is invoked synchronously from a Bluetooth task context, so
+ * implementations should immediately hop to a different task to process the
+ * event.
+ */
+ using EventHandler = std::function<void(bluetooth::Event)>;
+
+ Bluetooth(NvsStorage&, tasks::WorkerPool&, EventHandler);
+
+ /* Enables or disables the entire Bluetooth stack. */
+ auto enable(bool en) -> void;
+ auto enabled() -> bool;
+
+ auto sources(OutputBuffers*) -> void;
+ auto softVolume(float) -> void;
+
+ enum class ConnectionState {
+ kConnected,
+ kConnecting,
+ kDisconnected,
+ };
+
+ auto connectionState() -> ConnectionState;
+
+ /*
+ * The 'paired' device is a device that will be preferred for connections.
+ * When Bluetooth is first enabled, we immediately try to connect to the
+ * paired device. If the paired device is seen during a scan, then we will
+ * also automatically connect to it.
+ */
+ auto pairedDevice() -> std::optional<bluetooth::MacAndName>;
+
+ /*
+ * Sets the preferred device. If a device is provided, a connection will be
+ * attempted immediately, even if the device has not been detected in a
+ * previous scan.
+ */
+ auto pairedDevice(std::optional<bluetooth::MacAndName> dev) -> void;
+
+ /* A list of devices that have previously been the paired device. */
+ auto knownDevices() -> std::vector<bluetooth::MacAndName>;
+ auto forgetKnownDevice(const bluetooth::mac_addr_t&) -> void;
+
+ /* Enables or disables scanning for nearby Bluetooth devices. */
+ auto discoveryEnabled(bool) -> void;
+ auto discoveryEnabled() -> bool;
+
+ /*
+ * A list of nearby devices that have been discovered since discovery was
+ * last enabled. This list may include the paired device, as well as devices
+ * that are also present in the known devices list.
+ */
+ auto discoveredDevices() -> std::vector<bluetooth::MacAndName>;
- auto Enable() -> bool;
- auto Disable() -> void;
- auto IsEnabled() -> bool;
-
- auto IsConnected() -> bool;
- auto ConnectedDevice() -> std::optional<bluetooth::MacAndName>;
-
- auto KnownDevices() -> std::vector<bluetooth::Device>;
-
- auto SetPreferredDevice(std::optional<bluetooth::MacAndName> dev) -> void;
- auto PreferredDevice() -> std::optional<bluetooth::MacAndName>;
-
- auto SetSources(OutputBuffers*) -> void;
- auto SetVolumeFactor(float) -> void;
-
- auto SetEventHandler(std::function<void(bluetooth::Event)> cb) -> void;
+ private:
+ NvsStorage& nvs_;
};
namespace bluetooth {
@@ -56,7 +97,7 @@ struct Enable : public tinyfsm::Event {};
struct Disable : public tinyfsm::Event {};
struct ConnectTimedOut : public tinyfsm::Event {};
-struct PreferredDeviceChanged : public tinyfsm::Event {};
+struct PairedDeviceChanged : public tinyfsm::Event {};
struct SourcesChanged : public tinyfsm::Event {};
struct DeviceDiscovered : public tinyfsm::Event {
const Device& device;
@@ -94,6 +135,8 @@ class Scanner {
auto StopScanning() -> void;
auto StopScanningNow() -> void;
+ auto enabled() -> bool;
+
auto HandleGapEvent(const events::internal::Gap&) -> void;
private:
@@ -103,25 +146,22 @@ class Scanner {
auto HandleDeviceDiscovery(const esp_bt_gap_cb_param_t& param) -> void;
};
+/*
+ * The main state machine for managing the state of the Bluetooth stack, and
+ * the current (if any) Bluetooth connection.
+ */
class BluetoothState : public tinyfsm::Fsm<BluetoothState> {
public:
- static auto Init(NvsStorage& storage) -> void;
+ static auto Init(NvsStorage& storage, Bluetooth::EventHandler) -> void;
static auto lock() -> std::lock_guard<std::mutex>;
- static auto devices() -> std::vector<Device>;
-
- static auto preferred_device() -> std::optional<bluetooth::MacAndName>;
- static auto preferred_device(std::optional<bluetooth::MacAndName>) -> void;
+ static auto pairedDevice() -> std::optional<bluetooth::MacAndName>;
+ static auto pairedDevice(std::optional<bluetooth::MacAndName>) -> void;
- static auto scanning() -> bool;
static auto discovery() -> bool;
static auto discovery(bool) -> void;
-
- static auto sources() -> OutputBuffers*;
- static auto sources(OutputBuffers*) -> void;
-
- static auto event_handler(std::function<void(Event)>) -> void;
+ static auto discoveredDevices() -> std::vector<Device>;
virtual ~BluetoothState(){};
@@ -131,7 +171,7 @@ class BluetoothState : public tinyfsm::Fsm<BluetoothState> {
virtual void react(const events::Enable& ev){};
virtual void react(const events::Disable& ev) = 0;
virtual void react(const events::ConnectTimedOut& ev){};
- virtual void react(const events::PreferredDeviceChanged& ev){};
+ virtual void react(const events::PairedDeviceChanged& ev){};
virtual void react(const events::SourcesChanged& ev){};
virtual void react(const events::DeviceDiscovered&);
@@ -146,10 +186,9 @@ class BluetoothState : public tinyfsm::Fsm<BluetoothState> {
static Scanner* sScanner_;
static std::mutex sFsmMutex;
- static std::map<mac_addr_t, Device> sDevices_;
- static std::optional<bluetooth::MacAndName> sPreferredDevice_;
-
- static std::optional<bluetooth::MacAndName> sConnectingDevice_;
+ static std::map<mac_addr_t, Device> sDiscoveredDevices_;
+ static std::optional<bluetooth::MacAndName> sPairedWith_;
+ static std::optional<bluetooth::MacAndName> sConnectingTo_;
static int sConnectAttemptsRemaining_;
static std::function<void(Event)> sEventHandler_;
@@ -176,7 +215,7 @@ class Idle : public BluetoothState {
void exit() override;
void react(const events::Disable& ev) override;
- void react(const events::PreferredDeviceChanged& ev) override;
+ void react(const events::PairedDeviceChanged& ev) override;
void react(events::internal::Gap ev) override;
@@ -188,7 +227,7 @@ class Connecting : public BluetoothState {
void entry() override;
void exit() override;
- void react(const events::PreferredDeviceChanged& ev) override;
+ void react(const events::PairedDeviceChanged& ev) override;
void react(const events::ConnectTimedOut& ev) override;
void react(const events::Disable& ev) override;
@@ -203,7 +242,7 @@ class Connected : public BluetoothState {
void entry() override;
void exit() override;
- void react(const events::PreferredDeviceChanged& ev) override;
+ void react(const events::PairedDeviceChanged& ev) override;
void react(const events::SourcesChanged& ev) override;
void react(const events::Disable& ev) override;
diff --git a/src/drivers/include/drivers/bluetooth_types.hpp b/src/drivers/include/drivers/bluetooth_types.hpp
index d2e55ee5..05caee47 100644
--- a/src/drivers/include/drivers/bluetooth_types.hpp
+++ b/src/drivers/include/drivers/bluetooth_types.hpp
@@ -27,9 +27,12 @@ struct Device {
};
enum class SimpleEvent {
- kKnownDevicesChanged,
kConnectionStateChanged,
- kPreferredDeviceChanged,
+ kPairedDeviceChanged,
+ kKnownDevicesChanged,
+ kDiscoveryChanged,
+ kDeviceDiscovered,
+
// Passthrough events
kPlayPause,
kStop,
diff --git a/src/drivers/include/drivers/nvs.hpp b/src/drivers/include/drivers/nvs.hpp
index 88dd5ae0..e147c8c7 100644
--- a/src/drivers/include/drivers/nvs.hpp
+++ b/src/drivers/include/drivers/nvs.hpp
@@ -34,7 +34,7 @@ class Setting {
dirty_ = true;
}
}
- auto get() -> std::optional<T>& { return val_; }
+ auto get() -> std::optional<T> { return val_; }
/* Reads the stored value from NVS and parses it into the correct type. */
auto load(nvs_handle_t) -> std::optional<T>;
@@ -90,12 +90,19 @@ class NvsStorage {
auto LraCalibration() -> std::optional<LraData>;
auto LraCalibration(const LraData&) -> void;
+ auto FastCharge() -> bool;
+ auto FastCharge(bool) -> void;
+
auto PreferredBluetoothDevice() -> std::optional<bluetooth::MacAndName>;
auto PreferredBluetoothDevice(std::optional<bluetooth::MacAndName>) -> void;
auto BluetoothVolume(const bluetooth::mac_addr_t&) -> uint8_t;
auto BluetoothVolume(const bluetooth::mac_addr_t&, uint8_t) -> void;
+ auto BluetoothNames() -> std::vector<bluetooth::MacAndName>;
+ auto BluetoothName(const bluetooth::mac_addr_t&, std::optional<std::string>)
+ -> void;
+
enum class Output : uint8_t {
kHeadphones = 0,
kBluetooth = 1,
@@ -106,6 +113,9 @@ class NvsStorage {
auto ScreenBrightness() -> uint_fast8_t;
auto ScreenBrightness(uint_fast8_t) -> void;
+ auto InterfaceTheme() -> std::optional<std::string>;
+ auto InterfaceTheme(std::string) -> void;
+
auto ScrollSensitivity() -> uint_fast8_t;
auto ScrollSensitivity(uint_fast8_t) -> void;
@@ -146,6 +156,7 @@ class NvsStorage {
Setting<uint16_t> display_rows_;
Setting<uint8_t> haptic_motor_type_;
Setting<LraData> lra_calibration_;
+ Setting<uint8_t> fast_charge_;
Setting<uint8_t> brightness_;
Setting<uint8_t> sensitivity_;
@@ -154,7 +165,12 @@ class NvsStorage {
Setting<int8_t> amp_left_bias_;
Setting<uint8_t> input_mode_;
Setting<uint8_t> output_mode_;
+
+ Setting<std::string> theme_;
+
Setting<bluetooth::MacAndName> bt_preferred_;
+ Setting<std::vector<bluetooth::MacAndName>> bt_names_;
+
Setting<uint8_t> db_auto_index_;
util::LruCache<10, bluetooth::mac_addr_t, uint8_t> bt_volumes_;
diff --git a/src/drivers/include/drivers/pcm_buffer.hpp b/src/drivers/include/drivers/pcm_buffer.hpp
index 968c3398..4e5fa041 100644
--- a/src/drivers/include/drivers/pcm_buffer.hpp
+++ b/src/drivers/include/drivers/pcm_buffer.hpp
@@ -28,8 +28,12 @@ class PcmBuffer {
PcmBuffer(size_t size_in_samples);
~PcmBuffer();
- /* Adds samples to the buffer. */
- auto send(std::span<const int16_t>) -> void;
+ /*
+ * Adds samples to the buffer. Returns the number of samples that were added,
+ * which may be less than the number of samples given if this PcmBuffer is
+ * close to full.
+ */
+ auto send(std::span<const int16_t>) -> size_t;
/*
* Fills the given span with samples. If enough samples are available in
diff --git a/src/drivers/include/drivers/samd.hpp b/src/drivers/include/drivers/samd.hpp
index 897e78d6..ff479225 100644
--- a/src/drivers/include/drivers/samd.hpp
+++ b/src/drivers/include/drivers/samd.hpp
@@ -10,6 +10,7 @@
#include <optional>
#include <string>
+#include "drivers/nvs.hpp"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
@@ -17,9 +18,7 @@ namespace drivers {
class Samd {
public:
- static auto Create() -> Samd* { return new Samd(); }
-
- Samd();
+ Samd(NvsStorage& nvs);
~Samd();
auto Version() -> std::string;
@@ -37,8 +36,14 @@ class Samd {
kChargingFast,
// The battery is full charged, and we are still plugged in.
kFullCharge,
+ // Charging failed.
+ kFault,
+ // The battery status returned isn't a known enum value.
+ kUnknown,
};
+ static auto chargeStatusToString(ChargeStatus) -> std::string;
+
auto GetChargeStatus() -> std::optional<ChargeStatus>;
auto UpdateChargeStatus() -> void;
@@ -68,6 +73,8 @@ class Samd {
Samd& operator=(const Samd&) = delete;
private:
+ NvsStorage& nvs_;
+
uint8_t version_;
std::optional<ChargeStatus> charge_status_;
UsbStatus usb_status_;
diff --git a/src/drivers/include/drivers/touchwheel.hpp b/src/drivers/include/drivers/touchwheel.hpp
index 60902087..9cd925a6 100644
--- a/src/drivers/include/drivers/touchwheel.hpp
+++ b/src/drivers/include/drivers/touchwheel.hpp
@@ -39,7 +39,8 @@ class TouchWheel {
auto Update() -> void;
auto GetTouchWheelData() const -> TouchWheelData;
- auto PowerDown() -> void;
+ auto Recalibrate() -> void;
+ auto LowPowerMode(bool en) -> void;
private:
TouchWheelData data_;
diff --git a/src/drivers/nvs.cpp b/src/drivers/nvs.cpp
index 5c7d2218..d004201b 100644
--- a/src/drivers/nvs.cpp
+++ b/src/drivers/nvs.cpp
@@ -26,8 +26,10 @@ static constexpr uint8_t kSchemaVersion = 1;
static constexpr char kKeyVersion[] = "ver";
static constexpr char kKeyBluetoothPreferred[] = "bt_dev";
static constexpr char kKeyBluetoothVolumes[] = "bt_vols";
+static constexpr char kKeyBluetoothNames[] = "bt_names";
static constexpr char kKeyOutput[] = "out";
static constexpr char kKeyBrightness[] = "bright";
+static constexpr char kKeyInterfaceTheme[] = "ui_theme";
static constexpr char kKeyAmpMaxVolume[] = "hp_vol_max";
static constexpr char kKeyAmpCurrentVolume[] = "hp_vol";
static constexpr char kKeyAmpLeftBias[] = "hp_bias";
@@ -39,6 +41,7 @@ static constexpr char kKeyDisplayRows[] = "disprows";
static constexpr char kKeyHapticMotorType[] = "hapticmtype";
static constexpr char kKeyLraCalibration[] = "lra_cali";
static constexpr char kKeyDbAutoIndex[] = "dbautoindex";
+static constexpr char kKeyFastCharge[] = "fastchg";
static auto nvs_get_string(nvs_handle_t nvs, const char* key)
-> std::optional<std::string> {
@@ -130,6 +133,69 @@ auto Setting<bluetooth::MacAndName>::store(nvs_handle_t nvs,
}
template <>
+auto Setting<std::vector<bluetooth::MacAndName>>::load(nvs_handle_t nvs)
+ -> std::optional<std::vector<bluetooth::MacAndName>> {
+ auto raw = nvs_get_string(nvs, name_);
+ if (!raw) {
+ return {};
+ }
+ auto [parsed, unused, err] = cppbor::parseWithViews(
+ reinterpret_cast<const uint8_t*>(raw->data()), raw->size());
+ if (parsed->type() != cppbor::MAP) {
+ return {};
+ }
+ std::vector<bluetooth::MacAndName> res;
+ for (const auto& i : *parsed->asMap()) {
+ auto mac = i.first->asViewBstr()->view();
+ auto name = i.second->asViewTstr()->view();
+ bluetooth::MacAndName entry{
+ .mac = {},
+ .name = {name.begin(), name.end()},
+ };
+ std::copy(mac.begin(), mac.end(), entry.mac.begin());
+ res.push_back(entry);
+ }
+ return res;
+}
+
+template <>
+auto Setting<std::vector<bluetooth::MacAndName>>::store(
+ nvs_handle_t nvs,
+ std::vector<bluetooth::MacAndName> v) -> void {
+ cppbor::Map cbor{};
+ for (const auto& i : v) {
+ cbor.add(cppbor::Bstr{{i.mac.data(), i.mac.size()}}, cppbor::Tstr{i.name});
+ }
+ auto encoded = cbor.encode();
+ nvs_set_blob(nvs, name_, encoded.data(), encoded.size());
+}
+
+template <>
+auto Setting<std::string>::store(
+ nvs_handle_t nvs,
+ std::string v) -> void {
+ cppbor::Tstr cbor{v};
+ auto encoded = cbor.encode();
+ nvs_set_blob(nvs, name_, encoded.data(), encoded.size());
+}
+
+template <>
+auto Setting<std::string>::load(nvs_handle_t nvs)
+ -> std::optional<std::string> {
+ auto raw = nvs_get_string(nvs, name_);
+ if (!raw) {
+ return {};
+ }
+ auto [parsed, unused, err] = cppbor::parseWithViews(
+ reinterpret_cast<const uint8_t*>(raw->data()), raw->size());
+ if (parsed->type() != cppbor::TSTR) {
+ return {};
+ }
+ auto v = parsed->asViewTstr()->view();
+ return std::string{v.begin(), v.end()};
+}
+
+template <>
auto Setting<NvsStorage::LraData>::load(nvs_handle_t nvs)
-> std::optional<NvsStorage::LraData> {
auto raw = nvs_get_string(nvs, name_);
@@ -200,6 +266,7 @@ NvsStorage::NvsStorage(nvs_handle_t handle)
display_rows_(kKeyDisplayRows),
haptic_motor_type_(kKeyHapticMotorType),
lra_calibration_(kKeyLraCalibration),
+ fast_charge_(kKeyFastCharge),
brightness_(kKeyBrightness),
sensitivity_(kKeyScrollSensitivity),
amp_max_vol_(kKeyAmpMaxVolume),
@@ -207,7 +274,9 @@ NvsStorage::NvsStorage(nvs_handle_t handle)
amp_left_bias_(kKeyAmpLeftBias),
input_mode_(kKeyPrimaryInput),
output_mode_(kKeyOutput),
+ theme_{kKeyInterfaceTheme},
bt_preferred_(kKeyBluetoothPreferred),
+ bt_names_(kKeyBluetoothNames),
db_auto_index_(kKeyDbAutoIndex),
bt_volumes_(),
bt_volumes_dirty_(false) {}
@@ -231,7 +300,9 @@ auto NvsStorage::Read() -> void {
amp_left_bias_.read(handle_);
input_mode_.read(handle_);
output_mode_.read(handle_);
+ theme_.read(handle_);
bt_preferred_.read(handle_);
+ bt_names_.read(handle_);
db_auto_index_.read(handle_);
readBtVolumes();
}
@@ -250,7 +321,9 @@ auto NvsStorage::Write() -> bool {
amp_left_bias_.write(handle_);
input_mode_.write(handle_);
output_mode_.write(handle_);
+ theme_.write(handle_);
bt_preferred_.write(handle_);
+ bt_names_.write(handle_);
db_auto_index_.write(handle_);
writeBtVolumes();
return nvs_commit(handle_) == ESP_OK;
@@ -341,6 +414,47 @@ auto NvsStorage::BluetoothVolume(const bluetooth::mac_addr_t& mac, uint8_t vol)
bt_volumes_.Put(mac, vol);
}
+auto NvsStorage::BluetoothNames() -> std::vector<bluetooth::MacAndName> {
+ std::lock_guard<std::mutex> lock{mutex_};
+ return bt_names_.get().value_or(std::vector<bluetooth::MacAndName>{});
+}
+
+auto NvsStorage::BluetoothName(const bluetooth::mac_addr_t& mac,
+ std::optional<std::string> name) -> void {
+ std::lock_guard<std::mutex> lock{mutex_};
+ auto val = bt_names_.get();
+ if (!val) {
+ val.emplace();
+ }
+
+ bool mut = false;
+ bool found = false;
+ for (auto it = val->begin(); it != val->end(); it++) {
+ if (it->mac == mac) {
+ if (name) {
+ it->name = *name;
+ } else {
+ val->erase(it);
+ }
+ found = true;
+ mut = true;
+ break;
+ }
+ }
+
+ if (!found && name) {
+ val->push_back(bluetooth::MacAndName{
+ .mac = mac,
+ .name = *name,
+ });
+ mut = true;
+ }
+
+ if (mut) {
+ bt_names_.set(*val);
+ }
+}
+
auto NvsStorage::OutputMode() -> Output {
std::lock_guard<std::mutex> lock{mutex_};
switch (output_mode_.get().value_or(0xFF)) {
@@ -361,6 +475,16 @@ auto NvsStorage::OutputMode(Output out) -> void {
nvs_commit(handle_);
}
+auto NvsStorage::FastCharge() -> bool {
+ std::lock_guard<std::mutex> lock{mutex_};
+ return fast_charge_.get().value_or(true);
+}
+
+auto NvsStorage::FastCharge(bool en) -> void {
+ std::lock_guard<std::mutex> lock{mutex_};
+ fast_charge_.set(en);
+}
+
auto NvsStorage::ScreenBrightness() -> uint_fast8_t {
std::lock_guard<std::mutex> lock{mutex_};
return std::clamp<uint8_t>(brightness_.get().value_or(50), 0, 100);
@@ -371,6 +495,16 @@ auto NvsStorage::ScreenBrightness(uint_fast8_t val) -> void {
brightness_.set(val);
}
+auto NvsStorage::InterfaceTheme() -> std::optional<std::string> {
+ std::lock_guard<std::mutex> lock{mutex_};
+ return theme_.get();
+}
+
+auto NvsStorage::InterfaceTheme(std::string themeFile) -> void {
+ std::lock_guard<std::mutex> lock{mutex_};
+ theme_.set(themeFile);
+}
+
auto NvsStorage::ScrollSensitivity() -> uint_fast8_t {
std::lock_guard<std::mutex> lock{mutex_};
return std::clamp<uint8_t>(sensitivity_.get().value_or(128), 0, 255);
diff --git a/src/drivers/pcm_buffer.cpp b/src/drivers/pcm_buffer.cpp
index 1d2bab1e..1e416301 100644
--- a/src/drivers/pcm_buffer.cpp
+++ b/src/drivers/pcm_buffer.cpp
@@ -17,6 +17,7 @@
#include "freertos/FreeRTOS.h"
#include "esp_heap_caps.h"
+#include "freertos/projdefs.h"
#include "freertos/ringbuf.h"
#include "portmacro.h"
@@ -39,9 +40,13 @@ PcmBuffer::~PcmBuffer() {
heap_caps_free(buf_);
}
-auto PcmBuffer::send(std::span<const int16_t> data) -> void {
- xRingbufferSend(ringbuf_, data.data(), data.size_bytes(), portMAX_DELAY);
+auto PcmBuffer::send(std::span<const int16_t> data) -> size_t {
+ if (!xRingbufferSend(ringbuf_, data.data(), data.size_bytes(),
+ pdMS_TO_TICKS(100))) {
+ return 0;
+ }
sent_ += data.size();
+ return data.size();
}
IRAM_ATTR auto PcmBuffer::receive(std::span<int16_t> dest, bool mix, bool isr)
@@ -67,10 +72,12 @@ IRAM_ATTR auto PcmBuffer::receive(std::span<int16_t> dest, bool mix, bool isr)
auto PcmBuffer::clear() -> void {
while (!isEmpty()) {
- size_t bytes_cleared;
+ size_t bytes_cleared = 0;
void* data = xRingbufferReceive(ringbuf_, &bytes_cleared, 0);
- vRingbufferReturnItem(ringbuf_, data);
- received_ += bytes_cleared / sizeof(int16_t);
+ if (data) {
+ vRingbufferReturnItem(ringbuf_, data);
+ received_ += bytes_cleared / sizeof(int16_t);
+ }
}
}
diff --git a/src/drivers/samd.cpp b/src/drivers/samd.cpp
index e4aa73ad..c2308760 100644
--- a/src/drivers/samd.cpp
+++ b/src/drivers/samd.cpp
@@ -5,11 +5,13 @@
*/
#include "drivers/samd.hpp"
+#include <stdint.h>
#include <cstdint>
#include <optional>
#include <string>
+#include "drivers/nvs.hpp"
#include "esp_err.h"
#include "esp_log.h"
#include "hal/gpio_types.h"
@@ -32,7 +34,29 @@ namespace drivers {
static constexpr gpio_num_t kIntPin = GPIO_NUM_35;
-Samd::Samd() {
+auto Samd::chargeStatusToString(ChargeStatus status) -> std::string {
+ switch (status) {
+ case ChargeStatus::kNoBattery:
+ return "no_battery";
+ case ChargeStatus::kBatteryCritical:
+ return "critical";
+ case ChargeStatus::kDischarging:
+ return "discharging";
+ case ChargeStatus::kChargingRegular:
+ return "charge_regular";
+ case ChargeStatus::kChargingFast:
+ return "charge_fast";
+ case ChargeStatus::kFullCharge:
+ return "full_charge";
+ case ChargeStatus::kFault:
+ return "fault";
+ case ChargeStatus::kUnknown:
+ default:
+ return "unknown";
+ }
+}
+
+Samd::Samd(NvsStorage& nvs) : nvs_(nvs) {
gpio_set_direction(kIntPin, GPIO_MODE_INPUT);
// Being able to interface with the SAMD properly is critical. To ensure we
@@ -51,7 +75,7 @@ Samd::Samd() {
UpdateChargeStatus();
UpdateUsbStatus();
- SetFastChargeEnabled(true);
+ SetFastChargeEnabled(nvs.FastCharge());
}
Samd::~Samd() {}
@@ -78,16 +102,38 @@ auto Samd::UpdateChargeStatus() -> void {
return;
}
- // FIXME: Ideally we should be using the three 'charge status' bits to work
- // out whether we're actually charging, or if we've got a full charge,
- // critically low charge, etc.
+ // Lower two bits are the usb power status, next three are the BMS status.
+ // See 'gpio.c' in the SAMD21 firmware for how these bits get packed.
+ uint8_t charge_state = (raw_res & 0b11100) >> 2;
uint8_t usb_state = raw_res & 0b11;
- if (usb_state == 0) {
- charge_status_ = ChargeStatus::kDischarging;
- } else if (usb_state == 1) {
- charge_status_ = ChargeStatus::kChargingRegular;
- } else {
- charge_status_ = ChargeStatus::kChargingFast;
+ switch (charge_state) {
+ case 0b000:
+ charge_status_ = ChargeStatus::kNoBattery;
+ break;
+ case 0b001:
+ // BMS says we're charging; work out how fast we're charging.
+ if (usb_state >= 0b10 && nvs_.FastCharge()) {
+ charge_status_ = ChargeStatus::kChargingFast;
+ } else {
+ charge_status_ = ChargeStatus::kChargingRegular;
+ }
+ break;
+ case 0b010:
+ charge_status_ = ChargeStatus::kFullCharge;
+ break;
+ case 0b011:
+ charge_status_ = ChargeStatus::kFault;
+ break;
+ case 0b100:
+ charge_status_ = ChargeStatus::kBatteryCritical;
+ break;
+ case 0b101:
+ charge_status_ = ChargeStatus::kDischarging;
+ break;
+ case 0b110:
+ case 0b111:
+ charge_status_ = ChargeStatus::kUnknown;
+ break;
}
}
@@ -127,9 +173,15 @@ auto Samd::ResetToFlashSamd() -> void {
}
auto Samd::SetFastChargeEnabled(bool en) -> void {
+ // Always update NVS, so that the setting is right after the SAMD firmware is
+ // updated.
+ nvs_.FastCharge(en);
+
if (version_ < 4) {
return;
}
+ ESP_LOGI(kTag, "set fast charge %u", en);
+
I2CTransaction transaction;
transaction.start()
.write_addr(kAddress, I2C_MASTER_WRITE)
diff --git a/src/drivers/spi.cpp b/src/drivers/spi.cpp
index 632fe89f..40487197 100644
--- a/src/drivers/spi.cpp
+++ b/src/drivers/spi.cpp
@@ -41,7 +41,7 @@ esp_err_t init_spi(void) {
// manages its own use of DMA-capable memory.
.max_transfer_sz = 4096,
.flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_IOMUX_PINS,
- .isr_cpu_id = ESP_INTR_CPU_AFFINITY_0,
+ .isr_cpu_id = ESP_INTR_CPU_AFFINITY_1,
.intr_flags = ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM,
};
diff --git a/src/drivers/test/test_samd.cpp b/src/drivers/test/test_samd.cpp
index c466d88e..96248377 100644
--- a/src/drivers/test/test_samd.cpp
+++ b/src/drivers/test/test_samd.cpp
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-only
*/
+#include "drivers/nvs.hpp"
#include "drivers/samd.hpp"
#include <cstdint>
@@ -16,7 +17,8 @@ namespace drivers {
TEST_CASE("samd21 interface", "[integration]") {
I2CFixture i2c;
- auto samd = std::make_unique<Samd>();
+ std::unique_ptr<drivers::NvsStorage> nvs{drivers::NvsStorage::OpenSync()};
+ auto samd = std::make_unique<Samd>(*nvs);
REQUIRE(samd);
diff --git a/src/drivers/touchwheel.cpp b/src/drivers/touchwheel.cpp
index 5d55c6f2..402b839d 100644
--- a/src/drivers/touchwheel.cpp
+++ b/src/drivers/touchwheel.cpp
@@ -137,8 +137,12 @@ TouchWheelData TouchWheel::GetTouchWheelData() const {
return data_;
}
-auto TouchWheel::PowerDown() -> void {
- WriteRegister(LOW_POWER, 0);
+auto TouchWheel::Recalibrate() -> void {
+ WriteRegister(CALIBRATE, 1);
+}
+
+auto TouchWheel::LowPowerMode(bool en) -> void {
+ WriteRegister(LOW_POWER, en ? 0 : 1);
}
} // namespace drivers