Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f9acf04dcb | |||
| a091923353 | |||
| 1593eb80ce | |||
| 869a56125f | |||
| 6116521556 | |||
| 3dfc27e93e | |||
| e4ecec64c9 | |||
| ce96474d84 | |||
| 2691dbb014 | |||
| 74eb830870 | |||
| 870924229a | |||
| b2647f46bb | |||
| d1c1a3a369 | |||
| a5090ec194 | |||
| f67cb241b7 | |||
| b2d4dc5ecb | |||
| a7c5d6a2ee | |||
| 08029a84dd |
@@ -18,7 +18,7 @@ runs:
|
||||
shell: bash
|
||||
run: cp sdkconfig.board.${{ inputs.board_id }} sdkconfig
|
||||
- name: 'Build'
|
||||
uses: espressif/esp-idf-ci-action@main
|
||||
uses: espressif/esp-idf-ci-action@v1
|
||||
with:
|
||||
esp_idf_version: v5.4
|
||||
target: ${{ inputs.arch }}
|
||||
|
||||
@@ -18,14 +18,18 @@ runs:
|
||||
shell: bash
|
||||
run: cp sdkconfig.board.${{ inputs.board_id }} sdkconfig
|
||||
- name: 'Build'
|
||||
uses: espressif/esp-idf-ci-action@main
|
||||
uses: espressif/esp-idf-ci-action@v1
|
||||
with:
|
||||
# NOTE: Update with ESP-IDF!
|
||||
esp_idf_version: v5.4
|
||||
target: ${{ inputs.arch }}
|
||||
path: './'
|
||||
- name: 'Release'
|
||||
shell: bash
|
||||
run: Buildscripts/release-sdk.sh release/TactilitySDK
|
||||
env:
|
||||
# NOTE: Update with ESP-IDF!
|
||||
ESP_IDF_VERSION: '5.4'
|
||||
run: Buildscripts/release-sdk.sh release/TactilitySDK
|
||||
- name: 'Upload Artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include <Tactility/hal/Configuration.h>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <sdkconfig.h>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "tt_init.h"
|
||||
#endif
|
||||
|
||||
extern const tt::app::AppManifest hello_world_app;
|
||||
// extern const tt::app::AppManifest hello_world_app;
|
||||
|
||||
extern "C" {
|
||||
|
||||
@@ -17,7 +17,7 @@ void app_main() {
|
||||
*/
|
||||
.hardware = TT_BOARD_HARDWARE,
|
||||
.apps = {
|
||||
&hello_world_app,
|
||||
// &hello_world_app,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,12 +3,26 @@
|
||||
#include "hal/CydDisplay.h"
|
||||
#include "hal/CydSdCard.h"
|
||||
|
||||
#include <Tactility/kernel/SystemEvents.h>
|
||||
|
||||
#include <PwmBacklight.h>
|
||||
|
||||
#define CYD_SPI_TRANSFER_SIZE_LIMIT (TWODOTFOUR_LCD_DRAW_BUFFER_SIZE * LV_COLOR_DEPTH / 8)
|
||||
|
||||
bool initBoot() {
|
||||
return driver::pwmbacklight::init(GPIO_NUM_27);
|
||||
if (!driver::pwmbacklight::init(GPIO_NUM_27)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This display has a weird glitch with gamma during boot, which results in uneven dark gray colours.
|
||||
// Setting gamma curve index to 0 doesn't work at boot for an unknown reason, so we set the curve index to 1:
|
||||
tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootInitLvglEnd, [](auto event) {
|
||||
auto display = tt::hal::findFirstDevice<tt::hal::display::DisplayDevice>(tt::hal::Device::Type::Display);
|
||||
assert(display != nullptr);
|
||||
tt::lvgl::lock(portMAX_DELAY);
|
||||
display->setGammaCurve(1U);
|
||||
tt::lvgl::unlock();
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const tt::hal::Configuration cyd_2432S032c_config = {
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
#define CORE2_SPI_TRANSFER_SIZE_LIMIT (CORE2_LCD_DRAW_BUFFER_SIZE * LV_COLOR_DEPTH / 8)
|
||||
|
||||
using namespace tt::hal;
|
||||
|
||||
extern const tt::hal::Configuration m5stack_core2 = {
|
||||
.initBoot = initBoot,
|
||||
.createDisplay = createDisplay,
|
||||
@@ -74,5 +76,30 @@ extern const tt::hal::Configuration m5stack_core2 = {
|
||||
.isMutable = false,
|
||||
.lock = tt::lvgl::getSyncLock() // esp_lvgl_port owns the lock for the display
|
||||
}
|
||||
},
|
||||
.uart {
|
||||
uart::Configuration {
|
||||
.name = "Grove",
|
||||
.port = UART_NUM_1,
|
||||
.rxPin = GPIO_NUM_32,
|
||||
.txPin = GPIO_NUM_33,
|
||||
.rtsPin = GPIO_NUM_NC,
|
||||
.ctsPin = GPIO_NUM_NC,
|
||||
.rxBufferSize = 1024,
|
||||
.txBufferSize = 1024,
|
||||
.config = {
|
||||
.baud_rate = 115200,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.rx_flow_ctrl_thresh = 0,
|
||||
.source_clk = UART_SCLK_DEFAULT,
|
||||
.flags = {
|
||||
.allow_pd = 0,
|
||||
.backup_before_sleep = 0,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "Main.h"
|
||||
#include <Tactility/hal/Configuration.h>
|
||||
|
||||
namespace simulator {
|
||||
/** Set the function pointer of the real app_main() */
|
||||
|
||||
@@ -137,7 +137,7 @@ static void updatePowerSwitch() {
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t powerSwitchMain(void*) { // check power switch every 10th of sec
|
||||
static int32_t powerSwitchMain() { // check power switch every 10th of sec
|
||||
while (true) {
|
||||
updatePowerSwitch();
|
||||
tt::kernel::delayMillis(200);
|
||||
@@ -148,8 +148,7 @@ static void startPowerSwitchThread() {
|
||||
powerThread = std::make_unique<tt::Thread>(
|
||||
"unphone_power_switch",
|
||||
4096,
|
||||
powerSwitchMain,
|
||||
nullptr
|
||||
[]() { return powerSwitchMain(); }
|
||||
);
|
||||
powerThread->start();
|
||||
}
|
||||
|
||||
@@ -23,15 +23,15 @@ std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
|
||||
esp_lcd_rgb_panel_config_t rgb_panel_config = {
|
||||
.clk_src = LCD_CLK_SRC_DEFAULT,
|
||||
.timings = {
|
||||
.pclk_hz = 12'000'000, // NOTE: original was 14MHz, but we had to slow it down with PSRAM frame buffer,
|
||||
.pclk_hz = 16'000'000,
|
||||
.h_res = 800,
|
||||
.v_res = 480,
|
||||
.hsync_pulse_width = 10,
|
||||
.hsync_back_porch = 10,
|
||||
.hsync_front_porch = 20,
|
||||
.vsync_pulse_width = 10,
|
||||
.vsync_back_porch = 10,
|
||||
.vsync_front_porch = 10,
|
||||
.hsync_pulse_width = 4,
|
||||
.hsync_back_porch = 8,
|
||||
.hsync_front_porch = 8,
|
||||
.vsync_pulse_width = 4,
|
||||
.vsync_back_porch = 8,
|
||||
.vsync_front_porch = 8,
|
||||
.flags = {
|
||||
.hsync_idle_low = false,
|
||||
.vsync_idle_low = false,
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
if (NOT WIN32)
|
||||
string(ASCII 27 Esc)
|
||||
set(ColorReset "${Esc}[m")
|
||||
set(Cyan "${Esc}[36m")
|
||||
else ()
|
||||
set(ColorReset "")
|
||||
set(Cyan "")
|
||||
endif ()
|
||||
|
||||
function(INIT_TACTILITY_GLOBALS SDKCONFIG_FILE)
|
||||
get_filename_component(SDKCONFIG_FILE_ABS ${SDKCONFIG_FILE} ABSOLUTE)
|
||||
# Find the board identifier in the sdkconfig file
|
||||
@@ -10,7 +19,7 @@ function(INIT_TACTILITY_GLOBALS SDKCONFIG_FILE)
|
||||
set(id_length 0)
|
||||
math(EXPR id_length "${sdkconfig_board_id_length} - 21")
|
||||
string(SUBSTRING ${sdkconfig_board_id} 20 ${id_length} board_id)
|
||||
message("Building board ${board_id}")
|
||||
message("Board name: ${Cyan}${board_id}${ColorReset}")
|
||||
|
||||
if (board_id STREQUAL "cyd-2432s024c")
|
||||
set(TACTILITY_BOARD_PROJECT CYD-2432S024C)
|
||||
@@ -53,7 +62,7 @@ function(INIT_TACTILITY_GLOBALS SDKCONFIG_FILE)
|
||||
if (TACTILITY_BOARD_PROJECT STREQUAL "")
|
||||
message(FATAL_ERROR "No subproject mapped to \"${TACTILITY_BOARD_ID}\" in root Buildscripts/board.cmake")
|
||||
else ()
|
||||
message("Board project: Boards/${TACTILITY_BOARD_PROJECT}")
|
||||
message("Board path: ${Cyan}Boards/${TACTILITY_BOARD_PROJECT}${ColorReset}\n")
|
||||
set_property(GLOBAL PROPERTY TACTILITY_BOARD_PROJECT ${TACTILITY_BOARD_PROJECT})
|
||||
set_property(GLOBAL PROPERTY TACTILITY_BOARD_ID ${board_id})
|
||||
endif ()
|
||||
|
||||
@@ -27,12 +27,13 @@ fi
|
||||
|
||||
echoNewPhase "Cleaning build folder"
|
||||
|
||||
rm -rf build
|
||||
#rm -rf build
|
||||
|
||||
echoNewPhase "Building $sdkconfig_file"
|
||||
|
||||
cp $sdkconfig_file sdkconfig
|
||||
if not idf.py build; then
|
||||
idf.py build
|
||||
if [[ $? != 0 ]]; then
|
||||
fatalError "Failed to build esp32s3 SDK"
|
||||
fi
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
if (NOT WIN32)
|
||||
string(ASCII 27 Esc)
|
||||
set(ColorReset "${Esc}[m")
|
||||
set(Cyan "${Esc}[36m")
|
||||
set(Grey "${Esc}[37m")
|
||||
set(LightPurple "${Esc}[1;35m")
|
||||
set(White "${Esc}[1;37m")
|
||||
else ()
|
||||
set(ColorReset "")
|
||||
set(Cyan "")
|
||||
set(Grey "")
|
||||
set(LightPurple "")
|
||||
set(White "")
|
||||
endif ()
|
||||
@@ -4,24 +4,24 @@ file(READ ${VERSION_TEXT_FILE} TACTILITY_VERSION)
|
||||
|
||||
if (DEFINED ENV{ESP_IDF_VERSION})
|
||||
set(TACTILITY_TARGET " @ ESP-IDF")
|
||||
else()
|
||||
else ()
|
||||
set(TACTILITY_TARGET " @ Simulator")
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
if(NOT WIN32)
|
||||
if (NOT WIN32)
|
||||
string(ASCII 27 Esc)
|
||||
set(ColourReset "${Esc}[m")
|
||||
set(Cyan "${Esc}[36m")
|
||||
set(Grey "${Esc}[37m")
|
||||
set(ColorReset "${Esc}[m")
|
||||
set(Cyan "${Esc}[36m")
|
||||
set(Grey "${Esc}[37m")
|
||||
set(LightPurple "${Esc}[1;35m")
|
||||
set(White "${Esc}[1;37m")
|
||||
else()
|
||||
set(ColourReset "")
|
||||
set(Cyan "")
|
||||
set(Grey "")
|
||||
set(White "${Esc}[1;37m")
|
||||
else ()
|
||||
set(ColorReset "")
|
||||
set(Cyan "")
|
||||
set(Grey "")
|
||||
set(LightPurple "")
|
||||
set(White "")
|
||||
endif()
|
||||
set(White "")
|
||||
endif ()
|
||||
|
||||
# Some terminals (e.g. GitHub Actions) reset colour for every in a multiline message(),
|
||||
# so we add the colour to each line instead of assuming it would automatically be re-used.
|
||||
@@ -41,4 +41,4 @@ message("\n\n\
|
||||
${Cyan}@@@\n\
|
||||
${Cyan}@@@\n\
|
||||
${Cyan}@@@\n\
|
||||
${Cyan}@@\n\n${ColourReset}")
|
||||
${Cyan}@@\n\n${ColorReset}")
|
||||
|
||||
@@ -46,4 +46,4 @@ cp Libraries/elf_loader/license.txt $elf_loader_library_path/
|
||||
|
||||
cp Buildscripts/CMake/TactilitySDK.cmake $target_path/
|
||||
cp Buildscripts/CMake/CMakeLists.txt $target_path/
|
||||
echo -n $ESP_IDF_VERSION >> $target_path/idf-version.txt
|
||||
printf '%s' "$ESP_IDF_VERSION" >> $target_path/idf-version.txt
|
||||
|
||||
@@ -8,14 +8,24 @@ set(CMAKE_ASM_COMPILE_OBJECT "${CMAKE_CXX_COMPILER_TARGET}")
|
||||
|
||||
include("Buildscripts/logo.cmake")
|
||||
|
||||
if (NOT WIN32)
|
||||
string(ASCII 27 Esc)
|
||||
set(ColorReset "${Esc}[m")
|
||||
set(Cyan "${Esc}[36m")
|
||||
else ()
|
||||
set(ColorReset "")
|
||||
set(Cyan "")
|
||||
endif ()
|
||||
|
||||
file(READ version.txt TACTILITY_VERSION)
|
||||
add_compile_definitions(TT_VERSION="${TACTILITY_VERSION}")
|
||||
|
||||
if (DEFINED ENV{ESP_IDF_VERSION})
|
||||
message("Building with ESP-IDF v$ENV{ESP_IDF_VERSION}")
|
||||
message("Using ESP-IDF ${Cyan}v$ENV{ESP_IDF_VERSION}${ColorReset}")
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
include("Buildscripts/board.cmake")
|
||||
|
||||
init_tactility_globals("sdkconfig")
|
||||
get_property(TACTILITY_BOARD_PROJECT GLOBAL PROPERTY TACTILITY_BOARD_PROJECT)
|
||||
|
||||
@@ -45,11 +55,11 @@ if (DEFINED ENV{ESP_IDF_VERSION})
|
||||
add_compile_definitions(LV_CONF_PATH="${LVGL_CONFIG_FULL_PATH}/lv_conf_kconfig.h")
|
||||
idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=esp_panic_handler" APPEND)
|
||||
idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=esp_log_write" APPEND)
|
||||
else()
|
||||
else ()
|
||||
message("Building for sim target")
|
||||
add_compile_definitions(CONFIG_TT_BOARD_ID="simulator")
|
||||
add_compile_definitions(CONFIG_TT_BOARD_NAME="Simulator")
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
project(Tactility)
|
||||
|
||||
@@ -58,7 +68,7 @@ if (NOT DEFINED ENV{ESP_IDF_VERSION})
|
||||
add_subdirectory(Tactility)
|
||||
add_subdirectory(TactilityCore)
|
||||
add_subdirectory(Boards/Simulator)
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
if (NOT DEFINED ENV{ESP_IDF_VERSION})
|
||||
# FreeRTOS
|
||||
@@ -94,4 +104,4 @@ if (NOT DEFINED ENV{ESP_IDF_VERSION})
|
||||
target_compile_definitions(lvgl PUBLIC "-DLV_CONF_PATH=\"${PROJECT_SOURCE_DIR}/Libraries/lvgl_conf/lv_conf_simulator.h\"")
|
||||
target_link_libraries(lvgl PRIVATE SDL2-static)
|
||||
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
|
Before Width: | Height: | Size: 564 B After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 724 B After Width: | Height: | Size: 421 B |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 432 B |
|
After Width: | Height: | Size: 518 B |
|
After Width: | Height: | Size: 308 B |
|
After Width: | Height: | Size: 239 B |
|
After Width: | Height: | Size: 287 B |
|
After Width: | Height: | Size: 412 B |
|
After Width: | Height: | Size: 417 B |
|
After Width: | Height: | Size: 489 B |
|
After Width: | Height: | Size: 290 B |
|
After Width: | Height: | Size: 498 B |
|
After Width: | Height: | Size: 448 B |
|
After Width: | Height: | Size: 530 B |
|
After Width: | Height: | Size: 446 B |
|
After Width: | Height: | Size: 522 B |
|
Before Width: | Height: | Size: 333 B After Width: | Height: | Size: 333 B |
|
Before Width: | Height: | Size: 240 B After Width: | Height: | Size: 240 B |
|
Before Width: | Height: | Size: 286 B After Width: | Height: | Size: 286 B |
|
After Width: | Height: | Size: 403 B |
|
Before Width: | Height: | Size: 429 B After Width: | Height: | Size: 429 B |
|
After Width: | Height: | Size: 491 B |
|
Before Width: | Height: | Size: 299 B After Width: | Height: | Size: 299 B |
|
Before Width: | Height: | Size: 535 B After Width: | Height: | Size: 535 B |
|
Before Width: | Height: | Size: 436 B After Width: | Height: | Size: 436 B |
|
Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 392 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M240-160q-33 0-56.5-23.5T160-240q0-33 23.5-56.5T240-320q33 0 56.5 23.5T320-240q0 33-23.5 56.5T240-160Zm240 0q-33 0-56.5-23.5T400-240q0-33 23.5-56.5T480-320q33 0 56.5 23.5T560-240q0 33-23.5 56.5T480-160Zm240 0q-33 0-56.5-23.5T640-240q0-33 23.5-56.5T720-320q33 0 56.5 23.5T800-240q0 33-23.5 56.5T720-160ZM240-400q-33 0-56.5-23.5T160-480q0-33 23.5-56.5T240-560q33 0 56.5 23.5T320-480q0 33-23.5 56.5T240-400Zm240 0q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm240 0q-33 0-56.5-23.5T640-480q0-33 23.5-56.5T720-560q33 0 56.5 23.5T800-480q0 33-23.5 56.5T720-400ZM240-640q-33 0-56.5-23.5T160-720q0-33 23.5-56.5T240-800q33 0 56.5 23.5T320-720q0 33-23.5 56.5T240-640Zm240 0q-33 0-56.5-23.5T400-720q0-33 23.5-56.5T480-800q33 0 56.5 23.5T560-720q0 33-23.5 56.5T480-640Zm240 0q-33 0-56.5-23.5T640-720q0-33 23.5-56.5T720-800q33 0 56.5 23.5T800-720q0 33-23.5 56.5T720-640Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h240l80 80h320q33 0 56.5 23.5T880-640v400q0 33-23.5 56.5T800-160H160Zm0-80h640v-400H447l-80-80H160v480Zm0 0v-480 480Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 301 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="m370-80-16-128q-13-5-24.5-12T307-235l-119 50L78-375l103-78q-1-7-1-13.5v-27q0-6.5 1-13.5L78-585l110-190 119 50q11-8 23-15t24-12l16-128h220l16 128q13 5 24.5 12t22.5 15l119-50 110 190-103 78q1 7 1 13.5v27q0 6.5-2 13.5l103 78-110 190-118-50q-11 8-23 15t-24 12L590-80H370Zm70-80h79l14-106q31-8 57.5-23.5T639-327l99 41 39-68-86-65q5-14 7-29.5t2-31.5q0-16-2-31.5t-7-29.5l86-65-39-68-99 42q-22-23-48.5-38.5T533-694l-13-106h-79l-14 106q-31 8-57.5 23.5T321-633l-99-41-39 68 86 64q-5 15-7 30t-2 32q0 16 2 31t7 30l-86 65 39 68 99-42q22 23 48.5 38.5T427-266l13 106Zm42-180q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Zm-2-140Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 771 B |
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#e8eaed"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="apps.svg"
|
||||
inkscape:export-filename="icon_apps.png"
|
||||
inkscape:export-xdpi="160"
|
||||
inkscape:export-ydpi="160"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="17.088414"
|
||||
inkscape:cx="14.600536"
|
||||
inkscape:cy="18.960215"
|
||||
inkscape:window-width="1503"
|
||||
inkscape:window-height="933"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="m 122.37644,-3.1685927 q -49.173234,0 -84.190541,-35.0173063 -35.0173063,-35.017307 -35.0173063,-84.190541 0,-49.17324 35.0173063,-84.19055 35.017307,-35.01731 84.190541,-35.01731 49.17324,0 84.19055,35.01731 35.01731,35.01731 35.01731,84.19055 0,49.173234 -35.01731,84.190541 -35.01731,35.0173063 -84.19055,35.0173063 z m 357.62356,0 q -49.17324,0 -84.19055,-35.0173063 -35.0173,-35.017307 -35.0173,-84.190541 0,-49.17324 35.0173,-84.19055 35.01731,-35.01731 84.19055,-35.01731 49.17324,0 84.19055,35.01731 35.0173,35.01731 35.0173,84.19055 0,49.173234 -35.0173,84.190541 Q 529.17324,-3.1685927 480,-3.1685927 Z m 357.62356,0 q -49.17324,0 -84.19055,-35.0173063 -35.01731,-35.017307 -35.01731,-84.190541 0,-49.17324 35.01731,-84.19055 35.01731,-35.01731 84.19055,-35.01731 49.17323,0 84.19054,35.01731 35.01731,35.01731 35.01731,84.19055 0,49.173234 -35.01731,84.190541 -35.01731,35.0173063 -84.19054,35.0173063 z M 122.37644,-360.79215 q -49.173234,0 -84.190541,-35.0173 Q 3.1685927,-430.82676 3.1685927,-480 q 0,-49.17324 35.0173063,-84.19055 35.017307,-35.0173 84.190541,-35.0173 49.17324,0 84.19055,35.0173 35.01731,35.01731 35.01731,84.19055 0,49.17324 -35.01731,84.19055 -35.01731,35.0173 -84.19055,35.0173 z m 357.62356,0 q -49.17324,0 -84.19055,-35.0173 -35.0173,-35.01731 -35.0173,-84.19055 0,-49.17324 35.0173,-84.19055 35.01731,-35.0173 84.19055,-35.0173 49.17324,0 84.19055,35.0173 35.0173,35.01731 35.0173,84.19055 0,49.17324 -35.0173,84.19055 -35.01731,35.0173 -84.19055,35.0173 z m 357.62356,0 q -49.17324,0 -84.19055,-35.0173 Q 718.4157,-430.82676 718.4157,-480 q 0,-49.17324 35.01731,-84.19055 35.01731,-35.0173 84.19055,-35.0173 49.17323,0 84.19054,35.0173 35.01731,35.01731 35.01731,84.19055 0,49.17324 -35.01731,84.19055 -35.01731,35.0173 -84.19054,35.0173 z M 122.37644,-718.4157 q -49.173234,0 -84.190541,-35.01731 -35.0173063,-35.01731 -35.0173063,-84.19055 0,-49.17323 35.0173063,-84.19054 35.017307,-35.01731 84.190541,-35.01731 49.17324,0 84.19055,35.01731 35.01731,35.01731 35.01731,84.19054 0,49.17324 -35.01731,84.19055 -35.01731,35.01731 -84.19055,35.01731 z m 357.62356,0 q -49.17324,0 -84.19055,-35.01731 -35.0173,-35.01731 -35.0173,-84.19055 0,-49.17323 35.0173,-84.19054 35.01731,-35.01731 84.19055,-35.01731 49.17324,0 84.19055,35.01731 35.0173,35.01731 35.0173,84.19054 0,49.17324 -35.0173,84.19055 Q 529.17324,-718.4157 480,-718.4157 Z m 357.62356,0 q -49.17324,0 -84.19055,-35.01731 -35.01731,-35.01731 -35.01731,-84.19055 0,-49.17323 35.01731,-84.19054 35.01731,-35.01731 84.19055,-35.01731 49.17323,0 84.19054,35.01731 35.01731,35.01731 35.01731,84.19054 0,49.17324 -35.01731,84.19055 -35.01731,35.01731 -84.19054,35.01731 z"
|
||||
id="path1"
|
||||
style="stroke-width:1.4901" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#e8eaed"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="folder.svg"
|
||||
inkscape:export-filename="icon_files.png"
|
||||
inkscape:export-xdpi="160"
|
||||
inkscape:export-ydpi="160"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="48.333333"
|
||||
inkscape:cx="11.989655"
|
||||
inkscape:cy="12"
|
||||
inkscape:window-width="1503"
|
||||
inkscape:window-height="933"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="m 96.441379,-96.441379 q -39.554482,0 -67.722069,-28.167591 -28.16758586,-28.16758 -28.16758586,-67.72206 v -575.33794 q 0,-39.55448 28.16758586,-67.72206 28.167587,-28.16759 67.722069,-28.16759 H 384.11034 L 480,-767.66897 h 383.55862 q 39.55448,0 67.72207,28.16759 28.16759,28.16759 28.16759,67.72207 v 479.44828 q 0,39.55448 -28.16759,67.72206 -28.16759,28.167591 -67.72207,28.167591 z m 0,-95.889651 H 863.55862 v -479.44828 h -423.1131 l -95.88966,-95.88966 H 96.441379 Z m 0,0 v -575.33794 z"
|
||||
id="path1"
|
||||
style="stroke-width:1.19862" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#e8eaed"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="settings.svg"
|
||||
inkscape:export-filename="icon_settings.png"
|
||||
inkscape:export-xdpi="160"
|
||||
inkscape:export-ydpi="160"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="48.333333"
|
||||
inkscape:cx="11.989655"
|
||||
inkscape:cy="12"
|
||||
inkscape:window-width="1503"
|
||||
inkscape:window-height="933"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="M 347.80751,0.69994853 328.57952,-153.12403 q -15.62275,-6.00875 -29.44288,-14.421 -13.82012,-8.41225 -27.03937,-18.02625 L 129.08904,-125.48379 -3.1034483,-353.81626 120.67679,-447.55275 q -1.20175,-8.41225 -1.20175,-16.22363 v -32.44724 q 0,-7.81138 1.20175,-16.22363 L -3.1034483,-606.18374 129.08904,-834.51621 l 143.00823,60.08749 q 13.21925,-9.614 27.64025,-18.02625 14.421,-8.41225 28.842,-14.421 l 19.22799,-153.82398 h 264.38498 l 19.22799,153.82398 q 15.62275,6.00875 29.44288,14.421 13.82012,8.41225 27.03937,18.02625 l 143.00823,-60.08749 132.19249,228.33247 -123.78024,93.73649 q 1.20175,8.41225 1.20175,16.22363 v 32.44724 q 0,7.81138 -2.4035,16.22363 l 123.78024,93.73649 -132.19249,228.33247 -141.80648,-60.08749 q -13.21925,9.614 -27.64025,18.02625 -14.421,8.41225 -28.842,14.421 L 612.19249,0.69994853 Z m 84.1225,-96.13998953 h 94.93823 l 16.8245,-127.385489 q 37.25425,-9.614 69.10062,-28.24112 31.84637,-18.62712 58.28487,-45.06562 l 118.97324,49.27175 46.86824,-81.719 -103.35049,-78.11374 q 6.00875,-16.8245 8.41225,-35.45162 2.4035,-18.62712 2.4035,-37.85512 0,-19.228 -2.4035,-37.85512 -2.4035,-18.62712 -8.41225,-35.45162 l 103.35049,-78.11374 -46.86824,-81.719 -118.97324,50.4735 q -26.4385,-27.64025 -58.28487,-46.26737 -31.84637,-18.62712 -69.10062,-28.24112 l -15.62275,-127.38549 h -94.93823 l -16.8245,127.38549 q -37.25425,9.614 -69.10062,28.24112 -31.84637,18.62712 -58.28487,45.06562 l -118.97324,-49.27175 -46.86824,81.719 103.35049,76.91199 q -6.00875,18.02625 -8.41225,36.05249 -2.4035,18.02625 -2.4035,38.456 0,19.228 2.4035,37.25425 2.4035,18.02624 8.41225,36.05249 l -103.35049,78.11374 46.86824,81.719 118.97324,-50.4735 q 26.4385,27.64025 58.28487,46.26737 31.84637,18.62712 69.10062,28.24112 z M 482.4035,-311.75502 q 69.70149,0 118.97324,-49.27174 49.27174,-49.27175 49.27174,-118.97324 0,-69.70149 -49.27174,-118.97324 -49.27175,-49.27174 -118.97324,-49.27174 -70.90324,0 -119.57411,49.27174 -48.67087,49.27175 -48.67087,118.97324 0,69.70149 48.67087,118.97324 48.67087,49.27174 119.57411,49.27174 z M 480,-480 Z"
|
||||
id="path1"
|
||||
style="stroke-width:1.20175" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- License: Apache. Made by Iconscout: https://github.com/Iconscout/unicons -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1"><path d="M12.71,17.29a1,1,0,0,0-.16-.12.56.56,0,0,0-.17-.09.6.6,0,0,0-.19-.06.93.93,0,0,0-.57.06.9.9,0,0,0-.54.54A.84.84,0,0,0,11,18a1,1,0,0,0,.07.38,1.46,1.46,0,0,0,.22.33A1,1,0,0,0,12,19a.84.84,0,0,0,.38-.08,1.15,1.15,0,0,0,.33-.21A1,1,0,0,0,13,18a1,1,0,0,0-.08-.38A1,1,0,0,0,12.71,17.29ZM8.55,13.17a.56.56,0,0,0-.17-.09A.6.6,0,0,0,8.19,13a.86.86,0,0,0-.39,0l-.18.06-.18.09-.15.12A1.05,1.05,0,0,0,7,14a1,1,0,0,0,.29.71,1.15,1.15,0,0,0,.33.21A1,1,0,0,0,9,14a1.05,1.05,0,0,0-.29-.71Zm.16,4.12a1,1,0,0,0-.33-.21A1,1,0,0,0,7.8,17l-.18.06a.76.76,0,0,0-.18.09,1.58,1.58,0,0,0-.15.12,1,1,0,0,0-.21.33.94.94,0,0,0,0,.76,1.15,1.15,0,0,0,.21.33A1,1,0,0,0,8,19a.84.84,0,0,0,.38-.08,1.15,1.15,0,0,0,.33-.21,1.15,1.15,0,0,0,.21-.33.94.94,0,0,0,0-.76A1,1,0,0,0,8.71,17.29Zm2.91-4.21a1,1,0,0,0-.33.21A1.05,1.05,0,0,0,11,14a1,1,0,0,0,1.38.92,1.15,1.15,0,0,0,.33-.21A1,1,0,0,0,13,14a1.05,1.05,0,0,0-.29-.71A1,1,0,0,0,11.62,13.08Zm5.09,4.21a1.15,1.15,0,0,0-.33-.21,1,1,0,0,0-1.09.21,1,1,0,0,0-.21.33.94.94,0,0,0,0,.76,1.15,1.15,0,0,0,.21.33A1,1,0,0,0,16,19a.84.84,0,0,0,.38-.08,1.15,1.15,0,0,0,.33-.21,1,1,0,0,0,.21-1.09A1,1,0,0,0,16.71,17.29ZM16,5H8A1,1,0,0,0,7,6v4a1,1,0,0,0,1,1h8a1,1,0,0,0,1-1V6A1,1,0,0,0,16,5ZM15,9H9V7h6Zm3-8H6A3,3,0,0,0,3,4V20a3,3,0,0,0,3,3H18a3,3,0,0,0,3-3V4A3,3,0,0,0,18,1Zm1,19a1,1,0,0,1-1,1H6a1,1,0,0,1-1-1V4A1,1,0,0,1,6,3H18a1,1,0,0,1,1,1Zm-2.45-6.83a.56.56,0,0,0-.17-.09.6.6,0,0,0-.19-.06.86.86,0,0,0-.39,0l-.18.06-.18.09-.15.12A1.05,1.05,0,0,0,15,14a1,1,0,0,0,1.38.92,1.15,1.15,0,0,0,.33-.21A1,1,0,0,0,17,14a1.05,1.05,0,0,0-.29-.71Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- License: PD. Made by Mary Akveo: https://maryakveo.com/ -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" id="chat" data-name="Line Color" xmlns="http://www.w3.org/2000/svg" class="icon line-color"><path id="primary" d="M18.81,16.23,20,21l-4.95-2.48A9.84,9.84,0,0,1,12,19c-5,0-9-3.58-9-8s4-8,9-8,9,3.58,9,8A7.49,7.49,0,0,1,18.81,16.23Z" style="fill: none; stroke: #000000; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path></svg>
|
||||
|
After Width: | Height: | Size: 518 B |
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- License: PD. Made by Mary Akveo: https://maryakveo.com/ -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" id="chip" data-name="Flat Line" xmlns="http://www.w3.org/2000/svg" class="icon flat-line"><rect id="secondary" x="6" y="6" width="12" height="12" rx="1" style="fill: none; stroke-width: 2;"></rect><path id="primary" d="M12,6V3m4,3V4M8,6V4m10,8h3m-3,4h2M18,8h2M12,18v3M8,18v2m8-2v2M6,12H3M6,8H4m2,8H4m14,1V7a1,1,0,0,0-1-1H7A1,1,0,0,0,6,7V17a1,1,0,0,0,1,1H17A1,1,0,0,0,18,17Z" style="fill: none; stroke: #333333; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path></svg>
|
||||
|
After Width: | Height: | Size: 662 B |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- License: MIT. Made by vmware: https://github.com/vmware/clarity-assets -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 36 36" version="1.1" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>note-line</title>
|
||||
<path d="M28,30H6V8H19.22l2-2H6A2,2,0,0,0,4,8V30a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V15l-2,2Z" class="clr-i-outline clr-i-outline-path-1"></path><path d="M33.53,5.84,30.16,2.47a1.61,1.61,0,0,0-2.28,0L14.17,16.26l-1.11,4.81A1.61,1.61,0,0,0,14.63,23,1.69,1.69,0,0,0,15,23l4.85-1.07L33.53,8.12A1.61,1.61,0,0,0,33.53,5.84ZM18.81,20.08l-3.66.81L16,17.26,26.32,6.87l2.82,2.82ZM30.27,8.56,27.45,5.74,29,4.16,31.84,7Z" class="clr-i-outline clr-i-outline-path-2"></path>
|
||||
<rect x="0" y="0" width="36" height="36" fill-opacity="0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 878 B |
@@ -1,21 +1,17 @@
|
||||
# TODOs
|
||||
- Split up boot stages, so the last stage can be done from the splash screen
|
||||
|
||||
- Add a Keyboard setting app to override the behaviour of soft keyboard hiding (e.g. keyboard hardware is present, but user wants soft keyboard)
|
||||
- HAL for display touch calibration
|
||||
- Start using non_null (either via MS GSL, or custom)
|
||||
- `hal/Configuration.h` defines C function types: Use C++ std::function instead
|
||||
- Fix system time to not be 1980 (use build year as minimum)
|
||||
- Use std::span or string_view in StringUtils https://youtu.be/FRkJCvHWdwQ?t=2754
|
||||
- Fix bug in T-Deck/etc: esp_lvgl_port settings has a large stack size (~9kB) to fix an issue where the T-Deck would get a stackoverflow. This sometimes happens when WiFi is auto-enabled and you open the app while it is still connecting.
|
||||
- Clean up static_cast when casting to base class.
|
||||
- Mutex: Implement give/take from ISR support (works only for non-recursive ones)
|
||||
- Extend unPhone power driver: add charging status, usb connection status, etc.
|
||||
- Expose app::Paths to TactilityC
|
||||
- Experiment with what happens when using C++ code in an external app (without using standard library!)
|
||||
- Boards' CMakeLists.txt manually declare each source folder. Update them all to do a recursive search of all folders.
|
||||
- We currently build all boards for a given platform (e.g. ESP32S3), but it's better to filter all irrelevant ones based on the Kconfig board settings:
|
||||
Projects will load and compile faster as it won't compile all the dependencies of all these other boards
|
||||
- Make a ledger for setting CPU affinity of various services and tasks
|
||||
- Boot hooks instead of a single boot method in config. Define different boot phases/levels in enum.
|
||||
- Add toggle to Display app for sysmon overlay: https://docs.lvgl.io/master/API/others/sysmon/index.html
|
||||
- CrashHandler: use "corrupted" flag
|
||||
- CrashHandler: process other types of crashes (WDT?)
|
||||
- Call tt::lvgl::isSyncSet after HAL init and show error (and crash?) when it is not set.
|
||||
@@ -38,6 +34,7 @@
|
||||
- Remove flex_flow from app_container in Gui.cpp
|
||||
|
||||
# Nice-to-haves
|
||||
|
||||
- Give external app a different icon. Allow an external app update their id, icon, type and name once they are running(and persist that info?). Loader will need to be able to find app by (external) location.
|
||||
- Audio player app
|
||||
- Audio recording app
|
||||
@@ -48,20 +45,18 @@
|
||||
- Capacity based on voltage: estimation for various devices uses linear voltage curve, but it should use some sort of battery discharge curve.
|
||||
- Statusbar widget to show how much memory is in use?
|
||||
- Wrapper for Slider that shows "+" and "-" buttons, and also the value in a label.
|
||||
- Display app: Add toggle to display performance measurement overlay (consider showing FPS in statusbar!)
|
||||
- Files app: copy/paste actions
|
||||
- On crash, try to save current log to flash or SD card? (this is risky, though, so ask in Discord first)
|
||||
- Support more than 1 hardware keyboard (see lvgl::hardware_keyboard_set_indev()). LVGL init currently calls keyboard init, but that part should probably be done from the KeyboardDevice base class.
|
||||
|
||||
|
||||
# App Ideas
|
||||
|
||||
- Map widget:
|
||||
https://github.com/portapack-mayhem/mayhem-firmware/blob/b66d8b1aa178d8a9cd06436fea788d5d58cb4c8d/firmware/application/ui/ui_geomap.cpp
|
||||
https://github.com/portapack-mayhem/mayhem-firmware/blob/b66d8b1aa178d8a9cd06436fea788d5d58cb4c8d/firmware/tools/generate_world_map.bin.py
|
||||
https://github.com/portapack-mayhem/mayhem-firmware/releases
|
||||
- Weather app: https://lab.flipper.net/apps/flip_weather
|
||||
- wget app: https://lab.flipper.net/apps/web_crawler (add profiles for known public APIs?)
|
||||
- USB implementation to make device act as mass storage device.
|
||||
- System logger
|
||||
- BlueTooth keyboard app
|
||||
- Chip 8 emulator
|
||||
- BadUSB (in December 2024, TinyUSB has a bug where uninstalling and re-installing the driver fails)
|
||||
@@ -70,7 +65,5 @@
|
||||
- GPS app
|
||||
- Investigate CSI https://stevenmhernandez.github.io/ESP32-CSI-Tool/
|
||||
- Compile unix tools to ELF apps?
|
||||
- Calculator
|
||||
- Text editor
|
||||
- Todo list
|
||||
- Calendar
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Releasing Tactility
|
||||
|
||||
1. Test the latest version on several devices
|
||||
2. Build the SDK locally and test it with `ExternalApps/HelloWorld`
|
||||
3. Test the latest SDK build from GitHub with the CDN:
|
||||
1. Download it from the [main branch](https://github.com/ByteWelder/Tactility/actions/workflows/build-sdk.yml)
|
||||
2. Upload it to the [CDN](https://dash.cloudflare.com)
|
||||
3. Update `sdk.json` from [TactilityTool](https://github.com/ByteWelder/TactilityTool) and upload it to [CDN](https://dash.cloudflare.com)
|
||||
4. Test it with `ExternalApps/HelloWorld` (clear all its cache and update the SDK version)
|
||||
4. Download the latest firmwares [main branch](https://github.com/ByteWelder/Tactility/actions/workflows/build-firmware.yml)
|
||||
5. Prepare a new version of [TactilityWebInstaller](https://github.com/ByteWelder/TactilityWebInstaller) locally
|
||||
6. Test the firmwares on all devices with the local web installer
|
||||
7. If all went well: release the web installer
|
||||
8. Test web installer in production (use popular devices)
|
||||
9. Make a new version of the docs available at [TactilityDocs](https://github.com/ByteWelder/TactilityDocs)
|
||||
10. Make a new [GitHub release](https://github.com/ByteWelder/Tactility/releases/new)
|
||||
|
||||
### Post-release
|
||||
|
||||
1. Mention on Discord
|
||||
2. Consider notifying vendors/stakeholders
|
||||
3. Remove dev versions in `sdk.json`from [TactilityTool](https://github.com/ByteWelder/TactilityTool) and upload it to [CDN](https://dash.cloudflare.com)
|
||||
@@ -12,15 +12,12 @@ static void touchReadCallback(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||
touch->readLast(data);
|
||||
}
|
||||
|
||||
static int32_t threadCallback(void* context) {
|
||||
auto* touch = (Ft6x36Touch*)context;
|
||||
touch->driverThreadMain();
|
||||
return 0;
|
||||
}
|
||||
|
||||
Ft6x36Touch::Ft6x36Touch(std::unique_ptr<Configuration> inConfiguration) :
|
||||
configuration(std::move(inConfiguration)),
|
||||
driverThread(tt::Thread("ft6x36", 4096, threadCallback, this))
|
||||
driverThread(tt::Thread("ft6x36", 4096, [this]() {
|
||||
driverThreadMain();
|
||||
return 0;
|
||||
}))
|
||||
{}
|
||||
|
||||
Ft6x36Touch::~Ft6x36Touch() {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
build*/
|
||||
.tactility/
|
||||
@@ -1,4 +0,0 @@
|
||||
rm sdkconfig
|
||||
cp ../../sdkconfig sdkconfig
|
||||
cat sdkconfig.override >> sdkconfig
|
||||
idf.py elf_app
|
||||
@@ -1,2 +0,0 @@
|
||||
CONFIG_PARTITION_TABLE_SINGLE_APP=y
|
||||
CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=n
|
||||
@@ -0,0 +1,2 @@
|
||||
[sdk]
|
||||
version = 0.4.0
|
||||
@@ -0,0 +1,410 @@
|
||||
import configparser
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import subprocess
|
||||
import time
|
||||
import urllib.request
|
||||
import zipfile
|
||||
|
||||
# Targetable platforms that represent a specific hardware target
|
||||
platform_targets = ["esp32", "esp32s3"]
|
||||
# All valid platform commandline arguments
|
||||
platform_arguments = platform_targets.copy()
|
||||
platform_arguments.append("all")
|
||||
ttbuild_path = ".tactility"
|
||||
ttbuild_version = "1.0.0"
|
||||
ttbuild_properties_file = "tactility.properties"
|
||||
ttbuild_cdn = "https://cdn.tactility.one"
|
||||
ttbuild_sdk_json_validity = 3600 # seconds
|
||||
verbose = False
|
||||
use_local_sdk = False
|
||||
|
||||
spinner_pattern = [
|
||||
"⠋",
|
||||
"⠙",
|
||||
"⠹",
|
||||
"⠸",
|
||||
"⠼",
|
||||
"⠴",
|
||||
"⠦",
|
||||
"⠧",
|
||||
"⠇",
|
||||
"⠏"
|
||||
]
|
||||
|
||||
if sys.platform == "win32":
|
||||
shell_color_red = ""
|
||||
shell_color_orange = ""
|
||||
shell_color_green = ""
|
||||
shell_color_purple = ""
|
||||
shell_color_cyan = ""
|
||||
shell_color_reset = ""
|
||||
else:
|
||||
shell_color_red = "\033[91m"
|
||||
shell_color_orange = "\033[93m"
|
||||
shell_color_green = "\033[32m"
|
||||
shell_color_purple = "\033[35m"
|
||||
shell_color_cyan = "\033[36m"
|
||||
shell_color_reset = "\033[m"
|
||||
|
||||
def print_help():
|
||||
print("Usage: python tactility.py [action] [options]")
|
||||
print("")
|
||||
print("Actions:")
|
||||
print(" build [esp32,esp32s3,all,local] Build the app for the specified platform")
|
||||
print(" esp32: ESP32")
|
||||
print(" esp32s3: ESP32 S3")
|
||||
print(" all: all supported ESP platforms")
|
||||
print(" clean Clean the build folders")
|
||||
print(" clearcache Clear the SDK cache")
|
||||
print(" updateself Update this tool")
|
||||
print("")
|
||||
print("Options:")
|
||||
print(" --help Show this commandline info")
|
||||
print(" --local-sdk Use SDK specifiedc by environment variable TACTILITY_SDK_PATH")
|
||||
print(" --skip-build Run everything except the idf.py/CMake commands")
|
||||
print(" --verbose Show extra console output")
|
||||
|
||||
def download_file(url, filepath):
|
||||
global verbose
|
||||
if verbose:
|
||||
print(f"Downloading from {url} to {filepath}")
|
||||
request = urllib.request.Request(
|
||||
url,
|
||||
data=None,
|
||||
headers={
|
||||
"User-Agent": f"Tactility Build Tool {ttbuild_version}"
|
||||
}
|
||||
)
|
||||
try:
|
||||
response = urllib.request.urlopen(request)
|
||||
file = open(filepath, mode="wb")
|
||||
file.write(response.read())
|
||||
file.close()
|
||||
return True
|
||||
except OSError as error:
|
||||
if verbose:
|
||||
print_error(f"Failed to fetch URL {url}\n{error}")
|
||||
return False
|
||||
|
||||
def print_warning(message):
|
||||
print(f"{shell_color_orange}WARNING: {message}{shell_color_reset}")
|
||||
|
||||
def print_error(message):
|
||||
print(f"{shell_color_red}ERROR: {message}{shell_color_reset}")
|
||||
|
||||
def exit_with_error(message):
|
||||
print_error(message)
|
||||
sys.exit(1)
|
||||
|
||||
def is_valid_platform_name(name):
|
||||
global platform_arguments
|
||||
return name in platform_arguments
|
||||
|
||||
def validate_environment():
|
||||
global ttbuild_properties_file, use_local_sdk
|
||||
if os.environ.get("IDF_PATH") is None:
|
||||
exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via $PATH_TO_IDF_SDK/export.sh")
|
||||
if not os.path.exists(ttbuild_properties_file):
|
||||
exit_with_error(f"{ttbuild_properties_file} file not found")
|
||||
if use_local_sdk == False and os.environ.get("TACTILITY_SDK_PATH") is not None:
|
||||
print_warning("TACTILITY_SDK_PATH is set, but will be ignored by this command.")
|
||||
print_warning("If you want to use it, use the 'build local' parameters.")
|
||||
elif use_local_sdk == True and os.environ.get("TACTILITY_SDK_PATH") is None:
|
||||
exit_with_error("local build was requested, but TACTILITY_SDK_PATH environment variable is not set.")
|
||||
|
||||
def setup_environment():
|
||||
global ttbuild_path
|
||||
os.makedirs(ttbuild_path, exist_ok=True)
|
||||
|
||||
def get_sdk_dir(version, platform):
|
||||
global use_local_sdk
|
||||
if use_local_sdk:
|
||||
return os.environ.get("TACTILITY_SDK_PATH")
|
||||
else:
|
||||
global ttbuild_cdn
|
||||
return os.path.join(ttbuild_path, f"{version}-{platform}", "TactilitySDK")
|
||||
|
||||
def get_sdk_version():
|
||||
global ttbuild_properties_file
|
||||
parser = configparser.RawConfigParser()
|
||||
parser.read(ttbuild_properties_file)
|
||||
sdk_dict = dict(parser.items("sdk"))
|
||||
if not "version" in sdk_dict:
|
||||
exit_with_error(f"Could not find 'version' in [sdk] section in {ttbuild_properties_file}")
|
||||
return sdk_dict["version"]
|
||||
|
||||
def get_sdk_root_dir(version, platform):
|
||||
global ttbuild_cdn
|
||||
return os.path.join(ttbuild_path, f"{version}-{platform}")
|
||||
|
||||
def get_sdk_url(version, platform):
|
||||
global ttbuild_cdn
|
||||
return f"{ttbuild_cdn}/TactilitySDK-{version}-{platform}.zip"
|
||||
|
||||
def sdk_exists(version, platform):
|
||||
sdk_dir = get_sdk_dir(version, platform)
|
||||
return os.path.isdir(sdk_dir)
|
||||
|
||||
def should_update_sdk_json():
|
||||
global ttbuild_cdn
|
||||
json_filepath = os.path.join(ttbuild_path, "sdk.json")
|
||||
if os.path.exists(json_filepath):
|
||||
json_modification_time = os.path.getmtime(json_filepath)
|
||||
now = time.time()
|
||||
global ttbuild_sdk_json_validity
|
||||
minimum_seconds_difference = ttbuild_sdk_json_validity
|
||||
return (now - json_modification_time) > minimum_seconds_difference
|
||||
else:
|
||||
return True
|
||||
|
||||
def update_sdk_json():
|
||||
global ttbuild_cdn, ttbuild_path
|
||||
json_url = f"{ttbuild_cdn}/sdk.json"
|
||||
json_filepath = os.path.join(ttbuild_path, "sdk.json")
|
||||
return download_file(json_url, json_filepath)
|
||||
|
||||
def should_fetch_sdkconfig_files():
|
||||
for platform in platform_targets:
|
||||
sdkconfig_filename = f"sdkconfig.app.{platform}"
|
||||
if not os.path.exists(os.path.join(ttbuild_path, sdkconfig_filename)):
|
||||
return True
|
||||
return False
|
||||
|
||||
def fetch_sdkconfig_files():
|
||||
for platform in platform_targets:
|
||||
sdkconfig_filename = f"sdkconfig.app.{platform}"
|
||||
target_path = os.path.join(ttbuild_path, sdkconfig_filename)
|
||||
if not download_file(f"{ttbuild_cdn}/{sdkconfig_filename}", target_path):
|
||||
exit_with_error(f"Failed to download sdkconfig file for {platform}")
|
||||
|
||||
|
||||
def validate_version_and_platforms(sdk_json, sdk_version, platforms_to_build):
|
||||
version_map = sdk_json["versions"]
|
||||
if not sdk_version in version_map:
|
||||
exit_with_error(f"Version not found: {sdk_version}")
|
||||
version_data = version_map[sdk_version]
|
||||
available_platforms = version_data["platforms"]
|
||||
for desired_platform in platforms_to_build:
|
||||
if not desired_platform in available_platforms:
|
||||
exit_with_error(f"Platform {desired_platform} is not available. Available ones: {available_platforms}")
|
||||
|
||||
def validate_self(sdk_json):
|
||||
if not "toolVersion" in sdk_json:
|
||||
exit_with_error("Server returned invalid SDK data format (toolVersion not found)")
|
||||
if not "toolCompatibility" in sdk_json:
|
||||
exit_with_error("Server returned invalid SDK data format (toolCompatibility not found)")
|
||||
if not "toolDownloadUrl" in sdk_json:
|
||||
exit_with_error("Server returned invalid SDK data format (toolDownloadUrl not found)")
|
||||
tool_version = sdk_json["toolVersion"]
|
||||
tool_compatibility = sdk_json["toolCompatibility"]
|
||||
if tool_version != ttbuild_version:
|
||||
print_warning(f"New version available: {tool_version} (currently using {ttbuild_version})")
|
||||
print_warning(f"Run 'tactility.py updateself' to update.")
|
||||
if re.search(tool_compatibility, ttbuild_version) is None:
|
||||
print_error("The tool is not compatible anymore.")
|
||||
print_error("Run 'tactility.py updateself' to update.")
|
||||
sys.exit(1)
|
||||
|
||||
def sdk_download(version, platform):
|
||||
sdk_root_dir = get_sdk_root_dir(version, platform)
|
||||
os.makedirs(sdk_root_dir, exist_ok=True)
|
||||
sdk_url = get_sdk_url(version, platform)
|
||||
filepath = os.path.join(sdk_root_dir, f"{version}-{platform}.zip")
|
||||
print(f"Downloading SDK version {version} for {platform}")
|
||||
if download_file(sdk_url, filepath):
|
||||
with zipfile.ZipFile(filepath, "r") as zip_ref:
|
||||
zip_ref.extractall(os.path.join(sdk_root_dir, "TactilitySDK"))
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def sdk_download_all(version, platforms):
|
||||
for platform in platforms:
|
||||
if not sdk_exists(version, platform):
|
||||
if not sdk_download(version, platform):
|
||||
return False
|
||||
else:
|
||||
if verbose:
|
||||
print(f"Using cached download for SDK version {version} and platform {platform}")
|
||||
return True
|
||||
|
||||
def find_elf_file(platform):
|
||||
build_dir = f"build-{platform}"
|
||||
if os.path.exists(build_dir):
|
||||
for file in os.listdir(build_dir):
|
||||
if file.endswith(".app.elf"):
|
||||
return os.path.join(build_dir, file)
|
||||
return None
|
||||
|
||||
def build_all(version, platforms, skip_build):
|
||||
for platform in platforms:
|
||||
# First build command must be "idf.py build", otherwise it fails to execute "idf.py elf"
|
||||
# We check if the ELF file exists and run the correct command
|
||||
# This can lead to code caching issues, so sometimes a clean build is required
|
||||
if find_elf_file(platform) is None:
|
||||
if not build_first(version, platform, skip_build):
|
||||
break
|
||||
else:
|
||||
if not build_consecutively(version, platform, skip_build):
|
||||
break
|
||||
|
||||
def wait_for_build(process, platform):
|
||||
buffer = []
|
||||
os.set_blocking(process.stdout.fileno(), False)
|
||||
while process.poll() is None:
|
||||
for i in spinner_pattern:
|
||||
time.sleep(0.1)
|
||||
progress_text = f"Building for {platform} {shell_color_cyan}" + str(i) + shell_color_reset
|
||||
sys.stdout.write(progress_text + "\r")
|
||||
while True:
|
||||
line = process.stdout.readline()
|
||||
decoded_line = line.decode("UTF-8")
|
||||
if decoded_line != "":
|
||||
buffer.append(decoded_line)
|
||||
else:
|
||||
break
|
||||
return buffer
|
||||
|
||||
# The first build must call "idf.py build" and consecutive builds must call "idf.py elf" as it finishes faster.
|
||||
# The problem is that the "idf.py build" always results in an error, even though the elf file is created.
|
||||
# The solution is to suppress the error if we find that the elf file was created.
|
||||
def build_first(version, platform, skip_build):
|
||||
sdk_dir = get_sdk_dir(version, platform)
|
||||
if verbose:
|
||||
print(f"Using SDK at {sdk_dir}")
|
||||
os.environ["TACTILITY_SDK_PATH"] = sdk_dir
|
||||
sdkconfig_path = os.path.join(ttbuild_path, f"sdkconfig.app.{platform}")
|
||||
os.system(f"cp {sdkconfig_path} sdkconfig")
|
||||
elf_path = find_elf_file(platform)
|
||||
# Remove previous elf file: re-creation of the file is used to measure if the build succeeded,
|
||||
# as the actual build job will always fail due to technical issues with the elf cmake script
|
||||
if elf_path is not None:
|
||||
os.remove(elf_path)
|
||||
if skip_build:
|
||||
return True
|
||||
print("Building first build")
|
||||
with subprocess.Popen(["idf.py", "-B", f"build-{platform}", "build"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process:
|
||||
build_output = wait_for_build(process, platform)
|
||||
# The return code is never expected to be 0 due to a bug in the elf cmake script, but we keep it just in case
|
||||
if process.returncode == 0:
|
||||
print(f"{shell_color_green}Building for {platform} ✅{shell_color_reset}")
|
||||
return True
|
||||
else:
|
||||
if find_elf_file(platform) is None:
|
||||
for line in build_output:
|
||||
print(line, end="")
|
||||
print(f"{shell_color_red}Building for {platform} failed ❌{shell_color_reset}")
|
||||
return False
|
||||
else:
|
||||
print(f"{shell_color_green}Building for {platform} ✅{shell_color_reset}")
|
||||
return True
|
||||
|
||||
def build_consecutively(version, platform, skip_build):
|
||||
sdk_dir = get_sdk_dir(version, platform)
|
||||
if verbose:
|
||||
print(f"Using SDK at {sdk_dir}")
|
||||
os.environ["TACTILITY_SDK_PATH"] = sdk_dir
|
||||
sdkconfig_path = os.path.join(ttbuild_path, f"sdkconfig.app.{platform}")
|
||||
os.system(f"cp {sdkconfig_path} sdkconfig")
|
||||
if skip_build:
|
||||
return True
|
||||
with subprocess.Popen(["idf.py", "-B", f"build-{platform}", "elf"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process:
|
||||
build_output = wait_for_build(process, platform)
|
||||
if process.returncode == 0:
|
||||
print(f"{shell_color_green}Building for {platform} ✅{shell_color_reset}")
|
||||
return True
|
||||
else:
|
||||
for line in build_output:
|
||||
print(line, end="")
|
||||
print(f"{shell_color_red}Building for {platform} failed ❌{shell_color_reset}")
|
||||
return False
|
||||
|
||||
def read_sdk_json():
|
||||
json_file_path = os.path.join(ttbuild_path, "sdk.json")
|
||||
json_file = open(json_file_path)
|
||||
return json.load(json_file)
|
||||
|
||||
def build_action(platform_arg):
|
||||
# Environment validation
|
||||
validate_environment()
|
||||
# Environment setup
|
||||
setup_environment()
|
||||
platforms_to_build = platform_targets if platform_arg == "all" else [platform_arg]
|
||||
if not is_valid_platform_name(platform_arg):
|
||||
print_help()
|
||||
exit_with_error("Invalid platform name")
|
||||
if not use_local_sdk:
|
||||
if should_fetch_sdkconfig_files():
|
||||
fetch_sdkconfig_files()
|
||||
# Update SDK cache
|
||||
if should_update_sdk_json() and not update_sdk_json():
|
||||
exit_with_error("Failed to retrieve SDK info")
|
||||
sdk_json = read_sdk_json()
|
||||
validate_self(sdk_json)
|
||||
if not "versions" in sdk_json:
|
||||
exit_with_error("Version data not found in sdk.json")
|
||||
# Build
|
||||
sdk_version = get_sdk_version()
|
||||
if not use_local_sdk:
|
||||
validate_version_and_platforms(sdk_json, sdk_version, platforms_to_build)
|
||||
if not sdk_download_all(sdk_version, platforms_to_build):
|
||||
exit_with_error("Failed to download one or more SDKs")
|
||||
build_all(sdk_version, platforms_to_build, skip_build) # Environment validation
|
||||
|
||||
def clean_action():
|
||||
count = 0
|
||||
for path in os.listdir("."):
|
||||
if path.startswith("build-"):
|
||||
print(f"Removing {path}/")
|
||||
shutil.rmtree(path)
|
||||
count = count + 1
|
||||
if count == 0:
|
||||
print("Nothing to clean")
|
||||
|
||||
def clear_cache_action():
|
||||
if os.path.exists(ttbuild_path):
|
||||
print(f"Removing {ttbuild_path}/")
|
||||
shutil.rmtree(ttbuild_path)
|
||||
else:
|
||||
print("Nothing to clear")
|
||||
|
||||
def update_self_action():
|
||||
sdk_json = read_sdk_json()
|
||||
tool_download_url = sdk_json["toolDownloadUrl"]
|
||||
if download_file(tool_download_url, "tactility.py"):
|
||||
print("Updated")
|
||||
else:
|
||||
exit_with_error("Update failed")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"Tactility Build System v{ttbuild_version}")
|
||||
if "--help" in sys.argv:
|
||||
print_help()
|
||||
sys.exit()
|
||||
# Argument validation
|
||||
if len(sys.argv) == 1:
|
||||
print_help()
|
||||
sys.exit()
|
||||
action_arg = sys.argv[1]
|
||||
verbose = "--verbose" in sys.argv
|
||||
skip_build = "--skip-build" in sys.argv
|
||||
use_local_sdk = "--local-sdk" in sys.argv
|
||||
# Actions
|
||||
if action_arg == "build":
|
||||
if len(sys.argv) < 3:
|
||||
print_help()
|
||||
exit_with_error("Commandline parameter missing")
|
||||
build_action(sys.argv[2])
|
||||
elif action_arg == "clean":
|
||||
clean_action()
|
||||
elif action_arg == "clearcache":
|
||||
clear_cache_action()
|
||||
elif action_arg == "updateself":
|
||||
update_self_action()
|
||||
else:
|
||||
print_help()
|
||||
exit_with_error("Unknown commandline parameter")
|
||||
@@ -558,7 +558,7 @@
|
||||
#if LV_USE_THEME_DEFAULT
|
||||
|
||||
/*0: Light mode; 1: Dark mode*/
|
||||
#define LV_THEME_DEFAULT_DARK 0
|
||||
#define LV_THEME_DEFAULT_DARK 1
|
||||
|
||||
/*1: Enable grow on press*/
|
||||
#define LV_THEME_DEFAULT_GROW 1
|
||||
|
||||
@@ -7,11 +7,15 @@
|
||||
#define TT_ASSETS_UI_SPINNER TT_ASSET("spinner.png")
|
||||
|
||||
// App icons
|
||||
#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_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_I2C_SETTINGS TT_ASSET("app_icon_i2c.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_TIME_DATE_SETTINGS TT_ASSET("app_icon_time_date_settings.png")
|
||||
#define TT_ASSETS_APP_ICON_FALLBACK TT_ASSET("app_icon_fallback_dark_mode.png")
|
||||
#define TT_ASSETS_APP_ICON_FILES TT_ASSET("app_icon_files_dark_mode.png")
|
||||
#define TT_ASSETS_APP_ICON_DISPLAY_SETTINGS TT_ASSET("app_icon_display_settings_dark_mode.png")
|
||||
#define TT_ASSETS_APP_ICON_POWER_SETTINGS TT_ASSET("app_icon_power_settings_dark_mode.png")
|
||||
#define TT_ASSETS_APP_ICON_I2C_SETTINGS TT_ASSET("app_icon_i2c_dark_mode.png")
|
||||
#define TT_ASSETS_APP_ICON_SETTINGS TT_ASSET("app_icon_settings_dark_mode.png")
|
||||
#define TT_ASSETS_APP_ICON_SYSTEM_INFO TT_ASSET("app_icon_system_info_dark_mode.png")
|
||||
#define TT_ASSETS_APP_ICON_TIME_DATE_SETTINGS TT_ASSET("app_icon_time_date_settings_dark_mode.png")
|
||||
#define TT_ASSETS_APP_ICON_CALCULATOR TT_ASSET("app_icon_calculator_dark_mode.png")
|
||||
#define TT_ASSETS_APP_ICON_NOTES TT_ASSET("app_icon_notes_dark_mode.png")
|
||||
#define TT_ASSETS_APP_ICON_CHAT TT_ASSET("app_icon_chat_dark_mode.png")
|
||||
#define TT_ASSETS_APP_ICON_GPIO TT_ASSET("app_icon_gpio_dark_mode.png")
|
||||
|
||||
@@ -8,9 +8,13 @@ namespace tt {
|
||||
/**
|
||||
* Settings that persist on NVS flash for ESP32.
|
||||
* On simulator, the settings are only in-memory.
|
||||
*
|
||||
* Note that on ESP32, there are limitations:
|
||||
* - namespace name is limited by NVS_NS_NAME_MAX_SIZE (generally 16 characters)
|
||||
* - key is limited by NVS_KEY_NAME_MAX_SIZE (generally 16 characters)
|
||||
*/
|
||||
class Preferences {
|
||||
private:
|
||||
|
||||
const char* namespace_;
|
||||
|
||||
public:
|
||||
|
||||
@@ -16,9 +16,9 @@ namespace tt::app {
|
||||
class AppContext;
|
||||
enum class Result;
|
||||
|
||||
class App {
|
||||
typedef unsigned int LaunchId;
|
||||
|
||||
private:
|
||||
class App {
|
||||
|
||||
Mutex mutex;
|
||||
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
virtual void onDestroy(AppContext& appContext) {}
|
||||
virtual void onShow(AppContext& appContext, lv_obj_t* parent) {}
|
||||
virtual void onHide(AppContext& appContext) {}
|
||||
virtual void onResult(AppContext& appContext, Result result, std::unique_ptr<Bundle> _Nullable resultData) {}
|
||||
virtual void onResult(AppContext& appContext, LaunchId launchId, Result result, std::unique_ptr<Bundle> _Nullable resultData) {}
|
||||
|
||||
Mutex& getMutex() { return mutex; }
|
||||
|
||||
@@ -83,15 +83,15 @@ std::shared_ptr<App> create() { return std::shared_ptr<T>(new T); }
|
||||
* @param[in] id application name or id
|
||||
* @param[in] parameters optional parameters to pass onto the application
|
||||
*/
|
||||
void start(const std::string& id, std::shared_ptr<const Bundle> _Nullable parameters = nullptr);
|
||||
LaunchId start(const std::string& id, std::shared_ptr<const Bundle> _Nullable parameters = nullptr);
|
||||
|
||||
/** @brief Stop the currently showing app. Show the previous app if any app was still running. */
|
||||
void stop();
|
||||
|
||||
/** @return the currently running app context (it is only ever null before the splash screen is shown) */
|
||||
std::shared_ptr<app::AppContext> _Nullable getCurrentAppContext();
|
||||
std::shared_ptr<AppContext> _Nullable getCurrentAppContext();
|
||||
|
||||
/** @return the currently running app (it is only ever null before the splash screen is shown) */
|
||||
std::shared_ptr<app::App> _Nullable getCurrentApp();
|
||||
std::shared_ptr<App> _Nullable getCurrentApp();
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ typedef void (*OnCreate)(void* appContext, void* _Nullable data);
|
||||
typedef void (*OnDestroy)(void* appContext, void* _Nullable data);
|
||||
typedef void (*OnShow)(void* appContext, void* _Nullable data, lv_obj_t* parent);
|
||||
typedef void (*OnHide)(void* appContext, void* _Nullable data);
|
||||
typedef void (*OnResult)(void* appContext, void* _Nullable data, Result result, Bundle* resultData);
|
||||
typedef void (*OnResult)(void* appContext, void* _Nullable data, LaunchId launchId, Result result, Bundle* resultData);
|
||||
|
||||
void setElfAppManifest(
|
||||
const char* name,
|
||||
|
||||
@@ -5,8 +5,15 @@
|
||||
namespace tt::app::display {
|
||||
|
||||
void setBacklightDuty(uint8_t value);
|
||||
uint8_t getBacklightDuty();
|
||||
|
||||
bool getBacklightDuty(uint8_t& duty);
|
||||
|
||||
void setGammaCurve(uint8_t curveIndex);
|
||||
|
||||
bool getGammaCurve(uint8_t& curveIndex);
|
||||
|
||||
void setRotation(lv_display_rotation_t rotation);
|
||||
|
||||
lv_display_rotation_t getRotation();
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace tt::app::filebrowser {
|
||||
|
||||
void start();
|
||||
|
||||
} // namespace
|
||||
@@ -1,15 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "app/files/View.h"
|
||||
#include "app/files/State.h"
|
||||
#include "app/AppManifest.h"
|
||||
|
||||
#include <lvgl.h>
|
||||
#include <dirent.h>
|
||||
#include <memory>
|
||||
|
||||
namespace tt::app::files {
|
||||
|
||||
void start();
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
namespace tt::app::fileselection {
|
||||
|
||||
/**
|
||||
* Show a file selection dialog that allows the user to select an existing file.
|
||||
* This app returns the absolute file path as a result.
|
||||
*/
|
||||
LaunchId startForExistingFile();
|
||||
|
||||
/**
|
||||
* Show a file selection dialog that allows the user to select a new or existing file.
|
||||
* This app returns the absolute file path as a result.
|
||||
*/
|
||||
LaunchId startForExistingOrNewFile();
|
||||
|
||||
/**
|
||||
* @param bundle the result bundle of an app
|
||||
* @return the path from the bundle, or empty string if none is present
|
||||
*/
|
||||
std::string getResultPath(const Bundle& bundle);
|
||||
|
||||
} // namespace
|
||||
@@ -98,6 +98,8 @@ public:
|
||||
lv_dropdown_set_options(busDropdown, bus_options.c_str());
|
||||
lv_obj_align(busDropdown, LV_ALIGN_TOP_RIGHT, 0, 0);
|
||||
lv_obj_set_width(busDropdown, LV_PCT(50));
|
||||
lv_obj_set_style_border_color(busDropdown, lv_color_hex(0xFAFAFA), LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(busDropdown, 1, LV_PART_MAIN);
|
||||
int32_t bus_index = 0;
|
||||
preferences.optInt32("bus", bus_index);
|
||||
if (bus_index < uartNames.size()) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "Tactility/hal/gps/GpsDevice.h"
|
||||
#include "Tactility/hal/sdcard/SdCardDevice.h"
|
||||
#include "Tactility/hal/spi/Spi.h"
|
||||
#include "Tactility/hal/uart/Uart.h"
|
||||
|
||||
@@ -53,7 +53,7 @@ std::shared_ptr<SdCardDevice> _Nullable find(const std::string& path);
|
||||
* Always calls the function, but doesn't lock if the path is not an SD card path.
|
||||
*/
|
||||
template<typename ReturnType>
|
||||
inline ReturnType withSdCardLock(const std::string& path, std::function<ReturnType()> fn) {
|
||||
ReturnType withSdCardLock(const std::string& path, std::function<ReturnType()> fn) {
|
||||
auto sdcard = find(path);
|
||||
if (sdcard != nullptr) {
|
||||
auto scoped_lockable = sdcard->getLock().asScopedLock();
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
lv_color_t lv_color_foreground();
|
||||
|
||||
lv_color_t lv_color_background();
|
||||
|
||||
lv_color_t lv_color_background_darkest();
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "lvgl.h"
|
||||
#include <lvgl.h>
|
||||
|
||||
namespace tt::lvgl {
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#include "./Colors.h"
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "lvgl.h"
|
||||
#include <lvgl.h>
|
||||
|
||||
namespace tt::lvgl {
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "lvgl.h"
|
||||
#include <lvgl.h>
|
||||
|
||||
namespace tt::lvgl {
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "lvgl.h"
|
||||
#include "../app/AppContext.h"
|
||||
#include <lvgl.h>
|
||||
|
||||
namespace tt::lvgl {
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include <Tactility/Bundle.h>
|
||||
#include <Tactility/PubSub.h>
|
||||
#include <Tactility/service/ServiceManifest.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
@@ -30,7 +29,7 @@ struct LoaderEvent {
|
||||
* @param[in] id application name or id
|
||||
* @param[in] parameters optional parameters to pass onto the application
|
||||
*/
|
||||
void startApp(const std::string& id, std::shared_ptr<const Bundle> _Nullable parameters = nullptr);
|
||||
app::LaunchId startApp(const std::string& id, std::shared_ptr<const Bundle> _Nullable parameters = nullptr);
|
||||
|
||||
/** @brief Stop the currently showing app. Show the previous app if any app was still running. */
|
||||
void stopApp();
|
||||
|
||||
@@ -25,22 +25,21 @@ enum class State {
|
||||
*/
|
||||
class AppInstance : public AppContext {
|
||||
|
||||
private:
|
||||
|
||||
Mutex mutex = Mutex(Mutex::Type::Normal);
|
||||
const std::shared_ptr<AppManifest> manifest;
|
||||
State state = State::Initial;
|
||||
LaunchId launchId;
|
||||
Flags flags = { .showStatusbar = true };
|
||||
/** @brief Optional parameters to start the app with
|
||||
* When these are stored in the app struct, the struct takes ownership.
|
||||
* Do not mutate after app creation.
|
||||
*/
|
||||
std::shared_ptr<const tt::Bundle> _Nullable parameters;
|
||||
std::shared_ptr<const Bundle> _Nullable parameters;
|
||||
|
||||
std::shared_ptr<App> app;
|
||||
|
||||
static std::shared_ptr<app::App> createApp(
|
||||
const std::shared_ptr<app::AppManifest>& manifest
|
||||
static std::shared_ptr<App> createApp(
|
||||
const std::shared_ptr<AppManifest>& manifest
|
||||
) {
|
||||
if (manifest->location.isInternal()) {
|
||||
assert(manifest->createApp != nullptr);
|
||||
@@ -50,7 +49,7 @@ private:
|
||||
TT_LOG_W("", "Manifest specifies createApp, but this is not used with external apps");
|
||||
}
|
||||
#ifdef ESP_PLATFORM
|
||||
return app::createElfApp(manifest);
|
||||
return createElfApp(manifest);
|
||||
#else
|
||||
tt_crash("not supported");
|
||||
#endif
|
||||
@@ -61,18 +60,23 @@ private:
|
||||
|
||||
public:
|
||||
|
||||
explicit AppInstance(const std::shared_ptr<AppManifest>& manifest) :
|
||||
explicit AppInstance(const std::shared_ptr<AppManifest>& manifest, LaunchId launchId) :
|
||||
manifest(manifest),
|
||||
launchId(launchId),
|
||||
app(createApp(manifest))
|
||||
{}
|
||||
|
||||
AppInstance(const std::shared_ptr<AppManifest>& manifest, std::shared_ptr<const Bundle> parameters) :
|
||||
AppInstance(const std::shared_ptr<AppManifest>& manifest, LaunchId launchId, std::shared_ptr<const Bundle> parameters) :
|
||||
manifest(manifest),
|
||||
launchId(launchId),
|
||||
parameters(std::move(parameters)),
|
||||
app(createApp(manifest)) {}
|
||||
app(createApp(manifest))
|
||||
{}
|
||||
|
||||
~AppInstance() override = default;
|
||||
|
||||
LaunchId getLaunchId() const { return launchId; }
|
||||
|
||||
void setState(State state);
|
||||
State getState() const;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <vector>
|
||||
#include <dirent.h>
|
||||
|
||||
namespace tt::app::files {
|
||||
namespace tt::app::filebrowser {
|
||||
|
||||
class State {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace tt::app::filebrowser {
|
||||
|
||||
bool isSupportedExecutableFile(const std::string& filename);
|
||||
bool isSupportedImageFile(const std::string& filename);
|
||||
bool isSupportedTextFile(const std::string& filename);
|
||||
|
||||
} // namespace
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <lvgl.h>
|
||||
#include <memory>
|
||||
|
||||
namespace tt::app::files {
|
||||
namespace tt::app::filebrowser {
|
||||
|
||||
class View {
|
||||
std::shared_ptr<State> state;
|
||||
@@ -1,66 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <dirent.h>
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
#include <vector>
|
||||
|
||||
namespace tt::app::files {
|
||||
|
||||
/** File types for `dirent`'s `d_type`. */
|
||||
enum {
|
||||
TT_DT_UNKNOWN = 0,
|
||||
#define TT_DT_UNKNOWN TT_DT_UNKNOWN // Unknown type
|
||||
TT_DT_FIFO = 1,
|
||||
#define TT_DT_FIFO TT_DT_FIFO // Named pipe or FIFO
|
||||
TT_DT_CHR = 2,
|
||||
#define TT_DT_CHR TT_DT_CHR // Character device
|
||||
TT_DT_DIR = 4,
|
||||
#define TT_DT_DIR TT_DT_DIR // Directory
|
||||
TT_DT_BLK = 6,
|
||||
#define TT_DT_BLK TT_DT_BLK // Block device
|
||||
TT_DT_REG = 8,
|
||||
#define TT_DT_REG TT_DT_REG // Regular file
|
||||
TT_DT_LNK = 10,
|
||||
#define TT_DT_LNK TT_DT_LNK // Symbolic link
|
||||
TT_DT_SOCK = 12,
|
||||
#define TT_DT_SOCK TT_DT_SOCK // Local-domain socket
|
||||
TT_DT_WHT = 14
|
||||
#define TT_DT_WHT TT_DT_WHT // Whiteout inodes
|
||||
};
|
||||
|
||||
|
||||
std::string getChildPath(const std::string& basePath, const std::string& childPath);
|
||||
|
||||
typedef int (*ScandirFilter)(const struct dirent*);
|
||||
|
||||
typedef bool (*ScandirSort)(const struct dirent&, const struct dirent&);
|
||||
|
||||
bool dirent_sort_alpha_and_type(const struct dirent& left, const struct dirent& right);
|
||||
|
||||
int dirent_filter_dot_entries(const struct dirent* entry);
|
||||
|
||||
/**
|
||||
* A scandir()-like implementation that works on ESP32.
|
||||
* It does not return "." and ".." items but otherwise functions the same.
|
||||
* It returns an allocated output array with allocated dirent instances.
|
||||
* The caller is responsible for free-ing the memory of these.
|
||||
*
|
||||
* @param[in] path path the scan for files and directories
|
||||
* @param[out] outList a pointer to vector of dirent
|
||||
* @param[in] filter an optional filter to filter out specific items
|
||||
* @param[in] sort an optional sorting function
|
||||
* @return the amount of items that were stored in "output" or -1 when an error occurred
|
||||
*/
|
||||
int scandir(
|
||||
const std::string& path,
|
||||
std::vector<dirent>& outList,
|
||||
ScandirFilter _Nullable filter,
|
||||
ScandirSort _Nullable sort
|
||||
);
|
||||
|
||||
bool isSupportedExecutableFile(const std::string& filename);
|
||||
bool isSupportedImageFile(const std::string& filename);
|
||||
bool isSupportedTextFile(const std::string& filename);
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/Bundle.h>
|
||||
|
||||
namespace tt::app::fileselection {
|
||||
|
||||
enum class Mode {
|
||||
Existing = 0,
|
||||
ExistingOrNew = 1
|
||||
};
|
||||
|
||||
Mode getMode(const Bundle& bundle);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/Mutex.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <dirent.h>
|
||||
|
||||
namespace tt::app::fileselection {
|
||||
|
||||
class State {
|
||||
|
||||
Mutex mutex = Mutex(Mutex::Type::Recursive);
|
||||
std::vector<dirent> dir_entries;
|
||||
std::string current_path;
|
||||
std::string selected_child_entry;
|
||||
|
||||
public:
|
||||
|
||||
State();
|
||||
|
||||
void freeEntries() {
|
||||
dir_entries.clear();
|
||||
}
|
||||
|
||||
~State() {
|
||||
freeEntries();
|
||||
}
|
||||
|
||||
bool setEntriesForChildPath(const std::string& child_path);
|
||||
bool setEntriesForPath(const std::string& path);
|
||||
|
||||
template <std::invocable<const std::vector<dirent> &> Func>
|
||||
void withEntries(Func&& onEntries) const {
|
||||
mutex.withLock([&]() {
|
||||
std::invoke(std::forward<Func>(onEntries), dir_entries);
|
||||
});
|
||||
}
|
||||
|
||||
bool getDirent(uint32_t index, dirent& dirent);
|
||||
|
||||
void setSelectedChildEntry(const std::string& newFile) {
|
||||
selected_child_entry = newFile;
|
||||
}
|
||||
|
||||
std::string getSelectedChildEntry() const { return selected_child_entry; }
|
||||
std::string getCurrentPath() const { return current_path; }
|
||||
std::string getCurrentPathWithTrailingSlash() const {
|
||||
if (current_path.length() > 1) {
|
||||
return current_path + "/";
|
||||
} else {
|
||||
return current_path;
|
||||
}
|
||||
}
|
||||
|
||||
std::string getSelectedChildPath() const;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "./State.h"
|
||||
#include "./FileSelectionPrivate.h"
|
||||
|
||||
#include "Tactility/app/AppManifest.h"
|
||||
|
||||
#include <lvgl.h>
|
||||
#include <memory>
|
||||
|
||||
namespace tt::app::fileselection {
|
||||
|
||||
class View {
|
||||
std::shared_ptr<State> state;
|
||||
|
||||
lv_obj_t* dir_entry_list = nullptr;
|
||||
lv_obj_t* navigate_up_button = nullptr;
|
||||
lv_obj_t* path_textarea = nullptr;
|
||||
lv_obj_t* select_button = nullptr;
|
||||
std::function<void(std::string path)> on_file_selected;
|
||||
|
||||
void onTapFile(const std::string&path, const std::string&filename);
|
||||
static void onSelectButtonPressed(lv_event_t* event);
|
||||
static void onPathTextChanged(lv_event_t* event);
|
||||
void createDirEntryWidget(lv_obj_t* parent, dirent& dir_entry);
|
||||
|
||||
public:
|
||||
|
||||
explicit View(const std::shared_ptr<State>& state, std::function<void(const std::string& path)> onFileSelected) :
|
||||
state(state),
|
||||
on_file_selected(std::move(onFileSelected))
|
||||
{}
|
||||
|
||||
void init(lv_obj_t* parent, Mode mode);
|
||||
void update();
|
||||
|
||||
void onNavigateUpPressed();
|
||||
void onDirEntryPressed(uint32_t index);
|
||||
void onFileSelected(const std::string& path) const {
|
||||
on_file_selected(path);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,13 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <TactilityCore.h>
|
||||
#include <Mutex.h>
|
||||
#include <Thread.h>
|
||||
#include "lvgl.h"
|
||||
#include <Tactility/hal/i2c/I2c.h>
|
||||
#include "Timer.h"
|
||||
#include <memory>
|
||||
|
||||
namespace tt::app::i2cscanner {
|
||||
|
||||
void start();
|
||||
|
||||
@@ -33,10 +33,12 @@ namespace app {
|
||||
namespace addgps { extern const AppManifest manifest; }
|
||||
namespace alertdialog { extern const AppManifest manifest; }
|
||||
namespace applist { extern const AppManifest manifest; }
|
||||
namespace calculator { extern const AppManifest manifest; }
|
||||
namespace chat { extern const AppManifest manifest; }
|
||||
namespace boot { extern const AppManifest manifest; }
|
||||
namespace display { extern const AppManifest manifest; }
|
||||
namespace files { extern const AppManifest manifest; }
|
||||
namespace filebrowser { extern const AppManifest manifest; }
|
||||
namespace fileselection { extern const AppManifest manifest; }
|
||||
namespace gpio { extern const AppManifest manifest; }
|
||||
namespace gpssettings { extern const AppManifest manifest; }
|
||||
namespace i2cscanner { extern const AppManifest manifest; }
|
||||
@@ -45,6 +47,7 @@ namespace app {
|
||||
namespace inputdialog { extern const AppManifest manifest; }
|
||||
namespace launcher { extern const AppManifest manifest; }
|
||||
namespace log { extern const AppManifest manifest; }
|
||||
namespace notes { extern const AppManifest manifest; }
|
||||
namespace power { extern const AppManifest manifest; }
|
||||
namespace selectiondialog { extern const AppManifest manifest; }
|
||||
namespace serialconsole { extern const AppManifest manifest; }
|
||||
@@ -74,8 +77,10 @@ static void registerSystemApps() {
|
||||
addApp(app::addgps::manifest);
|
||||
addApp(app::alertdialog::manifest);
|
||||
addApp(app::applist::manifest);
|
||||
addApp(app::calculator::manifest);
|
||||
addApp(app::display::manifest);
|
||||
addApp(app::files::manifest);
|
||||
addApp(app::filebrowser::manifest);
|
||||
addApp(app::fileselection::manifest);
|
||||
addApp(app::gpio::manifest);
|
||||
addApp(app::gpssettings::manifest);
|
||||
addApp(app::i2cscanner::manifest);
|
||||
@@ -84,6 +89,7 @@ static void registerSystemApps() {
|
||||
addApp(app::inputdialog::manifest);
|
||||
addApp(app::launcher::manifest);
|
||||
addApp(app::log::manifest);
|
||||
addApp(app::notes::manifest);
|
||||
addApp(app::serialconsole::manifest);
|
||||
addApp(app::settings::manifest);
|
||||
addApp(app::selectiondialog::manifest);
|
||||
|
||||
@@ -4,19 +4,19 @@
|
||||
|
||||
namespace tt::app {
|
||||
|
||||
void start(const std::string& id, std::shared_ptr<const Bundle> _Nullable parameters) {
|
||||
service::loader::startApp(id, std::move(parameters));
|
||||
LaunchId start(const std::string& id, std::shared_ptr<const Bundle> _Nullable parameters) {
|
||||
return service::loader::startApp(id, std::move(parameters));
|
||||
}
|
||||
|
||||
void stop() {
|
||||
service::loader::stopApp();
|
||||
}
|
||||
|
||||
std::shared_ptr<app::AppContext> _Nullable getCurrentAppContext() {
|
||||
std::shared_ptr<AppContext> _Nullable getCurrentAppContext() {
|
||||
return service::loader::getCurrentAppContext();
|
||||
}
|
||||
|
||||
std::shared_ptr<app::App> _Nullable getCurrentApp() {
|
||||
std::shared_ptr<App> _Nullable getCurrentApp() {
|
||||
return service::loader::getCurrentApp();
|
||||
}
|
||||
|
||||
|
||||
@@ -36,8 +36,6 @@ static ElfManifest elfManifest;
|
||||
|
||||
class ElfApp : public App {
|
||||
|
||||
private:
|
||||
|
||||
const std::string filePath;
|
||||
std::unique_ptr<uint8_t[]> elfFileData;
|
||||
esp_elf_t elf;
|
||||
@@ -143,9 +141,9 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void onResult(AppContext& appContext, Result result, std::unique_ptr<Bundle> resultBundle) override {
|
||||
void onResult(AppContext& appContext, LaunchId launchId, Result result, std::unique_ptr<Bundle> resultBundle) override {
|
||||
if (manifest != nullptr && manifest->onResult != nullptr) {
|
||||
manifest->onResult(&appContext, data, result, resultBundle.get());
|
||||
manifest->onResult(&appContext, data, launchId, result, resultBundle.get());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -101,6 +101,8 @@ public:
|
||||
lv_dropdown_set_options(uartDropdown, uart_options.c_str());
|
||||
lv_obj_align(uartDropdown, LV_ALIGN_TOP_RIGHT, 0, 0);
|
||||
lv_obj_set_width(uartDropdown, LV_PCT(50));
|
||||
lv_obj_set_style_border_color(uartDropdown, lv_color_hex(0xFAFAFA), LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(uartDropdown, 1, LV_PART_MAIN);
|
||||
|
||||
auto* uart_label = lv_label_create(uart_wrapper);
|
||||
lv_obj_align(uart_label, LV_ALIGN_TOP_LEFT, 0, 10);
|
||||
@@ -121,6 +123,8 @@ public:
|
||||
lv_dropdown_set_options(modelDropdown, model_options.c_str());
|
||||
lv_obj_align(modelDropdown, LV_ALIGN_TOP_RIGHT, 0, 0);
|
||||
lv_obj_set_width(modelDropdown, LV_PCT(50));
|
||||
lv_obj_set_style_border_color(modelDropdown, lv_color_hex(0xFAFAFA), LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(modelDropdown, 1, LV_PART_MAIN);
|
||||
|
||||
auto* model_label = lv_label_create(model_wrapper);
|
||||
lv_obj_align(model_label, LV_ALIGN_TOP_LEFT, 0, 10);
|
||||
@@ -139,6 +143,8 @@ public:
|
||||
lv_dropdown_set_options(baudDropdown, baudRatesDropdownValues);
|
||||
lv_obj_align(baudDropdown, LV_ALIGN_TOP_RIGHT, 0, 0);
|
||||
lv_obj_set_width(baudDropdown, LV_PCT(50));
|
||||
lv_obj_set_style_border_color(baudDropdown, lv_color_hex(0xFAFAFA), LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(baudDropdown, 1, LV_PART_MAIN);
|
||||
|
||||
auto* baud_rate_label = lv_label_create(baud_wrapper);
|
||||
lv_obj_align(baud_rate_label, LV_ALIGN_TOP_LEFT, 0, 10);
|
||||
|
||||
@@ -43,16 +43,8 @@ public:
|
||||
auto manifests = getApps();
|
||||
std::sort(manifests.begin(), manifests.end(), SortAppManifestByName);
|
||||
|
||||
lv_list_add_text(list, "User");
|
||||
for (const auto& manifest: manifests) {
|
||||
if (manifest->type == Type::User) {
|
||||
createAppWidget(manifest, list);
|
||||
}
|
||||
}
|
||||
|
||||
lv_list_add_text(list, "System");
|
||||
for (const auto& manifest: manifests) {
|
||||
if (manifest->type == Type::System) {
|
||||
if (manifest->type == Type::User || manifest->type == Type::System) {
|
||||
createAppWidget(manifest, list);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ class BootApp : public App {
|
||||
|
||||
private:
|
||||
|
||||
Thread thread = Thread("boot", 4096, bootThreadCallback, this);
|
||||
Thread thread = Thread("boot", 4096, [this]() { return bootThreadCallback(); });
|
||||
|
||||
static int32_t bootThreadCallback(TT_UNUSED void* context) {
|
||||
int32_t bootThreadCallback() {
|
||||
TickType_t start_time = kernel::getTicks();
|
||||
|
||||
kernel::publishSystemEvent(kernel::SystemEvent::BootSplash);
|
||||
@@ -42,14 +42,22 @@ private:
|
||||
auto hal_display = getHalDisplay();
|
||||
assert(hal_display != nullptr);
|
||||
if (hal_display->supportsBacklightDuty()) {
|
||||
int32_t backlight_duty = app::display::getBacklightDuty();
|
||||
TT_LOG_I(TAG, "backlight %ld", backlight_duty);
|
||||
uint8_t backlight_duty = 200;
|
||||
app::display::getBacklightDuty(backlight_duty);
|
||||
TT_LOG_I(TAG, "backlight %du", backlight_duty);
|
||||
hal_display->setBacklightDuty(backlight_duty);
|
||||
} else {
|
||||
|
||||
TT_LOG_I(TAG, "no backlight");
|
||||
}
|
||||
|
||||
if (hal_display->getGammaCurveCount() > 0) {
|
||||
uint8_t gamma_curve;
|
||||
if (app::display::getGammaCurve(gamma_curve)) {
|
||||
hal_display->setGammaCurve(gamma_curve);
|
||||
TT_LOG_I(TAG, "gamma %du", gamma_curve);
|
||||
}
|
||||
}
|
||||
|
||||
if (hal::usb::isUsbBootMode()) {
|
||||
TT_LOG_I(TAG, "Rebooting into mass storage device mode");
|
||||
hal::usb::resetUsbBootMode();
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
#include <Tactility/app/AppManifest.h>
|
||||
#include <Tactility/lvgl/Toolbar.h>
|
||||
#include <Tactility/Assets.h>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <lvgl.h>
|
||||
#include <queue>
|
||||
#include <stack>
|
||||
|
||||
namespace tt::app::calculator {
|
||||
|
||||
constexpr const char* TAG = "Calculator";
|
||||
|
||||
class CalculatorApp : public App {
|
||||
|
||||
lv_obj_t* displayLabel;
|
||||
lv_obj_t* resultLabel;
|
||||
char formulaBuffer[128] = {0}; // Stores the full input expression
|
||||
bool newInput = true;
|
||||
|
||||
static void button_event_cb(lv_event_t* e) {
|
||||
CalculatorApp* self = static_cast<CalculatorApp*>(lv_event_get_user_data(e));
|
||||
lv_obj_t* btnm = lv_event_get_current_target_obj(e);
|
||||
lv_event_code_t code = lv_event_get_code(e);
|
||||
|
||||
uint32_t btn_id = lv_buttonmatrix_get_selected_button(btnm);
|
||||
const char* txt = lv_buttonmatrix_get_button_text(btnm, btn_id);
|
||||
|
||||
if (code == LV_EVENT_VALUE_CHANGED) {
|
||||
self->handleInput(txt);
|
||||
}
|
||||
}
|
||||
|
||||
void handleInput(const char* txt) {
|
||||
if (std::strcmp(txt, "C") == 0) {
|
||||
resetCalculator();
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::strcmp(txt, "=") == 0) {
|
||||
evaluateExpression();
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::strlen(formulaBuffer) + std::strlen(txt) < sizeof(formulaBuffer) - 1) {
|
||||
if (newInput) {
|
||||
std::memset(formulaBuffer, 0, sizeof(formulaBuffer));
|
||||
newInput = false;
|
||||
}
|
||||
std::strcat(formulaBuffer, txt);
|
||||
lv_label_set_text(displayLabel, formulaBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
void evaluateExpression() {
|
||||
double result = computeFormula();
|
||||
|
||||
size_t formulaLen = std::strlen(formulaBuffer);
|
||||
size_t maxAvailable = sizeof(formulaBuffer) - formulaLen - 1;
|
||||
|
||||
if (maxAvailable > 10) {
|
||||
char resultBuffer[32];
|
||||
snprintf(resultBuffer, sizeof(resultBuffer), " = %.8g", result);
|
||||
strncat(formulaBuffer, resultBuffer, maxAvailable);
|
||||
} else {
|
||||
snprintf(formulaBuffer, sizeof(formulaBuffer), "%.8g", result);
|
||||
}
|
||||
|
||||
lv_label_set_text(displayLabel, "0");
|
||||
lv_label_set_text(resultLabel, formulaBuffer);
|
||||
newInput = true;
|
||||
}
|
||||
|
||||
double computeFormula() {
|
||||
return evaluateRPN(infixToRPN(std::string(formulaBuffer)));
|
||||
}
|
||||
|
||||
int precedence(char op) {
|
||||
if (op == '+' || op == '-') return 1;
|
||||
if (op == '*' || op == '/') return 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::queue<std::string> infixToRPN(const std::string& infix) {
|
||||
std::stack<char> opStack;
|
||||
std::queue<std::string> output;
|
||||
std::string token;
|
||||
size_t i = 0;
|
||||
|
||||
while (i < infix.size()) {
|
||||
char ch = infix[i];
|
||||
|
||||
if (isdigit(ch)) {
|
||||
token.clear();
|
||||
while (i < infix.size() && (isdigit(infix[i]) || infix[i] == '.')) {
|
||||
token += infix[i++];
|
||||
}
|
||||
output.push(token);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch == '(') {
|
||||
opStack.push(ch);
|
||||
} else if (ch == ')') {
|
||||
while (!opStack.empty() && opStack.top() != '(') {
|
||||
output.push(std::string(1, opStack.top()));
|
||||
opStack.pop();
|
||||
}
|
||||
opStack.pop();
|
||||
} else if (strchr("+-*/", ch)) {
|
||||
while (!opStack.empty() && precedence(opStack.top()) >= precedence(ch)) {
|
||||
output.push(std::string(1, opStack.top()));
|
||||
opStack.pop();
|
||||
}
|
||||
opStack.push(ch);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
while (!opStack.empty()) {
|
||||
output.push(std::string(1, opStack.top()));
|
||||
opStack.pop();
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
double evaluateRPN(std::queue<std::string> rpnQueue) {
|
||||
std::stack<double> values;
|
||||
|
||||
while (!rpnQueue.empty()) {
|
||||
std::string token = rpnQueue.front();
|
||||
rpnQueue.pop();
|
||||
|
||||
if (isdigit(token[0])) {
|
||||
values.push(std::stod(token));
|
||||
} else if (strchr("+-*/", token[0])) {
|
||||
if (values.size() < 2) return 0;
|
||||
|
||||
double b = values.top();
|
||||
values.pop();
|
||||
double a = values.top();
|
||||
values.pop();
|
||||
|
||||
if (token[0] == '+') values.push(a + b);
|
||||
else if (token[0] == '-')
|
||||
values.push(a - b);
|
||||
else if (token[0] == '*')
|
||||
values.push(a * b);
|
||||
else if (token[0] == '/' && b != 0)
|
||||
values.push(a / b);
|
||||
}
|
||||
}
|
||||
|
||||
return values.empty() ? 0 : values.top();
|
||||
}
|
||||
|
||||
void resetCalculator() {
|
||||
std::memset(formulaBuffer, 0, sizeof(formulaBuffer));
|
||||
lv_label_set_text(displayLabel, "0");
|
||||
lv_label_set_text(resultLabel, "");
|
||||
newInput = true;
|
||||
}
|
||||
|
||||
void onShow(AppContext& context, lv_obj_t* parent) override {
|
||||
lv_obj_remove_flag(parent, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
|
||||
lv_obj_t* toolbar = tt::lvgl::toolbar_create(parent, context);
|
||||
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
|
||||
|
||||
lv_obj_t* wrapper = lv_obj_create(parent);
|
||||
lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(wrapper, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START);
|
||||
lv_obj_set_width(wrapper, LV_PCT(100));
|
||||
lv_obj_set_height(wrapper, LV_SIZE_CONTENT);
|
||||
lv_obj_set_flex_grow(wrapper, 0);
|
||||
lv_obj_set_style_pad_top(wrapper, 4, LV_PART_MAIN);
|
||||
lv_obj_set_style_pad_bottom(wrapper, 4, LV_PART_MAIN);
|
||||
lv_obj_set_style_pad_left(wrapper, 10, LV_PART_MAIN);
|
||||
lv_obj_set_style_pad_right(wrapper, 10, LV_PART_MAIN);
|
||||
lv_obj_set_style_pad_column(wrapper, 40, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(wrapper, 0, 0);
|
||||
lv_obj_remove_flag(wrapper, LV_OBJ_FLAG_SCROLLABLE);
|
||||
|
||||
displayLabel = lv_label_create(wrapper);
|
||||
lv_label_set_text(displayLabel, "0");
|
||||
lv_obj_set_width(displayLabel, LV_SIZE_CONTENT);
|
||||
lv_obj_set_align(displayLabel, LV_ALIGN_LEFT_MID);
|
||||
|
||||
resultLabel = lv_label_create(wrapper);
|
||||
lv_label_set_text(resultLabel, "");
|
||||
lv_obj_set_width(resultLabel, LV_SIZE_CONTENT);
|
||||
lv_obj_set_align(resultLabel, LV_ALIGN_RIGHT_MID);
|
||||
|
||||
static const char* btn_map[] = {
|
||||
"(", ")", "C", "/", "\n",
|
||||
"7", "8", "9", "*", "\n",
|
||||
"4", "5", "6", "-", "\n",
|
||||
"1", "2", "3", "+", "\n",
|
||||
"0", "=", "", "", ""
|
||||
};
|
||||
|
||||
lv_obj_t* btnm = lv_btnmatrix_create(parent);
|
||||
lv_btnmatrix_set_map(btnm, btn_map);
|
||||
|
||||
lv_obj_set_style_pad_all(btnm, 5, LV_PART_MAIN);
|
||||
lv_obj_set_style_pad_row(btnm, 10, LV_PART_MAIN);
|
||||
lv_obj_set_style_pad_column(btnm, 5, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(btnm, 0, LV_PART_MAIN);
|
||||
lv_obj_set_style_bg_color(btnm, lv_palette_main(LV_PALETTE_BLUE), LV_PART_ITEMS);
|
||||
|
||||
lv_obj_set_style_border_width(btnm, 0, LV_PART_MAIN);
|
||||
if (lv_display_get_horizontal_resolution(nullptr) <= 240 || lv_display_get_vertical_resolution(nullptr) <= 240) { //small screens
|
||||
lv_obj_set_size(btnm, lv_pct(100), lv_pct(60));
|
||||
} else { //large screens
|
||||
lv_obj_set_size(btnm, lv_pct(100), lv_pct(80));
|
||||
}
|
||||
lv_obj_align(btnm, LV_ALIGN_BOTTOM_MID, 0, -5);
|
||||
|
||||
lv_obj_add_event_cb(btnm, button_event_cb, LV_EVENT_VALUE_CHANGED, this);
|
||||
}
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "Calculator",
|
||||
.name = "Calculator",
|
||||
.icon = TT_ASSETS_APP_ICON_CALCULATOR,
|
||||
.createApp = create<CalculatorApp>
|
||||
};
|
||||
|
||||
} // namespace tt::app::calculator
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <Tactility/app/AppManifest.h>
|
||||
#include <Tactility/lvgl/Toolbar.h>
|
||||
#include <Tactility/Assets.h>
|
||||
#include <Tactility/service/espnow/EspNow.h>
|
||||
|
||||
#include "Tactility/service/gui/Gui.h"
|
||||
@@ -115,7 +116,7 @@ public:
|
||||
msg_list = lv_list_create(parent);
|
||||
lv_obj_set_size(msg_list, lv_pct(75), lv_pct(43));
|
||||
lv_obj_align(msg_list, LV_ALIGN_TOP_LEFT, 5, toolbar_height + 45);
|
||||
lv_obj_set_style_bg_color(msg_list, lv_color_hex(0xEEEEEE), 0);
|
||||
lv_obj_set_style_bg_color(msg_list, lv_color_hex(0x262626), 0);
|
||||
lv_obj_set_style_border_width(msg_list, 1, 0);
|
||||
lv_obj_set_style_pad_all(msg_list, 5, 0);
|
||||
|
||||
@@ -171,6 +172,7 @@ public:
|
||||
extern const AppManifest manifest = {
|
||||
.id = "Chat",
|
||||
.name = "Chat",
|
||||
.icon = TT_ASSETS_APP_ICON_CHAT,
|
||||
.createApp = create<ChatApp>
|
||||
};
|
||||
|
||||
|
||||
@@ -22,79 +22,85 @@ static uint8_t gamma = 255;
|
||||
#define ROTATION_270 2
|
||||
#define ROTATION_90 3
|
||||
|
||||
static std::shared_ptr<tt::hal::display::DisplayDevice> getHalDisplay() {
|
||||
static std::shared_ptr<hal::display::DisplayDevice> getHalDisplay() {
|
||||
return hal::findFirstDevice<hal::display::DisplayDevice>(hal::Device::Type::Display);
|
||||
}
|
||||
|
||||
static void onBacklightSliderEvent(lv_event_t* event) {
|
||||
auto* slider = static_cast<lv_obj_t*>(lv_event_get_target(event));
|
||||
|
||||
auto hal_display = getHalDisplay();
|
||||
assert(hal_display != nullptr);
|
||||
|
||||
if (hal_display->supportsBacklightDuty()) {
|
||||
int32_t slider_value = lv_slider_get_value(slider);
|
||||
|
||||
backlight_duty = (uint8_t)slider_value;
|
||||
backlight_duty_set = true;
|
||||
|
||||
hal_display->setBacklightDuty(backlight_duty);
|
||||
}
|
||||
}
|
||||
|
||||
static void onGammaSliderEvent(lv_event_t* event) {
|
||||
auto* slider = static_cast<lv_obj_t*>(lv_event_get_target(event));
|
||||
auto* lvgl_display = lv_display_get_default();
|
||||
assert(lvgl_display != nullptr);
|
||||
auto* hal_display = (tt::hal::display::DisplayDevice*)lv_display_get_user_data(lvgl_display);
|
||||
assert(hal_display != nullptr);
|
||||
|
||||
if (hal_display->getGammaCurveCount() > 0) {
|
||||
int32_t slider_value = lv_slider_get_value(slider);
|
||||
|
||||
gamma = (uint8_t)slider_value;
|
||||
|
||||
hal_display->setGammaCurve(gamma);
|
||||
}
|
||||
}
|
||||
|
||||
static lv_display_rotation_t orientationSettingToDisplayRotation(uint32_t setting) {
|
||||
if (setting == ROTATION_180) {
|
||||
return LV_DISPLAY_ROTATION_180;
|
||||
} else if (setting == ROTATION_270) {
|
||||
return LV_DISPLAY_ROTATION_270;
|
||||
} else if (setting == ROTATION_90) {
|
||||
return LV_DISPLAY_ROTATION_90;
|
||||
} else {
|
||||
return LV_DISPLAY_ROTATION_0;
|
||||
switch (setting) {
|
||||
case ROTATION_180:
|
||||
return LV_DISPLAY_ROTATION_180;
|
||||
case ROTATION_270:
|
||||
return LV_DISPLAY_ROTATION_270;
|
||||
case ROTATION_90:
|
||||
return LV_DISPLAY_ROTATION_90;
|
||||
default:
|
||||
return LV_DISPLAY_ROTATION_0;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t dipslayOrientationToOrientationSetting(lv_display_rotation_t orientation) {
|
||||
if (orientation == LV_DISPLAY_ROTATION_90) {
|
||||
return ROTATION_90;
|
||||
} else if (orientation == LV_DISPLAY_ROTATION_180) {
|
||||
return ROTATION_180;
|
||||
} else if (orientation == LV_DISPLAY_ROTATION_270) {
|
||||
return ROTATION_270;
|
||||
} else {
|
||||
return ROTATION_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
static void onOrientationSet(lv_event_t* event) {
|
||||
auto* dropdown = static_cast<lv_obj_t*>(lv_event_get_target(event));
|
||||
uint32_t selected = lv_dropdown_get_selected(dropdown);
|
||||
TT_LOG_I(TAG, "Selected %ld", selected);
|
||||
lv_display_rotation_t rotation = orientationSettingToDisplayRotation(selected);
|
||||
if (lv_display_get_rotation(lv_display_get_default()) != rotation) {
|
||||
lv_display_set_rotation(lv_display_get_default(), rotation);
|
||||
setRotation(rotation);
|
||||
static uint32_t displayOrientationToOrientationSetting(lv_display_rotation_t orientation) {
|
||||
switch (orientation) {
|
||||
case LV_DISPLAY_ROTATION_90:
|
||||
return ROTATION_90;
|
||||
case LV_DISPLAY_ROTATION_180:
|
||||
return ROTATION_180;
|
||||
case LV_DISPLAY_ROTATION_270:
|
||||
return ROTATION_270;
|
||||
default:
|
||||
return ROTATION_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
class DisplayApp : public App {
|
||||
|
||||
static void onBacklightSliderEvent(lv_event_t* event) {
|
||||
auto* slider = static_cast<lv_obj_t*>(lv_event_get_target(event));
|
||||
|
||||
auto hal_display = getHalDisplay();
|
||||
assert(hal_display != nullptr);
|
||||
|
||||
if (hal_display->supportsBacklightDuty()) {
|
||||
int32_t slider_value = lv_slider_get_value(slider);
|
||||
|
||||
backlight_duty = static_cast<uint8_t>(slider_value);
|
||||
backlight_duty_set = true;
|
||||
|
||||
hal_display->setBacklightDuty(backlight_duty);
|
||||
}
|
||||
}
|
||||
|
||||
static void onGammaSliderEvent(lv_event_t* event) {
|
||||
auto* slider = static_cast<lv_obj_t*>(lv_event_get_target(event));
|
||||
auto* lvgl_display = lv_display_get_default();
|
||||
assert(lvgl_display != nullptr);
|
||||
auto* hal_display = static_cast<hal::display::DisplayDevice*>(lv_display_get_user_data(lvgl_display));
|
||||
assert(hal_display != nullptr);
|
||||
|
||||
if (hal_display->getGammaCurveCount() > 0) {
|
||||
int32_t slider_value = lv_slider_get_value(slider);
|
||||
|
||||
gamma = static_cast<uint8_t>(slider_value);
|
||||
|
||||
hal_display->setGammaCurve(gamma);
|
||||
setGammaCurve(gamma);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void onOrientationSet(lv_event_t* event) {
|
||||
auto* dropdown = static_cast<lv_obj_t*>(lv_event_get_target(event));
|
||||
uint32_t selected = lv_dropdown_get_selected(dropdown);
|
||||
TT_LOG_I(TAG, "Selected %ld", selected);
|
||||
lv_display_rotation_t rotation = orientationSettingToDisplayRotation(selected);
|
||||
if (lv_display_get_rotation(lv_display_get_default()) != rotation) {
|
||||
lv_display_set_rotation(lv_display_get_default(), rotation);
|
||||
setRotation(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
void onShow(AppContext& app, lv_obj_t* parent) override {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
|
||||
@@ -108,59 +114,81 @@ class DisplayApp : public App {
|
||||
lv_obj_set_width(main_wrapper, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(main_wrapper, 1);
|
||||
|
||||
auto* wrapper = lv_obj_create(main_wrapper);
|
||||
lv_obj_set_width(wrapper, LV_PCT(100));
|
||||
lv_obj_set_style_pad_all(wrapper, 8, 0);
|
||||
lv_obj_set_style_border_width(wrapper, 0, 0);
|
||||
if (hal_display->supportsBacklightDuty()) {
|
||||
auto* brightness_wrapper = lv_obj_create(main_wrapper);
|
||||
lv_obj_set_size(brightness_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
|
||||
lv_obj_set_style_pad_hor(brightness_wrapper, 0, 0);
|
||||
lv_obj_set_style_pad_ver(brightness_wrapper, 6, 0);
|
||||
lv_obj_set_style_border_width(brightness_wrapper, 0, 0);
|
||||
|
||||
auto* brightness_label = lv_label_create(wrapper);
|
||||
lv_label_set_text(brightness_label, "Brightness");
|
||||
auto* brightness_label = lv_label_create(brightness_wrapper);
|
||||
lv_label_set_text(brightness_label, "Brightness");
|
||||
|
||||
auto* brightness_slider = lv_slider_create(wrapper);
|
||||
lv_obj_set_width(brightness_slider, LV_PCT(50));
|
||||
lv_obj_align(brightness_slider, LV_ALIGN_TOP_RIGHT, -8, 0);
|
||||
lv_slider_set_range(brightness_slider, 0, 255);
|
||||
lv_obj_add_event_cb(brightness_slider, onBacklightSliderEvent, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
auto* brightness_slider = lv_slider_create(brightness_wrapper);
|
||||
lv_obj_set_width(brightness_slider, LV_PCT(50));
|
||||
lv_obj_align(brightness_slider, LV_ALIGN_TOP_RIGHT, -8, 0);
|
||||
lv_slider_set_range(brightness_slider, 0, 255);
|
||||
lv_obj_add_event_cb(brightness_slider, onBacklightSliderEvent, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
|
||||
auto* gamma_label = lv_label_create(wrapper);
|
||||
lv_label_set_text(gamma_label, "Gamma");
|
||||
lv_obj_set_y(gamma_label, 40);
|
||||
|
||||
auto* gamma_slider = lv_slider_create(wrapper);
|
||||
lv_obj_set_width(gamma_slider, LV_PCT(50));
|
||||
lv_obj_align(gamma_slider, LV_ALIGN_TOP_RIGHT, -8, 40);
|
||||
lv_slider_set_range(gamma_slider, 0, hal_display->getGammaCurveCount());
|
||||
lv_obj_add_event_cb(gamma_slider, onGammaSliderEvent, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
|
||||
if (!hal_display->supportsBacklightDuty()) {
|
||||
lv_slider_set_value(brightness_slider, 255, LV_ANIM_OFF);
|
||||
lv_obj_add_state(brightness_slider, LV_STATE_DISABLED);
|
||||
} else {
|
||||
uint8_t value = getBacklightDuty();
|
||||
lv_slider_set_value(brightness_slider, value, LV_ANIM_OFF);
|
||||
uint8_t value;
|
||||
if (getBacklightDuty(value)) {
|
||||
lv_slider_set_value(brightness_slider, value, LV_ANIM_OFF);
|
||||
} else {
|
||||
lv_slider_set_value(brightness_slider, 0, LV_ANIM_OFF);
|
||||
}
|
||||
}
|
||||
|
||||
lv_slider_set_value(gamma_slider, 128, LV_ANIM_OFF);
|
||||
if (hal_display->getGammaCurveCount() > 0) {
|
||||
auto* gamma_wrapper = lv_obj_create(main_wrapper);
|
||||
lv_obj_set_size(gamma_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
|
||||
lv_obj_set_style_pad_hor(gamma_wrapper, 0, 0);
|
||||
lv_obj_set_style_pad_ver(gamma_wrapper, 6, 0);
|
||||
lv_obj_set_style_border_width(gamma_wrapper, 0, 0);
|
||||
|
||||
auto* orientation_label = lv_label_create(wrapper);
|
||||
auto* gamma_label = lv_label_create(gamma_wrapper);
|
||||
lv_label_set_text(gamma_label, "Gamma");
|
||||
lv_obj_set_y(gamma_label, 0);
|
||||
|
||||
auto* gamma_slider = lv_slider_create(gamma_wrapper);
|
||||
lv_obj_set_width(gamma_slider, LV_PCT(50));
|
||||
lv_obj_align(gamma_slider, LV_ALIGN_TOP_RIGHT, -8, 0);
|
||||
lv_slider_set_range(gamma_slider, 0, hal_display->getGammaCurveCount());
|
||||
lv_obj_add_event_cb(gamma_slider, onGammaSliderEvent, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
|
||||
uint8_t curve_index;
|
||||
if (getGammaCurve(curve_index)) {
|
||||
lv_slider_set_value(gamma_slider, curve_index, LV_ANIM_OFF);
|
||||
} else {
|
||||
lv_slider_set_value(gamma_slider, 0, LV_ANIM_OFF);
|
||||
}
|
||||
}
|
||||
|
||||
auto* orientation_wrapper = lv_obj_create(main_wrapper);
|
||||
lv_obj_set_size(orientation_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
|
||||
lv_obj_set_style_pad_all(orientation_wrapper, 0, 0);
|
||||
lv_obj_set_style_border_width(orientation_wrapper, 0, 0);
|
||||
|
||||
auto* orientation_label = lv_label_create(orientation_wrapper);
|
||||
lv_label_set_text(orientation_label, "Orientation");
|
||||
lv_obj_align(orientation_label, LV_ALIGN_TOP_LEFT, 0, 80);
|
||||
lv_obj_align(orientation_label, LV_ALIGN_TOP_LEFT, 0, 8);
|
||||
|
||||
auto lvgl_display = lv_obj_get_display(parent);
|
||||
auto horizontal_px = lv_display_get_horizontal_resolution(lvgl_display);
|
||||
auto vertical_px = lv_display_get_vertical_resolution(lvgl_display);
|
||||
bool is_landscape_display = horizontal_px > vertical_px;
|
||||
|
||||
auto* orientation_dropdown = lv_dropdown_create(wrapper);
|
||||
auto* orientation_dropdown = lv_dropdown_create(orientation_wrapper);
|
||||
if (is_landscape_display) {
|
||||
lv_dropdown_set_options(orientation_dropdown, "Landscape\nLandscape (flipped)\nPortrait Left\nPortrait Right");
|
||||
} else {
|
||||
lv_dropdown_set_options(orientation_dropdown, "Portrait\nPortrait (flipped)\nLandscape Left\nLandscape Right");
|
||||
}
|
||||
|
||||
lv_obj_align(orientation_dropdown, LV_ALIGN_TOP_RIGHT, 0, 72);
|
||||
lv_obj_align(orientation_dropdown, LV_ALIGN_TOP_RIGHT, 0, 0);
|
||||
lv_obj_set_style_border_color(orientation_dropdown, lv_color_hex(0xFAFAFA), LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(orientation_dropdown, 1, LV_PART_MAIN);
|
||||
lv_obj_add_event_cb(orientation_dropdown, onOrientationSet, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
uint32_t orientation_selected = dipslayOrientationToOrientationSetting(
|
||||
uint32_t orientation_selected = displayOrientationToOrientationSetting(
|
||||
lv_display_get_rotation(lv_display_get_default())
|
||||
);
|
||||
lv_dropdown_set_selected(orientation_dropdown, orientation_selected);
|
||||
|
||||
@@ -6,19 +6,21 @@ namespace tt::app::display {
|
||||
|
||||
tt::Preferences preferences("display");
|
||||
|
||||
#define BACKLIGHT_DUTY_KEY "backlight_duty"
|
||||
#define ROTATION_KEY "rotation"
|
||||
constexpr const char* BACKLIGHT_DUTY_KEY = "backlight_duty";
|
||||
constexpr const char* GAMMA_CURVE_KEY = "gamma";
|
||||
constexpr const char* ROTATION_KEY = "rotation";
|
||||
|
||||
void setBacklightDuty(uint8_t value) {
|
||||
preferences.putInt32(BACKLIGHT_DUTY_KEY, (int32_t)value);
|
||||
}
|
||||
|
||||
uint8_t getBacklightDuty() {
|
||||
bool getBacklightDuty(uint8_t& duty) {
|
||||
int32_t result;
|
||||
if (preferences.optInt32(BACKLIGHT_DUTY_KEY, result)) {
|
||||
return (uint8_t)(result % 256);
|
||||
duty = (uint8_t)(result % 256);
|
||||
return true;
|
||||
} else {
|
||||
return 200;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,4 +37,18 @@ lv_display_rotation_t getRotation() {
|
||||
}
|
||||
}
|
||||
|
||||
void setGammaCurve(uint8_t curveIndex) {
|
||||
preferences.putInt32(GAMMA_CURVE_KEY, (int32_t)curveIndex);
|
||||
}
|
||||
|
||||
bool getGammaCurve(uint8_t& curveIndex) {
|
||||
int32_t result;
|
||||
if (preferences.optInt32(GAMMA_CURVE_KEY, result)) {
|
||||
curveIndex = (uint8_t)(result % 256);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "Tactility/app/files/View.h"
|
||||
#include "Tactility/app/files/State.h"
|
||||
#include "Tactility/app/filebrowser/View.h"
|
||||
#include "Tactility/app/filebrowser/State.h"
|
||||
#include "Tactility/app/AppContext.h"
|
||||
|
||||
#include <Tactility/Assets.h>
|
||||
@@ -7,18 +7,18 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace tt::app::files {
|
||||
namespace tt::app::filebrowser {
|
||||
|
||||
#define TAG "files_app"
|
||||
#define TAG "filebrowser_app"
|
||||
|
||||
extern const AppManifest manifest;
|
||||
|
||||
class FilesApp : public App {
|
||||
class FileBrowser : public App {
|
||||
std::unique_ptr<View> view;
|
||||
std::shared_ptr<State> state;
|
||||
|
||||
public:
|
||||
FilesApp() {
|
||||
FileBrowser() {
|
||||
state = std::make_shared<State>();
|
||||
view = std::make_unique<View>(state);
|
||||
}
|
||||
@@ -27,7 +27,7 @@ public:
|
||||
view->init(parent);
|
||||
}
|
||||
|
||||
void onResult(AppContext& appContext, Result result, std::unique_ptr<Bundle> bundle) override {
|
||||
void onResult(AppContext& appContext, TT_UNUSED LaunchId launchId, Result result, std::unique_ptr<Bundle> bundle) override {
|
||||
view->onResult(result, std::move(bundle));
|
||||
}
|
||||
};
|
||||
@@ -37,7 +37,7 @@ extern const AppManifest manifest = {
|
||||
.name = "Files",
|
||||
.icon = TT_ASSETS_APP_ICON_FILES,
|
||||
.type = Type::Hidden,
|
||||
.createApp = create<FilesApp>
|
||||
.createApp = create<FileBrowser>
|
||||
};
|
||||
|
||||
void start() {
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "Tactility/app/files/State.h"
|
||||
#include "Tactility/app/files/FileUtils.h"
|
||||
#include "Tactility/app/filebrowser/State.h"
|
||||
|
||||
#include <Tactility/file/File.h>
|
||||
#include "Tactility/hal/sdcard/SdCardDevice.h"
|
||||
#include <Tactility/Log.h>
|
||||
#include <Tactility/Partitions.h>
|
||||
@@ -11,9 +11,9 @@
|
||||
#include <vector>
|
||||
#include <dirent.h>
|
||||
|
||||
#define TAG "files_app"
|
||||
#define TAG "filebrowser_app"
|
||||
|
||||
namespace tt::app::files {
|
||||
namespace tt::app::filebrowser {
|
||||
|
||||
State::State() {
|
||||
if (kernel::getPlatform() == kernel::PlatformSimulator) {
|
||||
@@ -30,7 +30,7 @@ State::State() {
|
||||
}
|
||||
|
||||
std::string State::getSelectedChildPath() const {
|
||||
return getChildPath(current_path, selected_child_entry);
|
||||
return file::getChildPath(current_path, selected_child_entry);
|
||||
}
|
||||
|
||||
bool State::setEntriesForPath(const std::string& path) {
|
||||
@@ -52,12 +52,12 @@ bool State::setEntriesForPath(const std::string& path) {
|
||||
dir_entries.clear();
|
||||
dir_entries.push_back(dirent{
|
||||
.d_ino = 0,
|
||||
.d_type = TT_DT_DIR,
|
||||
.d_type = file::TT_DT_DIR,
|
||||
.d_name = SYSTEM_PARTITION_NAME
|
||||
});
|
||||
dir_entries.push_back(dirent{
|
||||
.d_ino = 1,
|
||||
.d_type = TT_DT_DIR,
|
||||
.d_type = file::TT_DT_DIR,
|
||||
.d_name = DATA_PARTITION_NAME
|
||||
});
|
||||
|
||||
@@ -68,7 +68,7 @@ bool State::setEntriesForPath(const std::string& path) {
|
||||
auto mount_name = sdcard->getMountPath().substr(1);
|
||||
auto dir_entry = dirent {
|
||||
.d_ino = 2,
|
||||
.d_type = TT_DT_DIR,
|
||||
.d_type = file::TT_DT_DIR,
|
||||
.d_name = { 0 }
|
||||
};
|
||||
assert(mount_name.length() < sizeof(dirent::d_name));
|
||||
@@ -83,7 +83,7 @@ bool State::setEntriesForPath(const std::string& path) {
|
||||
return true;
|
||||
} else {
|
||||
dir_entries.clear();
|
||||
int count = tt::app::files::scandir(path, dir_entries, &dirent_filter_dot_entries, dirent_sort_alpha_and_type);
|
||||
int count = file::scandir(path, dir_entries, &file::direntFilterDotEntries, file::direntSortAlphaAndType);
|
||||
if (count >= 0) {
|
||||
TT_LOG_I(TAG, "%s has %u entries", path.c_str(), count);
|
||||
current_path = path;
|
||||
@@ -97,8 +97,8 @@ bool State::setEntriesForPath(const std::string& path) {
|
||||
}
|
||||
}
|
||||
|
||||
bool State::setEntriesForChildPath(const std::string& child_path) {
|
||||
auto path = getChildPath(current_path, child_path);
|
||||
bool State::setEntriesForChildPath(const std::string& childPath) {
|
||||
auto path = file::getChildPath(current_path, childPath);
|
||||
TT_LOG_I(TAG, "Navigating from %s to %s", current_path.c_str(), path.c_str());
|
||||
return setEntriesForPath(path);
|
||||
}
|
||||