~aleteoryx/muditaos

aca0bb93e472a11e36d1bc09e936965923dd3d03 — Mateusz Piesta 4 years ago 232e5c1
[BH-1383] Battery scaling table

Fixed battery SOC scaling.
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)