summaryrefslogtreecommitdiff
path: root/src/battery/battery.cpp
blob: d73f4f29a190541dd465e0cbfc073ff3226f1049 (plain)
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
/*
 * 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