/* * Copyright 2023 jacqueline * * SPDX-License-Identifier: GPL-3.0-only */ #include "battery.hpp" #include #include "adc.hpp" #include "event_queue.hpp" #include "freertos/portmacro.h" #include "samd.hpp" #include "system_events.hpp" namespace battery { static const TickType_t kBatteryCheckPeriod = pdMS_TO_TICKS(60 * 1000); /* * Battery voltage, in millivolts, at which the battery charger IC will stop * charging. */ static const uint32_t kFullChargeMilliVolts = 4200; /* * Battery voltage, in millivolts, at which *we* will consider the battery to * be completely discharged. This is intentionally higher than the charger IC * cut-off and the protection on the battery itself; we want to make sure we * finish up and have everything unmounted and snoozing before the BMS cuts us * off. */ static const uint32_t kEmptyChargeMilliVolts = 3200; // BMS limit is 3100. using ChargeStatus = drivers::Samd::ChargeStatus; void check_voltage_cb(TimerHandle_t timer) { Battery* instance = reinterpret_cast(pvTimerGetTimerID(timer)); instance->Update(); } Battery::Battery(drivers::Samd* samd, drivers::AdcBattery* adc) : samd_(samd), adc_(adc) { timer_ = xTimerCreate("BATTERY", kBatteryCheckPeriod, true, this, check_voltage_cb); xTimerStart(timer_, portMAX_DELAY); Update(); } Battery::~Battery() { xTimerStop(timer_, portMAX_DELAY); xTimerDelete(timer_, portMAX_DELAY); } auto Battery::Update() -> void { std::lock_guard lock{state_mutex_}; auto charge_state = samd_->GetChargeStatus(); if (!charge_state || *charge_state == ChargeStatus::kNoBattery) { if (state_) { EmitEvent(); } state_.reset(); return; } // FIXME: So what we *should* do here is measure the actual real-life // time from full battery -> empty battery, store it in NVS, then rely on // that. If someone could please do this, it would be lovely. Thanks! uint32_t mV = std::max(adc_->Millivolts(), kEmptyChargeMilliVolts); uint_fast8_t percent = static_cast(std::min( std::max(0.0, mV - kEmptyChargeMilliVolts) / (kFullChargeMilliVolts - kEmptyChargeMilliVolts) * 100.0, 100.0)); bool is_charging = *charge_state == ChargeStatus::kChargingRegular || *charge_state == ChargeStatus::kChargingFast || *charge_state == ChargeStatus::kFullCharge; if (!state_ || state_->is_charging != is_charging || state_->percent != percent) { EmitEvent(); } state_ = BatteryState{ .percent = percent, .is_charging = is_charging, }; } auto Battery::State() -> std::optional { std::lock_guard lock{state_mutex_}; return state_; } auto Battery::EmitEvent() -> void { events::System().Dispatch(system_fsm::BatteryStateChanged{}); events::Ui().Dispatch(system_fsm::BatteryStateChanged{}); } } // namespace battery