Compare commits

...

4 Commits

Author SHA1 Message Date
Ken Van Hoeylandt
09f8031bff
Fix for open Wi-Fi (#387)
Don't encrypt password if there's no password set
2025-10-25 18:57:43 +02:00
Giasone
1450ca319d
Add CYD-2432S028RV3 board support (#385) 2025-10-25 18:16:55 +02:00
Ken Van Hoeylandt
c139300a58
Thread+locking improvements and more (#386) 2025-10-25 18:08:46 +02:00
Ken Van Hoeylandt
d0d05c67ca
PR review fixes (#384) 2025-10-25 13:47:43 +02:00
33 changed files with 464 additions and 89 deletions

View File

@ -27,6 +27,15 @@ jobs:
with:
board_id: cyd-2432s028r
arch: esp32
cyd-2432s028rv3:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: "Build"
uses: ./.github/actions/build-firmware
with:
board_id: cyd-2432s028rv3
arch: esp32
cyd-e32r28t:
runs-on: ubuntu-latest
steps:

View File

@ -0,0 +1,7 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port ST7789 XPT2046SoftSPI PwmBacklight driver vfs fatfs
)

View File

@ -0,0 +1,134 @@
#include "CYD2432S028RV3.h"
#include "devices/Display.h"
#include "devices/SdCard.h"
#include <Tactility/lvgl/LvglSync.h>
#include <PwmBacklight.h>
#include <Tactility/hal/Configuration.h>
// SPI Transfer
#define CYD_SPI_TRANSFER_SIZE_LIMIT (CYD2432S028RV3_LCD_DRAW_BUFFER_SIZE * LV_COLOR_DEPTH / 8)
// Display backlight (PWM)
#define CYD2432S028RV3_LCD_PIN_BACKLIGHT GPIO_NUM_21
using namespace tt::hal;
static bool initBoot() {
//Set the RGB Led Pins to output and turn them off
ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT)); //Red
ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_16, GPIO_MODE_OUTPUT)); //Green
ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT)); //Blue
//0 on, 1 off... yep it's backwards.
ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_4, 1)); //Red
ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_16, 1)); //Green
ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_17, 1)); //Blue
return driver::pwmbacklight::init(CYD2432S028RV3_LCD_PIN_BACKLIGHT);
}
static DeviceVector createDevices() {
return {
createDisplay(),
createSdCard()
};
}
const Configuration cyd_2432s028rv3_config = {
.initBoot = initBoot,
.createDevices = createDevices,
.i2c = {
i2c::Configuration {
.name = "CN1",
.port = I2C_NUM_0,
.initMode = i2c::InitMode::ByTactility,
.isMutable = true,
.config = (i2c_config_t) {
.mode = I2C_MODE_MASTER,
.sda_io_num = GPIO_NUM_27,
.scl_io_num = GPIO_NUM_22,
.sda_pullup_en = false,
.scl_pullup_en = false,
.master = {
.clk_speed = 400000
},
.clk_flags = 0
}
}
},
.spi {
// Display
spi::Configuration {
.device = SPI2_HOST,
.dma = SPI_DMA_CH_AUTO,
.config = {
.mosi_io_num = GPIO_NUM_13,
.miso_io_num = GPIO_NUM_12,
.sclk_io_num = GPIO_NUM_14,
.quadwp_io_num = GPIO_NUM_NC,
.quadhd_io_num = GPIO_NUM_NC,
.data4_io_num = GPIO_NUM_NC,
.data5_io_num = GPIO_NUM_NC,
.data6_io_num = GPIO_NUM_NC,
.data7_io_num = GPIO_NUM_NC,
.data_io_default_level = false,
.max_transfer_sz = CYD_SPI_TRANSFER_SIZE_LIMIT,
.flags = 0,
.isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO,
.intr_flags = 0
},
.initMode = spi::InitMode::ByTactility,
.isMutable = false,
.lock = tt::lvgl::getSyncLock()
},
// SDCard
spi::Configuration {
.device = SPI3_HOST,
.dma = SPI_DMA_CH_AUTO,
.config = {
.mosi_io_num = GPIO_NUM_23,
.miso_io_num = GPIO_NUM_19,
.sclk_io_num = GPIO_NUM_18,
.quadwp_io_num = GPIO_NUM_NC,
.quadhd_io_num = GPIO_NUM_NC,
.data4_io_num = GPIO_NUM_NC,
.data5_io_num = GPIO_NUM_NC,
.data6_io_num = GPIO_NUM_NC,
.data7_io_num = GPIO_NUM_NC,
.data_io_default_level = false,
.max_transfer_sz = 0,
.flags = 0,
.isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO,
.intr_flags = 0
},
.initMode = spi::InitMode::ByTactility,
.isMutable = false,
.lock = tt::lvgl::getSyncLock() // esp_lvgl_port owns the lock for the display
},
},
.uart {
uart::Configuration {
.name = "P1",
.port = UART_NUM_1,
.rxPin = GPIO_NUM_1,
.txPin = GPIO_NUM_3,
.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,
}
}
}
}
};

View File

@ -0,0 +1,6 @@
#pragma once
#include <Tactility/hal/Configuration.h>
// Resistive touch version of the 2.8" yellow board version 3
extern const tt::hal::Configuration cyd_2432s028rv3_config;

View File

@ -0,0 +1,54 @@
#include "Display.h"
#include "Xpt2046SoftSpi.h"
#include <St7789Display.h>
#include <PwmBacklight.h>
constexpr auto* TAG = "CYD";
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto configuration = std::make_unique<Xpt2046SoftSpi::Configuration>(
CYD_TOUCH_MOSI_PIN,
CYD_TOUCH_MISO_PIN,
CYD_TOUCH_SCK_PIN,
CYD_TOUCH_CS_PIN,
CYD2432S028RV3_LCD_HORIZONTAL_RESOLUTION, // 240
CYD2432S028RV3_LCD_VERTICAL_RESOLUTION, // 320
false, // swapXY
true, // mirrorX
false // mirrorY
);
// Allocate the driver
auto touch = std::make_shared<Xpt2046SoftSpi>(std::move(configuration));
// Start the driver
if (!touch->start()) {
ESP_LOGE(TAG, "Touch driver start failed");
return nullptr;
}
return touch;
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
auto touch = createTouch();
auto configuration = std::make_unique<St7789Display::Configuration>(
CYD2432S028RV3_LCD_SPI_HOST,
CYD2432S028RV3_LCD_PIN_CS,
CYD2432S028RV3_LCD_PIN_DC,
CYD2432S028RV3_LCD_HORIZONTAL_RESOLUTION,
CYD2432S028RV3_LCD_VERTICAL_RESOLUTION,
touch,
false, // swapXY
false, // mirrorX
false, // mirrorY
false, // invertColor
CYD2432S028RV3_LCD_DRAW_BUFFER_SIZE
);
configuration->backlightDutyFunction = driver::pwmbacklight::setBacklightDuty;
auto display = std::make_shared<St7789Display>(std::move(configuration));
return std::reinterpret_pointer_cast<tt::hal::display::DisplayDevice>(display);
}

View File

@ -0,0 +1,22 @@
#pragma once
#include "Tactility/hal/display/DisplayDevice.h"
#include <memory>
// Display
#define CYD2432S028RV3_LCD_SPI_HOST SPI2_HOST
#define CYD2432S028RV3_LCD_HORIZONTAL_RESOLUTION 240
#define CYD2432S028RV3_LCD_VERTICAL_RESOLUTION 320
#define CYD2432S028RV3_LCD_DRAW_BUFFER_HEIGHT (CYD2432S028RV3_LCD_VERTICAL_RESOLUTION / 10)
#define CYD2432S028RV3_LCD_DRAW_BUFFER_SIZE (CYD2432S028RV3_LCD_HORIZONTAL_RESOLUTION * CYD2432S028RV3_LCD_DRAW_BUFFER_HEIGHT)
#define CYD2432S028RV3_LCD_PIN_CS GPIO_NUM_15
#define CYD2432S028RV3_LCD_PIN_DC GPIO_NUM_2
// Touch (Software SPI)
#define CYD_TOUCH_MISO_PIN GPIO_NUM_39
#define CYD_TOUCH_MOSI_PIN GPIO_NUM_32
#define CYD_TOUCH_SCK_PIN GPIO_NUM_25
#define CYD_TOUCH_CS_PIN GPIO_NUM_33
#define CYD_TOUCH_IRQ_PIN GPIO_NUM_36
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();

View File

@ -0,0 +1,21 @@
#include "SdCard.h"
#include <Tactility/hal/sdcard/SpiSdCardDevice.h>
#include <Tactility/lvgl/LvglSync.h>
using tt::hal::sdcard::SpiSdCardDevice;
std::shared_ptr<SdCardDevice> createSdCard() {
auto config = std::make_unique<SpiSdCardDevice::Config>(
GPIO_NUM_5,
GPIO_NUM_NC,
GPIO_NUM_NC,
GPIO_NUM_NC,
SdCardDevice::MountBehaviour::AtBoot,
std::make_shared<tt::Mutex>(tt::Mutex::Type::Recursive),
std::vector<gpio_num_t>(),
SPI3_HOST
);
return std::make_shared<SpiSdCardDevice>(std::move(config));
}

View File

@ -0,0 +1,8 @@
#pragma once
#include "Tactility/hal/sdcard/SdCardDevice.h"
using tt::hal::sdcard::SdCardDevice;
std::shared_ptr<SdCardDevice> createSdCard();

View File

@ -25,6 +25,8 @@ function(INIT_TACTILITY_GLOBALS SDKCONFIG_FILE)
set(TACTILITY_BOARD_PROJECT CYD-2432S024C)
elseif (board_id STREQUAL "cyd-2432s028r")
set(TACTILITY_BOARD_PROJECT CYD-2432S028R)
elseif (board_id STREQUAL "cyd-2432s028rv3")
set(TACTILITY_BOARD_PROJECT CYD-2432S028RV3)
elseif (board_id STREQUAL "cyd-e32r28t")
set(TACTILITY_BOARD_PROJECT CYD-E32R28T)
elseif (board_id STREQUAL "cyd-2432s032c")

View File

@ -2,14 +2,12 @@
## Before release
- App Hub
- Fix Wi-Fi password(less) decryption crash
- Make better esp_lcd driver (and test all devices)
## Higher Priority
- Calculator bugs (see GitHub issue)
- Store last synced timestamp in NVS (see how HTTPS client example app)
- Expose http::download() and main dispatcher to TactiltyC.
- External app loading: Check the version of Tactility and check ESP target hardware to check for compatibility
Check during installation process, but also when starting (SD card might have old app install from before Tactility OS update)
- Make a URL handler. Use it for handling local files. Match file types with apps.

View File

@ -15,6 +15,8 @@ menu "Tactility App"
bool "CYD 2432S024C"
config TT_BOARD_CYD_2432S028R
bool "CYD 2432S028R"
config TT_BOARD_CYD_2432S028RV3
bool "CYD 2432S028RV3"
config TT_BOARD_CYD_E32R28T
bool "CYD E32R28T"
config TT_BOARD_CYD_2432S032C

View File

@ -20,6 +20,9 @@
#elif defined(CONFIG_TT_BOARD_CYD_2432S028R)
#include "CYD2432S028R.h"
#define TT_BOARD_HARDWARE &cyd_2432s028r_config
#elif defined(CONFIG_TT_BOARD_CYD_2432S028RV3)
#include "CYD2432S028RV3.h"
#define TT_BOARD_HARDWARE &cyd_2432s028rv3_config
#elif defined(CONFIG_TT_BOARD_CYD_E32R28T)
#include "E32R28T.h"
#define TT_BOARD_HARDWARE &cyd_e32r28t_config

View File

@ -6,6 +6,11 @@
namespace tt::hal::sdcard {
/**
* Warning: getLock() does not have to be used when calling any of the functions of this class.
* The lock is only used for file access on the path where the SD card is mounted.
* This is mainly used when accessing the SD card on a shared SPI bus.
*/
class SdCardDevice : public Device {
public:
@ -33,15 +38,28 @@ public:
Type getType() const final { return Type::SdCard; };
/**
* Mount the device.
* @param mountPath the path to mount at
* @return true on successful mount
*/
virtual bool mount(const std::string& mountPath) = 0;
/**
* Unmount the device.
* @return true on successful unmount
*/
virtual bool unmount() = 0;
virtual State getState(TickType_t timeout = portMAX_DELAY) const = 0;
/** Return empty string when not mounted or the mount path if mounted */
/** @return empty string when not mounted or the mount path if mounted */
virtual std::string getMountPath() const = 0;
/** Non-null lock */
/** @return non-null lock, used by code that wants to access files on the mount path of this SD card */
virtual std::shared_ptr<Lock> getLock() const = 0;
/** @return the MountBehaviour of this device */
virtual MountBehaviour getMountBehaviour() const { return mountBehaviour; }
/** @return true if the SD card was mounted, returns false when it was not or when a timeout happened. */

View File

@ -11,7 +11,7 @@ class EspHttpClient {
static constexpr auto* TAG = "EspHttpClient";
std::unique_ptr<esp_http_client_config_t> config = nullptr;
esp_http_client_handle_t client;
esp_http_client_handle_t client = nullptr;
bool isOpen = false;
public:
@ -87,11 +87,15 @@ public:
bool close() {
assert(client != nullptr);
TT_LOG_I(TAG, "close()");
return esp_http_client_close(client) == ESP_OK;
if (esp_http_client_close(client) == ESP_OK) {
isOpen = false;
}
return !isOpen;
}
bool cleanup() {
assert(client != nullptr);
assert(!isOpen);
TT_LOG_I(TAG, "cleanup()");
const auto result = esp_http_client_cleanup(client);
client = nullptr;

View File

@ -1,10 +1,19 @@
#pragma once
#include <string>
#include <functional>
namespace tt::network::http {
void download(
/**
* Download a file from a URL.
* The server must send the Content-Length header.
* @param url download source URL
* @param certFilePath the path to the .pem file
* @param downloadFilePath The path to downloadd the file to. The parent directories must exist.
* @param onSuccess the success result callback
* @param onError the error result callback
*/
void download(
const std::string& url,
const std::string& certFilePath,
const std::string &downloadFilePath,

View File

@ -46,7 +46,7 @@ public:
bool readNumber(const char* key, double& output) const {
const auto* child = cJSON_GetObjectItemCaseSensitive(root, key);
if (!cJSON_IsNumber(child)) {
TT_LOG_E(TAG, "%s is not a string", key);
TT_LOG_E(TAG, "%s is not a number", key);
return false;
}
output = cJSON_GetNumberValue(child);

View File

@ -90,6 +90,8 @@ void Preferences::putBool(const std::string& key, bool value) {
if (nvs_open(namespace_, NVS_READWRITE, &handle) == ESP_OK) {
if (nvs_set_u8(handle, key.c_str(), (uint8_t)value) != ESP_OK) {
TT_LOG_E(TAG, "Failed to write %s:%s", namespace_, key.c_str());
} else if (nvs_commit(handle) != ESP_OK) {
TT_LOG_E(TAG, "Failed to commit %s:%s", namespace_, key.c_str());
}
nvs_close(handle);
} else {
@ -102,6 +104,8 @@ void Preferences::putInt32(const std::string& key, int32_t value) {
if (nvs_open(namespace_, NVS_READWRITE, &handle) == ESP_OK) {
if (nvs_set_i32(handle, key.c_str(), value) != ESP_OK) {
TT_LOG_E(TAG, "Failed to write %s:%s", namespace_, key.c_str());
} else if (nvs_commit(handle) != ESP_OK) {
TT_LOG_E(TAG, "Failed to commit %s:%s", namespace_, key.c_str());
}
nvs_close(handle);
} else {
@ -114,6 +118,8 @@ void Preferences::putInt64(const std::string& key, int64_t value) {
if (nvs_open(namespace_, NVS_READWRITE, &handle) == ESP_OK) {
if (nvs_set_i64(handle, key.c_str(), value) != ESP_OK) {
TT_LOG_E(TAG, "Failed to write %s:%s", namespace_, key.c_str());
} else if (nvs_commit(handle) != ESP_OK) {
TT_LOG_E(TAG, "Failed to commit %s:%s", namespace_, key.c_str());
}
nvs_close(handle);
} else {
@ -125,6 +131,9 @@ void Preferences::putString(const std::string& key, const std::string& text) {
nvs_handle_t handle;
if (nvs_open(namespace_, NVS_READWRITE, &handle) == ESP_OK) {
nvs_set_str(handle, key.c_str(), text.c_str());
if (nvs_commit(handle) != ESP_OK) {
TT_LOG_E(TAG, "Failed to commit %s:%s", namespace_, key.c_str());
}
nvs_close(handle);
} else {
TT_LOG_E(TAG, "Failed to open namespace %s", namespace_);

View File

@ -101,6 +101,8 @@ namespace app {
// List of all apps excluding Boot app (as Boot app calls this function indirectly)
static void registerInternalApps() {
TT_LOG_I(TAG, "Registering internal apps");
addAppManifest(app::alertdialog::manifest);
addAppManifest(app::appdetails::manifest);
addAppManifest(app::apphub::manifest);
@ -180,6 +182,8 @@ static void registerInstalledApp(std::string path) {
}
static void registerInstalledApps(const std::string& path) {
TT_LOG_I(TAG, "Registering apps from %s", path.c_str());
file::listDirectory(path, [&path](const auto& entry) {
auto absolute_path = std::format("{}/{}", path, entry.d_name);
if (file::isDirectory(absolute_path)) {
@ -200,18 +204,12 @@ static void registerInstalledAppsFromSdCards() {
auto sdcard_devices = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
for (const auto& sdcard : sdcard_devices) {
if (sdcard->isMounted()) {
TT_LOG_I(TAG, "Registering apps from %s", sdcard->getMountPath().c_str());
registerInstalledAppsFromSdCard(sdcard);
}
}
}
static void registerInstalledAppsFromData() {
auto app_path = "/data/app";
if (file::isDirectory(app_path)) {
registerInstalledApps(app_path);
}
}
static void registerAndStartSecondaryServices() {
TT_LOG_I(TAG, "Registering and starting system services");
addService(service::loader::manifest);
@ -242,7 +240,6 @@ void registerApps() {
registerInstalledApps(data_apps_path);
}
registerInstalledAppsFromSdCards();
registerInstalledAppsFromData();
}
void run(const Configuration& config) {

View File

@ -40,11 +40,7 @@ class AppHubApp final : public App {
const auto* self = static_cast<AppHubApp*>(lv_event_get_user_data(e));
auto* widget = lv_event_get_target_obj(e);
const auto* user_data = lv_obj_get_user_data(widget);
#ifdef ESP_PLATFORM
const int index = reinterpret_cast<int>(user_data);
#else
const long long index = reinterpret_cast<long long>(user_data);
#endif
const intptr_t index = reinterpret_cast<intptr_t>(user_data);
self->mutex.lock();
if (index < self->entries.size()) {
apphubdetails::start(self->entries[index]);
@ -112,7 +108,8 @@ class AppHubApp final : public App {
TT_LOG_I(TAG, "Adding %s", entry.appName.c_str());
const char* icon = findAppManifestById(entry.appId) != nullptr ? LV_SYMBOL_OK : nullptr;
auto* entry_button = lv_list_add_button(list, icon, entry.appName.c_str());
lv_obj_set_user_data(entry_button, reinterpret_cast<void*>(i));
auto int_as_voidptr = reinterpret_cast<void*>(i);
lv_obj_set_user_data(entry_button, int_as_voidptr);
lv_obj_add_event_cb(entry_button, onAppPressed, LV_EVENT_SHORT_CLICKED, this);
}
} else {

View File

@ -23,6 +23,11 @@ bool parseJson(const std::string& filePath, std::vector<AppHubEntry>& entries) {
lock.lock();
auto data = file::readString(filePath);
if (data == nullptr) {
TT_LOG_E(TAG, "Failed to read %s", filePath.c_str());
return false;
}
auto data_ptr = reinterpret_cast<const char*>(data.get());
auto* json = cJSON_Parse(data_ptr);
if (json == nullptr) {

View File

@ -16,10 +16,9 @@ bool ObjectFileWriter::open() {
}
// Edit existing or create a new file
auto* mode = edit_existing ? "r+" : "w";
auto opening_file = std::unique_ptr<FILE, FileCloser>(std::fopen(filePath.c_str(), mode));
auto opening_file = std::unique_ptr<FILE, FileCloser>(std::fopen(filePath.c_str(), "wb"));
if (opening_file == nullptr) {
TT_LOG_E(TAG, "Failed to open file %s in %s mode", filePath.c_str(), mode);
TT_LOG_E(TAG, "Failed to open file %s", filePath.c_str());
return false;
}

View File

@ -9,12 +9,10 @@ constexpr auto* TAG = "SdCardMounting";
constexpr auto* TT_SDCARD_MOUNT_POINT = "/sdcard";
static void mount(const std::shared_ptr<SdCardDevice>& sdcard, const std::string& path) {
sdcard->getLock()->withLock([&sdcard, &path] {
TT_LOG_I(TAG, "Mounting sdcard at %s", path.c_str());
if (!sdcard->mount(path)) {
TT_LOG_W(TAG, "SD card mount failed for %s (init can continue)", path.c_str());
}
});
TT_LOG_I(TAG, "Mounting sdcard at %s", path.c_str());
if (!sdcard->mount(path)) {
TT_LOG_W(TAG, "SD card mount failed for %s (init can continue)", path.c_str());
}
}
static std::string getMountPath(int index, int count) {

View File

@ -58,6 +58,9 @@ bool SdmmcDevice::mountInternal(const std::string& newMountPath) {
}
bool SdmmcDevice::mount(const std::string& newMountPath) {
auto lock = getLock()->asScopedLock();
lock.lock();
if (mountInternal(newMountPath)) {
TT_LOG_I(TAG, "Mounted at %s", newMountPath.c_str());
sdmmc_card_print_info(stdout, card);
@ -69,6 +72,9 @@ bool SdmmcDevice::mount(const std::string& newMountPath) {
}
bool SdmmcDevice::unmount() {
auto lock = getLock()->asScopedLock();
lock.lock();
if (card == nullptr) {
TT_LOG_E(TAG, "Can't unmount: not mounted");
return false;

View File

@ -84,6 +84,9 @@ bool SpiSdCardDevice::mountInternal(const std::string& newMountPath) {
}
bool SpiSdCardDevice::mount(const std::string& newMountPath) {
auto lock = getLock()->asScopedLock();
lock.lock();
if (!applyGpioWorkAround()) {
TT_LOG_E(TAG, "Failed to apply GPIO work-around");
return false;
@ -100,6 +103,9 @@ bool SpiSdCardDevice::mount(const std::string& newMountPath) {
}
bool SpiSdCardDevice::unmount() {
auto lock = getLock()->asScopedLock();
lock.lock();
if (card == nullptr) {
TT_LOG_E(TAG, "Can't unmount: not mounted");
return false;

View File

@ -48,14 +48,6 @@ typedef struct {
PubSub<void*>::SubscriptionHandle pubsub_subscription;
} Statusbar;
static bool statusbar_lock(TickType_t timeoutTicks = portMAX_DELAY) {
return statusbar_data.mutex.lock(timeoutTicks);
}
static bool statusbar_unlock() {
return statusbar_data.mutex.unlock();
}
static void statusbar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj);
static void statusbar_destructor(const lv_obj_class_t* class_p, lv_obj_t* obj);
static void statusbar_event(const lv_obj_class_t* class_p, lv_event_t* event);
@ -111,10 +103,12 @@ static const lv_obj_class_t statusbar_class = {
static void statusbar_pubsub_event(Statusbar* statusbar) {
TT_LOG_D(TAG, "Update event");
if (lock(portMAX_DELAY)) {
if (lock(defaultLockTime)) {
update_main(statusbar);
lv_obj_invalidate(&statusbar->obj);
unlock();
} else {
TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "Statusbar");
}
}
@ -184,7 +178,7 @@ lv_obj_t* statusbar_create(lv_obj_t* parent) {
obj_set_style_bg_invisible(left_spacer);
lv_obj_set_flex_grow(left_spacer, 1);
statusbar_lock(portMAX_DELAY);
statusbar_data.mutex.lock(portMAX_DELAY);
for (int i = 0; i < STATUSBAR_ICON_LIMIT; ++i) {
auto* image = lv_image_create(obj);
lv_obj_set_size(image, STATUSBAR_ICON_SIZE, STATUSBAR_ICON_SIZE);
@ -194,7 +188,7 @@ lv_obj_t* statusbar_create(lv_obj_t* parent) {
update_icon(image, &(statusbar_data.icons[i]));
}
statusbar_unlock();
statusbar_data.mutex.unlock();
return obj;
}
@ -212,11 +206,11 @@ static void update_time(Statusbar* statusbar) {
static void update_main(Statusbar* statusbar) {
update_time(statusbar);
if (statusbar_lock(200 / portTICK_PERIOD_MS)) {
if (statusbar_data.mutex.lock(200 / portTICK_PERIOD_MS)) {
for (int i = 0; i < STATUSBAR_ICON_LIMIT; ++i) {
update_icon(statusbar->icons[i], &(statusbar_data.icons[i]));
}
statusbar_unlock();
statusbar_data.mutex.unlock();
}
}
@ -236,7 +230,7 @@ static void statusbar_event(TT_UNUSED const lv_obj_class_t* class_p, lv_event_t*
}
int8_t statusbar_icon_add(const std::string& image) {
statusbar_lock();
statusbar_data.mutex.lock();
int8_t result = -1;
for (int8_t i = 0; i < STATUSBAR_ICON_LIMIT; ++i) {
if (!statusbar_data.icons[i].claimed) {
@ -248,8 +242,8 @@ int8_t statusbar_icon_add(const std::string& image) {
break;
}
}
statusbar_data.mutex.unlock();
statusbar_data.pubsub->publish(nullptr);
statusbar_unlock();
return result;
}
@ -260,37 +254,35 @@ int8_t statusbar_icon_add() {
void statusbar_icon_remove(int8_t id) {
TT_LOG_D(TAG, "id %d: remove", id);
tt_check(id >= 0 && id < STATUSBAR_ICON_LIMIT);
statusbar_lock();
statusbar_data.mutex.lock();
StatusbarIcon* icon = &statusbar_data.icons[id];
icon->claimed = false;
icon->visible = false;
icon->image = "";
statusbar_data.mutex.unlock();
statusbar_data.pubsub->publish(nullptr);
statusbar_unlock();
}
void statusbar_icon_set_image(int8_t id, const std::string& image) {
TT_LOG_D(TAG, "id %d: set image %s", id, image.empty() ? "(none)" : image.c_str());
tt_check(id >= 0 && id < STATUSBAR_ICON_LIMIT);
if (statusbar_lock()) {
StatusbarIcon* icon = &statusbar_data.icons[id];
tt_check(icon->claimed);
icon->image = image;
statusbar_data.pubsub->publish(nullptr);
statusbar_unlock();
}
statusbar_data.mutex.lock();
StatusbarIcon* icon = &statusbar_data.icons[id];
tt_check(icon->claimed);
icon->image = image;
statusbar_data.mutex.unlock();
statusbar_data.pubsub->publish(nullptr);
}
void statusbar_icon_set_visibility(int8_t id, bool visible) {
TT_LOG_D(TAG, "id %d: set visibility %d", id, visible);
tt_check(id >= 0 && id < STATUSBAR_ICON_LIMIT);
if (statusbar_lock()) {
StatusbarIcon* icon = &statusbar_data.icons[id];
tt_check(icon->claimed);
icon->visible = visible;
statusbar_data.pubsub->publish(nullptr);
statusbar_unlock();
}
statusbar_data.mutex.lock();
StatusbarIcon* icon = &statusbar_data.icons[id];
tt_check(icon->claimed);
icon->visible = visible;
statusbar_data.mutex.unlock();
statusbar_data.pubsub->publish(nullptr);
}
} // namespace

View File

@ -223,6 +223,7 @@ lv_obj_t* toolbar_add_spinner_action(lv_obj_t* obj) {
void toolbar_clear_actions(lv_obj_t* obj) {
auto* toolbar = reinterpret_cast<Toolbar*>(obj);
lv_obj_clean(toolbar->action_container);
toolbar->action_count = 0;
}
} // namespace

View File

@ -24,6 +24,11 @@ void download(
getMainDispatcher().dispatch([url, certFilePath, downloadFilePath, onSuccess, onError] {
TT_LOG_I(TAG, "Loading certificate");
auto certificate = file::readString(certFilePath);
if (certificate == nullptr) {
onError("Failed to read certificate");
return;
}
auto certificate_length = strlen(reinterpret_cast<const char*>(certificate.get())) + 1;
auto config = std::make_unique<esp_http_client_config_t>(esp_http_client_config_t {
@ -40,35 +45,33 @@ void download(
auto client = std::make_unique<EspHttpClient>();
if (!client->init(std::move(config))) {
onError("Failed to initialize client");
return -1;
return;
}
if (!client->open()) {
onError("Failed to open connection");
return -1;
return;
}
if (!client->fetchHeaders()) {
onError("Failed to get request headers");
return -1;
return;
}
if (!client->isStatusCodeOk()) {
onError("Server response is not OK");
return -1;
return;
}
auto bytes_left = client->getContentLength();
auto lock = file::getLock(downloadFilePath)->asScopedLock();
lock.lock();
auto file_exists = file::isFile(downloadFilePath);
auto* file_mode = file_exists ? "r+" : "w";
TT_LOG_I(TAG, "opening %s with mode %s", downloadFilePath.c_str(), file_mode);
auto* file = fopen(downloadFilePath.c_str(), file_mode);
TT_LOG_I(TAG, "opening %s", downloadFilePath.c_str());
auto* file = fopen(downloadFilePath.c_str(), "wb");
if (file == nullptr) {
onError("Failed to open file");
return -1;
return;
}
TT_LOG_I(TAG, "Writing %d bytes to %s", bytes_left, downloadFilePath.c_str());
@ -78,19 +81,18 @@ void download(
if (data_read <= 0) {
fclose(file);
onError("Failed to read data");
return -1;
return;
}
bytes_left -= data_read;
if (fwrite(buffer, 1, data_read, file) != data_read) {
fclose(file);
onError("Failed to write all bytes");
return -1;
return;
}
}
fclose(file);
TT_LOG_I(TAG, "Downloaded %s to %s", url.c_str(), downloadFilePath.c_str());
onSuccess();
return 0;
});
#else
getMainDispatcher().dispatch([onError] {

View File

@ -22,7 +22,7 @@ bool HttpServer::startInternal() {
httpd_register_uri_handler(server, &handler);
}
TT_LOG_I(TAG, "Started on port %d", config.server_port);
TT_LOG_I(TAG, "Started on port %lu", config.server_port);
return true;
}

View File

@ -44,6 +44,8 @@ static void onTimeSynced(timeval* tv) {
}
void init() {
setTimeFromNvs();
esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG("time.cloudflare.com");
config.sync_cb = onTimeSynced;
esp_netif_sntp_init(&config);

View File

@ -132,7 +132,10 @@ bool load(const std::string& ssid, WifiApSettings& apSettings) {
if (map.contains(AP_PROPERTIES_KEY_PASSWORD)) {
std::string password_decrypted;
if (decrypt(map[AP_PROPERTIES_KEY_PASSWORD], password_decrypted)) {
const auto& encrypted_password = map[AP_PROPERTIES_KEY_PASSWORD];
if (encrypted_password.empty()) {
apSettings.password = "";
} else if (decrypt(encrypted_password, password_decrypted)) {
apSettings.password = password_decrypted;
} else {
return false;
@ -148,7 +151,7 @@ bool load(const std::string& ssid, WifiApSettings& apSettings) {
}
if (map.contains(AP_PROPERTIES_KEY_CHANNEL)) {
apSettings.channel = std::stoi(map[AP_PROPERTIES_KEY_CHANNEL].c_str());
apSettings.channel = std::stoi(map[AP_PROPERTIES_KEY_CHANNEL]);
} else {
apSettings.channel = 0;
}
@ -167,8 +170,12 @@ bool save(const WifiApSettings& apSettings) {
std::map<std::string, std::string> map;
std::string password_encrypted;
if (!encrypt(apSettings.password, password_encrypted)) {
return false;
if (!apSettings.password.empty()) {
if (!encrypt(apSettings.password, password_encrypted)) {
return false;
}
} else {
password_encrypted = "";
}
map[AP_PROPERTIES_KEY_PASSWORD] = password_encrypted;

View File

@ -10,6 +10,7 @@
#include <map>
#include <string>
#include <vector>
#include <Tactility/Tactility.h>
#include <Tactility/hal/sdcard/SdCardDevice.h>
namespace tt::service::wifi {
@ -115,14 +116,16 @@ static void importWifiApSettings(std::shared_ptr<hal::sdcard::SdCardDevice> sdca
}
void bootSplashInit() {
const auto sdcards = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
for (auto& sdcard : sdcards) {
if (sdcard->isMounted()) {
importWifiApSettings(sdcard);
} else {
TT_LOG_W(TAG, "Skipping unmounted SD card %s", sdcard->getMountPath().c_str());
getMainDispatcher().dispatch([] {
const auto sdcards = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
for (auto& sdcard : sdcards) {
if (sdcard->isMounted()) {
importWifiApSettings(sdcard);
} else {
TT_LOG_W(TAG, "Skipping unmounted SD card %s", sdcard->getMountPath().c_str());
}
}
}
});
}
}

View File

@ -61,7 +61,7 @@ bool Bundle::optInt32(const std::string& key, int32_t& out) const {
bool Bundle::optInt64(const std::string& key, int64_t& out) const {
auto entry = this->entries.find(key);
if (entry != std::end(this->entries) && entry->second.type == Type::Int64) {
out = entry->second.value_int32;
out = entry->second.value_int64;
return true;
} else {
return false;

View File

@ -0,0 +1,54 @@
# Software defaults
# Increase stack size for WiFi (fixes crash after scan)
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=3072
CONFIG_ESP_MAIN_TASK_STACK_SIZE=5120
CONFIG_LV_FONT_MONTSERRAT_14=y
CONFIG_LV_FONT_MONTSERRAT_18=y
CONFIG_LV_USE_USER_DATA=y
CONFIG_LV_USE_FS_STDIO=y
CONFIG_LV_FS_STDIO_LETTER=65
CONFIG_LV_FS_STDIO_PATH=""
CONFIG_LV_FS_STDIO_CACHE_SIZE=4096
CONFIG_LV_USE_LODEPNG=y
CONFIG_LV_USE_BUILTIN_MALLOC=n
CONFIG_LV_USE_CLIB_MALLOC=y
CONFIG_LV_USE_MSGBOX=n
CONFIG_LV_USE_SPINNER=n
CONFIG_LV_USE_WIN=n
CONFIG_LV_USE_SNAPSHOT=y
CONFIG_FREERTOS_HZ=1000
CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2
CONFIG_FREERTOS_SMP=n
CONFIG_FREERTOS_UNICORE=n
CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=5120
CONFIG_FREERTOS_USE_TRACE_FACILITY=y
CONFIG_FATFS_LFN_HEAP=y
CONFIG_FATFS_VOLUME_COUNT=3
CONFIG_FATFS_SECTOR_512=y
CONFIG_WL_SECTOR_SIZE_512=y
CONFIG_WL_SECTOR_SIZE=512
CONFIG_WL_SECTOR_MODE_SAFE=y
CONFIG_WL_SECTOR_MODE=1
CONFIG_MBEDTLS_SSL_PROTO_TLS1_3=y
# Hardware: Main
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-4mb.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions-4mb.csv"
CONFIG_TT_BOARD_NAME="CYD 2432S028RV3"
CONFIG_TT_BOARD_ID="cyd-2432s028rv3"
CONFIG_TT_BOARD_CYD_2432S028RV3=y
CONFIG_IDF_TARGET="esp32"
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_FLASHMODE_QIO=y
# LVGL
CONFIG_LV_DISP_DEF_REFR_PERIOD=10
CONFIG_LV_DPI_DEF=160
CONFIG_LV_THEME_DEFAULT_DARK=y
# Fix for IRAM
CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y
CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y
CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH=y
CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y