Add a settings screen with power+battery info

Mostly for debugging, but also u can toggle fast charging off and on now
custom
jacqueline 9 months ago
parent eb5d0d50cd
commit 0cc7536684
  1. 56
      lua/settings.lua
  2. 16
      lua/widgets.lua
  3. 2
      luals-stubs/power.lua
  4. 4
      src/drivers/include/drivers/nvs.hpp
  5. 13
      src/drivers/include/drivers/samd.hpp
  6. 12
      src/drivers/nvs.cpp
  7. 74
      src/drivers/samd.cpp
  8. 21
      src/tangara/app_console/app_console.cpp
  9. 2
      src/tangara/battery/battery.cpp
  10. 1
      src/tangara/battery/battery.hpp
  11. 6
      src/tangara/system_fsm/booting.cpp
  12. 34
      src/tangara/ui/ui_fsm.cpp
  13. 2
      src/tangara/ui/ui_fsm.hpp

@ -485,6 +485,59 @@ local DatabaseSettings = SettingsScreen:new {
end
}
local PowerSettings = SettingsScreen:new {
title = "Power",
createUi = function(self)
SettingsScreen.createUi(self)
local power = require("power")
local charge_pct = widgets.Row(self.content, "Charge").right
local charge_volts = widgets.Row(self.content, "Voltage").right
local charge_state = widgets.Row(self.content, "Status").right
self.bindings = self.bindings + {
power.battery_pct:bind(function(pct)
charge_pct:set { text = string.format("%d%%", pct) }
end),
power.battery_millivolts:bind(function(mv)
charge_volts:set { text = string.format("%.2fV", mv / 1000) }
end),
power.charge_state:bind(function(state)
charge_state:set { text = state }
end),
}
local fast_charge_container = self.content:Object {
flex = {
flex_direction = "row",
justify_content = "flex-start",
align_items = "center",
align_content = "flex-start",
},
w = lvgl.PCT(100),
h = lvgl.SIZE_CONTENT,
pad_bottom = 4,
}
fast_charge_container:add_style(styles.list_item)
fast_charge_container:Label { text = "Fast Charging", flex_grow = 1 }
local fast_charge_sw = fast_charge_container:Switch {}
fast_charge_sw:onevent(lvgl.EVENT.VALUE_CHANGED, function()
power.fast_charge:set(fast_charge_sw:enabled())
end)
self.bindings = self.bindings + {
power.fast_charge:bind(function(en)
if en then
fast_charge_sw:add_state(lvgl.STATE.CHECKED)
else
fast_charge_sw:clear_state(lvgl.STATE.CHECKED)
end
end),
}
end
}
local SamdConfirmation = SettingsScreen:new {
title = "Are you sure?",
createUi = function(self)
@ -696,6 +749,9 @@ return widgets.MenuScreen:new {
section("System")
submenu("Database", DatabaseSettings)
submenu("Power", PowerSettings)
section("About")
submenu("Firmware", FirmwareSettings)
submenu("Licenses", LicensesScreen)
submenu("Regulatory", RegulatoryScreen)

@ -58,7 +58,7 @@ widgets.MenuScreen = screen:new {
end
}
function widgets.Row(parent, left, right)
function widgets.Row(parent, left_text, right_text)
local container = parent:Object {
flex = {
flex_direction = "row",
@ -70,12 +70,16 @@ function widgets.Row(parent, left, right)
h = lvgl.SIZE_CONTENT
}
container:add_style(styles.list_item)
container:Label {
text = left,
flex_grow = 1
local left = container:Label {
text = left_text,
flex_grow = 1,
}
local right = container:Label {
text = right_text or "",
}
container:Label {
text = right
return {
left = left,
right = right,
}
end

@ -6,6 +6,8 @@
--- @field battery_pct Property The battery's current charge, as a percentage of the maximum charge.
--- @field battery_millivolts Property The battery's current voltage, in millivolts.
--- @field plugged_in Property Whether or not the device is currently receiving external power.
--- @field fast_charge Property Whether or not fast charging is enabled. Fast charging can fully recharge the battery up to two times faster than regular charging, but will have a small negative impact on the lifetime of the battery.
--- @field charge_state Property a string property describing the current charging state. May be one of "no_battery", "critical", "discharging", "charge_regular", "charge_fast", "full_charge", "fault", or "unknown".
local power = {}
return power

@ -90,6 +90,9 @@ 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;
@ -150,6 +153,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_;

@ -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_;

@ -40,6 +40,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> {
@ -239,6 +240,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),
@ -444,6 +446,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);

@ -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)

@ -465,26 +465,7 @@ int CmdSamd(int argc, char** argv) {
} else if (cmd == "charge") {
auto res = samd.GetChargeStatus();
if (res) {
switch (res.value()) {
case drivers::Samd::ChargeStatus::kNoBattery:
std::cout << "kNoBattery" << std::endl;
break;
case drivers::Samd::ChargeStatus::kBatteryCritical:
std::cout << "kBatteryCritical" << std::endl;
break;
case drivers::Samd::ChargeStatus::kDischarging:
std::cout << "kDischarging" << std::endl;
break;
case drivers::Samd::ChargeStatus::kChargingRegular:
std::cout << "kChargingRegular" << std::endl;
break;
case drivers::Samd::ChargeStatus::kChargingFast:
std::cout << "kChargingFast" << std::endl;
break;
case drivers::Samd::ChargeStatus::kFullCharge:
std::cout << "kFullCharge" << std::endl;
break;
}
std::cout << drivers::Samd::chargeStatusToString(*res) << std::endl;
} else {
std::cout << "unknown" << std::endl;
}

@ -93,6 +93,8 @@ auto Battery::Update() -> void {
.percent = percent,
.millivolts = mV,
.is_charging = is_charging,
.raw_status =
charge_state.value_or(drivers::Samd::ChargeStatus::kUnknown),
};
EmitEvent();
}

@ -27,6 +27,7 @@ class Battery {
uint_fast8_t percent;
uint32_t millivolts;
bool is_charging;
drivers::Samd::ChargeStatus raw_status;
bool operator==(const BatteryState& other) const {
return percent == other.percent && is_charging == other.is_charging;

@ -87,7 +87,7 @@ auto Booting::entry() -> void {
ESP_LOGI(kTag, "installing remaining drivers");
drivers::spiffs_mount();
sServices->samd(std::unique_ptr<drivers::Samd>(drivers::Samd::Create()));
sServices->samd(std::make_unique<drivers::Samd>(sServices->nvs()));
sServices->touchwheel(
std::unique_ptr<drivers::TouchWheel>{drivers::TouchWheel::Create()});
sServices->haptics(std::make_unique<drivers::Haptics>(sServices->nvs()));
@ -96,8 +96,8 @@ auto Booting::entry() -> void {
sServices->battery(std::make_unique<battery::Battery>(
sServices->samd(), std::unique_ptr<drivers::AdcBattery>(adc)));
sServices->track_queue(
std::make_unique<audio::TrackQueue>(sServices->bg_worker(), sServices->database()));
sServices->track_queue(std::make_unique<audio::TrackQueue>(
sServices->bg_worker(), sServices->database()));
sServices->tag_parser(std::make_unique<database::TagParserImpl>());
sServices->collator(locale::CreateCollator());
sServices->tts(std::make_unique<tts::Provider>());

@ -101,6 +101,15 @@ static auto lvgl_delay_cb(uint32_t ms) -> void {
lua::Property UiState::sBatteryPct{0};
lua::Property UiState::sBatteryMv{0};
lua::Property UiState::sBatteryCharging{false};
lua::Property UiState::sPowerChargeState{"unknown"};
lua::Property UiState::sPowerFastChargeEnabled{
false, [](const lua::LuaValue& val) {
if (!std::holds_alternative<bool>(val)) {
return false;
}
sServices->samd().SetFastChargeEnabled(std::get<bool>(val));
return true;
}};
lua::Property UiState::sBluetoothEnabled{
false, [](const lua::LuaValue& val) {
@ -406,6 +415,13 @@ void UiState::react(const system_fsm::BatteryStateChanged& ev) {
sBatteryPct.setDirect(static_cast<int>(ev.new_state.percent));
sBatteryMv.setDirect(static_cast<int>(ev.new_state.millivolts));
sBatteryCharging.setDirect(ev.new_state.is_charging);
sPowerChargeState.setDirect(
drivers::Samd::chargeStatusToString(ev.new_state.raw_status));
// FIXME: Avoid calling these event handlers before boot.
if (sServices) {
sPowerFastChargeEnabled.setDirect(sServices->nvs().FastCharge());
}
}
void UiState::react(const audio::QueueUpdate&) {
@ -414,7 +430,8 @@ void UiState::react(const audio::QueueUpdate&) {
sQueueSize.setDirect(static_cast<int>(queue_size));
int current_pos = queue.currentPosition();
// If there is nothing in the queue, the position should be 0, otherwise, add one because lua
// If there is nothing in the queue, the position should be 0, otherwise, add
// one because lua
if (queue_size > 0) {
current_pos++;
}
@ -564,11 +581,14 @@ void Lua::entry() {
auto& registry = lua::Registry::instance(*sServices);
sLua = registry.uiThread();
registry.AddPropertyModule("power", {
{"battery_pct", &sBatteryPct},
{"battery_millivolts", &sBatteryMv},
{"plugged_in", &sBatteryCharging},
});
registry.AddPropertyModule("power",
{
{"battery_pct", &sBatteryPct},
{"battery_millivolts", &sBatteryMv},
{"plugged_in", &sBatteryCharging},
{"charge_state", &sPowerChargeState},
{"fast_charge", &sPowerFastChargeEnabled},
});
registry.AddPropertyModule(
"bluetooth", {
{"enabled", &sBluetoothEnabled},
@ -663,6 +683,8 @@ void Lua::entry() {
}
sBluetoothKnownDevices.setDirect(bt.knownDevices());
sPowerFastChargeEnabled.setDirect(sServices->nvs().FastCharge());
if (sServices->sd() == drivers::SdState::kMounted) {
sLua->RunScript("/sdcard/config.lua");
}

@ -101,6 +101,8 @@ class UiState : public tinyfsm::Fsm<UiState> {
static lua::Property sBatteryPct;
static lua::Property sBatteryMv;
static lua::Property sBatteryCharging;
static lua::Property sPowerChargeState;
static lua::Property sPowerFastChargeEnabled;
static lua::Property sBluetoothEnabled;
static lua::Property sBluetoothConnecting;

Loading…
Cancel
Save