From f6a2d21624969754d399e8d24b3b12bc4835d37a Mon Sep 17 00:00:00 2001 From: Totoo Date: Mon, 29 Apr 2024 17:38:27 +0200 Subject: [PATCH] support for battery if any + TPMS app to ext (#2129) * batt initial * batt widgets * settings modify * batt info screen * ability to hide icon * battView * redo tmps * hide curr + charge if no data * fix flashing --- firmware/application/CMakeLists.txt | 2 + firmware/application/apps/ui_battinfo.cpp | 104 ++++++++++++++ firmware/application/apps/ui_battinfo.hpp | 83 +++++++++++ firmware/application/apps/ui_dfu_menu.cpp | 8 +- firmware/application/apps/ui_settings.cpp | 16 +++ firmware/application/apps/ui_settings.hpp | 29 ++-- firmware/application/bitmap.hpp | 76 +++++++++++ firmware/application/portapack.cpp | 3 +- firmware/application/portapack.hpp | 2 +- firmware/application/ui/ui_bmpview.cpp | 1 - firmware/application/ui_navigation.cpp | 51 ++++++- firmware/application/ui_navigation.hpp | 21 ++- firmware/common/ads1110.cpp | 25 ++-- firmware/common/ads1110.hpp | 11 +- firmware/common/battery.cpp | 111 +++++++++++++++ firmware/common/battery.hpp | 50 +++++++ firmware/common/message.hpp | 17 +++ .../common/portapack_persistent_memory.cpp | 20 ++- .../common/portapack_persistent_memory.hpp | 4 + firmware/common/ui_widget.cpp | 129 ++++++++++++++++++ firmware/common/ui_widget.hpp | 48 +++++++ firmware/graphics/icon_batt_icon.png | Bin 0 -> 167 bytes firmware/graphics/icon_batt_text.png | Bin 0 -> 188 bytes 23 files changed, 767 insertions(+), 44 deletions(-) create mode 100644 firmware/application/apps/ui_battinfo.cpp create mode 100644 firmware/application/apps/ui_battinfo.hpp create mode 100644 firmware/common/battery.cpp create mode 100644 firmware/common/battery.hpp create mode 100644 firmware/graphics/icon_batt_icon.png create mode 100644 firmware/graphics/icon_batt_text.png diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index 2eadc298..2aef23ea 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -177,6 +177,7 @@ set(CPPSRC ${COMMON}/utility.cpp ${COMMON}/wm8731.cpp ${COMMON}/ads1110.cpp + ${COMMON}/battery.cpp ${COMMON}/performance_counter.cpp ${COMMON}/bmpfile.cpp app_settings.cpp @@ -283,6 +284,7 @@ set(CPPSRC apps/ui_adsb_tx.cpp apps/ui_aprs_rx.cpp apps/ui_aprs_tx.cpp + apps/ui_battinfo.cpp apps/ui_bht_tx.cpp apps/ui_bmp_file_viewer.cpp apps/ui_btle_rx.cpp diff --git a/firmware/application/apps/ui_battinfo.cpp b/firmware/application/apps/ui_battinfo.cpp new file mode 100644 index 00000000..4d46367e --- /dev/null +++ b/firmware/application/apps/ui_battinfo.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ui_battinfo.hpp" + +#include "event_m0.hpp" +#include "portapack.hpp" + +#include + +using namespace portapack; + +namespace ui { + +void BattinfoView::focus() { + button_exit.focus(); +} + +// called each 1/60th of second, so 6 = 100ms +void BattinfoView::on_timer() { + if (++timer_counter == timer_period) { + timer_counter = 0; + update_result(); + } +} + +void BattinfoView::update_result() { + if (!battery::BatteryManagement::isDetected()) { + // todo show no batt management + text_percent.set("UNKNOWN"); + text_voltage.set("UNKNOWN"); + text_current.set("-"); + text_charge.set("-"); + return; + } + bool uichg = false; + battery::BatteryManagement::getBatteryInfo(percent, voltage, current, isCharging); + // update text fields + if (percent <= 100) + text_percent.set(to_string_dec_uint(percent) + " %"); + else + text_percent.set("UNKNOWN"); + if (voltage > 1) { + text_voltage.set(to_string_decimal(voltage / 1000.0, 3) + " V"); + } else { + text_voltage.set("UNKNOWN"); + } + if (current != 0) { + if (labels_opt.hidden()) uichg = true; + labels_opt.hidden(false); + text_current.hidden(false); + text_charge.hidden(false); + text_current.set(to_string_dec_int(current) + " mA"); + text_charge.set(isCharging ? "charge" : "discharge"); + labels_opt.hidden(false); + } else { + if (!labels_opt.hidden()) uichg = true; + labels_opt.hidden(true); + text_current.hidden(true); + text_charge.hidden(true); + } + if (uichg) set_dirty(); + // to update status bar too, send message in behalf of batt manager + BatteryStateMessage msg{percent, isCharging, voltage}; + EventDispatcher::send_message(msg); +} + +BattinfoView::BattinfoView(NavigationView& nav) + : nav_{nav} { + add_children({&labels, + &labels_opt, + &text_percent, + &text_voltage, + &text_current, + &text_charge, + &button_exit}); + + button_exit.on_select = [this, &nav](Button&) { + nav.pop(); + }; + + update_result(); +} + +} // namespace ui diff --git a/firmware/application/apps/ui_battinfo.hpp b/firmware/application/apps/ui_battinfo.hpp new file mode 100644 index 00000000..dc5c3261 --- /dev/null +++ b/firmware/application/apps/ui_battinfo.hpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __UI_BATTINFO_H__ +#define __UI_BATTINFO_H__ + +#include "ui.hpp" +#include "ui_widget.hpp" +#include "ui_navigation.hpp" +#include "string_format.hpp" + +namespace ui { +class BattinfoView : public View { + public: + BattinfoView(NavigationView& nav); + void focus() override; + std::string title() const override { return "Battery"; }; + + private: + void update_result(); + void on_timer(); + NavigationView& nav_; + uint16_t timer_period = 60; + uint16_t timer_counter = 0; + uint8_t percent = 0; + uint16_t voltage = 0; + int32_t current = 0; + bool isCharging = false; + + Labels labels{ + {{2 * 8, 1 * 16}, "Percent:", Color::light_grey()}, + {{2 * 8, 2 * 16}, "Voltage:", Color::light_grey()}}; + + Labels labels_opt{ + {{2 * 8, 3 * 16}, "Current:", Color::light_grey()}, + {{2 * 8, 4 * 16}, "Charge:", Color::light_grey()}}; + + Text text_percent{ + {13 * 8, 1 * 16, 10 * 16, 16}, + "-"}; + Text text_voltage{ + {13 * 8, 2 * 16, 10 * 16, 16}, + "-"}; + Text text_current{ + {13 * 8, 3 * 16, 10 * 16, 16}, + "-"}; + Text text_charge{ + {13 * 8, 4 * 16, 10 * 16, 16}, + "-"}; + + Button button_exit{ + {72, 17 * 16, 96, 32}, + "Back"}; + + MessageHandlerRegistration message_handler_frame_sync{ + Message::ID::DisplayFrameSync, + [this](const Message* const) { + this->on_timer(); + }}; +}; + +} /* namespace ui */ + +#endif /*__UI_BATTINFO__*/ diff --git a/firmware/application/apps/ui_dfu_menu.cpp b/firmware/application/apps/ui_dfu_menu.cpp index 887a33f0..62c80773 100644 --- a/firmware/application/apps/ui_dfu_menu.cpp +++ b/firmware/application/apps/ui_dfu_menu.cpp @@ -42,7 +42,7 @@ DfuMenu::DfuMenu(NavigationView& nav) &text_info_line_9, &text_info_line_10}); - if (portapack::battery_ads1110.isDetected()) { + if (battery::BatteryManagement::isDetected()) { add_child(&voltage_label); add_child(&text_info_line_11); } @@ -53,7 +53,7 @@ void DfuMenu::paint(Painter& painter) { size_t m0_fragmented_free_space = 0; const auto m0_fragments = chHeapStatus(NULL, &m0_fragmented_free_space); - auto lines = (portapack::battery_ads1110.isDetected() ? 11 : 10) + 2; + auto lines = (battery::BatteryManagement::isDetected() ? 11 : 10) + 2; text_info_line_1.set(to_string_dec_uint(chCoreStatus(), 6)); text_info_line_2.set(to_string_dec_uint(m0_fragmented_free_space, 6)); @@ -65,8 +65,8 @@ void DfuMenu::paint(Painter& painter) { text_info_line_8.set(to_string_dec_uint(shared_memory.m4_performance_counter, 6)); text_info_line_9.set(to_string_dec_uint(shared_memory.m4_buffer_missed, 6)); text_info_line_10.set(to_string_dec_uint(chTimeNow() / 1000, 6)); - if (portapack::battery_ads1110.isDetected()) { - text_info_line_11.set(to_string_decimal_padding(portapack::battery_ads1110.readVoltage(), 3, 6)); + if (battery::BatteryManagement::isDetected()) { + text_info_line_11.set(to_string_decimal_padding((float)battery::BatteryManagement::getVoltage() / 1000.0, 3, 6)); } constexpr auto margin = 5; diff --git a/firmware/application/apps/ui_settings.cpp b/firmware/application/apps/ui_settings.cpp index e5ba9cde..aed8f67e 100644 --- a/firmware/application/apps/ui_settings.cpp +++ b/firmware/application/apps/ui_settings.cpp @@ -329,6 +329,10 @@ SetUIView::SetUIView(NavigationView& nav) { if (audio::speaker_disable_supported()) { add_child(&toggle_speaker); } + if (battery::BatteryManagement::isDetected()) { + add_child(&toggle_battery_icon); + add_child(&toggle_battery_text); + } checkbox_disable_touchscreen.set_value(pmem::disable_touchscreen()); checkbox_showsplash.set_value(pmem::config_splash()); @@ -355,6 +359,8 @@ SetUIView::SetUIView(NavigationView& nav) { toggle_speaker.set_value(!pmem::ui_hide_speaker()); toggle_mute.set_value(!pmem::ui_hide_mute()); toggle_fake_brightness.set_value(!pmem::ui_hide_fake_brightness()); + toggle_battery_icon.set_value(!pmem::ui_hide_battery_icon()); + toggle_battery_text.set_value(!pmem::ui_hide_numeric_battery()); toggle_sd_card.set_value(!pmem::ui_hide_sd_card()); button_save.on_select = [&nav, this](Button&) { @@ -382,6 +388,8 @@ SetUIView::SetUIView(NavigationView& nav) { pmem::set_ui_hide_speaker(!toggle_speaker.value()); pmem::set_ui_hide_mute(!toggle_mute.value()); pmem::set_ui_hide_fake_brightness(!toggle_fake_brightness.value()); + pmem::set_ui_hide_battery_icon(!toggle_battery_icon.value()); + pmem::set_ui_hide_numeric_battery(!toggle_battery_text.value()); pmem::set_ui_hide_sd_card(!toggle_sd_card.value()); send_system_refresh(); @@ -807,6 +815,7 @@ SetMenuColorView::SetMenuColorView(NavigationView& nav) { &field_green_level, &field_blue_level, &button_save, + &button_reset, &button_cancel}); button_sample.set_focusable(false); @@ -824,6 +833,13 @@ SetMenuColorView::SetMenuColorView(NavigationView& nav) { field_green_level.on_change = color_changed_fn; field_blue_level.on_change = color_changed_fn; + button_reset.on_select = [&nav, this](Button&) { + field_red_level.set_value(127); + field_green_level.set_value(127); + field_blue_level.set_value(127); + set_dirty(); + }; + button_save.on_select = [&nav, this](Button&) { Color c = Color(field_red_level.value(), field_green_level.value(), field_blue_level.value()); pmem::set_menu_color(c); diff --git a/firmware/application/apps/ui_settings.hpp b/firmware/application/apps/ui_settings.hpp index de7929a1..92c0bb3d 100644 --- a/firmware/application/apps/ui_settings.hpp +++ b/firmware/application/apps/ui_settings.hpp @@ -323,37 +323,45 @@ class SetUIView : public View { }; ImageToggle toggle_camera{ - {6 * 8, 14 * 16 + 2, 16, 16}, + {2 * 8, 14 * 16 + 2, 16, 16}, &bitmap_icon_camera}; ImageToggle toggle_sleep{ - {8 * 8, 14 * 16 + 2, 16, 16}, + {4 * 8, 14 * 16 + 2, 16, 16}, &bitmap_icon_sleep}; ImageToggle toggle_stealth{ - {10 * 8, 14 * 16 + 2, 16, 16}, + {6 * 8, 14 * 16 + 2, 16, 16}, &bitmap_icon_stealth}; ImageToggle toggle_converter{ - {12 * 8, 14 * 16 + 2, 16, 16}, + {8 * 8, 14 * 16 + 2, 16, 16}, &bitmap_icon_upconvert}; ImageToggle toggle_bias_tee{ - {14 * 8, 14 * 16 + 2, 16, 16}, + {10 * 8, 14 * 16 + 2, 16, 16}, &bitmap_icon_biast_off}; ImageToggle toggle_clock{ - {16 * 8, 14 * 16 + 2, 8, 16}, + {12 * 8, 14 * 16 + 2, 8, 16}, &bitmap_icon_clk_ext}; ImageToggle toggle_mute{ - {17 * 8, 14 * 16 + 2, 16, 16}, + {13 * 8, 14 * 16 + 2, 16, 16}, &bitmap_icon_speaker_and_headphones_mute}; ImageToggle toggle_speaker{ - {19 * 8, 14 * 16 + 2, 16, 16}, + {15 * 8, 14 * 16 + 2, 16, 16}, &bitmap_icon_speaker_mute}; + ImageToggle toggle_battery_icon{ + {17 * 8, 14 * 16 + 2, 16, 16}, + &bitmap_icon_batt_icon}; + + ImageToggle toggle_battery_text{ + {19 * 8, 14 * 16 + 2, 16, 16}, + &bitmap_icon_batt_text}; + ImageToggle toggle_fake_brightness{ {21 * 8, 14 * 16 + 2, 16, 16}, &bitmap_icon_brightness}; @@ -789,6 +797,11 @@ class SetMenuColorView : public View { ' ', }; + Button button_reset{ + {2 * 8, 13 * 16, 12 * 8, 32}, + "Reset", + }; + Button button_save{ {2 * 8, 16 * 16, 12 * 8, 32}, "Save"}; diff --git a/firmware/application/bitmap.hpp b/firmware/application/bitmap.hpp index f4b1a2c2..37ef8690 100644 --- a/firmware/application/bitmap.hpp +++ b/firmware/application/bitmap.hpp @@ -2447,6 +2447,82 @@ static constexpr Bitmap bitmap_icon_camera{ {16, 16}, bitmap_icon_camera_data}; +static constexpr uint8_t bitmap_icon_batt_icon_data[] = { + 0xC0, + 0x03, + 0xC0, + 0x03, + 0xF0, + 0x0F, + 0x10, + 0x08, + 0x10, + 0x08, + 0x10, + 0x08, + 0x10, + 0x08, + 0x10, + 0x08, + 0xF0, + 0x0F, + 0xF0, + 0x0F, + 0xF0, + 0x0F, + 0xF0, + 0x0F, + 0xF0, + 0x0F, + 0xF0, + 0x0F, + 0xF0, + 0x0F, + 0x00, + 0x00, +}; +static constexpr Bitmap bitmap_icon_batt_icon{ + {16, 16}, + bitmap_icon_batt_icon_data}; + +static constexpr uint8_t bitmap_icon_batt_text_data[] = { + 0x00, + 0x00, + 0x30, + 0x06, + 0x48, + 0x09, + 0x48, + 0x09, + 0x70, + 0x0E, + 0x40, + 0x08, + 0x30, + 0x06, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x20, + 0x00, + 0x10, + 0x00, + 0x48, + 0x00, + 0x00, + 0x00, +}; +static constexpr Bitmap bitmap_icon_batt_text{ + {16, 16}, + bitmap_icon_batt_text_data}; + static constexpr uint8_t bitmap_icon_tools_wipesd_data[] = { 0xF0, 0x3F, diff --git a/firmware/application/portapack.cpp b/firmware/application/portapack.cpp index 806a1f33..0a211dce 100644 --- a/firmware/application/portapack.cpp +++ b/firmware/application/portapack.cpp @@ -85,7 +85,6 @@ ClockManager clock_manager{ WM8731 audio_codec_wm8731{i2c0, 0x1a}; AK4951 audio_codec_ak4951{i2c0, 0x12}; -ads1110::ADS1110 battery_ads1110{i2c0, 0x48}; ReceiverModel receiver_model; TransmitterModel transmitter_model; @@ -587,7 +586,7 @@ init_status_t init() { chThdSleepMilliseconds(10); audio::init(portapack_audio_codec()); - battery_ads1110.init(); + battery::BatteryManagement::init(); if (lcd_fast_setup) draw_splash_screen_icon(4, ui::bitmap_icon_speaker); diff --git a/firmware/application/portapack.hpp b/firmware/application/portapack.hpp index ba5f1147..fa3efc86 100644 --- a/firmware/application/portapack.hpp +++ b/firmware/application/portapack.hpp @@ -38,6 +38,7 @@ #include "radio.hpp" #include "clock_manager.hpp" #include "temperature_logger.hpp" +#include "battery.hpp" /* TODO: This would be better as a class to add * guardrails on setting properties. */ @@ -63,7 +64,6 @@ extern portapack::USBSerial usb_serial; extern si5351::Si5351 clock_generator; extern ClockManager clock_manager; -extern ads1110::ADS1110 battery_ads1110; extern ReceiverModel receiver_model; extern TransmitterModel transmitter_model; diff --git a/firmware/application/ui/ui_bmpview.cpp b/firmware/application/ui/ui_bmpview.cpp index 1d1fdd7c..c3d50189 100644 --- a/firmware/application/ui/ui_bmpview.cpp +++ b/firmware/application/ui/ui_bmpview.cpp @@ -1,5 +1,4 @@ #include "ui_bmpview.hpp" -#include "usb_serial_asyncmsg.hpp" #include "portapack.hpp" bool BMPViewer::load_bmp(const std::filesystem::path& file) { diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index 55abace9..813b3b2a 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -84,6 +84,7 @@ #include "ui_weatherstation.hpp" #include "ui_subghzd.hpp" #include "ui_whipcalc.hpp" +#include "ui_battinfo.hpp" #include "ui_external_items_menu_loader.hpp" // #include "acars_app.hpp" @@ -334,6 +335,9 @@ SystemStatusView::SystemStatusView( refresh(); }; + battery_icon.on_select = [this]() { on_battery_details(); }; + battery_text.on_select = [this]() { on_battery_details(); }; + button_fake_brightness.on_select = [this](ImageButton&) { set_dirty(); pmem::toggle_fake_brightness_level(); @@ -370,6 +374,26 @@ SystemStatusView::SystemStatusView( refresh(); } +// when battery icon / text is clicked +void SystemStatusView::on_battery_details() { + if (!nav_.is_valid()) return; + if (batt_info_up) return; + batt_info_up = true; + nav_.push(); + nav_.set_on_pop([this]() { + batt_info_up = false; + }); +} + +void SystemStatusView::on_battery_data(const BatteryStateMessage* msg) { + if (!pmem::ui_hide_numeric_battery()) { + battery_text.set_battery(msg->percent, msg->on_charger); + } + if (!pmem::ui_hide_battery_icon()) { + battery_icon.set_battery(msg->percent, msg->on_charger); + }; +} + void SystemStatusView::refresh() { // NB: Order of insertion is the display order Left->Right. // TODO: Might be better to support hide and only add once. @@ -386,8 +410,20 @@ void SystemStatusView::refresh() { if (audio::speaker_disable_supported() && !pmem::ui_hide_speaker()) status_icons.add(&toggle_speaker); if (!pmem::ui_hide_fake_brightness()) status_icons.add(&button_fake_brightness); + if (battery::BatteryManagement::isDetected()) { + uint8_t percent = battery::BatteryManagement::getPercent(); + if (!pmem::ui_hide_battery_icon()) { + status_icons.add(&battery_icon); + battery_text.set_battery(percent, false); // got an on select, that may pop up the details of the battery. + }; + if (!pmem::ui_hide_numeric_battery()) { + status_icons.add(&battery_text); + battery_text.set_battery(percent, false); + } + } if (!pmem::ui_hide_sd_card()) status_icons.add(&sd_card_status_view); + status_icons.update_layout(); // Clock status @@ -667,8 +703,9 @@ void NavigationView::display_modal( const std::string& title, const std::string& message, modal_t type, - std::function on_choice) { - push(title, message, type, on_choice); + std::function on_choice, + bool compact) { + push(title, message, type, on_choice, compact); } void NavigationView::free_view() { @@ -997,11 +1034,13 @@ ModalMessageView::ModalMessageView( const std::string& title, const std::string& message, modal_t type, - std::function on_choice) + std::function on_choice, + bool compact) : title_{title}, message_{message}, type_{type}, - on_choice_{on_choice} { + on_choice_{on_choice}, + compact{compact} { if (type == INFO) { add_child(&button_ok); button_ok.on_select = [this, &nav](Button&) { @@ -1034,13 +1073,13 @@ ModalMessageView::ModalMessageView( } void ModalMessageView::paint(Painter& painter) { - portapack::display.drawBMP({100, 48}, modal_warning_bmp, false); + if (!compact) portapack::display.drawBMP({100, 48}, modal_warning_bmp, false); // Break lines. auto lines = split_string(message_, '\n'); for (size_t i = 0; i < lines.size(); ++i) { painter.draw_string( - {1 * 8, (Coord)(120 + (i * 16))}, + {1 * 8, (Coord)(((compact) ? 8 * 3 : 120) + (i * 16))}, style(), lines[i]); } diff --git a/firmware/application/ui_navigation.hpp b/firmware/application/ui_navigation.hpp index 0340da11..ccc99088 100644 --- a/firmware/application/ui_navigation.hpp +++ b/firmware/application/ui_navigation.hpp @@ -120,7 +120,8 @@ class NavigationView : public View { const std::string& title, const std::string& message, modal_t type, - std::function on_choice = nullptr); + std::function on_choice = nullptr, + bool compact = false); void focus() override; @@ -191,7 +192,7 @@ class SystemStatusView : public View { private: static constexpr auto default_title = ""; - + bool batt_info_up = false; // to prevent show multiple batt info dialog NavigationView& nav_; Rectangle backdrop{ @@ -278,6 +279,9 @@ class SystemStatusView : public View { SDCardStatusView sd_card_status_view{ {0, 0 * 16, 2 * 8, 1 * 16}}; + BatteryTextField battery_text{{0, 0, 2 * 8, 1 * 16}, 102}; + BatteryIcon battery_icon{{0, 0, 10, 1 * 16}, 102}; + void on_converter(); void on_bias_tee(); void on_camera(); @@ -285,6 +289,8 @@ class SystemStatusView : public View { void refresh(); void on_clk(); void rtc_battery_workaround(); + void on_battery_data(const BatteryStateMessage* msg); + void on_battery_details(); MessageHandlerRegistration message_handler_refresh{ Message::ID::StatusRefresh, @@ -292,6 +298,13 @@ class SystemStatusView : public View { (void)p; this->refresh(); }}; + + MessageHandlerRegistration message_handler_battery{ + Message::ID::BatteryStateData, + [this](const Message* const p) { + const auto message = static_cast(p); + this->on_battery_data(message); + }}; }; class InformationView : public View { @@ -418,7 +431,8 @@ class ModalMessageView : public View { const std::string& title, const std::string& message, modal_t type, - std::function on_choice); + std::function on_choice, + bool compact = false); void paint(Painter& painter) override; void focus() override; @@ -430,6 +444,7 @@ class ModalMessageView : public View { const std::string message_; const modal_t type_; const std::function on_choice_; + const bool compact; Button button_ok{ {10 * 8, 14 * 16, 10 * 8, 48}, diff --git a/firmware/common/ads1110.cpp b/firmware/common/ads1110.cpp index 04bed5ea..191954ee 100644 --- a/firmware/common/ads1110.cpp +++ b/firmware/common/ads1110.cpp @@ -24,10 +24,11 @@ #include #include +namespace battery { namespace ads1110 { -constexpr float BATTERY_MIN_VOLTAGE = 3.0; -constexpr float BATTERY_MAX_VOLTAGE = 4.0; +constexpr uint16_t BATTERY_MIN_VOLTAGE = 3000; +constexpr uint16_t BATTERY_MAX_VOLTAGE = 4000; void ADS1110::init() { if (!detected_) { @@ -50,6 +51,7 @@ bool ADS1110::detect() { return true; } } + detected_ = false; return false; } @@ -57,7 +59,8 @@ bool ADS1110::write(const uint8_t value) { return bus.transmit(bus_address, &value, 1); } -float ADS1110::readVoltage() { +// returns the batt voltage in mV +uint16_t ADS1110::readVoltage() { // Read the conversion result uint8_t data[3]; if (!bus.receive(bus_address, data, 3)) { @@ -67,7 +70,7 @@ float ADS1110::readVoltage() { uint16_t raw = (static_cast(data[0]) << 8) | data[1]; // Calculate the voltage based on the output code - float voltage = 0.0f; + int16_t voltage = 0; float minCode = 0; float pga = 0.0f; @@ -110,21 +113,21 @@ float ADS1110::readVoltage() { } // 2.048 is the reference voltage & 2.0 is to make up for the voltage divider - voltage = raw / (-1.0 * minCode) * pga * 2.048 * 2.0; - - return voltage; + voltage = (int16_t)(raw / (-1.0 * minCode) * pga * 2.048 * 2.0 * 1000.0); // v to mV + if (voltage < 0) voltage *= -1; // should not happen in this build, but prevent it + return (uint16_t)voltage; } -void ADS1110::getBatteryInfo(float& batteryPercentage, float& voltage) { +void ADS1110::getBatteryInfo(uint8_t& batteryPercentage, uint16_t& voltage) { voltage = readVoltage(); // Calculate the remaining battery percentage - batteryPercentage = (voltage - BATTERY_MIN_VOLTAGE) / - (BATTERY_MAX_VOLTAGE - BATTERY_MIN_VOLTAGE) * 100.0; + batteryPercentage = (float)(voltage - BATTERY_MIN_VOLTAGE) / (float)(BATTERY_MAX_VOLTAGE - BATTERY_MIN_VOLTAGE) * 100.0; // Limit the values to the valid range - batteryPercentage = std::clamp(batteryPercentage, 0.0f, 100.0f); + batteryPercentage = (batteryPercentage > 100) ? 100 : batteryPercentage; // ToDo: if its > 4, then 100%, if < 3 then 0% } } /* namespace ads1110 */ +} // namespace battery \ No newline at end of file diff --git a/firmware/common/ads1110.hpp b/firmware/common/ads1110.hpp index 7b5231d2..312e051a 100644 --- a/firmware/common/ads1110.hpp +++ b/firmware/common/ads1110.hpp @@ -27,11 +27,10 @@ #include #include "i2c_pp.hpp" - +namespace battery { namespace ads1110 { using address_t = uint8_t; -using reg_t = uint16_t; class ADS1110 { public: @@ -39,10 +38,11 @@ class ADS1110 { : bus(bus), bus_address(bus_address), detected_(false) {} void init(); + bool detect(); bool isDetected() const { return detected_; } - float readVoltage(); - void getBatteryInfo(float& batteryPercentage, float& voltage); + uint16_t readVoltage(); + void getBatteryInfo(uint8_t& batteryPercentage, uint16_t& voltage); private: I2C& bus; @@ -50,9 +50,8 @@ class ADS1110 { bool detected_; bool write(const uint8_t value); - bool detect(); }; } /* namespace ads1110 */ - +} // namespace battery #endif /* __ADS1110_H__ */ \ No newline at end of file diff --git a/firmware/common/battery.cpp b/firmware/common/battery.cpp new file mode 100644 index 00000000..19be9fc1 --- /dev/null +++ b/firmware/common/battery.cpp @@ -0,0 +1,111 @@ +#include "battery.hpp" +#include "event_m0.hpp" +#include "portapack.hpp" +#include "ads1110.hpp" + +// uncomment if you want to emulate batt management system +// #define USE_BATT_EMULATOR + +extern I2C portapack::i2c0; + +namespace battery { + +constexpr uint32_t BATTERY_UPDATE_INTERVAL = 30000; +BatteryManagement::BatteryModules BatteryManagement::detected_ = BatteryManagement::BATT_NONE; + +ads1110::ADS1110 battery_ads1110{portapack::i2c0, 0x48}; + +Thread* BatteryManagement::thread = nullptr; + +void BatteryManagement::init() { + // try to detect supported modules + detected_ = BATT_NONE; + if (battery_ads1110.detect()) { + battery_ads1110.init(); + detected_ = BATT_ADS1110; + } + + // add new supported module detect + init here + +#ifdef USE_BATT_EMULATOR + if (detected_ == BATT_NONE) { + detected_ = BATT_EMULATOR; + } +#endif + + if (detected_ != BATT_NONE) { + // sets timer to query and broadcats this info + create_thread(); + } +} + +// sets the values, it the currend module supports it. +bool BatteryManagement::getBatteryInfo(uint8_t& batteryPercentage, uint16_t& voltage, int32_t& current, bool& isCharging) { + if (detected_ == BATT_NONE) return false; + if (detected_ == BATT_ADS1110) { + battery_ads1110.getBatteryInfo(batteryPercentage, voltage); + return true; + } + // add new module query here + +#ifdef USE_BATT_EMULATOR + if (detected_ == BATT_EMULATOR) { + batteryPercentage += 5; // % + if (batteryPercentage > 100) batteryPercentage = 0; + voltage = rand() % 1000 + 3000; // mV + current = rand() % 150; // mA + isCharging = rand() % 2; + return true; + } +#endif + + (void)isCharging; // keep the compiler calm + (void)current; + return false; +} + +uint8_t BatteryManagement::getPercent() { + if (detected_ == BATT_NONE) return 102; + uint8_t batteryPercentage = 0; + bool isCharging = false; + uint16_t voltage = 0; + int32_t current = 0; + getBatteryInfo(batteryPercentage, voltage, current, isCharging); + return batteryPercentage; +} + +uint16_t BatteryManagement::getVoltage() { + if (detected_ == BATT_NONE) return 0; + if (detected_ == BATT_NONE) return 102; + uint8_t batteryPercentage = 0; + bool isCharging = false; + uint16_t voltage = 0; + int32_t current = 0; + getBatteryInfo(batteryPercentage, voltage, current, isCharging); + return voltage; +} + +msg_t BatteryManagement::timer_fn(void* arg) { + (void)arg; + if (!detected_) return 0; + uint8_t batteryPercentage = 102; + bool isCharging = false; + uint16_t voltage = 0; + int32_t current = 0; + chThdSleepMilliseconds(1000); // wait ui for fully load + while (1) { + if (BatteryManagement::getBatteryInfo(batteryPercentage, voltage, current, isCharging)) { + // send local message + BatteryStateMessage msg{batteryPercentage, isCharging, voltage}; + EventDispatcher::send_message(msg); + } + chThdSleepMilliseconds(BATTERY_UPDATE_INTERVAL); + } + return 0; +} + +void BatteryManagement::create_thread() { + thread = chThdCreateFromHeap(NULL, 512, NORMALPRIO, BatteryManagement::timer_fn, nullptr); +} + +} // namespace battery \ No newline at end of file diff --git a/firmware/common/battery.hpp b/firmware/common/battery.hpp new file mode 100644 index 00000000..2ea05cca --- /dev/null +++ b/firmware/common/battery.hpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 HTotoo. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __BATTERY_H__ +#define __BATTERY_H__ + +#include +#include "ch.h" + +namespace battery { + +class BatteryManagement { + public: + enum BatteryModules { + BATT_NONE = 0, + BATT_ADS1110 = 1, + BATT_EMULATOR = 254 + }; + static void init(); + static bool isDetected() { return detected_ != BATT_NONE; } + static bool getBatteryInfo(uint8_t& batteryPercentage, uint16_t& voltage, int32_t& current, bool& isCharging); + static uint16_t getVoltage(); + static uint8_t getPercent(); + + private: + static void create_thread(); + static msg_t timer_fn(void* arg); + static Thread* thread; + static BatteryModules detected_; // if there is any batt management system +}; +}; // namespace battery +#endif \ No newline at end of file diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index 9f5da610..2517dc76 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -123,6 +123,7 @@ class Message { EnvironmentData = 65, AudioBeep = 66, PocsagTosend = 67, + BatteryStateData = 68, MAX }; @@ -1409,4 +1410,20 @@ class PocsagTosendMessage : public Message { uint64_t addr = 0; }; +class BatteryStateMessage : public Message { + public: + constexpr BatteryStateMessage( + uint8_t percent, + bool on_charger, + uint16_t voltage) + : Message{ID::BatteryStateData}, + percent{percent}, + on_charger{on_charger}, + voltage{voltage} { + } + uint8_t percent = 0; + bool on_charger = false; + uint16_t voltage = 0; // mV +}; + #endif /*__MESSAGE_H__*/ diff --git a/firmware/common/portapack_persistent_memory.cpp b/firmware/common/portapack_persistent_memory.cpp index 70ec9804..f867fdad 100644 --- a/firmware/common/portapack_persistent_memory.cpp +++ b/firmware/common/portapack_persistent_memory.cpp @@ -132,8 +132,8 @@ struct ui_config2_t { bool hide_mute : 1; bool hide_fake_brightness : 1; - bool UNUSED_1 : 1; - bool UNUSED_2 : 1; + bool hide_numeric_battery : 1; + bool hide_battery_icon : 1; bool UNUSED_3 : 1; bool UNUSED_4 : 1; bool UNUSED_5 : 1; @@ -423,6 +423,7 @@ void defaults() { set_encoder_dial_sensitivity(DIAL_SENSITIVITY_NORMAL); set_config_speaker_disable(true); // Disable AK4951 speaker by default (in case of OpenSourceSDRLab H2) set_menu_color(Color::grey()); + set_ui_hide_numeric_battery(true); // hide the numeric battery by default - no space to display it // Default values for recon app. set_recon_autosave_freqs(false); @@ -954,6 +955,13 @@ bool ui_hide_fake_brightness() { return data->ui_config2.hide_fake_brightness; } +bool ui_hide_numeric_battery() { + return data->ui_config2.hide_numeric_battery; +} +bool ui_hide_battery_icon() { + return data->ui_config2.hide_battery_icon; +} + void set_ui_hide_speaker(bool v) { data->ui_config2.hide_speaker = v; } @@ -986,6 +994,12 @@ void set_ui_hide_sd_card(bool v) { void set_ui_hide_fake_brightness(bool v) { data->ui_config2.hide_fake_brightness = v; } +void set_ui_hide_numeric_battery(bool v) { + data->ui_config2.hide_numeric_battery = v; +} +void set_ui_hide_battery_icon(bool v) { + data->ui_config2.hide_battery_icon = v; +} /* Converter */ bool config_converter() { @@ -1248,6 +1262,8 @@ bool debug_dump() { pmem_dump_file.write_line("ui_config2 hide_sd_card: " + to_string_dec_uint(data->ui_config2.hide_sd_card)); pmem_dump_file.write_line("ui_config2 hide_mute: " + to_string_dec_uint(data->ui_config2.hide_mute)); pmem_dump_file.write_line("ui_config2 hide_fake_brightness: " + to_string_dec_uint(data->ui_config2.hide_fake_brightness)); + pmem_dump_file.write_line("ui_config2 hide_battery_icon: " + to_string_dec_uint(data->ui_config2.hide_battery_icon)); + pmem_dump_file.write_line("ui_config2 hide_numeric_battery: " + to_string_dec_uint(data->ui_config2.hide_numeric_battery)); // misc_config bits pmem_dump_file.write_line("misc_config config_audio_mute: " + to_string_dec_int(config_audio_mute())); diff --git a/firmware/common/portapack_persistent_memory.hpp b/firmware/common/portapack_persistent_memory.hpp index a34973ef..3a042ccb 100644 --- a/firmware/common/portapack_persistent_memory.hpp +++ b/firmware/common/portapack_persistent_memory.hpp @@ -335,6 +335,8 @@ bool ui_hide_sleep(); bool ui_hide_bias_tee(); bool ui_hide_clock(); bool ui_hide_fake_brightness(); +bool ui_hide_numeric_battery(); +bool ui_hide_battery_icon(); bool ui_hide_sd_card(); void set_ui_hide_speaker(bool v); void set_ui_hide_mute(bool v); @@ -345,6 +347,8 @@ void set_ui_hide_sleep(bool v); void set_ui_hide_bias_tee(bool v); void set_ui_hide_clock(bool v); void set_ui_hide_fake_brightness(bool v); +void set_ui_hide_numeric_battery(bool v); +void set_ui_hide_battery_icon(bool v); void set_ui_hide_sd_card(bool v); // sd persisting settings diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index 25bee4d6..588d4b18 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -2044,6 +2044,135 @@ bool TextField::on_touch(TouchEvent event) { return false; } +/* BatteryIcon *************************************************************/ + +BatteryIcon::BatteryIcon(Rect parent_rect, uint8_t percent) + : Widget(parent_rect) { + this->set_battery(percent, false); + set_focusable(true); +} + +void BatteryIcon::getAccessibilityText(std::string& result) { + result = to_string_dec_uint(percent_) + "%"; +} +void BatteryIcon::getWidgetName(std::string& result) { + result = "Battery percent"; +} + +void BatteryIcon::set_battery(uint8_t percentage, bool charge) { + if (charge == charge_ && percent_ == percentage) return; + percent_ = percentage; + charge_ = charge; + set_dirty(); +} + +bool BatteryIcon::on_key(KeyEvent key) { + if (key == KeyEvent::Select && on_select) { + on_select(); + return true; + } + return false; +} + +bool BatteryIcon::on_touch(TouchEvent event) { + if (event.type == TouchEvent::Type::Start) { + focus(); + return true; + } + if (event.type == TouchEvent::Type::End && on_select) { + on_select(); + return true; + } + return false; +} +void BatteryIcon::paint(Painter& painter) { + ui::Rect rect = screen_rect(); // 10, 1 * 16 + painter.fill_rectangle(rect, has_focus() || highlighted() ? Color::light_grey() : Color::dark_grey()); // clear + ui::Color battColor = (charge_) ? Color::cyan() : Color::green(); + // 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); + painter.draw_hline({rect.left() + 1, rect.top() + 2}, rect.width() - 2, battColor); + painter.draw_hline({rect.left() + 1, rect.bottom() - 2}, rect.width() - 2, battColor); + // batt cap: + painter.draw_hline({rect.left() + 3, rect.top() + 1}, rect.width() - 6, battColor); + painter.draw_hline({rect.left() + 3, 0}, rect.width() - 6, battColor); + if (percent_ > 100) { // error / unk + painter.draw_string({rect.left() + 2, rect.top() + 3}, font::fixed_5x8, Color::white(), Color::dark_grey(), "?"); + return; + } + int8_t ppx = (rect.bottom() - 3) - (rect.top() + 2); // 11px max height to draw bars + int8_t ptd = (int8_t)((static_cast(percent_) / 100.0f) * (float)ppx + 0.5); // pixels to draw + int8_t pp = ppx - ptd; // pixels to start from + + if (percent_ >= 70) + battColor = Color::green(); + else if (percent_ >= 40) + battColor = Color::orange(); + else + battColor = Color::red(); + // fill the bars + for (int y = pp; y < ppx; y++) { + painter.draw_hline({rect.left() + 2, rect.top() + 3 + y}, rect.width() - 4, battColor); + } +} + +/* BatteryTextField *************************************************************/ + +BatteryTextField::BatteryTextField(Rect parent_rect, uint8_t percent) + : Widget(parent_rect) { + this->set_battery(percent, false); + set_focusable(true); +} + +void BatteryTextField::paint(Painter& painter) { + Color bg = has_focus() || highlighted() ? Color::light_grey() : Color::dark_grey(); + ui::Rect rect = screen_rect(); // 2 * 8, 1 * 16 + painter.fill_rectangle(rect, bg); // clear + std::string txt_batt = percent_ <= 100 ? to_string_dec_uint(percent_) : "UNK"; + int xdelta = 0; + if (txt_batt.length() == 1) + xdelta = 5; + else if (txt_batt.length() == 2) + xdelta = 2; + painter.draw_string({rect.left() + xdelta, rect.top()}, font::fixed_5x8, Color::white(), bg, txt_batt); + painter.draw_string({rect.left(), rect.top() + 8}, font::fixed_5x8, Color::white(), bg, (charge_) ? "+%" : " %"); +} + +void BatteryTextField::getAccessibilityText(std::string& result) { + result = to_string_dec_uint(percent_) + "%"; +} +void BatteryTextField::getWidgetName(std::string& result) { + result = "Battery percent"; +} + +void BatteryTextField::set_battery(uint8_t percentage, bool charge) { + if (charge == charge_ && percent_ == percentage) return; + charge_ = charge; + percent_ = percentage; + set_dirty(); +} + +bool BatteryTextField::on_key(KeyEvent key) { + if (key == KeyEvent::Select && on_select) { + on_select(); + return true; + } + return false; +} + +bool BatteryTextField::on_touch(TouchEvent event) { + if (event.type == TouchEvent::Type::Start) { + focus(); + return true; + } + if (event.type == TouchEvent::Type::End && on_select) { + on_select(); + return true; + } + return false; +} + /* NumberField ***********************************************************/ NumberField::NumberField( diff --git a/firmware/common/ui_widget.hpp b/firmware/common/ui_widget.hpp index 63f0e295..e1e65f2e 100644 --- a/firmware/common/ui_widget.hpp +++ b/firmware/common/ui_widget.hpp @@ -33,6 +33,8 @@ #include "portapack.hpp" #include "utility.hpp" +#include "ui/ui_font_fixed_5x8.hpp" + #include #include #include @@ -781,6 +783,52 @@ class TextField : public Text { using Text::set; }; +class BatteryTextField : public Widget { + public: + std::function on_select{}; + + BatteryTextField(Rect parent_rect, uint8_t percent); + void paint(Painter& painter) override; + + void set_battery(uint8_t percentage, bool charge); + void set_text(std::string_view value); + + bool on_key(KeyEvent key) override; + bool on_touch(TouchEvent event) override; + + void getAccessibilityText(std::string& result) override; + void getWidgetName(std::string& result) override; + + private: + uint8_t percent_{102}; + bool charge_{false}; + + static constexpr Style style{ + .font = font::fixed_5x8, + .background = Color::dark_grey(), + .foreground = Color::white(), + }; +}; + +class BatteryIcon : public Widget { + public: + std::function on_select{}; + + BatteryIcon(Rect parent_rect, uint8_t percent); + void paint(Painter& painter) override; + void set_battery(uint8_t percentage, bool charge); + + bool on_key(KeyEvent key) override; + bool on_touch(TouchEvent event) override; + + void getAccessibilityText(std::string& result) override; + void getWidgetName(std::string& result) override; + + private: + uint8_t percent_{102}; + bool charge_{false}; +}; + class NumberField : public Widget { public: std::function on_select{}; diff --git a/firmware/graphics/icon_batt_icon.png b/firmware/graphics/icon_batt_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..71540b89dcc77e113fe62d673b79ff8430846564 GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucK^spO#}Etu>rVLi4$veL+t@~!AyExB#Me{#Z)0n+FOWLjUXMa#_3sdN7x8MDA@zTA9 eHD4}RE#lHx6~4uG%`P>d;S8RxelF{r5}E*sK|jR+ literal 0 HcmV?d00001