/* * 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, std::unique_ptr adc) : samd_(samd), adc_(std::move(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(); // 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; if (!charge_state) { is_charging = false; } else { is_charging = *charge_state == ChargeStatus::kChargingRegular || *charge_state == ChargeStatus::kChargingFast || *charge_state == ChargeStatus::kFullCharge; } if (state_ && state_->is_charging == is_charging && state_->percent == percent) { return; } state_ = BatteryState{ .percent = percent, .millivolts = mV, .is_charging = is_charging, }; EmitEvent(); } auto Battery::State() -> std::optional { std::lock_guard lock{state_mutex_}; return state_; } auto Battery::EmitEvent() -> void { auto state = state_; if (!state) { return; } system_fsm::BatteryStateChanged ev{ .new_state = *state, }; events::System().Dispatch(ev); events::Ui().Dispatch(ev); } } // namespace battery