Implemented power management (#67)
- Implemented HAL for for power management - Implemented the Power app (accessible via Settings app) - Implemented status bar icon for battery status
12
README.md
@ -73,12 +73,12 @@ Implementing drivers can take some effort, so Tactility provides support for sev
|
|||||||
|
|
||||||
Predefined configurations are available for:
|
Predefined configurations are available for:
|
||||||
|
|
||||||
| Device | Screen&Touch | SD card | Other |
|
| Device | Screen&Touch | SD card | Power | Other |
|
||||||
|------------------------------------------|--------------|---------|----------|
|
|------------------------------------------|--------------|---------|-------|----------|
|
||||||
| [LilyGo T-Deck][tdeck] | ✅ | ✅ | Keyboard |
|
| [M5Stack Core2][m5stack] | ✅ | ✅ | ✅ | |
|
||||||
| [Waveshare S3 Touch][waveshare_s3_touch] | ✅ | ⏳ | |
|
| [LilyGo T-Deck][tdeck] | ✅ | ✅ | | Keyboard |
|
||||||
| Yellow Board 2432S024C (\*) | ✅ | ✅ | |
|
| [Waveshare S3 Touch][waveshare_s3_touch] | ✅ | ⏳ | | |
|
||||||
| [M5Stack Core2][m5stack] | ✅ | ✅ | |
|
| Yellow Board 2432S024C (\*) | ✅ | ✅ | | |
|
||||||
|
|
||||||
- ✅: Capable and implemented
|
- ✅: Capable and implemented
|
||||||
- ⏳: Capable but not yet implemented
|
- ⏳: Capable but not yet implemented
|
||||||
|
|||||||
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
#define TAG "hardware"
|
#define TAG "hardware"
|
||||||
|
|
||||||
|
extern const Power power;
|
||||||
|
|
||||||
static bool lvgl_init() {
|
static bool lvgl_init() {
|
||||||
lv_init();
|
lv_init();
|
||||||
lvgl_task_start();
|
lvgl_task_start();
|
||||||
@ -22,7 +24,12 @@ TT_UNUSED static void lvgl_deinit() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
HardwareConfig sim_hardware = {
|
const HardwareConfig sim_hardware = {
|
||||||
.bootstrap = NULL,
|
.bootstrap = NULL,
|
||||||
.init_graphics = &lvgl_init,
|
.init_graphics = &lvgl_init,
|
||||||
|
.display = {
|
||||||
|
.set_backlight_duty = NULL,
|
||||||
|
},
|
||||||
|
.power = &power,
|
||||||
|
.sdcard = NULL
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
#include "hello_world/hello_world.h"
|
#include "hello_world/hello_world.h"
|
||||||
#include "tactility.h"
|
#include "tactility.h"
|
||||||
|
|
||||||
extern HardwareConfig sim_hardware;
|
extern const HardwareConfig sim_hardware;
|
||||||
|
|
||||||
void app_main() {
|
void app_main() {
|
||||||
static const Config config = {
|
static const Config config = {
|
||||||
|
|||||||
34
app-sim/src/power.c
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#include "power.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static bool is_charging_enabled = false;
|
||||||
|
|
||||||
|
static bool power_is_charging() {
|
||||||
|
return is_charging_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void power_set_charging_enabled(bool enabled) {
|
||||||
|
is_charging_enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t power_get_charge_level() {
|
||||||
|
return 204;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t power_get_current() {
|
||||||
|
return is_charging_enabled ? 100 : -50;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Power power = {
|
||||||
|
.is_charging = &power_is_charging,
|
||||||
|
.set_charging_enabled = &power_set_charging_enabled,
|
||||||
|
.get_charge_level = &power_get_charge_level,
|
||||||
|
.get_current = &power_get_current
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@ -13,5 +13,6 @@ const HardwareConfig lilygo_tdeck = {
|
|||||||
.set_backlight_duty = &tdeck_backlight_set
|
.set_backlight_duty = &tdeck_backlight_set
|
||||||
},
|
},
|
||||||
.init_graphics = &tdeck_init_lvgl,
|
.init_graphics = &tdeck_init_lvgl,
|
||||||
.sdcard = &tdeck_sdcard
|
.sdcard = &tdeck_sdcard,
|
||||||
|
.power = NULL
|
||||||
};
|
};
|
||||||
|
|||||||
@ -32,7 +32,12 @@ lv_disp_t* core2_display_init() {
|
|||||||
const esp_lcd_panel_dev_config_t panel_config = {
|
const esp_lcd_panel_dev_config_t panel_config = {
|
||||||
.reset_gpio_num = GPIO_NUM_NC,
|
.reset_gpio_num = GPIO_NUM_NC,
|
||||||
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
|
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
|
||||||
|
.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
|
||||||
.bits_per_pixel = CORE2_LCD_BITS_PER_PIXEL,
|
.bits_per_pixel = CORE2_LCD_BITS_PER_PIXEL,
|
||||||
|
.flags = {
|
||||||
|
.reset_active_high = false
|
||||||
|
},
|
||||||
|
.vendor_config = nullptr
|
||||||
};
|
};
|
||||||
|
|
||||||
esp_lcd_panel_handle_t panel_handle;
|
esp_lcd_panel_handle_t panel_handle;
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#include "lvgl_i.h"
|
#include "lvgl_i.h"
|
||||||
|
|
||||||
extern const SdCard core2_sdcard;
|
extern const SdCard core2_sdcard;
|
||||||
|
extern Power core2_power; // Making it const fails the build
|
||||||
|
|
||||||
const HardwareConfig m5stack_core2 = {
|
const HardwareConfig m5stack_core2 = {
|
||||||
.bootstrap = &core2_bootstrap,
|
.bootstrap = &core2_bootstrap,
|
||||||
@ -10,5 +11,6 @@ const HardwareConfig m5stack_core2 = {
|
|||||||
.set_backlight_duty = NULL
|
.set_backlight_duty = NULL
|
||||||
},
|
},
|
||||||
.init_graphics = &core2_lvgl_init,
|
.init_graphics = &core2_lvgl_init,
|
||||||
.sdcard = &core2_sdcard
|
.sdcard = &core2_sdcard,
|
||||||
|
.power = &core2_power
|
||||||
};
|
};
|
||||||
|
|||||||
34
boards/m5stack_core2/source/power.cpp
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#include "power.h"
|
||||||
|
#include "M5Unified.hpp"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static bool power_is_charging() {
|
||||||
|
return M5.Power.isCharging() == m5::Power_Class::is_charging;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void power_set_charging_enabled(bool enabled) {
|
||||||
|
M5.Power.setBatteryCharge(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t power_get_charge_level() {
|
||||||
|
uint16_t scaled = (uint16_t)M5.Power.getBatteryLevel() * 255 / 100;
|
||||||
|
return (uint8_t)scaled;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t power_get_current() {
|
||||||
|
return M5.Power.getBatteryCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
Power core2_power = {
|
||||||
|
.is_charging = &power_is_charging,
|
||||||
|
.set_charging_enabled = &power_set_charging_enabled,
|
||||||
|
.get_charge_level = &power_get_charge_level,
|
||||||
|
.get_current = &power_get_current
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@ -9,7 +9,7 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static void read_touch(TT_UNUSED lv_indev_t* indev, lv_indev_data_t* data) {
|
static void read_touch(TT_UNUSED lv_indev_t* indev, lv_indev_data_t* data) {
|
||||||
static lgfx::touch_point_t point;
|
lgfx::touch_point_t point; // Making it static makes it unreliable
|
||||||
bool touched = M5.Lcd.getTouch(&point) > 0;
|
bool touched = M5.Lcd.getTouch(&point) > 0;
|
||||||
if (touched) {
|
if (touched) {
|
||||||
data->point.x = point.x;
|
data->point.x = point.x;
|
||||||
|
|||||||
@ -9,5 +9,7 @@ const HardwareConfig waveshare_s3_touch = {
|
|||||||
.display = {
|
.display = {
|
||||||
.set_backlight_duty = NULL // TODO: This requires implementing the CH422G IO expander
|
.set_backlight_duty = NULL // TODO: This requires implementing the CH422G IO expander
|
||||||
},
|
},
|
||||||
.init_graphics = &ws3t_init_lvgl
|
.init_graphics = &ws3t_init_lvgl,
|
||||||
|
.sdcard = NULL,
|
||||||
|
.power = NULL
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,5 +12,6 @@ const HardwareConfig yellow_board_24inch_cap = {
|
|||||||
.set_backlight_duty = &twodotfour_backlight_set
|
.set_backlight_duty = &twodotfour_backlight_set
|
||||||
},
|
},
|
||||||
.init_graphics = &twodotfour_lvgl_init,
|
.init_graphics = &twodotfour_lvgl_init,
|
||||||
.sdcard = &twodotfour_sdcard
|
.sdcard = &twodotfour_sdcard,
|
||||||
|
.power = NULL
|
||||||
};
|
};
|
||||||
|
|||||||
BIN
data/assets/app_icon_power_settings.png
Normal file
|
After Width: | Height: | Size: 299 B |
BIN
data/assets/power_020.png
Normal file
|
After Width: | Height: | Size: 256 B |
BIN
data/assets/power_040.png
Normal file
|
After Width: | Height: | Size: 261 B |
BIN
data/assets/power_060.png
Normal file
|
After Width: | Height: | Size: 268 B |
BIN
data/assets/power_080.png
Normal file
|
After Width: | Height: | Size: 262 B |
BIN
data/assets/power_100.png
Normal file
|
After Width: | Height: | Size: 259 B |
@ -7,10 +7,10 @@
|
|||||||
id="svg11"
|
id="svg11"
|
||||||
sodipodi:docname="Desktop Icons.svg"
|
sodipodi:docname="Desktop Icons.svg"
|
||||||
xml:space="preserve"
|
xml:space="preserve"
|
||||||
inkscape:export-filename="app_icon_display_settings.png"
|
inkscape:export-filename="../assets/power_020.png"
|
||||||
inkscape:export-xdpi="3"
|
inkscape:export-xdpi="3"
|
||||||
inkscape:export-ydpi="3"
|
inkscape:export-ydpi="3"
|
||||||
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
|
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -100,14 +100,14 @@
|
|||||||
in2="comp1"
|
in2="comp1"
|
||||||
id="feComposite41" /></filter></defs><sodipodi:namedview
|
id="feComposite41" /></filter></defs><sodipodi:namedview
|
||||||
id="base"
|
id="base"
|
||||||
inkscape:current-layer="layer4"
|
inkscape:current-layer="layer1"
|
||||||
showgrid="true"
|
showgrid="true"
|
||||||
units="px"
|
units="px"
|
||||||
showguides="true"
|
showguides="true"
|
||||||
inkscape:guide-bbox="true"
|
inkscape:guide-bbox="true"
|
||||||
inkscape:document-units="px"
|
inkscape:document-units="px"
|
||||||
inkscape:cx="202.98973"
|
inkscape:cx="263.92781"
|
||||||
inkscape:cy="282.86804"
|
inkscape:cy="282.45629"
|
||||||
pagecolor="#505050"
|
pagecolor="#505050"
|
||||||
bordercolor="#eeeeee"
|
bordercolor="#eeeeee"
|
||||||
borderopacity="1"
|
borderopacity="1"
|
||||||
@ -117,7 +117,7 @@
|
|||||||
inkscape:deskcolor="#d1d1d1"
|
inkscape:deskcolor="#d1d1d1"
|
||||||
inkscape:zoom="1.2143472"
|
inkscape:zoom="1.2143472"
|
||||||
inkscape:window-width="1503"
|
inkscape:window-width="1503"
|
||||||
inkscape:window-height="933"
|
inkscape:window-height="930"
|
||||||
inkscape:window-x="0"
|
inkscape:window-x="0"
|
||||||
inkscape:window-y="0"
|
inkscape:window-y="0"
|
||||||
inkscape:window-maximized="1"><inkscape:grid
|
inkscape:window-maximized="1"><inkscape:grid
|
||||||
@ -189,13 +189,64 @@
|
|||||||
d="m 0.48281402,192.83596 v -79.74848 c 0,-14.177585 4.99842408,-25.696778 14.99527098,-34.557575 C 25.47472,69.668868 37.492176,65.238358 51.530467,65.238358 H 459.91073 c 14.03785,0 26.04106,4.43051 36.00962,13.291547 9.96858,8.860797 14.95286,20.37999 14.95286,34.557575 v 79.74848 H 463.02408 V 113.08748 H 48.331914 v 79.74848 z M 51.445288,448.03116 c -14.037857,0 -26.041069,-4.43051 -36.009631,-13.29154 -9.9685626,-8.8608 -14.95284298,-20.38 -14.95284298,-34.55758 V 320.43356 H 48.331914 v 79.74848 H 463.02408 v -79.74848 h 47.84913 v 79.74848 c 0,14.17758 -4.99842,25.69678 -14.99528,34.55758 -9.99707,8.86103 -22.01453,13.29154 -36.0524,13.29154 z"
|
d="m 0.48281402,192.83596 v -79.74848 c 0,-14.177585 4.99842408,-25.696778 14.99527098,-34.557575 C 25.47472,69.668868 37.492176,65.238358 51.530467,65.238358 H 459.91073 c 14.03785,0 26.04106,4.43051 36.00962,13.291547 9.96858,8.860797 14.95286,20.37999 14.95286,34.557575 v 79.74848 H 463.02408 V 113.08748 H 48.331914 v 79.74848 z M 51.445288,448.03116 c -14.037857,0 -26.041069,-4.43051 -36.009631,-13.29154 -9.9685626,-8.8608 -14.95284298,-20.38 -14.95284298,-34.55758 V 320.43356 H 48.331914 v 79.74848 H 463.02408 v -79.74848 h 47.84913 v 79.74848 c 0,14.17758 -4.99842,25.69678 -14.99528,34.55758 -9.99707,8.86103 -22.01453,13.29154 -36.0524,13.29154 z"
|
||||||
id="System_Info"
|
id="System_Info"
|
||||||
style="display:inline;fill:#333333;fill-opacity:1;stroke-width:0.664569"
|
style="display:inline;fill:#333333;fill-opacity:1;stroke-width:0.664569"
|
||||||
sodipodi:nodetypes="cscsscsccccccscsccccccssss" /></g><path
|
sodipodi:nodetypes="cscsscsccccccscsccccccssss" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
inkscape:label="Power_Settings"><rect
|
||||||
|
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.8862;paint-order:stroke fill markers"
|
||||||
|
id="rect4"
|
||||||
|
width="346.68829"
|
||||||
|
height="197.63705"
|
||||||
|
x="65.055527"
|
||||||
|
y="164.69754"
|
||||||
|
inkscape:label="Power_at_100" /><rect
|
||||||
|
style="display:none;fill:#000000;fill-opacity:1;stroke-width:0.8862;paint-order:stroke fill markers"
|
||||||
|
id="rect16"
|
||||||
|
width="277"
|
||||||
|
height="197.63705"
|
||||||
|
x="65.055527"
|
||||||
|
y="164.69754"
|
||||||
|
inkscape:label="Power_at_080" /><rect
|
||||||
|
style="display:none;fill:#000000;fill-opacity:1;stroke-width:0.8862;paint-order:stroke fill markers"
|
||||||
|
id="rect18"
|
||||||
|
width="208"
|
||||||
|
height="197.63705"
|
||||||
|
x="65.055527"
|
||||||
|
y="164.69754"
|
||||||
|
inkscape:label="Power_at_060" /><rect
|
||||||
|
style="display:none;fill:#000000;fill-opacity:1;stroke-width:0.8862;paint-order:stroke fill markers"
|
||||||
|
id="rect19"
|
||||||
|
width="139"
|
||||||
|
height="197.63705"
|
||||||
|
x="65.055527"
|
||||||
|
y="164.69754"
|
||||||
|
inkscape:label="Power_at_040" /><rect
|
||||||
|
style="display:none;fill:#000000;fill-opacity:1;stroke-width:0.8862;paint-order:stroke fill markers"
|
||||||
|
id="rect20"
|
||||||
|
width="70"
|
||||||
|
height="197.63705"
|
||||||
|
x="65.055527"
|
||||||
|
y="164.69754"
|
||||||
|
inkscape:label="Power_at_020" /><g
|
||||||
|
id="g3"
|
||||||
|
inkscape:label="Shared"
|
||||||
|
transform="translate(0.41209841,0.82986943)"><path
|
||||||
|
id="rect15"
|
||||||
|
style="fill:#000000;paint-order:stroke fill markers"
|
||||||
|
d="M 1.6464844 104.58203 L 1.6464844 417.50781 L 475.15234 417.50781 L 475.15234 104.58203 L 1.6464844 104.58203 z M 23.880859 125.99414 L 451.27148 125.99414 L 451.27148 395.27344 L 23.880859 395.27344 L 23.880859 125.99414 z "
|
||||||
|
transform="translate(-0.41209841,-0.82986943)" /><rect
|
||||||
|
style="display:inline;fill:#000000;paint-order:stroke fill markers"
|
||||||
|
id="rect2-7"
|
||||||
|
width="39.527409"
|
||||||
|
height="113.6413"
|
||||||
|
x="471.03497"
|
||||||
|
y="201.28403" /></g></g><path
|
||||||
d="m 128.42457,319.70188 h 31.96224 V 223.81515 H 128.42457 V 255.7774 H 96.462329 v 31.96225 h 31.962241 z m 63.92449,-31.96223 H 416.08475 V 255.7774 H 192.34906 Z m 159.81121,-63.9245 h 31.96224 v -31.96223 h 31.96224 V 159.89067 H 384.12251 V 127.92843 H 352.16027 Z M 96.462329,191.85292 H 320.19802 V 159.89067 H 96.462329 Z m 63.924481,287.66019 v -63.9245 H 48.518961 q -19.776638,0 -33.860161,-14.09119 Q 0.575596,387.40591 0.575596,367.61871 V 79.796797 q 0,-19.788137 14.083204,-33.77162 Q 28.742323,32.041695 48.518961,32.041695 H 464.02812 q 19.77663,0 33.85984,14.091514 14.08352,14.091196 14.08352,33.878376 V 367.83383 q 0,19.78783 -14.08352,33.7713 -14.08321,13.98348 -33.85984,13.98348 H 352.16027 v 63.9245 z M 48.518961,367.64525 H 464.02812 V 79.98506 H 48.518961 Z m 0,0 V 79.98506 Z"
|
d="m 128.42457,319.70188 h 31.96224 V 223.81515 H 128.42457 V 255.7774 H 96.462329 v 31.96225 h 31.962241 z m 63.92449,-31.96223 H 416.08475 V 255.7774 H 192.34906 Z m 159.81121,-63.9245 h 31.96224 v -31.96223 h 31.96224 V 159.89067 H 384.12251 V 127.92843 H 352.16027 Z M 96.462329,191.85292 H 320.19802 V 159.89067 H 96.462329 Z m 63.924481,287.66019 v -63.9245 H 48.518961 q -19.776638,0 -33.860161,-14.09119 Q 0.575596,387.40591 0.575596,367.61871 V 79.796797 q 0,-19.788137 14.083204,-33.77162 Q 28.742323,32.041695 48.518961,32.041695 H 464.02812 q 19.77663,0 33.85984,14.091514 14.08352,14.091196 14.08352,33.878376 V 367.83383 q 0,19.78783 -14.08352,33.7713 -14.08321,13.98348 -33.85984,13.98348 H 352.16027 v 63.9245 z M 48.518961,367.64525 H 464.02812 V 79.98506 H 48.518961 Z m 0,0 V 79.98506 Z"
|
||||||
id="Display_Settings"
|
id="Display_Settings"
|
||||||
style="stroke-width:0.665879;fill:#333333;fill-opacity:1" /><path
|
style="display:none;fill:#333333;fill-opacity:1;stroke-width:0.665879" /><path
|
||||||
d="m 47.867683,447.55649 q -19.326766,0 -33.654939,-14.32849 Q -0.1157482,418.89983 -0.1157482,399.57306 V 111.67248 q 0,-19.793166 14.3284922,-33.888136 Q 28.540917,63.689052 47.867683,63.689052 H 191.81797 l 63.97791,63.977908 h 207.9282 q 19.79316,0 33.88814,14.09529 14.09529,14.09497 14.09529,33.88814 v 223.92267 q 0,19.32677 -14.09529,33.65494 -14.09498,14.32849 -33.88814,14.32849 z m 0,-47.98343 H 463.72408 V 175.65039 h -227.9213 l -63.9779,-63.97791 H 47.867683 Z m 0,0 V 111.67248 Z"
|
d="m 47.867683,447.55649 q -19.326766,0 -33.654939,-14.32849 Q -0.1157482,418.89983 -0.1157482,399.57306 V 111.67248 q 0,-19.793166 14.3284922,-33.888136 Q 28.540917,63.689052 47.867683,63.689052 H 191.81797 l 63.97791,63.977908 h 207.9282 q 19.79316,0 33.88814,14.09529 14.09529,14.09497 14.09529,33.88814 v 223.92267 q 0,19.32677 -14.09529,33.65494 -14.09498,14.32849 -33.88814,14.32849 z m 0,-47.98343 H 463.72408 V 175.65039 h -227.9213 l -63.9779,-63.97791 H 47.867683 Z m 0,0 V 111.67248 Z"
|
||||||
id="Files"
|
id="Files"
|
||||||
style="stroke-width:0.666435;fill:#333333;fill-opacity:1" /></g><style
|
style="display:none;fill:#333333;fill-opacity:1;stroke-width:0.666435" /></g><style
|
||||||
id="style1-9"><![CDATA[
|
id="style1-9"><![CDATA[
|
||||||
#keylines circle, #keylines rect {
|
#keylines circle, #keylines rect {
|
||||||
vector-effect: non-scaling-stroke;
|
vector-effect: non-scaling-stroke;
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 13 KiB |
@ -14,6 +14,7 @@
|
|||||||
- App lifecycle docs mention on_create/on_destroy but app lifecycle is on_start/on_stop
|
- App lifecycle docs mention on_create/on_destroy but app lifecycle is on_start/on_stop
|
||||||
- Explore LVGL9's FreeRTOS functionality
|
- Explore LVGL9's FreeRTOS functionality
|
||||||
- Explore LVGL9's ILI93414 driver for 2.4" Yellow Board
|
- Explore LVGL9's ILI93414 driver for 2.4" Yellow Board
|
||||||
|
- Bug: in LVGL9 with M5Core2, crash when bottom item is clicked without scrolling first
|
||||||
|
|
||||||
# Core Ideas
|
# Core Ideas
|
||||||
- Make a HAL? It would mainly be there to support PC development. It's a lot of effort for supporting what's effectively a dev-only feature.
|
- Make a HAL? It would mainly be there to support PC development. It's a lot of effort for supporting what's effectively a dev-only feature.
|
||||||
@ -35,4 +36,5 @@
|
|||||||
- BadUSB
|
- BadUSB
|
||||||
- Discord bot
|
- Discord bot
|
||||||
- IR transceiver app
|
- IR transceiver app
|
||||||
|
- GPS app
|
||||||
- Investigate CSI https://stevenmhernandez.github.io/ESP32-CSI-Tool/
|
- Investigate CSI https://stevenmhernandez.github.io/ESP32-CSI-Tool/
|
||||||
@ -7,6 +7,7 @@
|
|||||||
#define TT_ASSETS_APP_ICON_FALLBACK TT_ASSET("app_icon_fallback.png")
|
#define TT_ASSETS_APP_ICON_FALLBACK TT_ASSET("app_icon_fallback.png")
|
||||||
#define TT_ASSETS_APP_ICON_FILES TT_ASSET("app_icon_files.png")
|
#define TT_ASSETS_APP_ICON_FILES TT_ASSET("app_icon_files.png")
|
||||||
#define TT_ASSETS_APP_ICON_DISPLAY_SETTINGS TT_ASSET("app_icon_display_settings.png")
|
#define TT_ASSETS_APP_ICON_DISPLAY_SETTINGS TT_ASSET("app_icon_display_settings.png")
|
||||||
|
#define TT_ASSETS_APP_ICON_POWER_SETTINGS TT_ASSET("app_icon_power_settings.png")
|
||||||
#define TT_ASSETS_APP_ICON_SETTINGS TT_ASSET("app_icon_settings.png")
|
#define TT_ASSETS_APP_ICON_SETTINGS TT_ASSET("app_icon_settings.png")
|
||||||
#define TT_ASSETS_APP_ICON_SYSTEM_INFO TT_ASSET("app_icon_system_info.png")
|
#define TT_ASSETS_APP_ICON_SYSTEM_INFO TT_ASSET("app_icon_system_info.png")
|
||||||
|
|
||||||
@ -29,3 +30,10 @@
|
|||||||
#define TT_ASSETS_ICON_WIFI_SIGNAL_3_LOCKED TT_ASSET("wifi_signal_3_locked.png")
|
#define TT_ASSETS_ICON_WIFI_SIGNAL_3_LOCKED TT_ASSET("wifi_signal_3_locked.png")
|
||||||
#define TT_ASSETS_ICON_WIFI_SIGNAL_4 TT_ASSET("wifi_signal_4.png")
|
#define TT_ASSETS_ICON_WIFI_SIGNAL_4 TT_ASSET("wifi_signal_4.png")
|
||||||
#define TT_ASSETS_ICON_WIFI_SIGNAL_4_LOCKED TT_ASSET("wifi_signal_4_locked.png")
|
#define TT_ASSETS_ICON_WIFI_SIGNAL_4_LOCKED TT_ASSET("wifi_signal_4_locked.png")
|
||||||
|
|
||||||
|
// Power status
|
||||||
|
#define TT_ASSETS_ICON_POWER_020 TT_ASSET("power_020.png")
|
||||||
|
#define TT_ASSETS_ICON_POWER_040 TT_ASSET("power_040.png")
|
||||||
|
#define TT_ASSETS_ICON_POWER_060 TT_ASSET("power_060.png")
|
||||||
|
#define TT_ASSETS_ICON_POWER_080 TT_ASSET("power_080.png")
|
||||||
|
#define TT_ASSETS_ICON_POWER_100 TT_ASSET("power_100.png")
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "tactility_core.h"
|
#include "tactility_core.h"
|
||||||
#include "sdcard.h"
|
#include "sdcard.h"
|
||||||
|
#include "power.h"
|
||||||
|
|
||||||
typedef bool (*Bootstrap)();
|
typedef bool (*Bootstrap)();
|
||||||
typedef bool (*InitGraphics)();
|
typedef bool (*InitGraphics)();
|
||||||
@ -35,4 +36,9 @@ typedef struct {
|
|||||||
* An optional SD card interface.
|
* An optional SD card interface.
|
||||||
*/
|
*/
|
||||||
const SdCard* _Nullable sdcard;
|
const SdCard* _Nullable sdcard;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional power interface for battery or other power delivery.
|
||||||
|
*/
|
||||||
|
const Power* _Nullable power;
|
||||||
} HardwareConfig;
|
} HardwareConfig;
|
||||||
|
|||||||
1
tactility-headless/src/power.c
Normal file
@ -0,0 +1 @@
|
|||||||
|
#include "power.h"
|
||||||
24
tactility-headless/src/power.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef bool (*PowerIsCharging)();
|
||||||
|
typedef void (*PowerSetChargingEnabled)(bool enabled);
|
||||||
|
typedef uint8_t (*PowerGetBatteryCharge)(); // Power value [0, 255] which maps to 0-100% charge
|
||||||
|
typedef int32_t (*PowerGetCurrent)(); // Consumption or charge current in mAh
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PowerIsCharging is_charging;
|
||||||
|
PowerSetChargingEnabled set_charging_enabled;
|
||||||
|
PowerGetBatteryCharge get_charge_level;
|
||||||
|
PowerGetCurrent get_current;
|
||||||
|
} Power;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@ -3,7 +3,6 @@
|
|||||||
#include "lvgl.h"
|
#include "lvgl.h"
|
||||||
#include "preferences.h"
|
#include "preferences.h"
|
||||||
#include "tactility.h"
|
#include "tactility.h"
|
||||||
#include "ui/spacer.h"
|
|
||||||
#include "ui/toolbar.h"
|
#include "ui/toolbar.h"
|
||||||
|
|
||||||
#define TAG "display"
|
#define TAG "display"
|
||||||
|
|||||||
116
tactility/src/apps/settings/power/power.c
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
#include "app.h"
|
||||||
|
#include "assets.h"
|
||||||
|
#include "lvgl.h"
|
||||||
|
#include "preferences.h"
|
||||||
|
#include "tactility.h"
|
||||||
|
#include "timer.h"
|
||||||
|
#include "ui/lvgl_sync.h"
|
||||||
|
#include "ui/style.h"
|
||||||
|
#include "ui/toolbar.h"
|
||||||
|
|
||||||
|
#define TAG "power"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Timer* update_timer;
|
||||||
|
const Power* power;
|
||||||
|
lv_obj_t* charge_state;
|
||||||
|
lv_obj_t* charge_level;
|
||||||
|
lv_obj_t* current;
|
||||||
|
} AppData;
|
||||||
|
|
||||||
|
static void app_update_ui(App app) {
|
||||||
|
AppData* data = tt_app_get_data(app);
|
||||||
|
|
||||||
|
const char* charge_state = data->power->is_charging() ? "yes" : "no";
|
||||||
|
uint8_t charge_level = data->power->get_charge_level();
|
||||||
|
uint16_t charge_level_scaled = (int16_t)charge_level * 100 / 255;
|
||||||
|
int32_t current = data->power->get_current();
|
||||||
|
|
||||||
|
tt_lvgl_lock(tt_ms_to_ticks(1000));
|
||||||
|
lv_label_set_text_fmt(data->charge_state, "Charging: %s", charge_state);
|
||||||
|
lv_label_set_text_fmt(data->charge_level, "Charge level: %d%%", charge_level_scaled);
|
||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
lv_label_set_text_fmt(data->current, "Current: %ld mAh", current);
|
||||||
|
#else
|
||||||
|
lv_label_set_text_fmt(data->current, "Current: %d mAh", current);
|
||||||
|
#endif
|
||||||
|
tt_lvgl_unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_power_enabled_change(lv_event_t* event) {
|
||||||
|
lv_event_code_t code = lv_event_get_code(event);
|
||||||
|
lv_obj_t* enable_switch = lv_event_get_target(event);
|
||||||
|
if (code == LV_EVENT_VALUE_CHANGED) {
|
||||||
|
bool is_on = lv_obj_has_state(enable_switch, LV_STATE_CHECKED);
|
||||||
|
App app = lv_event_get_user_data(event);
|
||||||
|
AppData* data = tt_app_get_data(app);
|
||||||
|
data->power->set_charging_enabled(is_on);
|
||||||
|
app_update_ui(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void app_show(App app, lv_obj_t* parent) {
|
||||||
|
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||||
|
|
||||||
|
tt_toolbar_create_for_app(parent, app);
|
||||||
|
|
||||||
|
lv_obj_t* wrapper = lv_obj_create(parent);
|
||||||
|
lv_obj_set_width(wrapper, LV_PCT(100));
|
||||||
|
lv_obj_set_style_border_width(wrapper, 0, 0);
|
||||||
|
lv_obj_set_flex_grow(wrapper, 1);
|
||||||
|
lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN);
|
||||||
|
|
||||||
|
AppData* data = tt_app_get_data(app);
|
||||||
|
|
||||||
|
// Top row: enable/disable
|
||||||
|
lv_obj_t* switch_container = lv_obj_create(wrapper);
|
||||||
|
lv_obj_set_width(switch_container, LV_PCT(100));
|
||||||
|
lv_obj_set_height(switch_container, LV_SIZE_CONTENT);
|
||||||
|
tt_lv_obj_set_style_no_padding(switch_container);
|
||||||
|
tt_lv_obj_set_style_bg_invisible(switch_container);
|
||||||
|
|
||||||
|
lv_obj_t* enable_label = lv_label_create(switch_container);
|
||||||
|
lv_label_set_text(enable_label, "Charging enabled");
|
||||||
|
lv_obj_set_align(enable_label, LV_ALIGN_LEFT_MID);
|
||||||
|
|
||||||
|
lv_obj_t* enable_switch = lv_switch_create(switch_container);
|
||||||
|
lv_obj_add_event_cb(enable_switch, on_power_enabled_change, LV_EVENT_ALL, app);
|
||||||
|
lv_obj_set_align(enable_switch, LV_ALIGN_RIGHT_MID);
|
||||||
|
|
||||||
|
data->charge_state = lv_label_create(wrapper);
|
||||||
|
data->charge_level = lv_label_create(wrapper);
|
||||||
|
data->current = lv_label_create(wrapper);
|
||||||
|
|
||||||
|
app_update_ui(app);
|
||||||
|
tt_timer_start(data->update_timer, tt_ms_to_ticks(1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void app_hide(TT_UNUSED App app) {
|
||||||
|
AppData* data = tt_app_get_data(app);
|
||||||
|
tt_timer_stop(data->update_timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void app_start(App app) {
|
||||||
|
AppData* data = malloc(sizeof(AppData));
|
||||||
|
data->update_timer = tt_timer_alloc(&app_update_ui, TimerTypePeriodic, app);
|
||||||
|
data->power = tt_get_config()->hardware->power;
|
||||||
|
assert(data->power != NULL); // The Power app only shows up on supported devices
|
||||||
|
tt_app_set_data(app, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void app_stop(App app) {
|
||||||
|
AppData* data = tt_app_get_data(app);
|
||||||
|
tt_timer_free(data->update_timer);
|
||||||
|
free(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
const AppManifest power_app = {
|
||||||
|
.id = "power",
|
||||||
|
.name = "Power",
|
||||||
|
.icon = TT_ASSETS_APP_ICON_POWER_SETTINGS,
|
||||||
|
.type = AppTypeSettings,
|
||||||
|
.on_start = &app_start,
|
||||||
|
.on_stop = &app_stop,
|
||||||
|
.on_show = &app_show,
|
||||||
|
.on_hide = &app_hide
|
||||||
|
};
|
||||||
@ -1,6 +1,7 @@
|
|||||||
#include "app.h"
|
#include "app.h"
|
||||||
#include "assets.h"
|
#include "assets.h"
|
||||||
#include "lvgl.h"
|
#include "lvgl.h"
|
||||||
|
#include "tactility.h"
|
||||||
#include "ui/toolbar.h"
|
#include "ui/toolbar.h"
|
||||||
|
|
||||||
static size_t get_heap_free() {
|
static size_t get_heap_free() {
|
||||||
@ -95,6 +96,8 @@ static void app_show(App app, lv_obj_t* parent) {
|
|||||||
lv_obj_t* esp_idf_version = lv_label_create(build_info_wrapper);
|
lv_obj_t* esp_idf_version = lv_label_create(build_info_wrapper);
|
||||||
lv_label_set_text_fmt(esp_idf_version, "IDF version: %d.%d.%d", ESP_IDF_VERSION_MAJOR, ESP_IDF_VERSION_MINOR, ESP_IDF_VERSION_PATCH);
|
lv_label_set_text_fmt(esp_idf_version, "IDF version: %d.%d.%d", ESP_IDF_VERSION_MAJOR, ESP_IDF_VERSION_MINOR, ESP_IDF_VERSION_PATCH);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AppManifest system_info_app = {
|
AppManifest system_info_app = {
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
#include "mutex.h"
|
|
||||||
#include "service.h"
|
|
||||||
#include "ui/statusbar.h"
|
|
||||||
#include "assets.h"
|
#include "assets.h"
|
||||||
|
#include "mutex.h"
|
||||||
|
#include "power.h"
|
||||||
#include "sdcard.h"
|
#include "sdcard.h"
|
||||||
|
#include "service.h"
|
||||||
#include "services/wifi/wifi.h"
|
#include "services/wifi/wifi.h"
|
||||||
|
#include "tactility.h"
|
||||||
|
#include "ui/statusbar.h"
|
||||||
|
|
||||||
#define TAG "statusbar_service"
|
#define TAG "statusbar_service"
|
||||||
|
|
||||||
@ -15,6 +17,8 @@ typedef struct {
|
|||||||
const char* wifi_last_icon;
|
const char* wifi_last_icon;
|
||||||
int8_t sdcard_icon_id;
|
int8_t sdcard_icon_id;
|
||||||
const char* sdcard_last_icon;
|
const char* sdcard_last_icon;
|
||||||
|
int8_t power_icon_id;
|
||||||
|
const char* power_last_icon;
|
||||||
} ServiceData;
|
} ServiceData;
|
||||||
|
|
||||||
// region wifi
|
// region wifi
|
||||||
@ -89,6 +93,39 @@ static void update_sdcard_icon(ServiceData* data) {
|
|||||||
|
|
||||||
// endregion sdcard
|
// endregion sdcard
|
||||||
|
|
||||||
|
// region power
|
||||||
|
|
||||||
|
static _Nullable const char* power_get_status_icon() {
|
||||||
|
_Nullable const Power* power = tt_get_config()->hardware->power;
|
||||||
|
if (power != NULL) {
|
||||||
|
uint8_t charge = power->get_charge_level();
|
||||||
|
if (charge >= 230) {
|
||||||
|
return TT_ASSETS_ICON_POWER_100;
|
||||||
|
} else if (charge >= 161) {
|
||||||
|
return TT_ASSETS_ICON_POWER_080;
|
||||||
|
} else if (charge >= 127) {
|
||||||
|
return TT_ASSETS_ICON_POWER_060;
|
||||||
|
} else if (charge >= 76) {
|
||||||
|
return TT_ASSETS_ICON_POWER_040;
|
||||||
|
} else {
|
||||||
|
return TT_ASSETS_ICON_POWER_020;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_power_icon(ServiceData* data) {
|
||||||
|
const char* desired_icon = power_get_status_icon();
|
||||||
|
if (data->power_last_icon != desired_icon) {
|
||||||
|
tt_statusbar_icon_set_image(data->power_icon_id, desired_icon);
|
||||||
|
tt_statusbar_icon_set_visibility(data->power_icon_id, desired_icon != NULL);
|
||||||
|
data->power_last_icon = desired_icon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion power
|
||||||
|
|
||||||
// region service
|
// region service
|
||||||
|
|
||||||
static ServiceData* service_data_alloc() {
|
static ServiceData* service_data_alloc() {
|
||||||
@ -101,11 +138,14 @@ static ServiceData* service_data_alloc() {
|
|||||||
.wifi_last_icon = NULL,
|
.wifi_last_icon = NULL,
|
||||||
.sdcard_icon_id = tt_statusbar_icon_add(NULL),
|
.sdcard_icon_id = tt_statusbar_icon_add(NULL),
|
||||||
.sdcard_last_icon = NULL,
|
.sdcard_last_icon = NULL,
|
||||||
|
.power_icon_id = tt_statusbar_icon_add(NULL),
|
||||||
|
.power_last_icon = NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
tt_statusbar_icon_set_visibility(data->wifi_icon_id, true);
|
tt_statusbar_icon_set_visibility(data->wifi_icon_id, true);
|
||||||
update_wifi_icon(data);
|
update_wifi_icon(data);
|
||||||
update_sdcard_icon(data); // also updates visibility
|
update_sdcard_icon(data); // also updates visibility
|
||||||
|
update_power_icon(data);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@ -114,6 +154,8 @@ static void service_data_free(ServiceData* data) {
|
|||||||
tt_mutex_free(data->mutex);
|
tt_mutex_free(data->mutex);
|
||||||
tt_thread_free(data->thread);
|
tt_thread_free(data->thread);
|
||||||
tt_statusbar_icon_remove(data->wifi_icon_id);
|
tt_statusbar_icon_remove(data->wifi_icon_id);
|
||||||
|
tt_statusbar_icon_remove(data->sdcard_icon_id);
|
||||||
|
tt_statusbar_icon_remove(data->power_icon_id);
|
||||||
free(data);
|
free(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,6 +174,7 @@ int32_t service_main(TT_UNUSED void* parameter) {
|
|||||||
while (!data->service_interrupted) {
|
while (!data->service_interrupted) {
|
||||||
update_wifi_icon(data);
|
update_wifi_icon(data);
|
||||||
update_sdcard_icon(data);
|
update_sdcard_icon(data);
|
||||||
|
update_power_icon(data);
|
||||||
tt_delay_ms(1000);
|
tt_delay_ms(1000);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@ -35,6 +35,7 @@ static const ServiceManifest* const system_services[] = {
|
|||||||
extern const AppManifest desktop_app;
|
extern const AppManifest desktop_app;
|
||||||
extern const AppManifest display_app;
|
extern const AppManifest display_app;
|
||||||
extern const AppManifest files_app;
|
extern const AppManifest files_app;
|
||||||
|
extern const AppManifest power_app;
|
||||||
extern const AppManifest settings_app;
|
extern const AppManifest settings_app;
|
||||||
extern const AppManifest system_info_app;
|
extern const AppManifest system_info_app;
|
||||||
extern const AppManifest wifi_connect_app;
|
extern const AppManifest wifi_connect_app;
|
||||||
@ -65,10 +66,15 @@ static const AppManifest* const system_apps[] = {
|
|||||||
|
|
||||||
static void register_system_apps() {
|
static void register_system_apps() {
|
||||||
TT_LOG_I(TAG, "Registering default apps");
|
TT_LOG_I(TAG, "Registering default apps");
|
||||||
|
|
||||||
int app_count = sizeof(system_apps) / sizeof(AppManifest*);
|
int app_count = sizeof(system_apps) / sizeof(AppManifest*);
|
||||||
for (int i = 0; i < app_count; ++i) {
|
for (int i = 0; i < app_count; ++i) {
|
||||||
tt_app_manifest_registry_add(system_apps[i]);
|
tt_app_manifest_registry_add(system_apps[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tt_get_config()->hardware->power != NULL) {
|
||||||
|
tt_app_manifest_registry_add(&power_app);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void register_user_apps(const AppManifest* const apps[TT_CONFIG_APPS_LIMIT]) {
|
static void register_user_apps(const AppManifest* const apps[TT_CONFIG_APPS_LIMIT]) {
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include "lvgl.h"
|
#include "lvgl.h"
|
||||||
#include "lvgl_sync.h"
|
#include "lvgl_sync.h"
|
||||||
|
#include "tactility.h"
|
||||||
|
|
||||||
#define TAG "statusbar"
|
#define TAG "statusbar"
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ static StatusbarData statusbar_data = {
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
lv_obj_t obj;
|
lv_obj_t obj;
|
||||||
lv_obj_t* icons[STATUSBAR_ICON_LIMIT];
|
lv_obj_t* icons[STATUSBAR_ICON_LIMIT];
|
||||||
|
lv_obj_t* battery_icon;
|
||||||
PubSubSubscription* pubsub_subscription;
|
PubSubSubscription* pubsub_subscription;
|
||||||
} Statusbar;
|
} Statusbar;
|
||||||
|
|
||||||
|
|||||||