M module-bsp/board/rt1051/CMakeLists.txt => module-bsp/board/rt1051/CMakeLists.txt +1 -0
@@ 35,6 35,7 @@ target_sources(module-bsp
common/chip.cpp
common/startup_mimxrt1052.cpp
common/system_MIMXRT1051.c
+ common/soc_scaler.cpp
drivers/RT1051DriverDMA.cpp
drivers/RT1051DriverDMAMux.cpp
drivers/RT1051DriverGPIO.cpp
M module-bsp/board/rt1051/bellpx/hal/battery_charger/BatteryCharger.cpp => module-bsp/board/rt1051/bellpx/hal/battery_charger/BatteryCharger.cpp +9 -8
@@ 3,6 3,7 @@
#include "BatteryChargerIRQ.hpp"
#include "common/WorkerQueue.hpp"
+#include "common/soc_scaler.hpp"
#include "FreeRTOS.h"
#include "queue.h"
@@ 17,8 18,8 @@ namespace
{
using namespace bsp::devices::power;
- constexpr auto emergency_poll_time = pdMS_TO_TICKS(5 * 60 * 1000); /// 5min
- constexpr auto default_timeout = pdMS_TO_TICKS(100);
+ constexpr auto reinit_poll_time = pdMS_TO_TICKS(5 * 60 * 1000); /// 5min
+ constexpr auto default_timeout = pdMS_TO_TICKS(100);
constexpr auto i2c_baudrate = static_cast<std::uint32_t>(BoardDefinitions::BELL_FUELGAUGE_I2C_BAUDRATE);
constexpr auto i2c_instance = static_cast<drivers::I2CInstances>(BoardDefinitions::BELL_FUELGAUGE_I2C);
@@ 94,7 95,7 @@ namespace hal::battery
fuel_gauge{CW2015{drivers::DriverI2C::Create(i2c_instance, i2c_params)}}
{
- reinit_timer = xTimerCreate("emg_timer", emergency_poll_time, pdFALSE, this, [](TimerHandle_t xTimer) {
+ reinit_timer = xTimerCreate("reinit_timer", reinit_poll_time, pdFALSE, this, [](TimerHandle_t xTimer) {
BellBatteryCharger *inst = static_cast<BellBatteryCharger *>(pvTimerGetTimerID(xTimer));
inst->pollFuelGauge();
});
@@ 132,10 133,10 @@ namespace hal::battery
{
/// Charger status fetched from MP2639B alone is not enough to be 100% sure what state we are currently in. For
/// more info check the Table 2 (page 26) from the MP2639B datasheet. It is required to also take into account
- /// the current state of SOC.configuration.notification(inst->get_charge_status());
+ /// the current state of SOC.
const auto charger_status = charger.get_charge_status();
- const auto current_soc = fuel_gauge.get_battery_soc();
+ const auto current_soc = fetchBatterySOC();
if (charger_status == MP2639B::ChargingStatus::Complete && current_soc >= 100) {
return ChargingStatus::ChargingDone;
@@ 150,7 151,7 @@ namespace hal::battery
return ChargingStatus::PluggedNotCharging;
}
else if (charger_status == MP2639B::ChargingStatus::Charging && current_soc >= 100) {
- LOG_WARN("Reported SOC mismatch. The charger reports 'Charging state', but the battery SOC is 100");
+ LOG_WARN("The charger reports 'Charging state', but the battery SOC is 100");
return ChargingStatus::ChargingDone;
}
else {
@@ 159,8 160,8 @@ namespace hal::battery
}
AbstractBatteryCharger::SOC BellBatteryCharger::fetchBatterySOC() const
{
- if (const auto result = fuel_gauge.get_battery_soc(); result) {
- return *result;
+ if (const auto soc = fuel_gauge.get_battery_soc(); const auto scaled_soc = scale_soc(*soc)) {
+ return *scaled_soc;
}
else {
LOG_ERROR("Error during fetching battery level");
A module-bsp/board/rt1051/common/soc_scaler.cpp => module-bsp/board/rt1051/common/soc_scaler.cpp +23 -0
@@ 0,0 1,23 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "soc_scaler.hpp"
+
+#include <ValueScaler.hpp>
+
+namespace
+{
+ /// Scale SOC <5% to 0%.
+ // clang-format off
+ constexpr auto entries = std::array<utils::Entry<std::uint8_t>, 3>{{
+ {{0, 4}, {0, 0}},
+ {{5, 99}, {1, 99}},
+ {{100, 100}, {100, 100}}
+ }};
+ // clang-format on
+} // namespace
+
+std::optional<units::SOC> scale_soc(units::SOC soc)
+{
+ return utils::find_and_scale_value(entries, soc);
+}
A module-bsp/board/rt1051/common/soc_scaler.hpp => module-bsp/board/rt1051/common/soc_scaler.hpp +9 -0
@@ 0,0 1,9 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <Units.hpp>
+#include <optional>
+
+std::optional<units::SOC> scale_soc(units::SOC soc);
M module-bsp/board/rt1051/puretx/hal/battery_charger/BatteryCharger.cpp => module-bsp/board/rt1051/puretx/hal/battery_charger/BatteryCharger.cpp +8 -1
@@ 3,6 3,7 @@
#include "BatteryChargerIRQ.hpp"
#include "common/WorkerQueue.hpp"
+#include "common/soc_scaler.hpp"
#include <hal/battery_charger/AbstractBatteryCharger.hpp>
#include <bsp/battery_charger/battery_charger.hpp>
@@ 128,7 129,13 @@ namespace hal::battery
}
AbstractBatteryCharger::SOC PureBatteryCharger::getSOC() const
{
- return bsp::battery_charger::getBatteryLevel();
+ const auto soc = bsp::battery_charger::getBatteryLevel();
+ const auto scaled_soc = scale_soc(soc);
+ if (not scaled_soc) {
+ LOG_ERROR("SOC is out of valid range.");
+ return 0;
+ }
+ return *scaled_soc;
}
AbstractBatteryCharger::ChargingStatus PureBatteryCharger::getChargingStatus() const
{
M module-bsp/devices/power/CW2015.hpp => module-bsp/devices/power/CW2015.hpp +2 -1
@@ 3,10 3,11 @@
#pragma once
-#include "hal/Units.hpp"
#include "drivers/gpio/DriverGPIO.hpp"
#include "drivers/i2c/DriverI2C.hpp"
+#include <Units.hpp>
+
namespace bsp::devices::power
{
/// MP2639B fuel gauge driver
M module-bsp/hal/include/hal/battery_charger/AbstractBatteryCharger.hpp => module-bsp/hal/include/hal/battery_charger/AbstractBatteryCharger.hpp +1 -2
@@ 3,8 3,7 @@
#pragma once
-#include "hal/Units.hpp"
-
+#include <Units.hpp>
#include <FreeRTOS.h>
#include <queue.h>
M module-services/service-evtmgr/battery/BatteryController.cpp => module-services/service-evtmgr/battery/BatteryController.cpp +21 -19
@@ 91,37 91,21 @@ BatteryController::BatteryController(sys::Service *service, xQueueHandle notific
void sevm::battery::BatteryController::handleNotification(Events evt)
{
- const auto soc = Store::Battery::get().level;
- const auto chargingState = Store::Battery::get().state;
-
LOG_INFO("Incoming event: %s", std::string{magic_enum::enum_name(evt)}.c_str());
switch (evt) {
+ case Events::Charger:
case Events::SOC:
- Store::Battery::modify().level = charger->getSOC();
+ update();
break;
case Events::Brownout:
brownoutDetector.startDetection();
break;
- case Events::Charger:
- Store::Battery::modify().state = transformChargingState(charger->getChargingStatus());
- break;
}
-
- /// Send BatteryStatusChangeMessage only when battery SOC or charger state has changed
- if (soc != Store::Battery::get().level || chargingState != Store::Battery::get().state) {
- auto message = std::make_shared<sevm::BatteryStatusChangeMessage>();
- service->bus.sendUnicast(std::move(message), service::name::evt_manager);
- }
-
- batteryState.check(transformChargingState(Store::Battery::modify().state), Store::Battery::modify().level);
-
- printCurrentState();
}
void sevm::battery::BatteryController::poll()
{
- LOG_DEBUG("Manual poll");
- handleNotification(Events::SOC);
+ update();
}
void sevm::battery::BatteryController::printCurrentState()
{
@@ 130,3 114,21 @@ void sevm::battery::BatteryController::printCurrentState()
LOG_INFO("Battery voltage:%" PRIu32 "mV", charger->getBatteryVoltage());
LOG_INFO("Battery state:%s", magic_enum::enum_name(Store::Battery::get().levelState).data());
}
+void sevm::battery::BatteryController::update()
+{
+ const auto soc = Store::Battery::get().level;
+ const auto chargingState = Store::Battery::get().state;
+
+ Store::Battery::modify().level = charger->getSOC();
+ Store::Battery::modify().state = transformChargingState(charger->getChargingStatus());
+
+ /// Send BatteryStatusChangeMessage only when battery SOC or charger state has changed
+ if (soc != Store::Battery::get().level || chargingState != Store::Battery::get().state) {
+ auto message = std::make_shared<sevm::BatteryStatusChangeMessage>();
+ service->bus.sendUnicast(std::move(message), service::name::evt_manager);
+ }
+
+ batteryState.check(transformChargingState(Store::Battery::modify().state), Store::Battery::modify().level);
+
+ printCurrentState();
+}
M module-services/service-evtmgr/battery/BatteryController.hpp => module-services/service-evtmgr/battery/BatteryController.hpp +1 -0
@@ 27,6 27,7 @@ namespace sevm::battery
void handleNotification(Events);
private:
+ void update();
void printCurrentState();
sys::Service *service{nullptr};
std::unique_ptr<hal::battery::AbstractBatteryCharger> charger;
M module-services/service-evtmgr/battery/BatteryState.cpp => module-services/service-evtmgr/battery/BatteryState.cpp +1 -1
@@ 136,7 136,7 @@ class BatteryState::Pimpl
sml::sm<StateMachine, NotifyStateChangedCallback> sm{notifyCallback};
static constexpr auto criticalThreshold = 10; // %
- static constexpr auto shutdownThreshold = 5; // %
+ static constexpr auto shutdownThreshold = 1; // %
};
void BatteryState::check(const ChargingState state, const float soc)
M module-utils/utility/CMakeLists.txt => module-utils/utility/CMakeLists.txt +1 -0
@@ 11,6 11,7 @@ target_sources(utility
Temperature.hpp
Utility.hpp
Utils.hpp
+ Units.hpp
)
target_include_directories(utility
R module-bsp/hal/include/hal/Units.hpp => module-utils/utility/Units.hpp +0 -0
A module-utils/utility/ValueScaler.hpp => module-utils/utility/ValueScaler.hpp +60 -0
@@ 0,0 1,60 @@
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <cstdint>
+#include <algorithm>
+#include <optional>
+#include <cmath>
+
+namespace utils
+{
+ template <typename T> struct Range
+ {
+ T min;
+ T max;
+ };
+
+ /// Perform scaling of the input value based on the input&output ranges. If the input values is not contained within
+ /// the provided input range return std::nullopt.
+ template <typename T>
+ std::optional<T> scale_value(const Range<T> inputRange, const Range<T> outputRange, const T input)
+ {
+ if (input > inputRange.max || input < inputRange.min) {
+ return {};
+ }
+
+ const auto inRangeVal = inputRange.max - inputRange.min;
+ const auto outRangeVal = outputRange.max - outputRange.min;
+
+ if (outRangeVal == 0 || inRangeVal == 0) {
+ return outputRange.min;
+ }
+ float slope = 1.0 * (outRangeVal) / (inRangeVal);
+ auto output = outputRange.min + slope * (input - inputRange.min);
+
+ return static_cast<T>(std::floor(output));
+ }
+
+ template <typename T> struct Entry
+ {
+ const Range<T> input;
+ const Range<T> output;
+ };
+
+ /// Try to find the given input value in the the entries array. If the value is found, perform scaling and then
+ /// return the scaled value. If the value is not found, return std::nullopt.
+ template <typename T, const size_t N>
+ [[nodiscard]] std::optional<T> find_and_scale_value(const std::array<Entry<T>, N> &entries, const T val)
+ {
+ auto result = std::find_if(
+ entries.begin(), entries.end(), [val](const auto &e) { return val >= e.input.min && val <= e.input.max; });
+
+ if (result != entries.end()) {
+ return scale_value(result->input, result->output, val);
+ }
+
+ return {};
+ }
+} // namespace utils
M module-utils/utility/tests/CMakeLists.txt => module-utils/utility/tests/CMakeLists.txt +9 -0
@@ 16,3 16,12 @@ add_catch2_executable(
module-utils
)
+add_catch2_executable(
+ NAME
+ utils-value-scaler
+ SRCS
+ value_scaler.cpp
+ LIBS
+ module-utils
+)
+
A module-utils/utility/tests/value_scaler.cpp => module-utils/utility/tests/value_scaler.cpp +62 -0
@@ 0,0 1,62 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include <catch2/catch.hpp>
+
+#include <ValueScaler.hpp>
+
+#include <climits>
+
+using namespace utils;
+
+TEST_CASE("Linear approximation")
+{
+ SECTION("Corner cases")
+ {
+ SECTION("Unsigned")
+ {
+ constexpr auto entries = std::array<Entry<std::uint32_t>, 1>{{{{100, 100}, {100, 100}}}};
+ REQUIRE_FALSE(find_and_scale_value(entries, 0U));
+ REQUIRE(find_and_scale_value(entries, 100U).value() == 100);
+ REQUIRE_FALSE(find_and_scale_value(entries, 101U));
+ }
+
+ SECTION("Signed")
+ {
+ constexpr auto entries = std::array<Entry<std::int32_t>, 1>{{{{-100, -100}, {-100, -100}}}};
+ REQUIRE_FALSE(find_and_scale_value(entries, 0));
+ REQUIRE(find_and_scale_value(entries, -100).value() == -100);
+ REQUIRE_FALSE(find_and_scale_value(entries, 101));
+ REQUIRE_FALSE(find_and_scale_value(entries, -101));
+ }
+
+ SECTION("Float")
+ {
+ constexpr auto entries = std::array<Entry<float>, 1>{{{{-100, -100}, {-100, -100}}}};
+ REQUIRE_FALSE(find_and_scale_value(entries, 0.0f));
+ REQUIRE(find_and_scale_value(entries, -100.0f).value() == -100.0f);
+ REQUIRE_FALSE(find_and_scale_value(entries, 101.0f));
+ REQUIRE_FALSE(find_and_scale_value(entries, -101.0f));
+ }
+
+ SECTION("Basic use case")
+ {
+ constexpr auto entries = std::array<Entry<std::int32_t>, 8>{{{{-10, -1}, {0, 0}},
+ {{0, 5}, {1, 1}},
+ {{6, 10}, {2, 5}},
+ {{11, 30}, {6, 29}},
+ {{31, 50}, {30, 53}},
+ {{51, 70}, {54, 77}},
+ {{71, 99}, {78, 99}},
+ {{100, 100}, {100, 100}}}};
+
+ REQUIRE_FALSE(find_and_scale_value(entries, -11));
+ REQUIRE(find_and_scale_value(entries, -10).value() == 0);
+ REQUIRE(find_and_scale_value(entries, -1).value() == 0);
+ REQUIRE(find_and_scale_value(entries, 0).value() == 1);
+ REQUIRE(find_and_scale_value(entries, 6).value() == 2);
+ REQUIRE(find_and_scale_value(entries, 10).value() == 5);
+ REQUIRE(find_and_scale_value(entries, 99).value() == 99);
+ }
+ }
+}
M products/BellHybrid/apps/application-bell-main/data/BatteryUtils.hpp => products/BellHybrid/apps/application-bell-main/data/BatteryUtils.hpp +9 -21
@@ 7,39 7,27 @@
#include <algorithm>
#include <optional>
+#include <Units.hpp>
+
namespace battery_utils
{
struct BatteryLevelEntry
{
- using Range = std::pair<std::uint32_t, std::uint32_t>;
- Range realLvl;
- Range displayedLvl;
+ using Range = std::pair<units::SOC, units::SOC>;
+ const Range range;
std::string_view image;
};
- struct ScaledBatteryLevel
- {
- std::uint32_t level;
- std::string image;
- };
-
template <std::size_t N>
- [[nodiscard]] std::optional<ScaledBatteryLevel> getScaledBatteryLevel(
- const std::array<BatteryLevelEntry, N> &entries, const std::uint32_t val)
+ [[nodiscard]] std::optional<std::string_view> getBatteryLevelImage(const std::array<BatteryLevelEntry, N> &entries,
+ const units::SOC soc)
{
- auto result = std::find_if(entries.begin(), entries.end(), [val](const auto &e) {
- return val >= e.realLvl.first && val <= e.realLvl.second;
+ auto result = std::find_if(entries.begin(), entries.end(), [soc](const auto &e) {
+ return soc >= e.range.first && soc <= e.range.second;
});
if (result != entries.end()) {
- const auto outputStart = result->displayedLvl.first;
- const auto outputEnd = result->displayedLvl.second;
- const auto inputStart = result->realLvl.first;
- const auto inputEnd = result->realLvl.second;
- float slope = 1.0 * (outputEnd - outputStart) / (inputEnd - inputStart);
- auto output = outputStart + slope * (val - inputStart);
-
- return ScaledBatteryLevel{static_cast<std::uint32_t>(std::floor(output)), result->image.data()};
+ return result->image;
}
return {};
M products/BellHybrid/apps/application-bell-main/widgets/BellBattery.cpp => products/BellHybrid/apps/application-bell-main/widgets/BellBattery.cpp +11 -13
@@ 7,14 7,12 @@
namespace
{
- constexpr auto entries =
- std::array<battery_utils::BatteryLevelEntry, 7>{{{{0, 5}, {1, 1}, "bell_battery_empty"},
- {{5, 10}, {1, 5}, "bell_battery_empty"},
- {{11, 30}, {6, 29}, "bell_battery_lvl1"},
- {{31, 50}, {30, 53}, "bell_battery_lvl2"},
- {{51, 70}, {54, 77}, "bell_battery_lvl3"},
- {{71, 95}, {78, 99}, "bell_battery_lvl4"},
- {{96, 100}, {100, 100}, "bell_battery_lvl5"}}};
+ constexpr auto entries = std::array<battery_utils::BatteryLevelEntry, 6>{{{{0, 9}, "bell_battery_empty"},
+ {{10, 19}, "bell_battery_lvl1"},
+ {{20, 39}, "bell_battery_lvl2"},
+ {{40, 69}, "bell_battery_lvl3"},
+ {{70, 95}, "bell_battery_lvl4"},
+ {{96, 100}, "bell_battery_lvl5"}}};
}
namespace gui
@@ 36,12 34,12 @@ namespace gui
void BellBattery::update(const Store::Battery &batteryContext)
{
- const auto result = battery_utils::getScaledBatteryLevel(entries, batteryContext.level);
- if (not result) {
+ const auto image = battery_utils::getBatteryLevelImage(entries, batteryContext.level);
+ if (not image) {
return;
}
- const auto level = result->level;
+ const auto level = batteryContext.level;
if (batteryContext.state == Store::Battery::State::Charging) {
img->set(battery::battery_charging, gui::ImageTypeSpecifier::W_M);
@@ 62,7 60,7 @@ namespace gui
setVisible(true);
}
else {
- if (level > 20) {
+ if (level >= 20) {
setVisible(false);
}
else {
@@ 74,7 72,7 @@ namespace gui
img->setMargins(gui::Margins(battery::image_left_margin, 0, battery::image_right_margin, 0));
img->informContentChanged();
}
- img->set(result->image, gui::ImageTypeSpecifier::W_M);
+ img->set(image->data(), gui::ImageTypeSpecifier::W_M);
percentText->setText(std::to_string(level) + "%");
setVisible(true);
}
M products/BellHybrid/apps/application-bell-main/windows/BellBatteryStatusWindow.cpp => products/BellHybrid/apps/application-bell-main/windows/BellBatteryStatusWindow.cpp +10 -11
@@ 15,13 15,12 @@ namespace
{
constexpr auto imageType = gui::ImageTypeSpecifier::W_G;
constexpr auto batteryEntries =
- std::array<battery_utils::BatteryLevelEntry, 7>{{{{0, 5}, {1, 1}, "bell_status_battery_lvl0"},
- {{5, 10}, {1, 5}, "bell_status_battery_lvl0"},
- {{11, 30}, {6, 29}, "bell_status_battery_lvl1"},
- {{31, 50}, {30, 53}, "bell_status_battery_lvl2"},
- {{51, 70}, {54, 77}, "bell_status_battery_lvl3"},
- {{71, 95}, {78, 99}, "bell_status_battery_lvl4"},
- {{96, 100}, {100, 100}, "bell_status_battery_lvl5"}}};
+ std::array<battery_utils::BatteryLevelEntry, 6>{{{{0, 9}, "bell_status_battery_lvl0"},
+ {{10, 19}, "bell_status_battery_lvl1"},
+ {{20, 39}, "bell_status_battery_lvl2"},
+ {{40, 69}, "bell_status_battery_lvl3"},
+ {{70, 95}, "bell_status_battery_lvl4"},
+ {{96, 100}, "bell_status_battery_lvl5"}}};
} // namespace
namespace gui
@@ 95,10 94,10 @@ namespace gui
{
if (data != nullptr) {
const auto &switchData = static_cast<Data &>(*data);
- const auto entry = battery_utils::getScaledBatteryLevel(batteryEntries, switchData.chargeLevel);
- if (entry) {
- center->setImage(entry->image, imageType);
- bottomDescription->setText(std::to_string(entry->level) + "%");
+ const auto image = battery_utils::getBatteryLevelImage(batteryEntries, switchData.chargeLevel);
+ if (image) {
+ center->setImage(image->data(), imageType);
+ bottomDescription->setText(std::to_string(switchData.chargeLevel) + "%");
chargingIcon->setVisible(switchData.isCharging);
hbox->resizeItems();
body->resize();
M products/PurePhone/sys/SystemManager.cpp => products/PurePhone/sys/SystemManager.cpp +0 -2
@@ 134,8 134,6 @@ namespace sys
{
SystemManagerCommon::batteryNormalLevelAction();
CellularServiceAPI::ChangeModulePowerState(this, cellular::service::State::PowerState::On);
- auto battNormalMsg = std::make_shared<CriticalBatteryLevelNotification>(false);
- bus.sendUnicast(std::move(battNormalMsg), service::name::appmgr);
}
void SystemManager::batteryCriticalLevelAction(bool charging)