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
This commit is contained in:
Totoo 2024-04-29 17:38:27 +02:00 committed by GitHub
parent f572b00391
commit f6a2d21624
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 767 additions and 44 deletions

View File

@ -177,6 +177,7 @@ set(CPPSRC
${COMMON}/utility.cpp ${COMMON}/utility.cpp
${COMMON}/wm8731.cpp ${COMMON}/wm8731.cpp
${COMMON}/ads1110.cpp ${COMMON}/ads1110.cpp
${COMMON}/battery.cpp
${COMMON}/performance_counter.cpp ${COMMON}/performance_counter.cpp
${COMMON}/bmpfile.cpp ${COMMON}/bmpfile.cpp
app_settings.cpp app_settings.cpp
@ -283,6 +284,7 @@ set(CPPSRC
apps/ui_adsb_tx.cpp apps/ui_adsb_tx.cpp
apps/ui_aprs_rx.cpp apps/ui_aprs_rx.cpp
apps/ui_aprs_tx.cpp apps/ui_aprs_tx.cpp
apps/ui_battinfo.cpp
apps/ui_bht_tx.cpp apps/ui_bht_tx.cpp
apps/ui_bmp_file_viewer.cpp apps/ui_bmp_file_viewer.cpp
apps/ui_btle_rx.cpp apps/ui_btle_rx.cpp

View File

@ -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 <cstring>
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

View File

@ -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__*/

View File

@ -42,7 +42,7 @@ DfuMenu::DfuMenu(NavigationView& nav)
&text_info_line_9, &text_info_line_9,
&text_info_line_10}); &text_info_line_10});
if (portapack::battery_ads1110.isDetected()) { if (battery::BatteryManagement::isDetected()) {
add_child(&voltage_label); add_child(&voltage_label);
add_child(&text_info_line_11); add_child(&text_info_line_11);
} }
@ -53,7 +53,7 @@ void DfuMenu::paint(Painter& painter) {
size_t m0_fragmented_free_space = 0; size_t m0_fragmented_free_space = 0;
const auto m0_fragments = chHeapStatus(NULL, &m0_fragmented_free_space); 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_1.set(to_string_dec_uint(chCoreStatus(), 6));
text_info_line_2.set(to_string_dec_uint(m0_fragmented_free_space, 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_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_9.set(to_string_dec_uint(shared_memory.m4_buffer_missed, 6));
text_info_line_10.set(to_string_dec_uint(chTimeNow() / 1000, 6)); text_info_line_10.set(to_string_dec_uint(chTimeNow() / 1000, 6));
if (portapack::battery_ads1110.isDetected()) { if (battery::BatteryManagement::isDetected()) {
text_info_line_11.set(to_string_decimal_padding(portapack::battery_ads1110.readVoltage(), 3, 6)); text_info_line_11.set(to_string_decimal_padding((float)battery::BatteryManagement::getVoltage() / 1000.0, 3, 6));
} }
constexpr auto margin = 5; constexpr auto margin = 5;

View File

@ -329,6 +329,10 @@ SetUIView::SetUIView(NavigationView& nav) {
if (audio::speaker_disable_supported()) { if (audio::speaker_disable_supported()) {
add_child(&toggle_speaker); 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_disable_touchscreen.set_value(pmem::disable_touchscreen());
checkbox_showsplash.set_value(pmem::config_splash()); checkbox_showsplash.set_value(pmem::config_splash());
@ -355,6 +359,8 @@ SetUIView::SetUIView(NavigationView& nav) {
toggle_speaker.set_value(!pmem::ui_hide_speaker()); toggle_speaker.set_value(!pmem::ui_hide_speaker());
toggle_mute.set_value(!pmem::ui_hide_mute()); toggle_mute.set_value(!pmem::ui_hide_mute());
toggle_fake_brightness.set_value(!pmem::ui_hide_fake_brightness()); 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()); toggle_sd_card.set_value(!pmem::ui_hide_sd_card());
button_save.on_select = [&nav, this](Button&) { 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_speaker(!toggle_speaker.value());
pmem::set_ui_hide_mute(!toggle_mute.value()); pmem::set_ui_hide_mute(!toggle_mute.value());
pmem::set_ui_hide_fake_brightness(!toggle_fake_brightness.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()); pmem::set_ui_hide_sd_card(!toggle_sd_card.value());
send_system_refresh(); send_system_refresh();
@ -807,6 +815,7 @@ SetMenuColorView::SetMenuColorView(NavigationView& nav) {
&field_green_level, &field_green_level,
&field_blue_level, &field_blue_level,
&button_save, &button_save,
&button_reset,
&button_cancel}); &button_cancel});
button_sample.set_focusable(false); button_sample.set_focusable(false);
@ -824,6 +833,13 @@ SetMenuColorView::SetMenuColorView(NavigationView& nav) {
field_green_level.on_change = color_changed_fn; field_green_level.on_change = color_changed_fn;
field_blue_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&) { button_save.on_select = [&nav, this](Button&) {
Color c = Color(field_red_level.value(), field_green_level.value(), field_blue_level.value()); Color c = Color(field_red_level.value(), field_green_level.value(), field_blue_level.value());
pmem::set_menu_color(c); pmem::set_menu_color(c);

View File

@ -323,37 +323,45 @@ class SetUIView : public View {
}; };
ImageToggle toggle_camera{ ImageToggle toggle_camera{
{6 * 8, 14 * 16 + 2, 16, 16}, {2 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_camera}; &bitmap_icon_camera};
ImageToggle toggle_sleep{ ImageToggle toggle_sleep{
{8 * 8, 14 * 16 + 2, 16, 16}, {4 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_sleep}; &bitmap_icon_sleep};
ImageToggle toggle_stealth{ ImageToggle toggle_stealth{
{10 * 8, 14 * 16 + 2, 16, 16}, {6 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_stealth}; &bitmap_icon_stealth};
ImageToggle toggle_converter{ ImageToggle toggle_converter{
{12 * 8, 14 * 16 + 2, 16, 16}, {8 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_upconvert}; &bitmap_icon_upconvert};
ImageToggle toggle_bias_tee{ ImageToggle toggle_bias_tee{
{14 * 8, 14 * 16 + 2, 16, 16}, {10 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_biast_off}; &bitmap_icon_biast_off};
ImageToggle toggle_clock{ ImageToggle toggle_clock{
{16 * 8, 14 * 16 + 2, 8, 16}, {12 * 8, 14 * 16 + 2, 8, 16},
&bitmap_icon_clk_ext}; &bitmap_icon_clk_ext};
ImageToggle toggle_mute{ ImageToggle toggle_mute{
{17 * 8, 14 * 16 + 2, 16, 16}, {13 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_speaker_and_headphones_mute}; &bitmap_icon_speaker_and_headphones_mute};
ImageToggle toggle_speaker{ ImageToggle toggle_speaker{
{19 * 8, 14 * 16 + 2, 16, 16}, {15 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_speaker_mute}; &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{ ImageToggle toggle_fake_brightness{
{21 * 8, 14 * 16 + 2, 16, 16}, {21 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_brightness}; &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{ Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32}, {2 * 8, 16 * 16, 12 * 8, 32},
"Save"}; "Save"};

View File

@ -2447,6 +2447,82 @@ static constexpr Bitmap bitmap_icon_camera{
{16, 16}, {16, 16},
bitmap_icon_camera_data}; 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[] = { static constexpr uint8_t bitmap_icon_tools_wipesd_data[] = {
0xF0, 0xF0,
0x3F, 0x3F,

View File

@ -85,7 +85,6 @@ ClockManager clock_manager{
WM8731 audio_codec_wm8731{i2c0, 0x1a}; WM8731 audio_codec_wm8731{i2c0, 0x1a};
AK4951 audio_codec_ak4951{i2c0, 0x12}; AK4951 audio_codec_ak4951{i2c0, 0x12};
ads1110::ADS1110 battery_ads1110{i2c0, 0x48};
ReceiverModel receiver_model; ReceiverModel receiver_model;
TransmitterModel transmitter_model; TransmitterModel transmitter_model;
@ -587,7 +586,7 @@ init_status_t init() {
chThdSleepMilliseconds(10); chThdSleepMilliseconds(10);
audio::init(portapack_audio_codec()); audio::init(portapack_audio_codec());
battery_ads1110.init(); battery::BatteryManagement::init();
if (lcd_fast_setup) if (lcd_fast_setup)
draw_splash_screen_icon(4, ui::bitmap_icon_speaker); draw_splash_screen_icon(4, ui::bitmap_icon_speaker);

View File

@ -38,6 +38,7 @@
#include "radio.hpp" #include "radio.hpp"
#include "clock_manager.hpp" #include "clock_manager.hpp"
#include "temperature_logger.hpp" #include "temperature_logger.hpp"
#include "battery.hpp"
/* TODO: This would be better as a class to add /* TODO: This would be better as a class to add
* guardrails on setting properties. */ * guardrails on setting properties. */
@ -63,7 +64,6 @@ extern portapack::USBSerial usb_serial;
extern si5351::Si5351 clock_generator; extern si5351::Si5351 clock_generator;
extern ClockManager clock_manager; extern ClockManager clock_manager;
extern ads1110::ADS1110 battery_ads1110;
extern ReceiverModel receiver_model; extern ReceiverModel receiver_model;
extern TransmitterModel transmitter_model; extern TransmitterModel transmitter_model;

View File

@ -1,5 +1,4 @@
#include "ui_bmpview.hpp" #include "ui_bmpview.hpp"
#include "usb_serial_asyncmsg.hpp"
#include "portapack.hpp" #include "portapack.hpp"
bool BMPViewer::load_bmp(const std::filesystem::path& file) { bool BMPViewer::load_bmp(const std::filesystem::path& file) {

View File

@ -84,6 +84,7 @@
#include "ui_weatherstation.hpp" #include "ui_weatherstation.hpp"
#include "ui_subghzd.hpp" #include "ui_subghzd.hpp"
#include "ui_whipcalc.hpp" #include "ui_whipcalc.hpp"
#include "ui_battinfo.hpp"
#include "ui_external_items_menu_loader.hpp" #include "ui_external_items_menu_loader.hpp"
// #include "acars_app.hpp" // #include "acars_app.hpp"
@ -334,6 +335,9 @@ SystemStatusView::SystemStatusView(
refresh(); refresh();
}; };
battery_icon.on_select = [this]() { on_battery_details(); };
battery_text.on_select = [this]() { on_battery_details(); };
button_fake_brightness.on_select = [this](ImageButton&) { button_fake_brightness.on_select = [this](ImageButton&) {
set_dirty(); set_dirty();
pmem::toggle_fake_brightness_level(); pmem::toggle_fake_brightness_level();
@ -370,6 +374,26 @@ SystemStatusView::SystemStatusView(
refresh(); 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<BattinfoView>();
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() { void SystemStatusView::refresh() {
// NB: Order of insertion is the display order Left->Right. // NB: Order of insertion is the display order Left->Right.
// TODO: Might be better to support hide and only add once. // 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 (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 (!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); if (!pmem::ui_hide_sd_card()) status_icons.add(&sd_card_status_view);
status_icons.update_layout(); status_icons.update_layout();
// Clock status // Clock status
@ -667,8 +703,9 @@ void NavigationView::display_modal(
const std::string& title, const std::string& title,
const std::string& message, const std::string& message,
modal_t type, modal_t type,
std::function<void(bool)> on_choice) { std::function<void(bool)> on_choice,
push<ModalMessageView>(title, message, type, on_choice); bool compact) {
push<ModalMessageView>(title, message, type, on_choice, compact);
} }
void NavigationView::free_view() { void NavigationView::free_view() {
@ -997,11 +1034,13 @@ ModalMessageView::ModalMessageView(
const std::string& title, const std::string& title,
const std::string& message, const std::string& message,
modal_t type, modal_t type,
std::function<void(bool)> on_choice) std::function<void(bool)> on_choice,
bool compact)
: title_{title}, : title_{title},
message_{message}, message_{message},
type_{type}, type_{type},
on_choice_{on_choice} { on_choice_{on_choice},
compact{compact} {
if (type == INFO) { if (type == INFO) {
add_child(&button_ok); add_child(&button_ok);
button_ok.on_select = [this, &nav](Button&) { button_ok.on_select = [this, &nav](Button&) {
@ -1034,13 +1073,13 @@ ModalMessageView::ModalMessageView(
} }
void ModalMessageView::paint(Painter& painter) { 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. // Break lines.
auto lines = split_string(message_, '\n'); auto lines = split_string(message_, '\n');
for (size_t i = 0; i < lines.size(); ++i) { for (size_t i = 0; i < lines.size(); ++i) {
painter.draw_string( painter.draw_string(
{1 * 8, (Coord)(120 + (i * 16))}, {1 * 8, (Coord)(((compact) ? 8 * 3 : 120) + (i * 16))},
style(), style(),
lines[i]); lines[i]);
} }

View File

@ -120,7 +120,8 @@ class NavigationView : public View {
const std::string& title, const std::string& title,
const std::string& message, const std::string& message,
modal_t type, modal_t type,
std::function<void(bool)> on_choice = nullptr); std::function<void(bool)> on_choice = nullptr,
bool compact = false);
void focus() override; void focus() override;
@ -191,7 +192,7 @@ class SystemStatusView : public View {
private: private:
static constexpr auto default_title = ""; static constexpr auto default_title = "";
bool batt_info_up = false; // to prevent show multiple batt info dialog
NavigationView& nav_; NavigationView& nav_;
Rectangle backdrop{ Rectangle backdrop{
@ -278,6 +279,9 @@ class SystemStatusView : public View {
SDCardStatusView sd_card_status_view{ SDCardStatusView sd_card_status_view{
{0, 0 * 16, 2 * 8, 1 * 16}}; {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_converter();
void on_bias_tee(); void on_bias_tee();
void on_camera(); void on_camera();
@ -285,6 +289,8 @@ class SystemStatusView : public View {
void refresh(); void refresh();
void on_clk(); void on_clk();
void rtc_battery_workaround(); void rtc_battery_workaround();
void on_battery_data(const BatteryStateMessage* msg);
void on_battery_details();
MessageHandlerRegistration message_handler_refresh{ MessageHandlerRegistration message_handler_refresh{
Message::ID::StatusRefresh, Message::ID::StatusRefresh,
@ -292,6 +298,13 @@ class SystemStatusView : public View {
(void)p; (void)p;
this->refresh(); this->refresh();
}}; }};
MessageHandlerRegistration message_handler_battery{
Message::ID::BatteryStateData,
[this](const Message* const p) {
const auto message = static_cast<const BatteryStateMessage*>(p);
this->on_battery_data(message);
}};
}; };
class InformationView : public View { class InformationView : public View {
@ -418,7 +431,8 @@ class ModalMessageView : public View {
const std::string& title, const std::string& title,
const std::string& message, const std::string& message,
modal_t type, modal_t type,
std::function<void(bool)> on_choice); std::function<void(bool)> on_choice,
bool compact = false);
void paint(Painter& painter) override; void paint(Painter& painter) override;
void focus() override; void focus() override;
@ -430,6 +444,7 @@ class ModalMessageView : public View {
const std::string message_; const std::string message_;
const modal_t type_; const modal_t type_;
const std::function<void(bool)> on_choice_; const std::function<void(bool)> on_choice_;
const bool compact;
Button button_ok{ Button button_ok{
{10 * 8, 14 * 16, 10 * 8, 48}, {10 * 8, 14 * 16, 10 * 8, 48},

View File

@ -24,10 +24,11 @@
#include <algorithm> #include <algorithm>
#include <cstdint> #include <cstdint>
namespace battery {
namespace ads1110 { namespace ads1110 {
constexpr float BATTERY_MIN_VOLTAGE = 3.0; constexpr uint16_t BATTERY_MIN_VOLTAGE = 3000;
constexpr float BATTERY_MAX_VOLTAGE = 4.0; constexpr uint16_t BATTERY_MAX_VOLTAGE = 4000;
void ADS1110::init() { void ADS1110::init() {
if (!detected_) { if (!detected_) {
@ -50,6 +51,7 @@ bool ADS1110::detect() {
return true; return true;
} }
} }
detected_ = false;
return false; return false;
} }
@ -57,7 +59,8 @@ bool ADS1110::write(const uint8_t value) {
return bus.transmit(bus_address, &value, 1); return bus.transmit(bus_address, &value, 1);
} }
float ADS1110::readVoltage() { // returns the batt voltage in mV
uint16_t ADS1110::readVoltage() {
// Read the conversion result // Read the conversion result
uint8_t data[3]; uint8_t data[3];
if (!bus.receive(bus_address, data, 3)) { if (!bus.receive(bus_address, data, 3)) {
@ -67,7 +70,7 @@ float ADS1110::readVoltage() {
uint16_t raw = (static_cast<uint16_t>(data[0]) << 8) | data[1]; uint16_t raw = (static_cast<uint16_t>(data[0]) << 8) | data[1];
// Calculate the voltage based on the output code // Calculate the voltage based on the output code
float voltage = 0.0f; int16_t voltage = 0;
float minCode = 0; float minCode = 0;
float pga = 0.0f; 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 // 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; 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 voltage; return (uint16_t)voltage;
} }
void ADS1110::getBatteryInfo(float& batteryPercentage, float& voltage) { void ADS1110::getBatteryInfo(uint8_t& batteryPercentage, uint16_t& voltage) {
voltage = readVoltage(); voltage = readVoltage();
// Calculate the remaining battery percentage // Calculate the remaining battery percentage
batteryPercentage = (voltage - BATTERY_MIN_VOLTAGE) / batteryPercentage = (float)(voltage - BATTERY_MIN_VOLTAGE) / (float)(BATTERY_MAX_VOLTAGE - BATTERY_MIN_VOLTAGE) * 100.0;
(BATTERY_MAX_VOLTAGE - BATTERY_MIN_VOLTAGE) * 100.0;
// Limit the values to the valid range // 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% // ToDo: if its > 4, then 100%, if < 3 then 0%
} }
} /* namespace ads1110 */ } /* namespace ads1110 */
} // namespace battery

View File

@ -27,11 +27,10 @@
#include <string> #include <string>
#include "i2c_pp.hpp" #include "i2c_pp.hpp"
namespace battery {
namespace ads1110 { namespace ads1110 {
using address_t = uint8_t; using address_t = uint8_t;
using reg_t = uint16_t;
class ADS1110 { class ADS1110 {
public: public:
@ -39,10 +38,11 @@ class ADS1110 {
: bus(bus), bus_address(bus_address), detected_(false) {} : bus(bus), bus_address(bus_address), detected_(false) {}
void init(); void init();
bool detect();
bool isDetected() const { return detected_; } bool isDetected() const { return detected_; }
float readVoltage(); uint16_t readVoltage();
void getBatteryInfo(float& batteryPercentage, float& voltage); void getBatteryInfo(uint8_t& batteryPercentage, uint16_t& voltage);
private: private:
I2C& bus; I2C& bus;
@ -50,9 +50,8 @@ class ADS1110 {
bool detected_; bool detected_;
bool write(const uint8_t value); bool write(const uint8_t value);
bool detect();
}; };
} /* namespace ads1110 */ } /* namespace ads1110 */
} // namespace battery
#endif /* __ADS1110_H__ */ #endif /* __ADS1110_H__ */

111
firmware/common/battery.cpp Normal file
View File

@ -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

View File

@ -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 <cstdint>
#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

View File

@ -123,6 +123,7 @@ class Message {
EnvironmentData = 65, EnvironmentData = 65,
AudioBeep = 66, AudioBeep = 66,
PocsagTosend = 67, PocsagTosend = 67,
BatteryStateData = 68,
MAX MAX
}; };
@ -1409,4 +1410,20 @@ class PocsagTosendMessage : public Message {
uint64_t addr = 0; 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__*/ #endif /*__MESSAGE_H__*/

View File

@ -132,8 +132,8 @@ struct ui_config2_t {
bool hide_mute : 1; bool hide_mute : 1;
bool hide_fake_brightness : 1; bool hide_fake_brightness : 1;
bool UNUSED_1 : 1; bool hide_numeric_battery : 1;
bool UNUSED_2 : 1; bool hide_battery_icon : 1;
bool UNUSED_3 : 1; bool UNUSED_3 : 1;
bool UNUSED_4 : 1; bool UNUSED_4 : 1;
bool UNUSED_5 : 1; bool UNUSED_5 : 1;
@ -423,6 +423,7 @@ void defaults() {
set_encoder_dial_sensitivity(DIAL_SENSITIVITY_NORMAL); set_encoder_dial_sensitivity(DIAL_SENSITIVITY_NORMAL);
set_config_speaker_disable(true); // Disable AK4951 speaker by default (in case of OpenSourceSDRLab H2) set_config_speaker_disable(true); // Disable AK4951 speaker by default (in case of OpenSourceSDRLab H2)
set_menu_color(Color::grey()); 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. // Default values for recon app.
set_recon_autosave_freqs(false); set_recon_autosave_freqs(false);
@ -954,6 +955,13 @@ bool ui_hide_fake_brightness() {
return data->ui_config2.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) { void set_ui_hide_speaker(bool v) {
data->ui_config2.hide_speaker = 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) { void set_ui_hide_fake_brightness(bool v) {
data->ui_config2.hide_fake_brightness = 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 */ /* Converter */
bool config_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_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_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_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 // misc_config bits
pmem_dump_file.write_line("misc_config config_audio_mute: " + to_string_dec_int(config_audio_mute())); pmem_dump_file.write_line("misc_config config_audio_mute: " + to_string_dec_int(config_audio_mute()));

View File

@ -335,6 +335,8 @@ bool ui_hide_sleep();
bool ui_hide_bias_tee(); bool ui_hide_bias_tee();
bool ui_hide_clock(); bool ui_hide_clock();
bool ui_hide_fake_brightness(); bool ui_hide_fake_brightness();
bool ui_hide_numeric_battery();
bool ui_hide_battery_icon();
bool ui_hide_sd_card(); bool ui_hide_sd_card();
void set_ui_hide_speaker(bool v); void set_ui_hide_speaker(bool v);
void set_ui_hide_mute(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_bias_tee(bool v);
void set_ui_hide_clock(bool v); void set_ui_hide_clock(bool v);
void set_ui_hide_fake_brightness(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); void set_ui_hide_sd_card(bool v);
// sd persisting settings // sd persisting settings

View File

@ -2044,6 +2044,135 @@ bool TextField::on_touch(TouchEvent event) {
return false; 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<float>(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::NumberField( NumberField::NumberField(

View File

@ -33,6 +33,8 @@
#include "portapack.hpp" #include "portapack.hpp"
#include "utility.hpp" #include "utility.hpp"
#include "ui/ui_font_fixed_5x8.hpp"
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <string> #include <string>
@ -781,6 +783,52 @@ class TextField : public Text {
using Text::set; using Text::set;
}; };
class BatteryTextField : public Widget {
public:
std::function<void()> 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<void()> 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 { class NumberField : public Widget {
public: public:
std::function<void(NumberField&)> on_select{}; std::function<void(NumberField&)> on_select{};

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 B