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
23 changed files with 767 additions and 44 deletions

View File

@@ -24,10 +24,11 @@
#include <algorithm>
#include <cstdint>
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<uint16_t>(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

View File

@@ -27,11 +27,10 @@
#include <string>
#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__ */

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

View File

@@ -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()));

View File

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

View File

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

View File

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