commit
db601935c6
@ -0,0 +1,10 @@ |
|||||||
|
# Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
# |
||||||
|
# SPDX-License-Identifier: GPL-3.0-only |
||||||
|
|
||||||
|
idf_component_register( |
||||||
|
SRCS "battery.cpp" |
||||||
|
INCLUDE_DIRS "include" |
||||||
|
REQUIRES "drivers" "events") |
||||||
|
|
||||||
|
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
@ -0,0 +1,101 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "battery.hpp" |
||||||
|
|
||||||
|
#include <cstdint> |
||||||
|
|
||||||
|
#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<Battery*>(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<std::mutex> 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<uint_fast8_t>(std::min<double>( |
||||||
|
std::max<double>(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<BatteryState> { |
||||||
|
std::lock_guard<std::mutex> lock{state_mutex_}; |
||||||
|
return state_; |
||||||
|
} |
||||||
|
|
||||||
|
auto Battery::EmitEvent() -> void { |
||||||
|
events::System().Dispatch(system_fsm::BatteryStateChanged{}); |
||||||
|
events::Ui().Dispatch(system_fsm::BatteryStateChanged{}); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace battery
|
@ -0,0 +1,44 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstdint> |
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h" |
||||||
|
#include "freertos/timers.h" |
||||||
|
|
||||||
|
#include "adc.hpp" |
||||||
|
#include "samd.hpp" |
||||||
|
|
||||||
|
namespace battery { |
||||||
|
|
||||||
|
class Battery { |
||||||
|
public: |
||||||
|
Battery(drivers::Samd* samd, drivers::AdcBattery* adc); |
||||||
|
~Battery(); |
||||||
|
|
||||||
|
auto Update() -> void; |
||||||
|
|
||||||
|
struct BatteryState { |
||||||
|
uint_fast8_t percent; |
||||||
|
bool is_charging; |
||||||
|
}; |
||||||
|
|
||||||
|
auto State() -> std::optional<BatteryState>; |
||||||
|
|
||||||
|
private: |
||||||
|
auto EmitEvent() -> void; |
||||||
|
|
||||||
|
drivers::Samd* samd_; |
||||||
|
drivers::AdcBattery* adc_; |
||||||
|
|
||||||
|
TimerHandle_t timer_; |
||||||
|
std::mutex state_mutex_; |
||||||
|
std::optional<BatteryState> state_; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace battery
|
Loading…
Reference in new issue