diff --git a/firmware/application/apps/ui_battinfo.cpp b/firmware/application/apps/ui_battinfo.cpp index 013bb4e7d..a99066526 100644 --- a/firmware/application/apps/ui_battinfo.cpp +++ b/firmware/application/apps/ui_battinfo.cpp @@ -52,7 +52,6 @@ void BattinfoView::update_result() { text_voltage.set("UNKNOWN"); text_current.set("-"); text_charge.set("-"); - // text_cycles.set("-"); text_ttef.set("-"); text_method.set("-"); // text_warn.set(""); @@ -60,7 +59,9 @@ void BattinfoView::update_result() { } bool uichg = false; uint8_t valid_mask = 0; - battery::BatteryManagement::getBatteryInfo(valid_mask, percent, voltage, current); + bool battMayChanged = false; + battery::BatteryManagement::getBatteryInfo(valid_mask, percent, voltage, current, battMayChanged); + text_state.set((battMayChanged || !persistent_memory::battery_cap_valid()) ? "Needs config." : "ok"); // update text fields if (percent <= 100 && (valid_mask & battery::BatteryManagement::BATT_VALID_VOLTAGE) == battery::BatteryManagement::BATT_VALID_VOLTAGE) text_percent.set(to_string_dec_uint(percent) + " %"); @@ -76,6 +77,7 @@ void BattinfoView::update_result() { labels_opt.hidden(false); text_current.hidden(false); text_charge.hidden(false); + text_state.hidden(false); text_current.set(to_string_dec_int(current) + " mA"); if (current >= 25) // when >25mA it is charging in any scenario text_charge.set("Charging"); @@ -98,7 +100,7 @@ void BattinfoView::update_result() { labels_opt.hidden(true); text_current.hidden(true); text_charge.hidden(true); - // text_cycles.hidden(true); + text_state.hidden(true); text_ttef.hidden(true); // text_warn.set(""); } @@ -143,9 +145,10 @@ void BattinfoView::update_result() { } else { text_method.set("Voltage"); } + if (battMayChanged) text_capacity.set_style(Theme::getInstance()->fg_red); if (uichg) set_dirty(); // to update status bar too, send message in behalf of batt manager - BatteryStateMessage msg{valid_mask, percent, current >= 25, voltage}; + BatteryStateMessage msg{valid_mask, percent, current >= 25, voltage, battMayChanged}; EventDispatcher::send_message(msg); } @@ -161,7 +164,7 @@ BattinfoView::BattinfoView(NavigationView& nav) &button_settings, &button_exit, &text_capacity, - // &text_cycles, + //&text_state, //disabled for now // &text_warn, &text_ttef}); diff --git a/firmware/application/apps/ui_battinfo.hpp b/firmware/application/apps/ui_battinfo.hpp index fd4798503..2c24087c5 100644 --- a/firmware/application/apps/ui_battinfo.hpp +++ b/firmware/application/apps/ui_battinfo.hpp @@ -64,7 +64,7 @@ class BattinfoView : public View { {{UI_POS_X(2), UI_POS_Y(5)}, "Current:", Theme::getInstance()->fg_light->foreground}, {{UI_POS_X(2), UI_POS_Y(6)}, "Charge:", Theme::getInstance()->fg_light->foreground}, {{UI_POS_X(2), UI_POS_Y(7)}, "TTF/E:", Theme::getInstance()->fg_light->foreground}, - // {{UI_POS_X(2), UI_POS_Y(8)}, "Cycles:", Theme::getInstance()->fg_light->foreground}, + //{{UI_POS_X(2), UI_POS_Y(8)}, "State:", Theme::getInstance()->fg_light->foreground}, //for now, this is disabled. see max ic implementation {{UI_POS_X(2), UI_POS_Y(10)}, "Change settings:", Theme::getInstance()->fg_light->foreground}, }; @@ -90,13 +90,14 @@ class BattinfoView : public View { {UI_POS_X(13), UI_POS_Y(7), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)}, "-"}; - /* Text text_cycles{ - {UI_POS_X(13), UI_POS_Y(8), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)}, - "-"}; - + Text text_state{ + {UI_POS_X(13), UI_POS_Y(8), UI_POS_WIDTH_REMAINING(13), UI_POS_HEIGHT(1)}, + "ok"}; + /* Text text_warn{ {UI_POS_X(1), UI_POS_Y(9), screen_width, UI_POS_HEIGHT(2)}, - ""}; */ + ""}; + */ Button button_settings{ {UI_POS_X(2), UI_POS_Y(11) + 5, UI_POS_WIDTH(10), UI_POS_HEIGHT(2)}, diff --git a/firmware/application/apps/ui_settings.cpp b/firmware/application/apps/ui_settings.cpp index 3f81df4a6..47eebeba1 100644 --- a/firmware/application/apps/ui_settings.cpp +++ b/firmware/application/apps/ui_settings.cpp @@ -1163,6 +1163,7 @@ SetBatteryView::SetBatteryView(NavigationView& nav) { &field_battcap, &button_help_cap, &checkbox_overridebatt, + &checkbox_battery_replaceable, &checkbox_battery_charge_hint}); if (i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055)) add_children({&button_reset, &labels2}); @@ -1170,10 +1171,12 @@ SetBatteryView::SetBatteryView(NavigationView& nav) { button_save.on_select = [&nav, this](Button&) { pmem::set_ui_override_batt_calc(checkbox_overridebatt.value()); pmem::set_ui_battery_charge_hint(checkbox_battery_charge_hint.value()); + pmem::set_battery_replaceable(checkbox_battery_replaceable.value()); battery::BatteryManagement::set_calc_override(checkbox_overridebatt.value()); - if (((uint32_t)field_battcap.value() != pmem::battery_cap_mah()) || (!pmem::battery_cap_valid())) { + i2cdev::I2cDev_MAX17055* dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055); + if ((((uint32_t)field_battcap.value() != pmem::battery_cap_mah()) || (!pmem::battery_cap_valid())) || (dev && dev->getIsBattChanged())) { pmem::set_battery_cap_mah(field_battcap.value()); - i2cdev::I2cDev_MAX17055* dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055); + if (dev) dev->resetChangedFlag(); if (dev && !dev->reInit()) { nav.display_modal("Error", "Battery gauge re-init failed"); return; @@ -1206,6 +1209,7 @@ SetBatteryView::SetBatteryView(NavigationView& nav) { checkbox_overridebatt.set_value(pmem::ui_override_batt_calc()); checkbox_battery_charge_hint.set_value(pmem::ui_battery_charge_hint()); + checkbox_battery_replaceable.set_value(pmem::battery_replaceable()); field_battcap.set_value(pmem::battery_cap_mah()); diff --git a/firmware/application/apps/ui_settings.hpp b/firmware/application/apps/ui_settings.hpp index 45dc033dc..6c4a3eaea 100644 --- a/firmware/application/apps/ui_settings.hpp +++ b/firmware/application/apps/ui_settings.hpp @@ -1053,7 +1053,7 @@ class SetBatteryView : public View { {{UI_POS_X_CENTER(17), UI_POS_Y(8)}, "Battery capacity", Theme::getInstance()->fg_light->foreground}, {{UI_POS_X(7), UI_POS_Y(9)}, "mAh", Theme::getInstance()->fg_light->foreground}}; - Labels labels2{{{UI_POS_X(1), UI_POS_Y(11)}, "Reset IC's learned params.", Theme::getInstance()->fg_light->foreground}}; + Labels labels2{{{UI_POS_X(1), UI_POS_Y_BOTTOM(7)}, "Reset IC's learned params.", Theme::getInstance()->fg_light->foreground}}; NumberField field_battcap{ {UI_POS_X(1), UI_POS_Y(9)}, @@ -1078,17 +1078,22 @@ class SetBatteryView : public View { 23, "Charge hint"}; + Checkbox checkbox_battery_replaceable{ + {UI_POS_X(2), UI_POS_Y(11)}, + 23, + "Replaceable battery"}; + Button button_cancel{ - {UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)}, + {UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)}, "Cancel", }; Button button_save{ - {UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)}, + {UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)}, "Save"}; Button button_reset{ - {UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y(13), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)}, + {UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(6), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)}, "Reset", }; }; diff --git a/firmware/application/event_m0.cpp b/firmware/application/event_m0.cpp index ddf904a52..088fc58cc 100644 --- a/firmware/application/event_m0.cpp +++ b/firmware/application/event_m0.cpp @@ -238,7 +238,8 @@ void EventDispatcher::charge_deep_sleep(const bool sleep) { // --- Battery Status Check (I2C) --- detect = battery::BatteryManagement::isDetected(); if (detect) { - battery::BatteryManagement::getBatteryInfo(valid_mask, percent, voltage, current); + bool dummy; + battery::BatteryManagement::getBatteryInfo(valid_mask, percent, voltage, current, dummy); bool is_full = (valid_mask == 31 && percent == 100 && current <= 10) || (valid_mask == 1 && percent == 100); diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index 5775382d9..331a59524 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -73,6 +73,8 @@ #include "file_path.hpp" #include "ff.h" +#include "i2cdev_max17055.hpp" + #include #include @@ -334,10 +336,10 @@ void SystemStatusView::on_battery_data(const BatteryStateMessage* msg) { was_charging = msg->on_charger; if (!pmem::ui_hide_numeric_battery()) { - battery_text.set_battery(msg->valid_mask, msg->percent, msg->on_charger); + battery_text.set_battery(msg->valid_mask, msg->percent, msg->on_charger, msg->battMayChanged); } if (!pmem::ui_hide_battery_icon()) { - battery_icon.set_battery(msg->valid_mask, msg->percent, msg->on_charger); + battery_icon.set_battery(msg->valid_mask, msg->percent, msg->on_charger, msg->battMayChanged); } } @@ -880,6 +882,10 @@ void SystemMenuView::hackrf_mode(NavigationView& nav) { YESNO, [this](bool choice) { if (choice) { + i2cdev::I2cDev_MAX17055* dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055); + if (dev) { + dev->sleep_config(false); // don't enable sleep even if no i2c communication. so we won't lose any data. on reboot (like exit hackrf mode) we set it to true again. + } EventDispatcher::request_stop(); } }); diff --git a/firmware/common/battery.cpp b/firmware/common/battery.cpp index bf49ba4a5..85dc2ac23 100644 --- a/firmware/common/battery.cpp +++ b/firmware/common/battery.cpp @@ -30,10 +30,11 @@ uint8_t BatteryManagement::calc_percent_voltage(uint16_t voltage) { } // helper function to get data from ANY batt management ic -void BatteryManagement::getBatteryInfo(uint8_t& valid_mask, uint8_t& percent, uint16_t& voltage, int32_t& current) { +void BatteryManagement::getBatteryInfo(uint8_t& valid_mask, uint8_t& percent, uint16_t& voltage, int32_t& current, bool& battMayChanged) { auto dev = i2cdev::I2CDevManager::get_dev_by_model(I2CDEVMDL_MAX17055); + battMayChanged = false; if (dev) { - ((i2cdev::I2cDev_MAX17055*)dev)->getBatteryInfo(valid_mask, percent, voltage, current); + ((i2cdev::I2cDev_MAX17055*)dev)->getBatteryInfo(valid_mask, percent, voltage, current, battMayChanged); return; } dev = i2cdev::I2CDevManager::get_dev_by_model(I2CDEVMDL_ADS1110); @@ -47,6 +48,13 @@ void BatteryManagement::getBatteryInfo(uint8_t& valid_mask, uint8_t& percent, ui valid_mask = 0; } +void BatteryManagement::reset_changed_flag() { + auto dev = i2cdev::I2CDevManager::get_dev_by_model(I2CDEVMDL_MAX17055); + if (dev) { + ((i2cdev::I2cDev_MAX17055*)dev)->resetChangedFlag(); + } +} + // helper function to get data from ANY batt management ic uint16_t BatteryManagement::get_cycles() { auto dev = i2cdev::I2CDevManager::get_dev_by_model(I2CDEVMDL_MAX17055); diff --git a/firmware/common/battery.hpp b/firmware/common/battery.hpp index 3f6482fb7..820ec6ece 100644 --- a/firmware/common/battery.hpp +++ b/firmware/common/battery.hpp @@ -46,10 +46,11 @@ class BatteryManagement { static void set_calc_override(bool override); static uint8_t calc_percent_voltage(uint16_t); // calculates battery percentage from the voltage static bool calcOverride; // if set to true, it'll override the battery percent calculation based on current voltage. - static void getBatteryInfo(uint8_t& valid_mask, uint8_t& percent, uint16_t& voltage, int32_t& current); + static void getBatteryInfo(uint8_t& valid_mask, uint8_t& percent, uint16_t& voltage, int32_t& current, bool& battMayChanged); static uint16_t get_cycles(); static float get_tte(); static float get_ttf(); + static void reset_changed_flag(); private: }; diff --git a/firmware/common/i2cdev_ads1110.cpp b/firmware/common/i2cdev_ads1110.cpp index 97256b262..8583e3b4f 100644 --- a/firmware/common/i2cdev_ads1110.cpp +++ b/firmware/common/i2cdev_ads1110.cpp @@ -57,7 +57,7 @@ void I2cDev_ADS1110::update() { uint16_t voltage = readVoltage(); uint8_t batteryPercentage = battery::BatteryManagement::calc_percent_voltage(voltage); // send local message - BatteryStateMessage msg{1, batteryPercentage, false, voltage}; + BatteryStateMessage msg{1, batteryPercentage, false, voltage, false}; EventDispatcher::send_message(msg); } diff --git a/firmware/common/i2cdev_max17055.cpp b/firmware/common/i2cdev_max17055.cpp index e7e61eed8..38a9161db 100644 --- a/firmware/common/i2cdev_max17055.cpp +++ b/firmware/common/i2cdev_max17055.cpp @@ -22,10 +22,10 @@ Boston, MA 02110-1301, USA. #include "i2cdev_max17055.hpp" #include "battery.hpp" #include "utility.hpp" -#include "portapack_persistent_memory.hpp" #include #include #include +#include "portapack_persistent_memory.hpp" namespace i2cdev { @@ -189,13 +189,33 @@ void I2cDev_MAX17055::update() { uint8_t batteryPercentage = 102; uint16_t voltage = 0; int32_t current = 0; - getBatteryInfo(validity, batteryPercentage, voltage, current); + bool battChanged = false; + + getBatteryInfo(validity, batteryPercentage, voltage, current, battChanged); // send local message - BatteryStateMessage msg{validity, batteryPercentage, current >= 25, voltage}; + BatteryStateMessage msg{validity, batteryPercentage, current >= 25, voltage, battChanged}; EventDispatcher::send_message(msg); } +void I2cDev_MAX17055::sleep_config(bool enable_sleep) { + uint16_t config1 = read_register(0x1D); + if (enable_sleep) { + // A COMMSH bit (bit 6) + config1 |= 0x0040; + // ShdnTimer (0x3F): + // THR = 0 -> ~45 sec (def, 0x0000) + // THR = 1 -> ~90 sec (0x1000) + // THR = 2 -> ~180 sec (0x2000) + write_register(0x3F, 0x2000); + } else { + // A COMMSH bit (bit 6) + config1 &= ~0x0040; + } + // write config register + write_register(0x1D, config1); +} + bool I2cDev_MAX17055::init(uint8_t addr_) { if (addr_ != I2CDEV_MAX17055_ADDR_1) return false; addr = addr_; @@ -207,8 +227,8 @@ bool I2cDev_MAX17055::init(uint8_t addr_) { // First-time or POR initialization return_status = full_reset_and_init(); } - partialInit(); // If you always want hibernation disabled - // statusClear(); // I am not sure if this should be here or not (Clear all bits in the Status register (0x00)) + chThdSleepMilliseconds(300); // wait for adc to fully wake up! + partialInit(); return return_status; } return false; @@ -232,9 +252,10 @@ bool I2cDev_MAX17055::full_reset_and_init() { if (!load_custom_parameters()) { return false; } - if (!clear_por()) { + /*if (!clear_por()) { return false; - } + }*/ + // don't clear it, we'll need to know the state return true; } @@ -253,7 +274,7 @@ bool I2cDev_MAX17055::initialize_custom_parameters() { if (!write_register(0x1E, 0x03C0)) return false; // IChgTerm if (!write_register(0x3A, 0x9661)) return false; // VEmpty if (!write_register(0x60, 0x0090)) return false; // Unknown register - if (!write_register(0x46, ((designcap / 32) * 44138) * designcap)) return false; // dPAcc + if (!write_register(0x46, ((designcap / 32) * 44138) / designcap)) return false; // dPAcc = dQAcc * 44138 / DesignCap (Maxim EZ config) if (!write_register(0xDB, 0x8000)) return false; // ModelCfg --we should wait till it loads here. While (ReadRegister(0xDB)&0x8000) Wait(10);//do not continue until ModelCFG.Refresh == 0 if (!write_register(0x40, 0x0001)) return false; // Set user mem to 1 return true; @@ -284,17 +305,18 @@ bool I2cDev_MAX17055::clear_por() { } bool I2cDev_MAX17055::needsInitialization() { - uint16_t UserMem1 = read_register(0x40); - - if (UserMem1 == 0) { - return true; + uint16_t status = read_register(0x00); + if (status == 0xFFFF) { + chThdSleepMilliseconds(15); + status = read_register(0x00); } - return false; + return (status & 0x0002) != 0; } void I2cDev_MAX17055::partialInit() { // Only update necessary volatile settings - setHibCFG(0x0000); // If you always want hibernation disabled + // setHibCFG(0x0000); // If you always want hibernation disabled. this is a lower resolution mode, when the ic is on, and measuring, but on lower freq. depends un tha current (mA). + sleep_config(true); // shut down the comm // Add any other volatile settings that need updating } @@ -312,7 +334,12 @@ bool I2cDev_MAX17055::detect() { if (dev_name == 0x4010) { return true; } - + // try again, we we just woke up. + chThdSleepMilliseconds(5); + dev_name = read_register(0x21); + if (dev_name == 0x4010) { + return true; + } // If DevName doesn't match, try reading Status register as a fallback uint16_t status = read_register(0x00); if (status != 0xFFFF && status != 0x0000) { @@ -354,14 +381,27 @@ bool I2cDev_MAX17055::write_register(const uint8_t reg, const uint16_t value) { return success; } -void I2cDev_MAX17055::getBatteryInfo(uint8_t& valid_mask, uint8_t& batteryPercentage, uint16_t& voltage, int32_t& current) { +void I2cDev_MAX17055::getBatteryInfo(uint8_t& valid_mask, uint8_t& batteryPercentage, uint16_t& voltage, int32_t& current, bool& battMayChanged) { // Read Status Register uint16_t status = read_register(0x00); + battMayChanged = false; voltage = averageMVoltage(); if ((status == 0 && voltage == 0) || (status == 0x0002 && voltage == 3600) || (status == 0x0002 && voltage == 0)) { valid_mask = 0; return; } + bool requires_reset = false; + + if (((status & 0xFF) >> 1) & 0x01) { // POR FLAG + requires_reset = true; + } + // Execute the reset if either flag was tripped + if (requires_reset) { + reInit(); // todo maybe don't do it automatically, just signal. but for now it is safer to do it. + // battMayChanged = true && portapack::persistent_memory::battery_replaceable(); // only signal a change if the battery is replaceable. othervise do it silently + // We don't want to signal this for now. the Voltage drop during power up messes this up a bit. + } + batteryPercentage = stateOfCharge(); current = instantCurrent(); valid_mask = 31; // BATT_VALID_VOLTAGE + CURRENT + PERCENT + BATT_VALID_CYCLES + BATT_VALID_TTEF @@ -551,15 +591,6 @@ bool I2cDev_MAX17055::setModelCfg(const uint8_t _Model_ID) { } bool I2cDev_MAX17055::setHibCFG(const uint16_t _Config) { - uint16_t config1_reg = read_register(0x1D); - - // (SHDN: 0x80) and (COMMSH: 0x40) Otherwise it will go into sleep mode after 45 seconds. - config1_reg &= ~(0x0080 | 0x0040); - - if (!write_register(0x1D, config1_reg)) { - return false; - } - return write_register(0xBA, _Config); } diff --git a/firmware/common/i2cdev_max17055.hpp b/firmware/common/i2cdev_max17055.hpp index 255b76ad6..87dd60d99 100644 --- a/firmware/common/i2cdev_max17055.hpp +++ b/firmware/common/i2cdev_max17055.hpp @@ -139,7 +139,7 @@ // Tmn, Vmx, Vmn, Imx, and Imn bits of the Status register // (00h) are not disabled. #ifndef MAX17055_Aen -#define MAX17055_Aen 1 +#define MAX17055_Aen 0 #endif // This allows the host to control the bias of the thermistor switch or @@ -175,7 +175,7 @@ // Set to 1 and set ETHRM or FTHRM to 1 to enable temperature measurements selected by Config.TSel. #ifndef MAX17055_Ten -#define MAX17055_Ten 1 +#define MAX17055_Ten 0 #endif // Set to 1 to enable device shutdown when the IC is mounted host side and the battery is removed. @@ -231,7 +231,7 @@ // Set this bit to 1 to enable temperature based alerts. // Write this bit to 0 to disable temperature alerts. This bit is set to 1 at power-up. #ifndef MAX17055_TAIrtEN -#define MAX17055_TAIrtEN 1 +#define MAX17055_TAIrtEN 0 #endif // Set this bit to 1 to enable alert output with the Status.dSOCi bit function. @@ -283,14 +283,17 @@ class I2cDev_MAX17055 : public I2cDev { void update() override; bool detect(); - void getBatteryInfo(uint8_t& valid_mask, uint8_t& batteryPercentage, uint16_t& voltage, int32_t& current); + void getBatteryInfo(uint8_t& valid_mask, uint8_t& batteryPercentage, uint16_t& voltage, int32_t& current, bool& battMayChanged); bool reset_learned(); float getValue(const char* entityName); uint16_t averageMVoltage(void); int32_t instantCurrent(void); uint16_t stateOfCharge(void); - bool reInit(); // call when battery parameters changed from ui. don't call if not needed, or the battery is not changed!!! + bool reInit(); // call when battery parameters changed from ui. don't call if not needed, or the battery is not changed!!! + bool getIsBattChanged() { return statusControl(MAX17055_POR); } // true if the ic thinks we have a new battery + void resetChangedFlag() { clear_por(); } // reset the flag + void sleep_config(bool enable_sleep); // if true, the ic can sleep after 3 minutes of inactivity (over i2c). can be waken up on any i2c communication (first packet may be dropped) private: const RegisterEntry* findEntry(const char* name) const; diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index ed2c77c5e..db712a168 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -1610,17 +1610,20 @@ class BatteryStateMessage : public Message { uint8_t valid_mask, uint8_t percent, bool on_charger, - uint16_t voltage) + uint16_t voltage, + bool battMayChanged) : Message{ID::BatteryStateData}, valid_mask{valid_mask}, percent{percent}, on_charger{on_charger}, - voltage{voltage} { + voltage{voltage}, + battMayChanged{battMayChanged} { } uint8_t valid_mask = 0; uint8_t percent = 0; bool on_charger = false; uint16_t voltage = 0; // mV + bool battMayChanged = false; }; class ProtoViewDataMessage : public Message { diff --git a/firmware/common/portapack_persistent_memory.cpp b/firmware/common/portapack_persistent_memory.cpp index 53571c8ca..d67fa4c6a 100644 --- a/firmware/common/portapack_persistent_memory.cpp +++ b/firmware/common/portapack_persistent_memory.cpp @@ -212,7 +212,7 @@ struct data_t { bool updown_frequency_tx_correction; bool lcd_normally_black : 1; bool encoder_dial_direction : 1; // false = normal, true = reverse - bool UNUSED_6 : 1; + bool battery_replaceable : 1; bool UNUSED_7 : 1; // up/down converter offset @@ -290,7 +290,7 @@ struct data_t { updown_frequency_tx_correction(false), lcd_normally_black(false), encoder_dial_direction(false), - UNUSED_6(false), + battery_replaceable(false), UNUSED_7(false), converter_frequency_offset(0), @@ -446,6 +446,7 @@ void defaults() { set_config_tx_gain_max_db(47); set_battery_cap_mah(0); + set_battery_replaceable(false); } void init() { @@ -1258,6 +1259,14 @@ void set_battery_cap_mah(uint16_t mah) { } } +void set_battery_replaceable(bool v) { + data->battery_replaceable = v; +} + +bool battery_replaceable() { + return data->battery_replaceable; +} + uint32_t battery_cap_mah() { if (battery_cap_valid()) { return data->misc_config.batt_cap_mah; diff --git a/firmware/common/portapack_persistent_memory.hpp b/firmware/common/portapack_persistent_memory.hpp index 9a0a0ae11..91bc8e432 100644 --- a/firmware/common/portapack_persistent_memory.hpp +++ b/firmware/common/portapack_persistent_memory.hpp @@ -395,6 +395,8 @@ uint32_t pmem_calculated_checksum(void); void set_battery_cap_mah(uint16_t mah); // 0 is not known; use assumed default based on device type/build config uint32_t battery_cap_mah(); bool battery_cap_valid(); +void set_battery_replaceable(bool v); +bool battery_replaceable(); size_t data_size(); diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index 59c0e07b0..2f66fcf7a 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -2184,7 +2184,7 @@ bool TextField::on_touch(TouchEvent event) { BatteryIcon::BatteryIcon(Rect parent_rect, uint8_t percent) : Widget(parent_rect) { - this->set_battery(percent <= 100 ? 1 : 0, percent, false); + this->set_battery(percent <= 100 ? 1 : 0, percent, false, false); set_focusable(true); } @@ -2195,11 +2195,13 @@ void BatteryIcon::getWidgetName(std::string& result) { result = "Battery percent"; } -void BatteryIcon::set_battery(uint8_t valid_mask, uint8_t percentage, bool charge) { - if (charge == charge_ && percent_ == percentage && valid_ == valid_mask) return; +void BatteryIcon::set_battery(uint8_t valid_mask, uint8_t percentage, bool charge, bool batt_changed) { + if (charge == charge_ && percent_ == percentage && valid_ == valid_mask && batt_changed == batt_changed_) return; percent_ = percentage; charge_ = charge; valid_ = valid_mask; + batt_changed_ = batt_changed; + if ((valid_mask & battery::BatteryManagement::BATT_VALID_VOLTAGE) != battery::BatteryManagement::BATT_VALID_VOLTAGE) percent_ = 102; // to indicate error set_dirty(); } @@ -2227,6 +2229,7 @@ void BatteryIcon::paint(Painter& painter) { ui::Rect rect = screen_rect(); // 10, 1 * 16 painter.fill_rectangle(rect, has_focus() || highlighted() ? Theme::getInstance()->fg_light->foreground : Theme::getInstance()->bg_dark->background); // clear ui::Color battColor = (charge_) ? Theme::getInstance()->fg_blue->foreground : Theme::getInstance()->fg_green->foreground; + if (batt_changed_) battColor = Theme::getInstance()->fg_orange->foreground; // batt body: painter.draw_vline({rect.left() + 1, rect.top() + 2}, rect.height() - 4, battColor); painter.draw_vline({rect.right() - 2, rect.top() + 2}, rect.height() - 4, battColor); @@ -2251,6 +2254,7 @@ void BatteryIcon::paint(Painter& painter) { else battColor = Theme::getInstance()->fg_red->foreground; } + if (batt_changed_) battColor = Theme::getInstance()->fg_orange->foreground; // fill the bars for (int y = pp; y < ppx; y++) { painter.draw_hline({rect.left() + 2, rect.top() + 3 + y}, rect.width() - 4, battColor); @@ -2261,7 +2265,7 @@ void BatteryIcon::paint(Painter& painter) { BatteryTextField::BatteryTextField(Rect parent_rect, uint8_t percent) : Widget(parent_rect) { - this->set_battery(percent <= 100 ? 1 : 0, percent, false); + this->set_battery(percent <= 100 ? 1 : 0, percent, false, false); set_focusable(true); } @@ -2275,8 +2279,11 @@ void BatteryTextField::paint(Painter& painter) { xdelta = 5; else if (txt_batt.length() == 2) xdelta = 2; + std::string post = " %"; + if (charge_) post = "+%"; + if (batt_changed_) post = "!%"; painter.draw_string({rect.left() + xdelta, rect.top()}, font::fixed_5x8, Theme::getInstance()->bg_dark->foreground, bg, txt_batt); - painter.draw_string({rect.left(), rect.top() + 8}, font::fixed_5x8, Theme::getInstance()->bg_dark->foreground, bg, (charge_) ? "+%" : " %"); + painter.draw_string({rect.left(), rect.top() + 8}, font::fixed_5x8, Theme::getInstance()->bg_dark->foreground, bg, post); } void BatteryTextField::getAccessibilityText(std::string& result) { @@ -2286,11 +2293,12 @@ void BatteryTextField::getWidgetName(std::string& result) { result = "Battery percent"; } -void BatteryTextField::set_battery(uint8_t valid_mask, uint8_t percentage, bool charge) { - if (charge == charge_ && percent_ == percentage && valid_ == valid_mask) return; +void BatteryTextField::set_battery(uint8_t valid_mask, uint8_t percentage, bool charge, bool batt_changed) { + if (charge == charge_ && percent_ == percentage && valid_ == valid_mask && batt_changed == batt_changed_) return; charge_ = charge; percent_ = percentage; valid_ = valid_mask; + batt_changed_ = batt_changed; if ((valid_mask & battery::BatteryManagement::BATT_VALID_VOLTAGE) != battery::BatteryManagement::BATT_VALID_VOLTAGE) percent_ = 102; // to indicate error set_dirty(); } diff --git a/firmware/common/ui_widget.hpp b/firmware/common/ui_widget.hpp index 04b6d3a20..19d7e15ea 100644 --- a/firmware/common/ui_widget.hpp +++ b/firmware/common/ui_widget.hpp @@ -808,7 +808,7 @@ class BatteryTextField : public Widget { BatteryTextField(Rect parent_rect, uint8_t percent); void paint(Painter& painter) override; - void set_battery(uint8_t valid_mask, uint8_t percentage, bool charge); + void set_battery(uint8_t valid_mask, uint8_t percentage, bool charge, bool batt_changed); void set_text(std::string_view value); bool on_key(KeyEvent key) override; @@ -821,6 +821,7 @@ class BatteryTextField : public Widget { uint8_t percent_{102}; uint8_t valid_{0}; bool charge_{false}; + bool batt_changed_{false}; Style style{ .font = font::fixed_5x8, @@ -835,7 +836,7 @@ class BatteryIcon : public Widget { BatteryIcon(Rect parent_rect, uint8_t percent); void paint(Painter& painter) override; - void set_battery(uint8_t valid_mask, uint8_t percentage, bool charge); + void set_battery(uint8_t valid_mask, uint8_t percentage, bool charge, bool batt_changed); bool on_key(KeyEvent key) override; bool on_touch(TouchEvent event) override; @@ -847,6 +848,7 @@ class BatteryIcon : public Widget { uint8_t percent_{102}; uint8_t valid_{0}; bool charge_{false}; + bool batt_changed_{false}; }; class NumberField : public Widget {