Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a540c644e |
@@ -0,0 +1,8 @@
|
||||
# Force CMake reload to detect new files module.cpp and Configuration.cpp
|
||||
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
|
||||
|
||||
idf_component_register(
|
||||
SRCS ${SOURCE_FILES}
|
||||
INCLUDE_DIRS "Source"
|
||||
REQUIRES Tactility ButtonControl ST7305 PwmBacklight driver
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
#include "devices/Display.h"
|
||||
|
||||
#include <Tactility/hal/Configuration.h>
|
||||
#include <ButtonControl.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <tactility/log.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
static constexpr auto* TAG = "WaveshareRLCD";
|
||||
|
||||
using namespace tt::hal;
|
||||
|
||||
static DeviceVector createDevices() {
|
||||
return {
|
||||
createDisplay(),
|
||||
ButtonControl::createTwoButtonControl(GPIO_NUM_18, GPIO_NUM_0) // KEY=18 cycles, BOOT=0 selects
|
||||
};
|
||||
}
|
||||
|
||||
static bool initBoot() {
|
||||
// Amp enable GPIO46 active HIGH - keep LOW during boot to avoid pop,
|
||||
// then HIGH after delay so speaker works for MCP / Mp3Player
|
||||
gpio_config_t io_conf = {};
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pin_bit_mask = (1ULL << GPIO_NUM_46);
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||
gpio_config(&io_conf);
|
||||
gpio_set_level(GPIO_NUM_46, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
gpio_set_level(GPIO_NUM_46, 1);
|
||||
LOG_I(TAG, "Speaker amp GPIO46 enabled");
|
||||
return true;
|
||||
}
|
||||
|
||||
extern const Configuration hardwareConfiguration = {
|
||||
.initBoot = initBoot,
|
||||
.createDevices = createDevices
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "Display.h"
|
||||
#include <St7305Display.h>
|
||||
|
||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
|
||||
auto configuration = std::make_unique<St7305Display::Configuration>(
|
||||
SPI2_HOST,
|
||||
GPIO_NUM_40, // CS
|
||||
GPIO_NUM_5, // DC
|
||||
400, // Width
|
||||
300, // Height
|
||||
nullptr, // Touch device
|
||||
false, // swapXY
|
||||
false, // mirrorX
|
||||
false, // mirrorY
|
||||
true, // invertColor - initial true to match existing inverted look, user can toggle via Settings->Display
|
||||
0 // bufferSize (0 = default, which is full screen = 120,000 pixels)
|
||||
);
|
||||
|
||||
configuration->resetPin = GPIO_NUM_41;
|
||||
|
||||
auto display = std::make_shared<St7305Display>(std::move(configuration));
|
||||
return std::reinterpret_pointer_cast<tt::hal::display::DisplayDevice>(display);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/hal/display/DisplayDevice.h>
|
||||
#include <memory>
|
||||
|
||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
||||
@@ -0,0 +1,21 @@
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
static error_t start() {
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
struct Module waveshare_esp32_s3_rlcd_module = {
|
||||
.name = "waveshare-esp32-s3-rlcd",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
general.vendor=WaveShare
|
||||
general.name=ESP32-S3-RLCD-4.2
|
||||
general.incubating=true
|
||||
|
||||
apps.launcherAppId=Launcher
|
||||
# apps.autoStartAppId=ApWebServer
|
||||
|
||||
hardware.target=ESP32S3
|
||||
hardware.flashSize=16MB
|
||||
hardware.spiRam=true
|
||||
hardware.spiRamMode=OCT
|
||||
hardware.spiRamSpeed=120M
|
||||
hardware.tinyUsb=true
|
||||
hardware.esptoolFlashFreq=120M
|
||||
hardware.bluetooth=true
|
||||
|
||||
storage.userDataLocation=SD
|
||||
|
||||
display.size=4.2"
|
||||
display.shape=rectangle
|
||||
display.dpi=120
|
||||
|
||||
lvgl.colorDepth=16
|
||||
lvgl.uiDensity=compact
|
||||
lvgl.fontSize=20
|
||||
lvgl.theme=DefaultDark
|
||||
@@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
- Platforms/platform-esp32
|
||||
dts: waveshare,esp32-s3-rlcd.dts
|
||||
@@ -0,0 +1,78 @@
|
||||
/dts-v1/;
|
||||
|
||||
#include <tactility/bindings/root.h>
|
||||
#include <tactility/bindings/esp32_ble.h>
|
||||
#include <tactility/bindings/esp32_gpio.h>
|
||||
#include <tactility/bindings/esp32_i2c_master.h>
|
||||
#include <tactility/bindings/esp32_sdmmc.h>
|
||||
#include <tactility/bindings/esp32_spi.h>
|
||||
#include <tactility/bindings/esp32_i2s.h>
|
||||
#include <tactility/bindings/esp32_uart.h>
|
||||
#include <tactility/bindings/display_placeholder.h>
|
||||
|
||||
/ {
|
||||
compatible = "root";
|
||||
model = "Waveshare ESP32-S3-RLCD-4.2";
|
||||
|
||||
ble0 {
|
||||
compatible = "espressif,esp32-ble";
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
gpio0 {
|
||||
compatible = "espressif,esp32-gpio";
|
||||
gpio-count = <49>;
|
||||
};
|
||||
|
||||
// Audio: ES7210 mic + speaker via I2S0
|
||||
// Pins verified from working MicroPython board_config.py for WAVESHARE_RLCD:
|
||||
// mclk=16, bclk=9, ws=45, tx=8 (DAC out), rx=10 (ES7210 mic in)
|
||||
// NOTE: Display DC is GPIO5, so I2S must NOT use GPIO5 to avoid bus corruption
|
||||
i2s0 {
|
||||
compatible = "espressif,esp32-i2s";
|
||||
port = <I2S_NUM_0>;
|
||||
pin-bclk = <&gpio0 9 GPIO_FLAG_NONE>;
|
||||
pin-ws = <&gpio0 45 GPIO_FLAG_NONE>;
|
||||
pin-data-out = <&gpio0 8 GPIO_FLAG_NONE>;
|
||||
pin-data-in = <&gpio0 10 GPIO_FLAG_NONE>;
|
||||
pin-mclk = <&gpio0 16 GPIO_FLAG_NONE>;
|
||||
};
|
||||
|
||||
i2c0 {
|
||||
compatible = "espressif,esp32-i2c-master";
|
||||
port = <I2C_NUM_0>;
|
||||
clock-frequency = <400000>;
|
||||
pin-sda = <&gpio0 13 GPIO_FLAG_NONE>;
|
||||
pin-scl = <&gpio0 14 GPIO_FLAG_NONE>;
|
||||
};
|
||||
|
||||
spi0 {
|
||||
compatible = "espressif,esp32-spi";
|
||||
host = <SPI2_HOST>;
|
||||
cs-gpios = <&gpio0 40 GPIO_FLAG_NONE>;
|
||||
pin-mosi = <&gpio0 12 GPIO_FLAG_NONE>;
|
||||
pin-sclk = <&gpio0 11 GPIO_FLAG_NONE>;
|
||||
|
||||
display {
|
||||
compatible = "display-placeholder";
|
||||
};
|
||||
};
|
||||
|
||||
sdmmc0 {
|
||||
compatible = "espressif,esp32-sdmmc";
|
||||
pin-clk = <&gpio0 38 GPIO_FLAG_NONE>;
|
||||
pin-cmd = <&gpio0 21 GPIO_FLAG_NONE>;
|
||||
pin-d0 = <&gpio0 39 GPIO_FLAG_NONE>;
|
||||
pin-d3 = <&gpio0 17 GPIO_FLAG_NONE>;
|
||||
slot = <SDMMC_HOST_SLOT_1>;
|
||||
bus-width = <1>;
|
||||
pullups;
|
||||
};
|
||||
|
||||
uart0 {
|
||||
compatible = "espressif,esp32-uart";
|
||||
port = <UART_NUM_0>;
|
||||
pin-tx = <&gpio0 43 GPIO_FLAG_NONE>;
|
||||
pin-rx = <&gpio0 44 GPIO_FLAG_NONE>;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "Source"
|
||||
INCLUDE_DIRS "Source"
|
||||
REQUIRES Tactility driver EspLcdCompat
|
||||
)
|
||||
@@ -0,0 +1,108 @@
|
||||
#include "St7305Display.h"
|
||||
#include "esp_lcd_st7305.h"
|
||||
|
||||
#include <tactility/log.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <esp_lvgl_port.h>
|
||||
|
||||
static const char* TAG = "ST7305";
|
||||
|
||||
bool St7305Display::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
|
||||
LOG_I(TAG, "Starting ST7305 SPI panel IO creation");
|
||||
|
||||
const esp_lcd_panel_io_spi_config_t panel_io_config = {
|
||||
.cs_gpio_num = configuration->csPin,
|
||||
.dc_gpio_num = configuration->dcPin,
|
||||
.spi_mode = 0,
|
||||
.pclk_hz = configuration->pixelClockFrequency,
|
||||
.trans_queue_depth = configuration->transactionQueueDepth,
|
||||
.on_color_trans_done = nullptr,
|
||||
.user_ctx = nullptr,
|
||||
.lcd_cmd_bits = 8,
|
||||
.lcd_param_bits = 8,
|
||||
.flags = {
|
||||
.dc_high_on_cmd = 0,
|
||||
.dc_low_on_data = 0,
|
||||
.dc_low_on_param = 0,
|
||||
.octal_mode = 0,
|
||||
.quad_mode = 0,
|
||||
.sio_mode = 0,
|
||||
.lsb_first = 0,
|
||||
.cs_high_active = 0
|
||||
}
|
||||
};
|
||||
|
||||
if (esp_lcd_new_panel_io_spi(configuration->spiHostDevice, &panel_io_config, &outHandle) != ESP_OK) {
|
||||
LOG_E(TAG, "Failed to create panel SPI IO");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool St7305Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t& panelHandle) {
|
||||
const esp_lcd_panel_dev_config_t panel_config = {
|
||||
.reset_gpio_num = configuration->resetPin,
|
||||
.color_space = ESP_LCD_COLOR_SPACE_MONOCHROME,
|
||||
.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
|
||||
.bits_per_pixel = 1,
|
||||
.flags = {
|
||||
.reset_active_high = false
|
||||
},
|
||||
.vendor_config = nullptr
|
||||
};
|
||||
|
||||
if (esp_lcd_new_panel_st7305(ioHandle, &panel_config, &panelHandle) != ESP_OK) {
|
||||
LOG_E(TAG, "Failed to create st7305 panel");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ST7305 needs extra delay after reset before init — prevents freeze on fast boot
|
||||
if (esp_lcd_panel_reset(panelHandle) != ESP_OK) {
|
||||
LOG_E(TAG, "Failed to reset st7305 panel");
|
||||
return false;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(150));
|
||||
|
||||
if (esp_lcd_panel_init(panelHandle) != ESP_OK) {
|
||||
LOG_E(TAG, "Failed to init st7305 panel");
|
||||
return false;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
|
||||
if (configuration->invertColor) {
|
||||
if (esp_lcd_panel_invert_color(panelHandle, true) != ESP_OK) {
|
||||
LOG_W(TAG, "Failed to apply initial invertColor");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
lvgl_port_display_cfg_t St7305Display::getLvglPortDisplayConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) {
|
||||
return lvgl_port_display_cfg_t {
|
||||
.io_handle = ioHandle,
|
||||
.panel_handle = panelHandle,
|
||||
.control_handle = nullptr,
|
||||
.buffer_size = configuration->bufferSize,
|
||||
.double_buffer = false, // Monochrome displays usually use single buffering to save RAM
|
||||
.trans_size = 0,
|
||||
.hres = configuration->horizontalResolution,
|
||||
.vres = configuration->verticalResolution,
|
||||
.monochrome = true, // Enables esp_lvgl_port monochrome converter
|
||||
.rotation = {
|
||||
.swap_xy = false,
|
||||
.mirror_x = true,
|
||||
.mirror_y = false,
|
||||
},
|
||||
.color_format = LV_COLOR_FORMAT_RGB565, // Must be RGB565 for monochrome mode to trigger converter
|
||||
.flags = {
|
||||
.buff_dma = false,
|
||||
.buff_spiram = false,
|
||||
.sw_rotate = false,
|
||||
.swap_bytes = false,
|
||||
.full_refresh = true, // We want full refresh to rewrite the converted block format to ST7305
|
||||
.direct_mode = false
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
|
||||
#include <EspLcdDisplay.h>
|
||||
#include <Tactility/hal/display/DisplayDevice.h>
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_types.h>
|
||||
#include <functional>
|
||||
#include <lvgl.h>
|
||||
|
||||
class St7305Display final : public EspLcdDisplay {
|
||||
|
||||
public:
|
||||
|
||||
class Configuration {
|
||||
|
||||
public:
|
||||
|
||||
Configuration(
|
||||
spi_host_device_t spiHostDevice,
|
||||
gpio_num_t csPin,
|
||||
gpio_num_t dcPin,
|
||||
unsigned int horizontalResolution,
|
||||
unsigned int verticalResolution,
|
||||
std::shared_ptr<tt::hal::touch::TouchDevice> touch = nullptr,
|
||||
bool swapXY = false,
|
||||
bool mirrorX = false,
|
||||
bool mirrorY = false,
|
||||
bool invertColor = false,
|
||||
uint32_t bufferSize = 0
|
||||
) : spiHostDevice(spiHostDevice),
|
||||
csPin(csPin),
|
||||
dcPin(dcPin),
|
||||
horizontalResolution(horizontalResolution),
|
||||
verticalResolution(verticalResolution),
|
||||
swapXY(swapXY),
|
||||
mirrorX(mirrorX),
|
||||
mirrorY(mirrorY),
|
||||
invertColor(invertColor),
|
||||
bufferSize(bufferSize),
|
||||
touch(std::move(touch))
|
||||
{
|
||||
if (this->bufferSize == 0) {
|
||||
// For monochrome display, full pixel count is expected for buffer size
|
||||
this->bufferSize = horizontalResolution * verticalResolution;
|
||||
}
|
||||
}
|
||||
|
||||
spi_host_device_t spiHostDevice;
|
||||
gpio_num_t csPin;
|
||||
gpio_num_t dcPin;
|
||||
gpio_num_t resetPin = GPIO_NUM_NC;
|
||||
unsigned int pixelClockFrequency = 10'000'000; // 10MHz SPI clock for ST7305
|
||||
size_t transactionQueueDepth = 10;
|
||||
unsigned int horizontalResolution;
|
||||
unsigned int verticalResolution;
|
||||
bool swapXY = false;
|
||||
bool mirrorX = false;
|
||||
bool mirrorY = false;
|
||||
bool invertColor = false;
|
||||
uint32_t bufferSize = 0;
|
||||
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
|
||||
std::function<void(uint8_t)> _Nullable backlightDutyFunction = nullptr;
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
std::unique_ptr<Configuration> configuration;
|
||||
|
||||
bool createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) override;
|
||||
|
||||
bool createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t& panelHandle) override;
|
||||
|
||||
lvgl_port_display_cfg_t getLvglPortDisplayConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) override;
|
||||
|
||||
public:
|
||||
|
||||
explicit St7305Display(std::unique_ptr<Configuration> inConfiguration) :
|
||||
configuration(std::move(inConfiguration))
|
||||
{}
|
||||
|
||||
std::string getName() const override { return "ST7305"; }
|
||||
|
||||
std::string getDescription() const override { return "ST7305 monochrome reflective LCD display"; }
|
||||
|
||||
std::shared_ptr<tt::hal::touch::TouchDevice> _Nullable getTouchDevice() override { return configuration->touch; }
|
||||
|
||||
void setBacklightDuty(uint8_t backlightDuty) override {
|
||||
if (configuration->backlightDutyFunction != nullptr) {
|
||||
configuration->backlightDutyFunction(backlightDuty);
|
||||
}
|
||||
}
|
||||
|
||||
bool supportsBacklightDuty() const override { return configuration->backlightDutyFunction != nullptr; }
|
||||
|
||||
};
|
||||
|
||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
||||
@@ -0,0 +1,300 @@
|
||||
#include "esp_lcd_st7305.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_lcd_types.h"
|
||||
#include <stdlib.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_lcd_panel_interface.h"
|
||||
#include "esp_lcd_panel_io.h"
|
||||
#include "esp_lcd_panel_vendor.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "esp_lcd_panel_commands.h"
|
||||
#include "driver/gpio.h"
|
||||
#include <string.h>
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "st7305";
|
||||
|
||||
static esp_err_t panel_st7305_del(esp_lcd_panel_t *panel);
|
||||
static esp_err_t panel_st7305_reset(esp_lcd_panel_t *panel);
|
||||
static esp_err_t panel_st7305_init(esp_lcd_panel_t *panel);
|
||||
static esp_err_t panel_st7305_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
|
||||
static esp_err_t panel_st7305_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
|
||||
static esp_err_t panel_st7305_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
|
||||
static esp_err_t panel_st7305_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
|
||||
static esp_err_t panel_st7305_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap);
|
||||
static esp_err_t panel_st7305_disp_on_off(esp_lcd_panel_t *panel, bool off);
|
||||
|
||||
typedef struct {
|
||||
esp_lcd_panel_t base;
|
||||
esp_lcd_panel_io_handle_t io;
|
||||
int reset_gpio_num;
|
||||
bool reset_level;
|
||||
int x_gap;
|
||||
int y_gap;
|
||||
int width;
|
||||
int height;
|
||||
uint8_t rotation;
|
||||
uint8_t madctl_val;
|
||||
const st7305_lcd_init_cmd_t *init_cmds;
|
||||
uint16_t init_cmds_size;
|
||||
uint8_t *draw_buffer;
|
||||
} st7305_panel_t;
|
||||
|
||||
static const st7305_lcd_init_cmd_t st7305_init_cmds[] = {
|
||||
{0xD6, (uint8_t[]){0x17, 0x02}, 2, 0}, // NVM Load Control
|
||||
{0xD1, (uint8_t[]){0x01}, 1, 0}, // Booster Enable
|
||||
{0xC0, (uint8_t[]){0x11, 0x04}, 2, 0}, // Gate Voltage Setting
|
||||
{0xC1, (uint8_t[]){0x41, 0x41, 0x41, 0x41}, 4, 0}, // VSHP Setting
|
||||
{0xC2, (uint8_t[]){0x19, 0x19, 0x19, 0x19}, 4, 0}, // VSLP Setting
|
||||
{0xC4, (uint8_t[]){0x41, 0x41, 0x41, 0x41}, 4, 0}, // VSHN Setting
|
||||
{0xC5, (uint8_t[]){0x19, 0x13, 0x19, 0x19}, 4, 0}, // VSLN Setting
|
||||
{0xD8, (uint8_t[]){0xA6, 0xE9}, 2, 0}, // OSC Setting
|
||||
{0xB2, (uint8_t[]){0x05}, 1, 0}, // Frame Rate Control
|
||||
{0xB3, (uint8_t[]){0xE5, 0xF6, 0x05, 0x46, 0x77, 0x77, 0x77, 0x77, 0x76, 0x45}, 10, 0}, // Gate EQ HPM
|
||||
{0xB4, (uint8_t[]){0x05, 0x46, 0x77, 0x77, 0x77, 0x77, 0x76, 0x45}, 8, 0}, // Gate EQ LPM
|
||||
{0x62, (uint8_t[]){0x32, 0x03, 0x1F}, 3, 0}, // Gate Timing Control
|
||||
{0xB7, (uint8_t[]){0x13}, 1, 0}, // Source EQ Enable
|
||||
{0xB0, (uint8_t[]){0x64}, 1, 0}, // Gate Line Setting: 300 line (0x64 = 100 * 3)
|
||||
{0x11, NULL, 0, 200}, // Sleep out
|
||||
{0xC9, (uint8_t[]){0x00}, 1, 0}, // Source Voltage Select
|
||||
{0x36, (uint8_t[]){0x48}, 1, 0}, // Memory Data Access Control (MX=1, DO=1)
|
||||
{0x3A, (uint8_t[]){0x11}, 1, 0}, // Data Format Select: 1bpp
|
||||
{0xB9, (uint8_t[]){0x20}, 1, 0}, // Gamma Mode Setting
|
||||
{0xB8, (uint8_t[]){0x29}, 1, 0}, // Panel Setting
|
||||
{0x21, NULL, 0, 0}, // Display Inversion On
|
||||
{0x2A, (uint8_t[]){0x12, 0x2A}, 2, 0}, // Column Address Setting
|
||||
{0x2B, (uint8_t[]){0x00, 0xC7}, 2, 0}, // Row Address Setting
|
||||
{0x35, (uint8_t[]){0x00}, 1, 0}, // TE Line
|
||||
{0xD0, (uint8_t[]){0xFF}, 1, 0}, // Auto power down ON
|
||||
{0x38, NULL, 0, 0}, // High Power Mode ON
|
||||
{0x29, NULL, 0, 100}, // Display ON
|
||||
};
|
||||
|
||||
esp_err_t esp_lcd_new_panel_st7305(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
|
||||
esp_lcd_panel_handle_t *ret_panel)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
st7305_panel_t *st7305 = NULL;
|
||||
gpio_config_t io_conf = { 0 };
|
||||
|
||||
ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
st7305 = (st7305_panel_t *)calloc(1, sizeof(st7305_panel_t));
|
||||
ESP_GOTO_ON_FALSE(st7305, ESP_ERR_NO_MEM, err, TAG, "no mem for st7305 panel");
|
||||
|
||||
if (panel_dev_config->reset_gpio_num >= 0) {
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num;
|
||||
ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
|
||||
}
|
||||
st7305->width = ST7305_WIDTH;
|
||||
st7305->height = ST7305_HEIGHT;
|
||||
st7305->madctl_val = 0x48; // MX=1, DO=1
|
||||
st7305->rotation = 0;
|
||||
|
||||
st7305->io = io;
|
||||
st7305->reset_gpio_num = panel_dev_config->reset_gpio_num;
|
||||
st7305->reset_level = panel_dev_config->flags.reset_active_high;
|
||||
if (panel_dev_config->vendor_config) {
|
||||
st7305->init_cmds = ((st7305_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds;
|
||||
st7305->init_cmds_size = ((st7305_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds_size;
|
||||
} else {
|
||||
st7305->init_cmds = st7305_init_cmds;
|
||||
st7305->init_cmds_size = sizeof(st7305_init_cmds) / sizeof(st7305_lcd_init_cmd_t);
|
||||
}
|
||||
|
||||
st7305->draw_buffer = heap_caps_malloc(15000, MALLOC_CAP_DMA);
|
||||
ESP_GOTO_ON_FALSE(st7305->draw_buffer, ESP_ERR_NO_MEM, err, TAG, "no mem for st7305 draw buffer");
|
||||
memset(st7305->draw_buffer, 0, 15000);
|
||||
st7305->base.del = panel_st7305_del;
|
||||
st7305->base.reset = panel_st7305_reset;
|
||||
st7305->base.init = panel_st7305_init;
|
||||
st7305->base.draw_bitmap = panel_st7305_draw_bitmap;
|
||||
st7305->base.invert_color = panel_st7305_invert_color;
|
||||
st7305->base.set_gap = panel_st7305_set_gap;
|
||||
st7305->base.mirror = panel_st7305_mirror;
|
||||
st7305->base.swap_xy = panel_st7305_swap_xy;
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
st7305->base.disp_off = panel_st7305_disp_on_off;
|
||||
#else
|
||||
st7305->base.disp_on_off = panel_st7305_disp_on_off;
|
||||
#endif
|
||||
*ret_panel = &(st7305->base);
|
||||
ESP_LOGD(TAG, "new st7305 panel @%p", st7305);
|
||||
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (st7305) {
|
||||
if (panel_dev_config->reset_gpio_num >= 0) {
|
||||
gpio_reset_pin(panel_dev_config->reset_gpio_num);
|
||||
}
|
||||
if (st7305->draw_buffer) {
|
||||
free(st7305->draw_buffer);
|
||||
}
|
||||
free(st7305);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7305_del(esp_lcd_panel_t *panel)
|
||||
{
|
||||
st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base);
|
||||
if (st7305->reset_gpio_num >= 0) {
|
||||
gpio_reset_pin(st7305->reset_gpio_num);
|
||||
}
|
||||
if (st7305->draw_buffer) {
|
||||
free(st7305->draw_buffer);
|
||||
}
|
||||
free(st7305);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7305_reset(esp_lcd_panel_t *panel)
|
||||
{
|
||||
st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base);
|
||||
if (st7305->reset_gpio_num >= 0) {
|
||||
gpio_set_level(st7305->reset_gpio_num, !st7305->reset_level);
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
gpio_set_level(st7305->reset_gpio_num, st7305->reset_level);
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
gpio_set_level(st7305->reset_gpio_num, !st7305->reset_level);
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7305_init(esp_lcd_panel_t *panel)
|
||||
{
|
||||
st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base);
|
||||
esp_lcd_panel_io_handle_t io = st7305->io;
|
||||
|
||||
for (size_t i = 0; i < st7305->init_cmds_size; i++) {
|
||||
if (st7305->init_cmds[i].data_bytes > 0) {
|
||||
esp_lcd_panel_io_tx_param(io, st7305->init_cmds[i].cmd,
|
||||
st7305->init_cmds[i].data,
|
||||
st7305->init_cmds[i].data_bytes);
|
||||
} else {
|
||||
esp_lcd_panel_io_tx_param(io, st7305->init_cmds[i].cmd, NULL, 0);
|
||||
}
|
||||
if (st7305->init_cmds[i].delay_ms > 0) {
|
||||
vTaskDelay(pdMS_TO_TICKS(st7305->init_cmds[i].delay_ms));
|
||||
}
|
||||
}
|
||||
|
||||
// Explicitly clear display RAM to white
|
||||
if (st7305->draw_buffer) {
|
||||
memset(st7305->draw_buffer, 0xFF, 15000); // 0xFF is White
|
||||
uint8_t caset[] = {0x12, 0x2A};
|
||||
uint8_t raset[] = {0x00, 0xC7};
|
||||
esp_lcd_panel_io_tx_param(io, ST7305_CMD_CASET, caset, sizeof(caset));
|
||||
esp_lcd_panel_io_tx_param(io, ST7305_CMD_RASET, raset, sizeof(raset));
|
||||
esp_lcd_panel_io_tx_color(io, ST7305_CMD_RAMWR, st7305->draw_buffer, 15000);
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7305_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
|
||||
{
|
||||
st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base);
|
||||
esp_lcd_panel_io_handle_t io = st7305->io;
|
||||
|
||||
const uint8_t *src = (const uint8_t *)color_data;
|
||||
|
||||
// Convert from vertical-page format (SSD1306-style) to ST7305 landscape 2x4 block format
|
||||
for (int y = 0; y < 300; y++) {
|
||||
int inv_y = 299 - y;
|
||||
int block_y = inv_y >> 2;
|
||||
int local_y = inv_y & 3;
|
||||
|
||||
int src_y_div_8 = y >> 3;
|
||||
int src_y_mod_8 = y & 7;
|
||||
uint8_t src_bit_mask = 1 << src_y_mod_8;
|
||||
|
||||
for (int x = 0; x < 400; x++) {
|
||||
int byte_x = x >> 1;
|
||||
int local_x = x & 1;
|
||||
|
||||
int dest_byte_idx = byte_x * 75 + block_y;
|
||||
int dest_bit_pos = 7 - ((local_y << 1) | local_x);
|
||||
|
||||
int src_byte_idx = 400 * src_y_div_8 + x;
|
||||
|
||||
// Read standard vertical page byte bit
|
||||
bool is_pixel_set = (src[src_byte_idx] & src_bit_mask) != 0;
|
||||
|
||||
// Invert the pixel logic if required, standard:
|
||||
// esp_lvgl_port monochrome transform clears the bit (0) for light/chroma colors
|
||||
// and sets the bit (1) for dark/black.
|
||||
// In ST7305 display RAM, White/Light is 1, Black/Dark is 0.
|
||||
// So we write: 1 (White) if is_pixel_set is false (light), and 0 (Black) if is_pixel_set is true (dark).
|
||||
if (!is_pixel_set) {
|
||||
st7305->draw_buffer[dest_byte_idx] |= (1 << dest_bit_pos);
|
||||
} else {
|
||||
st7305->draw_buffer[dest_byte_idx] &= ~(1 << dest_bit_pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t caset[] = {0x12, 0x2A};
|
||||
uint8_t raset[] = {0x00, 0xC7};
|
||||
|
||||
esp_lcd_panel_io_tx_param(io, ST7305_CMD_CASET, caset, sizeof(caset));
|
||||
esp_lcd_panel_io_tx_param(io, ST7305_CMD_RASET, raset, sizeof(raset));
|
||||
esp_lcd_panel_io_tx_color(io, ST7305_CMD_RAMWR, st7305->draw_buffer, 15000);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7305_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
|
||||
{
|
||||
st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base);
|
||||
return esp_lcd_panel_io_tx_param(st7305->io, invert_color_data ? ST7305_CMD_INVON : ST7305_CMD_INVOFF, NULL, 0);
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7305_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
|
||||
{
|
||||
st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base);
|
||||
esp_lcd_panel_io_handle_t io = st7305->io;
|
||||
if (mirror_x) {
|
||||
st7305->madctl_val |= ST7305_MADCTL_MX;
|
||||
} else {
|
||||
st7305->madctl_val &= ~ST7305_MADCTL_MX;
|
||||
}
|
||||
if (mirror_y) {
|
||||
st7305->madctl_val |= ST7305_MADCTL_MY;
|
||||
} else {
|
||||
st7305->madctl_val &= ~ST7305_MADCTL_MY;
|
||||
}
|
||||
return esp_lcd_panel_io_tx_param(io, ST7305_CMD_MADCTL, &st7305->madctl_val, 1);
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7305_swap_xy(esp_lcd_panel_t *panel, bool swap_axes)
|
||||
{
|
||||
st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base);
|
||||
esp_lcd_panel_io_handle_t io = st7305->io;
|
||||
if (swap_axes) {
|
||||
st7305->madctl_val |= ST7305_MADCTL_MV;
|
||||
} else {
|
||||
st7305->madctl_val &= ~ST7305_MADCTL_MV;
|
||||
}
|
||||
return esp_lcd_panel_io_tx_param(io, ST7305_CMD_MADCTL, (uint8_t[]) { st7305->madctl_val }, 1);
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7305_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap)
|
||||
{
|
||||
st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base);
|
||||
st7305->x_gap = x_gap;
|
||||
st7305->y_gap = y_gap;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_st7305_disp_on_off(esp_lcd_panel_t *panel, bool off)
|
||||
{
|
||||
st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base);
|
||||
return esp_lcd_panel_io_tx_param(st7305->io, off ? ST7305_CMD_DISPOFF : ST7305_CMD_DISPON, NULL, 0);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_lcd_types.h"
|
||||
#include "esp_lcd_panel_vendor.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
int cmd; /*<! The specific LCD command */
|
||||
const void *data; /*<! Buffer that holds the command specific data */
|
||||
size_t data_bytes; /*<! Size of `data` in memory, in bytes */
|
||||
unsigned int delay_ms; /*<! Delay in milliseconds after this command */
|
||||
} st7305_lcd_init_cmd_t;
|
||||
|
||||
typedef struct {
|
||||
const st7305_lcd_init_cmd_t *init_cmds;
|
||||
uint16_t init_cmds_size;
|
||||
} st7305_vendor_config_t;
|
||||
|
||||
#define ST7305_WIDTH 400
|
||||
#define ST7305_HEIGHT 300
|
||||
|
||||
#define ST7305_CMD_NOP 0x00
|
||||
#define ST7305_CMD_SWRESET 0x01
|
||||
#define ST7305_CMD_SLPOUT 0x11
|
||||
#define ST7305_CMD_NORON 0x13
|
||||
#define ST7305_CMD_INVOFF 0x20
|
||||
#define ST7305_CMD_INVON 0x21
|
||||
#define ST7305_CMD_DISPOFF 0x28
|
||||
#define ST7305_CMD_DISPON 0x29
|
||||
#define ST7305_CMD_CASET 0x2A
|
||||
#define ST7305_CMD_RASET 0x2B
|
||||
#define ST7305_CMD_RAMWR 0x2C
|
||||
#define ST7305_CMD_MADCTL 0x36
|
||||
|
||||
#define ST7305_MADCTL_MY 0x80
|
||||
#define ST7305_MADCTL_MX 0x40
|
||||
#define ST7305_MADCTL_MV 0x20
|
||||
#define ST7305_MADCTL_ML 0x10
|
||||
|
||||
esp_err_t esp_lcd_new_panel_st7305(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -199,52 +199,12 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
|
||||
DEFINE_MODULE_SYMBOL(lv_buttonmatrix_set_button_width),
|
||||
DEFINE_MODULE_SYMBOL(lv_buttonmatrix_set_selected_button),
|
||||
DEFINE_MODULE_SYMBOL(lv_buttonmatrix_clear_button_ctrl),
|
||||
// lv_canvas - full API for emulator framebuffer use-cases (GameBoy 160x144)
|
||||
// lv_canvas
|
||||
DEFINE_MODULE_SYMBOL(lv_canvas_create),
|
||||
DEFINE_MODULE_SYMBOL(lv_canvas_set_buffer),
|
||||
DEFINE_MODULE_SYMBOL(lv_canvas_set_draw_buf),
|
||||
DEFINE_MODULE_SYMBOL(lv_canvas_set_px),
|
||||
DEFINE_MODULE_SYMBOL(lv_canvas_set_palette),
|
||||
DEFINE_MODULE_SYMBOL(lv_canvas_get_draw_buf),
|
||||
DEFINE_MODULE_SYMBOL(lv_canvas_get_px),
|
||||
DEFINE_MODULE_SYMBOL(lv_canvas_get_image),
|
||||
DEFINE_MODULE_SYMBOL(lv_canvas_get_buf),
|
||||
DEFINE_MODULE_SYMBOL(lv_canvas_copy_buf),
|
||||
DEFINE_MODULE_SYMBOL(lv_canvas_fill_bg),
|
||||
DEFINE_MODULE_SYMBOL(lv_canvas_init_layer),
|
||||
DEFINE_MODULE_SYMBOL(lv_canvas_finish_layer),
|
||||
DEFINE_MODULE_SYMBOL(lv_canvas_buf_size),
|
||||
// lv_draw_buf - LVGL9 buffers (was lv_img_buf)
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_buf_create),
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_buf_destroy),
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_buf_init),
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_buf_dup),
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_buf_copy),
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_buf_goto_xy),
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_buf_clear),
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_buf_width_to_stride),
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_buf_align),
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_buf_set_palette),
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_buf_from_image),
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_buf_to_image),
|
||||
// LVGL cache invalidation - critical for raw framebuffer apps (GB emulator) where buffer mutated directly
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_buf_invalidate_cache),
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_buf_flush_cache),
|
||||
DEFINE_MODULE_SYMBOL(lv_image_cache_drop),
|
||||
// lv_draw layer helpers
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_layer_create),
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_layer_alloc_buf),
|
||||
// lv_image transform/scale - critical for GB 2x/3x scaling (image as canvas)
|
||||
DEFINE_MODULE_SYMBOL(lv_image_set_scale),
|
||||
DEFINE_MODULE_SYMBOL(lv_image_set_scale_x),
|
||||
DEFINE_MODULE_SYMBOL(lv_image_set_scale_y),
|
||||
DEFINE_MODULE_SYMBOL(lv_image_set_rotation),
|
||||
DEFINE_MODULE_SYMBOL(lv_image_set_pivot),
|
||||
DEFINE_MODULE_SYMBOL(lv_image_set_offset_x),
|
||||
DEFINE_MODULE_SYMBOL(lv_image_set_offset_y),
|
||||
// generic obj transform styles (fallback scaling path) - pivot_x/y already exported above, only add scale
|
||||
DEFINE_MODULE_SYMBOL(lv_obj_set_style_transform_scale_x),
|
||||
DEFINE_MODULE_SYMBOL(lv_obj_set_style_transform_scale_y),
|
||||
DEFINE_MODULE_SYMBOL(lv_canvas_set_draw_buf),
|
||||
DEFINE_MODULE_SYMBOL(lv_canvas_set_buffer),
|
||||
DEFINE_MODULE_SYMBOL(lv_canvas_set_px),
|
||||
// lv_label
|
||||
DEFINE_MODULE_SYMBOL(lv_label_create),
|
||||
DEFINE_MODULE_SYMBOL(lv_label_cut_text),
|
||||
@@ -471,7 +431,7 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_task_get_draw_dsc),
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_task_get_label_dsc),
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_task_get_fill_dsc),
|
||||
// lv_draw_buf_create moved to canvas block (duplicate removed)
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_buf_create),
|
||||
// lv_image
|
||||
DEFINE_MODULE_SYMBOL(lv_image_create),
|
||||
DEFINE_MODULE_SYMBOL(lv_image_set_src),
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace tt::network::mdns {
|
||||
|
||||
/**
|
||||
* A discovered mDNS service instance.
|
||||
* Example: instanceName="kidsOS-AB12", serviceType="_http", proto="_tcp",
|
||||
* hostname="kidsOS-AB12", port=80, addresses=["192.168.1.42"]
|
||||
*/
|
||||
struct Service {
|
||||
std::string instanceName; ///< Instance name (e.g. "ESP32-WebServer")
|
||||
std::string serviceType; ///< Service type (e.g. "_http", "_tactility")
|
||||
std::string proto; ///< Protocol (e.g. "_tcp", "_udp")
|
||||
std::string hostname; ///< Hostname without .local (e.g. "kidsOS-AB12")
|
||||
uint16_t port = 0; ///< Service port
|
||||
std::vector<std::string> addresses; ///< All resolved IP addresses (v4 and v6)
|
||||
std::string primaryAddress; ///< First IPv4 address, or first address if no v4
|
||||
uint32_t ttl = 0; ///< Time to live
|
||||
std::map<std::string, std::string> txtRecords; ///< TXT key-value pairs
|
||||
};
|
||||
|
||||
/**
|
||||
* @return true if mDNS subsystem is initialized and ready for queries.
|
||||
* On simulator/POSIX it returns false until a platform implementation is present.
|
||||
*/
|
||||
bool isAvailable();
|
||||
|
||||
/**
|
||||
* Browse for mDNS services.
|
||||
*
|
||||
* This wraps `mdns_query_ptr(serviceType, proto, timeout, maxResults, ...)`.
|
||||
* It blocks for up to timeoutMs while collecting results.
|
||||
*
|
||||
* @param serviceType e.g. "_http", "_tactility", "_arduino"
|
||||
* @param proto e.g. "_tcp", "_udp" (include leading underscore)
|
||||
* @param timeoutMs how long to wait for answers (e.g. 3000)
|
||||
* @param maxResults maximum number of results to collect (e.g. 20)
|
||||
* @param outResults filled with discovered services
|
||||
* @return true on success (may still be 0 results), false if mDNS not running or error
|
||||
*/
|
||||
bool browse(const std::string& serviceType, const std::string& proto, uint32_t timeoutMs, size_t maxResults, std::vector<Service>& outResults);
|
||||
|
||||
/**
|
||||
* Browse with sensible defaults: 3s timeout, 20 max results.
|
||||
*/
|
||||
inline bool browse(const std::string& serviceType, const std::string& proto, std::vector<Service>& outResults) {
|
||||
return browse(serviceType, proto, 3000, 20, outResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a hostname (e.g. "kidsOS-AB12" or "kidsOS-AB12.local") to an IPv4 address string.
|
||||
*
|
||||
* @param hostname hostname to resolve, ".local" suffix is optional and stripped
|
||||
* @param timeoutMs time to wait
|
||||
* @param outIp resolved IP (e.g. "192.168.1.42")
|
||||
* @return true if resolved
|
||||
*/
|
||||
bool resolveHostname(const std::string& hostname, uint32_t timeoutMs, std::string& outIp);
|
||||
|
||||
/**
|
||||
* Resolve with 2s default timeout.
|
||||
*/
|
||||
inline bool resolveHostname(const std::string& hostname, std::string& outIp) {
|
||||
return resolveHostname(hostname, 2000, outIp);
|
||||
}
|
||||
|
||||
} // namespace tt::network::mdns
|
||||
@@ -14,9 +14,6 @@ bool isValidAppVersionName(const std::string& version);
|
||||
bool isValidAppVersionCode(const std::string& version);
|
||||
bool isValidName(const std::string& name);
|
||||
|
||||
/** Parses a comma-separated flags string (e.g. "HideStatusBar,Hidden") into appFlags bitmask. */
|
||||
uint16_t parseAppFlagsString(const std::string& raw);
|
||||
|
||||
/** Parses a V1 (sectioned INI, e.g. "[app]versionName=...") manifest map. */
|
||||
bool parseManifestV1(const std::map<std::string, std::string>& map, AppManifest& manifest);
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace tt::app::files {
|
||||
|
||||
bool isSupportedAppFile(const std::string& filename);
|
||||
bool isSupportedImageFile(const std::string& filename);
|
||||
bool isSupportedTextFile(const std::string& filename);
|
||||
bool isSupportedAudioFile(const std::string& filename);
|
||||
bool isSupportedGameBoyFile(const std::string& filename);
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -56,34 +56,6 @@ bool isValidName(const std::string& name) {
|
||||
});
|
||||
}
|
||||
|
||||
uint16_t parseAppFlagsString(const std::string& raw) {
|
||||
uint16_t flags = AppManifest::Flags::None;
|
||||
if (raw.empty()) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
auto parts = string::split(raw, ",");
|
||||
for (auto& part : parts) {
|
||||
std::string trimmed = string::trim(part, " \t\r\n");
|
||||
if (trimmed.empty()) {
|
||||
continue;
|
||||
}
|
||||
std::string lower = string::lowercase(trimmed);
|
||||
|
||||
if (lower == "hidestatusbar" || lower == "hide_statusbar" || lower == "hide-statusbar" || lower == "hide_status_bar") {
|
||||
flags |= AppManifest::Flags::HideStatusBar;
|
||||
} else if (lower == "hidden") {
|
||||
flags |= AppManifest::Flags::Hidden;
|
||||
} else if (lower == "none" || lower == "0" || lower == "") {
|
||||
// keep as none, no additional flag
|
||||
} else {
|
||||
LOG_W(TAG, "Unknown app flag \"%s\" - ignoring", trimmed.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
/** The V1 format's first line is always the literal "[manifest]" section header; V2 files are flat from the first line onward. */
|
||||
static bool detectIsV1Format(const std::string& filePath) {
|
||||
std::string first_line;
|
||||
|
||||
@@ -71,12 +71,6 @@ bool parseManifestV1(const std::map<std::string, std::string>& map, AppManifest&
|
||||
return false;
|
||||
}
|
||||
|
||||
// Optional: [app]flags - e.g. "HideStatusBar" or "HideStatusBar,Hidden"
|
||||
auto flags_it = map.find("[app]flags");
|
||||
if (flags_it != map.end()) {
|
||||
manifest.appFlags = parseAppFlagsString(flags_it->second);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,12 +71,6 @@ bool parseManifestV2(const std::map<std::string, std::string>& map, AppManifest&
|
||||
return false;
|
||||
}
|
||||
|
||||
// Optional: app.flags - e.g. "HideStatusBar" or "HideStatusBar,Hidden"
|
||||
auto flags_it = map.find("app.flags");
|
||||
if (flags_it != map.end()) {
|
||||
manifest.appFlags = parseAppFlagsString(flags_it->second);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
#include <Tactility/StringUtils.h>
|
||||
#include <Tactility/TactilityCore.h>
|
||||
|
||||
namespace tt::app::files {
|
||||
|
||||
constexpr auto* TAG = "Files";
|
||||
|
||||
bool isSupportedAppFile(const std::string& filename) {
|
||||
return filename.ends_with(".app");
|
||||
}
|
||||
|
||||
bool isSupportedImageFile(const std::string& filename) {
|
||||
// Currently only the PNG library is built into Tactility
|
||||
return string::lowercase(filename).ends_with(".png");
|
||||
}
|
||||
|
||||
bool isSupportedTextFile(const std::string& filename) {
|
||||
std::string l = string::lowercase(filename);
|
||||
return l.ends_with(".txt") || l.ends_with(".ini") || l.ends_with(".json") || l.ends_with(".yaml") || l.ends_with(".yml") ||
|
||||
l.ends_with(".lua") || l.ends_with(".js") || l.ends_with(".properties");
|
||||
std::string filename_lower = string::lowercase(filename);
|
||||
return filename_lower.ends_with(".txt") ||
|
||||
filename_lower.ends_with(".ini") ||
|
||||
filename_lower.ends_with(".json") ||
|
||||
filename_lower.ends_with(".yaml") ||
|
||||
filename_lower.ends_with(".yml") ||
|
||||
filename_lower.ends_with(".lua") ||
|
||||
filename_lower.ends_with(".js") ||
|
||||
filename_lower.ends_with(".properties");
|
||||
}
|
||||
bool isSupportedAudioFile(const std::string& filename) {
|
||||
std::string l = string::lowercase(filename);
|
||||
return l.ends_with(".mp3") || l.ends_with(".wav") || l.ends_with(".ogg") || l.ends_with(".flac");
|
||||
}
|
||||
bool isSupportedGameBoyFile(const std::string& filename) {
|
||||
std::string l = string::lowercase(filename);
|
||||
return l.ends_with(".gb") || l.ends_with(".gbc") || l.ends_with(".sgb");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
} // namespace tt::app::filebrowser
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include <Tactility/app/files/SupportedFiles.h>
|
||||
#include <Tactility/Bundle.h>
|
||||
#include <Tactility/app/files/View.h>
|
||||
|
||||
#include <Tactility/Platform.h>
|
||||
@@ -229,26 +228,9 @@ void View::viewFile(const std::string& path, const std::string& filename) {
|
||||
if (kernel::getPlatform() == kernel::PlatformEsp) {
|
||||
notes::start(processed_filepath);
|
||||
} else {
|
||||
// Remove forward slash, because we need a relative path
|
||||
notes::start(processed_filepath.substr(1));
|
||||
}
|
||||
} else if (isSupportedAudioFile(filename)) {
|
||||
#ifdef ESP_PLATFORM
|
||||
auto bundle = std::make_shared<Bundle>();
|
||||
bundle->putString("file", processed_filepath);
|
||||
auto loader = service::loader::findLoaderService();
|
||||
if (loader) {
|
||||
loader->start("one.tactility.mp3player", bundle);
|
||||
}
|
||||
#endif
|
||||
} else if (isSupportedGameBoyFile(filename)) {
|
||||
#ifdef ESP_PLATFORM
|
||||
auto bundle = std::make_shared<Bundle>();
|
||||
bundle->putString("file", processed_filepath);
|
||||
auto loader = service::loader::findLoaderService();
|
||||
if (loader) {
|
||||
loader->start("one.tactility.gameboy", bundle);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
LOG_W(TAG, "Opening files of this type is not supported");
|
||||
}
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
#include <Tactility/network/Mdns.h>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
#include <tactility/log.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <esp_netif.h>
|
||||
#include <lwip/ip4_addr.h>
|
||||
#if CONFIG_LWIP_IPV6
|
||||
#include <lwip/ip6_addr.h>
|
||||
#endif
|
||||
#include <mdns.h>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
namespace tt::network::mdns {
|
||||
|
||||
constexpr auto* TAG = "Mdns";
|
||||
|
||||
static std::string ipAddrToString(const esp_ip_addr_t& ip) {
|
||||
char buf[64];
|
||||
if (ip.type == ESP_IPADDR_TYPE_V4) {
|
||||
esp_ip4addr_ntoa(&ip.u_addr.ip4, buf, sizeof(buf));
|
||||
} else {
|
||||
#if CONFIG_LWIP_IPV6
|
||||
ip6addr_ntoa_r(reinterpret_cast<const ip6_addr_t*>(&ip.u_addr.ip6), buf, sizeof(buf));
|
||||
#else
|
||||
snprintf(buf, sizeof(buf), "IPv6(not enabled)");
|
||||
#endif
|
||||
}
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
static std::string normalizeHostname(std::string host) {
|
||||
const std::string suffix = ".local";
|
||||
if (host.size() > suffix.size() && host.compare(host.size() - suffix.size(), suffix.size(), suffix) == 0) {
|
||||
host.erase(host.size() - suffix.size());
|
||||
}
|
||||
if (!host.empty() && host.back() == '.') {
|
||||
host.pop_back();
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
bool isAvailable() {
|
||||
char buf[64];
|
||||
return mdns_hostname_get(buf) == ESP_OK;
|
||||
}
|
||||
|
||||
static Service convertResult(const mdns_result_t* r) {
|
||||
Service s;
|
||||
if (r->instance_name) s.instanceName = r->instance_name;
|
||||
if (r->service_type) s.serviceType = r->service_type;
|
||||
if (r->proto) s.proto = r->proto;
|
||||
if (r->hostname) s.hostname = r->hostname;
|
||||
s.port = r->port;
|
||||
s.ttl = r->ttl;
|
||||
|
||||
for (size_t i = 0; i < r->txt_count; i++) {
|
||||
if (r->txt[i].key) {
|
||||
std::string key = r->txt[i].key;
|
||||
std::string value;
|
||||
if (r->txt[i].value && r->txt_value_len) {
|
||||
value = std::string(r->txt[i].value, r->txt_value_len[i]);
|
||||
} else if (r->txt[i].value) {
|
||||
value = r->txt[i].value;
|
||||
}
|
||||
s.txtRecords[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
std::string firstV4;
|
||||
for (mdns_ip_addr_t* a = r->addr; a != nullptr; a = a->next) {
|
||||
std::string ipStr = ipAddrToString(a->addr);
|
||||
if (!ipStr.empty()) {
|
||||
s.addresses.push_back(ipStr);
|
||||
if (firstV4.empty() && a->addr.type == ESP_IPADDR_TYPE_V4) {
|
||||
firstV4 = ipStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!firstV4.empty()) {
|
||||
s.primaryAddress = firstV4;
|
||||
} else if (!s.addresses.empty()) {
|
||||
s.primaryAddress = s.addresses.front();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
bool browse(const std::string& serviceType, const std::string& proto, uint32_t timeoutMs, size_t maxResults, std::vector<Service>& outResults) {
|
||||
if (serviceType.empty() || proto.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outResults.clear();
|
||||
|
||||
mdns_result_t* results = nullptr;
|
||||
esp_err_t err = mdns_query_ptr(serviceType.c_str(), proto.c_str(), timeoutMs, maxResults, &results);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
if (err == ESP_ERR_INVALID_STATE) {
|
||||
LOG_W(TAG, "browse: mDNS not running");
|
||||
} else {
|
||||
LOG_W(TAG, "browse %s.%s failed: %s", serviceType.c_str(), proto.c_str(), esp_err_to_name(err));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for (mdns_result_t* r = results; r != nullptr; r = r->next) {
|
||||
outResults.push_back(convertResult(r));
|
||||
}
|
||||
|
||||
mdns_query_results_free(results);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool resolveHostname(const std::string& hostname, uint32_t timeoutMs, std::string& outIp) {
|
||||
std::string normalized = normalizeHostname(hostname);
|
||||
if (normalized.empty()) return false;
|
||||
|
||||
outIp.clear();
|
||||
|
||||
esp_ip4_addr_t addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
|
||||
esp_err_t err = mdns_query_a(normalized.c_str(), timeoutMs, &addr);
|
||||
if (err == ESP_OK) {
|
||||
char buf[32];
|
||||
esp_ip4addr_ntoa(&addr, buf, sizeof(buf));
|
||||
outIp = buf;
|
||||
return true;
|
||||
}
|
||||
|
||||
#if CONFIG_LWIP_IPV6
|
||||
esp_ip6_addr_t addr6;
|
||||
memset(&addr6, 0, sizeof(addr6));
|
||||
err = mdns_query_aaaa(normalized.c_str(), timeoutMs, &addr6);
|
||||
if (err == ESP_OK) {
|
||||
char buf[64];
|
||||
ip6addr_ntoa_r(reinterpret_cast<const ip6_addr_t*>(&addr6), buf, sizeof(buf));
|
||||
outIp = buf;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace tt::network::mdns
|
||||
|
||||
#else // !ESP_PLATFORM — POSIX simulator stub
|
||||
|
||||
namespace tt::network::mdns {
|
||||
|
||||
bool isAvailable() { return false; }
|
||||
|
||||
bool browse(const std::string& serviceType, const std::string& proto, uint32_t timeoutMs, size_t maxResults, std::vector<Service>& outResults) {
|
||||
(void)serviceType; (void)proto; (void)timeoutMs; (void)maxResults;
|
||||
outResults.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool resolveHostname(const std::string& hostname, uint32_t timeoutMs, std::string& outIp) {
|
||||
(void)hostname; (void)timeoutMs;
|
||||
outIp.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace tt::network::mdns
|
||||
|
||||
#endif
|
||||
@@ -1,94 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* TactilityC mDNS bindings — usable from external ELF apps.
|
||||
*
|
||||
* Provides synchronous mDNS browsing (PTR queries) and hostname resolution
|
||||
* on top of the ESP-IDF mdns component. The API is intentionally C-only,
|
||||
* string-copy based, to avoid complex lifetime issues across ELF boundaries.
|
||||
*
|
||||
* Implementation lives in Tactility (tt::network::mdns) and is exported via
|
||||
* module symbols. TactilityC provides thin wrappers + symbol export.
|
||||
*
|
||||
* WiFi must be connected for mDNS queries to return results.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define TT_MDNS_MAX_RESULTS 32
|
||||
#define TT_MDNS_MAX_ADDRESSES 4
|
||||
#define TT_MDNS_MAX_TXT_RECORDS 8
|
||||
|
||||
#define TT_MDNS_HOSTNAME_LEN 64
|
||||
#define TT_MDNS_INSTANCE_LEN 64
|
||||
#define TT_MDNS_SERVICE_TYPE_LEN 32
|
||||
#define TT_MDNS_PROTO_LEN 16
|
||||
#define TT_MDNS_IP_LEN 64
|
||||
#define TT_MDNS_TXT_KEY_LEN 32
|
||||
#define TT_MDNS_TXT_VALUE_LEN 64
|
||||
|
||||
/** One TXT key-value pair */
|
||||
typedef struct {
|
||||
char key[TT_MDNS_TXT_KEY_LEN];
|
||||
char value[TT_MDNS_TXT_VALUE_LEN];
|
||||
} TtMdnsTxtRecord;
|
||||
|
||||
/** One discovered service (copy-based, no pointers into mdns_result_t). */
|
||||
typedef struct {
|
||||
char instanceName[TT_MDNS_INSTANCE_LEN]; ///< e.g. "kidsOS-AB12"
|
||||
char serviceType[TT_MDNS_SERVICE_TYPE_LEN];///< e.g. "_http"
|
||||
char proto[TT_MDNS_PROTO_LEN]; ///< e.g. "_tcp"
|
||||
char hostname[TT_MDNS_HOSTNAME_LEN]; ///< without .local
|
||||
uint16_t port; ///< service port
|
||||
char addresses[TT_MDNS_MAX_ADDRESSES][TT_MDNS_IP_LEN]; ///< resolved IPs
|
||||
uint8_t addressCount;
|
||||
char primaryAddress[TT_MDNS_IP_LEN]; ///< first IPv4 or first address
|
||||
uint32_t ttl;
|
||||
TtMdnsTxtRecord txtRecords[TT_MDNS_MAX_TXT_RECORDS];
|
||||
uint8_t txtCount;
|
||||
} TtMdnsService;
|
||||
|
||||
/** Result set returned by browse. */
|
||||
typedef struct {
|
||||
TtMdnsService services[TT_MDNS_MAX_RESULTS];
|
||||
uint8_t count;
|
||||
} TtMdnsBrowseResult;
|
||||
|
||||
/**
|
||||
* @return true if mDNS is initialized and running.
|
||||
*/
|
||||
bool tt_mdns_is_available();
|
||||
|
||||
/**
|
||||
* Browse for mDNS service instances.
|
||||
*
|
||||
* Blocks for up to timeoutMs.
|
||||
*
|
||||
* @param serviceType e.g. "_http", "_tactility" (with or without leading underscore both accepted, but conventional is with)
|
||||
* @param proto e.g. "_tcp", "_udp"
|
||||
* @param timeoutMs how long to wait (e.g. 3000ms). 0 = use default 3000ms.
|
||||
* @param maxResults clamp to TT_MDNS_MAX_RESULTS. 0 = default 20.
|
||||
* @param outResult filled with 0..maxResults services. Caller provides storage.
|
||||
* @return true on successful query (even 0 results is success). false if mDNS not running.
|
||||
*/
|
||||
bool tt_mdns_browse(const char* serviceType, const char* proto, uint32_t timeoutMs, size_t maxResults, TtMdnsBrowseResult* outResult);
|
||||
|
||||
/**
|
||||
* Resolve a hostname like "kidsOS-AB12" or "kidsOS-AB12.local" to an IPv4 address string.
|
||||
*
|
||||
* @param hostname hostname (".local" suffix optional)
|
||||
* @param timeoutMs wait time, 0 = 2000ms default
|
||||
* @param outIp buffer of at least TT_MDNS_IP_LEN, filled with IP string e.g. "192.168.1.42"
|
||||
* @return true if resolved.
|
||||
*/
|
||||
bool tt_mdns_resolve_hostname(const char* hostname, uint32_t timeoutMs, char* outIp);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "tt_hal_display.h"
|
||||
#include "tt_hal_touch.h"
|
||||
#include "tt_hal_uart.h"
|
||||
#include "tt_mdns.h"
|
||||
#include <tt_lock.h>
|
||||
#include "tt_lvgl.h"
|
||||
#include "tt_lvgl_keyboard.h"
|
||||
@@ -367,10 +366,6 @@ const esp_elfsym main_symbols[] {
|
||||
ESP_ELFSYM_EXPORT(tt_timezone_set_format_24_hour),
|
||||
// tt::lvgl
|
||||
ESP_ELFSYM_EXPORT(tt_lvgl_spinner_create),
|
||||
// mDNS
|
||||
ESP_ELFSYM_EXPORT(tt_mdns_is_available),
|
||||
ESP_ELFSYM_EXPORT(tt_mdns_browse),
|
||||
ESP_ELFSYM_EXPORT(tt_mdns_resolve_hostname),
|
||||
|
||||
// stdio.h
|
||||
ESP_ELFSYM_EXPORT(rename),
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
#include "tt_mdns.h"
|
||||
|
||||
#include <Tactility/network/Mdns.h>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
extern "C" {
|
||||
|
||||
bool tt_mdns_is_available() {
|
||||
return tt::network::mdns::isAvailable();
|
||||
}
|
||||
|
||||
bool tt_mdns_browse(const char* serviceType, const char* proto, uint32_t timeoutMs, size_t maxResults, TtMdnsBrowseResult* outResult) {
|
||||
if (outResult == nullptr) return false;
|
||||
if (serviceType == nullptr || proto == nullptr) return false;
|
||||
|
||||
memset(outResult, 0, sizeof(TtMdnsBrowseResult));
|
||||
|
||||
uint32_t effectiveTimeout = timeoutMs == 0 ? 3000 : timeoutMs;
|
||||
size_t effectiveMax = maxResults == 0 ? 20 : maxResults;
|
||||
effectiveMax = std::min<size_t>(effectiveMax, TT_MDNS_MAX_RESULTS);
|
||||
|
||||
std::vector<tt::network::mdns::Service> services;
|
||||
if (!tt::network::mdns::browse(serviceType, proto, effectiveTimeout, effectiveMax, services)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t toCopy = std::min(services.size(), static_cast<size_t>(TT_MDNS_MAX_RESULTS));
|
||||
for (size_t i = 0; i < toCopy; i++) {
|
||||
const auto& src = services[i];
|
||||
auto& dst = outResult->services[i];
|
||||
|
||||
strncpy(dst.instanceName, src.instanceName.c_str(), TT_MDNS_INSTANCE_LEN - 1);
|
||||
strncpy(dst.serviceType, src.serviceType.c_str(), TT_MDNS_SERVICE_TYPE_LEN - 1);
|
||||
strncpy(dst.proto, src.proto.c_str(), TT_MDNS_PROTO_LEN - 1);
|
||||
strncpy(dst.hostname, src.hostname.c_str(), TT_MDNS_HOSTNAME_LEN - 1);
|
||||
dst.port = src.port;
|
||||
dst.ttl = src.ttl;
|
||||
strncpy(dst.primaryAddress, src.primaryAddress.c_str(), TT_MDNS_IP_LEN - 1);
|
||||
|
||||
size_t addrCount = std::min(src.addresses.size(), static_cast<size_t>(TT_MDNS_MAX_ADDRESSES));
|
||||
dst.addressCount = static_cast<uint8_t>(addrCount);
|
||||
for (size_t a = 0; a < addrCount; a++) {
|
||||
strncpy(dst.addresses[a], src.addresses[a].c_str(), TT_MDNS_IP_LEN - 1);
|
||||
}
|
||||
|
||||
size_t txtCount = std::min(src.txtRecords.size(), static_cast<size_t>(TT_MDNS_MAX_TXT_RECORDS));
|
||||
dst.txtCount = static_cast<uint8_t>(txtCount);
|
||||
size_t idx = 0;
|
||||
for (const auto& kv : src.txtRecords) {
|
||||
if (idx >= txtCount) break;
|
||||
strncpy(dst.txtRecords[idx].key, kv.first.c_str(), TT_MDNS_TXT_KEY_LEN - 1);
|
||||
strncpy(dst.txtRecords[idx].value, kv.second.c_str(), TT_MDNS_TXT_VALUE_LEN - 1);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
outResult->count = static_cast<uint8_t>(toCopy);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tt_mdns_resolve_hostname(const char* hostname, uint32_t timeoutMs, char* outIp) {
|
||||
if (hostname == nullptr || outIp == nullptr) return false;
|
||||
memset(outIp, 0, TT_MDNS_IP_LEN);
|
||||
|
||||
uint32_t effectiveTimeout = timeoutMs == 0 ? 2000 : timeoutMs;
|
||||
std::string ip;
|
||||
if (!tt::network::mdns::resolveHostname(hostname, effectiveTimeout, ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
strncpy(outIp, ip.c_str(), TT_MDNS_IP_LEN - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -81,70 +81,3 @@ TEST_CASE("parseManifestV2() should fail when the app id is invalid") {
|
||||
AppManifest manifest;
|
||||
CHECK_EQ(parseManifestV2(properties, manifest), false);
|
||||
}
|
||||
|
||||
TEST_CASE("parseAppFlagsString() should parse various flag combinations") {
|
||||
CHECK_EQ(parseAppFlagsString(""), 0);
|
||||
CHECK_EQ(parseAppFlagsString("None"), 0);
|
||||
CHECK_EQ(parseAppFlagsString("HideStatusBar"), AppManifest::Flags::HideStatusBar);
|
||||
CHECK_EQ(parseAppFlagsString("hidden"), AppManifest::Flags::Hidden);
|
||||
CHECK_EQ(parseAppFlagsString("HideStatusBar,Hidden"), AppManifest::Flags::HideStatusBar | AppManifest::Flags::Hidden);
|
||||
CHECK_EQ(parseAppFlagsString(" hidestatusbar , hidden "), AppManifest::Flags::HideStatusBar | AppManifest::Flags::Hidden);
|
||||
CHECK_EQ(parseAppFlagsString("HideStatusBar, Hidden"), AppManifest::Flags::HideStatusBar | AppManifest::Flags::Hidden);
|
||||
}
|
||||
|
||||
TEST_CASE("parseManifest() should parse V1 flags for fullscreen") {
|
||||
TestFile file("test-manifest-v1-flags.properties");
|
||||
file.writeData(
|
||||
"[manifest]\n"
|
||||
"version=0.1\n"
|
||||
"[target]\n"
|
||||
"sdk=0.0.0\n"
|
||||
"platforms=esp32\n"
|
||||
"[app]\n"
|
||||
"id=one.tactility.sdktest\n"
|
||||
"versionName=0.1.0\n"
|
||||
"versionCode=1\n"
|
||||
"name=SDK Test\n"
|
||||
"flags=HideStatusBar\n"
|
||||
);
|
||||
|
||||
AppManifest manifest;
|
||||
CHECK_EQ(parseManifest(file.getPath(), manifest), true);
|
||||
CHECK_EQ(manifest.appFlags & AppManifest::Flags::HideStatusBar, AppManifest::Flags::HideStatusBar);
|
||||
}
|
||||
|
||||
TEST_CASE("parseManifest() should parse V2 flags for fullscreen") {
|
||||
TestFile file("test-manifest-v2-flags.properties");
|
||||
file.writeData(
|
||||
"manifest.version=0.1\n"
|
||||
"target.sdk=0.0.0\n"
|
||||
"target.platforms=esp32\n"
|
||||
"app.id=one.tactility.sdktest\n"
|
||||
"app.version.name=0.1.0\n"
|
||||
"app.version.code=1\n"
|
||||
"app.name=SDK Test\n"
|
||||
"app.flags=HideStatusBar\n"
|
||||
);
|
||||
|
||||
AppManifest manifest;
|
||||
CHECK_EQ(parseManifest(file.getPath(), manifest), true);
|
||||
CHECK_EQ(manifest.appFlags & AppManifest::Flags::HideStatusBar, AppManifest::Flags::HideStatusBar);
|
||||
}
|
||||
|
||||
TEST_CASE("parseManifest() should parse combined flags") {
|
||||
TestFile file("test-manifest-v2-flags2.properties");
|
||||
file.writeData(
|
||||
"manifest.version=0.1\n"
|
||||
"target.sdk=0.0.0\n"
|
||||
"target.platforms=esp32\n"
|
||||
"app.id=one.tactility.sdktest\n"
|
||||
"app.version.name=0.1.0\n"
|
||||
"app.version.code=1\n"
|
||||
"app.name=SDK Test\n"
|
||||
"app.flags=HideStatusBar,Hidden\n"
|
||||
);
|
||||
|
||||
AppManifest manifest;
|
||||
CHECK_EQ(parseManifest(file.getPath(), manifest), true);
|
||||
CHECK_EQ(manifest.appFlags, (AppManifest::Flags::HideStatusBar | AppManifest::Flags::Hidden));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user