mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-04-18 17:35:05 +00:00
Compare commits
No commits in common. "f12a52ebb96e34e2d557e8d55a2b3d347bdce047" and "d63a401cd50efd7a6a7310e3d1f4b3b33ee9603f" have entirely different histories.
f12a52ebb9
...
d63a401cd5
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@ -53,7 +53,6 @@ jobs:
|
|||||||
{ id: guition-jc8048w550c, arch: esp32s3 },
|
{ id: guition-jc8048w550c, arch: esp32s3 },
|
||||||
{ id: heltec-wifi-lora-32-v3, arch: esp32s3 },
|
{ id: heltec-wifi-lora-32-v3, arch: esp32s3 },
|
||||||
{ id: lilygo-tdeck, arch: esp32s3 },
|
{ id: lilygo-tdeck, arch: esp32s3 },
|
||||||
{ id: lilygo-thmi-s3, arch: esp32s3 },
|
|
||||||
{ id: lilygo-tdongle-s3, arch: esp32s3 },
|
{ id: lilygo-tdongle-s3, arch: esp32s3 },
|
||||||
{ id: lilygo-tdisplay-s3, arch: esp32s3 },
|
{ id: lilygo-tdisplay-s3, arch: esp32s3 },
|
||||||
{ id: lilygo-tlora-pager, arch: esp32s3 },
|
{ id: lilygo-tlora-pager, arch: esp32s3 },
|
||||||
|
|||||||
@ -18,7 +18,6 @@ static const auto LOGGER = tt::Logger("JcSdCard");
|
|||||||
// ESP32-P4 Slot 0 uses IO MUX (fixed pins, not manually configurable)
|
// ESP32-P4 Slot 0 uses IO MUX (fixed pins, not manually configurable)
|
||||||
// CLK=43, CMD=44, D0=39, D1=40, D2=41, D3=42 (defined automatically by hardware)
|
// CLK=43, CMD=44, D0=39, D1=40, D2=41, D3=42 (defined automatically by hardware)
|
||||||
|
|
||||||
// TODO: Migrate to "espressif,esp32-sdmmc" driver (needs LDO code porting)
|
|
||||||
class SdCardDeviceImpl final : public SdCardDevice {
|
class SdCardDeviceImpl final : public SdCardDevice {
|
||||||
|
|
||||||
class NoLock final : public tt::Lock {
|
class NoLock final : public tt::Lock {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
#include "devices/Display.h"
|
#include "devices/Display.h"
|
||||||
|
#include "devices/Sdcard.h"
|
||||||
|
|
||||||
#include <Tactility/hal/Configuration.h>
|
#include <Tactility/hal/Configuration.h>
|
||||||
|
|
||||||
@ -8,7 +9,8 @@ using namespace tt::hal;
|
|||||||
|
|
||||||
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
|
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
|
||||||
return {
|
return {
|
||||||
createDisplay()
|
createDisplay(),
|
||||||
|
createSdCard()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
22
Devices/lilygo-tdongle-s3/Source/devices/Sdcard.cpp
Normal file
22
Devices/lilygo-tdongle-s3/Source/devices/Sdcard.cpp
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#include "Sdcard.h"
|
||||||
|
|
||||||
|
#include <Tactility/hal/sdcard/SdmmcDevice.h>
|
||||||
|
#include <Tactility/lvgl/LvglSync.h>
|
||||||
|
|
||||||
|
using tt::hal::sdcard::SdmmcDevice;
|
||||||
|
|
||||||
|
std::shared_ptr<SdCardDevice> createSdCard() {
|
||||||
|
auto configuration = std::make_unique<SdmmcDevice::Config>(
|
||||||
|
GPIO_NUM_12,
|
||||||
|
GPIO_NUM_16,
|
||||||
|
GPIO_NUM_14,
|
||||||
|
GPIO_NUM_17,
|
||||||
|
GPIO_NUM_21,
|
||||||
|
GPIO_NUM_18,
|
||||||
|
SdCardDevice::MountBehaviour::AtBoot
|
||||||
|
);
|
||||||
|
|
||||||
|
return std::make_shared<SdmmcDevice>(
|
||||||
|
std::move(configuration)
|
||||||
|
);
|
||||||
|
}
|
||||||
7
Devices/lilygo-tdongle-s3/Source/devices/Sdcard.h
Normal file
7
Devices/lilygo-tdongle-s3/Source/devices/Sdcard.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Tactility/hal/sdcard/SdCardDevice.h"
|
||||||
|
|
||||||
|
using tt::hal::sdcard::SdCardDevice;
|
||||||
|
|
||||||
|
std::shared_ptr<SdCardDevice> createSdCard();
|
||||||
@ -22,4 +22,3 @@ dpi=186
|
|||||||
[lvgl]
|
[lvgl]
|
||||||
colorDepth=16
|
colorDepth=16
|
||||||
uiDensity=compact
|
uiDensity=compact
|
||||||
fontSize=10
|
|
||||||
|
|||||||
@ -32,14 +32,15 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
sdmmc0 {
|
sdmmc0 {
|
||||||
compatible = "espressif,esp32-sdmmc";
|
compatible = "espressif,sdmmc";
|
||||||
pin-clk = <&gpio0 12 GPIO_FLAG_NONE>;
|
pin-clk = <&gpio 12 GPIO_FLAG_NONE>;
|
||||||
pin-cmd = <&gpio0 16 GPIO_FLAG_NONE>;
|
pin-cmd = <&gpio 16 GPIO_FLAG_NONE>;
|
||||||
pin-d0 = <&gpio0 14 GPIO_FLAG_NONE>;
|
pin-d0 = <&gpio 14 GPIO_FLAG_NONE>;
|
||||||
pin-d1 = <&gpio0 17 GPIO_FLAG_NONE>;
|
pin-d1 = <&gpio 17 GPIO_FLAG_NONE>;
|
||||||
pin-d2 = <&gpio0 21 GPIO_FLAG_NONE>;
|
pin-d2 = <&gpio 21 GPIO_FLAG_NONE>;
|
||||||
pin-d3 = <&gpio0 18 GPIO_FLAG_NONE>;
|
pin-d3 = <&gpio 18 GPIO_FLAG_NONE>;
|
||||||
bus-width = <4>;
|
bus-width = <4>;
|
||||||
|
max-open-files = <4>;
|
||||||
};
|
};
|
||||||
|
|
||||||
stemma_qt: uart1 {
|
stemma_qt: uart1 {
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
|
|
||||||
|
|
||||||
idf_component_register(
|
|
||||||
SRCS ${SOURCE_FILES}
|
|
||||||
INCLUDE_DIRS "Source"
|
|
||||||
REQUIRES Tactility ButtonControl XPT2046SoftSPI PwmBacklight EstimatedPower ST7789-i8080 driver vfs fatfs
|
|
||||||
)
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
#include "devices/Power.h"
|
|
||||||
#include "devices/Display.h"
|
|
||||||
|
|
||||||
#include <ButtonControl.h>
|
|
||||||
#include <Tactility/hal/Configuration.h>
|
|
||||||
|
|
||||||
bool initBoot();
|
|
||||||
|
|
||||||
using namespace tt::hal;
|
|
||||||
|
|
||||||
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
|
|
||||||
return {
|
|
||||||
createDisplay(),
|
|
||||||
std::make_shared<Power>(),
|
|
||||||
ButtonControl::createOneButtonControl(0)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
extern const Configuration hardwareConfiguration = {
|
|
||||||
.initBoot = initBoot,
|
|
||||||
.createDevices = createDevices
|
|
||||||
};
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
#include "devices/Power.h"
|
|
||||||
#include "devices/Display.h"
|
|
||||||
|
|
||||||
#include "PwmBacklight.h"
|
|
||||||
#include "Tactility/kernel/SystemEvents.h"
|
|
||||||
#include <Tactility/TactilityCore.h>
|
|
||||||
|
|
||||||
#define TAG "thmi-s3"
|
|
||||||
|
|
||||||
static bool powerOn() {
|
|
||||||
gpio_config_t power_signal_config = {
|
|
||||||
.pin_bit_mask = (1ULL << THMI_S3_POWERON_GPIO) | (1ULL << THMI_S3_POWEREN_GPIO),
|
|
||||||
.mode = GPIO_MODE_OUTPUT,
|
|
||||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
|
||||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
|
||||||
.intr_type = GPIO_INTR_DISABLE,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (gpio_config(&power_signal_config) != ESP_OK) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gpio_set_level(THMI_S3_POWERON_GPIO, 1) != ESP_OK) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gpio_set_level(THMI_S3_POWEREN_GPIO, 1) != ESP_OK) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool initBoot() {
|
|
||||||
ESP_LOGI(TAG, "Powering on the board...");
|
|
||||||
if (!powerOn()) {
|
|
||||||
ESP_LOGE(TAG, "Failed to power on the board.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!driver::pwmbacklight::init(DISPLAY_BL, 30000)) {
|
|
||||||
ESP_LOGE(TAG, "Failed to initialize backlight.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
#include <Xpt2046SoftSpi.h>
|
|
||||||
#include <Tactility/hal/touch/TouchDevice.h>
|
|
||||||
|
|
||||||
#include "Display.h"
|
|
||||||
#include "PwmBacklight.h"
|
|
||||||
#include "St7789i8080Display.h"
|
|
||||||
|
|
||||||
static bool touchSpiInitialized = false;
|
|
||||||
|
|
||||||
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
|
|
||||||
auto config = std::make_unique<Xpt2046SoftSpi::Configuration>(
|
|
||||||
TOUCH_MOSI_PIN,
|
|
||||||
TOUCH_MISO_PIN,
|
|
||||||
TOUCH_SCK_PIN,
|
|
||||||
TOUCH_CS_PIN,
|
|
||||||
DISPLAY_HORIZONTAL_RESOLUTION,
|
|
||||||
DISPLAY_VERTICAL_RESOLUTION
|
|
||||||
);
|
|
||||||
|
|
||||||
return std::make_shared<Xpt2046SoftSpi>(std::move(config));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
|
|
||||||
// Create configuration
|
|
||||||
auto config = St7789i8080Display::Configuration(
|
|
||||||
DISPLAY_CS, // CS
|
|
||||||
DISPLAY_DC, // DC
|
|
||||||
DISPLAY_WR, // WR
|
|
||||||
DISPLAY_RD, // RD
|
|
||||||
{ DISPLAY_I80_D0, DISPLAY_I80_D1, DISPLAY_I80_D2, DISPLAY_I80_D3,
|
|
||||||
DISPLAY_I80_D4, DISPLAY_I80_D5, DISPLAY_I80_D6, DISPLAY_I80_D7 }, // D0..D7
|
|
||||||
DISPLAY_RST, // RST
|
|
||||||
DISPLAY_BL // BL
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set resolution explicitly
|
|
||||||
config.horizontalResolution = DISPLAY_HORIZONTAL_RESOLUTION;
|
|
||||||
config.verticalResolution = DISPLAY_VERTICAL_RESOLUTION;
|
|
||||||
config.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty;
|
|
||||||
config.touch = createTouch();
|
|
||||||
config.invertColor = false;
|
|
||||||
|
|
||||||
auto display = std::make_shared<St7789i8080Display>(config);
|
|
||||||
return display;
|
|
||||||
}
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
#include <Tactility/hal/display/DisplayDevice.h>
|
|
||||||
|
|
||||||
#include "driver/spi_common.h"
|
|
||||||
|
|
||||||
class St7789i8080Display;
|
|
||||||
|
|
||||||
constexpr auto DISPLAY_CS = GPIO_NUM_6;
|
|
||||||
constexpr auto DISPLAY_DC = GPIO_NUM_7;
|
|
||||||
constexpr auto DISPLAY_WR = GPIO_NUM_8;
|
|
||||||
constexpr auto DISPLAY_RD = GPIO_NUM_NC;
|
|
||||||
constexpr auto DISPLAY_RST = GPIO_NUM_NC;
|
|
||||||
constexpr auto DISPLAY_BL = GPIO_NUM_38;
|
|
||||||
constexpr auto DISPLAY_I80_D0 = GPIO_NUM_48;
|
|
||||||
constexpr auto DISPLAY_I80_D1 = GPIO_NUM_47;
|
|
||||||
constexpr auto DISPLAY_I80_D2 = GPIO_NUM_39;
|
|
||||||
constexpr auto DISPLAY_I80_D3 = GPIO_NUM_40;
|
|
||||||
constexpr auto DISPLAY_I80_D4 = GPIO_NUM_41;
|
|
||||||
constexpr auto DISPLAY_I80_D5 = GPIO_NUM_42;
|
|
||||||
constexpr auto DISPLAY_I80_D6 = GPIO_NUM_45;
|
|
||||||
constexpr auto DISPLAY_I80_D7 = GPIO_NUM_46;
|
|
||||||
constexpr auto DISPLAY_HORIZONTAL_RESOLUTION = 240;
|
|
||||||
constexpr auto DISPLAY_VERTICAL_RESOLUTION = 320;
|
|
||||||
|
|
||||||
// Touch (XPT2046, resistive)
|
|
||||||
constexpr auto TOUCH_SPI_HOST = SPI2_HOST;
|
|
||||||
constexpr auto TOUCH_MISO_PIN = GPIO_NUM_4;
|
|
||||||
constexpr auto TOUCH_MOSI_PIN = GPIO_NUM_3;
|
|
||||||
constexpr auto TOUCH_SCK_PIN = GPIO_NUM_1;
|
|
||||||
constexpr auto TOUCH_CS_PIN = GPIO_NUM_2;
|
|
||||||
constexpr auto TOUCH_IRQ_PIN = GPIO_NUM_9;
|
|
||||||
|
|
||||||
// Factory function for registration
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
#include "Power.h"
|
|
||||||
|
|
||||||
#include <Tactility/Logger.h>
|
|
||||||
#include <driver/adc.h>
|
|
||||||
|
|
||||||
static const auto LOGGER = tt::Logger("Power");
|
|
||||||
|
|
||||||
bool Power::adcInitCalibration() {
|
|
||||||
bool calibrated = false;
|
|
||||||
|
|
||||||
esp_err_t efuse_read_result = esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP_FIT);
|
|
||||||
if (efuse_read_result == ESP_ERR_NOT_SUPPORTED) {
|
|
||||||
LOGGER.warn("Calibration scheme not supported, skip software calibration");
|
|
||||||
} else if (efuse_read_result == ESP_ERR_INVALID_VERSION) {
|
|
||||||
LOGGER.warn("eFuse not burnt, skip software calibration");
|
|
||||||
} else if (efuse_read_result == ESP_OK) {
|
|
||||||
calibrated = true;
|
|
||||||
LOGGER.info("Calibration success");
|
|
||||||
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, static_cast<adc_bits_width_t>(ADC_WIDTH_BIT_DEFAULT), 0, &adcCharacteristics);
|
|
||||||
} else {
|
|
||||||
LOGGER.warn("eFuse read failed, skipping calibration");
|
|
||||||
}
|
|
||||||
|
|
||||||
return calibrated;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t Power::adcReadValue() const {
|
|
||||||
int adc_raw = adc1_get_raw(ADC1_CHANNEL_4);
|
|
||||||
LOGGER.debug("Raw data: {}", adc_raw);
|
|
||||||
|
|
||||||
uint32_t voltage;
|
|
||||||
|
|
||||||
if (calibrated) {
|
|
||||||
voltage = esp_adc_cal_raw_to_voltage(adc_raw, &adcCharacteristics);
|
|
||||||
LOGGER.debug("Calibrated data: {} mV", voltage);
|
|
||||||
} else {
|
|
||||||
voltage = (adc_raw * 3300) / 4095; // fallback
|
|
||||||
LOGGER.debug("Estimated data: {} mV", voltage);
|
|
||||||
}
|
|
||||||
|
|
||||||
return voltage;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Power::ensureInitialized() {
|
|
||||||
if (!initialized) {
|
|
||||||
|
|
||||||
if (adc1_config_width(ADC_WIDTH_BIT_12) != ESP_OK) {
|
|
||||||
LOGGER.error("ADC1 config width failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_DB_11) != ESP_OK) {
|
|
||||||
LOGGER.error("ADC1 config attenuation failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
calibrated = adcInitCalibration();
|
|
||||||
|
|
||||||
initialized = true;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Power::supportsMetric(MetricType type) const {
|
|
||||||
switch (type) {
|
|
||||||
using enum MetricType;
|
|
||||||
case BatteryVoltage:
|
|
||||||
case ChargeLevel:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Power::getMetric(MetricType type, MetricData& data) {
|
|
||||||
if (!ensureInitialized()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case MetricType::BatteryVoltage:
|
|
||||||
data.valueAsUint32 = adcReadValue() * 2;
|
|
||||||
return true;
|
|
||||||
case MetricType::ChargeLevel:
|
|
||||||
data.valueAsUint8 = chargeFromAdcVoltage.estimateCharge(adcReadValue() * 2);
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <esp_adc_cal.h>
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
#include <ChargeFromVoltage.h>
|
|
||||||
#include <Tactility/hal/power/PowerDevice.h>
|
|
||||||
|
|
||||||
constexpr auto THMI_S3_POWEREN_GPIO = GPIO_NUM_10;
|
|
||||||
constexpr auto THMI_S3_POWERON_GPIO = GPIO_NUM_14;
|
|
||||||
|
|
||||||
using tt::hal::power::PowerDevice;
|
|
||||||
|
|
||||||
class Power final : public PowerDevice {
|
|
||||||
|
|
||||||
ChargeFromVoltage chargeFromAdcVoltage = ChargeFromVoltage(3.3f, 4.2f);
|
|
||||||
bool initialized = false;
|
|
||||||
esp_adc_cal_characteristics_t adcCharacteristics;
|
|
||||||
bool calibrated = false;
|
|
||||||
|
|
||||||
bool adcInitCalibration();
|
|
||||||
uint32_t adcReadValue() const;
|
|
||||||
|
|
||||||
bool ensureInitialized();
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
std::string getName() const override { return "T-hmi Power"; }
|
|
||||||
std::string getDescription() const override { return "Power measurement via ADC"; }
|
|
||||||
|
|
||||||
bool supportsMetric(MetricType type) const override;
|
|
||||||
bool getMetric(MetricType type, MetricData& data) override;
|
|
||||||
};
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
#include <tactility/module.h>
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
static error_t start() {
|
|
||||||
// Empty for now
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
// Empty for now
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module lilygo_thmi_s3_module = {
|
|
||||||
.name = "lilygo-thmi-s3",
|
|
||||||
.start = start,
|
|
||||||
.stop = stop,
|
|
||||||
.symbols = nullptr,
|
|
||||||
.internal = nullptr
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
[general]
|
|
||||||
vendor=LilyGO
|
|
||||||
name=T-HMI S3
|
|
||||||
|
|
||||||
[apps]
|
|
||||||
launcherAppId=Launcher
|
|
||||||
|
|
||||||
[hardware]
|
|
||||||
target=ESP32S3
|
|
||||||
flashSize=16MB
|
|
||||||
spiRam=true
|
|
||||||
spiRamMode=OCT
|
|
||||||
spiRamSpeed=120M
|
|
||||||
tinyUsb=true
|
|
||||||
esptoolFlashFreq=120M
|
|
||||||
|
|
||||||
[display]
|
|
||||||
size=2.8"
|
|
||||||
shape=rectangle
|
|
||||||
dpi=125
|
|
||||||
|
|
||||||
[lvgl]
|
|
||||||
colorDepth=16
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
dependencies:
|
|
||||||
- Platforms/platform-esp32
|
|
||||||
dts: lilygo,thmi-s3.dts
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
/dts-v1/;
|
|
||||||
|
|
||||||
#include <tactility/bindings/root.h>
|
|
||||||
#include <tactility/bindings/esp32_gpio.h>
|
|
||||||
#include <tactility/bindings/esp32_i2c.h>
|
|
||||||
#include <tactility/bindings/esp32_sdmmc.h>
|
|
||||||
#include <tactility/bindings/esp32_spi.h>
|
|
||||||
|
|
||||||
/ {
|
|
||||||
compatible = "root";
|
|
||||||
model = "LilyGO T-HMI S3";
|
|
||||||
|
|
||||||
gpio0 {
|
|
||||||
compatible = "espressif,esp32-gpio";
|
|
||||||
gpio-count = <49>;
|
|
||||||
};
|
|
||||||
|
|
||||||
spi0 {
|
|
||||||
compatible = "espressif,esp32-spi";
|
|
||||||
host = <SPI2_HOST>;
|
|
||||||
pin-mosi = <&gpio0 3 GPIO_FLAG_NONE>;
|
|
||||||
pin-miso = <&gpio0 4 GPIO_FLAG_NONE>;
|
|
||||||
pin-sclk = <&gpio0 1 GPIO_FLAG_NONE>;
|
|
||||||
};
|
|
||||||
|
|
||||||
sdmmc0 {
|
|
||||||
compatible = "espressif,esp32-sdmmc";
|
|
||||||
pin-clk = <&gpio0 12 GPIO_FLAG_NONE>;
|
|
||||||
pin-cmd = <&gpio0 11 GPIO_FLAG_NONE>;
|
|
||||||
pin-d0 = <&gpio0 13 GPIO_FLAG_NONE>;
|
|
||||||
bus-width = <1>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -2,5 +2,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
|
|||||||
|
|
||||||
idf_component_register(SRCS ${SOURCE_FILES}
|
idf_component_register(SRCS ${SOURCE_FILES}
|
||||||
INCLUDE_DIRS "Source"
|
INCLUDE_DIRS "Source"
|
||||||
REQUIRES EPDiyDisplay GT911 TactilityCore driver EstimatedPower
|
REQUIRES FastEpdDisplay GT911 TactilityCore
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,22 +1,19 @@
|
|||||||
#include "devices/Display.h"
|
#include "devices/Display.h"
|
||||||
#include "devices/SdCard.h"
|
#include "devices/SdCard.h"
|
||||||
#include "devices/Power.h"
|
|
||||||
|
|
||||||
#include <Tactility/hal/Configuration.h>
|
#include <Tactility/hal/Configuration.h>
|
||||||
|
|
||||||
using namespace tt::hal;
|
using namespace tt::hal;
|
||||||
|
|
||||||
bool initBoot();
|
|
||||||
|
|
||||||
static DeviceVector createDevices() {
|
static DeviceVector createDevices() {
|
||||||
|
auto touch = createTouch();
|
||||||
return {
|
return {
|
||||||
createPower(),
|
|
||||||
createDisplay(),
|
|
||||||
createSdCard(),
|
createSdCard(),
|
||||||
|
createDisplay(touch)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
extern const Configuration hardwareConfiguration = {
|
extern const Configuration hardwareConfiguration = {
|
||||||
.initBoot = initBoot,
|
.initBoot = nullptr,
|
||||||
.createDevices = createDevices
|
.createDevices = createDevices
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,48 +0,0 @@
|
|||||||
#include <Tactility/Logger.h>
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
|
|
||||||
static const auto LOGGER = tt::Logger("Paper S3");
|
|
||||||
|
|
||||||
constexpr gpio_num_t VBAT_PIN = GPIO_NUM_3;
|
|
||||||
constexpr gpio_num_t CHARGE_STATUS_PIN = GPIO_NUM_4;
|
|
||||||
constexpr gpio_num_t USB_DETECT_PIN = GPIO_NUM_5;
|
|
||||||
|
|
||||||
static bool powerOn() {
|
|
||||||
if (gpio_reset_pin(CHARGE_STATUS_PIN) != ESP_OK) {
|
|
||||||
LOGGER.error("Failed to reset CHARGE_STATUS_PIN");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gpio_set_direction(CHARGE_STATUS_PIN, GPIO_MODE_INPUT) != ESP_OK) {
|
|
||||||
LOGGER.error("Failed to set direction for CHARGE_STATUS_PIN");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gpio_reset_pin(USB_DETECT_PIN) != ESP_OK) {
|
|
||||||
LOGGER.error("Failed to reset USB_DETECT_PIN");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gpio_set_direction(USB_DETECT_PIN, GPIO_MODE_INPUT) != ESP_OK) {
|
|
||||||
LOGGER.error("Failed to set direction for USB_DETECT_PIN");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// VBAT_PIN is used as ADC input; only reset it here to clear any previous
|
|
||||||
// configuration. The ADC driver (ChargeFromAdcVoltage) configures it for ADC use.
|
|
||||||
if (gpio_reset_pin(VBAT_PIN) != ESP_OK) {
|
|
||||||
LOGGER.error("Failed to reset VBAT_PIN");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool initBoot() {
|
|
||||||
LOGGER.info("Power on");
|
|
||||||
if (!powerOn()) {
|
|
||||||
LOGGER.error("Power on failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@ -1,23 +1,34 @@
|
|||||||
#include "Display.h"
|
#include "Display.h"
|
||||||
#include <Gt911Touch.h>
|
#include <Gt911Touch.h>
|
||||||
#include <EpdiyDisplayHelper.h>
|
#include <FastEpdDisplay.h>
|
||||||
|
#include <Tactility/lvgl/LvglSync.h>
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
|
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
|
||||||
auto configuration = std::make_unique<Gt911Touch::Configuration>(
|
auto configuration = std::make_unique<Gt911Touch::Configuration>(
|
||||||
I2C_NUM_0,
|
I2C_NUM_0,
|
||||||
540,
|
540,
|
||||||
960,
|
960,
|
||||||
true, // swapXy
|
false, // swapXy
|
||||||
true, // mirrorX
|
false, // mirrorX
|
||||||
false, // mirrorY
|
false, // mirrorY
|
||||||
GPIO_NUM_NC, // pinReset
|
GPIO_NUM_NC, // pinReset
|
||||||
GPIO_NUM_NC //48 pinInterrupt
|
GPIO_NUM_NC // pinInterrupt
|
||||||
);
|
);
|
||||||
|
|
||||||
return std::make_shared<Gt911Touch>(std::move(configuration));
|
auto touch = std::make_shared<Gt911Touch>(std::move(configuration));
|
||||||
|
return std::static_pointer_cast<tt::hal::touch::TouchDevice>(touch);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
|
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay(std::shared_ptr<tt::hal::touch::TouchDevice> touch) {
|
||||||
auto touch = createTouch();
|
FastEpdDisplay::Configuration configuration = {
|
||||||
return EpdiyDisplayHelper::createM5PaperS3Display(touch);
|
.horizontalResolution = 540,
|
||||||
|
.verticalResolution = 960,
|
||||||
|
.touch = std::move(touch),
|
||||||
|
.busSpeedHz = 20000000,
|
||||||
|
.rotationDegrees = 90,
|
||||||
|
.use4bppGrayscale = false,
|
||||||
|
.fullRefreshEveryNFlushes = 40,
|
||||||
|
};
|
||||||
|
|
||||||
|
return std::make_shared<FastEpdDisplay>(configuration, tt::lvgl::getSyncLock());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <Tactility/hal/display/DisplayDevice.h>
|
#include <Tactility/hal/display/DisplayDevice.h>
|
||||||
|
#include <Tactility/hal/touch/TouchDevice.h>
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch();
|
||||||
|
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay(std::shared_ptr<tt::hal::touch::TouchDevice> touch);
|
||||||
|
|||||||
@ -1,282 +0,0 @@
|
|||||||
#include "Power.h"
|
|
||||||
|
|
||||||
#include <tactility/log.h>
|
|
||||||
#include <freertos/FreeRTOS.h>
|
|
||||||
#include <freertos/task.h>
|
|
||||||
#include <driver/ledc.h>
|
|
||||||
|
|
||||||
using namespace tt::hal::power;
|
|
||||||
|
|
||||||
constexpr auto* TAG = "PaperS3Power";
|
|
||||||
|
|
||||||
// M5Stack PaperS3 hardware pin definitions
|
|
||||||
constexpr gpio_num_t VBAT_PIN = GPIO_NUM_3; // Battery voltage with 2x divider
|
|
||||||
constexpr adc_channel_t VBAT_ADC_CHANNEL = ADC_CHANNEL_2; // GPIO3 = ADC1_CHANNEL_2
|
|
||||||
|
|
||||||
constexpr gpio_num_t CHARGE_STATUS_PIN = GPIO_NUM_4; // Charge IC status: 0 = charging, 1 = full/no USB
|
|
||||||
constexpr gpio_num_t USB_DETECT_PIN = GPIO_NUM_5; // USB detect: 1 = USB connected
|
|
||||||
constexpr gpio_num_t POWER_OFF_PIN = GPIO_NUM_44; // Pull high to trigger shutdown
|
|
||||||
constexpr gpio_num_t BUZZER_PIN = GPIO_NUM_21;
|
|
||||||
|
|
||||||
// Battery voltage divider ratio (voltage is divided by 2)
|
|
||||||
constexpr float VOLTAGE_DIVIDER_MULTIPLIER = 2.0f;
|
|
||||||
|
|
||||||
// Battery voltage range for LiPo batteries
|
|
||||||
constexpr float MIN_BATTERY_VOLTAGE = 3.3f;
|
|
||||||
constexpr float MAX_BATTERY_VOLTAGE = 4.2f;
|
|
||||||
|
|
||||||
// Power-off signal timing
|
|
||||||
constexpr int POWER_OFF_PULSE_COUNT = 5;
|
|
||||||
constexpr int POWER_OFF_PULSE_DURATION_MS = 100;
|
|
||||||
|
|
||||||
constexpr uint32_t BUZZER_DUTY_50_PERCENT = 4096; // 50% of 13-bit (8192)
|
|
||||||
|
|
||||||
PaperS3Power::PaperS3Power(
|
|
||||||
std::unique_ptr<ChargeFromAdcVoltage> chargeFromAdcVoltage,
|
|
||||||
gpio_num_t powerOffPin
|
|
||||||
)
|
|
||||||
: chargeFromAdcVoltage(std::move(chargeFromAdcVoltage)),
|
|
||||||
powerOffPin(powerOffPin) {
|
|
||||||
LOG_I(TAG, "Initialized M5Stack PaperS3 power management");
|
|
||||||
}
|
|
||||||
|
|
||||||
void PaperS3Power::buzzerLedcInit() {
|
|
||||||
if (buzzerInitialized) {
|
|
||||||
LOG_I(TAG, "Buzzer already initialized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ledc_timer_config_t timer_cfg = {
|
|
||||||
.speed_mode = LEDC_LOW_SPEED_MODE,
|
|
||||||
.duty_resolution = LEDC_TIMER_13_BIT,
|
|
||||||
.timer_num = LEDC_TIMER_0,
|
|
||||||
.freq_hz = 1000,
|
|
||||||
.clk_cfg = LEDC_AUTO_CLK,
|
|
||||||
.deconfigure = false
|
|
||||||
};
|
|
||||||
esp_err_t err = ledc_timer_config(&timer_cfg);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
LOG_E(TAG, "LEDC timer config failed: %s", esp_err_to_name(err));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ledc_channel_config_t channel_cfg = {
|
|
||||||
.gpio_num = BUZZER_PIN,
|
|
||||||
.speed_mode = LEDC_LOW_SPEED_MODE,
|
|
||||||
.channel = LEDC_CHANNEL_0,
|
|
||||||
.intr_type = LEDC_INTR_DISABLE,
|
|
||||||
.timer_sel = LEDC_TIMER_0,
|
|
||||||
.duty = 0,
|
|
||||||
.hpoint = 0,
|
|
||||||
.sleep_mode = LEDC_SLEEP_MODE_NO_ALIVE_NO_PD,
|
|
||||||
.flags = {
|
|
||||||
.output_invert = 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
err = ledc_channel_config(&channel_cfg);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
LOG_E(TAG, "LEDC channel config failed: %s", esp_err_to_name(err));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
buzzerInitialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PaperS3Power::initializePowerOff() {
|
|
||||||
if (powerOffInitialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gpio_config_t io_conf = {
|
|
||||||
.pin_bit_mask = (1ULL << powerOffPin),
|
|
||||||
.mode = GPIO_MODE_OUTPUT,
|
|
||||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
|
||||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
|
||||||
.intr_type = GPIO_INTR_DISABLE,
|
|
||||||
};
|
|
||||||
|
|
||||||
esp_err_t err = gpio_config(&io_conf);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to configure power-off pin GPIO%d: %s", powerOffPin, esp_err_to_name(err));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gpio_set_level(powerOffPin, 0);
|
|
||||||
powerOffInitialized = true;
|
|
||||||
LOG_I(TAG, "Power-off control initialized on GPIO%d", powerOffPin);
|
|
||||||
|
|
||||||
buzzerLedcInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Fix USB Detection
|
|
||||||
bool PaperS3Power::isUsbConnected() {
|
|
||||||
// USB_DETECT_PIN is configured as input with pull-down by initBoot() in Init.cpp.
|
|
||||||
// Level 1 = USB VBUS present (per M5PaperS3 hardware spec).
|
|
||||||
bool usbConnected = gpio_get_level(USB_DETECT_PIN) == 1;
|
|
||||||
LOG_D(TAG, "USB_STATUS(GPIO%d)=%d", USB_DETECT_PIN, (int)usbConnected);
|
|
||||||
return usbConnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PaperS3Power::isCharging() {
|
|
||||||
// CHARGE_STATUS_PIN is configured as GPIO_MODE_INPUT by initBoot() in Init.cpp.
|
|
||||||
int chargePin = gpio_get_level(CHARGE_STATUS_PIN);
|
|
||||||
LOG_D(TAG, "CHG_STATUS(GPIO%d)=%d", CHARGE_STATUS_PIN, chargePin);
|
|
||||||
return chargePin == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PaperS3Power::supportsMetric(MetricType type) const {
|
|
||||||
switch (type) {
|
|
||||||
using enum MetricType;
|
|
||||||
case BatteryVoltage:
|
|
||||||
case ChargeLevel:
|
|
||||||
case IsCharging:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PaperS3Power::getMetric(MetricType type, MetricData& data) {
|
|
||||||
switch (type) {
|
|
||||||
using enum MetricType;
|
|
||||||
|
|
||||||
case BatteryVoltage:
|
|
||||||
return chargeFromAdcVoltage->readBatteryVoltageSampled(data.valueAsUint32);
|
|
||||||
|
|
||||||
case ChargeLevel: {
|
|
||||||
uint32_t voltage = 0;
|
|
||||||
if (chargeFromAdcVoltage->readBatteryVoltageSampled(voltage)) {
|
|
||||||
data.valueAsUint8 = chargeFromAdcVoltage->estimateChargeLevelFromVoltage(voltage);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
case IsCharging:
|
|
||||||
// isUsbConnected() is tracked separately but not used as a gate here:
|
|
||||||
// when USB is absent the charge IC's CHG pin is inactive (high), so
|
|
||||||
// isCharging() already returns false correctly.
|
|
||||||
data.valueAsBool = isCharging();
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PaperS3Power::toneOn(int frequency, int duration) {
|
|
||||||
if (!buzzerInitialized) {
|
|
||||||
LOG_I(TAG, "Buzzer not initialized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frequency <= 0) {
|
|
||||||
LOG_I(TAG, "Invalid frequency: %d", frequency);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t err = ledc_set_freq(LEDC_LOW_SPEED_MODE, LEDC_TIMER_0, frequency);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
LOG_E(TAG, "LEDC set freq failed: %s", esp_err_to_name(err));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, BUZZER_DUTY_50_PERCENT);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
LOG_E(TAG, "LEDC set duty failed: %s", esp_err_to_name(err));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
LOG_E(TAG, "LEDC update duty failed: %s", esp_err_to_name(err));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (duration > 0) {
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(duration));
|
|
||||||
toneOff();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PaperS3Power::toneOff() {
|
|
||||||
if (!buzzerInitialized) {
|
|
||||||
LOG_I(TAG, "Buzzer not initialized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t err = ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
LOG_E(TAG, "LEDC set duty failed: %s", esp_err_to_name(err));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
LOG_E(TAG, "LEDC update duty failed: %s", esp_err_to_name(err));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PaperS3Power::powerOff() {
|
|
||||||
LOG_W(TAG, "Power-off requested");
|
|
||||||
// Note: callers are responsible for stopping the display (e.g. EPD refresh) before
|
|
||||||
// calling powerOff(). The beep sequence below (~500 ms) provides some lead time,
|
|
||||||
// but a full EPD refresh can take up to ~1500 ms. If a refresh is still in flight
|
|
||||||
// when GPIO44 cuts power, the current frame will be incomplete; the display will
|
|
||||||
// recover correctly on next boot via a full-screen clear.
|
|
||||||
|
|
||||||
if (!powerOffInitialized) {
|
|
||||||
initializePowerOff();
|
|
||||||
if (!powerOffInitialized) {
|
|
||||||
LOG_E(TAG, "Power-off failed: GPIO not initialized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//beep on
|
|
||||||
toneOn(440, 200);
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(100));
|
|
||||||
//beep on
|
|
||||||
toneOn(440, 200);
|
|
||||||
|
|
||||||
LOG_W(TAG, "Triggering shutdown via GPIO%d (sending %d pulses)...", powerOffPin, POWER_OFF_PULSE_COUNT);
|
|
||||||
|
|
||||||
for (int i = 0; i < POWER_OFF_PULSE_COUNT; i++) {
|
|
||||||
gpio_set_level(powerOffPin, 1);
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(POWER_OFF_PULSE_DURATION_MS));
|
|
||||||
gpio_set_level(powerOffPin, 0);
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(POWER_OFF_PULSE_DURATION_MS));
|
|
||||||
}
|
|
||||||
|
|
||||||
gpio_set_level(powerOffPin, 1); // Final high state
|
|
||||||
|
|
||||||
LOG_W(TAG, "Shutdown signal sent. Waiting for power-off...");
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
||||||
LOG_E(TAG, "Device did not power off as expected");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<PowerDevice> createPower() {
|
|
||||||
ChargeFromAdcVoltage::Configuration config = {
|
|
||||||
.adcMultiplier = VOLTAGE_DIVIDER_MULTIPLIER,
|
|
||||||
.adcRefVoltage = 3.3f,
|
|
||||||
.adcChannel = VBAT_ADC_CHANNEL,
|
|
||||||
.adcConfig = {
|
|
||||||
.unit_id = ADC_UNIT_1,
|
|
||||||
.clk_src = ADC_RTC_CLK_SRC_DEFAULT,
|
|
||||||
.ulp_mode = ADC_ULP_MODE_DISABLE,
|
|
||||||
},
|
|
||||||
.adcChannelConfig = {
|
|
||||||
.atten = ADC_ATTEN_DB_12,
|
|
||||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
auto adc = std::make_unique<ChargeFromAdcVoltage>(config, MIN_BATTERY_VOLTAGE, MAX_BATTERY_VOLTAGE);
|
|
||||||
if (!adc->isInitialized()) {
|
|
||||||
LOG_E(TAG, "ADC initialization failed; power monitoring unavailable");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::make_shared<PaperS3Power>(std::move(adc), POWER_OFF_PIN);
|
|
||||||
}
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <ChargeFromAdcVoltage.h>
|
|
||||||
#include <Tactility/hal/power/PowerDevice.h>
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
|
|
||||||
using tt::hal::power::PowerDevice;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Power management for M5Stack PaperS3
|
|
||||||
*
|
|
||||||
* Hardware configuration:
|
|
||||||
* - Battery voltage: GPIO3 (ADC1_CHANNEL_2) with 2x voltage divider
|
|
||||||
* - Charge status: GPIO4 - digital signal (0 = charging, 1 = not charging)
|
|
||||||
* - USB detect: GPIO5 - digital signal (1 = USB connected)
|
|
||||||
* - Power off: GPIO44 - pull high to trigger shutdown
|
|
||||||
*/
|
|
||||||
class PaperS3Power final : public PowerDevice {
|
|
||||||
private:
|
|
||||||
std::unique_ptr<::ChargeFromAdcVoltage> chargeFromAdcVoltage;
|
|
||||||
gpio_num_t powerOffPin;
|
|
||||||
bool powerOffInitialized = false;
|
|
||||||
bool buzzerInitialized = false;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit PaperS3Power(
|
|
||||||
std::unique_ptr<::ChargeFromAdcVoltage> chargeFromAdcVoltage,
|
|
||||||
gpio_num_t powerOffPin
|
|
||||||
);
|
|
||||||
~PaperS3Power() override = default;
|
|
||||||
|
|
||||||
std::string getName() const override { return "M5Stack PaperS3 Power"; }
|
|
||||||
std::string getDescription() const override { return "Battery monitoring with charge detection and power-off"; }
|
|
||||||
|
|
||||||
bool supportsMetric(MetricType type) const override;
|
|
||||||
bool getMetric(MetricType type, MetricData& data) override;
|
|
||||||
|
|
||||||
bool supportsPowerOff() const override { return true; }
|
|
||||||
void powerOff() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void initializePowerOff();
|
|
||||||
bool isCharging();
|
|
||||||
// TODO: Fix USB Detection
|
|
||||||
bool isUsbConnected();
|
|
||||||
|
|
||||||
// Buzzer functions only used for the power off signal sound.
|
|
||||||
// So the user actually knows the epaper display is turning off.
|
|
||||||
void buzzerLedcInit();
|
|
||||||
void toneOn(int frequency, int duration);
|
|
||||||
void toneOff();
|
|
||||||
};
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::power::PowerDevice> createPower();
|
|
||||||
@ -22,7 +22,6 @@ dpi=235
|
|||||||
|
|
||||||
[lvgl]
|
[lvgl]
|
||||||
colorDepth=8
|
colorDepth=8
|
||||||
fontSize=24
|
|
||||||
theme=Mono
|
theme=Mono
|
||||||
|
|
||||||
[sdkconfig]
|
[sdkconfig]
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
#include "devices/Display.h"
|
#include "devices/Display.h"
|
||||||
|
#include "devices/SdCard.h"
|
||||||
|
|
||||||
#include <Tactility/hal/Configuration.h>
|
#include <Tactility/hal/Configuration.h>
|
||||||
#include <PwmBacklight.h>
|
#include <PwmBacklight.h>
|
||||||
@ -9,6 +10,7 @@ using namespace tt::hal;
|
|||||||
static DeviceVector createDevices() {
|
static DeviceVector createDevices() {
|
||||||
return {
|
return {
|
||||||
createDisplay(),
|
createDisplay(),
|
||||||
|
createSdCard(),
|
||||||
ButtonControl::createOneButtonControl(0)
|
ButtonControl::createOneButtonControl(0)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
22
Devices/waveshare-esp32-s3-geek/Source/devices/SdCard.cpp
Normal file
22
Devices/waveshare-esp32-s3-geek/Source/devices/SdCard.cpp
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#include "SdCard.h"
|
||||||
|
|
||||||
|
#include <Tactility/lvgl/LvglSync.h>
|
||||||
|
#include <Tactility/hal/sdcard/SdmmcDevice.h>
|
||||||
|
|
||||||
|
using tt::hal::sdcard::SdmmcDevice;
|
||||||
|
|
||||||
|
std::shared_ptr<SdCardDevice> createSdCard() {
|
||||||
|
auto configuration = std::make_unique<SdmmcDevice::Config>(
|
||||||
|
GPIO_NUM_36, //CLK
|
||||||
|
GPIO_NUM_35, //CMD
|
||||||
|
GPIO_NUM_37, //D0
|
||||||
|
GPIO_NUM_33, //D1
|
||||||
|
GPIO_NUM_38, //D2
|
||||||
|
GPIO_NUM_34, //D3
|
||||||
|
SdCardDevice::MountBehaviour::AtBoot
|
||||||
|
);
|
||||||
|
|
||||||
|
return std::make_shared<SdmmcDevice>(
|
||||||
|
std::move(configuration)
|
||||||
|
);
|
||||||
|
}
|
||||||
7
Devices/waveshare-esp32-s3-geek/Source/devices/SdCard.h
Normal file
7
Devices/waveshare-esp32-s3-geek/Source/devices/SdCard.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Tactility/hal/sdcard/SdCardDevice.h"
|
||||||
|
|
||||||
|
using tt::hal::sdcard::SdCardDevice;
|
||||||
|
|
||||||
|
std::shared_ptr<SdCardDevice> createSdCard();
|
||||||
@ -3,7 +3,6 @@
|
|||||||
#include <tactility/bindings/root.h>
|
#include <tactility/bindings/root.h>
|
||||||
#include <tactility/bindings/esp32_gpio.h>
|
#include <tactility/bindings/esp32_gpio.h>
|
||||||
#include <tactility/bindings/esp32_i2c.h>
|
#include <tactility/bindings/esp32_i2c.h>
|
||||||
#include <tactility/bindings/esp32_sdmmc.h>
|
|
||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
|
|
||||||
@ -31,17 +30,6 @@
|
|||||||
pin-sclk = <&gpio0 12 GPIO_FLAG_NONE>;
|
pin-sclk = <&gpio0 12 GPIO_FLAG_NONE>;
|
||||||
};
|
};
|
||||||
|
|
||||||
sdmmc0 {
|
|
||||||
compatible = "espressif,esp32-sdmmc";
|
|
||||||
pin-clk = <&gpio0 36 GPIO_FLAG_NONE>;
|
|
||||||
pin-cmd = <&gpio0 35 GPIO_FLAG_NONE>;
|
|
||||||
pin-d0 = <&gpio0 37 GPIO_FLAG_NONE>;
|
|
||||||
pin-d1 = <&gpio0 33 GPIO_FLAG_NONE>;
|
|
||||||
pin-d2 = <&gpio0 38 GPIO_FLAG_NONE>;
|
|
||||||
pin-d3 = <&gpio0 34 GPIO_FLAG_NONE>;
|
|
||||||
bus-width = <4>;
|
|
||||||
};
|
|
||||||
|
|
||||||
uart0 {
|
uart0 {
|
||||||
compatible = "espressif,esp32-uart";
|
compatible = "espressif,esp32-uart";
|
||||||
port = <UART_NUM_0>;
|
port = <UART_NUM_0>;
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
# EPDiy Display Driver
|
|
||||||
|
|
||||||
A display driver for e-paper/e-ink displays using the EPDiy library. This driver provides LVGL integration and high-level display management for EPD panels.
|
|
||||||
@ -1,472 +0,0 @@
|
|||||||
#include "EpdiyDisplay.h"
|
|
||||||
|
|
||||||
#include <tactility/check.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
|
|
||||||
#include <esp_heap_caps.h>
|
|
||||||
#include <esp_timer.h>
|
|
||||||
|
|
||||||
constexpr const char* TAG = "EpdiyDisplay";
|
|
||||||
|
|
||||||
bool EpdiyDisplay::s_hlInitialized = false;
|
|
||||||
EpdiyHighlevelState EpdiyDisplay::s_hlState = {};
|
|
||||||
|
|
||||||
EpdiyDisplay::EpdiyDisplay(std::unique_ptr<Configuration> inConfiguration)
|
|
||||||
: configuration(std::move(inConfiguration)) {
|
|
||||||
check(configuration != nullptr);
|
|
||||||
check(configuration->board != nullptr);
|
|
||||||
check(configuration->display != nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
EpdiyDisplay::~EpdiyDisplay() {
|
|
||||||
if (lvglDisplay != nullptr) {
|
|
||||||
stopLvgl();
|
|
||||||
}
|
|
||||||
if (initialized) {
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EpdiyDisplay::start() {
|
|
||||||
if (initialized) {
|
|
||||||
LOG_W(TAG, "Already initialized");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize EPDiy low-level hardware
|
|
||||||
epd_init(
|
|
||||||
configuration->board,
|
|
||||||
configuration->display,
|
|
||||||
configuration->initOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set rotation BEFORE initializing highlevel state
|
|
||||||
epd_set_rotation(configuration->rotation);
|
|
||||||
LOG_I(TAG, "Display rotation set to %d", configuration->rotation);
|
|
||||||
|
|
||||||
// Initialize the high-level API only once — epd_hl_init() sets a static flag internally
|
|
||||||
// and there is no matching epd_hl_deinit(). Reuse the existing state on subsequent starts.
|
|
||||||
if (!s_hlInitialized) {
|
|
||||||
s_hlState = epd_hl_init(configuration->waveform);
|
|
||||||
if (s_hlState.front_fb == nullptr) {
|
|
||||||
LOG_E(TAG, "Failed to initialize EPDiy highlevel state");
|
|
||||||
epd_deinit();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
s_hlInitialized = true;
|
|
||||||
LOG_I(TAG, "EPDiy highlevel state initialized");
|
|
||||||
} else {
|
|
||||||
LOG_I(TAG, "Reusing existing EPDiy highlevel state");
|
|
||||||
}
|
|
||||||
|
|
||||||
highlevelState = s_hlState;
|
|
||||||
framebuffer = epd_hl_get_framebuffer(&highlevelState);
|
|
||||||
|
|
||||||
initialized = true;
|
|
||||||
LOG_I(TAG, "EPDiy initialized successfully (%dx%d native, %dx%d rotated)", epd_width(), epd_height(), epd_rotated_display_width(), epd_rotated_display_height());
|
|
||||||
|
|
||||||
// Perform initial clear to ensure clean state
|
|
||||||
LOG_I(TAG, "Performing initial screen clear...");
|
|
||||||
clearScreen();
|
|
||||||
LOG_I(TAG, "Screen cleared");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EpdiyDisplay::stop() {
|
|
||||||
if (!initialized) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lvglDisplay != nullptr) {
|
|
||||||
stopLvgl();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Power off the display
|
|
||||||
if (powered) {
|
|
||||||
setPowerOn(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deinitialize EPDiy low-level hardware.
|
|
||||||
// The HL framebuffers (s_hlState) are intentionally kept alive: epd_hl_init() has no
|
|
||||||
// matching deinit and sets an internal already_initialized flag, so the HL state must
|
|
||||||
// persist across stop()/start() cycles and be reused on the next start().
|
|
||||||
epd_deinit();
|
|
||||||
|
|
||||||
// Clear instance references to HL state (the static s_hlState still owns the memory)
|
|
||||||
highlevelState = {};
|
|
||||||
framebuffer = nullptr;
|
|
||||||
|
|
||||||
initialized = false;
|
|
||||||
LOG_I(TAG, "EPDiy deinitialized (HL state preserved for restart)");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdiyDisplay::setPowerOn(bool turnOn) {
|
|
||||||
if (!initialized) {
|
|
||||||
LOG_W(TAG, "Cannot change power state - EPD not initialized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (powered == turnOn) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (turnOn) {
|
|
||||||
epd_poweron();
|
|
||||||
powered = true;
|
|
||||||
LOG_D(TAG, "EPD power on");
|
|
||||||
} else {
|
|
||||||
epd_poweroff();
|
|
||||||
powered = false;
|
|
||||||
LOG_D(TAG, "EPD power off");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LVGL functions
|
|
||||||
bool EpdiyDisplay::startLvgl() {
|
|
||||||
if (lvglDisplay != nullptr) {
|
|
||||||
LOG_W(TAG, "LVGL already initialized");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!initialized) {
|
|
||||||
LOG_E(TAG, "EPD not initialized, call start() first");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the native display dimensions
|
|
||||||
uint16_t width = epd_width();
|
|
||||||
uint16_t height = epd_height();
|
|
||||||
|
|
||||||
LOG_I(TAG, "Creating LVGL display: %dx%d (EPDiy rotation: %d)", width, height, configuration->rotation);
|
|
||||||
|
|
||||||
// Create LVGL display with native dimensions
|
|
||||||
lvglDisplay = lv_display_create(width, height);
|
|
||||||
if (lvglDisplay == nullptr) {
|
|
||||||
LOG_E(TAG, "Failed to create LVGL display");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// EPD uses 4-bit grayscale (16 levels)
|
|
||||||
// Map to LVGL's L8 format (8-bit grayscale)
|
|
||||||
lv_display_set_color_format(lvglDisplay, LV_COLOR_FORMAT_L8);
|
|
||||||
auto lv_rotation = epdRotationToLvgl(configuration->rotation);
|
|
||||||
lv_display_set_rotation(lvglDisplay, lv_rotation);
|
|
||||||
|
|
||||||
// Allocate LVGL draw buffer (L8 format: 1 byte per pixel)
|
|
||||||
size_t draw_buffer_size = static_cast<size_t>(width) * height;
|
|
||||||
|
|
||||||
lvglDrawBuffer = static_cast<uint8_t*>(heap_caps_malloc(draw_buffer_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
|
|
||||||
if (lvglDrawBuffer == nullptr) {
|
|
||||||
LOG_W(TAG, "PSRAM allocation failed for draw buffer, falling back to internal memory");
|
|
||||||
lvglDrawBuffer = static_cast<uint8_t*>(heap_caps_malloc(draw_buffer_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lvglDrawBuffer == nullptr) {
|
|
||||||
LOG_E(TAG, "Failed to allocate draw buffer");
|
|
||||||
lv_display_delete(lvglDisplay);
|
|
||||||
lvglDisplay = nullptr;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pre-allocate 4-bit packed pixel buffer used in flushInternal (avoids per-flush heap allocation)
|
|
||||||
// Row stride with odd-width padding: (width + 1) / 2 bytes per row
|
|
||||||
size_t packed_buffer_size = static_cast<size_t>((width + 1) / 2) * static_cast<size_t>(height);
|
|
||||||
packedBuffer = static_cast<uint8_t*>(heap_caps_malloc(packed_buffer_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
|
|
||||||
if (packedBuffer == nullptr) {
|
|
||||||
LOG_W(TAG, "PSRAM allocation failed for packed buffer, falling back to internal memory");
|
|
||||||
packedBuffer = static_cast<uint8_t*>(heap_caps_malloc(packed_buffer_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packedBuffer == nullptr) {
|
|
||||||
LOG_E(TAG, "Failed to allocate packed pixel buffer");
|
|
||||||
heap_caps_free(lvglDrawBuffer);
|
|
||||||
lvglDrawBuffer = nullptr;
|
|
||||||
lv_display_delete(lvglDisplay);
|
|
||||||
lvglDisplay = nullptr;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For EPD, we want full refresh mode based on configuration
|
|
||||||
lv_display_render_mode_t render_mode = configuration->fullRefresh
|
|
||||||
? LV_DISPLAY_RENDER_MODE_FULL
|
|
||||||
: LV_DISPLAY_RENDER_MODE_PARTIAL;
|
|
||||||
|
|
||||||
lv_display_set_buffers(lvglDisplay, lvglDrawBuffer, NULL, draw_buffer_size, render_mode);
|
|
||||||
|
|
||||||
// Set flush callback
|
|
||||||
lv_display_set_flush_cb(lvglDisplay, flushCallback);
|
|
||||||
lv_display_set_user_data(lvglDisplay, this);
|
|
||||||
|
|
||||||
// Register rotation change event callback
|
|
||||||
lv_display_add_event_cb(lvglDisplay, rotationEventCallback, LV_EVENT_RESOLUTION_CHANGED, this);
|
|
||||||
LOG_D(TAG, "Registered rotation change event callback");
|
|
||||||
|
|
||||||
// Start touch device if present
|
|
||||||
auto touch_device = getTouchDevice();
|
|
||||||
if (touch_device != nullptr && touch_device->supportsLvgl()) {
|
|
||||||
LOG_D(TAG, "Starting touch device for LVGL");
|
|
||||||
if (!touch_device->startLvgl(lvglDisplay)) {
|
|
||||||
LOG_W(TAG, "Failed to start touch device for LVGL");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_I(TAG, "LVGL display initialized");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EpdiyDisplay::stopLvgl() {
|
|
||||||
if (lvglDisplay == nullptr) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_I(TAG, "Stopping LVGL display");
|
|
||||||
|
|
||||||
// Stop touch device
|
|
||||||
auto touch_device = getTouchDevice();
|
|
||||||
if (touch_device != nullptr) {
|
|
||||||
touch_device->stopLvgl();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lvglDrawBuffer != nullptr) {
|
|
||||||
heap_caps_free(lvglDrawBuffer);
|
|
||||||
lvglDrawBuffer = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packedBuffer != nullptr) {
|
|
||||||
heap_caps_free(packedBuffer);
|
|
||||||
packedBuffer = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the LVGL display object
|
|
||||||
lv_display_delete(lvglDisplay);
|
|
||||||
lvglDisplay = nullptr;
|
|
||||||
|
|
||||||
|
|
||||||
LOG_I(TAG, "LVGL display stopped");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdiyDisplay::flushCallback(lv_display_t* display, const lv_area_t* area, uint8_t* pixelMap) {
|
|
||||||
auto* instance = static_cast<EpdiyDisplay*>(lv_display_get_user_data(display));
|
|
||||||
if (instance != nullptr) {
|
|
||||||
uint64_t t0 = esp_timer_get_time();
|
|
||||||
const bool isLast = lv_display_flush_is_last(display);
|
|
||||||
instance->flushInternal(area, pixelMap, isLast);
|
|
||||||
LOG_D(TAG, "flush took %llu us", (unsigned long long)(esp_timer_get_time() - t0));
|
|
||||||
} else {
|
|
||||||
LOG_W(TAG, "flush callback called with null instance");
|
|
||||||
}
|
|
||||||
lv_display_flush_ready(display);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// EPD functions
|
|
||||||
void EpdiyDisplay::clearScreen() {
|
|
||||||
if (!initialized) {
|
|
||||||
LOG_E(TAG, "EPD not initialized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!powered) {
|
|
||||||
setPowerOn(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
epd_clear();
|
|
||||||
|
|
||||||
// Also clear the framebuffer
|
|
||||||
epd_hl_set_all_white(&highlevelState);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdiyDisplay::clearArea(EpdRect area) {
|
|
||||||
if (!initialized) {
|
|
||||||
LOG_E(TAG, "EPD not initialized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!powered) {
|
|
||||||
setPowerOn(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
epd_clear_area(area);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EpdDrawError EpdiyDisplay::updateScreen(enum EpdDrawMode mode, int temperature) {
|
|
||||||
if (!initialized) {
|
|
||||||
LOG_E(TAG, "EPD not initialized");
|
|
||||||
return EPD_DRAW_FAILED_ALLOC;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!powered) {
|
|
||||||
setPowerOn(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use defaults if not specified
|
|
||||||
if (mode == MODE_UNKNOWN_WAVEFORM) {
|
|
||||||
mode = configuration->defaultDrawMode;
|
|
||||||
}
|
|
||||||
if (temperature == -1) {
|
|
||||||
temperature = configuration->defaultTemperature;
|
|
||||||
}
|
|
||||||
|
|
||||||
return epd_hl_update_screen(&highlevelState, mode, temperature);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EpdDrawError EpdiyDisplay::updateArea(EpdRect area, enum EpdDrawMode mode, int temperature) {
|
|
||||||
if (!initialized) {
|
|
||||||
LOG_E(TAG, "EPD not initialized");
|
|
||||||
return EPD_DRAW_FAILED_ALLOC;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!powered) {
|
|
||||||
setPowerOn(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use defaults if not specified
|
|
||||||
if (mode == MODE_UNKNOWN_WAVEFORM) {
|
|
||||||
mode = configuration->defaultDrawMode;
|
|
||||||
}
|
|
||||||
if (temperature == -1) {
|
|
||||||
temperature = configuration->defaultTemperature;
|
|
||||||
}
|
|
||||||
|
|
||||||
return epd_hl_update_area(&highlevelState, mode, temperature, area);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdiyDisplay::setAllWhite() {
|
|
||||||
if (!initialized) {
|
|
||||||
LOG_E(TAG, "EPD not initialized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
epd_hl_set_all_white(&highlevelState);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal functions
|
|
||||||
void EpdiyDisplay::flushInternal(const lv_area_t* area, uint8_t* pixelMap, bool isLast) {
|
|
||||||
if (!initialized) {
|
|
||||||
LOG_E(TAG, "Cannot flush - EPD not initialized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!powered) {
|
|
||||||
setPowerOn(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
const int x = area->x1;
|
|
||||||
const int y = area->y1;
|
|
||||||
const int width = lv_area_get_width(area);
|
|
||||||
const int height = lv_area_get_height(area);
|
|
||||||
|
|
||||||
LOG_D(TAG, "Flushing area: x=%d, y=%d, w=%d, h=%d isLast=%d", x, y, width, height, (int)isLast);
|
|
||||||
|
|
||||||
// Convert L8 (8-bit grayscale, 0=black/255=white) to EPDiy 4-bit (0=black/15=white).
|
|
||||||
// Pack 2 pixels per byte: lower nibble = even column, upper nibble = odd column.
|
|
||||||
// Row stride includes one padding nibble for odd widths to keep rows aligned.
|
|
||||||
// Threshold at 128 (matching FastEPD BB_MODE_1BPP): pixels > 127 → full white (15),
|
|
||||||
// pixels ≤ 127 → full black (0). Maximum contrast for the Mono theme and correct for
|
|
||||||
// MODE_DU which only drives two levels. For greyscale content / MODE_GL16, replace
|
|
||||||
// the threshold with `src[col] >> 4` to preserve intermediate grey levels.
|
|
||||||
const int row_stride = (width + 1) / 2;
|
|
||||||
for (int row = 0; row < height; ++row) {
|
|
||||||
const uint8_t* src = pixelMap + static_cast<size_t>(row) * width;
|
|
||||||
uint8_t* dst = packedBuffer + static_cast<size_t>(row) * row_stride;
|
|
||||||
for (int col = 0; col < width; col += 2) {
|
|
||||||
const uint8_t p0 = (src[col] > 127) ? 15u : 0u;
|
|
||||||
const uint8_t p1 = (col + 1 < width) ? ((src[col + 1] > 127) ? 15u : 0u) : 0u;
|
|
||||||
dst[col / 2] = static_cast<uint8_t>((p1 << 4) | p0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const EpdRect update_area = {
|
|
||||||
.x = x,
|
|
||||||
.y = y,
|
|
||||||
.width = static_cast<uint16_t>(width),
|
|
||||||
.height = static_cast<uint16_t>(height)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Write pixels into EPDiy's framebuffer (no hardware I/O, just memory)
|
|
||||||
epd_draw_rotated_image(update_area, packedBuffer, framebuffer);
|
|
||||||
|
|
||||||
// Only trigger EPD hardware update on the last flush of this render cycle.
|
|
||||||
// EPDiy's epd_prep tasks run at configMAX_PRIORITIES-1 with busy-wait loops; calling
|
|
||||||
// epd_hl_update_area on every partial flush starves IDLE and triggers the task watchdog
|
|
||||||
// during scroll animations. Batching to one hardware update per LVGL render cycle fixes this.
|
|
||||||
if (isLast) {
|
|
||||||
epd_hl_update_screen(
|
|
||||||
&highlevelState,
|
|
||||||
static_cast<EpdDrawMode>(configuration->defaultDrawMode | MODE_PACKING_2PPB),
|
|
||||||
configuration->defaultTemperature
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lv_display_rotation_t EpdiyDisplay::epdRotationToLvgl(enum EpdRotation epdRotation) {
|
|
||||||
// Static lookup table for EPD -> LVGL rotation mapping
|
|
||||||
// EPDiy: LANDSCAPE = 0°, PORTRAIT = 90° CW, INVERTED_LANDSCAPE = 180°, INVERTED_PORTRAIT = 270° CW
|
|
||||||
// LVGL: 0 = 0°, 90 = 90° CW, 180 = 180°, 270 = 270° CW
|
|
||||||
static const lv_display_rotation_t rotationMap[] = {
|
|
||||||
LV_DISPLAY_ROTATION_0, // EPD_ROT_LANDSCAPE (0)
|
|
||||||
LV_DISPLAY_ROTATION_270, // EPD_ROT_PORTRAIT (1) - 90° CW in EPD is 270° in LVGL
|
|
||||||
LV_DISPLAY_ROTATION_180, // EPD_ROT_INVERTED_LANDSCAPE (2)
|
|
||||||
LV_DISPLAY_ROTATION_90 // EPD_ROT_INVERTED_PORTRAIT (3) - 270° CW in EPD is 90° in LVGL
|
|
||||||
};
|
|
||||||
|
|
||||||
// Validate input and return mapped value
|
|
||||||
if (epdRotation >= 0 && epdRotation < 4) {
|
|
||||||
return rotationMap[epdRotation];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default to landscape if invalid
|
|
||||||
return LV_DISPLAY_ROTATION_0;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EpdRotation EpdiyDisplay::lvglRotationToEpd(lv_display_rotation_t lvglRotation) {
|
|
||||||
// Static lookup table for LVGL -> EPD rotation mapping
|
|
||||||
static const enum EpdRotation rotationMap[] = {
|
|
||||||
EPD_ROT_LANDSCAPE, // LV_DISPLAY_ROTATION_0 (0)
|
|
||||||
EPD_ROT_INVERTED_PORTRAIT, // LV_DISPLAY_ROTATION_90 (1)
|
|
||||||
EPD_ROT_INVERTED_LANDSCAPE, // LV_DISPLAY_ROTATION_180 (2)
|
|
||||||
EPD_ROT_PORTRAIT // LV_DISPLAY_ROTATION_270 (3)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Validate input and return mapped value
|
|
||||||
if (lvglRotation >= LV_DISPLAY_ROTATION_0 && lvglRotation <= LV_DISPLAY_ROTATION_270) {
|
|
||||||
return rotationMap[lvglRotation];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default to landscape if invalid
|
|
||||||
return EPD_ROT_LANDSCAPE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdiyDisplay::rotationEventCallback(lv_event_t* event) {
|
|
||||||
auto* display = static_cast<EpdiyDisplay*>(lv_event_get_user_data(event));
|
|
||||||
if (display == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lv_display_t* lvgl_display = static_cast<lv_display_t*>(lv_event_get_target(event));
|
|
||||||
if (lvgl_display == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lv_display_rotation_t rotation = lv_display_get_rotation(lvgl_display);
|
|
||||||
display->handleRotationChange(rotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdiyDisplay::handleRotationChange(lv_display_rotation_t lvgl_rotation) {
|
|
||||||
// Map LVGL rotation to EPDiy rotation using lookup table
|
|
||||||
enum EpdRotation epd_rotation = lvglRotationToEpd(lvgl_rotation);
|
|
||||||
|
|
||||||
// Update EPDiy rotation
|
|
||||||
LOG_I(TAG, "LVGL rotation changed to %d, setting EPDiy rotation to %d", lvgl_rotation, epd_rotation);
|
|
||||||
epd_set_rotation(epd_rotation);
|
|
||||||
|
|
||||||
// Update configuration to keep it in sync
|
|
||||||
configuration->rotation = epd_rotation;
|
|
||||||
|
|
||||||
// Log the new dimensions
|
|
||||||
LOG_I(TAG, "Display dimensions after rotation: %dx%d", epd_rotated_display_width(), epd_rotated_display_height());
|
|
||||||
}
|
|
||||||
@ -1,163 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Tactility/hal/display/DisplayDevice.h>
|
|
||||||
#include <Tactility/hal/touch/TouchDevice.h>
|
|
||||||
|
|
||||||
#include <epd_highlevel.h>
|
|
||||||
#include <epdiy.h>
|
|
||||||
#include <lvgl.h>
|
|
||||||
#include <memory>
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
class EpdiyDisplay final : public tt::hal::display::DisplayDevice {
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
class Configuration {
|
|
||||||
public:
|
|
||||||
|
|
||||||
Configuration(
|
|
||||||
const EpdBoardDefinition* board,
|
|
||||||
const EpdDisplay_t* display,
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDevice> touch = nullptr,
|
|
||||||
enum EpdInitOptions initOptions = EPD_OPTIONS_DEFAULT,
|
|
||||||
const EpdWaveform* waveform = EPD_BUILTIN_WAVEFORM,
|
|
||||||
int defaultTemperature = 25,
|
|
||||||
enum EpdDrawMode defaultDrawMode = MODE_GL16,
|
|
||||||
bool fullRefresh = false,
|
|
||||||
enum EpdRotation rotation = EPD_ROT_LANDSCAPE
|
|
||||||
) : board(board),
|
|
||||||
display(display),
|
|
||||||
touch(std::move(touch)),
|
|
||||||
initOptions(initOptions),
|
|
||||||
waveform(waveform),
|
|
||||||
defaultTemperature(defaultTemperature),
|
|
||||||
defaultDrawMode(defaultDrawMode),
|
|
||||||
fullRefresh(fullRefresh),
|
|
||||||
rotation(rotation) {
|
|
||||||
check(board != nullptr);
|
|
||||||
check(display != nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
const EpdBoardDefinition* board;
|
|
||||||
const EpdDisplay_t* display;
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
|
|
||||||
enum EpdInitOptions initOptions;
|
|
||||||
const EpdWaveform* waveform;
|
|
||||||
int defaultTemperature;
|
|
||||||
enum EpdDrawMode defaultDrawMode;
|
|
||||||
bool fullRefresh;
|
|
||||||
enum EpdRotation rotation;
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
std::unique_ptr<Configuration> configuration;
|
|
||||||
lv_display_t* _Nullable lvglDisplay = nullptr;
|
|
||||||
EpdiyHighlevelState highlevelState = {};
|
|
||||||
uint8_t* framebuffer = nullptr;
|
|
||||||
uint8_t* lvglDrawBuffer = nullptr;
|
|
||||||
uint8_t* packedBuffer = nullptr; // Pre-allocated 4-bit packed pixel buffer for flushInternal
|
|
||||||
bool initialized = false;
|
|
||||||
bool powered = false;
|
|
||||||
|
|
||||||
// epd_hl_init() sets an internal already_initialized flag and has no matching deinit.
|
|
||||||
// We track first-time init statically and keep the HL state alive across stop()/start() cycles.
|
|
||||||
static bool s_hlInitialized;
|
|
||||||
static EpdiyHighlevelState s_hlState;
|
|
||||||
|
|
||||||
static void flushCallback(lv_display_t* display, const lv_area_t* area, uint8_t* pixelMap);
|
|
||||||
void flushInternal(const lv_area_t* area, uint8_t* pixelMap, bool isLast);
|
|
||||||
|
|
||||||
static void rotationEventCallback(lv_event_t* event);
|
|
||||||
void handleRotationChange(lv_display_rotation_t rotation);
|
|
||||||
|
|
||||||
// Rotation mapping helpers
|
|
||||||
static lv_display_rotation_t epdRotationToLvgl(enum EpdRotation epdRotation);
|
|
||||||
static enum EpdRotation lvglRotationToEpd(lv_display_rotation_t lvglRotation);
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
explicit EpdiyDisplay(std::unique_ptr<Configuration> inConfiguration);
|
|
||||||
|
|
||||||
~EpdiyDisplay() override;
|
|
||||||
|
|
||||||
std::string getName() const override { return "EPDiy"; }
|
|
||||||
|
|
||||||
std::string getDescription() const override {
|
|
||||||
return "E-Ink display powered by EPDiy library";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Device lifecycle
|
|
||||||
bool start() override;
|
|
||||||
bool stop() override;
|
|
||||||
|
|
||||||
// Power control
|
|
||||||
void setPowerOn(bool turnOn) override;
|
|
||||||
bool isPoweredOn() const override { return powered; }
|
|
||||||
bool supportsPowerControl() const override { return true; }
|
|
||||||
|
|
||||||
// Touch device
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDevice> _Nullable getTouchDevice() override {
|
|
||||||
return configuration->touch;
|
|
||||||
}
|
|
||||||
|
|
||||||
// LVGL support
|
|
||||||
bool supportsLvgl() const override { return true; }
|
|
||||||
bool startLvgl() override;
|
|
||||||
bool stopLvgl() override;
|
|
||||||
lv_display_t* _Nullable getLvglDisplay() const override { return lvglDisplay; }
|
|
||||||
|
|
||||||
// DisplayDriver (not supported for EPD)
|
|
||||||
bool supportsDisplayDriver() const override { return false; }
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDriver> _Nullable getDisplayDriver() override {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// EPD specific functions
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a reference to the framebuffer
|
|
||||||
*/
|
|
||||||
uint8_t* getFramebuffer() {
|
|
||||||
return epd_hl_get_framebuffer(&highlevelState);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the screen by flashing it
|
|
||||||
*/
|
|
||||||
void clearScreen();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear an area by flashing it
|
|
||||||
*/
|
|
||||||
void clearArea(EpdRect area);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manually trigger a screen update
|
|
||||||
* @param mode The draw mode to use (defaults to configuration default)
|
|
||||||
* @param temperature Temperature in °C (defaults to configuration default)
|
|
||||||
*/
|
|
||||||
enum EpdDrawError updateScreen(
|
|
||||||
enum EpdDrawMode mode = MODE_UNKNOWN_WAVEFORM,
|
|
||||||
int temperature = -1
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a specific area of the screen
|
|
||||||
* @param area The area to update
|
|
||||||
* @param mode The draw mode to use (defaults to configuration default)
|
|
||||||
* @param temperature Temperature in °C (defaults to configuration default)
|
|
||||||
*/
|
|
||||||
enum EpdDrawError updateArea(
|
|
||||||
EpdRect area,
|
|
||||||
enum EpdDrawMode mode = MODE_UNKNOWN_WAVEFORM,
|
|
||||||
int temperature = -1
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the display to all white
|
|
||||||
*/
|
|
||||||
void setAllWhite();
|
|
||||||
};
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "EpdiyDisplay.h"
|
|
||||||
#include <epd_board.h>
|
|
||||||
#include <epd_display.h>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class to create EPDiy displays with common configurations
|
|
||||||
*/
|
|
||||||
class EpdiyDisplayHelper {
|
|
||||||
public:
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a display for M5Paper S3
|
|
||||||
* @param touch Optional touch device
|
|
||||||
* @param temperature Display temperature in °C (default: 20)
|
|
||||||
* @param drawMode Default draw mode (default: MODE_DU)
|
|
||||||
* @param fullRefresh Use full refresh mode (default: false for partial updates)
|
|
||||||
* @param rotation Display rotation (default: EPD_ROT_PORTRAIT)
|
|
||||||
*/
|
|
||||||
static std::shared_ptr<EpdiyDisplay> createM5PaperS3Display(
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDevice> touch = nullptr,
|
|
||||||
int temperature = 20,
|
|
||||||
enum EpdDrawMode drawMode = MODE_DU,
|
|
||||||
bool fullRefresh = false,
|
|
||||||
enum EpdRotation rotation = EPD_ROT_PORTRAIT
|
|
||||||
) {
|
|
||||||
auto config = std::make_unique<EpdiyDisplay::Configuration>(
|
|
||||||
&epd_board_m5papers3,
|
|
||||||
&ED047TC1,
|
|
||||||
touch,
|
|
||||||
static_cast<EpdInitOptions>(EPD_LUT_1K | EPD_FEED_QUEUE_32),
|
|
||||||
static_cast<const EpdWaveform*>(EPD_BUILTIN_WAVEFORM),
|
|
||||||
temperature,
|
|
||||||
drawMode,
|
|
||||||
fullRefresh,
|
|
||||||
rotation
|
|
||||||
);
|
|
||||||
|
|
||||||
return std::make_shared<EpdiyDisplay>(std::move(config));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -18,7 +18,6 @@ ChargeFromAdcVoltage::ChargeFromAdcVoltage(
|
|||||||
LOGGER.error("ADC channel config failed");
|
LOGGER.error("ADC channel config failed");
|
||||||
|
|
||||||
adc_oneshot_del_unit(adcHandle);
|
adc_oneshot_del_unit(adcHandle);
|
||||||
adcHandle = nullptr;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -30,9 +29,6 @@ ChargeFromAdcVoltage::~ChargeFromAdcVoltage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ChargeFromAdcVoltage::readBatteryVoltageOnce(uint32_t& output) const {
|
bool ChargeFromAdcVoltage::readBatteryVoltageOnce(uint32_t& output) const {
|
||||||
if (adcHandle == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
int raw;
|
int raw;
|
||||||
if (adc_oneshot_read(adcHandle, configuration.adcChannel, &raw) == ESP_OK) {
|
if (adc_oneshot_read(adcHandle, configuration.adcChannel, &raw) == ESP_OK) {
|
||||||
output = configuration.adcMultiplier * ((1000.f * configuration.adcRefVoltage) / 4096.f) * (float)raw;
|
output = configuration.adcMultiplier * ((1000.f * configuration.adcRefVoltage) / 4096.f) * (float)raw;
|
||||||
|
|||||||
@ -34,8 +34,6 @@ public:
|
|||||||
|
|
||||||
~ChargeFromAdcVoltage();
|
~ChargeFromAdcVoltage();
|
||||||
|
|
||||||
bool isInitialized() const { return adcHandle != nullptr; }
|
|
||||||
|
|
||||||
bool readBatteryVoltageSampled(uint32_t& output) const;
|
bool readBatteryVoltageSampled(uint32_t& output) const;
|
||||||
|
|
||||||
bool readBatteryVoltageOnce(uint32_t& output) const;
|
bool readBatteryVoltageOnce(uint32_t& output) const;
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRC_DIRS "Source"
|
SRC_DIRS "Source"
|
||||||
INCLUDE_DIRS "Source"
|
INCLUDE_DIRS "Source"
|
||||||
REQUIRES Tactility epdiy esp_lvgl_port
|
REQUIRES FastEPD TactilityCore Tactility
|
||||||
PRIV_REQUIRES esp_timer
|
|
||||||
)
|
)
|
||||||
256
Drivers/FastEpdDisplay/Source/FastEpdDisplay.cpp
Normal file
256
Drivers/FastEpdDisplay/Source/FastEpdDisplay.cpp
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
#include "FastEpdDisplay.h"
|
||||||
|
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
#include <esp_heap_caps.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define TAG "FastEpdDisplay"
|
||||||
|
|
||||||
|
FastEpdDisplay::~FastEpdDisplay() {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FastEpdDisplay::flush_cb(lv_display_t* disp, const lv_area_t* area, uint8_t* px_map) {
|
||||||
|
auto* self = static_cast<FastEpdDisplay*>(lv_display_get_user_data(disp));
|
||||||
|
|
||||||
|
static uint32_t s_flush_log_counter = 0;
|
||||||
|
|
||||||
|
const int32_t width = area->x2 - area->x1 + 1;
|
||||||
|
const bool grayscale4bpp = self->configuration.use4bppGrayscale;
|
||||||
|
|
||||||
|
// LVGL logical resolution is portrait (540x960). FastEPD PaperS3 native is landscape (960x540).
|
||||||
|
// Keep FastEPD at rotation 0 and do the coordinate transform ourselves.
|
||||||
|
// For a 90° clockwise transform:
|
||||||
|
// x_native = y
|
||||||
|
// y_native = (native_height - 1) - x
|
||||||
|
const int native_width = self->epd.width();
|
||||||
|
const int native_height = self->epd.height();
|
||||||
|
|
||||||
|
// Compute the native line range that will be affected by this flush.
|
||||||
|
// With our mapping y_native = (native_height - 1) - x
|
||||||
|
// So x range maps to y_native range.
|
||||||
|
int start_line = (native_height - 1) - (int)area->x2;
|
||||||
|
int end_line = (native_height - 1) - (int)area->x1;
|
||||||
|
if (start_line > end_line) {
|
||||||
|
const int tmp = start_line;
|
||||||
|
start_line = end_line;
|
||||||
|
end_line = tmp;
|
||||||
|
}
|
||||||
|
if (start_line < 0) start_line = 0;
|
||||||
|
if (end_line >= native_height) end_line = native_height - 1;
|
||||||
|
|
||||||
|
for (int32_t y = area->y1; y <= area->y2; y++) {
|
||||||
|
for (int32_t x = area->x1; x <= area->x2; x++) {
|
||||||
|
const uint8_t gray8 = px_map[(y - area->y1) * width + (x - area->x1)];
|
||||||
|
const uint8_t color = grayscale4bpp ? (uint8_t)(gray8 >> 4) : (uint8_t)((gray8 > 127) ? BBEP_BLACK : BBEP_WHITE);
|
||||||
|
|
||||||
|
const int x_native = y;
|
||||||
|
const int y_native = (native_height - 1) - x;
|
||||||
|
|
||||||
|
// Be defensive: any out-of-range drawPixelFast will corrupt memory.
|
||||||
|
if (x_native < 0 || x_native >= native_width || y_native < 0 || y_native >= native_height) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
self->epd.drawPixelFast(x_native, y_native, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start_line <= end_line) {
|
||||||
|
(void)self->epd.einkPower(1);
|
||||||
|
self->flushCount++;
|
||||||
|
const uint32_t cadence = self->configuration.fullRefreshEveryNFlushes;
|
||||||
|
const bool requested_full = self->forceNextFullRefresh.exchange(false);
|
||||||
|
const bool do_full = requested_full || ((cadence > 0) && (self->flushCount % cadence == 0));
|
||||||
|
|
||||||
|
const bool should_log = ((++s_flush_log_counter % 25U) == 0U);
|
||||||
|
if (should_log) {
|
||||||
|
LOG_I(TAG, "flush #%lu area=(%ld,%ld)-(%ld,%ld) lines=[%d..%d] full=%d",
|
||||||
|
(unsigned long)self->flushCount,
|
||||||
|
(long)area->x1, (long)area->y1, (long)area->x2, (long)area->y2,
|
||||||
|
start_line, end_line, (int)do_full);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (do_full) {
|
||||||
|
const int rc = self->epd.fullUpdate(CLEAR_FAST, true, nullptr);
|
||||||
|
if (should_log) {
|
||||||
|
LOG_I(TAG, "fullUpdate rc=%d", rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// After a full update, keep FastEPD's previous/current buffers in sync so that
|
||||||
|
// subsequent partial updates compute correct diffs.
|
||||||
|
const int w = self->epd.width();
|
||||||
|
const int h = self->epd.height();
|
||||||
|
const size_t bytes_per_row = grayscale4bpp
|
||||||
|
? (size_t)(w + 1) / 2
|
||||||
|
: (size_t)(w + 7) / 8;
|
||||||
|
const size_t plane_size = bytes_per_row * (size_t)h;
|
||||||
|
if (self->epd.currentBuffer() && self->epd.previousBuffer()) {
|
||||||
|
memcpy(self->epd.previousBuffer(), self->epd.currentBuffer(), plane_size);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (grayscale4bpp) {
|
||||||
|
// FastEPD partialUpdate only supports 1bpp mode.
|
||||||
|
// For 4bpp we currently do a fullUpdate. Region-based updates are tricky here because
|
||||||
|
// we also manually rotate/transform pixels in flush_cb; a mismatched rect can refresh
|
||||||
|
// the wrong strip of the panel (seen as "split" buttons on the final refresh).
|
||||||
|
const int rc = self->epd.fullUpdate(CLEAR_FAST, true, nullptr);
|
||||||
|
if (should_log) {
|
||||||
|
LOG_I(TAG, "fullUpdate(4bpp) rc=%d", rc);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const int rc = self->epd.partialUpdate(true, start_line, end_line);
|
||||||
|
|
||||||
|
// Keep FastEPD's previous/current buffers in sync after partial updates as well.
|
||||||
|
// This avoids stale diffs where subsequent updates don't visibly apply.
|
||||||
|
const int w = self->epd.width();
|
||||||
|
const int h = self->epd.height();
|
||||||
|
const size_t bytes_per_row = (size_t)(w + 7) / 8;
|
||||||
|
const size_t plane_size = bytes_per_row * (size_t)h;
|
||||||
|
if (rc == BBEP_SUCCESS && self->epd.currentBuffer() && self->epd.previousBuffer()) {
|
||||||
|
memcpy(self->epd.previousBuffer(), self->epd.currentBuffer(), plane_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (should_log) {
|
||||||
|
LOG_I(TAG, "partialUpdate rc=%d", rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_display_flush_ready(disp);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FastEpdDisplay::start() {
|
||||||
|
if (initialized) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int rc = epd.initPanel(BB_PANEL_M5PAPERS3, configuration.busSpeedHz);
|
||||||
|
if (rc != BBEP_SUCCESS) {
|
||||||
|
LOG_E(TAG, "FastEPD initPanel failed rc=%d", rc);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_I(TAG, "FastEPD native size %dx%d", epd.width(), epd.height());
|
||||||
|
|
||||||
|
const int desired_mode = configuration.use4bppGrayscale ? BB_MODE_4BPP : BB_MODE_1BPP;
|
||||||
|
if (epd.setMode(desired_mode) != BBEP_SUCCESS) {
|
||||||
|
LOG_E(TAG, "FastEPD setMode(%d) failed", desired_mode);
|
||||||
|
epd.deInit();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep FastEPD at rotation 0. LVGL-to-native mapping is handled in flush_cb.
|
||||||
|
|
||||||
|
// Ensure previous/current buffers are in sync and the panel starts from a known state.
|
||||||
|
if (epd.einkPower(1) != BBEP_SUCCESS) {
|
||||||
|
LOG_W(TAG, "FastEPD einkPower(1) failed");
|
||||||
|
} else {
|
||||||
|
epd.fillScreen(configuration.use4bppGrayscale ? 0x0F : BBEP_WHITE);
|
||||||
|
|
||||||
|
const int native_width = epd.width();
|
||||||
|
const int native_height = epd.height();
|
||||||
|
const size_t bytes_per_row = configuration.use4bppGrayscale
|
||||||
|
? (size_t)(native_width + 1) / 2
|
||||||
|
: (size_t)(native_width + 7) / 8;
|
||||||
|
const size_t plane_size = bytes_per_row * (size_t)native_height;
|
||||||
|
|
||||||
|
if (epd.currentBuffer() && epd.previousBuffer()) {
|
||||||
|
memcpy(epd.previousBuffer(), epd.currentBuffer(), plane_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (epd.fullUpdate(CLEAR_FAST, true, nullptr) != BBEP_SUCCESS) {
|
||||||
|
LOG_W(TAG, "FastEPD fullUpdate failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FastEpdDisplay::stop() {
|
||||||
|
if (lvglDisplay) {
|
||||||
|
stopLvgl();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initialized) {
|
||||||
|
epd.deInit();
|
||||||
|
initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FastEpdDisplay::startLvgl() {
|
||||||
|
if (lvglDisplay != nullptr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
lvglDisplay = lv_display_create(configuration.horizontalResolution, configuration.verticalResolution);
|
||||||
|
if (lvglDisplay == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_display_set_color_format(lvglDisplay, LV_COLOR_FORMAT_L8);
|
||||||
|
|
||||||
|
if (lv_display_get_rotation(lvglDisplay) != LV_DISPLAY_ROTATION_0) {
|
||||||
|
lv_display_set_rotation(lvglDisplay, LV_DISPLAY_ROTATION_0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t pixel_count = (uint32_t)(configuration.horizontalResolution * configuration.verticalResolution / 10);
|
||||||
|
const uint32_t buf_size = pixel_count * (uint32_t)lv_color_format_get_size(LV_COLOR_FORMAT_L8);
|
||||||
|
lvglBufSize = buf_size;
|
||||||
|
|
||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
lvglBuf1 = heap_caps_malloc(buf_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||||
|
#else
|
||||||
|
lvglBuf1 = malloc(buf_size);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (lvglBuf1 == nullptr) {
|
||||||
|
lv_display_delete(lvglDisplay);
|
||||||
|
lvglDisplay = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lvglBuf2 = nullptr;
|
||||||
|
lv_display_set_buffers(lvglDisplay, lvglBuf1, lvglBuf2, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
|
||||||
|
|
||||||
|
lv_display_set_user_data(lvglDisplay, this);
|
||||||
|
lv_display_set_flush_cb(lvglDisplay, FastEpdDisplay::flush_cb);
|
||||||
|
|
||||||
|
if (configuration.touch && configuration.touch->supportsLvgl()) {
|
||||||
|
configuration.touch->startLvgl(lvglDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FastEpdDisplay::stopLvgl() {
|
||||||
|
if (lvglDisplay) {
|
||||||
|
if (configuration.touch) {
|
||||||
|
configuration.touch->stopLvgl();
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_display_delete(lvglDisplay);
|
||||||
|
lvglDisplay = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lvglBuf1 != nullptr) {
|
||||||
|
free(lvglBuf1);
|
||||||
|
lvglBuf1 = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lvglBuf2 != nullptr) {
|
||||||
|
free(lvglBuf2);
|
||||||
|
lvglBuf2 = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
lvglBufSize = 0;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
65
Drivers/FastEpdDisplay/Source/FastEpdDisplay.h
Normal file
65
Drivers/FastEpdDisplay/Source/FastEpdDisplay.h
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Tactility/Lock.h>
|
||||||
|
#include <Tactility/hal/display/DisplayDevice.h>
|
||||||
|
#include <Tactility/hal/touch/TouchDevice.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <FastEPD.h>
|
||||||
|
#include <lvgl.h>
|
||||||
|
|
||||||
|
class FastEpdDisplay final : public tt::hal::display::DisplayDevice {
|
||||||
|
public:
|
||||||
|
struct Configuration final {
|
||||||
|
int horizontalResolution;
|
||||||
|
int verticalResolution;
|
||||||
|
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
|
||||||
|
uint32_t busSpeedHz = 20000000;
|
||||||
|
int rotationDegrees = 90;
|
||||||
|
bool use4bppGrayscale = false;
|
||||||
|
uint32_t fullRefreshEveryNFlushes = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
Configuration configuration;
|
||||||
|
std::shared_ptr<tt::Lock> lock;
|
||||||
|
|
||||||
|
lv_display_t* lvglDisplay = nullptr;
|
||||||
|
void* lvglBuf1 = nullptr;
|
||||||
|
void* lvglBuf2 = nullptr;
|
||||||
|
uint32_t lvglBufSize = 0;
|
||||||
|
|
||||||
|
FASTEPD epd;
|
||||||
|
bool initialized = false;
|
||||||
|
uint32_t flushCount = 0;
|
||||||
|
std::atomic_bool forceNextFullRefresh{false};
|
||||||
|
|
||||||
|
static void flush_cb(lv_display_t* disp, const lv_area_t* area, uint8_t* px_map);
|
||||||
|
|
||||||
|
public:
|
||||||
|
FastEpdDisplay(Configuration configuration, std::shared_ptr<tt::Lock> lock)
|
||||||
|
: configuration(std::move(configuration)), lock(std::move(lock)) {}
|
||||||
|
|
||||||
|
~FastEpdDisplay() override;
|
||||||
|
|
||||||
|
void requestFullRefresh() override { forceNextFullRefresh.store(true); }
|
||||||
|
|
||||||
|
std::string getName() const override { return "FastEpdDisplay"; }
|
||||||
|
std::string getDescription() const override { return "FastEPD (bitbank2) E-Ink display driver"; }
|
||||||
|
|
||||||
|
bool start() override;
|
||||||
|
bool stop() override;
|
||||||
|
|
||||||
|
bool supportsLvgl() const override { return true; }
|
||||||
|
bool startLvgl() override;
|
||||||
|
bool stopLvgl() override;
|
||||||
|
|
||||||
|
lv_display_t* getLvglDisplay() const override { return lvglDisplay; }
|
||||||
|
|
||||||
|
bool supportsDisplayDriver() const override { return false; }
|
||||||
|
std::shared_ptr<tt::hal::display::DisplayDriver> getDisplayDriver() override { return nullptr; }
|
||||||
|
|
||||||
|
std::shared_ptr<tt::hal::touch::TouchDevice> getTouchDevice() override {
|
||||||
|
return configuration.touch;
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -56,9 +56,9 @@ dependencies:
|
|||||||
- if: "target == esp32s3"
|
- if: "target == esp32s3"
|
||||||
espressif/esp_lvgl_port: "2.5.0"
|
espressif/esp_lvgl_port: "2.5.0"
|
||||||
lvgl/lvgl: "9.3.0"
|
lvgl/lvgl: "9.3.0"
|
||||||
epdiy:
|
FastEPD:
|
||||||
git: https://github.com/Shadowtrance/epdiy.git
|
git: https://github.com/bitbank2/FastEPD.git
|
||||||
version: 2.0.1
|
version: 1.4.2
|
||||||
rules:
|
rules:
|
||||||
# More hardware might be supported - enable as needed
|
# More hardware might be supported - enable as needed
|
||||||
- if: "target in [esp32s3]"
|
- if: "target in [esp32s3]"
|
||||||
|
|||||||
@ -424,14 +424,6 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(lv_anim_path_ease_out),
|
DEFINE_MODULE_SYMBOL(lv_anim_path_ease_out),
|
||||||
// lv_async
|
// lv_async
|
||||||
DEFINE_MODULE_SYMBOL(lv_async_call),
|
DEFINE_MODULE_SYMBOL(lv_async_call),
|
||||||
// lv_span
|
|
||||||
DEFINE_MODULE_SYMBOL(lv_spangroup_create),
|
|
||||||
DEFINE_MODULE_SYMBOL(lv_spangroup_set_align),
|
|
||||||
DEFINE_MODULE_SYMBOL(lv_spangroup_set_mode),
|
|
||||||
DEFINE_MODULE_SYMBOL(lv_spangroup_add_span),
|
|
||||||
DEFINE_MODULE_SYMBOL(lv_spangroup_refresh),
|
|
||||||
DEFINE_MODULE_SYMBOL(lv_span_get_style),
|
|
||||||
DEFINE_MODULE_SYMBOL(lv_span_set_text),
|
|
||||||
// lv_binfont
|
// lv_binfont
|
||||||
DEFINE_MODULE_SYMBOL(lv_binfont_create),
|
DEFINE_MODULE_SYMBOL(lv_binfont_create),
|
||||||
DEFINE_MODULE_SYMBOL(lv_binfont_destroy),
|
DEFINE_MODULE_SYMBOL(lv_binfont_destroy),
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
description: ESP32 SDMMC
|
description: ESP32 SDMMC
|
||||||
|
|
||||||
compatible: "espressif,esp32-sdmmc"
|
compatible: "espressif,sdmmc"
|
||||||
|
|
||||||
properties:
|
properties:
|
||||||
pin-clk:
|
pin-clk:
|
||||||
@ -43,6 +43,10 @@ properties:
|
|||||||
type: int
|
type: int
|
||||||
required: true
|
required: true
|
||||||
description: Bus width in bits
|
description: Bus width in bits
|
||||||
|
max-open-files:
|
||||||
|
type: int
|
||||||
|
required: true
|
||||||
|
description: Maximum number of open files
|
||||||
wp-active-high:
|
wp-active-high:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
@ -1,11 +1,9 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
#pragma once
|
#pragma once
|
||||||
#if SOC_SDMMC_HOST_SUPPORTED
|
|
||||||
|
|
||||||
#include <sd_protocol_types.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <tactility/drivers/gpio.h>
|
#include <tactility/drivers/gpio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -25,14 +23,11 @@ struct Esp32SdmmcConfig {
|
|||||||
struct GpioPinSpec pin_cd;
|
struct GpioPinSpec pin_cd;
|
||||||
struct GpioPinSpec pin_wp;
|
struct GpioPinSpec pin_wp;
|
||||||
uint8_t bus_width;
|
uint8_t bus_width;
|
||||||
|
uint8_t max_open_files;
|
||||||
bool wp_active_high;
|
bool wp_active_high;
|
||||||
bool enable_uhs;
|
bool enable_uhs;
|
||||||
};
|
};
|
||||||
|
|
||||||
sdmmc_card_t* esp32_sdmmc_get_card(struct Device* device);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
#pragma once
|
|
||||||
#if SOC_SDMMC_HOST_SUPPORTED
|
|
||||||
#include <tactility/filesystem/file_system.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct Esp32SdmmcConfig;
|
|
||||||
typedef void* Esp32SdmmcHandle;
|
|
||||||
|
|
||||||
extern Esp32SdmmcHandle esp32_sdmmc_fs_alloc(const struct Esp32SdmmcConfig* config, const char* mount_path);
|
|
||||||
|
|
||||||
extern void esp32_sdmmc_fs_free(Esp32SdmmcHandle handle);
|
|
||||||
|
|
||||||
extern sdmmc_card_t* esp32_sdmmc_fs_get_card(Esp32SdmmcHandle handle);
|
|
||||||
|
|
||||||
extern const FileSystemApi esp32_sdmmc_fs_api;
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,15 +1,13 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
#if SOC_SDMMC_HOST_SUPPORTED
|
|
||||||
#include <tactility/device.h>
|
#include <tactility/device.h>
|
||||||
#include <tactility/drivers/esp32_sdmmc.h>
|
#include <tactility/drivers/esp32_sdmmc.h>
|
||||||
#include <tactility/drivers/esp32_sdmmc_fs.h>
|
|
||||||
#include <tactility/concurrent/recursive_mutex.h>
|
#include <tactility/concurrent/recursive_mutex.h>
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
|
|
||||||
#include "tactility/drivers/gpio_descriptor.h"
|
#include "tactility/drivers/gpio_descriptor.h"
|
||||||
#include <new>
|
#include <new>
|
||||||
#include <tactility/drivers/esp32_gpio_helpers.h>
|
#include <tactility/drivers/esp32_gpio_helpers.h>
|
||||||
#include <tactility/filesystem/file_system.h>
|
#include <tactility/drivers/esp32_gpio_fs.h>
|
||||||
|
|
||||||
#define TAG "esp32_sdmmc"
|
#define TAG "esp32_sdmmc"
|
||||||
|
|
||||||
@ -21,8 +19,13 @@ extern "C" {
|
|||||||
struct Esp32SdmmcInternal {
|
struct Esp32SdmmcInternal {
|
||||||
RecursiveMutex mutex = {};
|
RecursiveMutex mutex = {};
|
||||||
bool initialized = false;
|
bool initialized = false;
|
||||||
Esp32SdmmcHandle esp32_sdmmc_fs_handle = nullptr;
|
char fs_device_name[16] = "esp32_sdmmc_fs0";
|
||||||
FileSystem* file_system = nullptr;
|
Device fs_device = {
|
||||||
|
.name = fs_device_name,
|
||||||
|
.config = nullptr,
|
||||||
|
.parent = nullptr,
|
||||||
|
.internal = nullptr
|
||||||
|
};
|
||||||
|
|
||||||
// Pin descriptors
|
// Pin descriptors
|
||||||
GpioDescriptor* pin_clk_descriptor = nullptr;
|
GpioDescriptor* pin_clk_descriptor = nullptr;
|
||||||
@ -45,7 +48,6 @@ struct Esp32SdmmcInternal {
|
|||||||
~Esp32SdmmcInternal() {
|
~Esp32SdmmcInternal() {
|
||||||
cleanup_pins();
|
cleanup_pins();
|
||||||
recursive_mutex_destruct(&mutex);
|
recursive_mutex_destruct(&mutex);
|
||||||
if (esp32_sdmmc_fs_handle) free(esp32_sdmmc_fs_handle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void cleanup_pins() {
|
void cleanup_pins() {
|
||||||
@ -95,6 +97,7 @@ static error_t start(Device* device) {
|
|||||||
acquire_pin_or_set_null(sdmmc_config->pin_cd, &data->pin_cd_descriptor) &&
|
acquire_pin_or_set_null(sdmmc_config->pin_cd, &data->pin_cd_descriptor) &&
|
||||||
acquire_pin_or_set_null(sdmmc_config->pin_wp, &data->pin_wp_descriptor);
|
acquire_pin_or_set_null(sdmmc_config->pin_wp, &data->pin_wp_descriptor);
|
||||||
|
|
||||||
|
|
||||||
if (!pins_ok) {
|
if (!pins_ok) {
|
||||||
LOG_E(TAG, "Failed to acquire required one or more pins");
|
LOG_E(TAG, "Failed to acquire required one or more pins");
|
||||||
data->cleanup_pins();
|
data->cleanup_pins();
|
||||||
@ -103,13 +106,11 @@ static error_t start(Device* device) {
|
|||||||
return ERROR_RESOURCE;
|
return ERROR_RESOURCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
data->esp32_sdmmc_fs_handle = esp32_sdmmc_fs_alloc(sdmmc_config, "/sdcard");
|
// Create filesystem child device
|
||||||
data->file_system = file_system_add(&esp32_sdmmc_fs_api, data->esp32_sdmmc_fs_handle);
|
auto* fs_device = &data->fs_device;
|
||||||
if (file_system_mount(data->file_system) != ERROR_NONE) {
|
fs_device->parent = device;
|
||||||
// Error is not recoverable at the time, but it might be recoverable later,
|
fs_device->config = sdmmc_config;
|
||||||
// so we don't return start() failure.
|
check(device_construct_add_start(fs_device, "espressif,esp32-sdmmc-fs") == ERROR_NONE);
|
||||||
LOG_E(TAG, "Failed to mount SD card filesystem");
|
|
||||||
}
|
|
||||||
|
|
||||||
data->initialized = true;
|
data->initialized = true;
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
@ -120,19 +121,11 @@ static error_t stop(Device* device) {
|
|||||||
auto* data = GET_DATA(device);
|
auto* data = GET_DATA(device);
|
||||||
auto* dts_config = GET_CONFIG(device);
|
auto* dts_config = GET_CONFIG(device);
|
||||||
|
|
||||||
if (file_system_is_mounted(data->file_system)) {
|
// Create filesystem child device
|
||||||
if (file_system_unmount(data->file_system) != ERROR_NONE) {
|
auto* fs_device = &data->fs_device;
|
||||||
LOG_E(TAG, "Failed to unmount SD card filesystem");
|
check(device_stop(fs_device) == ERROR_NONE);
|
||||||
return ERROR_RESOURCE;
|
check(device_remove(fs_device) == ERROR_NONE);
|
||||||
}
|
check(device_destruct(fs_device) == ERROR_NONE);
|
||||||
}
|
|
||||||
|
|
||||||
// Free file system
|
|
||||||
file_system_remove(data->file_system);
|
|
||||||
data->file_system = nullptr;
|
|
||||||
// Free file system data
|
|
||||||
esp32_sdmmc_fs_free(data->esp32_sdmmc_fs_handle);
|
|
||||||
data->esp32_sdmmc_fs_handle = nullptr;
|
|
||||||
|
|
||||||
data->cleanup_pins();
|
data->cleanup_pins();
|
||||||
device_set_driver_data(device, nullptr);
|
device_set_driver_data(device, nullptr);
|
||||||
@ -140,15 +133,9 @@ static error_t stop(Device* device) {
|
|||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
sdmmc_card_t* esp32_sdmmc_get_card(Device* device) {
|
|
||||||
if (!device_is_ready(device)) return nullptr;
|
|
||||||
auto* data = GET_DATA(device);
|
|
||||||
return esp32_sdmmc_fs_get_card(data->esp32_sdmmc_fs_handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern Module platform_esp32_module;
|
extern Module platform_esp32_module;
|
||||||
|
|
||||||
Driver esp32_sdmmc_driver = {
|
Driver esp32_spi_driver = {
|
||||||
.name = "esp32_sdmmc",
|
.name = "esp32_sdmmc",
|
||||||
.compatible = (const char*[]) { "espressif,esp32-sdmmc", nullptr },
|
.compatible = (const char*[]) { "espressif,esp32-sdmmc", nullptr },
|
||||||
.start_device = start,
|
.start_device = start,
|
||||||
@ -160,4 +147,3 @@ Driver esp32_sdmmc_driver = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
|
||||||
@ -1,41 +1,25 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
#if SOC_SDMMC_HOST_SUPPORTED
|
|
||||||
#include <tactility/device.h>
|
#include <tactility/device.h>
|
||||||
#include <tactility/drivers/esp32_sdmmc.h>
|
#include <tactility/drivers/esp32_sdmmc.h>
|
||||||
#include <tactility/drivers/esp32_sdmmc_fs.h>
|
#include <tactility/drivers/file_system.h>
|
||||||
#include <tactility/filesystem/file_system.h>
|
|
||||||
#include <tactility/drivers/gpio_descriptor.h>
|
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
|
|
||||||
#include <driver/sdmmc_host.h>
|
#include <driver/sdmmc_host.h>
|
||||||
#include <esp_vfs_fat.h>
|
#include <esp_vfs_fat.h>
|
||||||
|
#include <new>
|
||||||
#include <sdmmc_cmd.h>
|
#include <sdmmc_cmd.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <tactility/drivers/gpio_descriptor.h>
|
||||||
#if SOC_SD_PWR_CTRL_SUPPORTED
|
|
||||||
#include <sd_pwr_ctrl_by_on_chip_ldo.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define TAG "esp32_sdmmc_fs"
|
#define TAG "esp32_sdmmc_fs"
|
||||||
|
|
||||||
#define GET_DATA(data) static_cast<Esp32SdmmcFsData*>(data)
|
#define GET_DATA(device) ((struct Esp32SdmmcFsInternal*)device_get_driver_data(device))
|
||||||
|
// We re-use the config from the parent device
|
||||||
|
#define GET_CONFIG(device) ((const struct Esp32SdmmcConfig*)device->config)
|
||||||
|
|
||||||
struct Esp32SdmmcFsData {
|
struct Esp32SdmmcFsInternal {
|
||||||
const std::string mount_path;
|
std::string mount_path {};
|
||||||
const Esp32SdmmcConfig* config;
|
sdmmc_card_t* card = nullptr;
|
||||||
sdmmc_card_t* card;
|
|
||||||
#if SOC_SD_PWR_CTRL_SUPPORTED
|
|
||||||
sd_pwr_ctrl_handle_t pwr_ctrl_handle;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Esp32SdmmcFsData(const Esp32SdmmcConfig* config, const std::string& mount_path) :
|
|
||||||
mount_path(mount_path),
|
|
||||||
config(config),
|
|
||||||
card(nullptr)
|
|
||||||
#if SOC_SD_PWR_CTRL_SUPPORTED
|
|
||||||
,pwr_ctrl_handle(nullptr)
|
|
||||||
#endif
|
|
||||||
{}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static gpio_num_t to_native_pin(GpioPinSpec pin_spec) {
|
static gpio_num_t to_native_pin(GpioPinSpec pin_spec) {
|
||||||
@ -45,30 +29,15 @@ static gpio_num_t to_native_pin(GpioPinSpec pin_spec) {
|
|||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static error_t get_path(void* data, char* out_path, size_t out_path_size);
|
error_t mount(Device* device, const char* mount_path) {
|
||||||
|
LOG_I(TAG, "Mounting %s", mount_path);
|
||||||
|
|
||||||
Esp32SdmmcHandle esp32_sdmmc_fs_alloc(const Esp32SdmmcConfig* config, const char* mount_path) {
|
auto* data = GET_DATA(device);
|
||||||
return new(std::nothrow) Esp32SdmmcFsData(config, mount_path);
|
auto* config = GET_CONFIG(device);
|
||||||
}
|
|
||||||
|
|
||||||
sdmmc_card_t* esp32_sdmmc_fs_get_card(Esp32SdmmcHandle handle) {
|
|
||||||
return GET_DATA(handle)->card;
|
|
||||||
}
|
|
||||||
|
|
||||||
void esp32_sdmmc_fs_free(Esp32SdmmcHandle handle) {
|
|
||||||
auto* fs_data = GET_DATA(handle);
|
|
||||||
delete fs_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t mount(void* data) {
|
|
||||||
auto* fs_data = GET_DATA(data);
|
|
||||||
auto* config = fs_data->config;
|
|
||||||
|
|
||||||
LOG_I(TAG, "Mounting %s", fs_data->mount_path.c_str());
|
|
||||||
|
|
||||||
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||||
.format_if_mount_failed = false,
|
.format_if_mount_failed = false,
|
||||||
.max_files = 4,
|
.max_files = config->max_open_files,
|
||||||
.allocation_unit_size = 0, // Default is sector size
|
.allocation_unit_size = 0, // Default is sector size
|
||||||
.disk_status_check_enable = false,
|
.disk_status_check_enable = false,
|
||||||
.use_one_fat = false
|
.use_one_fat = false
|
||||||
@ -76,18 +45,6 @@ static error_t mount(void* data) {
|
|||||||
|
|
||||||
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
|
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
|
||||||
|
|
||||||
#if SOC_SD_PWR_CTRL_SUPPORTED
|
|
||||||
sd_pwr_ctrl_ldo_config_t ldo_config = {
|
|
||||||
.ldo_chan_id = 4, // LDO4 is typically used for SDMMC on ESP32-S3
|
|
||||||
};
|
|
||||||
esp_err_t pwr_err = sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &data->pwr_ctrl_handle);
|
|
||||||
if (pwr_err != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to create SD power control driver, err=0x%x", pwr_err);
|
|
||||||
return ERROR_NOT_SUPPORTED;
|
|
||||||
}
|
|
||||||
host.pwr_ctrl_handle = data->pwr_ctrl_handle;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
uint32_t slot_config_flags = 0;
|
uint32_t slot_config_flags = 0;
|
||||||
if (config->enable_uhs) slot_config_flags |= SDMMC_SLOT_FLAG_UHS1;
|
if (config->enable_uhs) slot_config_flags |= SDMMC_SLOT_FLAG_UHS1;
|
||||||
if (config->wp_active_high) slot_config_flags |= SDMMC_SLOT_FLAG_WP_ACTIVE_HIGH;
|
if (config->wp_active_high) slot_config_flags |= SDMMC_SLOT_FLAG_WP_ACTIVE_HIGH;
|
||||||
@ -109,71 +66,97 @@ static error_t mount(void* data) {
|
|||||||
.flags = slot_config_flags
|
.flags = slot_config_flags
|
||||||
};
|
};
|
||||||
|
|
||||||
esp_err_t result = esp_vfs_fat_sdmmc_mount(fs_data->mount_path.c_str(), &host, &slot_config, &mount_config, &fs_data->card);
|
esp_err_t result = esp_vfs_fat_sdmmc_mount(mount_path, &host, &slot_config, &mount_config, &data->card);
|
||||||
|
|
||||||
if (result != ESP_OK || fs_data->card == nullptr) {
|
if (result != ESP_OK || data->card == nullptr) {
|
||||||
if (result == ESP_FAIL) {
|
if (result == ESP_FAIL) {
|
||||||
LOG_E(TAG, "Mounting failed. Ensure the card is formatted with FAT.");
|
LOG_E(TAG, "Mounting failed. Ensure the card is formatted with FAT.");
|
||||||
} else {
|
} else {
|
||||||
LOG_E(TAG, "Mounting failed: %s", esp_err_to_name(result));
|
LOG_E(TAG, "Mounting failed: %s", esp_err_to_name(result));
|
||||||
}
|
}
|
||||||
#if SOC_SD_PWR_CTRL_SUPPORTED
|
|
||||||
if (data->pwr_ctrl_handle) {
|
|
||||||
sd_pwr_ctrl_del_on_chip_ldo(data->pwr_ctrl_handle);
|
|
||||||
data->pwr_ctrl_handle = nullptr;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return ERROR_UNDEFINED;
|
return ERROR_UNDEFINED;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_I(TAG, "Mounted %s", fs_data->mount_path.c_str());
|
data->mount_path = mount_path;
|
||||||
|
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static error_t unmount(void* data) {
|
error_t unmount(Device* device) {
|
||||||
auto* fs_data = GET_DATA(data);
|
auto* data = GET_DATA(device);
|
||||||
LOG_I(TAG, "Unmounting %s", fs_data->mount_path.c_str());
|
|
||||||
|
|
||||||
if (esp_vfs_fat_sdcard_unmount(fs_data->mount_path.c_str(), fs_data->card) != ESP_OK) {
|
if (esp_vfs_fat_sdcard_unmount(data->mount_path.c_str(), data->card) != ESP_OK) {
|
||||||
LOG_E(TAG, "Unmount failed for %s", fs_data->mount_path.c_str());
|
LOG_E(TAG, "Unmount failed for %s", data->mount_path.c_str());
|
||||||
return ERROR_UNDEFINED;
|
return ERROR_UNDEFINED;
|
||||||
}
|
}
|
||||||
|
|
||||||
fs_data->card = nullptr;
|
LOG_I(TAG, "Unmounted %s", data->mount_path.c_str());
|
||||||
|
data->mount_path = "";
|
||||||
|
data->card = nullptr;
|
||||||
|
|
||||||
#if SOC_SD_PWR_CTRL_SUPPORTED
|
return ERROR_NONE;
|
||||||
if (data->pwr_ctrl_handle) {
|
}
|
||||||
sd_pwr_ctrl_del_on_chip_ldo(data->pwr_ctrl_handle);
|
|
||||||
data->pwr_ctrl_handle = nullptr;
|
bool is_mounted(Device* device) {
|
||||||
|
const auto* data = GET_DATA(device);
|
||||||
|
return data != nullptr && data->card != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t get_mount_path(Device* device, char* out_path, size_t out_path_size) {
|
||||||
|
const auto* data = GET_DATA(device);
|
||||||
|
if (data == nullptr || data->card == nullptr) return ERROR_INVALID_STATE;
|
||||||
|
if (data->mount_path.size() >= out_path_size) return ERROR_BUFFER_OVERFLOW;
|
||||||
|
strncpy(out_path, data->mount_path.c_str(), out_path_size);
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t start(Device* device) {
|
||||||
|
LOG_I(TAG, "start %s", device->name);
|
||||||
|
auto* data = new (std::nothrow) Esp32SdmmcFsInternal();
|
||||||
|
if (!data) return ERROR_OUT_OF_MEMORY;
|
||||||
|
|
||||||
|
device_set_driver_data(device, data);
|
||||||
|
|
||||||
|
if (mount(device, "/sdcard") != ERROR_NONE) {
|
||||||
|
LOG_E(TAG, "Failed to mount SD card");
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
LOG_I(TAG, "Unmounted %s", fs_data->mount_path.c_str());
|
|
||||||
|
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool is_mounted(void* data) {
|
static error_t stop(Device* device) {
|
||||||
const auto* fs_data = GET_DATA(data);
|
ESP_LOGI(TAG, "stop %s", device->name);
|
||||||
if (fs_data == nullptr || fs_data->card == nullptr) return false;
|
auto* driver_data = GET_DATA(device);
|
||||||
return sdmmc_get_status(fs_data->card) == ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t get_path(void* data, char* out_path, size_t out_path_size) {
|
if (is_mounted(device)) {
|
||||||
const auto* fs_data = GET_DATA(data);
|
if (unmount(device) != ERROR_NONE) {
|
||||||
if (fs_data == nullptr || fs_data->card == nullptr) return ERROR_INVALID_STATE;
|
LOG_E(TAG, "Failed to unmount SD card");
|
||||||
if (fs_data->mount_path.size() >= out_path_size) return ERROR_BUFFER_OVERFLOW;
|
}
|
||||||
strncpy(out_path, fs_data->mount_path.c_str(), out_path_size);
|
}
|
||||||
|
|
||||||
|
device_set_driver_data(device, nullptr);
|
||||||
|
delete driver_data;
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FileSystemApi esp32_sdmmc_fs_api = {
|
extern Module platform_esp32_module;
|
||||||
|
|
||||||
|
static const FileSystemApi sdmmc_fs_api = {
|
||||||
.mount = mount,
|
.mount = mount,
|
||||||
.unmount = unmount,
|
.unmount = unmount,
|
||||||
.is_mounted = is_mounted,
|
.is_mounted = is_mounted,
|
||||||
.get_path = get_path
|
.get_mount_path = get_mount_path
|
||||||
|
};
|
||||||
|
|
||||||
|
Driver esp32_sdmmc_fs_driver = {
|
||||||
|
.name = "esp32_sdmmc_fs",
|
||||||
|
.compatible = (const char*[]) { "espressif,esp32-sdmmc-fs", nullptr },
|
||||||
|
.start_device = start,
|
||||||
|
.stop_device = stop,
|
||||||
|
.api = &sdmmc_fs_api,
|
||||||
|
.device_type = &FILE_SYSTEM_TYPE,
|
||||||
|
.owner = &platform_esp32_module,
|
||||||
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
|
||||||
@ -7,9 +7,8 @@ extern "C" {
|
|||||||
extern Driver esp32_gpio_driver;
|
extern Driver esp32_gpio_driver;
|
||||||
extern Driver esp32_i2c_driver;
|
extern Driver esp32_i2c_driver;
|
||||||
extern Driver esp32_i2s_driver;
|
extern Driver esp32_i2s_driver;
|
||||||
#if SOC_SDMMC_HOST_SUPPORTED
|
|
||||||
extern Driver esp32_sdmmc_driver;
|
extern Driver esp32_sdmmc_driver;
|
||||||
#endif
|
extern Driver esp32_sdmmc_fs_driver;
|
||||||
extern Driver esp32_spi_driver;
|
extern Driver esp32_spi_driver;
|
||||||
extern Driver esp32_uart_driver;
|
extern Driver esp32_uart_driver;
|
||||||
|
|
||||||
@ -19,9 +18,8 @@ static error_t start() {
|
|||||||
check(driver_construct_add(&esp32_gpio_driver) == ERROR_NONE);
|
check(driver_construct_add(&esp32_gpio_driver) == ERROR_NONE);
|
||||||
check(driver_construct_add(&esp32_i2c_driver) == ERROR_NONE);
|
check(driver_construct_add(&esp32_i2c_driver) == ERROR_NONE);
|
||||||
check(driver_construct_add(&esp32_i2s_driver) == ERROR_NONE);
|
check(driver_construct_add(&esp32_i2s_driver) == ERROR_NONE);
|
||||||
#if SOC_SDMMC_HOST_SUPPORTED
|
|
||||||
check(driver_construct_add(&esp32_sdmmc_driver) == ERROR_NONE);
|
check(driver_construct_add(&esp32_sdmmc_driver) == ERROR_NONE);
|
||||||
#endif
|
check(driver_construct_add(&esp32_sdmmc_fs_driver) == ERROR_NONE);
|
||||||
check(driver_construct_add(&esp32_spi_driver) == ERROR_NONE);
|
check(driver_construct_add(&esp32_spi_driver) == ERROR_NONE);
|
||||||
check(driver_construct_add(&esp32_uart_driver) == ERROR_NONE);
|
check(driver_construct_add(&esp32_uart_driver) == ERROR_NONE);
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
@ -33,9 +31,8 @@ static error_t stop() {
|
|||||||
check(driver_remove_destruct(&esp32_gpio_driver) == ERROR_NONE);
|
check(driver_remove_destruct(&esp32_gpio_driver) == ERROR_NONE);
|
||||||
check(driver_remove_destruct(&esp32_i2c_driver) == ERROR_NONE);
|
check(driver_remove_destruct(&esp32_i2c_driver) == ERROR_NONE);
|
||||||
check(driver_remove_destruct(&esp32_i2s_driver) == ERROR_NONE);
|
check(driver_remove_destruct(&esp32_i2s_driver) == ERROR_NONE);
|
||||||
#if SOC_SDMMC_HOST_SUPPORTED
|
|
||||||
check(driver_remove_destruct(&esp32_sdmmc_driver) == ERROR_NONE);
|
check(driver_remove_destruct(&esp32_sdmmc_driver) == ERROR_NONE);
|
||||||
#endif
|
check(driver_remove_destruct(&esp32_sdmmc_fs_driver) == ERROR_NONE);
|
||||||
check(driver_remove_destruct(&esp32_spi_driver) == ERROR_NONE);
|
check(driver_remove_destruct(&esp32_spi_driver) == ERROR_NONE);
|
||||||
check(driver_remove_destruct(&esp32_uart_driver) == ERROR_NONE);
|
check(driver_remove_destruct(&esp32_uart_driver) == ERROR_NONE);
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
|
|||||||
@ -21,6 +21,6 @@ constexpr auto* MOUNT_POINT_DATA = "/data";
|
|||||||
constexpr auto* MOUNT_POINT_DATA = "data";
|
constexpr auto* MOUNT_POINT_DATA = "data";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::vector<dirent> getFileSystemDirents();
|
std::vector<dirent> getMountPoints();
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@ -2,19 +2,10 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <tactility/filesystem/file_system.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace tt {
|
namespace tt {
|
||||||
|
|
||||||
bool findFirstMountedSdCardPath(std::string& path);
|
bool findFirstMountedSdCardPath(std::string& path);
|
||||||
|
|
||||||
bool hasMountedSdCard();
|
|
||||||
|
|
||||||
FileSystem* findFirstMountedSdcardFileSystem();
|
|
||||||
|
|
||||||
FileSystem* findFirstSdcardFileSystem();
|
|
||||||
|
|
||||||
std::string getSystemRootPath();
|
std::string getSystemRootPath();
|
||||||
|
|
||||||
std::string getTempPath();
|
std::string getTempPath();
|
||||||
|
|||||||
@ -2,11 +2,9 @@
|
|||||||
|
|
||||||
#include <tactility/hal/Device.h>
|
#include <tactility/hal/Device.h>
|
||||||
|
|
||||||
#include <Tactility/Lock.h>
|
|
||||||
#include <Tactility/TactilityCore.h>
|
#include <Tactility/TactilityCore.h>
|
||||||
|
#include <Tactility/Lock.h>
|
||||||
|
|
||||||
|
|
||||||
struct FileSystem;
|
|
||||||
namespace tt::hal::sdcard {
|
namespace tt::hal::sdcard {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,12 +31,11 @@ public:
|
|||||||
private:
|
private:
|
||||||
|
|
||||||
MountBehaviour mountBehaviour;
|
MountBehaviour mountBehaviour;
|
||||||
FileSystem* fileSystem;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
explicit SdCardDevice(MountBehaviour mountBehaviour);
|
explicit SdCardDevice(MountBehaviour mountBehaviour) : mountBehaviour(mountBehaviour) {}
|
||||||
~SdCardDevice() override;
|
~SdCardDevice() override = default;
|
||||||
|
|
||||||
Type getType() const final { return Type::SdCard; };
|
Type getType() const final { return Type::SdCard; };
|
||||||
|
|
||||||
|
|||||||
92
Tactility/Include/Tactility/hal/sdcard/SdmmcDevice.h
Normal file
92
Tactility/Include/Tactility/hal/sdcard/SdmmcDevice.h
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "SdCardDevice.h"
|
||||||
|
|
||||||
|
#include <Tactility/RecursiveMutex.h>
|
||||||
|
#include <tactility/hal/Device.h>
|
||||||
|
#include <sd_protocol_types.h>
|
||||||
|
#include <soc/gpio_num.h>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace tt::hal::sdcard {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SD card interface for the SDMMC interface.
|
||||||
|
*/
|
||||||
|
class SdmmcDevice final : public SdCardDevice {
|
||||||
|
|
||||||
|
std::shared_ptr<RecursiveMutex> mutex = std::make_shared<RecursiveMutex>();
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
Config(
|
||||||
|
gpio_num_t pinClock,
|
||||||
|
gpio_num_t pinCmd,
|
||||||
|
gpio_num_t pinD0,
|
||||||
|
gpio_num_t pinD1,
|
||||||
|
gpio_num_t pinD2,
|
||||||
|
gpio_num_t pinD3,
|
||||||
|
MountBehaviour mountBehaviourAtBoot,
|
||||||
|
uint8_t busWidth = 4
|
||||||
|
) :
|
||||||
|
pinClock(pinClock),
|
||||||
|
pinCmd(pinCmd),
|
||||||
|
pinD0(pinD0),
|
||||||
|
pinD1(pinD1),
|
||||||
|
pinD2(pinD2),
|
||||||
|
pinD3(pinD3),
|
||||||
|
mountBehaviourAtBoot(mountBehaviourAtBoot),
|
||||||
|
busWidth(busWidth)
|
||||||
|
{}
|
||||||
|
|
||||||
|
gpio_num_t pinClock;
|
||||||
|
gpio_num_t pinCmd;
|
||||||
|
gpio_num_t pinD0;
|
||||||
|
gpio_num_t pinD1;
|
||||||
|
gpio_num_t pinD2;
|
||||||
|
gpio_num_t pinD3;
|
||||||
|
MountBehaviour mountBehaviourAtBoot;
|
||||||
|
uint8_t busWidth;
|
||||||
|
bool formatOnMountFailed = false;
|
||||||
|
uint16_t maxOpenFiles = 4;
|
||||||
|
uint16_t allocUnitSize = 16 * 1024;
|
||||||
|
bool statusCheckEnabled = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::string mountPath;
|
||||||
|
sdmmc_card_t* card = nullptr;
|
||||||
|
std::shared_ptr<Config> config;
|
||||||
|
|
||||||
|
bool applyGpioWorkAround();
|
||||||
|
bool mountInternal(const std::string& mountPath);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit SdmmcDevice(std::unique_ptr<Config> config) : SdCardDevice(config->mountBehaviourAtBoot),
|
||||||
|
config(std::move(config))
|
||||||
|
{}
|
||||||
|
|
||||||
|
std::string getName() const override { return "SDMMC"; }
|
||||||
|
std::string getDescription() const override { return "SD card via SDMMC interface"; }
|
||||||
|
|
||||||
|
bool mount(const std::string& mountPath) override;
|
||||||
|
bool unmount() override;
|
||||||
|
std::string getMountPath() const override { return mountPath; }
|
||||||
|
|
||||||
|
std::shared_ptr<Lock> getLock() const override { return mutex; }
|
||||||
|
|
||||||
|
State getState(TickType_t timeout) const override;
|
||||||
|
|
||||||
|
/** return card when mounted, otherwise return nullptr */
|
||||||
|
sdmmc_card_t* getCard() { return card; }
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -2,37 +2,56 @@
|
|||||||
|
|
||||||
#include "Tactility/TactilityConfig.h"
|
#include "Tactility/TactilityConfig.h"
|
||||||
#include <tactility/hal/Device.h>
|
#include <tactility/hal/Device.h>
|
||||||
|
#include "Tactility/hal/sdcard/SdCardDevice.h"
|
||||||
|
|
||||||
#include <Tactility/file/File.h>
|
#include <Tactility/file/File.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <dirent.h>
|
|
||||||
#include <tactility/filesystem/file_system.h>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
namespace tt::file {
|
namespace tt::file {
|
||||||
|
|
||||||
std::vector<dirent> getFileSystemDirents() {
|
std::vector<dirent> getMountPoints() {
|
||||||
std::vector<dirent> dir_entries;
|
std::vector<dirent> dir_entries;
|
||||||
dir_entries.clear();
|
dir_entries.clear();
|
||||||
|
|
||||||
file_system_for_each(&dir_entries, [](auto* fs, void* context) {
|
// Data partition
|
||||||
if (!file_system_is_mounted(fs)) return true;
|
auto data_dirent = dirent{
|
||||||
char path[128];
|
.d_ino = 1,
|
||||||
if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true;
|
.d_type = TT_DT_DIR,
|
||||||
auto mount_name = std::string(path).substr(1);
|
.d_name = { 0 }
|
||||||
if (!config::SHOW_SYSTEM_PARTITION && mount_name.starts_with(SYSTEM_PARTITION_NAME)) return true;
|
};
|
||||||
|
strcpy(data_dirent.d_name, DATA_PARTITION_NAME);
|
||||||
|
dir_entries.push_back(data_dirent);
|
||||||
|
|
||||||
|
// SD card partitions
|
||||||
|
auto sdcards = tt::hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||||
|
for (auto& sdcard : sdcards) {
|
||||||
|
auto state = sdcard->getState();
|
||||||
|
if (state == hal::sdcard::SdCardDevice::State::Mounted) {
|
||||||
|
auto mount_name = sdcard->getMountPath().substr(1);
|
||||||
auto dir_entry = dirent {
|
auto dir_entry = dirent {
|
||||||
.d_ino = 2,
|
.d_ino = 2,
|
||||||
.d_type = TT_DT_DIR,
|
.d_type = TT_DT_DIR,
|
||||||
.d_name = { 0 }
|
.d_name = { 0 }
|
||||||
};
|
};
|
||||||
auto& dir_entries = *static_cast<std::vector<dirent>*>(context);
|
assert(mount_name.length() < sizeof(dirent::d_name));
|
||||||
strcpy(dir_entry.d_name, mount_name.c_str());
|
strcpy(dir_entry.d_name, mount_name.c_str());
|
||||||
dir_entries.push_back(dir_entry);
|
dir_entries.push_back(dir_entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
if (config::SHOW_SYSTEM_PARTITION) {
|
||||||
});
|
// System partition
|
||||||
|
auto system_dirent = dirent{
|
||||||
|
.d_ino = 0,
|
||||||
|
.d_type = TT_DT_DIR,
|
||||||
|
.d_name = { 0 }
|
||||||
|
};
|
||||||
|
strcpy(system_dirent.d_name, SYSTEM_PARTITION_NAME);
|
||||||
|
dir_entries.push_back(system_dirent);
|
||||||
|
}
|
||||||
|
|
||||||
return dir_entries;
|
return dir_entries;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,47 +5,11 @@
|
|||||||
|
|
||||||
#include <esp_vfs_fat.h>
|
#include <esp_vfs_fat.h>
|
||||||
#include <nvs_flash.h>
|
#include <nvs_flash.h>
|
||||||
#include <tactility/error.h>
|
|
||||||
#include <tactility/filesystem/file_system.h>
|
|
||||||
|
|
||||||
namespace tt {
|
namespace tt {
|
||||||
|
|
||||||
static const auto LOGGER = Logger("Partitions");
|
static const auto LOGGER = Logger("Partitions");
|
||||||
|
|
||||||
// region file_system stub
|
|
||||||
|
|
||||||
struct PartitionFsData {
|
|
||||||
const char* path;
|
|
||||||
};
|
|
||||||
|
|
||||||
static error_t mount(void* data) {
|
|
||||||
return ERROR_NOT_SUPPORTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t unmount(void* data) {
|
|
||||||
return ERROR_NOT_SUPPORTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool is_mounted(void* data) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t get_path(void* data, char* out_path, size_t out_path_size) {
|
|
||||||
auto* fs_data = static_cast<PartitionFsData*>(data);
|
|
||||||
if (strlen(fs_data->path) >= out_path_size) return ERROR_BUFFER_OVERFLOW;
|
|
||||||
strncpy(out_path, fs_data->path, out_path_size);
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
FileSystemApi partition_fs_api = {
|
|
||||||
.mount = mount,
|
|
||||||
.unmount = unmount,
|
|
||||||
.is_mounted = is_mounted,
|
|
||||||
.get_path = get_path
|
|
||||||
};
|
|
||||||
|
|
||||||
// endregion file_system stub
|
|
||||||
|
|
||||||
static esp_err_t initNvsFlashSafely() {
|
static esp_err_t initNvsFlashSafely() {
|
||||||
esp_err_t result = nvs_flash_init();
|
esp_err_t result = nvs_flash_init();
|
||||||
if (result == ESP_ERR_NVS_NO_FREE_PAGES || result == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
if (result == ESP_ERR_NVS_NO_FREE_PAGES || result == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||||
@ -92,7 +56,6 @@ esp_err_t initPartitionsEsp() {
|
|||||||
LOGGER.error("Failed to mount /system ({})", esp_err_to_name(system_result));
|
LOGGER.error("Failed to mount /system ({})", esp_err_to_name(system_result));
|
||||||
} else {
|
} else {
|
||||||
LOGGER.info("Mounted /system");
|
LOGGER.info("Mounted /system");
|
||||||
file_system_add(&partition_fs_api, new PartitionFsData("/system"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto data_result = esp_vfs_fat_spiflash_mount_rw_wl("/data", "data", &mount_config, &data_wl_handle);
|
auto data_result = esp_vfs_fat_spiflash_mount_rw_wl("/data", "data", &mount_config, &data_wl_handle);
|
||||||
@ -100,7 +63,6 @@ esp_err_t initPartitionsEsp() {
|
|||||||
LOGGER.error("Failed to mount /data ({})", esp_err_to_name(data_result));
|
LOGGER.error("Failed to mount /data ({})", esp_err_to_name(data_result));
|
||||||
} else {
|
} else {
|
||||||
LOGGER.info("Mounted /data");
|
LOGGER.info("Mounted /data");
|
||||||
file_system_add(&partition_fs_api, new PartitionFsData("/data"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return system_result == ESP_OK && data_result == ESP_OK;
|
return system_result == ESP_OK && data_result == ESP_OK;
|
||||||
|
|||||||
@ -2,54 +2,25 @@
|
|||||||
|
|
||||||
#include <Tactility/app/AppManifestParsing.h>
|
#include <Tactility/app/AppManifestParsing.h>
|
||||||
#include <Tactility/MountPoints.h>
|
#include <Tactility/MountPoints.h>
|
||||||
|
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||||
|
|
||||||
#include <format>
|
#include <format>
|
||||||
#include <tactility/filesystem/file_system.h>
|
|
||||||
|
|
||||||
namespace tt {
|
namespace tt {
|
||||||
|
|
||||||
bool findFirstMountedSdCardPath(std::string& path) {
|
bool findFirstMountedSdCardPath(std::string& path) {
|
||||||
auto* fs = findFirstMountedSdcardFileSystem();
|
// const auto sdcards = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||||
if (fs == nullptr) return false;
|
bool is_set = false;
|
||||||
char found_path[128];
|
hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard, [&is_set, &path](const auto& device) {
|
||||||
if (file_system_get_path(fs, found_path, sizeof(found_path)) != ERROR_NONE) return false;
|
if (device->isMounted()) {
|
||||||
path = found_path;
|
path = device->getMountPath();
|
||||||
|
is_set = true;
|
||||||
|
return false; // stop iterating
|
||||||
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
bool hasMountedSdCard() {
|
|
||||||
auto* fs = findFirstMountedSdcardFileSystem();
|
|
||||||
return fs != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
FileSystem* findFirstMountedSdcardFileSystem() {
|
|
||||||
FileSystem* found = nullptr;
|
|
||||||
file_system_for_each(&found, [](auto* fs, void* context) {
|
|
||||||
char path[128];
|
|
||||||
if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true;
|
|
||||||
// TODO: Find a better way to identify SD card paths
|
|
||||||
if (std::string(path).starts_with("/sdcard") && file_system_is_mounted(fs)) {
|
|
||||||
*static_cast<FileSystem**>(context) = fs;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
});
|
});
|
||||||
return found;
|
return is_set;
|
||||||
}
|
|
||||||
|
|
||||||
FileSystem* findFirstSdcardFileSystem() {
|
|
||||||
FileSystem* found = nullptr;
|
|
||||||
file_system_for_each(&found, [](auto* fs, void* context) {
|
|
||||||
char path[128];
|
|
||||||
if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true;
|
|
||||||
// TODO: Find a better way to identify SD card paths
|
|
||||||
if (std::string(path).starts_with("/sdcard")) {
|
|
||||||
*static_cast<FileSystem**>(context) = fs;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
return found;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getSystemRootPath() {
|
std::string getSystemRootPath() {
|
||||||
|
|||||||
@ -8,19 +8,19 @@
|
|||||||
#include <Tactility/Tactility.h>
|
#include <Tactility/Tactility.h>
|
||||||
#include <Tactility/TactilityConfig.h>
|
#include <Tactility/TactilityConfig.h>
|
||||||
|
|
||||||
#include <Tactility/CpuAffinity.h>
|
|
||||||
#include <Tactility/DispatcherThread.h>
|
|
||||||
#include <Tactility/LogMessages.h>
|
|
||||||
#include <Tactility/Logger.h>
|
|
||||||
#include <Tactility/MountPoints.h>
|
|
||||||
#include <Tactility/Paths.h>
|
|
||||||
#include <Tactility/app/AppManifestParsing.h>
|
#include <Tactility/app/AppManifestParsing.h>
|
||||||
#include <Tactility/app/AppRegistration.h>
|
#include <Tactility/app/AppRegistration.h>
|
||||||
|
#include <Tactility/CpuAffinity.h>
|
||||||
|
#include <Tactility/DispatcherThread.h>
|
||||||
#include <Tactility/file/File.h>
|
#include <Tactility/file/File.h>
|
||||||
#include <Tactility/file/FileLock.h>
|
#include <Tactility/file/FileLock.h>
|
||||||
#include <Tactility/file/PropertiesFile.h>
|
#include <Tactility/file/PropertiesFile.h>
|
||||||
#include <Tactility/hal/HalPrivate.h>
|
#include <Tactility/hal/HalPrivate.h>
|
||||||
|
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||||
|
#include <Tactility/Logger.h>
|
||||||
|
#include <Tactility/LogMessages.h>
|
||||||
#include <Tactility/lvgl/LvglPrivate.h>
|
#include <Tactility/lvgl/LvglPrivate.h>
|
||||||
|
#include <Tactility/MountPoints.h>
|
||||||
#include <Tactility/network/NtpPrivate.h>
|
#include <Tactility/network/NtpPrivate.h>
|
||||||
#include <Tactility/service/ServiceManifest.h>
|
#include <Tactility/service/ServiceManifest.h>
|
||||||
#include <Tactility/service/ServiceRegistration.h>
|
#include <Tactility/service/ServiceRegistration.h>
|
||||||
@ -28,7 +28,6 @@
|
|||||||
|
|
||||||
#include <tactility/concurrent/thread.h>
|
#include <tactility/concurrent/thread.h>
|
||||||
#include <tactility/drivers/uart_controller.h>
|
#include <tactility/drivers/uart_controller.h>
|
||||||
#include <tactility/filesystem/file_system.h>
|
|
||||||
#include <tactility/hal_device_module.h>
|
#include <tactility/hal_device_module.h>
|
||||||
#include <tactility/kernel_init.h>
|
#include <tactility/kernel_init.h>
|
||||||
#include <tactility/lvgl_module.h>
|
#include <tactility/lvgl_module.h>
|
||||||
@ -229,18 +228,22 @@ static void registerInstalledApps(const std::string& path) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static void registerInstalledAppsFromFileSystems() {
|
static void registerInstalledAppsFromSdCard(const std::shared_ptr<hal::sdcard::SdCardDevice>& sdcard) {
|
||||||
file_system_for_each(nullptr, [](auto* fs, void* context) {
|
auto sdcard_root_path = sdcard->getMountPath();
|
||||||
if (!file_system_is_mounted(fs)) return true;
|
auto app_path = std::format("{}/app", sdcard_root_path);
|
||||||
char path[128];
|
if (file::isDirectory(app_path)) {
|
||||||
if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true;
|
|
||||||
const auto app_path = std::format("{}/app", path);
|
|
||||||
if (!app_path.starts_with(file::MOUNT_POINT_SYSTEM) && file::isDirectory(app_path)) {
|
|
||||||
LOGGER.info("Registering apps from {}", path);
|
|
||||||
registerInstalledApps(app_path);
|
registerInstalledApps(app_path);
|
||||||
}
|
}
|
||||||
return true;
|
}
|
||||||
});
|
|
||||||
|
static void registerInstalledAppsFromSdCards() {
|
||||||
|
auto sdcard_devices = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||||
|
for (const auto& sdcard : sdcard_devices) {
|
||||||
|
if (sdcard->isMounted()) {
|
||||||
|
LOGGER.info("Registering apps from {}", sdcard->getMountPath());
|
||||||
|
registerInstalledAppsFromSdCard(sdcard);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void registerAndStartSecondaryServices() {
|
static void registerAndStartSecondaryServices() {
|
||||||
@ -263,7 +266,7 @@ static void registerAndStartSecondaryServices() {
|
|||||||
static void registerAndStartPrimaryServices() {
|
static void registerAndStartPrimaryServices() {
|
||||||
LOGGER.info("Registering and starting primary system services");
|
LOGGER.info("Registering and starting primary system services");
|
||||||
addService(service::gps::manifest);
|
addService(service::gps::manifest);
|
||||||
if (hasMountedSdCard()) {
|
if (hal::hasDevice(hal::Device::Type::SdCard)) {
|
||||||
addService(service::sdcard::manifest);
|
addService(service::sdcard::manifest);
|
||||||
}
|
}
|
||||||
addService(service::wifi::manifest);
|
addService(service::wifi::manifest);
|
||||||
@ -298,19 +301,26 @@ void createTempDirectory(const std::string& rootPath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void prepareFileSystems() {
|
void prepareFileSystems() {
|
||||||
file_system_for_each(nullptr, [](auto* fs, void* context) {
|
// Temporary directories for SD cards
|
||||||
if (!file_system_is_mounted(fs)) return true;
|
auto sdcard_devices = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||||
char path[128];
|
for (const auto& sdcard : sdcard_devices) {
|
||||||
if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true;
|
if (sdcard->isMounted()) {
|
||||||
if (std::string(path) == file::MOUNT_POINT_SYSTEM) return true;
|
createTempDirectory(sdcard->getMountPath());
|
||||||
createTempDirectory(path);
|
}
|
||||||
return true;
|
}
|
||||||
});
|
// Temporary directory for /data
|
||||||
|
if (file::isDirectory(file::MOUNT_POINT_DATA)) {
|
||||||
|
createTempDirectory(file::MOUNT_POINT_DATA);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void registerApps() {
|
void registerApps() {
|
||||||
registerInternalApps();
|
registerInternalApps();
|
||||||
registerInstalledAppsFromFileSystems();
|
auto data_apps_path = std::format("{}/app", file::MOUNT_POINT_DATA);
|
||||||
|
if (file::isDirectory(data_apps_path)) {
|
||||||
|
registerInstalledApps(data_apps_path);
|
||||||
|
}
|
||||||
|
registerInstalledAppsFromSdCards();
|
||||||
}
|
}
|
||||||
|
|
||||||
void run(const Configuration& config, Module* dtsModules[], DtsDevice dtsDevices[]) {
|
void run(const Configuration& config, Module* dtsModules[], DtsDevice dtsDevices[]) {
|
||||||
|
|||||||
@ -204,11 +204,7 @@ public:
|
|||||||
lv_obj_set_width(description_label, LV_PCT(100));
|
lv_obj_set_width(description_label, LV_PCT(100));
|
||||||
lv_label_set_long_mode(description_label, LV_LABEL_LONG_MODE_WRAP);
|
lv_label_set_long_mode(description_label, LV_LABEL_LONG_MODE_WRAP);
|
||||||
if (!entry.appDescription.empty()) {
|
if (!entry.appDescription.empty()) {
|
||||||
std::string description = entry.appDescription;
|
lv_label_set_text(description_label, entry.appDescription.c_str());
|
||||||
for (size_t pos = 0; (pos = description.find("\\n", pos)) != std::string::npos;) {
|
|
||||||
description.replace(pos, 2, "\n");
|
|
||||||
}
|
|
||||||
lv_label_set_text(description_label, description.c_str());
|
|
||||||
} else {
|
} else {
|
||||||
lv_label_set_text(description_label, "This app has no description yet.");
|
lv_label_set_text(description_label, "This app has no description yet.");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
#include <Tactility/app/files/State.h>
|
#include <Tactility/app/files/State.h>
|
||||||
#include <Tactility/app/AppContext.h>
|
#include <Tactility/app/AppContext.h>
|
||||||
|
|
||||||
|
#include <Tactility/Assets.h>
|
||||||
#include <Tactility/service/loader/Loader.h>
|
#include <Tactility/service/loader/Loader.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|||||||
@ -51,7 +51,7 @@ bool State::setEntriesForPath(const std::string& path) {
|
|||||||
bool get_mount_points = (kernel::getPlatform() == kernel::PlatformEsp) && (path == "/");
|
bool get_mount_points = (kernel::getPlatform() == kernel::PlatformEsp) && (path == "/");
|
||||||
if (get_mount_points) {
|
if (get_mount_points) {
|
||||||
LOGGER.info("Setting custom root");
|
LOGGER.info("Setting custom root");
|
||||||
dir_entries = file::getFileSystemDirents();
|
dir_entries = file::getMountPoints();
|
||||||
current_path = path;
|
current_path = path;
|
||||||
selected_child_entry = "";
|
selected_child_entry = "";
|
||||||
action = ActionNone;
|
action = ActionNone;
|
||||||
|
|||||||
@ -275,10 +275,6 @@ void View::onDirEntryPressed(uint32_t index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void View::onDirEntryLongPressed(int32_t index) {
|
void View::onDirEntryLongPressed(int32_t index) {
|
||||||
if (state->getCurrentPath() == "/") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dirent dir_entry;
|
dirent dir_entry;
|
||||||
if (!resolveDirentFromListIndex(index, dir_entry)) {
|
if (!resolveDirentFromListIndex(index, dir_entry)) {
|
||||||
return;
|
return;
|
||||||
@ -456,7 +452,7 @@ void View::update(size_t start_index) {
|
|||||||
|
|
||||||
if (!is_root && last_loaded_index < total_entries) {
|
if (!is_root && last_loaded_index < total_entries) {
|
||||||
if (total_entries > current_start_index &&
|
if (total_entries > current_start_index &&
|
||||||
(total_entries - current_start_index) > MAX_BATCH) {
|
+ (total_entries - current_start_index) > MAX_BATCH) {
|
||||||
auto* next_btn = lv_list_add_btn(dir_entry_list, LV_SYMBOL_RIGHT, "Next");
|
auto* next_btn = lv_list_add_btn(dir_entry_list, LV_SYMBOL_RIGHT, "Next");
|
||||||
lv_obj_add_event_cb(next_btn, [](lv_event_t* event) {
|
lv_obj_add_event_cb(next_btn, [](lv_event_t* event) {
|
||||||
auto* view = static_cast<View*>(lv_event_get_user_data(event));
|
auto* view = static_cast<View*>(lv_event_get_user_data(event));
|
||||||
@ -553,7 +549,7 @@ void View::onResult(LaunchId launchId, Result result, std::unique_ptr<Bundle> bu
|
|||||||
} else if (file::isFile(filepath)) {
|
} else if (file::isFile(filepath)) {
|
||||||
auto lock = file::getLock(filepath);
|
auto lock = file::getLock(filepath);
|
||||||
lock->lock();
|
lock->lock();
|
||||||
if (remove(filepath.c_str()) != 0) {
|
if (remove(filepath.c_str()) <= 0) {
|
||||||
LOGGER.warn("Failed to delete {}", filepath);
|
LOGGER.warn("Failed to delete {}", filepath);
|
||||||
}
|
}
|
||||||
lock->unlock();
|
lock->unlock();
|
||||||
|
|||||||
@ -43,7 +43,7 @@ bool State::setEntriesForPath(const std::string& path) {
|
|||||||
bool show_custom_root = (kernel::getPlatform() == kernel::PlatformEsp) && (path == "/");
|
bool show_custom_root = (kernel::getPlatform() == kernel::PlatformEsp) && (path == "/");
|
||||||
if (show_custom_root) {
|
if (show_custom_root) {
|
||||||
LOGGER.info("Setting custom root");
|
LOGGER.info("Setting custom root");
|
||||||
dir_entries = file::getFileSystemDirents();
|
dir_entries = file::getMountPoints();
|
||||||
current_path = path;
|
current_path = path;
|
||||||
selected_child_entry = "";
|
selected_child_entry = "";
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -48,8 +48,18 @@ class LauncherApp final : public App {
|
|||||||
lv_obj_set_style_text_font(button_image, lvgl_get_launcher_icon_font(), LV_STATE_DEFAULT);
|
lv_obj_set_style_text_font(button_image, lvgl_get_launcher_icon_font(), LV_STATE_DEFAULT);
|
||||||
lv_image_set_src(button_image, imageFile);
|
lv_image_set_src(button_image, imageFile);
|
||||||
lv_obj_set_style_text_color(button_image, lv_theme_get_color_primary(button_image), LV_STATE_DEFAULT);
|
lv_obj_set_style_text_color(button_image, lv_theme_get_color_primary(button_image), LV_STATE_DEFAULT);
|
||||||
|
|
||||||
|
// Recolor handling:
|
||||||
|
// For color builds use theme primary color
|
||||||
|
// For 1-bit/monochrome builds force a visible color (black)
|
||||||
|
#if LV_COLOR_DEPTH == 1
|
||||||
|
// Try forcing black recolor on monochrome builds
|
||||||
|
lv_obj_set_style_image_recolor(button_image, lv_color_black(), LV_STATE_DEFAULT);
|
||||||
|
lv_obj_set_style_image_recolor_opa(button_image, LV_OPA_COVER, LV_STATE_DEFAULT);
|
||||||
|
#else
|
||||||
lv_obj_set_style_image_recolor(button_image, lv_theme_get_color_primary(parent), LV_STATE_DEFAULT);
|
lv_obj_set_style_image_recolor(button_image, lv_theme_get_color_primary(parent), LV_STATE_DEFAULT);
|
||||||
lv_obj_set_style_image_recolor_opa(button_image, LV_OPA_COVER, LV_STATE_DEFAULT);
|
lv_obj_set_style_image_recolor_opa(button_image, LV_OPA_COVER, LV_STATE_DEFAULT);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Ensure it's square (Material Symbols are slightly wider than tall)
|
// Ensure it's square (Material Symbols are slightly wider than tall)
|
||||||
lv_obj_set_size(button_image, button_size, button_size);
|
lv_obj_set_size(button_image, button_size, button_size);
|
||||||
@ -147,7 +157,7 @@ public:
|
|||||||
createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_SETTINGS, "Settings", margin, is_landscape_display);
|
createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_SETTINGS, "Settings", margin, is_landscape_display);
|
||||||
|
|
||||||
if (shouldShowPowerButton()) {
|
if (shouldShowPowerButton()) {
|
||||||
auto* power_button = lv_button_create(parent);
|
auto* power_button = lv_btn_create(parent);
|
||||||
lv_obj_set_style_pad_all(power_button, 8, 0);
|
lv_obj_set_style_pad_all(power_button, 8, 0);
|
||||||
lv_obj_align(power_button, LV_ALIGN_BOTTOM_MID, 0, -10);
|
lv_obj_align(power_button, LV_ALIGN_BOTTOM_MID, 0, -10);
|
||||||
lv_obj_add_event_cb(power_button, onPowerOffPressed, LV_EVENT_SHORT_CLICKED, nullptr);
|
lv_obj_add_event_cb(power_button, onPowerOffPressed, LV_EVENT_SHORT_CLICKED, nullptr);
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
#include <Tactility/Tactility.h>
|
#include <Tactility/Tactility.h>
|
||||||
#include <Tactility/TactilityConfig.h>
|
#include <Tactility/TactilityConfig.h>
|
||||||
|
|
||||||
|
|
||||||
#if TT_FEATURE_SCREENSHOT_ENABLED
|
#if TT_FEATURE_SCREENSHOT_ENABLED
|
||||||
|
|
||||||
#include <Tactility/app/App.h>
|
#include <Tactility/app/App.h>
|
||||||
#include <Tactility/app/AppManifest.h>
|
#include <Tactility/app/AppManifest.h>
|
||||||
|
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||||
#include <Tactility/kernel/Platform.h>
|
#include <Tactility/kernel/Platform.h>
|
||||||
#include <Tactility/Logger.h>
|
#include <Tactility/Logger.h>
|
||||||
#include <Tactility/lvgl/Lvgl.h>
|
#include <Tactility/lvgl/Lvgl.h>
|
||||||
#include <Tactility/lvgl/LvglSync.h>
|
#include <Tactility/lvgl/LvglSync.h>
|
||||||
#include <Tactility/lvgl/Toolbar.h>
|
#include <Tactility/lvgl/Toolbar.h>
|
||||||
#include <Tactility/service/screenshot/Screenshot.h>
|
#include <Tactility/service/screenshot/Screenshot.h>
|
||||||
|
|
||||||
#include <Tactility/Paths.h>
|
|
||||||
#include <Tactility/Timer.h>
|
#include <Tactility/Timer.h>
|
||||||
|
|
||||||
#include <tactility/lvgl_icon_shared.h>
|
#include <tactility/lvgl_icon_shared.h>
|
||||||
@ -204,9 +204,12 @@ void ScreenshotApp::createFilePathWidgets(lv_obj_t* parent) {
|
|||||||
lv_textarea_set_one_line(pathTextArea, true);
|
lv_textarea_set_one_line(pathTextArea, true);
|
||||||
lv_obj_set_flex_grow(pathTextArea, 1);
|
lv_obj_set_flex_grow(pathTextArea, 1);
|
||||||
if (kernel::getPlatform() == kernel::PlatformEsp) {
|
if (kernel::getPlatform() == kernel::PlatformEsp) {
|
||||||
std::string sdcard_path;
|
auto sdcard_devices = tt::hal::findDevices<tt::hal::sdcard::SdCardDevice>(tt::hal::Device::Type::SdCard);
|
||||||
if (findFirstMountedSdCardPath(sdcard_path)) {
|
if (sdcard_devices.size() > 1) {
|
||||||
std::string lvgl_mount_path = lvgl::PATH_PREFIX + sdcard_path + "/screenshots";
|
LOGGER.warn("Found multiple SD card devices - picking first");
|
||||||
|
}
|
||||||
|
if (!sdcard_devices.empty() && sdcard_devices.front()->isMounted()) {
|
||||||
|
std::string lvgl_mount_path = lvgl::PATH_PREFIX + sdcard_devices.front()->getMountPath();
|
||||||
lv_textarea_set_text(pathTextArea, lvgl_mount_path.c_str());
|
lv_textarea_set_text(pathTextArea, lvgl_mount_path.c_str());
|
||||||
} else {
|
} else {
|
||||||
lv_textarea_set_text(pathTextArea, "Error: no SD card");
|
lv_textarea_set_text(pathTextArea, "Error: no SD card");
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
#include <Tactility/TactilityConfig.h>
|
#include <Tactility/TactilityConfig.h>
|
||||||
#include <Tactility/lvgl/LvglSync.h>
|
#include <Tactility/lvgl/LvglSync.h>
|
||||||
#include <Tactility/lvgl/Toolbar.h>
|
#include <Tactility/lvgl/Toolbar.h>
|
||||||
|
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||||
#include <Tactility/Tactility.h>
|
#include <Tactility/Tactility.h>
|
||||||
#include <Tactility/Timer.h>
|
#include <Tactility/Timer.h>
|
||||||
|
|
||||||
#include <Tactility/Paths.h>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <format>
|
#include <format>
|
||||||
@ -343,9 +343,14 @@ class SystemInfoApp final : public App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string sdcard_path;
|
if (hasSdcardStorage) {
|
||||||
if (findFirstMountedSdCardPath(sdcard_path) && esp_vfs_fat_info(sdcard_path.c_str(), &storage_total, &storage_free) == ESP_OK) {
|
const auto sdcard_devices = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||||
|
for (const auto& sdcard : sdcard_devices) {
|
||||||
|
if (sdcard->isMounted() && esp_vfs_fat_info(sdcard->getMountPath().c_str(), &storage_total, &storage_free) == ESP_OK) {
|
||||||
updateMemoryBar(sdcardStorageBar, storage_free, storage_total);
|
updateMemoryBar(sdcardStorageBar, storage_free, storage_total);
|
||||||
|
break; // Only update first SD card
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasSystemStorage) {
|
if (hasSystemStorage) {
|
||||||
@ -619,10 +624,13 @@ class SystemInfoApp final : public App {
|
|||||||
dataStorageBar = createMemoryBar(storage_tab, file::MOUNT_POINT_DATA);
|
dataStorageBar = createMemoryBar(storage_tab, file::MOUNT_POINT_DATA);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string sdcard_path;
|
const auto sdcard_devices = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||||
if (findFirstMountedSdCardPath(sdcard_path) && esp_vfs_fat_info(sdcard_path.c_str(), &storage_total, &storage_free) == ESP_OK) {
|
for (const auto& sdcard : sdcard_devices) {
|
||||||
|
if (sdcard->isMounted() && esp_vfs_fat_info(sdcard->getMountPath().c_str(), &storage_total, &storage_free) == ESP_OK) {
|
||||||
hasSdcardStorage = true;
|
hasSdcardStorage = true;
|
||||||
sdcardStorageBar = createMemoryBar(storage_tab, sdcard_path.c_str());
|
sdcardStorageBar = createMemoryBar(storage_tab, sdcard->getMountPath().c_str());
|
||||||
|
break; // Only show first SD card
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config::SHOW_SYSTEM_PARTITION) {
|
if (config::SHOW_SYSTEM_PARTITION) {
|
||||||
|
|||||||
@ -1,50 +0,0 @@
|
|||||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
|
||||||
|
|
||||||
#include <tactility/filesystem/file_system.h>
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
namespace tt::hal::sdcard {
|
|
||||||
|
|
||||||
static error_t mount(void* data) {
|
|
||||||
auto* device = static_cast<SdCardDevice*>(data);
|
|
||||||
auto path = device->getMountPath();
|
|
||||||
if (!device->mount(path)) return ERROR_UNDEFINED;
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t unmount(void* data) {
|
|
||||||
auto* device = static_cast<SdCardDevice*>(data);
|
|
||||||
if (!device->unmount()) return ERROR_UNDEFINED;
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool is_mounted(void* data) {
|
|
||||||
auto* device = static_cast<SdCardDevice*>(data);
|
|
||||||
return device->isMounted();
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t get_path(void* data, char* out_path, size_t out_path_size) {
|
|
||||||
auto* device = static_cast<SdCardDevice*>(data);
|
|
||||||
if (device->getMountPath().size() >= out_path_size) return ERROR_BUFFER_OVERFLOW;
|
|
||||||
strncpy(out_path, device->getMountPath().c_str(), out_path_size);
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
FileSystemApi sdCardDeviceApi = {
|
|
||||||
.mount = mount,
|
|
||||||
.unmount = unmount,
|
|
||||||
.is_mounted = is_mounted,
|
|
||||||
.get_path = get_path
|
|
||||||
};
|
|
||||||
|
|
||||||
SdCardDevice::SdCardDevice(MountBehaviour mountBehaviour) : mountBehaviour(mountBehaviour) {
|
|
||||||
fileSystem = file_system_add(&sdCardDeviceApi, this);
|
|
||||||
check(fileSystem != nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
SdCardDevice::~SdCardDevice() {
|
|
||||||
file_system_remove(fileSystem);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
123
Tactility/Source/hal/sdcard/SdmmcDevice.cpp
Normal file
123
Tactility/Source/hal/sdcard/SdmmcDevice.cpp
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
#include <soc/soc_caps.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(ESP_PLATFORM) && defined(SOC_SDMMC_HOST_SUPPORTED)
|
||||||
|
|
||||||
|
#include <Tactility/hal/sdcard/SdmmcDevice.h>
|
||||||
|
#include <Tactility/Logger.h>
|
||||||
|
|
||||||
|
#include <esp_vfs_fat.h>
|
||||||
|
#include <sdmmc_cmd.h>
|
||||||
|
#include <driver/sdmmc_host.h>
|
||||||
|
|
||||||
|
namespace tt::hal::sdcard {
|
||||||
|
|
||||||
|
static const auto LOGGER = Logger("SdmmcDevice");
|
||||||
|
|
||||||
|
bool SdmmcDevice::mountInternal(const std::string& newMountPath) {
|
||||||
|
LOGGER.info("Mounting {}", newMountPath);
|
||||||
|
|
||||||
|
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||||
|
.format_if_mount_failed = config->formatOnMountFailed,
|
||||||
|
.max_files = config->maxOpenFiles,
|
||||||
|
.allocation_unit_size = 512 * 8,
|
||||||
|
.disk_status_check_enable = config->statusCheckEnabled,
|
||||||
|
.use_one_fat = false
|
||||||
|
};
|
||||||
|
|
||||||
|
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
|
||||||
|
|
||||||
|
sdmmc_slot_config_t slot_config = {
|
||||||
|
.clk = config->pinClock,
|
||||||
|
.cmd = config->pinCmd,
|
||||||
|
.d0 = config->pinD0,
|
||||||
|
.d1 = config->pinD1,
|
||||||
|
.d2 = config->pinD2,
|
||||||
|
.d3 = config->pinD3,
|
||||||
|
.d4 = static_cast<gpio_num_t>(0),
|
||||||
|
.d5 = static_cast<gpio_num_t>(0),
|
||||||
|
.d6 = static_cast<gpio_num_t>(0),
|
||||||
|
.d7 = static_cast<gpio_num_t>(0),
|
||||||
|
.cd = GPIO_NUM_NC,
|
||||||
|
.wp = GPIO_NUM_NC,
|
||||||
|
.width = config->busWidth,
|
||||||
|
.flags = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_err_t result = esp_vfs_fat_sdmmc_mount(newMountPath.c_str(), &host, &slot_config, &mount_config, &card);
|
||||||
|
|
||||||
|
if (result != ESP_OK || card == nullptr) {
|
||||||
|
if (result == ESP_FAIL) {
|
||||||
|
LOGGER.error("Mounting failed. Ensure the card is formatted with FAT.");
|
||||||
|
} else {
|
||||||
|
LOGGER.error("Mounting failed ({})", esp_err_to_name(result));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mountPath = newMountPath;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SdmmcDevice::mount(const std::string& newMountPath) {
|
||||||
|
auto lock = getLock()->asScopedLock();
|
||||||
|
lock.lock();
|
||||||
|
|
||||||
|
if (mountInternal(newMountPath)) {
|
||||||
|
LOGGER.info("Mounted at {}", newMountPath);
|
||||||
|
sdmmc_card_print_info(stdout, card);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
LOGGER.error("Mount failed for {}", newMountPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SdmmcDevice::unmount() {
|
||||||
|
auto lock = getLock()->asScopedLock();
|
||||||
|
lock.lock();
|
||||||
|
|
||||||
|
if (card == nullptr) {
|
||||||
|
LOGGER.error("Can't unmount: not mounted");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esp_vfs_fat_sdcard_unmount(mountPath.c_str(), card) != ESP_OK) {
|
||||||
|
LOGGER.error("Unmount failed for {}", mountPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Unmounted {}", mountPath);
|
||||||
|
mountPath = "";
|
||||||
|
card = nullptr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SdmmcDevice::State SdmmcDevice::getState(TickType_t timeout) const {
|
||||||
|
if (card == nullptr) {
|
||||||
|
return State::Unmounted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SD card and the screen are on the same SPI bus.
|
||||||
|
* Writing and reading to the bus from 2 devices at the same time causes crashes.
|
||||||
|
* This work-around ensures that this check is only happening when LVGL isn't rendering.
|
||||||
|
*/
|
||||||
|
auto lock = getLock()->asScopedLock();
|
||||||
|
bool locked = lock.lock(timeout);
|
||||||
|
if (!locked) {
|
||||||
|
return State::Timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sdmmc_get_status(card) != ESP_OK) {
|
||||||
|
return State::Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return State::Mounted;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -5,7 +5,6 @@
|
|||||||
#include <Tactility/hal/usb/UsbTusb.h>
|
#include <Tactility/hal/usb/UsbTusb.h>
|
||||||
|
|
||||||
#include <Tactility/Logger.h>
|
#include <Tactility/Logger.h>
|
||||||
#include <tactility/drivers/esp32_sdmmc.h>
|
|
||||||
|
|
||||||
namespace tt::hal::usb {
|
namespace tt::hal::usb {
|
||||||
|
|
||||||
@ -22,37 +21,29 @@ static Mode currentMode = Mode::Default;
|
|||||||
static RTC_NOINIT_ATTR BootModeData bootModeData;
|
static RTC_NOINIT_ATTR BootModeData bootModeData;
|
||||||
|
|
||||||
sdmmc_card_t* getCard() {
|
sdmmc_card_t* getCard() {
|
||||||
sdmmc_card_t* sdcard = nullptr;
|
|
||||||
|
|
||||||
// Find old HAL SD card device:
|
|
||||||
auto sdcards = findDevices<sdcard::SpiSdCardDevice>(Device::Type::SdCard);
|
auto sdcards = findDevices<sdcard::SpiSdCardDevice>(Device::Type::SdCard);
|
||||||
for (auto& device : sdcards) {
|
|
||||||
auto sdcard_device= std::static_pointer_cast<sdcard::SpiSdCardDevice>(device);
|
std::shared_ptr<sdcard::SpiSdCardDevice> usable_sdcard;
|
||||||
if (sdcard_device != nullptr && sdcard_device->isMounted() && sdcard_device->getCard() != nullptr) {
|
for (auto& sdcard : sdcards) {
|
||||||
sdcard = sdcard_device->getCard();
|
auto sdcard_candidate = std::static_pointer_cast<sdcard::SpiSdCardDevice>(sdcard);
|
||||||
|
if (sdcard_candidate != nullptr && sdcard_candidate->isMounted() && sdcard_candidate->getCard() != nullptr) {
|
||||||
|
usable_sdcard = sdcard_candidate;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if SOC_SDMMC_HOST_SUPPORTED
|
if (usable_sdcard == nullptr) {
|
||||||
// Find ESP32 SDMMC device:
|
LOGGER.warn("Couldn't find a mounted SpiSdCard");
|
||||||
if (sdcard == nullptr) {
|
return nullptr;
|
||||||
device_for_each(&sdcard, [](auto* device, void* context) {
|
|
||||||
if (device_is_ready(device) && device_is_compatible(device, "espressif,esp32-sdmmc")) {
|
|
||||||
auto** sdcard = static_cast<sdmmc_card_t**>(context);
|
|
||||||
*sdcard = esp32_sdmmc_get_card(device);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (sdcard == nullptr) {
|
|
||||||
LOGGER.warn("Couldn't find a mounted SD card");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return sdcard;
|
auto* sdmmc_card = usable_sdcard->getCard();
|
||||||
|
if (sdmmc_card == nullptr) {
|
||||||
|
LOGGER.warn("SD card has no card object available");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sdmmc_card;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool canStartNewMode() {
|
static bool canStartNewMode() {
|
||||||
|
|||||||
@ -192,8 +192,8 @@ lv_obj_t* statusbar_create(lv_obj_t* parent) {
|
|||||||
auto* image = lv_image_create(obj);
|
auto* image = lv_image_create(obj);
|
||||||
lv_obj_set_size(image, icon_size, icon_size); // regular padding doesn't work
|
lv_obj_set_size(image, icon_size, icon_size); // regular padding doesn't work
|
||||||
lv_obj_set_style_text_font(image, lvgl_get_statusbar_icon_font(), LV_STATE_DEFAULT);
|
lv_obj_set_style_text_font(image, lvgl_get_statusbar_icon_font(), LV_STATE_DEFAULT);
|
||||||
lv_obj_set_style_text_color(image, lv_color_white(), LV_STATE_DEFAULT);
|
|
||||||
lv_obj_set_style_pad_all(image, 0, LV_STATE_DEFAULT);
|
lv_obj_set_style_pad_all(image, 0, LV_STATE_DEFAULT);
|
||||||
|
obj_set_style_bg_blacken(image);
|
||||||
statusbar->icons[i] = image;
|
statusbar->icons[i] = image;
|
||||||
|
|
||||||
update_icon(image, &(statusbar_data.icons[i]));
|
update_icon(image, &(statusbar_data.icons[i]));
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
#include <Tactility/LogMessages.h>
|
|
||||||
#include <Tactility/Logger.h>
|
|
||||||
#include <Tactility/Mutex.h>
|
|
||||||
#include <Tactility/Paths.h>
|
|
||||||
#include <Tactility/Tactility.h>
|
|
||||||
#include <Tactility/Timer.h>
|
|
||||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||||
|
#include <Tactility/Logger.h>
|
||||||
|
#include <Tactility/LogMessages.h>
|
||||||
|
#include <Tactility/Mutex.h>
|
||||||
#include <Tactility/service/ServiceContext.h>
|
#include <Tactility/service/ServiceContext.h>
|
||||||
#include <Tactility/service/ServiceRegistration.h>
|
#include <Tactility/service/ServiceRegistration.h>
|
||||||
|
#include <Tactility/Tactility.h>
|
||||||
|
#include <Tactility/Timer.h>
|
||||||
|
|
||||||
namespace tt::service::sdcard {
|
namespace tt::service::sdcard {
|
||||||
|
|
||||||
@ -18,7 +17,7 @@ class SdCardService final : public Service {
|
|||||||
|
|
||||||
Mutex mutex;
|
Mutex mutex;
|
||||||
std::unique_ptr<Timer> updateTimer;
|
std::unique_ptr<Timer> updateTimer;
|
||||||
bool lastMountedState = false;
|
hal::sdcard::SdCardDevice::State lastState = hal::sdcard::SdCardDevice::State::Unmounted;
|
||||||
|
|
||||||
bool lock(TickType_t timeout) const {
|
bool lock(TickType_t timeout) const {
|
||||||
return mutex.lock(timeout);
|
return mutex.lock(timeout);
|
||||||
@ -30,13 +29,23 @@ class SdCardService final : public Service {
|
|||||||
|
|
||||||
void update() {
|
void update() {
|
||||||
// TODO: Support multiple SD cards
|
// TODO: Support multiple SD cards
|
||||||
auto* file_system = findFirstSdcardFileSystem();
|
auto sdcard = hal::findFirstDevice<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||||
|
if (sdcard == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (lock(50)) {
|
if (lock(50)) {
|
||||||
auto is_mounted = file_system_is_mounted(file_system);
|
auto new_state = sdcard->getState();
|
||||||
if (is_mounted != lastMountedState) {
|
|
||||||
lastMountedState = is_mounted;
|
if (new_state == hal::sdcard::SdCardDevice::State::Error) {
|
||||||
|
LOGGER.error("Sdcard error - unmounting. Did you eject the card in an unsafe manner?");
|
||||||
|
sdcard->unmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (new_state != lastState) {
|
||||||
|
lastState = new_state;
|
||||||
|
}
|
||||||
|
|
||||||
unlock();
|
unlock();
|
||||||
} else {
|
} else {
|
||||||
LOGGER.warn(LOG_MESSAGE_MUTEX_LOCK_FAILED);
|
LOGGER.warn(LOG_MESSAGE_MUTEX_LOCK_FAILED);
|
||||||
@ -46,8 +55,7 @@ class SdCardService final : public Service {
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
bool onStart(ServiceContext& serviceContext) override {
|
bool onStart(ServiceContext& serviceContext) override {
|
||||||
auto* sdcard_fs = findFirstSdcardFileSystem();
|
if (hal::findFirstDevice<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard) == nullptr) {
|
||||||
if (sdcard_fs == nullptr) {
|
|
||||||
LOGGER.warn("No SD card device found - not starting Service");
|
LOGGER.warn("No SD card device found - not starting Service");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
#include <Tactility/Logger.h>
|
#include <Tactility/Logger.h>
|
||||||
#include <Tactility/Mutex.h>
|
#include <Tactility/Mutex.h>
|
||||||
#include <Tactility/Paths.h>
|
|
||||||
#include <Tactility/Timer.h>
|
#include <Tactility/Timer.h>
|
||||||
|
#include <tactility/check.h>
|
||||||
#include <Tactility/hal/power/PowerDevice.h>
|
#include <Tactility/hal/power/PowerDevice.h>
|
||||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||||
#include <Tactility/lvgl/Lvgl.h>
|
#include <Tactility/lvgl/Lvgl.h>
|
||||||
@ -13,7 +13,6 @@
|
|||||||
#include <Tactility/service/ServiceRegistration.h>
|
#include <Tactility/service/ServiceRegistration.h>
|
||||||
#include <Tactility/service/gps/GpsService.h>
|
#include <Tactility/service/gps/GpsService.h>
|
||||||
#include <Tactility/service/wifi/Wifi.h>
|
#include <Tactility/service/wifi/Wifi.h>
|
||||||
#include <tactility/check.h>
|
|
||||||
|
|
||||||
#include <tactility/lvgl_icon_statusbar.h>
|
#include <tactility/lvgl_icon_statusbar.h>
|
||||||
|
|
||||||
@ -57,9 +56,18 @@ static const char* getWifiStatusIcon(wifi::RadioState state) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* getSdCardStatusIcon(bool mounted) {
|
static const char* getSdCardStatusIcon(hal::sdcard::SdCardDevice::State state) {
|
||||||
if (mounted) return LVGL_ICON_STATUSBAR_SD_CARD;
|
switch (state) {
|
||||||
|
using enum hal::sdcard::SdCardDevice::State;
|
||||||
|
case Mounted:
|
||||||
|
return LVGL_ICON_STATUSBAR_SD_CARD;
|
||||||
|
case Error:
|
||||||
|
case Unmounted:
|
||||||
|
case Timeout:
|
||||||
return LVGL_ICON_STATUSBAR_SD_CARD_ALERT;
|
return LVGL_ICON_STATUSBAR_SD_CARD_ALERT;
|
||||||
|
default:
|
||||||
|
check(false, "Unhandled SdCard state");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* getPowerStatusIcon() {
|
static const char* getPowerStatusIcon() {
|
||||||
@ -164,16 +172,19 @@ class StatusbarService final : public Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void updateSdCardIcon() {
|
void updateSdCardIcon() {
|
||||||
auto* sdcard_fs = findFirstSdcardFileSystem();
|
auto sdcards = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||||
// TODO: Support multiple SD cards
|
// TODO: Support multiple SD cards
|
||||||
if (sdcard_fs != nullptr) {
|
auto sdcard = sdcards.empty() ? nullptr : sdcards[0];
|
||||||
auto mounted = file_system_is_mounted(sdcard_fs);
|
if (sdcard != nullptr) {
|
||||||
auto* desired_icon = getSdCardStatusIcon(mounted);
|
auto state = sdcard->getState(50 / portTICK_PERIOD_MS);
|
||||||
|
if (state != hal::sdcard::SdCardDevice::State::Timeout) {
|
||||||
|
auto* desired_icon = getSdCardStatusIcon(state);
|
||||||
if (sdcard_last_icon != desired_icon) {
|
if (sdcard_last_icon != desired_icon) {
|
||||||
lvgl::statusbar_icon_set_image(sdcard_icon_id, desired_icon);
|
lvgl::statusbar_icon_set_image(sdcard_icon_id, desired_icon);
|
||||||
lvgl::statusbar_icon_set_visibility(sdcard_icon_id, true);
|
lvgl::statusbar_icon_set_visibility(sdcard_icon_id, true);
|
||||||
sdcard_last_icon = desired_icon;
|
sdcard_last_icon = desired_icon;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// TODO: Consider tracking how long the SD card has been in unknown status and then show error
|
// TODO: Consider tracking how long the SD card has been in unknown status and then show error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,6 @@
|
|||||||
#include <Tactility/app/App.h>
|
#include <Tactility/app/App.h>
|
||||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||||
#include <Tactility/service/wifi/Wifi.h>
|
#include <Tactility/service/wifi/Wifi.h>
|
||||||
#include <esp_wifi_default.h>
|
|
||||||
#include <Tactility/network/HttpdReq.h>
|
#include <Tactility/network/HttpdReq.h>
|
||||||
#include <Tactility/network/Url.h>
|
#include <Tactility/network/Url.h>
|
||||||
#include <Tactility/Paths.h>
|
#include <Tactility/Paths.h>
|
||||||
@ -27,7 +26,6 @@
|
|||||||
#include <Tactility/StringUtils.h>
|
#include <Tactility/StringUtils.h>
|
||||||
|
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
#include <tactility/filesystem/file_system.h>
|
|
||||||
|
|
||||||
#if TT_FEATURE_SCREENSHOT_ENABLED
|
#if TT_FEATURE_SCREENSHOT_ENABLED
|
||||||
#include <lv_screenshot.h>
|
#include <lv_screenshot.h>
|
||||||
@ -402,9 +400,6 @@ bool WebServerService::startApMode() {
|
|||||||
void WebServerService::stopApMode() {
|
void WebServerService::stopApMode() {
|
||||||
if (apWifiInitialized) {
|
if (apWifiInitialized) {
|
||||||
esp_err_t err;
|
esp_err_t err;
|
||||||
if (apNetif != nullptr) {
|
|
||||||
esp_wifi_clear_default_wifi_driver_and_handlers(apNetif);
|
|
||||||
}
|
|
||||||
err = esp_wifi_stop();
|
err = esp_wifi_stop();
|
||||||
if (err != ESP_OK && err != ESP_ERR_WIFI_NOT_STARTED) {
|
if (err != ESP_OK && err != ESP_ERR_WIFI_NOT_STARTED) {
|
||||||
LOGGER.warn("esp_wifi_stop() in cleanup: {}", esp_err_to_name(err));
|
LOGGER.warn("esp_wifi_stop() in cleanup: {}", esp_err_to_name(err));
|
||||||
@ -765,24 +760,20 @@ esp_err_t WebServerService::handleFsList(httpd_req_t* request) {
|
|||||||
|
|
||||||
std::ostringstream json;
|
std::ostringstream json;
|
||||||
json << "{\"path\":\"" << norm << "\",\"entries\":[";
|
json << "{\"path\":\"" << norm << "\",\"entries\":[";
|
||||||
struct FsIterContext {
|
|
||||||
std::ostringstream& json;
|
|
||||||
uint16_t count = 0;
|
|
||||||
};
|
|
||||||
FsIterContext fs_iter_context { json };
|
|
||||||
// Special handling for root: show available mount points
|
// Special handling for root: show available mount points
|
||||||
if (norm == "/") {
|
if (norm == "/") {
|
||||||
file_system_for_each(&fs_iter_context, [] (auto* fs, void* context) {
|
// Always show /data
|
||||||
auto* fs_iter_context = static_cast<FsIterContext*>(context);
|
json << "{\"name\":\"data\",\"type\":\"dir\",\"size\":0}";
|
||||||
char path[128];
|
|
||||||
if (file_system_is_mounted(fs) && file_system_get_path(fs, path, sizeof(path)) == ESP_OK && strcmp(path, "/system") != 0) {
|
|
||||||
fs_iter_context->count++;
|
|
||||||
if (fs_iter_context->count != 1) fs_iter_context->json << ","; // add separator between json array entries
|
|
||||||
fs_iter_context->json << "{\"name\":\"" << path << "\",\"type\":\"dir\",\"size\":0}";
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// Show /sdcard if mounted
|
||||||
|
const auto sdcard_devices = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||||
|
for (const auto& sdcard : sdcard_devices) {
|
||||||
|
if (sdcard->isMounted()) {
|
||||||
|
json << ",{\"name\":\"sdcard\",\"type\":\"dir\",\"size\":0}";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
json << "]}";
|
json << "]}";
|
||||||
} else {
|
} else {
|
||||||
std::vector<dirent> entries;
|
std::vector<dirent> entries;
|
||||||
@ -1165,38 +1156,34 @@ esp_err_t WebServerService::handleApiSysinfo(httpd_req_t* request) {
|
|||||||
json << "\"storage\":{";
|
json << "\"storage\":{";
|
||||||
uint64_t storage_total = 0, storage_free = 0;
|
uint64_t storage_total = 0, storage_free = 0;
|
||||||
|
|
||||||
struct FsIterContext {
|
// Data partition
|
||||||
std::ostringstream& json;
|
json << "\"data\":{";
|
||||||
uint16_t count = 0;
|
if (esp_vfs_fat_info(file::MOUNT_POINT_DATA, &storage_total, &storage_free) == ESP_OK) {
|
||||||
};
|
json << "\"free\":" << storage_free << ",";
|
||||||
FsIterContext fs_iter_context { json };
|
json << "\"total\":" << storage_total << ",";
|
||||||
file_system_for_each(&fs_iter_context, [] (auto* fs, void* context) {
|
json << "\"mounted\":true";
|
||||||
char mount_path[128] = "";
|
|
||||||
if (file_system_get_path(fs, mount_path, sizeof(mount_path)) != ERROR_NONE) return true;
|
|
||||||
if (strcmp(mount_path, "/system") == 0) return true; // Hide system partition
|
|
||||||
|
|
||||||
bool mounted = file_system_is_mounted(fs);
|
|
||||||
auto* fs_iter_context = static_cast<FsIterContext*>(context);
|
|
||||||
auto& json_context = fs_iter_context->json;
|
|
||||||
std::string mount_path_cpp = mount_path;
|
|
||||||
|
|
||||||
fs_iter_context->count++;
|
|
||||||
if (fs_iter_context->count != 1) json_context << ","; // add separator between json array entries
|
|
||||||
json_context << "\"" << mount_path_cpp.substr(1) << "\":{";
|
|
||||||
|
|
||||||
uint64_t storage_total = 0, storage_free = 0;
|
|
||||||
if (esp_vfs_fat_info(mount_path, &storage_total, &storage_free) == ESP_OK) {
|
|
||||||
json_context << "\"free\":" << storage_free << ",";
|
|
||||||
json_context << "\"total\":" << storage_total << ",";
|
|
||||||
} else {
|
} else {
|
||||||
json_context << "\"free\":0,";
|
json << "\"mounted\":false";
|
||||||
json_context << "\"total\":0,";
|
|
||||||
}
|
}
|
||||||
|
json << "},";
|
||||||
|
|
||||||
json_context << "\"mounted\":" << (mounted ? "true" : "false") << "";
|
// SD card - check all sdcard devices
|
||||||
json_context << "}";
|
json << "\"sdcard\":{";
|
||||||
return true;
|
bool sdcard_found = false;
|
||||||
});
|
const auto sdcard_devices = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||||
|
for (const auto& sdcard : sdcard_devices) {
|
||||||
|
if (sdcard->isMounted() && esp_vfs_fat_info(sdcard->getMountPath().c_str(), &storage_total, &storage_free) == ESP_OK) {
|
||||||
|
json << "\"free\":" << storage_free << ",";
|
||||||
|
json << "\"total\":" << storage_total << ",";
|
||||||
|
json << "\"mounted\":true";
|
||||||
|
sdcard_found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!sdcard_found) {
|
||||||
|
json << "\"mounted\":false";
|
||||||
|
}
|
||||||
|
json << "}";
|
||||||
|
|
||||||
json << "},"; // end storage
|
json << "},"; // end storage
|
||||||
|
|
||||||
@ -1468,7 +1455,14 @@ esp_err_t WebServerService::handleApiScreenshot(httpd_req_t* request) {
|
|||||||
#if TT_FEATURE_SCREENSHOT_ENABLED
|
#if TT_FEATURE_SCREENSHOT_ENABLED
|
||||||
// Determine save location: prefer SD card root if mounted, otherwise /data
|
// Determine save location: prefer SD card root if mounted, otherwise /data
|
||||||
std::string save_path;
|
std::string save_path;
|
||||||
if (!findFirstMountedSdCardPath(save_path)) {
|
auto sdcard_devices = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||||
|
for (const auto& sdcard : sdcard_devices) {
|
||||||
|
if (sdcard->isMounted()) {
|
||||||
|
save_path = sdcard->getMountPath();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (save_path.empty()) {
|
||||||
save_path = file::MOUNT_POINT_DATA;
|
save_path = file::MOUNT_POINT_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1545,7 +1539,7 @@ esp_err_t WebServerService::handleFsTree(httpd_req_t* request) {
|
|||||||
std::ostringstream json;
|
std::ostringstream json;
|
||||||
json << "{";
|
json << "{";
|
||||||
// Gather mount points
|
// Gather mount points
|
||||||
auto mounts = file::getFileSystemDirents();
|
auto mounts = file::getMountPoints();
|
||||||
json << "\"mounts\": [";
|
json << "\"mounts\": [";
|
||||||
bool firstMount = true;
|
bool firstMount = true;
|
||||||
for (auto& m : mounts) {
|
for (auto& m : mounts) {
|
||||||
|
|||||||
@ -6,14 +6,13 @@
|
|||||||
#include <Tactility/Logger.h>
|
#include <Tactility/Logger.h>
|
||||||
#include <Tactility/service/wifi/WifiApSettings.h>
|
#include <Tactility/service/wifi/WifiApSettings.h>
|
||||||
|
|
||||||
#include <Tactility/Paths.h>
|
|
||||||
#include <Tactility/Tactility.h>
|
|
||||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <format>
|
#include <format>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <Tactility/Tactility.h>
|
||||||
|
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||||
|
|
||||||
namespace tt::service::wifi {
|
namespace tt::service::wifi {
|
||||||
|
|
||||||
@ -119,14 +118,18 @@ static void importWifiApSettingsFromDir(const std::string& path) {
|
|||||||
void bootSplashInit() {
|
void bootSplashInit() {
|
||||||
getMainDispatcher().dispatch([] {
|
getMainDispatcher().dispatch([] {
|
||||||
// First import any provisioning files placed on the system data partition.
|
// First import any provisioning files placed on the system data partition.
|
||||||
const std::string data_settings_path = file::getChildPath(file::MOUNT_POINT_DATA, "settings");
|
const std::string settings_path = file::getChildPath(file::MOUNT_POINT_DATA, "settings");
|
||||||
importWifiApSettingsFromDir(data_settings_path);
|
importWifiApSettingsFromDir(settings_path);
|
||||||
|
|
||||||
// Then scan attached SD cards as before.
|
// Then scan attached SD cards as before.
|
||||||
std::string sdcard_path;
|
const auto sdcards = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||||
if (findFirstMountedSdCardPath((sdcard_path))) {
|
for (auto& sdcard : sdcards) {
|
||||||
const std::string sd_settings_path = file::getChildPath(sdcard_path, "settings");
|
if (sdcard->isMounted()) {
|
||||||
importWifiApSettingsFromDir(sd_settings_path);
|
const std::string settings_path = file::getChildPath(sdcard->getMountPath(), "settings");
|
||||||
|
importWifiApSettingsFromDir(settings_path);
|
||||||
|
} else {
|
||||||
|
LOGGER.warn("Skipping unmounted SD card {}", sdcard->getMountPath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,6 @@
|
|||||||
#include <Tactility/service/wifi/WifiGlobals.h>
|
#include <Tactility/service/wifi/WifiGlobals.h>
|
||||||
#include <Tactility/service/wifi/WifiSettings.h>
|
#include <Tactility/service/wifi/WifiSettings.h>
|
||||||
|
|
||||||
#include <esp_wifi_default.h>
|
|
||||||
#include <lwip/esp_netif_net_stack.h>
|
#include <lwip/esp_netif_net_stack.h>
|
||||||
#include <freertos/FreeRTOS.h>
|
#include <freertos/FreeRTOS.h>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@ -538,7 +537,6 @@ static void dispatchEnable(std::shared_ptr<Wifi> wifi) {
|
|||||||
publish_event(wifi, WifiEvent::RadioStateOnPending);
|
publish_event(wifi, WifiEvent::RadioStateOnPending);
|
||||||
|
|
||||||
if (wifi->netif != nullptr) {
|
if (wifi->netif != nullptr) {
|
||||||
esp_wifi_clear_default_wifi_driver_and_handlers(wifi->netif);
|
|
||||||
esp_netif_destroy(wifi->netif);
|
esp_netif_destroy(wifi->netif);
|
||||||
}
|
}
|
||||||
wifi->netif = esp_netif_create_default_wifi_sta();
|
wifi->netif = esp_netif_create_default_wifi_sta();
|
||||||
@ -635,21 +633,11 @@ static void dispatchDisable(std::shared_ptr<Wifi> wifi) {
|
|||||||
// Free up scan list memory
|
// Free up scan list memory
|
||||||
scan_list_free_safely(wifi_singleton);
|
scan_list_free_safely(wifi_singleton);
|
||||||
|
|
||||||
// Detach netif from the internal WiFi event handlers before stopping.
|
|
||||||
// Those handlers call esp_netif_action_stop on WIFI_EVENT_STA_STOP, which
|
|
||||||
// queues esp_netif_stop_api to the lwIP thread. esp_netif_destroy later
|
|
||||||
// queues a second one; the first zeroes lwip_netif, and the second then
|
|
||||||
// crashes in netif_ip6_addr_set_parts (null pointer + 0x263 offset).
|
|
||||||
if (wifi->netif != nullptr) {
|
|
||||||
esp_wifi_clear_default_wifi_driver_and_handlers(wifi->netif);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: handlers are already detached above, so we cannot safely return to
|
|
||||||
// RadioState::On from here — the netif would be missing its default WiFi
|
|
||||||
// event handlers and subsequent disable attempts would behave incorrectly.
|
|
||||||
// If stop fails, continue the teardown anyway so we end in a clean Off state.
|
|
||||||
if (esp_wifi_stop() != ESP_OK) {
|
if (esp_wifi_stop() != ESP_OK) {
|
||||||
LOGGER.error("Failed to stop radio - continuing teardown");
|
LOGGER.error("Failed to stop radio");
|
||||||
|
wifi->setRadioState(RadioState::On);
|
||||||
|
publish_event(wifi, WifiEvent::RadioStateOn);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (esp_wifi_set_mode(WIFI_MODE_NULL) != ESP_OK) {
|
if (esp_wifi_set_mode(WIFI_MODE_NULL) != ESP_OK) {
|
||||||
|
|||||||
@ -5,7 +5,6 @@
|
|||||||
#include <Tactility/Logger.h>
|
#include <Tactility/Logger.h>
|
||||||
#include <Tactility/settings/BootSettings.h>
|
#include <Tactility/settings/BootSettings.h>
|
||||||
|
|
||||||
#include <Tactility/Paths.h>
|
|
||||||
#include <format>
|
#include <format>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -19,9 +18,9 @@ constexpr auto* PROPERTIES_KEY_LAUNCHER_APP_ID = "launcherAppId";
|
|||||||
constexpr auto* PROPERTIES_KEY_AUTO_START_APP_ID = "autoStartAppId";
|
constexpr auto* PROPERTIES_KEY_AUTO_START_APP_ID = "autoStartAppId";
|
||||||
|
|
||||||
static std::string getPropertiesFilePath() {
|
static std::string getPropertiesFilePath() {
|
||||||
std::string sdcard_path;
|
const auto sdcards = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||||
if (findFirstMountedSdCardPath(sdcard_path)) {
|
for (auto& sdcard : sdcards) {
|
||||||
std::string path = std::format(PROPERTIES_FILE_FORMAT, sdcard_path);
|
std::string path = std::format(PROPERTIES_FILE_FORMAT, sdcard->getMountPath());
|
||||||
if (file::isFile(path)) {
|
if (file::isFile(path)) {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,7 +35,6 @@ const esp_elfsym freertos_symbols[] = {
|
|||||||
ESP_ELFSYM_EXPORT(xTaskCreateStatic),
|
ESP_ELFSYM_EXPORT(xTaskCreateStatic),
|
||||||
ESP_ELFSYM_EXPORT(xTaskCreateStaticPinnedToCore),
|
ESP_ELFSYM_EXPORT(xTaskCreateStaticPinnedToCore),
|
||||||
ESP_ELFSYM_EXPORT(xTaskCreateWithCaps),
|
ESP_ELFSYM_EXPORT(xTaskCreateWithCaps),
|
||||||
ESP_ELFSYM_EXPORT(xTaskCreatePinnedToCoreWithCaps),
|
|
||||||
ESP_ELFSYM_EXPORT(xTaskDelayUntil),
|
ESP_ELFSYM_EXPORT(xTaskDelayUntil),
|
||||||
ESP_ELFSYM_EXPORT(xTaskGenericNotify),
|
ESP_ELFSYM_EXPORT(xTaskGenericNotify),
|
||||||
ESP_ELFSYM_EXPORT(xTaskGenericNotifyFromISR),
|
ESP_ELFSYM_EXPORT(xTaskGenericNotifyFromISR),
|
||||||
|
|||||||
@ -40,7 +40,6 @@
|
|||||||
#include <esp_random.h>
|
#include <esp_random.h>
|
||||||
#include <esp_sntp.h>
|
#include <esp_sntp.h>
|
||||||
#include <esp_netif.h>
|
#include <esp_netif.h>
|
||||||
#include <esp_heap_caps.h>
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <lwip/sockets.h>
|
#include <lwip/sockets.h>
|
||||||
#include <lwip/netdb.h>
|
#include <lwip/netdb.h>
|
||||||
@ -429,11 +428,6 @@ const esp_elfsym main_symbols[] {
|
|||||||
ESP_ELFSYM_EXPORT(ledc_timer_pause),
|
ESP_ELFSYM_EXPORT(ledc_timer_pause),
|
||||||
ESP_ELFSYM_EXPORT(ledc_timer_resume),
|
ESP_ELFSYM_EXPORT(ledc_timer_resume),
|
||||||
ESP_ELFSYM_EXPORT(ledc_timer_rst),
|
ESP_ELFSYM_EXPORT(ledc_timer_rst),
|
||||||
// esp_heap_caps.h
|
|
||||||
ESP_ELFSYM_EXPORT(heap_caps_get_total_size),
|
|
||||||
ESP_ELFSYM_EXPORT(heap_caps_get_allocated_size),
|
|
||||||
ESP_ELFSYM_EXPORT(heap_caps_get_free_size),
|
|
||||||
ESP_ELFSYM_EXPORT(heap_caps_get_largest_free_block),
|
|
||||||
// delimiter
|
// delimiter
|
||||||
ESP_ELFSYM_END
|
ESP_ELFSYM_END
|
||||||
};
|
};
|
||||||
|
|||||||
33
TactilityKernel/include/tactility/drivers/file_system.h
Normal file
33
TactilityKernel/include/tactility/drivers/file_system.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <tactility/error.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct Device;
|
||||||
|
|
||||||
|
struct FileSystemApi {
|
||||||
|
error_t (*mount)(struct Device* device, const char* mount_path);
|
||||||
|
error_t (*unmount)(struct Device* device);
|
||||||
|
bool (*is_mounted)(struct Device* device);
|
||||||
|
error_t (*get_mount_path)(struct Device*, char* out_path, size_t out_path_size);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern const struct DeviceType FILE_SYSTEM_TYPE;
|
||||||
|
|
||||||
|
error_t file_system_mount(struct Device* device);
|
||||||
|
|
||||||
|
error_t file_system_unmount(struct Device* device);
|
||||||
|
|
||||||
|
bool file_system_is_mounted(struct Device* device);
|
||||||
|
|
||||||
|
error_t file_system_get_mount_path(struct Device*, char* out_path);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@ -1,37 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <tactility/error.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct FileSystem;
|
|
||||||
|
|
||||||
struct FileSystemApi {
|
|
||||||
error_t (*mount)(void* data);
|
|
||||||
error_t (*unmount)(void* data);
|
|
||||||
bool (*is_mounted)(void* data);
|
|
||||||
error_t (*get_path)(void* data, char* out_path, size_t out_path_size);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FileSystem* file_system_add(const struct FileSystemApi* fs_api, void* data);
|
|
||||||
|
|
||||||
void file_system_remove(struct FileSystem* fs);
|
|
||||||
|
|
||||||
void file_system_for_each(void* callback_context, bool (*callback)(struct FileSystem* fs, void* context));
|
|
||||||
|
|
||||||
error_t file_system_mount(struct FileSystem* fs);
|
|
||||||
|
|
||||||
error_t file_system_unmount(struct FileSystem* fs);
|
|
||||||
|
|
||||||
bool file_system_is_mounted(struct FileSystem* fs);
|
|
||||||
|
|
||||||
error_t file_system_get_path(struct FileSystem* fs, char* out_path, size_t out_path_size);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
32
TactilityKernel/source/drivers/file_system.cpp
Normal file
32
TactilityKernel/source/drivers/file_system.cpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#include <tactility/drivers/file_system.h>
|
||||||
|
#include <tactility/device.h>
|
||||||
|
|
||||||
|
#define INTERNAL_API(driver) ((FileSystemApi*)(driver)->api)
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
error_t file_system_mount(Device* device) {
|
||||||
|
const auto* driver = device_get_driver(device);
|
||||||
|
return INTERNAL_API(driver)->mount(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t file_system_unmount(Device* device) {
|
||||||
|
const auto* driver = device_get_driver(device);
|
||||||
|
return INTERNAL_API(driver)->unmount(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool file_system_is_mounted(Device* device) {
|
||||||
|
const auto* driver = device_get_driver(device);
|
||||||
|
return INTERNAL_API(driver)->is_mounted(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t file_system_get_mount_path(Device* device, char* out_path) {
|
||||||
|
const auto* driver = device_get_driver(device);
|
||||||
|
return INTERNAL_API(driver)->get_mount_path(device, out_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeviceType FILE_SYSTEM_TYPE {
|
||||||
|
.name = "file-system"
|
||||||
|
};
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
@ -1,85 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
#include <tactility/filesystem/file_system.h>
|
|
||||||
#include <tactility/concurrent/mutex.h>
|
|
||||||
#include <vector>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
// Define the internal FileSystem structure
|
|
||||||
struct FileSystem {
|
|
||||||
const FileSystemApi* api;
|
|
||||||
void* data;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Global Mutex and the master list of file systems
|
|
||||||
static Mutex fs_mutex;
|
|
||||||
static bool fs_mutex_initialized = false;
|
|
||||||
static std::vector<FileSystem*> file_systems;
|
|
||||||
|
|
||||||
static void ensure_mutex_initialized() {
|
|
||||||
if (!fs_mutex_initialized) {
|
|
||||||
mutex_construct(&fs_mutex);
|
|
||||||
fs_mutex_initialized = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
FileSystem* file_system_add(const FileSystemApi* fs_api, void* data) {
|
|
||||||
ensure_mutex_initialized();
|
|
||||||
mutex_lock(&fs_mutex);
|
|
||||||
|
|
||||||
auto* fs = new(std::nothrow) struct FileSystem();
|
|
||||||
check(fs != nullptr);
|
|
||||||
fs->api = fs_api;
|
|
||||||
fs->data = data;
|
|
||||||
file_systems.push_back(fs);
|
|
||||||
|
|
||||||
mutex_unlock(&fs_mutex);
|
|
||||||
return fs;
|
|
||||||
}
|
|
||||||
|
|
||||||
void file_system_remove(FileSystem* fs) {
|
|
||||||
check(!file_system_is_mounted(fs));
|
|
||||||
ensure_mutex_initialized();
|
|
||||||
mutex_lock(&fs_mutex);
|
|
||||||
|
|
||||||
auto it = std::ranges::find(file_systems, fs);
|
|
||||||
if (it != file_systems.end()) {
|
|
||||||
file_systems.erase(it);
|
|
||||||
delete fs;
|
|
||||||
}
|
|
||||||
|
|
||||||
mutex_unlock(&fs_mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void file_system_for_each(void* callback_context, bool (*callback)(FileSystem* fs, void* context)) {
|
|
||||||
ensure_mutex_initialized();
|
|
||||||
mutex_lock(&fs_mutex);
|
|
||||||
|
|
||||||
for (auto* fs : file_systems) {
|
|
||||||
if (!callback(fs, callback_context)) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
mutex_unlock(&fs_mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
error_t file_system_mount(FileSystem* fs) {
|
|
||||||
// Assuming 'device' is accessible or passed via a different mechanism
|
|
||||||
// as it's required by the FileSystemApi signatures.
|
|
||||||
return fs->api->mount(fs->data);
|
|
||||||
}
|
|
||||||
|
|
||||||
error_t file_system_unmount(FileSystem* fs) {
|
|
||||||
return fs->api->unmount(fs->data);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool file_system_is_mounted(FileSystem* fs) {
|
|
||||||
return fs->api->is_mounted(fs->data);
|
|
||||||
}
|
|
||||||
|
|
||||||
error_t file_system_get_path(FileSystem* fs, char* out_path, size_t out_path_size) {
|
|
||||||
return fs->api->get_path(fs->data, out_path, out_path_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -5,13 +5,13 @@
|
|||||||
#include <tactility/device.h>
|
#include <tactility/device.h>
|
||||||
#include <tactility/driver.h>
|
#include <tactility/driver.h>
|
||||||
#include <tactility/drivers/gpio_controller.h>
|
#include <tactility/drivers/gpio_controller.h>
|
||||||
|
#include <tactility/drivers/file_system.h>
|
||||||
#include <tactility/drivers/i2c_controller.h>
|
#include <tactility/drivers/i2c_controller.h>
|
||||||
#include <tactility/drivers/i2s_controller.h>
|
#include <tactility/drivers/i2s_controller.h>
|
||||||
#include <tactility/drivers/root.h>
|
#include <tactility/drivers/root.h>
|
||||||
#include <tactility/drivers/spi_controller.h>
|
#include <tactility/drivers/spi_controller.h>
|
||||||
#include <tactility/drivers/uart_controller.h>
|
#include <tactility/drivers/uart_controller.h>
|
||||||
#include <tactility/error.h>
|
#include <tactility/error.h>
|
||||||
#include <tactility/filesystem/file_system.h>
|
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,6 +58,11 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(driver_is_compatible),
|
DEFINE_MODULE_SYMBOL(driver_is_compatible),
|
||||||
DEFINE_MODULE_SYMBOL(driver_find_compatible),
|
DEFINE_MODULE_SYMBOL(driver_find_compatible),
|
||||||
DEFINE_MODULE_SYMBOL(driver_get_device_type),
|
DEFINE_MODULE_SYMBOL(driver_get_device_type),
|
||||||
|
// file system
|
||||||
|
DEFINE_MODULE_SYMBOL(file_system_mount),
|
||||||
|
DEFINE_MODULE_SYMBOL(file_system_unmount),
|
||||||
|
DEFINE_MODULE_SYMBOL(file_system_is_mounted),
|
||||||
|
DEFINE_MODULE_SYMBOL(file_system_get_mount_path),
|
||||||
// drivers/gpio_controller
|
// drivers/gpio_controller
|
||||||
DEFINE_MODULE_SYMBOL(gpio_descriptor_acquire),
|
DEFINE_MODULE_SYMBOL(gpio_descriptor_acquire),
|
||||||
DEFINE_MODULE_SYMBOL(gpio_descriptor_release),
|
DEFINE_MODULE_SYMBOL(gpio_descriptor_release),
|
||||||
@ -153,11 +158,6 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(timer_set_callback_priority),
|
DEFINE_MODULE_SYMBOL(timer_set_callback_priority),
|
||||||
// error
|
// error
|
||||||
DEFINE_MODULE_SYMBOL(error_to_string),
|
DEFINE_MODULE_SYMBOL(error_to_string),
|
||||||
// file system
|
|
||||||
DEFINE_MODULE_SYMBOL(file_system_mount),
|
|
||||||
DEFINE_MODULE_SYMBOL(file_system_unmount),
|
|
||||||
DEFINE_MODULE_SYMBOL(file_system_is_mounted),
|
|
||||||
DEFINE_MODULE_SYMBOL(file_system_get_path),
|
|
||||||
// log
|
// log
|
||||||
#ifndef ESP_PLATFORM
|
#ifndef ESP_PLATFORM
|
||||||
DEFINE_MODULE_SYMBOL(log_generic),
|
DEFINE_MODULE_SYMBOL(log_generic),
|
||||||
|
|||||||
@ -227,7 +227,8 @@ def write_lvgl_variables(output_file, device_properties: ConfigParser):
|
|||||||
elif theme == "DefaultLight":
|
elif theme == "DefaultLight":
|
||||||
output_file.write("CONFIG_LV_THEME_DEFAULT_LIGHT=y\n")
|
output_file.write("CONFIG_LV_THEME_DEFAULT_LIGHT=y\n")
|
||||||
elif theme == "Mono":
|
elif theme == "Mono":
|
||||||
output_file.write("CONFIG_LV_USE_THEME_MONO=y\n")
|
output_file.write("CONFIG_LV_THEME_DEFAULT_DARK=y\n")
|
||||||
|
output_file.write("CONFIG_LV_THEME_MONO=y\n")
|
||||||
else:
|
else:
|
||||||
exit_with_error(f"Unknown theme: {theme}")
|
exit_with_error(f"Unknown theme: {theme}")
|
||||||
font_height_text = get_property_or_default(device_properties, "lvgl", "fontSize", "14")
|
font_height_text = get_property_or_default(device_properties, "lvgl", "fontSize", "14")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user