1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
/*
* 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;
static void check_voltage_cb(TimerHandle_t timer) {
Battery* instance = reinterpret_cast<Battery*>(pvTimerGetTimerID(timer));
instance->Update();
}
Battery::Battery(drivers::Samd& samd, std::unique_ptr<drivers::AdcBattery> 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<std::mutex> 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<uint_fast8_t>(std::min<double>(
std::max<double>(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 ||
// Treat 'no battery' as charging because, for UI purposes,
// we're *kind of* at full charge if u think about it.
*charge_state == ChargeStatus::kNoBattery;
}
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<BatteryState> {
std::lock_guard<std::mutex> 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
|