New kernel drivers, filesystem API, and more (#513)

* **New Features**
  * BMI270 6-axis IMU driver added; new unified filesystem abstraction for mounted filesystems.
  * Public Wi‑Fi API surface (no implementation yet)
  * SDMMC driver added (kernel drive$)
  * expanded GPIO interrupt/callback support
* **Improvements**
  * M5Stack Tab5: revamped GPIO/power initialization and IMU integration.
  * LVGL updates including device fontSize configuration.
  * Updated all code related to SD card device/fs handling
  * Rename LilyGO T-HMI S3 to LilyGO T-HMI
* **Bug Fixes**
  * Simplified and consolidated SD card handling and mount discovery.
This commit is contained in:
Ken Van Hoeylandt 2026-03-07 16:13:39 +01:00 committed by GitHub
parent 2de35b2d2d
commit aa7530e515
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
88 changed files with 2792 additions and 846 deletions

View File

@ -53,7 +53,7 @@ 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-thmi, 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 },

View File

@ -18,6 +18,7 @@ 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 {

View File

@ -1,5 +1,4 @@
#include "devices/Display.h" #include "devices/Display.h"
#include "devices/Sdcard.h"
#include <Tactility/hal/Configuration.h> #include <Tactility/hal/Configuration.h>
@ -9,8 +8,7 @@ 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()
}; };
} }

View File

@ -1,22 +0,0 @@
#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)
);
}

View File

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

View File

@ -22,3 +22,4 @@ dpi=186
[lvgl] [lvgl]
colorDepth=16 colorDepth=16
uiDensity=compact uiDensity=compact
fontSize=10

View File

@ -3,6 +3,7 @@
#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>
@ -30,6 +31,17 @@
pin-sclk = <&gpio0 5 GPIO_FLAG_NONE>; pin-sclk = <&gpio0 5 GPIO_FLAG_NONE>;
}; };
sdmmc0 {
compatible = "espressif,esp32-sdmmc";
pin-clk = <&gpio0 12 GPIO_FLAG_NONE>;
pin-cmd = <&gpio0 16 GPIO_FLAG_NONE>;
pin-d0 = <&gpio0 14 GPIO_FLAG_NONE>;
pin-d1 = <&gpio0 17 GPIO_FLAG_NONE>;
pin-d2 = <&gpio0 21 GPIO_FLAG_NONE>;
pin-d3 = <&gpio0 18 GPIO_FLAG_NONE>;
bus-width = <4>;
};
stemma_qt: uart1 { stemma_qt: uart1 {
compatible = "espressif,esp32-uart"; compatible = "espressif,esp32-uart";
port = <UART_NUM_1>; port = <UART_NUM_1>;

View File

@ -1,23 +0,0 @@
#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>(
SD_DIO_SCLK, //CLK
SD_DIO_CMD, //CMD
SD_DIO_DATA0, //D0
SD_DIO_NC, //D1
SD_DIO_NC, //D2
SD_DIO_NC, //D3
SdCardDevice::MountBehaviour::AtBoot,
SD_DIO_BUS_WIDTH
);
return std::make_shared<SdmmcDevice>(
std::move(configuration)
);
}

View File

@ -1,15 +0,0 @@
#pragma once
#include <driver/gpio.h>
#include "Tactility/hal/sdcard/SdCardDevice.h"
using tt::hal::sdcard::SdCardDevice;
constexpr auto SD_DIO_CMD = GPIO_NUM_11;
constexpr auto SD_DIO_SCLK = GPIO_NUM_12;
constexpr auto SD_DIO_DATA0 = GPIO_NUM_13;
constexpr auto SD_DIO_NC = GPIO_NUM_NC;
constexpr auto SD_DIO_BUS_WIDTH = 1;
std::shared_ptr<SdCardDevice> createSdCard();

View File

@ -1,5 +1,4 @@
#include "devices/Power.h" #include "devices/Power.h"
#include "devices/SdCard.h"
#include "devices/Display.h" #include "devices/Display.h"
#include <ButtonControl.h> #include <ButtonControl.h>
@ -11,7 +10,6 @@ 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 {
createSdCard(),
createDisplay(), createDisplay(),
std::make_shared<Power>(), std::make_shared<Power>(),
ButtonControl::createOneButtonControl(0) ButtonControl::createOneButtonControl(0)

View File

@ -5,11 +5,11 @@
#include "Tactility/kernel/SystemEvents.h" #include "Tactility/kernel/SystemEvents.h"
#include <Tactility/TactilityCore.h> #include <Tactility/TactilityCore.h>
#define TAG "thmi-s3" #define TAG "thmi"
static bool powerOn() { static bool powerOn() {
gpio_config_t power_signal_config = { gpio_config_t power_signal_config = {
.pin_bit_mask = (1ULL << THMI_S3_POWERON_GPIO) | (1ULL << THMI_S3_POWEREN_GPIO), .pin_bit_mask = (1ULL << THMI_POWERON_GPIO) | (1ULL << THMI_POWEREN_GPIO),
.mode = GPIO_MODE_OUTPUT, .mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE, .pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE,
@ -20,11 +20,11 @@ static bool powerOn() {
return false; return false;
} }
if (gpio_set_level(THMI_S3_POWERON_GPIO, 1) != ESP_OK) { if (gpio_set_level(THMI_POWERON_GPIO, 1) != ESP_OK) {
return false; return false;
} }
if (gpio_set_level(THMI_S3_POWEREN_GPIO, 1) != ESP_OK) { if (gpio_set_level(THMI_POWEREN_GPIO, 1) != ESP_OK) {
return false; return false;
} }

View File

@ -6,16 +6,16 @@
#include <ChargeFromVoltage.h> #include <ChargeFromVoltage.h>
#include <Tactility/hal/power/PowerDevice.h> #include <Tactility/hal/power/PowerDevice.h>
constexpr auto THMI_S3_POWEREN_GPIO = GPIO_NUM_10; constexpr auto THMI_POWEREN_GPIO = GPIO_NUM_10;
constexpr auto THMI_S3_POWERON_GPIO = GPIO_NUM_14; constexpr auto THMI_POWERON_GPIO = GPIO_NUM_14;
using tt::hal::power::PowerDevice; using tt::hal::power::PowerDevice;
class Power final : public PowerDevice { class Power final : public PowerDevice {
ChargeFromVoltage chargeFromAdcVoltage = ChargeFromVoltage(3.3f, 4.2f); ChargeFromVoltage chargeFromAdcVoltage = ChargeFromVoltage(3.3f, 4.2f);
bool initialized = false;
esp_adc_cal_characteristics_t adcCharacteristics; esp_adc_cal_characteristics_t adcCharacteristics;
bool initialized = false;
bool calibrated = false; bool calibrated = false;
bool adcInitCalibration(); bool adcInitCalibration();
@ -25,7 +25,7 @@ class Power final : public PowerDevice {
public: public:
std::string getName() const override { return "T-hmi Power"; } std::string getName() const override { return "T-HMI Power"; }
std::string getDescription() const override { return "Power measurement via ADC"; } std::string getDescription() const override { return "Power measurement via ADC"; }
bool supportsMetric(MetricType type) const override; bool supportsMetric(MetricType type) const override;

View File

@ -13,7 +13,7 @@ static error_t stop() {
} }
struct Module lilygo_thmi_s3_module = { struct Module lilygo_thmi_s3_module = {
.name = "lilygo-thmi-s3", .name = "lilygo-thmi",
.start = start, .start = start,
.stop = stop, .stop = stop,
.symbols = nullptr, .symbols = nullptr,

View File

@ -1,6 +1,6 @@
[general] [general]
vendor=LilyGO vendor=LilyGO
name=T-HMI S3 name=T-HMI
[apps] [apps]
launcherAppId=Launcher launcherAppId=Launcher

View File

@ -1,3 +1,3 @@
dependencies: dependencies:
- Platforms/platform-esp32 - Platforms/platform-esp32
dts: lilygo,thmi-s3.dts dts: lilygo,thmi.dts

View File

@ -3,11 +3,12 @@
#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>
/ { / {
compatible = "root"; compatible = "root";
model = "LilyGO T-HMI S3"; model = "LilyGO T-HMI";
gpio0 { gpio0 {
compatible = "espressif,esp32-gpio"; compatible = "espressif,esp32-gpio";
@ -21,4 +22,12 @@
pin-miso = <&gpio0 4 GPIO_FLAG_NONE>; pin-miso = <&gpio0 4 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 1 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>;
};
}; };

View File

@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
idf_component_register( idf_component_register(
SRCS ${SOURCE_FILES} SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source" INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port esp_lcd EspLcdCompat esp_lcd_ili9881c GT911 PwmBacklight driver vfs fatfs pi4ioe5v6408-module REQUIRES Tactility esp_lvgl_port esp_lcd EspLcdCompat esp_lcd_ili9881c GT911 PwmBacklight driver vfs fatfs
) )

View File

@ -1,12 +1,11 @@
#include "devices/Display.h" #include "devices/Display.h"
#include "devices/SdCard.h" #include "devices/SdCard.h"
#include <driver/gpio.h>
#include <tactility/drivers/gpio_controller.h>
#include <tactility/drivers/i2c_controller.h> #include <tactility/drivers/i2c_controller.h>
#include <Tactility/hal/Configuration.h> #include <Tactility/hal/Configuration.h>
#include <Tactility/hal/i2c/I2c.h> #include <Tactility/hal/i2c/I2c.h>
#include <drivers/pi4ioe5v6408.h>
using namespace tt::hal; using namespace tt::hal;
@ -19,50 +18,115 @@ static DeviceVector createDevices() {
}; };
} }
static error_t initPower(::Device* io_expander0, ::Device* io_expander1) { /*
constexpr TickType_t i2c_timeout = pdMS_TO_TICKS(10); PI4IOE5V6408-0 (0x43)
- Bit 0: RF internal/external switch
- Bit 1: Speaker enable
- Bit 2: External 5V bus enable
- Bit 3: /
- Bit 4: LCD reset
- Bit 5: Touch reset
- Bit 6: Camera reset
- Bit 7: Headphone detect
*/
constexpr auto GPIO_EXP0_PIN_RF_INTERNAL_EXTERNAL = 0;
constexpr auto GPIO_EXP0_PIN_SPEAKER_ENABLE = 1;
constexpr auto GPIO_EXP0_PIN_EXTERNAL_5V_BUS_ENABLE = 2;
constexpr auto GPIO_EXP0_PIN_LCD_RESET = 4;
constexpr auto GPIO_EXP0_PIN_TOUCH_RESET = 5;
constexpr auto GPIO_EXP0_PIN_CAMERA_RESET = 6;
constexpr auto GPIO_EXP0_PIN_HEADPHONE_DETECT = 7;
/* /*
PI4IOE5V6408-0 (0x43) PI4IOE5V6408-1 (0x44)
- Bit 0: RF internal/external switch - Bit 0: C6 WLAN enable
- Bit 1: Speaker enable - Bit 1: /
- Bit 2: External 5V bus enable - Bit 2: /
- Bit 3: / - Bit 3: USB-A 5V enable
- Bit 4: LCD reset - Bit 4: Device power: PWROFF_PLUSE
- Bit 5: Touch reset - Bit 5: IP2326: nCHG_QC_EN
- Bit 6: Camera reset - Bit 6: IP2326: CHG_STAT_LED
- Bit 7: Headphone detect - Bit 7: IP2326: CHG_EN
*/ */
constexpr auto GPIO_EXP1_PIN_C6_WLAN_ENABLE = 0;
constexpr auto GPIO_EXP1_PIN_USB_A_5V_ENABLE = 3;
constexpr auto GPIO_EXP1_PIN_DEVICE_POWER = 4;
constexpr auto GPIO_EXP1_PIN_IP2326_NCHG_QC_EN = 5;
constexpr auto GPIO_EXP1_PIN_IP2326_CHG_STAT_LED = 6;
constexpr auto GPIO_EXP1_PIN_IP2326_CHG_EN = 7;
check(pi4ioe5v6408_set_direction(io_expander0, 0b01111111, i2c_timeout) == ERROR_NONE); static void initExpander0(::Device* io_expander0) {
check(pi4ioe5v6408_set_output_level(io_expander0, 0b01000110, i2c_timeout) == ERROR_NONE); auto* rf_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_RF_INTERNAL_EXTERNAL, GPIO_OWNER_GPIO);
check(pi4ioe5v6408_set_output_high_impedance(io_expander0, 0b00000000, i2c_timeout) == ERROR_NONE); check(rf_pin);
check(pi4ioe5v6408_set_pull_select(io_expander0, 0b01111111, i2c_timeout) == ERROR_NONE); auto* speaker_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_OWNER_GPIO);
check(pi4ioe5v6408_set_pull_enable(io_expander0, 0b01111111, i2c_timeout) == ERROR_NONE); check(speaker_enable_pin);
auto* external_5v_bus_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_EXTERNAL_5V_BUS_ENABLE, GPIO_OWNER_GPIO);
check(external_5v_bus_enable_pin);
auto* lcd_reset_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_LCD_RESET, GPIO_OWNER_GPIO);
check(lcd_reset_pin);
auto* touch_reset_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_TOUCH_RESET, GPIO_OWNER_GPIO);
check(touch_reset_pin);
auto* camera_reset_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_CAMERA_RESET, GPIO_OWNER_GPIO);
check(camera_reset_pin);
auto* headphone_detect_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_HEADPHONE_DETECT, GPIO_OWNER_GPIO);
check(headphone_detect_pin);
gpio_descriptor_set_flags(rf_pin, GPIO_FLAG_DIRECTION_OUTPUT);
gpio_descriptor_set_flags(speaker_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT);
gpio_descriptor_set_flags(external_5v_bus_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT);
gpio_descriptor_set_flags(lcd_reset_pin, GPIO_FLAG_DIRECTION_OUTPUT);
gpio_descriptor_set_flags(touch_reset_pin, GPIO_FLAG_DIRECTION_OUTPUT);
gpio_descriptor_set_flags(camera_reset_pin, GPIO_FLAG_DIRECTION_OUTPUT);
gpio_descriptor_set_flags(headphone_detect_pin, GPIO_FLAG_DIRECTION_INPUT);
gpio_descriptor_set_level(rf_pin, false);
gpio_descriptor_set_level(speaker_enable_pin, false);
gpio_descriptor_set_level(external_5v_bus_enable_pin, true);
gpio_descriptor_set_level(lcd_reset_pin, false);
gpio_descriptor_set_level(touch_reset_pin, false);
gpio_descriptor_set_level(camera_reset_pin, true);
vTaskDelay(pdMS_TO_TICKS(10)); vTaskDelay(pdMS_TO_TICKS(10));
check(pi4ioe5v6408_set_output_level(io_expander0, 0b01110110, i2c_timeout) == ERROR_NONE); // Enable touch and lcd, but not the camera
gpio_descriptor_set_level(lcd_reset_pin, true);
gpio_descriptor_set_level(touch_reset_pin, true);
/* gpio_descriptor_release(rf_pin);
PI4IOE5V6408-1 (0x44) gpio_descriptor_release(speaker_enable_pin);
- Bit 0: C6 WLAN enable gpio_descriptor_release(external_5v_bus_enable_pin);
- Bit 1: / gpio_descriptor_release(lcd_reset_pin);
- Bit 2: / gpio_descriptor_release(touch_reset_pin);
- Bit 3: USB-A 5V enable gpio_descriptor_release(camera_reset_pin);
- Bit 4: Device power: PWROFF_PLUSE gpio_descriptor_release(headphone_detect_pin);
- Bit 5: IP2326: nCHG_QC_EN }
- Bit 6: IP2326: CHG_STAT_LED
- Bit 7: IP2326: CHG_EN
*/
check(pi4ioe5v6408_set_direction(io_expander1, 0b10111001, i2c_timeout) == ERROR_NONE); static void initExpander1(::Device* io_expander1) {
check(pi4ioe5v6408_set_output_high_impedance(io_expander1, 0b00000110, i2c_timeout) == ERROR_NONE);
check(pi4ioe5v6408_set_pull_select(io_expander1, 0b10111001, i2c_timeout) == ERROR_NONE);
check(pi4ioe5v6408_set_pull_enable(io_expander1, 0b11111001, i2c_timeout) == ERROR_NONE);
check(pi4ioe5v6408_set_input_default_level(io_expander1, 0b01000000, i2c_timeout) == ERROR_NONE);
check(pi4ioe5v6408_set_interrupt_mask(io_expander1, 0b10111111, i2c_timeout) == ERROR_NONE);
check(pi4ioe5v6408_set_output_level(io_expander1, 0b10001001, i2c_timeout) == ERROR_NONE);
return ERROR_NONE; auto* c6_wlan_enable_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_C6_WLAN_ENABLE, GPIO_OWNER_GPIO);
auto* usb_a_5v_enable_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_USB_A_5V_ENABLE, GPIO_OWNER_GPIO);
auto* device_power_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_DEVICE_POWER, GPIO_OWNER_GPIO);
auto* ip2326_ncharge_qc_enable_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_IP2326_NCHG_QC_EN, GPIO_OWNER_GPIO);
auto* ip2326_charge_state_led_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_IP2326_CHG_STAT_LED, GPIO_OWNER_GPIO);
auto* ip2326_charge_enable_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_IP2326_CHG_EN, GPIO_OWNER_GPIO);
gpio_descriptor_set_flags(c6_wlan_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT);
gpio_descriptor_set_flags(usb_a_5v_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT);
gpio_descriptor_set_flags(device_power_pin, GPIO_FLAG_DIRECTION_OUTPUT);
gpio_descriptor_set_flags(ip2326_ncharge_qc_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT);
gpio_descriptor_set_flags(ip2326_charge_state_led_pin, GPIO_FLAG_DIRECTION_OUTPUT);
gpio_descriptor_set_flags(ip2326_charge_enable_pin, GPIO_FLAG_DIRECTION_INPUT | GPIO_FLAG_PULL_UP);
gpio_descriptor_set_level(c6_wlan_enable_pin, true);
gpio_descriptor_set_level(usb_a_5v_enable_pin, true);
gpio_descriptor_set_level(device_power_pin, false);
gpio_descriptor_set_level(ip2326_ncharge_qc_enable_pin, false);
gpio_descriptor_set_level(ip2326_charge_state_led_pin, false);
gpio_descriptor_release(c6_wlan_enable_pin);
gpio_descriptor_release(usb_a_5v_enable_pin);
gpio_descriptor_release(device_power_pin);
gpio_descriptor_release(ip2326_ncharge_qc_enable_pin);
gpio_descriptor_release(ip2326_charge_state_led_pin);
gpio_descriptor_release(ip2326_charge_enable_pin);
} }
static error_t initSound(::Device* i2c_controller, ::Device* io_expander0 = nullptr) { static error_t initSound(::Device* i2c_controller, ::Device* io_expander0 = nullptr) {
@ -113,13 +177,11 @@ static error_t initSound(::Device* i2c_controller, ::Device* io_expander0 = null
return error; return error;
} }
uint8_t output_level = 0; auto* speaker_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_OWNER_GPIO);
if (pi4ioe5v6408_get_output_level(io_expander0, &output_level, pdMS_TO_TICKS(100)) != ERROR_NONE) { check(speaker_enable_pin, "Failed to acquire speaker enable pin");
LOG_E(TAG, "Failed to read power level: %s", error_to_string(error)); error = gpio_descriptor_set_level(speaker_enable_pin, true);
return ERROR_RESOURCE; gpio_descriptor_release(speaker_enable_pin);
} if (error != ERROR_NONE) {
if (pi4ioe5v6408_set_output_level(io_expander0, output_level | 0b00000010, pdMS_TO_TICKS(100)) != ERROR_NONE) {
LOG_E(TAG, "Failed to enable amplifier: %s", error_to_string(error)); LOG_E(TAG, "Failed to enable amplifier: %s", error_to_string(error));
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
@ -132,10 +194,12 @@ static bool initBoot() {
check(i2c0, "i2c0 not found"); check(i2c0, "i2c0 not found");
auto* io_expander0 = device_find_by_name("io_expander0"); auto* io_expander0 = device_find_by_name("io_expander0");
check(io_expander0, "io_expander0 not found");
auto* io_expander1 = device_find_by_name("io_expander1"); auto* io_expander1 = device_find_by_name("io_expander1");
check(i2c0, "i2c0 not found"); check(io_expander1, "io_expander1 not found");
initPower(io_expander0, io_expander1); initExpander0(io_expander0);
initExpander1(io_expander1);
error_t error = initSound(i2c0, io_expander0); error_t error = initSound(i2c0, io_expander0);
if (error != ERROR_NONE) { if (error != ERROR_NONE) {

View File

@ -1,4 +1,5 @@
dependencies: dependencies:
- Platforms/platform-esp32 - Platforms/platform-esp32
- Drivers/pi4ioe5v6408-module - Drivers/pi4ioe5v6408-module
- Drivers/bmi270-module
dts: m5stack,tab5.dts dts: m5stack,tab5.dts

View File

@ -5,6 +5,7 @@
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_i2s.h> #include <tactility/bindings/esp32_i2s.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
#include <bindings/bmi270.h>
#include <bindings/pi4ioe5v6408.h> #include <bindings/pi4ioe5v6408.h>
/ { / {
@ -23,35 +24,20 @@
pin-sda = <&gpio0 31 GPIO_FLAG_NONE>; pin-sda = <&gpio0 31 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 32 GPIO_FLAG_NONE>; pin-scl = <&gpio0 32 GPIO_FLAG_NONE>;
/*
- Bit 0: RF internal/external switch
- Bit 1: Speaker enable
- Bit 2: External 5V bus enable
- Bit 3: /
- Bit 4: LCD reset
- Bit 5: Touch reset
- Bit 6: Camera reset
- Bit 7: Headphone detect
*/
io_expander0 { io_expander0 {
compatible = "diodes,pi4ioe5v6408"; compatible = "diodes,pi4ioe5v6408";
reg = <0x43>; reg = <0x43>;
}; };
/*
- Bit 0: C6 WLAN enable
- Bit 1: /
- Bit 2: /
- Bit 3: USB-A 5V enable
- Bit 4: Device power: PWROFF_PLUSE
- Bit 5: IP2326: nCHG_QC_EN
- Bit 6: IP2326: CHG_STAT_LED
- Bit 7: IP2326: CHG_EN
*/
io_expander1 { io_expander1 {
compatible = "diodes,pi4ioe5v6408"; compatible = "diodes,pi4ioe5v6408";
reg = <0x44>; reg = <0x44>;
}; };
bmi270 {
compatible = "bosch,bmi270";
reg = <0x68>;
};
}; };
i2c_port_a: i2c1 { i2c_port_a: i2c1 {

View File

@ -1,5 +1,4 @@
#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>
@ -10,7 +9,6 @@ using namespace tt::hal;
static DeviceVector createDevices() { static DeviceVector createDevices() {
return { return {
createDisplay(), createDisplay(),
createSdCard(),
ButtonControl::createOneButtonControl(0) ButtonControl::createOneButtonControl(0)
}; };
} }

View File

@ -1,22 +0,0 @@
#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)
);
}

View File

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

View File

@ -3,6 +3,7 @@
#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>
@ -30,6 +31,17 @@
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>;

View File

@ -0,0 +1,12 @@
cmake_minimum_required(VERSION 3.20)
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
tactility_add_module(bmi270-module
SRCS ${SOURCE_FILES}
INCLUDE_DIRS include/
PRIV_INCLUDE_DIRS private/
REQUIRES TactilityKernel
)

View File

@ -0,0 +1,195 @@
Apache License
==============
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,7 @@
# BMI270 I2C Driver
A driver for the `BMI270` 6-axis IMU.
See https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmi270-ds000.pdf
License: [Apache v2.0](LICENSE-Apache-2.0.md)

View File

@ -0,0 +1,5 @@
description: Bosch BMI270 6-axis IMU
include: ["i2c-device.yaml"]
compatible: "bosch,bmi270"

View File

@ -0,0 +1,3 @@
dependencies:
- TactilityKernel
bindings: bindings

View File

@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <drivers/bmi270.h>
#ifdef __cplusplus
extern "C" {
#endif
DEFINE_DEVICETREE(bmi270, struct Bmi270Config)
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,14 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/module.h>
#ifdef __cplusplus
extern "C" {
#endif
extern struct Module bmi270_module;
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,33 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <stdint.h>
#include <tactility/error.h>
struct Device;
#ifdef __cplusplus
extern "C" {
#endif
struct Bmi270Config {
/** Address on bus */
uint8_t address;
};
struct Bmi270Data {
float ax, ay, az; // acceleration in g (±8g range)
float gx, gy, gz; // angular rate in °/s (±2000°/s range)
};
/**
* Read accelerometer and gyroscope data.
* @param[in] device bmi270 device
* @param[out] data Pointer to Bmi270Data structure to store the data
* @return ERROR_NONE on success
*/
error_t bmi270_read(struct Device* device, struct Bmi270Data* data);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,442 @@
#pragma once
// BMI270 configuration file
// Copyright (c) 2023 Bosch Sensortec GmbH
// SPDX-License-Identifier: BSD-3-Clause
// Source: https://github.com/boschsensortec/BMI270_SensorAPI
#include <stdint.h>
static const uint8_t bmi270_config_data[] = {
0xc8, 0x2e, 0x00, 0x2e, 0x80, 0x2e, 0x3d, 0xb1, 0xc8, 0x2e, 0x00, 0x2e, 0x80, 0x2e, 0x91, 0x03, 0x80, 0x2e, 0xbc,
0xb0, 0x80, 0x2e, 0xa3, 0x03, 0xc8, 0x2e, 0x00, 0x2e, 0x80, 0x2e, 0x00, 0xb0, 0x50, 0x30, 0x21, 0x2e, 0x59, 0xf5,
0x10, 0x30, 0x21, 0x2e, 0x6a, 0xf5, 0x80, 0x2e, 0x3b, 0x03, 0x00, 0x00, 0x00, 0x00, 0x08, 0x19, 0x01, 0x00, 0x22,
0x00, 0x75, 0x00, 0x00, 0x10, 0x00, 0x10, 0xd1, 0x00, 0xb3, 0x43, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1,
0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00,
0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e,
0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80,
0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1,
0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00,
0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e,
0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80,
0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1,
0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0xe0, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x19, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
0xe0, 0xaa, 0x38, 0x05, 0xe0, 0x90, 0x30, 0xfa, 0x00, 0x96, 0x00, 0x4b, 0x09, 0x11, 0x00, 0x11, 0x00, 0x02, 0x00,
0x2d, 0x01, 0xd4, 0x7b, 0x3b, 0x01, 0xdb, 0x7a, 0x04, 0x00, 0x3f, 0x7b, 0xcd, 0x6c, 0xc3, 0x04, 0x85, 0x09, 0xc3,
0x04, 0xec, 0xe6, 0x0c, 0x46, 0x01, 0x00, 0x27, 0x00, 0x19, 0x00, 0x96, 0x00, 0xa0, 0x00, 0x01, 0x00, 0x0c, 0x00,
0xf0, 0x3c, 0x00, 0x01, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x32, 0x00, 0x05, 0x00, 0xee,
0x06, 0x04, 0x00, 0xc8, 0x00, 0x00, 0x00, 0x04, 0x00, 0xa8, 0x05, 0xee, 0x06, 0x00, 0x04, 0xbc, 0x02, 0xb3, 0x00,
0x85, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xb4, 0x00, 0x01, 0x00, 0xb9, 0x00, 0x01, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x00, 0x80, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x2e, 0x00, 0xc1, 0xfd, 0x2d, 0xde,
0x00, 0xeb, 0x00, 0xda, 0x00, 0x00, 0x0c, 0xff, 0x0f, 0x00, 0x04, 0xc0, 0x00, 0x5b, 0xf5, 0xc9, 0x01, 0x1e, 0xf2,
0x80, 0x00, 0x3f, 0xff, 0x19, 0xf4, 0x58, 0xf5, 0x66, 0xf5, 0x64, 0xf5, 0xc0, 0xf1, 0xf0, 0x00, 0xe0, 0x00, 0xcd,
0x01, 0xd3, 0x01, 0xdb, 0x01, 0xff, 0x7f, 0xff, 0x01, 0xe4, 0x00, 0x74, 0xf7, 0xf3, 0x00, 0xfa, 0x00, 0xff, 0x3f,
0xca, 0x03, 0x6c, 0x38, 0x56, 0xfe, 0x44, 0xfd, 0xbc, 0x02, 0xf9, 0x06, 0x00, 0xfc, 0x12, 0x02, 0xae, 0x01, 0x58,
0xfa, 0x9a, 0xfd, 0x77, 0x05, 0xbb, 0x02, 0x96, 0x01, 0x95, 0x01, 0x7f, 0x01, 0x82, 0x01, 0x89, 0x01, 0x87, 0x01,
0x88, 0x01, 0x8a, 0x01, 0x8c, 0x01, 0x8f, 0x01, 0x8d, 0x01, 0x92, 0x01, 0x91, 0x01, 0xdd, 0x00, 0x9f, 0x01, 0x7e,
0x01, 0xdb, 0x00, 0xb6, 0x01, 0x70, 0x69, 0x26, 0xd3, 0x9c, 0x07, 0x1f, 0x05, 0x9d, 0x00, 0x00, 0x08, 0xbc, 0x05,
0x37, 0xfa, 0xa2, 0x01, 0xaa, 0x01, 0xa1, 0x01, 0xa8, 0x01, 0xa0, 0x01, 0xa8, 0x05, 0xb4, 0x01, 0xb4, 0x01, 0xce,
0x00, 0xd0, 0x00, 0xfc, 0x00, 0xc5, 0x01, 0xff, 0xfb, 0xb1, 0x00, 0x00, 0x38, 0x00, 0x30, 0xfd, 0xf5, 0xfc, 0xf5,
0xcd, 0x01, 0xa0, 0x00, 0x5f, 0xff, 0x00, 0x40, 0xff, 0x00, 0x00, 0x80, 0x6d, 0x0f, 0xeb, 0x00, 0x7f, 0xff, 0xc2,
0xf5, 0x68, 0xf7, 0xb3, 0xf1, 0x67, 0x0f, 0x5b, 0x0f, 0x61, 0x0f, 0x80, 0x0f, 0x58, 0xf7, 0x5b, 0xf7, 0x83, 0x0f,
0x86, 0x00, 0x72, 0x0f, 0x85, 0x0f, 0xc6, 0xf1, 0x7f, 0x0f, 0x6c, 0xf7, 0x00, 0xe0, 0x00, 0xff, 0xd1, 0xf5, 0x87,
0x0f, 0x8a, 0x0f, 0xff, 0x03, 0xf0, 0x3f, 0x8b, 0x00, 0x8e, 0x00, 0x90, 0x00, 0xb9, 0x00, 0x2d, 0xf5, 0xca, 0xf5,
0xcb, 0x01, 0x20, 0xf2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x50, 0x98, 0x2e,
0xd7, 0x0e, 0x50, 0x32, 0x98, 0x2e, 0xfa, 0x03, 0x00, 0x30, 0xf0, 0x7f, 0x00, 0x2e, 0x00, 0x2e, 0xd0, 0x2e, 0x00,
0x2e, 0x01, 0x80, 0x08, 0xa2, 0xfb, 0x2f, 0x98, 0x2e, 0xba, 0x03, 0x21, 0x2e, 0x19, 0x00, 0x01, 0x2e, 0xee, 0x00,
0x00, 0xb2, 0x07, 0x2f, 0x01, 0x2e, 0x19, 0x00, 0x00, 0xb2, 0x03, 0x2f, 0x01, 0x50, 0x03, 0x52, 0x98, 0x2e, 0x07,
0xcc, 0x01, 0x2e, 0xdd, 0x00, 0x00, 0xb2, 0x27, 0x2f, 0x05, 0x2e, 0x8a, 0x00, 0x05, 0x52, 0x98, 0x2e, 0xc7, 0xc1,
0x03, 0x2e, 0xe9, 0x00, 0x40, 0xb2, 0xf0, 0x7f, 0x08, 0x2f, 0x01, 0x2e, 0x19, 0x00, 0x00, 0xb2, 0x04, 0x2f, 0x00,
0x30, 0x21, 0x2e, 0xe9, 0x00, 0x98, 0x2e, 0xb4, 0xb1, 0x01, 0x2e, 0x18, 0x00, 0x00, 0xb2, 0x10, 0x2f, 0x05, 0x50,
0x98, 0x2e, 0x4d, 0xc3, 0x05, 0x50, 0x98, 0x2e, 0x5a, 0xc7, 0x98, 0x2e, 0xf9, 0xb4, 0x98, 0x2e, 0x54, 0xb2, 0x98,
0x2e, 0x67, 0xb6, 0x98, 0x2e, 0x17, 0xb2, 0x10, 0x30, 0x21, 0x2e, 0x77, 0x00, 0x01, 0x2e, 0xef, 0x00, 0x00, 0xb2,
0x04, 0x2f, 0x98, 0x2e, 0x7a, 0xb7, 0x00, 0x30, 0x21, 0x2e, 0xef, 0x00, 0x01, 0x2e, 0xd4, 0x00, 0x04, 0xae, 0x0b,
0x2f, 0x01, 0x2e, 0xdd, 0x00, 0x00, 0xb2, 0x07, 0x2f, 0x05, 0x52, 0x98, 0x2e, 0x8e, 0x0e, 0x00, 0xb2, 0x02, 0x2f,
0x10, 0x30, 0x21, 0x2e, 0x7d, 0x00, 0x01, 0x2e, 0x7d, 0x00, 0x00, 0x90, 0x90, 0x2e, 0xf1, 0x02, 0x01, 0x2e, 0xd7,
0x00, 0x00, 0xb2, 0x04, 0x2f, 0x98, 0x2e, 0x2f, 0x0e, 0x00, 0x30, 0x21, 0x2e, 0x7b, 0x00, 0x01, 0x2e, 0x7b, 0x00,
0x00, 0xb2, 0x12, 0x2f, 0x01, 0x2e, 0xd4, 0x00, 0x00, 0x90, 0x02, 0x2f, 0x98, 0x2e, 0x1f, 0x0e, 0x09, 0x2d, 0x98,
0x2e, 0x81, 0x0d, 0x01, 0x2e, 0xd4, 0x00, 0x04, 0x90, 0x02, 0x2f, 0x50, 0x32, 0x98, 0x2e, 0xfa, 0x03, 0x00, 0x30,
0x21, 0x2e, 0x7b, 0x00, 0x01, 0x2e, 0x7c, 0x00, 0x00, 0xb2, 0x90, 0x2e, 0x09, 0x03, 0x01, 0x2e, 0x7c, 0x00, 0x01,
0x31, 0x01, 0x08, 0x00, 0xb2, 0x04, 0x2f, 0x98, 0x2e, 0x47, 0xcb, 0x10, 0x30, 0x21, 0x2e, 0x77, 0x00, 0x81, 0x30,
0x01, 0x2e, 0x7c, 0x00, 0x01, 0x08, 0x00, 0xb2, 0x61, 0x2f, 0x03, 0x2e, 0x89, 0x00, 0x01, 0x2e, 0xd4, 0x00, 0x98,
0xbc, 0x98, 0xb8, 0x05, 0xb2, 0x0f, 0x58, 0x23, 0x2f, 0x07, 0x90, 0x09, 0x54, 0x00, 0x30, 0x37, 0x2f, 0x15, 0x41,
0x04, 0x41, 0xdc, 0xbe, 0x44, 0xbe, 0xdc, 0xba, 0x2c, 0x01, 0x61, 0x00, 0x0f, 0x56, 0x4a, 0x0f, 0x0c, 0x2f, 0xd1,
0x42, 0x94, 0xb8, 0xc1, 0x42, 0x11, 0x30, 0x05, 0x2e, 0x6a, 0xf7, 0x2c, 0xbd, 0x2f, 0xb9, 0x80, 0xb2, 0x08, 0x22,
0x98, 0x2e, 0xc3, 0xb7, 0x21, 0x2d, 0x61, 0x30, 0x23, 0x2e, 0xd4, 0x00, 0x98, 0x2e, 0xc3, 0xb7, 0x00, 0x30, 0x21,
0x2e, 0x5a, 0xf5, 0x18, 0x2d, 0xe1, 0x7f, 0x50, 0x30, 0x98, 0x2e, 0xfa, 0x03, 0x0f, 0x52, 0x07, 0x50, 0x50, 0x42,
0x70, 0x30, 0x0d, 0x54, 0x42, 0x42, 0x7e, 0x82, 0xe2, 0x6f, 0x80, 0xb2, 0x42, 0x42, 0x05, 0x2f, 0x21, 0x2e, 0xd4,
0x00, 0x10, 0x30, 0x98, 0x2e, 0xc3, 0xb7, 0x03, 0x2d, 0x60, 0x30, 0x21, 0x2e, 0xd4, 0x00, 0x01, 0x2e, 0xd4, 0x00,
0x06, 0x90, 0x18, 0x2f, 0x01, 0x2e, 0x76, 0x00, 0x0b, 0x54, 0x07, 0x52, 0xe0, 0x7f, 0x98, 0x2e, 0x7a, 0xc1, 0xe1,
0x6f, 0x08, 0x1a, 0x40, 0x30, 0x08, 0x2f, 0x21, 0x2e, 0xd4, 0x00, 0x20, 0x30, 0x98, 0x2e, 0xaf, 0xb7, 0x50, 0x32,
0x98, 0x2e, 0xfa, 0x03, 0x05, 0x2d, 0x98, 0x2e, 0x38, 0x0e, 0x00, 0x30, 0x21, 0x2e, 0xd4, 0x00, 0x00, 0x30, 0x21,
0x2e, 0x7c, 0x00, 0x18, 0x2d, 0x01, 0x2e, 0xd4, 0x00, 0x03, 0xaa, 0x01, 0x2f, 0x98, 0x2e, 0x45, 0x0e, 0x01, 0x2e,
0xd4, 0x00, 0x3f, 0x80, 0x03, 0xa2, 0x01, 0x2f, 0x00, 0x2e, 0x02, 0x2d, 0x98, 0x2e, 0x5b, 0x0e, 0x30, 0x30, 0x98,
0x2e, 0xce, 0xb7, 0x00, 0x30, 0x21, 0x2e, 0x7d, 0x00, 0x50, 0x32, 0x98, 0x2e, 0xfa, 0x03, 0x01, 0x2e, 0x77, 0x00,
0x00, 0xb2, 0x24, 0x2f, 0x98, 0x2e, 0xf5, 0xcb, 0x03, 0x2e, 0xd5, 0x00, 0x11, 0x54, 0x01, 0x0a, 0xbc, 0x84, 0x83,
0x86, 0x21, 0x2e, 0xc9, 0x01, 0xe0, 0x40, 0x13, 0x52, 0xc4, 0x40, 0x82, 0x40, 0xa8, 0xb9, 0x52, 0x42, 0x43, 0xbe,
0x53, 0x42, 0x04, 0x0a, 0x50, 0x42, 0xe1, 0x7f, 0xf0, 0x31, 0x41, 0x40, 0xf2, 0x6f, 0x25, 0xbd, 0x08, 0x08, 0x02,
0x0a, 0xd0, 0x7f, 0x98, 0x2e, 0xa8, 0xcf, 0x06, 0xbc, 0xd1, 0x6f, 0xe2, 0x6f, 0x08, 0x0a, 0x80, 0x42, 0x98, 0x2e,
0x58, 0xb7, 0x00, 0x30, 0x21, 0x2e, 0xee, 0x00, 0x21, 0x2e, 0x77, 0x00, 0x21, 0x2e, 0xdd, 0x00, 0x80, 0x2e, 0xf4,
0x01, 0x1a, 0x24, 0x22, 0x00, 0x80, 0x2e, 0xec, 0x01, 0x10, 0x50, 0xfb, 0x7f, 0x98, 0x2e, 0xf3, 0x03, 0x57, 0x50,
0xfb, 0x6f, 0x01, 0x30, 0x71, 0x54, 0x11, 0x42, 0x42, 0x0e, 0xfc, 0x2f, 0xc0, 0x2e, 0x01, 0x42, 0xf0, 0x5f, 0x80,
0x2e, 0x00, 0xc1, 0xfd, 0x2d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0x01,
0x34, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x20, 0x50, 0xe7, 0x7f, 0xf6, 0x7f, 0x06, 0x32, 0x0f, 0x2e, 0x61, 0xf5, 0xfe, 0x09, 0xc0, 0xb3, 0x04,
0x2f, 0x17, 0x30, 0x2f, 0x2e, 0xef, 0x00, 0x2d, 0x2e, 0x61, 0xf5, 0xf6, 0x6f, 0xe7, 0x6f, 0xe0, 0x5f, 0xc8, 0x2e,
0x20, 0x50, 0xe7, 0x7f, 0xf6, 0x7f, 0x46, 0x30, 0x0f, 0x2e, 0xa4, 0xf1, 0xbe, 0x09, 0x80, 0xb3, 0x06, 0x2f, 0x0d,
0x2e, 0xd4, 0x00, 0x84, 0xaf, 0x02, 0x2f, 0x16, 0x30, 0x2d, 0x2e, 0x7b, 0x00, 0x86, 0x30, 0x2d, 0x2e, 0x60, 0xf5,
0xf6, 0x6f, 0xe7, 0x6f, 0xe0, 0x5f, 0xc8, 0x2e, 0x01, 0x2e, 0x77, 0xf7, 0x09, 0xbc, 0x0f, 0xb8, 0x00, 0xb2, 0x10,
0x50, 0xfb, 0x7f, 0x10, 0x30, 0x0b, 0x2f, 0x03, 0x2e, 0x8a, 0x00, 0x96, 0xbc, 0x9f, 0xb8, 0x40, 0xb2, 0x05, 0x2f,
0x03, 0x2e, 0x68, 0xf7, 0x9e, 0xbc, 0x9f, 0xb8, 0x40, 0xb2, 0x07, 0x2f, 0x03, 0x2e, 0x7e, 0x00, 0x41, 0x90, 0x01,
0x2f, 0x98, 0x2e, 0xdc, 0x03, 0x03, 0x2c, 0x00, 0x30, 0x21, 0x2e, 0x7e, 0x00, 0xfb, 0x6f, 0xf0, 0x5f, 0xb8, 0x2e,
0x20, 0x50, 0xe0, 0x7f, 0xfb, 0x7f, 0x00, 0x2e, 0x27, 0x50, 0x98, 0x2e, 0x3b, 0xc8, 0x29, 0x50, 0x98, 0x2e, 0xa7,
0xc8, 0x01, 0x50, 0x98, 0x2e, 0x55, 0xcc, 0xe1, 0x6f, 0x2b, 0x50, 0x98, 0x2e, 0xe0, 0xc9, 0xfb, 0x6f, 0x00, 0x30,
0xe0, 0x5f, 0x21, 0x2e, 0x7e, 0x00, 0xb8, 0x2e, 0x73, 0x50, 0x01, 0x30, 0x57, 0x54, 0x11, 0x42, 0x42, 0x0e, 0xfc,
0x2f, 0xb8, 0x2e, 0x21, 0x2e, 0x59, 0xf5, 0x10, 0x30, 0xc0, 0x2e, 0x21, 0x2e, 0x4a, 0xf1, 0x90, 0x50, 0xf7, 0x7f,
0xe6, 0x7f, 0xd5, 0x7f, 0xc4, 0x7f, 0xb3, 0x7f, 0xa1, 0x7f, 0x90, 0x7f, 0x82, 0x7f, 0x7b, 0x7f, 0x98, 0x2e, 0x35,
0xb7, 0x00, 0xb2, 0x90, 0x2e, 0x97, 0xb0, 0x03, 0x2e, 0x8f, 0x00, 0x07, 0x2e, 0x91, 0x00, 0x05, 0x2e, 0xb1, 0x00,
0x3f, 0xba, 0x9f, 0xb8, 0x01, 0x2e, 0xb1, 0x00, 0xa3, 0xbd, 0x4c, 0x0a, 0x05, 0x2e, 0xb1, 0x00, 0x04, 0xbe, 0xbf,
0xb9, 0xcb, 0x0a, 0x4f, 0xba, 0x22, 0xbd, 0x01, 0x2e, 0xb3, 0x00, 0xdc, 0x0a, 0x2f, 0xb9, 0x03, 0x2e, 0xb8, 0x00,
0x0a, 0xbe, 0x9a, 0x0a, 0xcf, 0xb9, 0x9b, 0xbc, 0x01, 0x2e, 0x97, 0x00, 0x9f, 0xb8, 0x93, 0x0a, 0x0f, 0xbc, 0x91,
0x0a, 0x0f, 0xb8, 0x90, 0x0a, 0x25, 0x2e, 0x18, 0x00, 0x05, 0x2e, 0xc1, 0xf5, 0x2e, 0xbd, 0x2e, 0xb9, 0x01, 0x2e,
0x19, 0x00, 0x31, 0x30, 0x8a, 0x04, 0x00, 0x90, 0x07, 0x2f, 0x01, 0x2e, 0xd4, 0x00, 0x04, 0xa2, 0x03, 0x2f, 0x01,
0x2e, 0x18, 0x00, 0x00, 0xb2, 0x0c, 0x2f, 0x19, 0x50, 0x05, 0x52, 0x98, 0x2e, 0x4d, 0xb7, 0x05, 0x2e, 0x78, 0x00,
0x80, 0x90, 0x10, 0x30, 0x01, 0x2f, 0x21, 0x2e, 0x78, 0x00, 0x25, 0x2e, 0xdd, 0x00, 0x98, 0x2e, 0x3e, 0xb7, 0x00,
0xb2, 0x02, 0x30, 0x01, 0x30, 0x04, 0x2f, 0x01, 0x2e, 0x19, 0x00, 0x00, 0xb2, 0x00, 0x2f, 0x21, 0x30, 0x01, 0x2e,
0xea, 0x00, 0x08, 0x1a, 0x0e, 0x2f, 0x23, 0x2e, 0xea, 0x00, 0x33, 0x30, 0x1b, 0x50, 0x0b, 0x09, 0x01, 0x40, 0x17,
0x56, 0x46, 0xbe, 0x4b, 0x08, 0x4c, 0x0a, 0x01, 0x42, 0x0a, 0x80, 0x15, 0x52, 0x01, 0x42, 0x00, 0x2e, 0x01, 0x2e,
0x18, 0x00, 0x00, 0xb2, 0x1f, 0x2f, 0x03, 0x2e, 0xc0, 0xf5, 0xf0, 0x30, 0x48, 0x08, 0x47, 0xaa, 0x74, 0x30, 0x07,
0x2e, 0x7a, 0x00, 0x61, 0x22, 0x4b, 0x1a, 0x05, 0x2f, 0x07, 0x2e, 0x66, 0xf5, 0xbf, 0xbd, 0xbf, 0xb9, 0xc0, 0x90,
0x0b, 0x2f, 0x1d, 0x56, 0x2b, 0x30, 0xd2, 0x42, 0xdb, 0x42, 0x01, 0x04, 0xc2, 0x42, 0x04, 0xbd, 0xfe, 0x80, 0x81,
0x84, 0x23, 0x2e, 0x7a, 0x00, 0x02, 0x42, 0x02, 0x32, 0x25, 0x2e, 0x62, 0xf5, 0x05, 0x2e, 0xd6, 0x00, 0x81, 0x84,
0x25, 0x2e, 0xd6, 0x00, 0x02, 0x31, 0x25, 0x2e, 0x60, 0xf5, 0x05, 0x2e, 0x8a, 0x00, 0x0b, 0x50, 0x90, 0x08, 0x80,
0xb2, 0x0b, 0x2f, 0x05, 0x2e, 0xca, 0xf5, 0xf0, 0x3e, 0x90, 0x08, 0x25, 0x2e, 0xca, 0xf5, 0x05, 0x2e, 0x59, 0xf5,
0xe0, 0x3f, 0x90, 0x08, 0x25, 0x2e, 0x59, 0xf5, 0x90, 0x6f, 0xa1, 0x6f, 0xb3, 0x6f, 0xc4, 0x6f, 0xd5, 0x6f, 0xe6,
0x6f, 0xf7, 0x6f, 0x7b, 0x6f, 0x82, 0x6f, 0x70, 0x5f, 0xc8, 0x2e, 0xc0, 0x50, 0x90, 0x7f, 0xe5, 0x7f, 0xd4, 0x7f,
0xc3, 0x7f, 0xb1, 0x7f, 0xa2, 0x7f, 0x87, 0x7f, 0xf6, 0x7f, 0x7b, 0x7f, 0x00, 0x2e, 0x01, 0x2e, 0x60, 0xf5, 0x60,
0x7f, 0x98, 0x2e, 0x35, 0xb7, 0x02, 0x30, 0x63, 0x6f, 0x15, 0x52, 0x50, 0x7f, 0x62, 0x7f, 0x5a, 0x2c, 0x02, 0x32,
0x1a, 0x09, 0x00, 0xb3, 0x14, 0x2f, 0x00, 0xb2, 0x03, 0x2f, 0x09, 0x2e, 0x18, 0x00, 0x00, 0x91, 0x0c, 0x2f, 0x43,
0x7f, 0x98, 0x2e, 0x97, 0xb7, 0x1f, 0x50, 0x02, 0x8a, 0x02, 0x32, 0x04, 0x30, 0x25, 0x2e, 0x64, 0xf5, 0x15, 0x52,
0x50, 0x6f, 0x43, 0x6f, 0x44, 0x43, 0x25, 0x2e, 0x60, 0xf5, 0xd9, 0x08, 0xc0, 0xb2, 0x36, 0x2f, 0x98, 0x2e, 0x3e,
0xb7, 0x00, 0xb2, 0x06, 0x2f, 0x01, 0x2e, 0x19, 0x00, 0x00, 0xb2, 0x02, 0x2f, 0x50, 0x6f, 0x00, 0x90, 0x0a, 0x2f,
0x01, 0x2e, 0x79, 0x00, 0x00, 0x90, 0x19, 0x2f, 0x10, 0x30, 0x21, 0x2e, 0x79, 0x00, 0x00, 0x30, 0x98, 0x2e, 0xdc,
0x03, 0x13, 0x2d, 0x01, 0x2e, 0xc3, 0xf5, 0x0c, 0xbc, 0x0f, 0xb8, 0x12, 0x30, 0x10, 0x04, 0x03, 0xb0, 0x26, 0x25,
0x21, 0x50, 0x03, 0x52, 0x98, 0x2e, 0x4d, 0xb7, 0x10, 0x30, 0x21, 0x2e, 0xee, 0x00, 0x02, 0x30, 0x60, 0x7f, 0x25,
0x2e, 0x79, 0x00, 0x60, 0x6f, 0x00, 0x90, 0x05, 0x2f, 0x00, 0x30, 0x21, 0x2e, 0xea, 0x00, 0x15, 0x50, 0x21, 0x2e,
0x64, 0xf5, 0x15, 0x52, 0x23, 0x2e, 0x60, 0xf5, 0x02, 0x32, 0x50, 0x6f, 0x00, 0x90, 0x02, 0x2f, 0x03, 0x30, 0x27,
0x2e, 0x78, 0x00, 0x07, 0x2e, 0x60, 0xf5, 0x1a, 0x09, 0x00, 0x91, 0xa3, 0x2f, 0x19, 0x09, 0x00, 0x91, 0xa0, 0x2f,
0x90, 0x6f, 0xa2, 0x6f, 0xb1, 0x6f, 0xc3, 0x6f, 0xd4, 0x6f, 0xe5, 0x6f, 0x7b, 0x6f, 0xf6, 0x6f, 0x87, 0x6f, 0x40,
0x5f, 0xc8, 0x2e, 0xc0, 0x50, 0xe7, 0x7f, 0xf6, 0x7f, 0x26, 0x30, 0x0f, 0x2e, 0x61, 0xf5, 0x2f, 0x2e, 0x7c, 0x00,
0x0f, 0x2e, 0x7c, 0x00, 0xbe, 0x09, 0xa2, 0x7f, 0x80, 0x7f, 0x80, 0xb3, 0xd5, 0x7f, 0xc4, 0x7f, 0xb3, 0x7f, 0x91,
0x7f, 0x7b, 0x7f, 0x0b, 0x2f, 0x23, 0x50, 0x1a, 0x25, 0x12, 0x40, 0x42, 0x7f, 0x74, 0x82, 0x12, 0x40, 0x52, 0x7f,
0x00, 0x2e, 0x00, 0x40, 0x60, 0x7f, 0x98, 0x2e, 0x6a, 0xd6, 0x81, 0x30, 0x01, 0x2e, 0x7c, 0x00, 0x01, 0x08, 0x00,
0xb2, 0x42, 0x2f, 0x03, 0x2e, 0x89, 0x00, 0x01, 0x2e, 0x89, 0x00, 0x97, 0xbc, 0x06, 0xbc, 0x9f, 0xb8, 0x0f, 0xb8,
0x00, 0x90, 0x23, 0x2e, 0xd8, 0x00, 0x10, 0x30, 0x01, 0x30, 0x2a, 0x2f, 0x03, 0x2e, 0xd4, 0x00, 0x44, 0xb2, 0x05,
0x2f, 0x47, 0xb2, 0x00, 0x30, 0x2d, 0x2f, 0x21, 0x2e, 0x7c, 0x00, 0x2b, 0x2d, 0x03, 0x2e, 0xfd, 0xf5, 0x9e, 0xbc,
0x9f, 0xb8, 0x40, 0x90, 0x14, 0x2f, 0x03, 0x2e, 0xfc, 0xf5, 0x99, 0xbc, 0x9f, 0xb8, 0x40, 0x90, 0x0e, 0x2f, 0x03,
0x2e, 0x49, 0xf1, 0x25, 0x54, 0x4a, 0x08, 0x40, 0x90, 0x08, 0x2f, 0x98, 0x2e, 0x35, 0xb7, 0x00, 0xb2, 0x10, 0x30,
0x03, 0x2f, 0x50, 0x30, 0x21, 0x2e, 0xd4, 0x00, 0x10, 0x2d, 0x98, 0x2e, 0xaf, 0xb7, 0x00, 0x30, 0x21, 0x2e, 0x7c,
0x00, 0x0a, 0x2d, 0x05, 0x2e, 0x69, 0xf7, 0x2d, 0xbd, 0x2f, 0xb9, 0x80, 0xb2, 0x01, 0x2f, 0x21, 0x2e, 0x7d, 0x00,
0x23, 0x2e, 0x7c, 0x00, 0xe0, 0x31, 0x21, 0x2e, 0x61, 0xf5, 0xf6, 0x6f, 0xe7, 0x6f, 0x80, 0x6f, 0xa2, 0x6f, 0xb3,
0x6f, 0xc4, 0x6f, 0xd5, 0x6f, 0x7b, 0x6f, 0x91, 0x6f, 0x40, 0x5f, 0xc8, 0x2e, 0x60, 0x51, 0x0a, 0x25, 0x36, 0x88,
0xf4, 0x7f, 0xeb, 0x7f, 0x00, 0x32, 0x31, 0x52, 0x32, 0x30, 0x13, 0x30, 0x98, 0x2e, 0x15, 0xcb, 0x0a, 0x25, 0x33,
0x84, 0xd2, 0x7f, 0x43, 0x30, 0x05, 0x50, 0x2d, 0x52, 0x98, 0x2e, 0x95, 0xc1, 0xd2, 0x6f, 0x27, 0x52, 0x98, 0x2e,
0xd7, 0xc7, 0x2a, 0x25, 0xb0, 0x86, 0xc0, 0x7f, 0xd3, 0x7f, 0xaf, 0x84, 0x29, 0x50, 0xf1, 0x6f, 0x98, 0x2e, 0x4d,
0xc8, 0x2a, 0x25, 0xae, 0x8a, 0xaa, 0x88, 0xf2, 0x6e, 0x2b, 0x50, 0xc1, 0x6f, 0xd3, 0x6f, 0xf4, 0x7f, 0x98, 0x2e,
0xb6, 0xc8, 0xe0, 0x6e, 0x00, 0xb2, 0x32, 0x2f, 0x33, 0x54, 0x83, 0x86, 0xf1, 0x6f, 0xc3, 0x7f, 0x04, 0x30, 0x30,
0x30, 0xf4, 0x7f, 0xd0, 0x7f, 0xb2, 0x7f, 0xe3, 0x30, 0xc5, 0x6f, 0x56, 0x40, 0x45, 0x41, 0x28, 0x08, 0x03, 0x14,
0x0e, 0xb4, 0x08, 0xbc, 0x82, 0x40, 0x10, 0x0a, 0x2f, 0x54, 0x26, 0x05, 0x91, 0x7f, 0x44, 0x28, 0xa3, 0x7f, 0x98,
0x2e, 0xd9, 0xc0, 0x08, 0xb9, 0x33, 0x30, 0x53, 0x09, 0xc1, 0x6f, 0xd3, 0x6f, 0xf4, 0x6f, 0x83, 0x17, 0x47, 0x40,
0x6c, 0x15, 0xb2, 0x6f, 0xbe, 0x09, 0x75, 0x0b, 0x90, 0x42, 0x45, 0x42, 0x51, 0x0e, 0x32, 0xbc, 0x02, 0x89, 0xa1,
0x6f, 0x7e, 0x86, 0xf4, 0x7f, 0xd0, 0x7f, 0xb2, 0x7f, 0x04, 0x30, 0x91, 0x6f, 0xd6, 0x2f, 0xeb, 0x6f, 0xa0, 0x5e,
0xb8, 0x2e, 0x03, 0x2e, 0x97, 0x00, 0x1b, 0xbc, 0x60, 0x50, 0x9f, 0xbc, 0x0c, 0xb8, 0xf0, 0x7f, 0x40, 0xb2, 0xeb,
0x7f, 0x2b, 0x2f, 0x03, 0x2e, 0x7f, 0x00, 0x41, 0x40, 0x01, 0x2e, 0xc8, 0x00, 0x01, 0x1a, 0x11, 0x2f, 0x37, 0x58,
0x23, 0x2e, 0xc8, 0x00, 0x10, 0x41, 0xa0, 0x7f, 0x38, 0x81, 0x01, 0x41, 0xd0, 0x7f, 0xb1, 0x7f, 0x98, 0x2e, 0x64,
0xcf, 0xd0, 0x6f, 0x07, 0x80, 0xa1, 0x6f, 0x11, 0x42, 0x00, 0x2e, 0xb1, 0x6f, 0x01, 0x42, 0x11, 0x30, 0x01, 0x2e,
0xfc, 0x00, 0x00, 0xa8, 0x03, 0x30, 0xcb, 0x22, 0x4a, 0x25, 0x01, 0x2e, 0x7f, 0x00, 0x3c, 0x89, 0x35, 0x52, 0x05,
0x54, 0x98, 0x2e, 0xc4, 0xce, 0xc1, 0x6f, 0xf0, 0x6f, 0x98, 0x2e, 0x95, 0xcf, 0x04, 0x2d, 0x01, 0x30, 0xf0, 0x6f,
0x98, 0x2e, 0x95, 0xcf, 0xeb, 0x6f, 0xa0, 0x5f, 0xb8, 0x2e, 0x03, 0x2e, 0xb3, 0x00, 0x02, 0x32, 0xf0, 0x30, 0x03,
0x31, 0x30, 0x50, 0x8a, 0x08, 0x08, 0x08, 0xcb, 0x08, 0xe0, 0x7f, 0x80, 0xb2, 0xf3, 0x7f, 0xdb, 0x7f, 0x25, 0x2f,
0x03, 0x2e, 0xca, 0x00, 0x41, 0x90, 0x04, 0x2f, 0x01, 0x30, 0x23, 0x2e, 0xca, 0x00, 0x98, 0x2e, 0x3f, 0x03, 0xc0,
0xb2, 0x05, 0x2f, 0x03, 0x2e, 0xda, 0x00, 0x00, 0x30, 0x41, 0x04, 0x23, 0x2e, 0xda, 0x00, 0x98, 0x2e, 0x92, 0xb2,
0x10, 0x25, 0xf0, 0x6f, 0x00, 0xb2, 0x05, 0x2f, 0x01, 0x2e, 0xda, 0x00, 0x02, 0x30, 0x10, 0x04, 0x21, 0x2e, 0xda,
0x00, 0x40, 0xb2, 0x01, 0x2f, 0x23, 0x2e, 0xc8, 0x01, 0xdb, 0x6f, 0xe0, 0x6f, 0xd0, 0x5f, 0x80, 0x2e, 0x95, 0xcf,
0x01, 0x30, 0xe0, 0x6f, 0x98, 0x2e, 0x95, 0xcf, 0x11, 0x30, 0x23, 0x2e, 0xca, 0x00, 0xdb, 0x6f, 0xd0, 0x5f, 0xb8,
0x2e, 0xd0, 0x50, 0x0a, 0x25, 0x33, 0x84, 0x55, 0x50, 0xd2, 0x7f, 0xe2, 0x7f, 0x03, 0x8c, 0xc0, 0x7f, 0xbb, 0x7f,
0x00, 0x30, 0x05, 0x5a, 0x39, 0x54, 0x51, 0x41, 0xa5, 0x7f, 0x96, 0x7f, 0x80, 0x7f, 0x98, 0x2e, 0xd9, 0xc0, 0x05,
0x30, 0xf5, 0x7f, 0x20, 0x25, 0x91, 0x6f, 0x3b, 0x58, 0x3d, 0x5c, 0x3b, 0x56, 0x98, 0x2e, 0x67, 0xcc, 0xc1, 0x6f,
0xd5, 0x6f, 0x52, 0x40, 0x50, 0x43, 0xc1, 0x7f, 0xd5, 0x7f, 0x10, 0x25, 0x98, 0x2e, 0xfe, 0xc9, 0x10, 0x25, 0x98,
0x2e, 0x74, 0xc0, 0x86, 0x6f, 0x30, 0x28, 0x92, 0x6f, 0x82, 0x8c, 0xa5, 0x6f, 0x6f, 0x52, 0x69, 0x0e, 0x39, 0x54,
0xdb, 0x2f, 0x19, 0xa0, 0x15, 0x30, 0x03, 0x2f, 0x00, 0x30, 0x21, 0x2e, 0x81, 0x01, 0x0a, 0x2d, 0x01, 0x2e, 0x81,
0x01, 0x05, 0x28, 0x42, 0x36, 0x21, 0x2e, 0x81, 0x01, 0x02, 0x0e, 0x01, 0x2f, 0x98, 0x2e, 0xf3, 0x03, 0x57, 0x50,
0x12, 0x30, 0x01, 0x40, 0x98, 0x2e, 0xfe, 0xc9, 0x51, 0x6f, 0x0b, 0x5c, 0x8e, 0x0e, 0x3b, 0x6f, 0x57, 0x58, 0x02,
0x30, 0x21, 0x2e, 0x95, 0x01, 0x45, 0x6f, 0x2a, 0x8d, 0xd2, 0x7f, 0xcb, 0x7f, 0x13, 0x2f, 0x02, 0x30, 0x3f, 0x50,
0xd2, 0x7f, 0xa8, 0x0e, 0x0e, 0x2f, 0xc0, 0x6f, 0x53, 0x54, 0x02, 0x00, 0x51, 0x54, 0x42, 0x0e, 0x10, 0x30, 0x59,
0x52, 0x02, 0x30, 0x01, 0x2f, 0x00, 0x2e, 0x03, 0x2d, 0x50, 0x42, 0x42, 0x42, 0x12, 0x30, 0xd2, 0x7f, 0x80, 0xb2,
0x03, 0x2f, 0x00, 0x30, 0x21, 0x2e, 0x80, 0x01, 0x12, 0x2d, 0x01, 0x2e, 0xc9, 0x00, 0x02, 0x80, 0x05, 0x2e, 0x80,
0x01, 0x11, 0x30, 0x91, 0x28, 0x00, 0x40, 0x25, 0x2e, 0x80, 0x01, 0x10, 0x0e, 0x05, 0x2f, 0x01, 0x2e, 0x7f, 0x01,
0x01, 0x90, 0x01, 0x2f, 0x98, 0x2e, 0xf3, 0x03, 0x00, 0x2e, 0xa0, 0x41, 0x01, 0x90, 0xa6, 0x7f, 0x90, 0x2e, 0xe3,
0xb4, 0x01, 0x2e, 0x95, 0x01, 0x00, 0xa8, 0x90, 0x2e, 0xe3, 0xb4, 0x5b, 0x54, 0x95, 0x80, 0x82, 0x40, 0x80, 0xb2,
0x02, 0x40, 0x2d, 0x8c, 0x3f, 0x52, 0x96, 0x7f, 0x90, 0x2e, 0xc2, 0xb3, 0x29, 0x0e, 0x76, 0x2f, 0x01, 0x2e, 0xc9,
0x00, 0x00, 0x40, 0x81, 0x28, 0x45, 0x52, 0xb3, 0x30, 0x98, 0x2e, 0x0f, 0xca, 0x5d, 0x54, 0x80, 0x7f, 0x00, 0x2e,
0xa1, 0x40, 0x72, 0x7f, 0x82, 0x80, 0x82, 0x40, 0x60, 0x7f, 0x98, 0x2e, 0xfe, 0xc9, 0x10, 0x25, 0x98, 0x2e, 0x74,
0xc0, 0x62, 0x6f, 0x05, 0x30, 0x87, 0x40, 0xc0, 0x91, 0x04, 0x30, 0x05, 0x2f, 0x05, 0x2e, 0x83, 0x01, 0x80, 0xb2,
0x14, 0x30, 0x00, 0x2f, 0x04, 0x30, 0x05, 0x2e, 0xc9, 0x00, 0x73, 0x6f, 0x81, 0x40, 0xe2, 0x40, 0x69, 0x04, 0x11,
0x0f, 0xe1, 0x40, 0x16, 0x30, 0xfe, 0x29, 0xcb, 0x40, 0x02, 0x2f, 0x83, 0x6f, 0x83, 0x0f, 0x22, 0x2f, 0x47, 0x56,
0x13, 0x0f, 0x12, 0x30, 0x77, 0x2f, 0x49, 0x54, 0x42, 0x0e, 0x12, 0x30, 0x73, 0x2f, 0x00, 0x91, 0x0a, 0x2f, 0x01,
0x2e, 0x8b, 0x01, 0x19, 0xa8, 0x02, 0x30, 0x6c, 0x2f, 0x63, 0x50, 0x00, 0x2e, 0x17, 0x42, 0x05, 0x42, 0x68, 0x2c,
0x12, 0x30, 0x0b, 0x25, 0x08, 0x0f, 0x50, 0x30, 0x02, 0x2f, 0x21, 0x2e, 0x83, 0x01, 0x03, 0x2d, 0x40, 0x30, 0x21,
0x2e, 0x83, 0x01, 0x2b, 0x2e, 0x85, 0x01, 0x5a, 0x2c, 0x12, 0x30, 0x00, 0x91, 0x2b, 0x25, 0x04, 0x2f, 0x63, 0x50,
0x02, 0x30, 0x17, 0x42, 0x17, 0x2c, 0x02, 0x42, 0x98, 0x2e, 0xfe, 0xc9, 0x10, 0x25, 0x98, 0x2e, 0x74, 0xc0, 0x05,
0x2e, 0xc9, 0x00, 0x81, 0x84, 0x5b, 0x30, 0x82, 0x40, 0x37, 0x2e, 0x83, 0x01, 0x02, 0x0e, 0x07, 0x2f, 0x5f, 0x52,
0x40, 0x30, 0x62, 0x40, 0x41, 0x40, 0x91, 0x0e, 0x01, 0x2f, 0x21, 0x2e, 0x83, 0x01, 0x05, 0x30, 0x2b, 0x2e, 0x85,
0x01, 0x12, 0x30, 0x36, 0x2c, 0x16, 0x30, 0x15, 0x25, 0x81, 0x7f, 0x98, 0x2e, 0xfe, 0xc9, 0x10, 0x25, 0x98, 0x2e,
0x74, 0xc0, 0x19, 0xa2, 0x16, 0x30, 0x15, 0x2f, 0x05, 0x2e, 0x97, 0x01, 0x80, 0x6f, 0x82, 0x0e, 0x05, 0x2f, 0x01,
0x2e, 0x86, 0x01, 0x06, 0x28, 0x21, 0x2e, 0x86, 0x01, 0x0b, 0x2d, 0x03, 0x2e, 0x87, 0x01, 0x5f, 0x54, 0x4e, 0x28,
0x91, 0x42, 0x00, 0x2e, 0x82, 0x40, 0x90, 0x0e, 0x01, 0x2f, 0x21, 0x2e, 0x88, 0x01, 0x02, 0x30, 0x13, 0x2c, 0x05,
0x30, 0xc0, 0x6f, 0x08, 0x1c, 0xa8, 0x0f, 0x16, 0x30, 0x05, 0x30, 0x5b, 0x50, 0x09, 0x2f, 0x02, 0x80, 0x2d, 0x2e,
0x82, 0x01, 0x05, 0x42, 0x05, 0x80, 0x00, 0x2e, 0x02, 0x42, 0x3e, 0x80, 0x00, 0x2e, 0x06, 0x42, 0x02, 0x30, 0x90,
0x6f, 0x3e, 0x88, 0x01, 0x40, 0x04, 0x41, 0x4c, 0x28, 0x01, 0x42, 0x07, 0x80, 0x10, 0x25, 0x24, 0x40, 0x00, 0x40,
0x00, 0xa8, 0xf5, 0x22, 0x23, 0x29, 0x44, 0x42, 0x7a, 0x82, 0x7e, 0x88, 0x43, 0x40, 0x04, 0x41, 0x00, 0xab, 0xf5,
0x23, 0xdf, 0x28, 0x43, 0x42, 0xd9, 0xa0, 0x14, 0x2f, 0x00, 0x90, 0x02, 0x2f, 0xd2, 0x6f, 0x81, 0xb2, 0x05, 0x2f,
0x63, 0x54, 0x06, 0x28, 0x90, 0x42, 0x85, 0x42, 0x09, 0x2c, 0x02, 0x30, 0x5b, 0x50, 0x03, 0x80, 0x29, 0x2e, 0x7e,
0x01, 0x2b, 0x2e, 0x82, 0x01, 0x05, 0x42, 0x12, 0x30, 0x2b, 0x2e, 0x83, 0x01, 0x45, 0x82, 0x00, 0x2e, 0x40, 0x40,
0x7a, 0x82, 0x02, 0xa0, 0x08, 0x2f, 0x63, 0x50, 0x3b, 0x30, 0x15, 0x42, 0x05, 0x42, 0x37, 0x80, 0x37, 0x2e, 0x7e,
0x01, 0x05, 0x42, 0x12, 0x30, 0x01, 0x2e, 0xc9, 0x00, 0x02, 0x8c, 0x40, 0x40, 0x84, 0x41, 0x7a, 0x8c, 0x04, 0x0f,
0x03, 0x2f, 0x01, 0x2e, 0x8b, 0x01, 0x19, 0xa4, 0x04, 0x2f, 0x2b, 0x2e, 0x82, 0x01, 0x98, 0x2e, 0xf3, 0x03, 0x12,
0x30, 0x81, 0x90, 0x61, 0x52, 0x08, 0x2f, 0x65, 0x42, 0x65, 0x42, 0x43, 0x80, 0x39, 0x84, 0x82, 0x88, 0x05, 0x42,
0x45, 0x42, 0x85, 0x42, 0x05, 0x43, 0x00, 0x2e, 0x80, 0x41, 0x00, 0x90, 0x90, 0x2e, 0xe1, 0xb4, 0x65, 0x54, 0xc1,
0x6f, 0x80, 0x40, 0x00, 0xb2, 0x43, 0x58, 0x69, 0x50, 0x44, 0x2f, 0x55, 0x5c, 0xb7, 0x87, 0x8c, 0x0f, 0x0d, 0x2e,
0x96, 0x01, 0xc4, 0x40, 0x36, 0x2f, 0x41, 0x56, 0x8b, 0x0e, 0x2a, 0x2f, 0x0b, 0x52, 0xa1, 0x0e, 0x0a, 0x2f, 0x05,
0x2e, 0x8f, 0x01, 0x14, 0x25, 0x98, 0x2e, 0xfe, 0xc9, 0x4b, 0x54, 0x02, 0x0f, 0x69, 0x50, 0x05, 0x30, 0x65, 0x54,
0x15, 0x2f, 0x03, 0x2e, 0x8e, 0x01, 0x4d, 0x5c, 0x8e, 0x0f, 0x3a, 0x2f, 0x05, 0x2e, 0x8f, 0x01, 0x98, 0x2e, 0xfe,
0xc9, 0x4f, 0x54, 0x82, 0x0f, 0x05, 0x30, 0x69, 0x50, 0x65, 0x54, 0x30, 0x2f, 0x6d, 0x52, 0x15, 0x30, 0x42, 0x8c,
0x45, 0x42, 0x04, 0x30, 0x2b, 0x2c, 0x84, 0x43, 0x6b, 0x52, 0x42, 0x8c, 0x00, 0x2e, 0x85, 0x43, 0x15, 0x30, 0x24,
0x2c, 0x45, 0x42, 0x8e, 0x0f, 0x20, 0x2f, 0x0d, 0x2e, 0x8e, 0x01, 0xb1, 0x0e, 0x1c, 0x2f, 0x23, 0x2e, 0x8e, 0x01,
0x1a, 0x2d, 0x0e, 0x0e, 0x17, 0x2f, 0xa1, 0x0f, 0x15, 0x2f, 0x23, 0x2e, 0x8d, 0x01, 0x13, 0x2d, 0x98, 0x2e, 0x74,
0xc0, 0x43, 0x54, 0xc2, 0x0e, 0x0a, 0x2f, 0x65, 0x50, 0x04, 0x80, 0x0b, 0x30, 0x06, 0x82, 0x0b, 0x42, 0x79, 0x80,
0x41, 0x40, 0x12, 0x30, 0x25, 0x2e, 0x8c, 0x01, 0x01, 0x42, 0x05, 0x30, 0x69, 0x50, 0x65, 0x54, 0x84, 0x82, 0x43,
0x84, 0xbe, 0x8c, 0x84, 0x40, 0x86, 0x41, 0x26, 0x29, 0x94, 0x42, 0xbe, 0x8e, 0xd5, 0x7f, 0x19, 0xa1, 0x43, 0x40,
0x0b, 0x2e, 0x8c, 0x01, 0x84, 0x40, 0xc7, 0x41, 0x5d, 0x29, 0x27, 0x29, 0x45, 0x42, 0x84, 0x42, 0xc2, 0x7f, 0x01,
0x2f, 0xc0, 0xb3, 0x1d, 0x2f, 0x05, 0x2e, 0x94, 0x01, 0x99, 0xa0, 0x01, 0x2f, 0x80, 0xb3, 0x13, 0x2f, 0x80, 0xb3,
0x18, 0x2f, 0xc0, 0xb3, 0x16, 0x2f, 0x12, 0x40, 0x01, 0x40, 0x92, 0x7f, 0x98, 0x2e, 0x74, 0xc0, 0x92, 0x6f, 0x10,
0x0f, 0x20, 0x30, 0x03, 0x2f, 0x10, 0x30, 0x21, 0x2e, 0x7e, 0x01, 0x0a, 0x2d, 0x21, 0x2e, 0x7e, 0x01, 0x07, 0x2d,
0x20, 0x30, 0x21, 0x2e, 0x7e, 0x01, 0x03, 0x2d, 0x10, 0x30, 0x21, 0x2e, 0x7e, 0x01, 0xc2, 0x6f, 0x01, 0x2e, 0xc9,
0x00, 0xbc, 0x84, 0x02, 0x80, 0x82, 0x40, 0x00, 0x40, 0x90, 0x0e, 0xd5, 0x6f, 0x02, 0x2f, 0x15, 0x30, 0x98, 0x2e,
0xf3, 0x03, 0x41, 0x91, 0x05, 0x30, 0x07, 0x2f, 0x67, 0x50, 0x3d, 0x80, 0x2b, 0x2e, 0x8f, 0x01, 0x05, 0x42, 0x04,
0x80, 0x00, 0x2e, 0x05, 0x42, 0x02, 0x2c, 0x00, 0x30, 0x00, 0x30, 0xa2, 0x6f, 0x98, 0x8a, 0x86, 0x40, 0x80, 0xa7,
0x05, 0x2f, 0x98, 0x2e, 0xf3, 0x03, 0xc0, 0x30, 0x21, 0x2e, 0x95, 0x01, 0x06, 0x25, 0x1a, 0x25, 0xe2, 0x6f, 0x76,
0x82, 0x96, 0x40, 0x56, 0x43, 0x51, 0x0e, 0xfb, 0x2f, 0xbb, 0x6f, 0x30, 0x5f, 0xb8, 0x2e, 0x01, 0x2e, 0xb8, 0x00,
0x01, 0x31, 0x41, 0x08, 0x40, 0xb2, 0x20, 0x50, 0xf2, 0x30, 0x02, 0x08, 0xfb, 0x7f, 0x01, 0x30, 0x10, 0x2f, 0x05,
0x2e, 0xcc, 0x00, 0x81, 0x90, 0xe0, 0x7f, 0x03, 0x2f, 0x23, 0x2e, 0xcc, 0x00, 0x98, 0x2e, 0x55, 0xb6, 0x98, 0x2e,
0x1d, 0xb5, 0x10, 0x25, 0xfb, 0x6f, 0xe0, 0x6f, 0xe0, 0x5f, 0x80, 0x2e, 0x95, 0xcf, 0x98, 0x2e, 0x95, 0xcf, 0x10,
0x30, 0x21, 0x2e, 0xcc, 0x00, 0xfb, 0x6f, 0xe0, 0x5f, 0xb8, 0x2e, 0x00, 0x51, 0x05, 0x58, 0xeb, 0x7f, 0x2a, 0x25,
0x89, 0x52, 0x6f, 0x5a, 0x89, 0x50, 0x13, 0x41, 0x06, 0x40, 0xb3, 0x01, 0x16, 0x42, 0xcb, 0x16, 0x06, 0x40, 0xf3,
0x02, 0x13, 0x42, 0x65, 0x0e, 0xf5, 0x2f, 0x05, 0x40, 0x14, 0x30, 0x2c, 0x29, 0x04, 0x42, 0x08, 0xa1, 0x00, 0x30,
0x90, 0x2e, 0x52, 0xb6, 0xb3, 0x88, 0xb0, 0x8a, 0xb6, 0x84, 0xa4, 0x7f, 0xc4, 0x7f, 0xb5, 0x7f, 0xd5, 0x7f, 0x92,
0x7f, 0x73, 0x30, 0x04, 0x30, 0x55, 0x40, 0x42, 0x40, 0x8a, 0x17, 0xf3, 0x08, 0x6b, 0x01, 0x90, 0x02, 0x53, 0xb8,
0x4b, 0x82, 0xad, 0xbe, 0x71, 0x7f, 0x45, 0x0a, 0x09, 0x54, 0x84, 0x7f, 0x98, 0x2e, 0xd9, 0xc0, 0xa3, 0x6f, 0x7b,
0x54, 0xd0, 0x42, 0xa3, 0x7f, 0xf2, 0x7f, 0x60, 0x7f, 0x20, 0x25, 0x71, 0x6f, 0x75, 0x5a, 0x77, 0x58, 0x79, 0x5c,
0x75, 0x56, 0x98, 0x2e, 0x67, 0xcc, 0xb1, 0x6f, 0x62, 0x6f, 0x50, 0x42, 0xb1, 0x7f, 0xb3, 0x30, 0x10, 0x25, 0x98,
0x2e, 0x0f, 0xca, 0x84, 0x6f, 0x20, 0x29, 0x71, 0x6f, 0x92, 0x6f, 0xa5, 0x6f, 0x76, 0x82, 0x6a, 0x0e, 0x73, 0x30,
0x00, 0x30, 0xd0, 0x2f, 0xd2, 0x6f, 0xd1, 0x7f, 0xb4, 0x7f, 0x98, 0x2e, 0x2b, 0xb7, 0x15, 0xbd, 0x0b, 0xb8, 0x02,
0x0a, 0xc2, 0x6f, 0xc0, 0x7f, 0x98, 0x2e, 0x2b, 0xb7, 0x15, 0xbd, 0x0b, 0xb8, 0x42, 0x0a, 0xc0, 0x6f, 0x08, 0x17,
0x41, 0x18, 0x89, 0x16, 0xe1, 0x18, 0xd0, 0x18, 0xa1, 0x7f, 0x27, 0x25, 0x16, 0x25, 0x98, 0x2e, 0x79, 0xc0, 0x8b,
0x54, 0x90, 0x7f, 0xb3, 0x30, 0x82, 0x40, 0x80, 0x90, 0x0d, 0x2f, 0x7d, 0x52, 0x92, 0x6f, 0x98, 0x2e, 0x0f, 0xca,
0xb2, 0x6f, 0x90, 0x0e, 0x06, 0x2f, 0x8b, 0x50, 0x14, 0x30, 0x42, 0x6f, 0x51, 0x6f, 0x14, 0x42, 0x12, 0x42, 0x01,
0x42, 0x00, 0x2e, 0x31, 0x6f, 0x98, 0x2e, 0x74, 0xc0, 0x41, 0x6f, 0x80, 0x7f, 0x98, 0x2e, 0x74, 0xc0, 0x82, 0x6f,
0x10, 0x04, 0x43, 0x52, 0x01, 0x0f, 0x05, 0x2e, 0xcb, 0x00, 0x00, 0x30, 0x04, 0x30, 0x21, 0x2f, 0x51, 0x6f, 0x43,
0x58, 0x8c, 0x0e, 0x04, 0x30, 0x1c, 0x2f, 0x85, 0x88, 0x41, 0x6f, 0x04, 0x41, 0x8c, 0x0f, 0x04, 0x30, 0x16, 0x2f,
0x84, 0x88, 0x00, 0x2e, 0x04, 0x41, 0x04, 0x05, 0x8c, 0x0e, 0x04, 0x30, 0x0f, 0x2f, 0x82, 0x88, 0x31, 0x6f, 0x04,
0x41, 0x04, 0x05, 0x8c, 0x0e, 0x04, 0x30, 0x08, 0x2f, 0x83, 0x88, 0x00, 0x2e, 0x04, 0x41, 0x8c, 0x0f, 0x04, 0x30,
0x02, 0x2f, 0x21, 0x2e, 0xad, 0x01, 0x14, 0x30, 0x00, 0x91, 0x14, 0x2f, 0x03, 0x2e, 0xa1, 0x01, 0x41, 0x90, 0x0e,
0x2f, 0x03, 0x2e, 0xad, 0x01, 0x14, 0x30, 0x4c, 0x28, 0x23, 0x2e, 0xad, 0x01, 0x46, 0xa0, 0x06, 0x2f, 0x81, 0x84,
0x8d, 0x52, 0x48, 0x82, 0x82, 0x40, 0x21, 0x2e, 0xa1, 0x01, 0x42, 0x42, 0x5c, 0x2c, 0x02, 0x30, 0x05, 0x2e, 0xaa,
0x01, 0x80, 0xb2, 0x02, 0x30, 0x55, 0x2f, 0x03, 0x2e, 0xa9, 0x01, 0x92, 0x6f, 0xb3, 0x30, 0x98, 0x2e, 0x0f, 0xca,
0xb2, 0x6f, 0x90, 0x0f, 0x00, 0x30, 0x02, 0x30, 0x4a, 0x2f, 0xa2, 0x6f, 0x87, 0x52, 0x91, 0x00, 0x85, 0x52, 0x51,
0x0e, 0x02, 0x2f, 0x00, 0x2e, 0x43, 0x2c, 0x02, 0x30, 0xc2, 0x6f, 0x7f, 0x52, 0x91, 0x0e, 0x02, 0x30, 0x3c, 0x2f,
0x51, 0x6f, 0x81, 0x54, 0x98, 0x2e, 0xfe, 0xc9, 0x10, 0x25, 0xb3, 0x30, 0x21, 0x25, 0x98, 0x2e, 0x0f, 0xca, 0x32,
0x6f, 0xc0, 0x7f, 0xb3, 0x30, 0x12, 0x25, 0x98, 0x2e, 0x0f, 0xca, 0x42, 0x6f, 0xb0, 0x7f, 0xb3, 0x30, 0x12, 0x25,
0x98, 0x2e, 0x0f, 0xca, 0xb2, 0x6f, 0x90, 0x28, 0x83, 0x52, 0x98, 0x2e, 0xfe, 0xc9, 0xc2, 0x6f, 0x90, 0x0f, 0x00,
0x30, 0x02, 0x30, 0x1d, 0x2f, 0x05, 0x2e, 0xa1, 0x01, 0x80, 0xb2, 0x12, 0x30, 0x0f, 0x2f, 0x42, 0x6f, 0x03, 0x2e,
0xab, 0x01, 0x91, 0x0e, 0x02, 0x30, 0x12, 0x2f, 0x52, 0x6f, 0x03, 0x2e, 0xac, 0x01, 0x91, 0x0f, 0x02, 0x30, 0x0c,
0x2f, 0x21, 0x2e, 0xaa, 0x01, 0x0a, 0x2c, 0x12, 0x30, 0x03, 0x2e, 0xcb, 0x00, 0x8d, 0x58, 0x08, 0x89, 0x41, 0x40,
0x11, 0x43, 0x00, 0x43, 0x25, 0x2e, 0xa1, 0x01, 0xd4, 0x6f, 0x8f, 0x52, 0x00, 0x43, 0x3a, 0x89, 0x00, 0x2e, 0x10,
0x43, 0x10, 0x43, 0x61, 0x0e, 0xfb, 0x2f, 0x03, 0x2e, 0xa0, 0x01, 0x11, 0x1a, 0x02, 0x2f, 0x02, 0x25, 0x21, 0x2e,
0xa0, 0x01, 0xeb, 0x6f, 0x00, 0x5f, 0xb8, 0x2e, 0x91, 0x52, 0x10, 0x30, 0x02, 0x30, 0x95, 0x56, 0x52, 0x42, 0x4b,
0x0e, 0xfc, 0x2f, 0x8d, 0x54, 0x88, 0x82, 0x93, 0x56, 0x80, 0x42, 0x53, 0x42, 0x40, 0x42, 0x42, 0x86, 0x83, 0x54,
0xc0, 0x2e, 0xc2, 0x42, 0x00, 0x2e, 0xa3, 0x52, 0x00, 0x51, 0x52, 0x40, 0x47, 0x40, 0x1a, 0x25, 0x01, 0x2e, 0x97,
0x00, 0x8f, 0xbe, 0x72, 0x86, 0xfb, 0x7f, 0x0b, 0x30, 0x7c, 0xbf, 0xa5, 0x50, 0x10, 0x08, 0xdf, 0xba, 0x70, 0x88,
0xf8, 0xbf, 0xcb, 0x42, 0xd3, 0x7f, 0x6c, 0xbb, 0xfc, 0xbb, 0xc5, 0x0a, 0x90, 0x7f, 0x1b, 0x7f, 0x0b, 0x43, 0xc0,
0xb2, 0xe5, 0x7f, 0xb7, 0x7f, 0xa6, 0x7f, 0xc4, 0x7f, 0x90, 0x2e, 0x1c, 0xb7, 0x07, 0x2e, 0xd2, 0x00, 0xc0, 0xb2,
0x0b, 0x2f, 0x97, 0x52, 0x01, 0x2e, 0xcd, 0x00, 0x82, 0x7f, 0x98, 0x2e, 0xbb, 0xcc, 0x0b, 0x30, 0x37, 0x2e, 0xd2,
0x00, 0x82, 0x6f, 0x90, 0x6f, 0x1a, 0x25, 0x00, 0xb2, 0x8b, 0x7f, 0x14, 0x2f, 0xa6, 0xbd, 0x25, 0xbd, 0xb6, 0xb9,
0x2f, 0xb9, 0x80, 0xb2, 0xd4, 0xb0, 0x0c, 0x2f, 0x99, 0x54, 0x9b, 0x56, 0x0b, 0x30, 0x0b, 0x2e, 0xb1, 0x00, 0xa1,
0x58, 0x9b, 0x42, 0xdb, 0x42, 0x6c, 0x09, 0x2b, 0x2e, 0xb1, 0x00, 0x8b, 0x42, 0xcb, 0x42, 0x86, 0x7f, 0x73, 0x84,
0xa7, 0x56, 0xc3, 0x08, 0x39, 0x52, 0x05, 0x50, 0x72, 0x7f, 0x63, 0x7f, 0x98, 0x2e, 0xc2, 0xc0, 0xe1, 0x6f, 0x62,
0x6f, 0xd1, 0x0a, 0x01, 0x2e, 0xcd, 0x00, 0xd5, 0x6f, 0xc4, 0x6f, 0x72, 0x6f, 0x97, 0x52, 0x9d, 0x5c, 0x98, 0x2e,
0x06, 0xcd, 0x23, 0x6f, 0x90, 0x6f, 0x99, 0x52, 0xc0, 0xb2, 0x04, 0xbd, 0x54, 0x40, 0xaf, 0xb9, 0x45, 0x40, 0xe1,
0x7f, 0x02, 0x30, 0x06, 0x2f, 0xc0, 0xb2, 0x02, 0x30, 0x03, 0x2f, 0x9b, 0x5c, 0x12, 0x30, 0x94, 0x43, 0x85, 0x43,
0x03, 0xbf, 0x6f, 0xbb, 0x80, 0xb3, 0x20, 0x2f, 0x06, 0x6f, 0x26, 0x01, 0x16, 0x6f, 0x6e, 0x03, 0x45, 0x42, 0xc0,
0x90, 0x29, 0x2e, 0xce, 0x00, 0x9b, 0x52, 0x14, 0x2f, 0x9b, 0x5c, 0x00, 0x2e, 0x93, 0x41, 0x86, 0x41, 0xe3, 0x04,
0xae, 0x07, 0x80, 0xab, 0x04, 0x2f, 0x80, 0x91, 0x0a, 0x2f, 0x86, 0x6f, 0x73, 0x0f, 0x07, 0x2f, 0x83, 0x6f, 0xc0,
0xb2, 0x04, 0x2f, 0x54, 0x42, 0x45, 0x42, 0x12, 0x30, 0x04, 0x2c, 0x11, 0x30, 0x02, 0x2c, 0x11, 0x30, 0x11, 0x30,
0x02, 0xbc, 0x0f, 0xb8, 0xd2, 0x7f, 0x00, 0xb2, 0x0a, 0x2f, 0x01, 0x2e, 0xfc, 0x00, 0x05, 0x2e, 0xc7, 0x01, 0x10,
0x1a, 0x02, 0x2f, 0x21, 0x2e, 0xc7, 0x01, 0x03, 0x2d, 0x02, 0x2c, 0x01, 0x30, 0x01, 0x30, 0xb0, 0x6f, 0x98, 0x2e,
0x95, 0xcf, 0xd1, 0x6f, 0xa0, 0x6f, 0x98, 0x2e, 0x95, 0xcf, 0xe2, 0x6f, 0x9f, 0x52, 0x01, 0x2e, 0xce, 0x00, 0x82,
0x40, 0x50, 0x42, 0x0c, 0x2c, 0x42, 0x42, 0x11, 0x30, 0x23, 0x2e, 0xd2, 0x00, 0x01, 0x30, 0xb0, 0x6f, 0x98, 0x2e,
0x95, 0xcf, 0xa0, 0x6f, 0x01, 0x30, 0x98, 0x2e, 0x95, 0xcf, 0x00, 0x2e, 0xfb, 0x6f, 0x00, 0x5f, 0xb8, 0x2e, 0x83,
0x86, 0x01, 0x30, 0x00, 0x30, 0x94, 0x40, 0x24, 0x18, 0x06, 0x00, 0x53, 0x0e, 0x4f, 0x02, 0xf9, 0x2f, 0xb8, 0x2e,
0xa9, 0x52, 0x00, 0x2e, 0x60, 0x40, 0x41, 0x40, 0x0d, 0xbc, 0x98, 0xbc, 0xc0, 0x2e, 0x01, 0x0a, 0x0f, 0xb8, 0xab,
0x52, 0x53, 0x3c, 0x52, 0x40, 0x40, 0x40, 0x4b, 0x00, 0x82, 0x16, 0x26, 0xb9, 0x01, 0xb8, 0x41, 0x40, 0x10, 0x08,
0x97, 0xb8, 0x01, 0x08, 0xc0, 0x2e, 0x11, 0x30, 0x01, 0x08, 0x43, 0x86, 0x25, 0x40, 0x04, 0x40, 0xd8, 0xbe, 0x2c,
0x0b, 0x22, 0x11, 0x54, 0x42, 0x03, 0x80, 0x4b, 0x0e, 0xf6, 0x2f, 0xb8, 0x2e, 0x9f, 0x50, 0x10, 0x50, 0xad, 0x52,
0x05, 0x2e, 0xd3, 0x00, 0xfb, 0x7f, 0x00, 0x2e, 0x13, 0x40, 0x93, 0x42, 0x41, 0x0e, 0xfb, 0x2f, 0x98, 0x2e, 0xa5,
0xb7, 0x98, 0x2e, 0x87, 0xcf, 0x01, 0x2e, 0xd9, 0x00, 0x00, 0xb2, 0xfb, 0x6f, 0x0b, 0x2f, 0x01, 0x2e, 0x69, 0xf7,
0xb1, 0x3f, 0x01, 0x08, 0x01, 0x30, 0xf0, 0x5f, 0x23, 0x2e, 0xd9, 0x00, 0x21, 0x2e, 0x69, 0xf7, 0x80, 0x2e, 0x7a,
0xb7, 0xf0, 0x5f, 0xb8, 0x2e, 0x01, 0x2e, 0xc0, 0xf8, 0x03, 0x2e, 0xfc, 0xf5, 0x15, 0x54, 0xaf, 0x56, 0x82, 0x08,
0x0b, 0x2e, 0x69, 0xf7, 0xcb, 0x0a, 0xb1, 0x58, 0x80, 0x90, 0xdd, 0xbe, 0x4c, 0x08, 0x5f, 0xb9, 0x59, 0x22, 0x80,
0x90, 0x07, 0x2f, 0x03, 0x34, 0xc3, 0x08, 0xf2, 0x3a, 0x0a, 0x08, 0x02, 0x35, 0xc0, 0x90, 0x4a, 0x0a, 0x48, 0x22,
0xc0, 0x2e, 0x23, 0x2e, 0xfc, 0xf5, 0x10, 0x50, 0xfb, 0x7f, 0x98, 0x2e, 0x56, 0xc7, 0x98, 0x2e, 0x49, 0xc3, 0x10,
0x30, 0xfb, 0x6f, 0xf0, 0x5f, 0x21, 0x2e, 0xcc, 0x00, 0x21, 0x2e, 0xca, 0x00, 0xb8, 0x2e, 0x03, 0x2e, 0xd3, 0x00,
0x16, 0xb8, 0x02, 0x34, 0x4a, 0x0c, 0x21, 0x2e, 0x2d, 0xf5, 0xc0, 0x2e, 0x23, 0x2e, 0xd3, 0x00, 0x03, 0xbc, 0x21,
0x2e, 0xd5, 0x00, 0x03, 0x2e, 0xd5, 0x00, 0x40, 0xb2, 0x10, 0x30, 0x21, 0x2e, 0x77, 0x00, 0x01, 0x30, 0x05, 0x2f,
0x05, 0x2e, 0xd8, 0x00, 0x80, 0x90, 0x01, 0x2f, 0x23, 0x2e, 0x6f, 0xf5, 0xc0, 0x2e, 0x21, 0x2e, 0xd9, 0x00, 0x11,
0x30, 0x81, 0x08, 0x01, 0x2e, 0x6a, 0xf7, 0x71, 0x3f, 0x23, 0xbd, 0x01, 0x08, 0x02, 0x0a, 0xc0, 0x2e, 0x21, 0x2e,
0x6a, 0xf7, 0x30, 0x25, 0x00, 0x30, 0x21, 0x2e, 0x5a, 0xf5, 0x10, 0x50, 0x21, 0x2e, 0x7b, 0x00, 0x21, 0x2e, 0x7c,
0x00, 0xfb, 0x7f, 0x98, 0x2e, 0xc3, 0xb7, 0x40, 0x30, 0x21, 0x2e, 0xd4, 0x00, 0xfb, 0x6f, 0xf0, 0x5f, 0x03, 0x25,
0x80, 0x2e, 0xaf, 0xb7, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00,
0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e,
0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80,
0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x01, 0x2e, 0x5d, 0xf7, 0x08, 0xbc, 0x80, 0xac, 0x0e, 0xbb, 0x02, 0x2f,
0x00, 0x30, 0x41, 0x04, 0x82, 0x06, 0xc0, 0xa4, 0x00, 0x30, 0x11, 0x2f, 0x40, 0xa9, 0x03, 0x2f, 0x40, 0x91, 0x0d,
0x2f, 0x00, 0xa7, 0x0b, 0x2f, 0x80, 0xb3, 0xb3, 0x58, 0x02, 0x2f, 0x90, 0xa1, 0x26, 0x13, 0x20, 0x23, 0x80, 0x90,
0x10, 0x30, 0x01, 0x2f, 0xcc, 0x0e, 0x00, 0x2f, 0x00, 0x30, 0xb8, 0x2e, 0xb5, 0x50, 0x18, 0x08, 0x08, 0xbc, 0x88,
0xb6, 0x0d, 0x17, 0xc6, 0xbd, 0x56, 0xbc, 0xb7, 0x58, 0xda, 0xba, 0x04, 0x01, 0x1d, 0x0a, 0x10, 0x50, 0x05, 0x30,
0x32, 0x25, 0x45, 0x03, 0xfb, 0x7f, 0xf6, 0x30, 0x21, 0x25, 0x98, 0x2e, 0x37, 0xca, 0x16, 0xb5, 0x9a, 0xbc, 0x06,
0xb8, 0x80, 0xa8, 0x41, 0x0a, 0x0e, 0x2f, 0x80, 0x90, 0x02, 0x2f, 0x2d, 0x50, 0x48, 0x0f, 0x09, 0x2f, 0xbf, 0xa0,
0x04, 0x2f, 0xbf, 0x90, 0x06, 0x2f, 0xb7, 0x54, 0xca, 0x0f, 0x03, 0x2f, 0x00, 0x2e, 0x02, 0x2c, 0xb7, 0x52, 0x2d,
0x52, 0xf2, 0x33, 0x98, 0x2e, 0xd9, 0xc0, 0xfb, 0x6f, 0xf1, 0x37, 0xc0, 0x2e, 0x01, 0x08, 0xf0, 0x5f, 0xbf, 0x56,
0xb9, 0x54, 0xd0, 0x40, 0xc4, 0x40, 0x0b, 0x2e, 0xfd, 0xf3, 0xbf, 0x52, 0x90, 0x42, 0x94, 0x42, 0x95, 0x42, 0x05,
0x30, 0xc1, 0x50, 0x0f, 0x88, 0x06, 0x40, 0x04, 0x41, 0x96, 0x42, 0xc5, 0x42, 0x48, 0xbe, 0x73, 0x30, 0x0d, 0x2e,
0xd8, 0x00, 0x4f, 0xba, 0x84, 0x42, 0x03, 0x42, 0x81, 0xb3, 0x02, 0x2f, 0x2b, 0x2e, 0x6f, 0xf5, 0x06, 0x2d, 0x05,
0x2e, 0x77, 0xf7, 0xbd, 0x56, 0x93, 0x08, 0x25, 0x2e, 0x77, 0xf7, 0xbb, 0x54, 0x25, 0x2e, 0xc2, 0xf5, 0x07, 0x2e,
0xfd, 0xf3, 0x42, 0x30, 0xb4, 0x33, 0xda, 0x0a, 0x4c, 0x00, 0x27, 0x2e, 0xfd, 0xf3, 0x43, 0x40, 0xd4, 0x3f, 0xdc,
0x08, 0x43, 0x42, 0x00, 0x2e, 0x00, 0x2e, 0x43, 0x40, 0x24, 0x30, 0xdc, 0x0a, 0x43, 0x42, 0x04, 0x80, 0x03, 0x2e,
0xfd, 0xf3, 0x4a, 0x0a, 0x23, 0x2e, 0xfd, 0xf3, 0x61, 0x34, 0xc0, 0x2e, 0x01, 0x42, 0x00, 0x2e, 0x60, 0x50, 0x1a,
0x25, 0x7a, 0x86, 0xe0, 0x7f, 0xf3, 0x7f, 0x03, 0x25, 0xc3, 0x52, 0x41, 0x84, 0xdb, 0x7f, 0x33, 0x30, 0x98, 0x2e,
0x16, 0xc2, 0x1a, 0x25, 0x7d, 0x82, 0xf0, 0x6f, 0xe2, 0x6f, 0x32, 0x25, 0x16, 0x40, 0x94, 0x40, 0x26, 0x01, 0x85,
0x40, 0x8e, 0x17, 0xc4, 0x42, 0x6e, 0x03, 0x95, 0x42, 0x41, 0x0e, 0xf4, 0x2f, 0xdb, 0x6f, 0xa0, 0x5f, 0xb8, 0x2e,
0xb0, 0x51, 0xfb, 0x7f, 0x98, 0x2e, 0xe8, 0x0d, 0x5a, 0x25, 0x98, 0x2e, 0x0f, 0x0e, 0xcb, 0x58, 0x32, 0x87, 0xc4,
0x7f, 0x65, 0x89, 0x6b, 0x8d, 0xc5, 0x5a, 0x65, 0x7f, 0xe1, 0x7f, 0x83, 0x7f, 0xa6, 0x7f, 0x74, 0x7f, 0xd0, 0x7f,
0xb6, 0x7f, 0x94, 0x7f, 0x17, 0x30, 0xc7, 0x52, 0xc9, 0x54, 0x51, 0x7f, 0x00, 0x2e, 0x85, 0x6f, 0x42, 0x7f, 0x00,
0x2e, 0x51, 0x41, 0x45, 0x81, 0x42, 0x41, 0x13, 0x40, 0x3b, 0x8a, 0x00, 0x40, 0x4b, 0x04, 0xd0, 0x06, 0xc0, 0xac,
0x85, 0x7f, 0x02, 0x2f, 0x02, 0x30, 0x51, 0x04, 0xd3, 0x06, 0x41, 0x84, 0x05, 0x30, 0x5d, 0x02, 0xc9, 0x16, 0xdf,
0x08, 0xd3, 0x00, 0x8d, 0x02, 0xaf, 0xbc, 0xb1, 0xb9, 0x59, 0x0a, 0x65, 0x6f, 0x11, 0x43, 0xa1, 0xb4, 0x52, 0x41,
0x53, 0x41, 0x01, 0x43, 0x34, 0x7f, 0x65, 0x7f, 0x26, 0x31, 0xe5, 0x6f, 0xd4, 0x6f, 0x98, 0x2e, 0x37, 0xca, 0x32,
0x6f, 0x75, 0x6f, 0x83, 0x40, 0x42, 0x41, 0x23, 0x7f, 0x12, 0x7f, 0xf6, 0x30, 0x40, 0x25, 0x51, 0x25, 0x98, 0x2e,
0x37, 0xca, 0x14, 0x6f, 0x20, 0x05, 0x70, 0x6f, 0x25, 0x6f, 0x69, 0x07, 0xa2, 0x6f, 0x31, 0x6f, 0x0b, 0x30, 0x04,
0x42, 0x9b, 0x42, 0x8b, 0x42, 0x55, 0x42, 0x32, 0x7f, 0x40, 0xa9, 0xc3, 0x6f, 0x71, 0x7f, 0x02, 0x30, 0xd0, 0x40,
0xc3, 0x7f, 0x03, 0x2f, 0x40, 0x91, 0x15, 0x2f, 0x00, 0xa7, 0x13, 0x2f, 0x00, 0xa4, 0x11, 0x2f, 0x84, 0xbd, 0x98,
0x2e, 0x79, 0xca, 0x55, 0x6f, 0xb7, 0x54, 0x54, 0x41, 0x82, 0x00, 0xf3, 0x3f, 0x45, 0x41, 0xcb, 0x02, 0xf6, 0x30,
0x98, 0x2e, 0x37, 0xca, 0x35, 0x6f, 0xa4, 0x6f, 0x41, 0x43, 0x03, 0x2c, 0x00, 0x43, 0xa4, 0x6f, 0x35, 0x6f, 0x17,
0x30, 0x42, 0x6f, 0x51, 0x6f, 0x93, 0x40, 0x42, 0x82, 0x00, 0x41, 0xc3, 0x00, 0x03, 0x43, 0x51, 0x7f, 0x00, 0x2e,
0x94, 0x40, 0x41, 0x41, 0x4c, 0x02, 0xc4, 0x6f, 0xd1, 0x56, 0x63, 0x0e, 0x74, 0x6f, 0x51, 0x43, 0xa5, 0x7f, 0x8a,
0x2f, 0x09, 0x2e, 0xd8, 0x00, 0x01, 0xb3, 0x21, 0x2f, 0xcb, 0x58, 0x90, 0x6f, 0x13, 0x41, 0xb6, 0x6f, 0xe4, 0x7f,
0x00, 0x2e, 0x91, 0x41, 0x14, 0x40, 0x92, 0x41, 0x15, 0x40, 0x17, 0x2e, 0x6f, 0xf5, 0xb6, 0x7f, 0xd0, 0x7f, 0xcb,
0x7f, 0x98, 0x2e, 0x00, 0x0c, 0x07, 0x15, 0xc2, 0x6f, 0x14, 0x0b, 0x29, 0x2e, 0x6f, 0xf5, 0xc3, 0xa3, 0xc1, 0x8f,
0xe4, 0x6f, 0xd0, 0x6f, 0xe6, 0x2f, 0x14, 0x30, 0x05, 0x2e, 0x6f, 0xf5, 0x14, 0x0b, 0x29, 0x2e, 0x6f, 0xf5, 0x18,
0x2d, 0xcd, 0x56, 0x04, 0x32, 0xb5, 0x6f, 0x1c, 0x01, 0x51, 0x41, 0x52, 0x41, 0xc3, 0x40, 0xb5, 0x7f, 0xe4, 0x7f,
0x98, 0x2e, 0x1f, 0x0c, 0xe4, 0x6f, 0x21, 0x87, 0x00, 0x43, 0x04, 0x32, 0xcf, 0x54, 0x5a, 0x0e, 0xef, 0x2f, 0x15,
0x54, 0x09, 0x2e, 0x77, 0xf7, 0x22, 0x0b, 0x29, 0x2e, 0x77, 0xf7, 0xfb, 0x6f, 0x50, 0x5e, 0xb8, 0x2e, 0x10, 0x50,
0x01, 0x2e, 0xd4, 0x00, 0x00, 0xb2, 0xfb, 0x7f, 0x51, 0x2f, 0x01, 0xb2, 0x48, 0x2f, 0x02, 0xb2, 0x42, 0x2f, 0x03,
0x90, 0x56, 0x2f, 0xd7, 0x52, 0x79, 0x80, 0x42, 0x40, 0x81, 0x84, 0x00, 0x40, 0x42, 0x42, 0x98, 0x2e, 0x93, 0x0c,
0xd9, 0x54, 0xd7, 0x50, 0xa1, 0x40, 0x98, 0xbd, 0x82, 0x40, 0x3e, 0x82, 0xda, 0x0a, 0x44, 0x40, 0x8b, 0x16, 0xe3,
0x00, 0x53, 0x42, 0x00, 0x2e, 0x43, 0x40, 0x9a, 0x02, 0x52, 0x42, 0x00, 0x2e, 0x41, 0x40, 0x15, 0x54, 0x4a, 0x0e,
0x3a, 0x2f, 0x3a, 0x82, 0x00, 0x30, 0x41, 0x40, 0x21, 0x2e, 0x85, 0x0f, 0x40, 0xb2, 0x0a, 0x2f, 0x98, 0x2e, 0xb1,
0x0c, 0x98, 0x2e, 0x45, 0x0e, 0x98, 0x2e, 0x5b, 0x0e, 0xfb, 0x6f, 0xf0, 0x5f, 0x00, 0x30, 0x80, 0x2e, 0xce, 0xb7,
0xdd, 0x52, 0xd3, 0x54, 0x42, 0x42, 0x4f, 0x84, 0x73, 0x30, 0xdb, 0x52, 0x83, 0x42, 0x1b, 0x30, 0x6b, 0x42, 0x23,
0x30, 0x27, 0x2e, 0xd7, 0x00, 0x37, 0x2e, 0xd4, 0x00, 0x21, 0x2e, 0xd6, 0x00, 0x7a, 0x84, 0x17, 0x2c, 0x42, 0x42,
0x30, 0x30, 0x21, 0x2e, 0xd4, 0x00, 0x12, 0x2d, 0x21, 0x30, 0x00, 0x30, 0x23, 0x2e, 0xd4, 0x00, 0x21, 0x2e, 0x7b,
0xf7, 0x0b, 0x2d, 0x17, 0x30, 0x98, 0x2e, 0x51, 0x0c, 0xd5, 0x50, 0x0c, 0x82, 0x72, 0x30, 0x2f, 0x2e, 0xd4, 0x00,
0x25, 0x2e, 0x7b, 0xf7, 0x40, 0x42, 0x00, 0x2e, 0xfb, 0x6f, 0xf0, 0x5f, 0xb8, 0x2e, 0x70, 0x50, 0x0a, 0x25, 0x39,
0x86, 0xfb, 0x7f, 0xe1, 0x32, 0x62, 0x30, 0x98, 0x2e, 0xc2, 0xc4, 0xb5, 0x56, 0xa5, 0x6f, 0xab, 0x08, 0x91, 0x6f,
0x4b, 0x08, 0xdf, 0x56, 0xc4, 0x6f, 0x23, 0x09, 0x4d, 0xba, 0x93, 0xbc, 0x8c, 0x0b, 0xd1, 0x6f, 0x0b, 0x09, 0xcb,
0x52, 0xe1, 0x5e, 0x56, 0x42, 0xaf, 0x09, 0x4d, 0xba, 0x23, 0xbd, 0x94, 0x0a, 0xe5, 0x6f, 0x68, 0xbb, 0xeb, 0x08,
0xbd, 0xb9, 0x63, 0xbe, 0xfb, 0x6f, 0x52, 0x42, 0xe3, 0x0a, 0xc0, 0x2e, 0x43, 0x42, 0x90, 0x5f, 0xd1, 0x50, 0x03,
0x2e, 0x25, 0xf3, 0x13, 0x40, 0x00, 0x40, 0x9b, 0xbc, 0x9b, 0xb4, 0x08, 0xbd, 0xb8, 0xb9, 0x98, 0xbc, 0xda, 0x0a,
0x08, 0xb6, 0x89, 0x16, 0xc0, 0x2e, 0x19, 0x00, 0x62, 0x02, 0x10, 0x50, 0xfb, 0x7f, 0x98, 0x2e, 0x81, 0x0d, 0x01,
0x2e, 0xd4, 0x00, 0x31, 0x30, 0x08, 0x04, 0xfb, 0x6f, 0x01, 0x30, 0xf0, 0x5f, 0x23, 0x2e, 0xd6, 0x00, 0x21, 0x2e,
0xd7, 0x00, 0xb8, 0x2e, 0x01, 0x2e, 0xd7, 0x00, 0x03, 0x2e, 0xd6, 0x00, 0x48, 0x0e, 0x01, 0x2f, 0x80, 0x2e, 0x1f,
0x0e, 0xb8, 0x2e, 0xe3, 0x50, 0x21, 0x34, 0x01, 0x42, 0x82, 0x30, 0xc1, 0x32, 0x25, 0x2e, 0x62, 0xf5, 0x01, 0x00,
0x22, 0x30, 0x01, 0x40, 0x4a, 0x0a, 0x01, 0x42, 0xb8, 0x2e, 0xe3, 0x54, 0xf0, 0x3b, 0x83, 0x40, 0xd8, 0x08, 0xe5,
0x52, 0x83, 0x42, 0x00, 0x30, 0x83, 0x30, 0x50, 0x42, 0xc4, 0x32, 0x27, 0x2e, 0x64, 0xf5, 0x94, 0x00, 0x50, 0x42,
0x40, 0x42, 0xd3, 0x3f, 0x84, 0x40, 0x7d, 0x82, 0xe3, 0x08, 0x40, 0x42, 0x83, 0x42, 0xb8, 0x2e, 0xdd, 0x52, 0x00,
0x30, 0x40, 0x42, 0x7c, 0x86, 0xb9, 0x52, 0x09, 0x2e, 0x70, 0x0f, 0xbf, 0x54, 0xc4, 0x42, 0xd3, 0x86, 0x54, 0x40,
0x55, 0x40, 0x94, 0x42, 0x85, 0x42, 0x21, 0x2e, 0xd7, 0x00, 0x42, 0x40, 0x25, 0x2e, 0xfd, 0xf3, 0xc0, 0x42, 0x7e,
0x82, 0x05, 0x2e, 0x7d, 0x00, 0x80, 0xb2, 0x14, 0x2f, 0x05, 0x2e, 0x89, 0x00, 0x27, 0xbd, 0x2f, 0xb9, 0x80, 0x90,
0x02, 0x2f, 0x21, 0x2e, 0x6f, 0xf5, 0x0c, 0x2d, 0x07, 0x2e, 0x71, 0x0f, 0x14, 0x30, 0x1c, 0x09, 0x05, 0x2e, 0x77,
0xf7, 0xbd, 0x56, 0x47, 0xbe, 0x93, 0x08, 0x94, 0x0a, 0x25, 0x2e, 0x77, 0xf7, 0xe7, 0x54, 0x50, 0x42, 0x4a, 0x0e,
0xfc, 0x2f, 0xb8, 0x2e, 0x50, 0x50, 0x02, 0x30, 0x43, 0x86, 0xe5, 0x50, 0xfb, 0x7f, 0xe3, 0x7f, 0xd2, 0x7f, 0xc0,
0x7f, 0xb1, 0x7f, 0x00, 0x2e, 0x41, 0x40, 0x00, 0x40, 0x48, 0x04, 0x98, 0x2e, 0x74, 0xc0, 0x1e, 0xaa, 0xd3, 0x6f,
0x14, 0x30, 0xb1, 0x6f, 0xe3, 0x22, 0xc0, 0x6f, 0x52, 0x40, 0xe4, 0x6f, 0x4c, 0x0e, 0x12, 0x42, 0xd3, 0x7f, 0xeb,
0x2f, 0x03, 0x2e, 0x86, 0x0f, 0x40, 0x90, 0x11, 0x30, 0x03, 0x2f, 0x23, 0x2e, 0x86, 0x0f, 0x02, 0x2c, 0x00, 0x30,
0xd0, 0x6f, 0xfb, 0x6f, 0xb0, 0x5f, 0xb8, 0x2e, 0x40, 0x50, 0xf1, 0x7f, 0x0a, 0x25, 0x3c, 0x86, 0xeb, 0x7f, 0x41,
0x33, 0x22, 0x30, 0x98, 0x2e, 0xc2, 0xc4, 0xd3, 0x6f, 0xf4, 0x30, 0xdc, 0x09, 0x47, 0x58, 0xc2, 0x6f, 0x94, 0x09,
0xeb, 0x58, 0x6a, 0xbb, 0xdc, 0x08, 0xb4, 0xb9, 0xb1, 0xbd, 0xe9, 0x5a, 0x95, 0x08, 0x21, 0xbd, 0xf6, 0xbf, 0x77,
0x0b, 0x51, 0xbe, 0xf1, 0x6f, 0xeb, 0x6f, 0x52, 0x42, 0x54, 0x42, 0xc0, 0x2e, 0x43, 0x42, 0xc0, 0x5f, 0x50, 0x50,
0xf5, 0x50, 0x31, 0x30, 0x11, 0x42, 0xfb, 0x7f, 0x7b, 0x30, 0x0b, 0x42, 0x11, 0x30, 0x02, 0x80, 0x23, 0x33, 0x01,
0x42, 0x03, 0x00, 0x07, 0x2e, 0x80, 0x03, 0x05, 0x2e, 0xd3, 0x00, 0x23, 0x52, 0xe2, 0x7f, 0xd3, 0x7f, 0xc0, 0x7f,
0x98, 0x2e, 0xb6, 0x0e, 0xd1, 0x6f, 0x08, 0x0a, 0x1a, 0x25, 0x7b, 0x86, 0xd0, 0x7f, 0x01, 0x33, 0x12, 0x30, 0x98,
0x2e, 0xc2, 0xc4, 0xd1, 0x6f, 0x08, 0x0a, 0x00, 0xb2, 0x0d, 0x2f, 0xe3, 0x6f, 0x01, 0x2e, 0x80, 0x03, 0x51, 0x30,
0xc7, 0x86, 0x23, 0x2e, 0x21, 0xf2, 0x08, 0xbc, 0xc0, 0x42, 0x98, 0x2e, 0xa5, 0xb7, 0x00, 0x2e, 0x00, 0x2e, 0xd0,
0x2e, 0xb0, 0x6f, 0x0b, 0xb8, 0x03, 0x2e, 0x1b, 0x00, 0x08, 0x1a, 0xb0, 0x7f, 0x70, 0x30, 0x04, 0x2f, 0x21, 0x2e,
0x21, 0xf2, 0x00, 0x2e, 0x00, 0x2e, 0xd0, 0x2e, 0x98, 0x2e, 0x6d, 0xc0, 0x98, 0x2e, 0x5d, 0xc0, 0xed, 0x50, 0x98,
0x2e, 0x44, 0xcb, 0xef, 0x50, 0x98, 0x2e, 0x46, 0xc3, 0xf1, 0x50, 0x98, 0x2e, 0x53, 0xc7, 0x35, 0x50, 0x98, 0x2e,
0x64, 0xcf, 0x10, 0x30, 0x98, 0x2e, 0xdc, 0x03, 0x20, 0x26, 0xc0, 0x6f, 0x02, 0x31, 0x12, 0x42, 0xab, 0x33, 0x0b,
0x42, 0x37, 0x80, 0x01, 0x30, 0x01, 0x42, 0xf3, 0x37, 0xf7, 0x52, 0xfb, 0x50, 0x44, 0x40, 0xa2, 0x0a, 0x42, 0x42,
0x8b, 0x31, 0x09, 0x2e, 0x5e, 0xf7, 0xf9, 0x54, 0xe3, 0x08, 0x83, 0x42, 0x1b, 0x42, 0x23, 0x33, 0x4b, 0x00, 0xbc,
0x84, 0x0b, 0x40, 0x33, 0x30, 0x83, 0x42, 0x0b, 0x42, 0xe0, 0x7f, 0xd1, 0x7f, 0x98, 0x2e, 0x58, 0xb7, 0xd1, 0x6f,
0x80, 0x30, 0x40, 0x42, 0x03, 0x30, 0xe0, 0x6f, 0xf3, 0x54, 0x04, 0x30, 0x00, 0x2e, 0x00, 0x2e, 0x01, 0x89, 0x62,
0x0e, 0xfa, 0x2f, 0x43, 0x42, 0x11, 0x30, 0xfb, 0x6f, 0xc0, 0x2e, 0x01, 0x42, 0xb0, 0x5f, 0xc1, 0x4a, 0x00, 0x00,
0x6d, 0x57, 0x00, 0x00, 0x77, 0x8e, 0x00, 0x00, 0xe0, 0xff, 0xff, 0xff, 0xd3, 0xff, 0xff, 0xff, 0xe5, 0xff, 0xff,
0xff, 0xee, 0xe1, 0xff, 0xff, 0x7c, 0x13, 0x00, 0x00, 0x46, 0xe6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x2e, 0x00, 0xc1, 0x80,
0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1,
0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00,
0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e,
0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80,
0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1,
0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00,
0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e,
0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80,
0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1,
0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00,
0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e,
0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80,
0x2e, 0x00, 0xc1
};

View File

@ -0,0 +1,177 @@
// SPDX-License-Identifier: Apache-2.0
#include <drivers/bmi270.h>
#include <bmi270_module.h>
#include <drivers/bmi270_config_data.h>
#include <tactility/device.h>
#include <tactility/drivers/i2c_controller.h>
#include <tactility/log.h>
#define TAG "BMI270"
static constexpr uint8_t REG_CHIP_ID = 0x00; // read: expect 0x24
static constexpr uint8_t REG_DATA_ACC = 0x0C; // 6 bytes: acc X/Y/Z LSB/MSB
static constexpr uint8_t REG_DATA_GYR = 0x12; // 6 bytes: gyr X/Y/Z LSB/MSB
static constexpr uint8_t REG_INTERNAL_ST = 0x21; // bit0: init done
static constexpr uint8_t REG_ACC_CONF = 0x40; // ODR + BWP + filter_perf
static constexpr uint8_t REG_ACC_RANGE = 0x41; // range selector
static constexpr uint8_t REG_GYR_CONF = 0x42; // ODR + BWP
static constexpr uint8_t REG_GYR_RANGE = 0x43; // range selector
static constexpr uint8_t REG_INIT_CTRL = 0x59; // 0=start upload, 1=done
static constexpr uint8_t REG_INIT_ADDR_0 = 0x5B; // burst address low nibble
// REG_INIT_ADDR_1 = 0x5C written together with 0x5B (2-byte burst)
static constexpr uint8_t REG_INIT_DATA = 0x5E; // config burst write target
static constexpr uint8_t REG_PWR_CONF = 0x7C; // 0=disable adv. power save
static constexpr uint8_t REG_PWR_CTRL = 0x7D; // bit1=gyr_en, bit2=acc_en
static constexpr uint8_t REG_CMD = 0x7E; // 0xB6 = soft reset
// ACC_CONF: filter_perf=1, bwp=normal(2), odr=100Hz(8) → 0xA8
static constexpr uint8_t ACC_CONF_VAL = 0xA8;
static constexpr uint8_t ACC_RANGE_VAL = 0x02; // ±8g
// GYR_CONF: filter_perf=1, noise_perf=1, bwp=normal(2), odr=100Hz(8) → 0xE8
static constexpr uint8_t GYR_CONF_VAL = 0xE8;
static constexpr uint8_t GYR_RANGE_VAL = 0x00; // ±2000°/s
// Scaling: full-scale / 2^15
static constexpr float ACCEL_SCALE = 8.0f / 32768.0f; // g per LSB (±8g)
static constexpr float GYRO_SCALE = 2000.0f / 32768.0f; // °/s per LSB (±2000°/s)
// Config upload chunk size (bytes of config data per I2C transaction)
static constexpr size_t CHUNK_SIZE = 64;
static constexpr TickType_t I2C_TIMEOUT_TICKS = pdMS_TO_TICKS(10);
#define GET_CONFIG(device) (static_cast<const Bmi270Config*>((device)->config))
// region Helpers
static bool configure(Device* i2c_controller, uint8_t address) {
// Disable advanced power save before uploading
if (i2c_controller_register8_set(i2c_controller, address, REG_PWR_CONF, 0x00, I2C_TIMEOUT_TICKS) != ERROR_NONE) return false;
vTaskDelay(pdMS_TO_TICKS(1));
// Signal start of config upload
if (i2c_controller_register8_set(i2c_controller, address, REG_INIT_CTRL, 0x00, I2C_TIMEOUT_TICKS) != ERROR_NONE) return false;
// Upload config in CHUNK_SIZE-byte bursts
// The half-word address (hwAddr) increments by CHUNK_SIZE/2 per chunk
constexpr size_t config_data_size = sizeof(bmi270_config_data);
for (size_t offset = 0; offset < config_data_size; offset += CHUNK_SIZE) {
// Set INIT_ADDR_0 and INIT_ADDR_1 (consecutive registers 0x5B/0x5C)
auto hardware_address = static_cast<uint16_t>(offset / 2);
uint8_t address_buffer[2] = {
static_cast<uint8_t>(hardware_address & 0x0Fu), // INIT_ADDR_0: bits [3:0]
static_cast<uint8_t>((hardware_address >> 4) & 0xFFu) // INIT_ADDR_1: bits [11:4]
};
if (i2c_controller_write_register(i2c_controller, address, REG_INIT_ADDR_0, address_buffer, 2, I2C_TIMEOUT_TICKS) != ERROR_NONE) return false;
// Write chunk to INIT_DATA register.
// Copy to a stack buffer first: the config array may live in DROM (SPI flash)
// which is not DMA-accessible; the I2C driver requires DRAM-backed buffers.
size_t chunk_length = (offset + CHUNK_SIZE <= config_data_size) ? CHUNK_SIZE : (config_data_size - offset);
uint8_t chunk_buffer[CHUNK_SIZE];
memcpy(chunk_buffer, bmi270_config_data + offset, chunk_length);
if (i2c_controller_write_register(i2c_controller, address, REG_INIT_DATA, chunk_buffer, static_cast<uint16_t>(chunk_length), I2C_TIMEOUT_TICKS) != ERROR_NONE) return false;
}
// Signal end of config upload
if (i2c_controller_register8_set(i2c_controller, address, REG_INIT_CTRL, 0x01, I2C_TIMEOUT_TICKS) != ERROR_NONE) return false;
vTaskDelay(pdMS_TO_TICKS(20));
// Verify initialization
uint8_t status = 0;
if (i2c_controller_register8_get(i2c_controller, address, REG_INTERNAL_ST, &status, I2C_TIMEOUT_TICKS) != ERROR_NONE) return false;
return (status & 0x01u) != 0; // bit 0 = init_ok
}
// endregion
// region Driver lifecycle
static error_t start(Device* device) {
auto* i2c_controller = device_get_parent(device);
if (device_get_type(i2c_controller) != &I2C_CONTROLLER_TYPE) {
LOG_E(TAG, "Parent is not an I2C controller");
return ERROR_RESOURCE;
}
auto address = GET_CONFIG(device)->address;
// Verify chip ID
uint8_t chip_id= 0;
if (i2c_controller_register8_get(i2c_controller, address, REG_CHIP_ID, &chip_id, I2C_TIMEOUT_TICKS) != ERROR_NONE || chip_id != 0x24) {
return ERROR_RESOURCE;
}
// Soft reset — clears all registers; datasheet specifies 2 ms startup time.
// Use 20 ms to be safe and allow the chip to fully re-initialise before
// any further I2C traffic (a second chip-ID read immediately after reset
// is unreliable and not part of the Bosch SensorAPI init flow).
if (i2c_controller_register8_set(i2c_controller, address, REG_CMD, 0xB6, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE;
vTaskDelay(pdMS_TO_TICKS(20));
// Upload 8KB configuration (enables internal feature engine)
if (!configure(i2c_controller, address)) {
return ERROR_RESOURCE;
}
// Configure accelerometer: ODR=100Hz, normal filter, ±8g
if (i2c_controller_register8_set(i2c_controller, address, REG_ACC_CONF, ACC_CONF_VAL, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE;
if (i2c_controller_register8_set(i2c_controller, address, REG_ACC_RANGE, ACC_RANGE_VAL, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE;
// Configure gyroscope: ODR=100Hz, normal filter, ±2000°/s
if (i2c_controller_register8_set(i2c_controller, address, REG_GYR_CONF, GYR_CONF_VAL, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE;
if (i2c_controller_register8_set(i2c_controller, address, REG_GYR_RANGE, GYR_RANGE_VAL, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE;
// Enable accelerometer and gyroscope (bit1=gyr_en, bit2=acc_en)
if (i2c_controller_register8_set(i2c_controller, address, REG_PWR_CTRL, 0x06, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE;
vTaskDelay(pdMS_TO_TICKS(2));
return ERROR_NONE;
}
static error_t stop(Device* device) {
return ERROR_NONE;
}
// endregion
extern "C" {
error_t bmi270_read(Device* device, Bmi270Data* data) {
auto* i2c_controller = device_get_parent(device);
auto address = GET_CONFIG(device)->address;
// Burst-read 12 bytes: acc X/Y/Z (6 bytes) + gyro X/Y/Z (6 bytes)
// Registers: 0x0C0x17 are contiguous for acc+gyro in I2C mode (no dummy byte)
uint8_t buffer[12] = {};
error_t error = i2c_controller_read_register(i2c_controller, address, REG_DATA_ACC, buffer, sizeof(buffer), I2C_TIMEOUT_TICKS);
if (error != ERROR_NONE) return error;
auto toI16 = [](uint8_t lo, uint8_t hi) -> int16_t {
return static_cast<int16_t>(static_cast<uint16_t>(hi) << 8 | lo);
};
data->ax = toI16(buffer[0], buffer[1]) * ACCEL_SCALE;
data->ay = toI16(buffer[2], buffer[3]) * ACCEL_SCALE;
data->az = toI16(buffer[4], buffer[5]) * ACCEL_SCALE;
data->gx = toI16(buffer[6], buffer[7]) * GYRO_SCALE;
data->gy = toI16(buffer[8], buffer[9]) * GYRO_SCALE;
data->gz = toI16(buffer[10], buffer[11]) * GYRO_SCALE;
return ERROR_NONE;
}
Driver bmi270_driver = {
.name = "bmi270",
.compatible = (const char*[]) { "bosch,bmi270", nullptr},
.start_device = start,
.stop_device = stop,
.api = nullptr,
.device_type = nullptr,
.owner = &bmi270_module,
.internal = nullptr
};
}

View File

@ -0,0 +1,34 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h>
#include <tactility/module.h>
extern "C" {
extern Driver bmi270_driver;
static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */
check(driver_construct_add(&bmi270_driver) == ERROR_NONE);
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&bmi270_driver) == ERROR_NONE);
return ERROR_NONE;
}
extern const ModuleSymbol bmi270_module_symbols[];
Module bmi270_module = {
.name = "bmi270",
.start = start,
.stop = stop,
.symbols = bmi270_module_symbols,
.internal = nullptr
};
}

View File

@ -0,0 +1,8 @@
// SPDX-License-Identifier: Apache-2.0
#include <drivers/bmi270.h>
#include <tactility/module.h>
const struct ModuleSymbol bmi270_module_symbols[] = {
DEFINE_MODULE_SYMBOL(bmi270_read),
MODULE_SYMBOL_TERMINATOR
};

View File

@ -2,40 +2,16 @@
#pragma once #pragma once
#include <stdint.h> #include <stdint.h>
#include <tactility/error.h>
#include <tactility/freertos/freertos.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
struct Device;
struct Pi4ioe5v6408Config { struct Pi4ioe5v6408Config {
/** Address on bus */ /** Address on bus */
uint8_t address; uint8_t address;
}; };
error_t pi4ioe5v6408_set_direction(struct Device* device, uint8_t bits, TickType_t timeout);
error_t pi4ioe5v6408_set_output_level(struct Device* device, uint8_t bits, TickType_t timeout);
error_t pi4ioe5v6408_get_output_level(struct Device* device, uint8_t* bits, TickType_t timeout);
error_t pi4ioe5v6408_set_output_high_impedance(struct Device* device, uint8_t bits, TickType_t timeout);
error_t pi4ioe5v6408_set_input_default_level(struct Device* device, uint8_t bits, TickType_t timeout);
error_t pi4ioe5v6408_set_pull_enable(struct Device* device, uint8_t bits, TickType_t timeout);
error_t pi4ioe5v6408_set_pull_select(struct Device* device, uint8_t bits, TickType_t timeout);
error_t pi4ioe5v6408_get_input_level(struct Device* device, uint8_t* bits, TickType_t timeout);
error_t pi4ioe5v6408_set_interrupt_mask(struct Device* device, uint8_t bits, TickType_t timeout);
error_t pi4ioe5v6408_get_interrupt_level(struct Device* device, uint8_t* bits, TickType_t timeout);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -6,7 +6,6 @@
extern "C" { extern "C" {
extern Driver pi4ioe5v6408_driver; extern Driver pi4ioe5v6408_driver;
extern const ModuleSymbol pi4ioe5v6408_module_symbols[];
static error_t start() { static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct, /* We crash when construct fails, because if a single driver fails to construct,
@ -26,7 +25,7 @@ Module pi4ioe5v6408_module = {
.name = "pi4ioe5v6408", .name = "pi4ioe5v6408",
.start = start, .start = start,
.stop = stop, .stop = stop,
.symbols = pi4ioe5v6408_module_symbols, .symbols = nullptr,
.internal = nullptr .internal = nullptr
}; };

View File

@ -3,6 +3,8 @@
#include <pi4ioe5v6408_module.h> #include <pi4ioe5v6408_module.h>
#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_descriptor.h>
#include <tactility/drivers/i2c_controller.h> #include <tactility/drivers/i2c_controller.h>
#include <tactility/log.h> #include <tactility/log.h>
@ -27,72 +29,181 @@ static error_t start(Device* device) {
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
LOG_I(TAG, "Started PI4IOE5V6408 device %s", device->name); LOG_I(TAG, "Started PI4IOE5V6408 device %s", device->name);
return ERROR_NONE;
return gpio_controller_init_descriptors(device, 8, nullptr);
} }
static error_t stop(Device* device) { static error_t stop(Device* device) {
check(gpio_controller_deinit_descriptors(device) == ERROR_NONE);
return ERROR_NONE; return ERROR_NONE;
} }
extern "C" { extern "C" {
error_t pi4ioe5v6408_set_direction(Device* device, uint8_t bits, TickType_t timeout) {
static error_t set_level(GpioDescriptor* descriptor, bool high) {
auto* device = descriptor->controller;
auto* parent = device_get_parent(device); auto* parent = device_get_parent(device);
return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_DIRECTION, bits, timeout); auto address = GET_CONFIG(device)->address;
uint8_t bit = 1 << descriptor->pin;
if (high) {
return i2c_controller_register8_set_bits(parent, address, PI4_REGISTER_OUTPUT_LEVEL, bit, portMAX_DELAY);
} else {
return i2c_controller_register8_reset_bits(parent, address, PI4_REGISTER_OUTPUT_LEVEL, bit, portMAX_DELAY);
}
} }
error_t pi4ioe5v6408_set_output_level(Device* device, uint8_t bits, TickType_t timeout) { static error_t get_level(GpioDescriptor* descriptor, bool* high) {
auto* device = descriptor->controller;
auto* parent = device_get_parent(device); auto* parent = device_get_parent(device);
return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_OUTPUT_LEVEL, bits, timeout); auto address = GET_CONFIG(device)->address;
uint8_t bits;
error_t err = i2c_controller_register8_get(parent, address, PI4_REGISTER_INPUT_LEVEL, &bits, portMAX_DELAY);
if (err != ERROR_NONE) {
return err;
}
*high = (bits & (1 << descriptor->pin)) != 0;
return ERROR_NONE;
} }
error_t pi4ioe5v6408_get_output_level(Device* device, uint8_t* bits, TickType_t timeout) { static error_t set_flags(GpioDescriptor* descriptor, gpio_flags_t flags) {
auto* device = descriptor->controller;
auto* parent = device_get_parent(device); auto* parent = device_get_parent(device);
return i2c_controller_register8_get(parent, GET_CONFIG(device)->address, PI4_REGISTER_OUTPUT_LEVEL, bits, timeout); auto address = GET_CONFIG(device)->address;
uint8_t bit = 1 << descriptor->pin;
error_t err;
// Direction
if (flags & GPIO_FLAG_DIRECTION_OUTPUT) {
err = i2c_controller_register8_set_bits(parent, address, PI4_REGISTER_DIRECTION, bit, portMAX_DELAY);
} else {
err = i2c_controller_register8_reset_bits(parent, address, PI4_REGISTER_DIRECTION, bit, portMAX_DELAY);
}
if (err != ERROR_NONE) {
return err;
}
// High Impedance
if (flags & GPIO_FLAG_HIGH_IMPEDANCE) {
err = i2c_controller_register8_set_bits(parent, address, PI4_REGISTER_OUTPUT_HIGH_IMPEDANCE, bit, portMAX_DELAY);
} else {
err = i2c_controller_register8_reset_bits(parent, address, PI4_REGISTER_OUTPUT_HIGH_IMPEDANCE, bit, portMAX_DELAY);
}
if (err != ERROR_NONE) {
return err;
}
// Pull-up/down
if (flags & (GPIO_FLAG_PULL_UP | GPIO_FLAG_PULL_DOWN)) {
// Set pull up or pull down
if (flags & GPIO_FLAG_PULL_UP) {
err = i2c_controller_register8_set_bits(parent, address, PI4_REGISTER_PULL_SELECT, bit, portMAX_DELAY);
} else {
err = i2c_controller_register8_reset_bits(parent, address, PI4_REGISTER_PULL_SELECT, bit, portMAX_DELAY);
}
if (err != ERROR_NONE) {
return err;
}
// Enable pull-up/down
err = i2c_controller_register8_set_bits(parent, address, PI4_REGISTER_PULL_ENABLE, bit, portMAX_DELAY);
} else {
// Disable pull-up/down
err = i2c_controller_register8_reset_bits(parent, address, PI4_REGISTER_PULL_ENABLE, bit, portMAX_DELAY);
}
return err;
} }
error_t pi4ioe5v6408_set_output_high_impedance(struct Device* device, uint8_t bits, TickType_t timeout) { static error_t get_flags(GpioDescriptor* descriptor, gpio_flags_t* flags) {
auto* device = descriptor->controller;
auto* parent = device_get_parent(device); auto* parent = device_get_parent(device);
return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_OUTPUT_HIGH_IMPEDANCE, bits, timeout); auto address = GET_CONFIG(device)->address;
uint8_t bit = 1 << descriptor->pin;
uint8_t val;
error_t err;
gpio_flags_t f = GPIO_FLAG_NONE;
// Direction
err = i2c_controller_register8_get(parent, address, PI4_REGISTER_DIRECTION, &val, portMAX_DELAY);
if (err != ERROR_NONE) return err;
if (val & bit) {
f |= GPIO_FLAG_DIRECTION_OUTPUT;
} else {
f |= GPIO_FLAG_DIRECTION_INPUT;
}
// Pull-up/down
err = i2c_controller_register8_get(parent, address, PI4_REGISTER_PULL_ENABLE, &val, portMAX_DELAY);
if (err != ERROR_NONE) return err;
if (val & bit) {
err = i2c_controller_register8_get(parent, address, PI4_REGISTER_PULL_SELECT, &val, portMAX_DELAY);
if (err != ERROR_NONE) return err;
if (val & bit) {
f |= GPIO_FLAG_PULL_UP;
} else {
f |= GPIO_FLAG_PULL_DOWN;
}
}
// High Impedance
err = i2c_controller_register8_get(parent, address, PI4_REGISTER_OUTPUT_HIGH_IMPEDANCE, &val, portMAX_DELAY);
if (err != ERROR_NONE) return err;
if (val & bit) {
f |= GPIO_FLAG_HIGH_IMPEDANCE;
}
*flags = f;
return ERROR_NONE;
} }
error_t pi4ioe5v6408_set_input_default_level(struct Device* device, uint8_t bits, TickType_t timeout) { static error_t get_native_pin_number(GpioDescriptor* descriptor, void* pin_number) {
auto* parent = device_get_parent(device); return ERROR_NOT_SUPPORTED;
return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_INPUT_DEFAULT_LEVEL, bits, timeout);
} }
error_t pi4ioe5v6408_set_pull_enable(struct Device* device, uint8_t bits, TickType_t timeout) { static error_t add_callback(GpioDescriptor* descriptor, void (*callback)(void*), void* arg) {
auto* parent = device_get_parent(device); return ERROR_NOT_SUPPORTED;
return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_PULL_ENABLE, bits, timeout);
} }
error_t pi4ioe5v6408_set_pull_select(struct Device* device, uint8_t bits, TickType_t timeout) { static error_t remove_callback(GpioDescriptor* descriptor) {
auto* parent = device_get_parent(device); return ERROR_NOT_SUPPORTED;
return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_PULL_SELECT, bits, timeout);
} }
error_t pi4ioe5v6408_get_input_level(struct Device* device, uint8_t* bits, TickType_t timeout) { static error_t enable_interrupt(GpioDescriptor* descriptor) {
auto* parent = device_get_parent(device); return ERROR_NOT_SUPPORTED;
return i2c_controller_register8_get(parent, GET_CONFIG(device)->address, PI4_REGISTER_INPUT_LEVEL, bits, timeout);
} }
error_t pi4ioe5v6408_set_interrupt_mask(struct Device* device, uint8_t bits, TickType_t timeout) { static error_t disable_interrupt(GpioDescriptor* descriptor) {
auto* parent = device_get_parent(device); return ERROR_NOT_SUPPORTED;
return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_INTERRUPT_MASK, bits, timeout);
} }
error_t pi4ioe5v6408_get_interrupt_level(struct Device* device, uint8_t* bits, TickType_t timeout) { const static GpioControllerApi pi4_gpio_api = {
auto* parent = device_get_parent(device); .set_level = set_level,
return i2c_controller_register8_get(parent, GET_CONFIG(device)->address, PI4_REGISTER_INTERRUPT_LEVEL, bits, timeout); .get_level = get_level,
} .set_flags = set_flags,
.get_flags = get_flags,
.get_native_pin_number = get_native_pin_number,
.add_callback = add_callback,
.remove_callback = remove_callback,
.enable_interrupt = enable_interrupt,
.disable_interrupt = disable_interrupt
};
Driver pi4ioe5v6408_driver = { Driver pi4ioe5v6408_driver = {
.name = "pi4ioe5v6408", .name = "pi4ioe5v6408",
.compatible = (const char*[]) { "diodes,pi4ioe5v6408", nullptr}, .compatible = (const char*[]) { "diodes,pi4ioe5v6408", nullptr},
.start_device = start, .start_device = start,
.stop_device = stop, .stop_device = stop,
.api = nullptr, .api = static_cast<const void*>(&pi4_gpio_api),
.device_type = nullptr, .device_type = &GPIO_CONTROLLER_TYPE,
.owner = &pi4ioe5v6408_module, .owner = &pi4ioe5v6408_module,
.internal = nullptr .internal = nullptr
}; };

View File

@ -1,15 +0,0 @@
#include <drivers/pi4ioe5v6408.h>
#include <tactility/module.h>
const struct ModuleSymbol pi4ioe5v6408_module_symbols[] = {
DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_direction),
DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_output_level),
DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_output_high_impedance),
DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_input_default_level),
DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_pull_enable),
DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_pull_select),
DEFINE_MODULE_SYMBOL(pi4ioe5v6408_get_input_level),
DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_interrupt_mask),
DEFINE_MODULE_SYMBOL(pi4ioe5v6408_get_interrupt_level),
MODULE_SYMBOL_TERMINATOR
};

View File

@ -60,6 +60,8 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
DEFINE_MODULE_SYMBOL(lv_obj_get_group), DEFINE_MODULE_SYMBOL(lv_obj_get_group),
DEFINE_MODULE_SYMBOL(lv_obj_get_user_data), DEFINE_MODULE_SYMBOL(lv_obj_get_user_data),
DEFINE_MODULE_SYMBOL(lv_obj_get_state), DEFINE_MODULE_SYMBOL(lv_obj_get_state),
DEFINE_MODULE_SYMBOL(lv_obj_get_scroll_bottom),
DEFINE_MODULE_SYMBOL(lv_obj_get_scroll_y),
DEFINE_MODULE_SYMBOL(lv_obj_has_flag), DEFINE_MODULE_SYMBOL(lv_obj_has_flag),
DEFINE_MODULE_SYMBOL(lv_obj_has_flag_any), DEFINE_MODULE_SYMBOL(lv_obj_has_flag_any),
DEFINE_MODULE_SYMBOL(lv_obj_has_state), DEFINE_MODULE_SYMBOL(lv_obj_has_state),
@ -158,7 +160,15 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
DEFINE_MODULE_SYMBOL(lv_obj_set_style_min_height), DEFINE_MODULE_SYMBOL(lv_obj_set_style_min_height),
DEFINE_MODULE_SYMBOL(lv_obj_set_style_max_height), DEFINE_MODULE_SYMBOL(lv_obj_set_style_max_height),
// lv_font // lv_font
DEFINE_MODULE_SYMBOL(lv_font_get_bitmap_fmt_txt),
DEFINE_MODULE_SYMBOL(lv_font_get_default), DEFINE_MODULE_SYMBOL(lv_font_get_default),
DEFINE_MODULE_SYMBOL(lv_font_get_glyph_bitmap),
DEFINE_MODULE_SYMBOL(lv_font_get_glyph_dsc),
DEFINE_MODULE_SYMBOL(lv_font_get_glyph_dsc_fmt_txt),
DEFINE_MODULE_SYMBOL(lv_font_get_glyph_static_bitmap),
DEFINE_MODULE_SYMBOL(lv_font_get_glyph_width),
DEFINE_MODULE_SYMBOL(lv_font_get_line_height),
DEFINE_MODULE_SYMBOL(lv_font_set_kerning),
// lv_theme // lv_theme
DEFINE_MODULE_SYMBOL(lv_theme_get_color_primary), DEFINE_MODULE_SYMBOL(lv_theme_get_color_primary),
DEFINE_MODULE_SYMBOL(lv_theme_get_color_secondary), DEFINE_MODULE_SYMBOL(lv_theme_get_color_secondary),
@ -422,5 +432,8 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
DEFINE_MODULE_SYMBOL(lv_spangroup_refresh), DEFINE_MODULE_SYMBOL(lv_spangroup_refresh),
DEFINE_MODULE_SYMBOL(lv_span_get_style), DEFINE_MODULE_SYMBOL(lv_span_get_style),
DEFINE_MODULE_SYMBOL(lv_span_set_text), DEFINE_MODULE_SYMBOL(lv_span_set_text),
// lv_binfont
DEFINE_MODULE_SYMBOL(lv_binfont_create),
DEFINE_MODULE_SYMBOL(lv_binfont_destroy),
MODULE_SYMBOL_TERMINATOR MODULE_SYMBOL_TERMINATOR
}; };

View File

@ -6,5 +6,5 @@ idf_component_register(
SRCS ${SOURCES} SRCS ${SOURCES}
INCLUDE_DIRS "include/" INCLUDE_DIRS "include/"
PRIV_INCLUDE_DIRS "private/" PRIV_INCLUDE_DIRS "private/"
REQUIRES TactilityKernel driver REQUIRES TactilityKernel driver vfs fatfs
) )

View File

@ -0,0 +1,53 @@
description: ESP32 SDMMC
compatible: "espressif,esp32-sdmmc"
properties:
pin-clk:
type: phandle-array
required: true
pin-cmd:
type: phandle-array
required: true
pin-d0:
type: phandle-array
required: true
pin-d1:
type: phandle-array
default: GPIO_PIN_SPEC_NONE
pin-d2:
type: phandle-array
default: GPIO_PIN_SPEC_NONE
pin-d3:
type: phandle-array
default: GPIO_PIN_SPEC_NONE
pin-d4:
type: phandle-array
default: GPIO_PIN_SPEC_NONE
pin-d5:
type: phandle-array
default: GPIO_PIN_SPEC_NONE
pin-d6:
type: phandle-array
default: GPIO_PIN_SPEC_NONE
pin-d7:
type: phandle-array
default: GPIO_PIN_SPEC_NONE
pin-cd:
type: phandle-array
default: GPIO_PIN_SPEC_NONE
pin-wp:
type: phandle-array
default: GPIO_PIN_SPEC_NONE
bus-width:
type: int
required: true
description: Bus width in bits
wp-active-high:
type: boolean
default: false
description: Whether the WP pin is active high
enable-uhs:
type: boolean
default: false
description: Enable UHS mode

View File

@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <tactility/drivers/esp32_sdmmc.h>
#ifdef __cplusplus
extern "C" {
#endif
DEFINE_DEVICETREE(esp32_sdmmc, struct Esp32SdmmcConfig)
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,44 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <soc/soc_caps.h>
#if SOC_SDMMC_HOST_SUPPORTED
#include <sd_protocol_types.h>
#include <stdbool.h>
#include <stdint.h>
#include <tactility/drivers/gpio.h>
#ifdef __cplusplus
extern "C" {
#endif
struct Esp32SdmmcConfig {
struct GpioPinSpec pin_clk;
struct GpioPinSpec pin_cmd;
struct GpioPinSpec pin_d0;
struct GpioPinSpec pin_d1;
struct GpioPinSpec pin_d2;
struct GpioPinSpec pin_d3;
struct GpioPinSpec pin_d4;
struct GpioPinSpec pin_d5;
struct GpioPinSpec pin_d6;
struct GpioPinSpec pin_d7;
struct GpioPinSpec pin_cd;
struct GpioPinSpec pin_wp;
uint8_t bus_width;
bool wp_active_high;
bool enable_uhs;
};
/**
* @brief Get the SD card handle for the given device.
* @param[in] device the device to get the card handle for
* @return the SD card handle, or NULL if the device is not mounted
*/
sdmmc_card_t* esp32_sdmmc_get_card(struct Device* device);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,26 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <soc/soc_caps.h>
#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

View File

@ -12,7 +12,12 @@
#define TAG "esp32_gpio" #define TAG "esp32_gpio"
struct Esp32GpioInternal {
uint8_t isr_service_ref_count = 0;
};
#define GET_CONFIG(device) ((struct Esp32GpioConfig*)device->config) #define GET_CONFIG(device) ((struct Esp32GpioConfig*)device->config)
#define GET_INTERNAL_FROM_DESCRIPTOR(gpio_descriptor) ((struct Esp32GpioInternal*)gpio_descriptor->controller_context)
extern "C" { extern "C" {
@ -97,15 +102,76 @@ static error_t get_native_pin_number(GpioDescriptor* descriptor, void* pin_numbe
return ERROR_NONE; return ERROR_NONE;
} }
static error_t add_callback(GpioDescriptor* descriptor, void (*callback)(void*), void* arg) {
auto* internal = GET_INTERNAL_FROM_DESCRIPTOR(descriptor);
if (internal->isr_service_ref_count == 0) {
auto esp_error = gpio_install_isr_service(0);
if (esp_error != ESP_OK && esp_error != ESP_ERR_INVALID_STATE) {
return esp_err_to_error(esp_error);
}
}
auto esp_error = gpio_isr_handler_add(static_cast<gpio_num_t>(descriptor->pin), callback, arg);
if (esp_error == ESP_OK) {
internal->isr_service_ref_count++;
} else if (internal->isr_service_ref_count == 0) {
gpio_uninstall_isr_service();
}
return esp_err_to_error(esp_error);
}
static error_t remove_callback(GpioDescriptor* descriptor) {
auto esp_error = gpio_isr_handler_remove(static_cast<gpio_num_t>(descriptor->pin));
if (esp_error == ESP_OK) {
auto* internal = GET_INTERNAL_FROM_DESCRIPTOR(descriptor);
check(internal->isr_service_ref_count > 0);
internal->isr_service_ref_count--;
if (internal->isr_service_ref_count == 0) {
gpio_uninstall_isr_service();
}
}
return esp_err_to_error(esp_error);
}
static error_t enable_interrupt(GpioDescriptor* descriptor) {
auto esp_error = gpio_intr_enable(static_cast<gpio_num_t>(descriptor->pin));
return esp_err_to_error(esp_error);
}
static error_t disable_interrupt(GpioDescriptor* descriptor) {
auto esp_error = gpio_intr_disable(static_cast<gpio_num_t>(descriptor->pin));
return esp_err_to_error(esp_error);
}
static error_t start(Device* device) { static error_t start(Device* device) {
ESP_LOGI(TAG, "start %s", device->name); LOG_I(TAG, "start %s", device->name);
auto pin_count = GET_CONFIG(device)->gpioCount; const Esp32GpioConfig* config = GET_CONFIG(device);
return gpio_controller_init_descriptors(device, pin_count, nullptr); auto* internal = new Esp32GpioInternal();
return gpio_controller_init_descriptors(device, config->gpioCount, internal);
} }
static error_t stop(Device* device) { static error_t stop(Device* device) {
ESP_LOGI(TAG, "stop %s", device->name); LOG_I(TAG, "stop %s", device->name);
return gpio_controller_deinit_descriptors(device); const Esp32GpioConfig* config = GET_CONFIG(device);
auto* internal = static_cast<Esp32GpioInternal*>(gpio_controller_get_controller_context(device));
// First, remove all ISR handlers to prevent callbacks during cleanup
for (uint8_t i = 0; i < config->gpioCount; ++i) {
gpio_isr_handler_remove(static_cast<gpio_num_t>(i));
}
// Then uninstall ISR service
if (internal->isr_service_ref_count > 0) {
gpio_uninstall_isr_service();
}
// Now safe to deinit descriptors and delete internal
check(gpio_controller_deinit_descriptors(device) == ERROR_NONE);
delete internal;
return ERROR_NONE;
} }
const static GpioControllerApi esp32_gpio_api = { const static GpioControllerApi esp32_gpio_api = {
@ -113,17 +179,21 @@ const static GpioControllerApi esp32_gpio_api = {
.get_level = get_level, .get_level = get_level,
.set_flags = set_flags, .set_flags = set_flags,
.get_flags = get_flags, .get_flags = get_flags,
.get_native_pin_number = get_native_pin_number .get_native_pin_number = get_native_pin_number,
.add_callback = add_callback,
.remove_callback = remove_callback,
.enable_interrupt = enable_interrupt,
.disable_interrupt = disable_interrupt
}; };
extern struct Module platform_esp32_module; extern Module platform_esp32_module;
Driver esp32_gpio_driver = { Driver esp32_gpio_driver = {
.name = "esp32_gpio", .name = "esp32_gpio",
.compatible = (const char*[]) { "espressif,esp32-gpio", nullptr }, .compatible = (const char*[]) { "espressif,esp32-gpio", nullptr },
.start_device = start, .start_device = start,
.stop_device = stop, .stop_device = stop,
.api = (void*)&esp32_gpio_api, .api = static_cast<const void*>(&esp32_gpio_api),
.device_type = &GPIO_CONTROLLER_TYPE, .device_type = &GPIO_CONTROLLER_TYPE,
.owner = &platform_esp32_module, .owner = &platform_esp32_module,
.internal = nullptr .internal = nullptr

View File

@ -0,0 +1,185 @@
// SPDX-License-Identifier: Apache-2.0
#include <soc/soc_caps.h>
#if SOC_SDMMC_HOST_SUPPORTED
#include <new>
#include <tactility/concurrent/recursive_mutex.h>
#include <tactility/device.h>
#include <tactility/drivers/esp32_gpio_helpers.h>
#include <tactility/drivers/esp32_sdmmc.h>
#include <tactility/drivers/esp32_sdmmc_fs.h>
#include <tactility/drivers/gpio_descriptor.h>
#include <tactility/filesystem/file_system.h>
#include <tactility/log.h>
#define TAG "esp32_sdmmc"
#define GET_CONFIG(device) ((const struct Esp32SdmmcConfig*)device->config)
#define GET_DATA(device) ((struct Esp32SdmmcInternal*)device_get_driver_data(device))
extern "C" {
struct Esp32SdmmcInternal {
RecursiveMutex mutex = {};
bool initialized = false;
Esp32SdmmcHandle esp32_sdmmc_fs_handle = nullptr;
FileSystem* file_system = nullptr;
// Pin descriptors
GpioDescriptor* pin_clk_descriptor = nullptr;
GpioDescriptor* pin_cmd_descriptor = nullptr;
GpioDescriptor* pin_d0_descriptor = nullptr;
GpioDescriptor* pin_d1_descriptor = nullptr;
GpioDescriptor* pin_d2_descriptor = nullptr;
GpioDescriptor* pin_d3_descriptor = nullptr;
GpioDescriptor* pin_d4_descriptor = nullptr;
GpioDescriptor* pin_d5_descriptor = nullptr;
GpioDescriptor* pin_d6_descriptor = nullptr;
GpioDescriptor* pin_d7_descriptor = nullptr;
GpioDescriptor* pin_cd_descriptor = nullptr;
GpioDescriptor* pin_wp_descriptor = nullptr;
explicit Esp32SdmmcInternal() {
recursive_mutex_construct(&mutex);
}
~Esp32SdmmcInternal() {
cleanup_pins();
recursive_mutex_destruct(&mutex);
if (esp32_sdmmc_fs_handle) esp32_sdmmc_fs_free(esp32_sdmmc_fs_handle);
}
void cleanup_pins() {
release_pin(&pin_clk_descriptor);
release_pin(&pin_cmd_descriptor);
release_pin(&pin_d0_descriptor);
release_pin(&pin_d1_descriptor);
release_pin(&pin_d2_descriptor);
release_pin(&pin_d3_descriptor);
release_pin(&pin_d4_descriptor);
release_pin(&pin_d5_descriptor);
release_pin(&pin_d6_descriptor);
release_pin(&pin_d7_descriptor);
release_pin(&pin_cd_descriptor);
release_pin(&pin_wp_descriptor);
}
void lock() { recursive_mutex_lock(&mutex); }
void unlock() { recursive_mutex_unlock(&mutex); }
};
static error_t start(Device* device) {
LOG_I(TAG, "start %s", device->name);
auto* data = new (std::nothrow) Esp32SdmmcInternal();
if (!data) return ERROR_OUT_OF_MEMORY;
data->lock();
device_set_driver_data(device, data);
auto* sdmmc_config = GET_CONFIG(device);
// Acquire pins from the specified GPIO pin specs. Optional pins are allowed.
bool pins_ok =
acquire_pin_or_set_null(sdmmc_config->pin_clk, &data->pin_clk_descriptor) &&
acquire_pin_or_set_null(sdmmc_config->pin_cmd, &data->pin_cmd_descriptor) &&
acquire_pin_or_set_null(sdmmc_config->pin_d0, &data->pin_d0_descriptor) &&
acquire_pin_or_set_null(sdmmc_config->pin_d1, &data->pin_d1_descriptor) &&
acquire_pin_or_set_null(sdmmc_config->pin_d2, &data->pin_d2_descriptor) &&
acquire_pin_or_set_null(sdmmc_config->pin_d3, &data->pin_d3_descriptor) &&
acquire_pin_or_set_null(sdmmc_config->pin_d4, &data->pin_d4_descriptor) &&
acquire_pin_or_set_null(sdmmc_config->pin_d5, &data->pin_d5_descriptor) &&
acquire_pin_or_set_null(sdmmc_config->pin_d6, &data->pin_d6_descriptor) &&
acquire_pin_or_set_null(sdmmc_config->pin_d7, &data->pin_d7_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);
if (!pins_ok) {
LOG_E(TAG, "Failed to acquire required one or more pins");
data->cleanup_pins();
device_set_driver_data(device, nullptr);
data->unlock();
delete data;
return ERROR_RESOURCE;
}
data->esp32_sdmmc_fs_handle = esp32_sdmmc_fs_alloc(sdmmc_config, "/sdcard");
if (!data->esp32_sdmmc_fs_handle) {
data->cleanup_pins();
device_set_driver_data(device, nullptr);
data->unlock();
delete data;
return ERROR_OUT_OF_MEMORY;
}
data->file_system = file_system_add(&esp32_sdmmc_fs_api, data->esp32_sdmmc_fs_handle);
if (file_system_mount(data->file_system) != ERROR_NONE) {
// Error is not recoverable at the time, but it might be recoverable later,
// so we don't return start() failure.
LOG_E(TAG, "Failed to mount SD card filesystem");
}
data->initialized = true;
data->unlock();
return ERROR_NONE;
}
static error_t stop(Device* device) {
LOG_I(TAG, "stop %s", device->name);
auto* data = GET_DATA(device);
if (!data) return ERROR_NONE;
data->lock();
if (file_system_is_mounted(data->file_system)) {
if (file_system_unmount(data->file_system) != ERROR_NONE) {
LOG_E(TAG, "Failed to unmount SD card filesystem");
data->unlock();
return ERROR_RESOURCE;
}
}
// 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();
device_set_driver_data(device, nullptr);
data->unlock();
delete data;
return ERROR_NONE;
}
sdmmc_card_t* esp32_sdmmc_get_card(Device* device) {
if (!device_is_ready(device)) return nullptr;
auto* data = GET_DATA(device);
if (!data) return nullptr;
data->lock();
auto* card = esp32_sdmmc_fs_get_card(data->esp32_sdmmc_fs_handle);
data->unlock();
return card;
}
extern Module platform_esp32_module;
Driver esp32_sdmmc_driver = {
.name = "esp32_sdmmc",
.compatible = (const char*[]) { "espressif,esp32-sdmmc", nullptr },
.start_device = start,
.stop_device = stop,
.api = nullptr,
.device_type = nullptr,
.owner = &platform_esp32_module,
.internal = nullptr
};
} // extern "C"
#endif

View File

@ -0,0 +1,179 @@
// SPDX-License-Identifier: Apache-2.0
#include <soc/soc_caps.h>
#if SOC_SDMMC_HOST_SUPPORTED
#include <tactility/device.h>
#include <tactility/drivers/esp32_sdmmc.h>
#include <tactility/drivers/esp32_sdmmc_fs.h>
#include <tactility/filesystem/file_system.h>
#include <tactility/drivers/gpio_descriptor.h>
#include <tactility/log.h>
#include <driver/sdmmc_host.h>
#include <esp_vfs_fat.h>
#include <sdmmc_cmd.h>
#include <string>
#if SOC_SD_PWR_CTRL_SUPPORTED
#include <sd_pwr_ctrl_by_on_chip_ldo.h>
#endif
#define TAG "esp32_sdmmc_fs"
#define GET_DATA(data) static_cast<Esp32SdmmcFsData*>(data)
struct Esp32SdmmcFsData {
const std::string mount_path;
const Esp32SdmmcConfig* config;
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) {
if (pin_spec.gpio_controller == nullptr) { return GPIO_NUM_NC; }
return static_cast<gpio_num_t>(pin_spec.pin);
}
extern "C" {
static error_t get_path(void* data, char* out_path, size_t out_path_size);
Esp32SdmmcHandle esp32_sdmmc_fs_alloc(const Esp32SdmmcConfig* config, const char* mount_path) {
return new(std::nothrow) Esp32SdmmcFsData(config, mount_path);
}
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 = {
.format_if_mount_failed = false,
.max_files = 4,
.allocation_unit_size = 0, // Default is sector size
.disk_status_check_enable = false,
.use_one_fat = false
};
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, &fs_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 = fs_data->pwr_ctrl_handle;
#endif
uint32_t slot_config_flags = 0;
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;
sdmmc_slot_config_t slot_config = {
.clk = to_native_pin(config->pin_clk),
.cmd = to_native_pin(config->pin_cmd),
.d0 = to_native_pin(config->pin_d0),
.d1 = to_native_pin(config->pin_d1),
.d2 = to_native_pin(config->pin_d2),
.d3 = to_native_pin(config->pin_d3),
.d4 = to_native_pin(config->pin_d4),
.d5 = to_native_pin(config->pin_d5),
.d6 = to_native_pin(config->pin_d6),
.d7 = to_native_pin(config->pin_d7),
.cd = to_native_pin(config->pin_cd),
.wp = to_native_pin(config->pin_wp),
.width = config->bus_width,
.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);
if (result != ESP_OK || fs_data->card == nullptr) {
if (result == ESP_FAIL) {
LOG_E(TAG, "Mounting failed. Ensure the card is formatted with FAT.");
} else {
LOG_E(TAG, "Mounting failed: %s", esp_err_to_name(result));
}
#if SOC_SD_PWR_CTRL_SUPPORTED
if (fs_data->pwr_ctrl_handle) {
sd_pwr_ctrl_del_on_chip_ldo(fs_data->pwr_ctrl_handle);
fs_data->pwr_ctrl_handle = nullptr;
}
#endif
return ERROR_UNDEFINED;
}
LOG_I(TAG, "Mounted %s", fs_data->mount_path.c_str());
return ERROR_NONE;
}
static error_t unmount(void* data) {
auto* fs_data = GET_DATA(data);
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) {
LOG_E(TAG, "Unmount failed for %s", fs_data->mount_path.c_str());
return ERROR_UNDEFINED;
}
fs_data->card = nullptr;
#if SOC_SD_PWR_CTRL_SUPPORTED
if (fs_data->pwr_ctrl_handle) {
sd_pwr_ctrl_del_on_chip_ldo(fs_data->pwr_ctrl_handle);
fs_data->pwr_ctrl_handle = nullptr;
}
#endif
LOG_I(TAG, "Unmounted %s", fs_data->mount_path.c_str());
return ERROR_NONE;
}
static bool is_mounted(void* data) {
const auto* fs_data = GET_DATA(data);
if (fs_data->card == nullptr) return false;
return sdmmc_get_status(fs_data->card) == ESP_OK;
}
static error_t get_path(void* data, char* out_path, size_t out_path_size) {
const auto* fs_data = GET_DATA(data);
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);
return ERROR_NONE;
}
const FileSystemApi esp32_sdmmc_fs_api = {
.mount = mount,
.unmount = unmount,
.is_mounted = is_mounted,
.get_path = get_path
};
} // extern "C"
#endif

View File

@ -2,11 +2,11 @@
#include <tactility/device.h> #include <tactility/device.h>
#include <tactility/drivers/esp32_spi.h> #include <tactility/drivers/esp32_spi.h>
#include <tactility/concurrent/recursive_mutex.h> #include <tactility/concurrent/recursive_mutex.h>
#include <tactility/log.h>
#include "tactility/drivers/gpio_descriptor.h" #include "tactility/drivers/gpio_descriptor.h"
#include <tactility/drivers/esp32_gpio_helpers.h> #include <tactility/drivers/esp32_gpio_helpers.h>
#include <cstring> #include <cstring>
#include <esp_log.h>
#include <new> #include <new>
#include <soc/gpio_num.h> #include <soc/gpio_num.h>
@ -84,7 +84,7 @@ static error_t start(Device* device) {
acquire_pin_or_set_null(dts_config->pin_hd, &data->hd_descriptor); acquire_pin_or_set_null(dts_config->pin_hd, &data->hd_descriptor);
if (!pins_ok) { if (!pins_ok) {
ESP_LOGE(TAG, "Failed to acquire required SPI pins"); LOG_E(TAG, "Failed to acquire required SPI pins");
data->cleanup_pins(); data->cleanup_pins();
device_set_driver_data(device, nullptr); device_set_driver_data(device, nullptr);
delete data; delete data;
@ -113,7 +113,7 @@ static error_t start(Device* device) {
data->cleanup_pins(); data->cleanup_pins();
device_set_driver_data(device, nullptr); device_set_driver_data(device, nullptr);
delete data; delete data;
ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret)); LOG_E(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret));
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
@ -122,7 +122,7 @@ static error_t start(Device* device) {
} }
static error_t stop(Device* device) { static error_t stop(Device* device) {
ESP_LOGI(TAG, "stop %s", device->name); LOG_I(TAG, "stop %s", device->name);
auto* driver_data = GET_DATA(device); auto* driver_data = GET_DATA(device);
auto* dts_config = GET_CONFIG(device); auto* dts_config = GET_CONFIG(device);

View File

@ -2,11 +2,16 @@
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/module.h> #include <tactility/module.h>
#include <soc/soc_caps.h>
extern "C" { 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;
#endif
extern Driver esp32_spi_driver; extern Driver esp32_spi_driver;
extern Driver esp32_uart_driver; extern Driver esp32_uart_driver;
@ -16,6 +21,9 @@ 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);
#endif
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;
@ -27,12 +35,15 @@ 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);
#endif
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;
} }
struct Module platform_esp32_module = { Module platform_esp32_module = {
.name = "platform-esp32", .name = "platform-esp32",
.start = start, .start = start,
.stop = stop, .stop = stop,

View File

@ -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> getMountPoints(); std::vector<dirent> getFileSystemDirents();
} // namespace } // namespace

View File

@ -2,10 +2,15 @@
#include <string> #include <string>
#include <tactility/filesystem/file_system.h>
namespace tt { namespace tt {
bool findFirstMountedSdCardPath(std::string& path); bool findFirstMountedSdCardPath(std::string& path);
FileSystem* findSdcardFileSystem(bool mustBeMounted);
std::string getSystemRootPath(); std::string getSystemRootPath();
std::string getTempPath(); std::string getTempPath();

View File

@ -2,9 +2,11 @@
#include <tactility/hal/Device.h> #include <tactility/hal/Device.h>
#include <Tactility/TactilityCore.h>
#include <Tactility/Lock.h> #include <Tactility/Lock.h>
#include <Tactility/TactilityCore.h>
struct FileSystem;
namespace tt::hal::sdcard { namespace tt::hal::sdcard {
/** /**
@ -31,11 +33,12 @@ public:
private: private:
MountBehaviour mountBehaviour; MountBehaviour mountBehaviour;
FileSystem* fileSystem;
public: public:
explicit SdCardDevice(MountBehaviour mountBehaviour) : mountBehaviour(mountBehaviour) {} explicit SdCardDevice(MountBehaviour mountBehaviour);
~SdCardDevice() override = default; ~SdCardDevice() override;
Type getType() const final { return Type::SdCard; }; Type getType() const final { return Type::SdCard; };

View File

@ -1,93 +0,0 @@
#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)
{}
int spiFrequencyKhz;
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

View File

@ -2,56 +2,36 @@
#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 <vector>
#include <dirent.h> #include <dirent.h>
#include <tactility/filesystem/file_system.h>
#include <vector>
namespace tt::file { namespace tt::file {
std::vector<dirent> getMountPoints() { std::vector<dirent> getFileSystemDirents() {
std::vector<dirent> dir_entries; std::vector<dirent> dir_entries;
dir_entries.clear();
// Data partition file_system_for_each(&dir_entries, [](auto* fs, void* context) {
auto data_dirent = dirent{ if (!file_system_is_mounted(fs)) return true;
.d_ino = 1, char path[128];
.d_type = TT_DT_DIR, if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true;
.d_name = { 0 } auto mount_name = std::string(path).substr(1);
}; if (!config::SHOW_SYSTEM_PARTITION && mount_name.starts_with(SYSTEM_PARTITION_NAME)) return true;
strcpy(data_dirent.d_name, DATA_PARTITION_NAME); auto dir_entry = dirent {
dir_entries.push_back(data_dirent); .d_ino = 2,
// 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 {
.d_ino = 2,
.d_type = TT_DT_DIR,
.d_name = { 0 }
};
assert(mount_name.length() < sizeof(dirent::d_name));
strcpy(dir_entry.d_name, mount_name.c_str());
dir_entries.push_back(dir_entry);
}
}
if (config::SHOW_SYSTEM_PARTITION) {
// System partition
auto system_dirent = dirent{
.d_ino = 0,
.d_type = TT_DT_DIR, .d_type = TT_DT_DIR,
.d_name = { 0 } .d_name = { 0 }
}; };
strcpy(system_dirent.d_name, SYSTEM_PARTITION_NAME); auto& dir_entries = *static_cast<std::vector<dirent>*>(context);
dir_entries.push_back(system_dirent); strcpy(dir_entry.d_name, mount_name.c_str());
} dir_entries.push_back(dir_entry);
return true;
});
return dir_entries; return dir_entries;
} }

View File

@ -5,11 +5,47 @@
#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) {
@ -56,6 +92,8 @@ 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");
static auto system_fs_data = PartitionFsData("/system");
file_system_add(&partition_fs_api, &system_fs_data);
} }
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);
@ -63,6 +101,8 @@ 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");
static auto data_fs_data = PartitionFsData("/data");
file_system_add(&partition_fs_api, &data_fs_data);
} }
return system_result == ESP_OK && data_result == ESP_OK; return system_result == ESP_OK && data_result == ESP_OK;

View File

@ -2,25 +2,37 @@
#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) {
// const auto sdcards = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard); auto* fs = findSdcardFileSystem(true);
bool is_set = false; if (fs == nullptr) return false;
hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard, [&is_set, &path](const auto& device) { char found_path[128];
if (device->isMounted()) { if (file_system_get_path(fs, found_path, sizeof(found_path)) != ERROR_NONE) return false;
path = device->getMountPath(); path = found_path;
is_set = true; return true;
return false; // stop iterating }
} else {
return true; FileSystem* findSdcardFileSystem(bool mustBeMounted) {
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 is_set; if (found && mustBeMounted && !file_system_is_mounted(found)) {
return nullptr;
}
return found;
} }
std::string getSystemRootPath() { std::string getSystemRootPath() {

View File

@ -8,19 +8,19 @@
#include <Tactility/Tactility.h> #include <Tactility/Tactility.h>
#include <Tactility/TactilityConfig.h> #include <Tactility/TactilityConfig.h>
#include <Tactility/app/AppManifestParsing.h>
#include <Tactility/app/AppRegistration.h>
#include <Tactility/CpuAffinity.h> #include <Tactility/CpuAffinity.h>
#include <Tactility/DispatcherThread.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/AppRegistration.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,6 +28,7 @@
#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>
@ -49,7 +50,6 @@ namespace service {
// Primary // Primary
namespace gps { extern const ServiceManifest manifest; } namespace gps { extern const ServiceManifest manifest; }
namespace wifi { extern const ServiceManifest manifest; } namespace wifi { extern const ServiceManifest manifest; }
namespace sdcard { extern const ServiceManifest manifest; }
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
namespace development { extern const ServiceManifest manifest; } namespace development { extern const ServiceManifest manifest; }
#endif #endif
@ -228,22 +228,18 @@ static void registerInstalledApps(const std::string& path) {
}); });
} }
static void registerInstalledAppsFromSdCard(const std::shared_ptr<hal::sdcard::SdCardDevice>& sdcard) { static void registerInstalledAppsFromFileSystems() {
auto sdcard_root_path = sdcard->getMountPath(); file_system_for_each(nullptr, [](auto* fs, void* context) {
auto app_path = std::format("{}/app", sdcard_root_path); if (!file_system_is_mounted(fs)) return true;
if (file::isDirectory(app_path)) { char path[128];
registerInstalledApps(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 {}", app_path);
static void registerInstalledAppsFromSdCards() { registerInstalledApps(app_path);
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);
} }
} return true;
});
} }
static void registerAndStartSecondaryServices() { static void registerAndStartSecondaryServices() {
@ -266,9 +262,6 @@ 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 (hal::hasDevice(hal::Device::Type::SdCard)) {
addService(service::sdcard::manifest);
}
addService(service::wifi::manifest); addService(service::wifi::manifest);
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
addService(service::development::manifest); addService(service::development::manifest);
@ -301,26 +294,19 @@ void createTempDirectory(const std::string& rootPath) {
} }
void prepareFileSystems() { void prepareFileSystems() {
// Temporary directories for SD cards file_system_for_each(nullptr, [](auto* fs, void* context) {
auto sdcard_devices = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard); if (!file_system_is_mounted(fs)) return true;
for (const auto& sdcard : sdcard_devices) { char path[128];
if (sdcard->isMounted()) { if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true;
createTempDirectory(sdcard->getMountPath()); if (std::string(path) == file::MOUNT_POINT_SYSTEM) return true;
} 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();
auto data_apps_path = std::format("{}/app", file::MOUNT_POINT_DATA); registerInstalledAppsFromFileSystems();
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[]) {

View File

@ -2,7 +2,6 @@
#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>

View File

@ -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::getMountPoints(); dir_entries = file::getFileSystemDirents();
current_path = path; current_path = path;
selected_child_entry = ""; selected_child_entry = "";
action = ActionNone; action = ActionNone;

View File

@ -6,10 +6,11 @@
#include <Tactility/MountPoints.h> #include <Tactility/MountPoints.h>
#include <Tactility/kernel/Platform.h> #include <Tactility/kernel/Platform.h>
#include <Tactility/LogMessages.h>
#include <cstring> #include <cstring>
#include <dirent.h>
#include <unistd.h> #include <unistd.h>
#include <vector> #include <vector>
#include <dirent.h>
namespace tt::app::fileselection { namespace tt::app::fileselection {
@ -36,6 +37,12 @@ std::string State::getSelectedChildPath() const {
bool State::setEntriesForPath(const std::string& path) { bool State::setEntriesForPath(const std::string& path) {
LOGGER.info("Changing path: {} -> {}", current_path, path); LOGGER.info("Changing path: {} -> {}", current_path, path);
auto lock = mutex.asScopedLock();
if (!lock.lock(100)) {
LOGGER.error(LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "setEntriesForPath");
return false;
}
/** /**
* ESP32 does not have a root directory, so we have to create it manually. * ESP32 does not have a root directory, so we have to create it manually.
* We'll add the NVS Flash partitions and the binding for the sdcard. * We'll add the NVS Flash partitions and the binding for the sdcard.
@ -43,7 +50,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::getMountPoints(); dir_entries = file::getFileSystemDirents();
current_path = path; current_path = path;
selected_child_entry = ""; selected_child_entry = "";
return true; return true;

View File

@ -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,12 +204,9 @@ 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) {
auto sdcard_devices = tt::hal::findDevices<tt::hal::sdcard::SdCardDevice>(tt::hal::Device::Type::SdCard); std::string sdcard_path;
if (sdcard_devices.size() > 1) { if (findFirstMountedSdCardPath(sdcard_path)) {
LOGGER.warn("Found multiple SD card devices - picking first"); std::string lvgl_mount_path = lvgl::PATH_PREFIX + sdcard_path + "/screenshots";
}
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");

View File

@ -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>
@ -321,7 +321,6 @@ class SystemInfoApp final : public App {
bool hasExternalMem = false; bool hasExternalMem = false;
bool hasDataStorage = false; bool hasDataStorage = false;
bool hasSdcardStorage = false;
bool hasSystemStorage = false; bool hasSystemStorage = false;
void updateMemory() { void updateMemory() {
@ -343,14 +342,9 @@ class SystemInfoApp final : public App {
} }
} }
if (hasSdcardStorage) { 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) { updateMemoryBar(sdcardStorageBar, storage_free, storage_total);
if (sdcard->isMounted() && esp_vfs_fat_info(sdcard->getMountPath().c_str(), &storage_total, &storage_free) == ESP_OK) {
updateMemoryBar(sdcardStorageBar, storage_free, storage_total);
break; // Only update first SD card
}
}
} }
if (hasSystemStorage) { if (hasSystemStorage) {
@ -624,13 +618,9 @@ class SystemInfoApp final : public App {
dataStorageBar = createMemoryBar(storage_tab, file::MOUNT_POINT_DATA); dataStorageBar = createMemoryBar(storage_tab, file::MOUNT_POINT_DATA);
} }
const auto sdcard_devices = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard); std::string sdcard_path;
for (const auto& sdcard : sdcard_devices) { if (findFirstMountedSdCardPath(sdcard_path) && esp_vfs_fat_info(sdcard_path.c_str(), &storage_total, &storage_free) == ESP_OK) {
if (sdcard->isMounted() && esp_vfs_fat_info(sdcard->getMountPath().c_str(), &storage_total, &storage_free) == ESP_OK) { sdcardStorageBar = createMemoryBar(storage_tab, sdcard_path.c_str());
hasSdcardStorage = true;
sdcardStorageBar = createMemoryBar(storage_tab, sdcard->getMountPath().c_str());
break; // Only show first SD card
}
} }
if (config::SHOW_SYSTEM_PARTITION) { if (config::SHOW_SYSTEM_PARTITION) {

View File

@ -0,0 +1,52 @@
#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);
const auto mount_path = device->getMountPath();
if (mount_path.size() >= out_path_size) return ERROR_BUFFER_OVERFLOW;
if (mount_path.empty()) return ERROR_INVALID_STATE;
strncpy(out_path, mount_path.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);
}
}

View File

@ -1,123 +0,0 @@
#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 = config->allocUnitSize,
.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

View File

@ -1,10 +1,13 @@
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
#include <soc/soc_caps.h>
#include <Tactility/hal/usb/Usb.h> #include <Tactility/hal/usb/Usb.h>
#include <Tactility/hal/sdcard/SpiSdCardDevice.h> #include <Tactility/hal/sdcard/SpiSdCardDevice.h>
#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 {
@ -21,29 +24,41 @@ 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() {
auto sdcards = findDevices<sdcard::SpiSdCardDevice>(Device::Type::SdCard); sdmmc_card_t* sdcard = nullptr;
std::shared_ptr<sdcard::SpiSdCardDevice> usable_sdcard; // Find old HAL SD card device:
for (auto& sdcard : sdcards) { auto sdcards = findDevices<sdcard::SpiSdCardDevice>(Device::Type::SdCard);
auto sdcard_candidate = std::static_pointer_cast<sdcard::SpiSdCardDevice>(sdcard); for (auto& device : sdcards) {
if (sdcard_candidate != nullptr && sdcard_candidate->isMounted() && sdcard_candidate->getCard() != nullptr) { auto sdcard_device= std::static_pointer_cast<sdcard::SpiSdCardDevice>(device);
usable_sdcard = sdcard_candidate; if (sdcard_device != nullptr && sdcard_device->isMounted() && sdcard_device->getCard() != nullptr) {
sdcard = sdcard_device->getCard();
break; break;
} }
} }
if (usable_sdcard == nullptr) { #if SOC_SDMMC_HOST_SUPPORTED
LOGGER.warn("Couldn't find a mounted SpiSdCard"); // Find ESP32 SDMMC device:
return nullptr; if (sdcard == 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);
auto* sdmmc_card = esp32_sdmmc_get_card(device);
if (sdmmc_card) {
*sdcard = sdmmc_card;
return false;
}
return true;
}
return true;
});
}
#endif
if (sdcard == nullptr) {
LOGGER.warn("Couldn't find a mounted SD card");
} }
auto* sdmmc_card = usable_sdcard->getCard(); return sdcard;
if (sdmmc_card == nullptr) {
LOGGER.warn("SD card has no card object available");
return nullptr;
}
return sdmmc_card;
} }
static bool canStartNewMode() { static bool canStartNewMode() {

View File

@ -1,88 +0,0 @@
#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/ServiceRegistration.h>
#include <Tactility/Tactility.h>
#include <Tactility/Timer.h>
namespace tt::service::sdcard {
static const auto LOGGER = Logger("SdcardService");
extern const ServiceManifest manifest;
class SdCardService final : public Service {
Mutex mutex;
std::unique_ptr<Timer> updateTimer;
hal::sdcard::SdCardDevice::State lastState = hal::sdcard::SdCardDevice::State::Unmounted;
bool lock(TickType_t timeout) const {
return mutex.lock(timeout);
}
void unlock() const {
mutex.unlock();
}
void update() {
// TODO: Support multiple SD cards
auto sdcard = hal::findFirstDevice<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
if (sdcard == nullptr) {
return;
}
if (lock(50)) {
auto new_state = sdcard->getState();
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();
} else {
LOGGER.warn(LOG_MESSAGE_MUTEX_LOCK_FAILED);
}
}
public:
bool onStart(ServiceContext& serviceContext) override {
if (hal::findFirstDevice<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard) == nullptr) {
LOGGER.warn("No SD card device found - not starting Service");
return false;
}
auto service = findServiceById<SdCardService>(manifest.id);
updateTimer = std::make_unique<Timer>(Timer::Type::Periodic, 1000, [service] {
service->update();
});
// We want to try and scan more often in case of startup or scan lock failure
updateTimer->start();
return true;
}
void onStop(ServiceContext& serviceContext) override {
if (updateTimer != nullptr) {
// Stop thread
updateTimer->stop();
updateTimer = nullptr;
}
}
};
extern const ServiceManifest manifest = {
.id = "sdcard",
.createService = create<SdCardService>
};
} // namespace

View File

@ -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,6 +13,7 @@
#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>
@ -56,18 +57,9 @@ static const char* getWifiStatusIcon(wifi::RadioState state) {
} }
} }
static const char* getSdCardStatusIcon(hal::sdcard::SdCardDevice::State state) { static const char* getSdCardStatusIcon(bool mounted) {
switch (state) { if (mounted) return LVGL_ICON_STATUSBAR_SD_CARD;
using enum hal::sdcard::SdCardDevice::State; return LVGL_ICON_STATUSBAR_SD_CARD_ALERT;
case Mounted:
return LVGL_ICON_STATUSBAR_SD_CARD;
case Error:
case Unmounted:
case Timeout:
return LVGL_ICON_STATUSBAR_SD_CARD_ALERT;
default:
check(false, "Unhandled SdCard state");
}
} }
static const char* getPowerStatusIcon() { static const char* getPowerStatusIcon() {
@ -172,20 +164,21 @@ class StatusbarService final : public Service {
} }
void updateSdCardIcon() { void updateSdCardIcon() {
auto sdcards = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard); auto* sdcard_fs = findSdcardFileSystem(false);
// TODO: Support multiple SD cards // TODO: Support multiple SD cards
auto sdcard = sdcards.empty() ? nullptr : sdcards[0]; if (sdcard_fs != nullptr) {
if (sdcard != nullptr) { auto mounted = file_system_is_mounted(sdcard_fs);
auto state = sdcard->getState(50 / portTICK_PERIOD_MS); auto* desired_icon = getSdCardStatusIcon(mounted);
if (state != hal::sdcard::SdCardDevice::State::Timeout) { if (sdcard_last_icon != desired_icon) {
auto* desired_icon = getSdCardStatusIcon(state); lvgl::statusbar_icon_set_image(sdcard_icon_id, desired_icon);
if (sdcard_last_icon != desired_icon) { lvgl::statusbar_icon_set_visibility(sdcard_icon_id, true);
lvgl::statusbar_icon_set_image(sdcard_icon_id, desired_icon); sdcard_last_icon = desired_icon;
lvgl::statusbar_icon_set_visibility(sdcard_icon_id, true); }
sdcard_last_icon = desired_icon; } else {
} if (sdcard_last_icon != nullptr) {
lvgl::statusbar_icon_set_visibility(sdcard_icon_id, false);
sdcard_last_icon = nullptr;
} }
// TODO: Consider tracking how long the SD card has been in unknown status and then show error
} }
} }

View File

@ -27,6 +27,7 @@
#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>
@ -764,20 +765,24 @@ 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 == "/") {
// Always show /data file_system_for_each(&fs_iter_context, [] (auto* fs, void* context) {
json << "{\"name\":\"data\",\"type\":\"dir\",\"size\":0}"; auto* fs_iter_context = static_cast<FsIterContext*>(context);
char path[128];
// Show /sdcard if mounted if (file_system_is_mounted(fs) && file_system_get_path(fs, path, sizeof(path)) == ERROR_NONE && strcmp(path, "/system") != 0) {
const auto sdcard_devices = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard); fs_iter_context->count++;
for (const auto& sdcard : sdcard_devices) { if (fs_iter_context->count != 1) fs_iter_context->json << ","; // add separator between json array entries
if (sdcard->isMounted()) { fs_iter_context->json << "{\"name\":\"" << path << "\",\"type\":\"dir\",\"size\":0}";
json << ",{\"name\":\"sdcard\",\"type\":\"dir\",\"size\":0}";
break;
} }
} return true;
});
json << "]}"; json << "]}";
} else { } else {
std::vector<dirent> entries; std::vector<dirent> entries;
@ -1160,34 +1165,38 @@ 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;
// Data partition struct FsIterContext {
json << "\"data\":{"; std::ostringstream& json;
if (esp_vfs_fat_info(file::MOUNT_POINT_DATA, &storage_total, &storage_free) == ESP_OK) { uint16_t count = 0;
json << "\"free\":" << storage_free << ","; };
json << "\"total\":" << storage_total << ","; FsIterContext fs_iter_context { json };
json << "\"mounted\":true"; file_system_for_each(&fs_iter_context, [] (auto* fs, void* context) {
} else { char mount_path[128] = "";
json << "\"mounted\":false"; 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
json << "},";
// SD card - check all sdcard devices bool mounted = file_system_is_mounted(fs);
json << "\"sdcard\":{"; auto* fs_iter_context = static_cast<FsIterContext*>(context);
bool sdcard_found = false; auto& json_context = fs_iter_context->json;
const auto sdcard_devices = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard); std::string mount_path_cpp = mount_path;
for (const auto& sdcard : sdcard_devices) {
if (sdcard->isMounted() && esp_vfs_fat_info(sdcard->getMountPath().c_str(), &storage_total, &storage_free) == ESP_OK) { fs_iter_context->count++;
json << "\"free\":" << storage_free << ","; if (fs_iter_context->count != 1) json_context << ","; // add separator between json array entries
json << "\"total\":" << storage_total << ","; json_context << "\"" << mount_path_cpp.substr(1) << "\":{";
json << "\"mounted\":true";
sdcard_found = true; uint64_t storage_total = 0, storage_free = 0;
break; if (esp_vfs_fat_info(mount_path, &storage_total, &storage_free) == ESP_OK) {
json_context << "\"free\":" << storage_free << ",";
json_context << "\"total\":" << storage_total << ",";
} else {
json_context << "\"free\":0,";
json_context << "\"total\":0,";
} }
}
if (!sdcard_found) { json_context << "\"mounted\":" << (mounted ? "true" : "false") << "";
json << "\"mounted\":false"; json_context << "}";
} return true;
json << "}"; });
json << "},"; // end storage json << "},"; // end storage
@ -1459,14 +1468,7 @@ 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;
auto sdcard_devices = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard); if (!findFirstMountedSdCardPath(save_path)) {
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;
} }
@ -1543,7 +1545,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::getMountPoints(); auto mounts = file::getFileSystemDirents();
json << "\"mounts\": ["; json << "\"mounts\": [";
bool firstMount = true; bool firstMount = true;
for (auto& m : mounts) { for (auto& m : mounts) {

View File

@ -6,13 +6,14 @@
#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 {
@ -118,18 +119,14 @@ 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 settings_path = file::getChildPath(file::MOUNT_POINT_DATA, "settings"); const std::string data_settings_path = file::getChildPath(file::MOUNT_POINT_DATA, "settings");
importWifiApSettingsFromDir(settings_path); importWifiApSettingsFromDir(data_settings_path);
// Then scan attached SD cards as before. // Then scan attached SD cards as before.
const auto sdcards = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard); std::string sdcard_path;
for (auto& sdcard : sdcards) { if (findFirstMountedSdCardPath((sdcard_path))) {
if (sdcard->isMounted()) { const std::string sd_settings_path = file::getChildPath(sdcard_path, "settings");
const std::string settings_path = file::getChildPath(sdcard->getMountPath(), "settings"); importWifiApSettingsFromDir(sd_settings_path);
importWifiApSettingsFromDir(settings_path);
} else {
LOGGER.warn("Skipping unmounted SD card {}", sdcard->getMountPath());
}
} }
}); });
} }

View File

@ -5,6 +5,7 @@
#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>
@ -18,9 +19,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() {
const auto sdcards = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard); std::string sdcard_path;
for (auto& sdcard : sdcards) { if (findFirstMountedSdCardPath(sdcard_path)) {
std::string path = std::format(PROPERTIES_FILE_FORMAT, sdcard->getMountPath()); std::string path = std::format(PROPERTIES_FILE_FORMAT, sdcard_path);
if (file::isFile(path)) { if (file::isFile(path)) {
return path; return path;
} }

View File

@ -5,6 +5,19 @@
#include <bits/functexcept.h> #include <bits/functexcept.h>
#include <tactility/module.h>
extern "C" {
// std::map / std::set red-black tree non-template helpers.
// We use the mangled names directly (same pattern as string.cpp) to avoid
// ambiguity from the overloaded const/non-const variants in stl_tree.h.
void* _ZSt18_Rb_tree_decrementPSt18_Rb_tree_node_base(void*);
void* _ZSt18_Rb_tree_incrementPSt18_Rb_tree_node_base(void*);
void _ZSt29_Rb_tree_insert_and_rebalancebPSt18_Rb_tree_node_baseS0_RS_(bool, void*, void*, void*);
}
const esp_elfsym stl_symbols[] = { const esp_elfsym stl_symbols[] = {
// Note: You have to use the mangled names here // Note: You have to use the mangled names here
{ "_ZSt17__throw_bad_allocv", (void*)&(std::__throw_bad_alloc) }, { "_ZSt17__throw_bad_allocv", (void*)&(std::__throw_bad_alloc) },
@ -12,7 +25,11 @@ const esp_elfsym stl_symbols[] = {
{ "_ZSt25__throw_bad_function_callv", (void*)&(std::__throw_bad_function_call) }, { "_ZSt25__throw_bad_function_callv", (void*)&(std::__throw_bad_function_call) },
{ "_ZSt20__throw_length_errorPKc", (void*)&(std::__throw_length_error) }, { "_ZSt20__throw_length_errorPKc", (void*)&(std::__throw_length_error) },
{ "_ZSt19__throw_logic_errorPKc", (void*)&std::__throw_logic_error }, { "_ZSt19__throw_logic_errorPKc", (void*)&std::__throw_logic_error },
// { "", (void*)&(std::) }, { "_ZSt24__throw_out_of_range_fmtPKcz", (void*)&std::__throw_out_of_range_fmt },
// std::map / std::set (red-black tree internals)
DEFINE_MODULE_SYMBOL(_ZSt18_Rb_tree_decrementPSt18_Rb_tree_node_base),
DEFINE_MODULE_SYMBOL(_ZSt18_Rb_tree_incrementPSt18_Rb_tree_node_base),
DEFINE_MODULE_SYMBOL(_ZSt29_Rb_tree_insert_and_rebalancebPSt18_Rb_tree_node_baseS0_RS_),
// delimiter // delimiter
ESP_ELFSYM_END ESP_ELFSYM_END
}; };

View File

@ -33,6 +33,7 @@
#include <cstring> #include <cstring>
#include <ctime> #include <ctime>
#include <ctype.h> #include <ctype.h>
#include <driver/ledc.h>
#include <getopt.h> #include <getopt.h>
#include <dirent.h> #include <dirent.h>
#include <esp_log.h> #include <esp_log.h>
@ -44,6 +45,7 @@
#include <lwip/sockets.h> #include <lwip/sockets.h>
#include <lwip/netdb.h> #include <lwip/netdb.h>
#include <lwip/inet.h> #include <lwip/inet.h>
#include <miniz.h>
#include <sys/select.h> #include <sys/select.h>
#include <locale.h> #include <locale.h>
#include <setjmp.h> #include <setjmp.h>
@ -221,6 +223,7 @@ const esp_elfsym main_symbols[] {
ESP_ELFSYM_EXPORT(strtod), ESP_ELFSYM_EXPORT(strtod),
ESP_ELFSYM_EXPORT(strrchr), ESP_ELFSYM_EXPORT(strrchr),
ESP_ELFSYM_EXPORT(strtol), ESP_ELFSYM_EXPORT(strtol),
ESP_ELFSYM_EXPORT(strtoul),
ESP_ELFSYM_EXPORT(strcspn), ESP_ELFSYM_EXPORT(strcspn),
ESP_ELFSYM_EXPORT(strncat), ESP_ELFSYM_EXPORT(strncat),
ESP_ELFSYM_EXPORT(strpbrk), ESP_ELFSYM_EXPORT(strpbrk),
@ -393,6 +396,7 @@ const esp_elfsym main_symbols[] {
ESP_ELFSYM_EXPORT(gpio_config), ESP_ELFSYM_EXPORT(gpio_config),
ESP_ELFSYM_EXPORT(gpio_get_level), ESP_ELFSYM_EXPORT(gpio_get_level),
ESP_ELFSYM_EXPORT(gpio_set_level), ESP_ELFSYM_EXPORT(gpio_set_level),
ESP_ELFSYM_EXPORT(gpio_reset_pin),
// driver/i2s_common.h // driver/i2s_common.h
ESP_ELFSYM_EXPORT(i2s_new_channel), ESP_ELFSYM_EXPORT(i2s_new_channel),
ESP_ELFSYM_EXPORT(i2s_del_channel), ESP_ELFSYM_EXPORT(i2s_del_channel),
@ -409,8 +413,30 @@ const esp_elfsym main_symbols[] {
ESP_ELFSYM_EXPORT(i2s_channel_reconfig_std_clock), ESP_ELFSYM_EXPORT(i2s_channel_reconfig_std_clock),
ESP_ELFSYM_EXPORT(i2s_channel_reconfig_std_slot), ESP_ELFSYM_EXPORT(i2s_channel_reconfig_std_slot),
ESP_ELFSYM_EXPORT(i2s_channel_reconfig_std_gpio), ESP_ELFSYM_EXPORT(i2s_channel_reconfig_std_gpio),
// miniz.h
ESP_ELFSYM_EXPORT(tinfl_decompress),
ESP_ELFSYM_EXPORT(tinfl_decompress_mem_to_callback),
ESP_ELFSYM_EXPORT(tinfl_decompress_mem_to_mem),
// ledc
ESP_ELFSYM_EXPORT(ledc_update_duty),
ESP_ELFSYM_EXPORT(ledc_set_freq),
ESP_ELFSYM_EXPORT(ledc_channel_config),
ESP_ELFSYM_EXPORT(ledc_set_duty),
ESP_ELFSYM_EXPORT(ledc_set_fade),
ESP_ELFSYM_EXPORT(ledc_set_fade_with_step),
ESP_ELFSYM_EXPORT(ledc_set_fade_with_time),
ESP_ELFSYM_EXPORT(ledc_set_fade_step_and_start),
ESP_ELFSYM_EXPORT(ledc_set_fade_time_and_start),
ESP_ELFSYM_EXPORT(ledc_set_pin),
ESP_ELFSYM_EXPORT(ledc_timer_config),
ESP_ELFSYM_EXPORT(ledc_timer_pause),
ESP_ELFSYM_EXPORT(ledc_timer_resume),
ESP_ELFSYM_EXPORT(ledc_timer_rst),
// esp_heap_caps.h // esp_heap_caps.h
ESP_ELFSYM_EXPORT(heap_caps_get_total_size), 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
}; };

View File

@ -7,10 +7,9 @@ extern "C" {
#endif #endif
#include <tactility/device.h> #include <tactility/device.h>
#include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#define GPIO_FLAGS_MASK 0x1f #define GPIO_FLAGS_MASK 0xff
#define GPIO_PIN_NONE -1 #define GPIO_PIN_NONE -1
@ -27,6 +26,7 @@ extern "C" {
#define GPIO_FLAG_INTERRUPT_BITMASK (0b111 << 5) // 3 bits to hold the values [0, 5] #define GPIO_FLAG_INTERRUPT_BITMASK (0b111 << 5) // 3 bits to hold the values [0, 5]
#define GPIO_FLAG_INTERRUPT_FROM_OPTIONS(options) (gpio_int_type_t)((options & GPIO_FLAG_INTERRUPT_BITMASK) >> 5) #define GPIO_FLAG_INTERRUPT_FROM_OPTIONS(options) (gpio_int_type_t)((options & GPIO_FLAG_INTERRUPT_BITMASK) >> 5)
#define GPIO_FLAG_INTERRUPT_TO_OPTIONS(options, interrupt) (options | (interrupt << 5)) #define GPIO_FLAG_INTERRUPT_TO_OPTIONS(options, interrupt) (options | (interrupt << 5))
#define GPIO_FLAG_HIGH_IMPEDANCE (1 << 8)
typedef enum { typedef enum {
GPIO_INTERRUPT_DISABLE = 0, GPIO_INTERRUPT_DISABLE = 0,

View File

@ -52,6 +52,36 @@ struct GpioControllerApi {
* @return ERROR_NONE if successful * @return ERROR_NONE if successful
*/ */
error_t (*get_native_pin_number)(struct GpioDescriptor* descriptor, void* pin_number); error_t (*get_native_pin_number)(struct GpioDescriptor* descriptor, void* pin_number);
/**
* @brief Adds a callback to be called when a GPIO interrupt occurs.
* @param[in] descriptor the pin descriptor
* @param[in] callback the callback function
* @param[in] arg the argument to pass to the callback
* @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented
*/
error_t (*add_callback)(struct GpioDescriptor* descriptor, void (*callback)(void*), void* arg);
/**
* @brief Removes a callback from a GPIO interrupt.
* @param[in] descriptor the pin descriptor
* @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented
*/
error_t (*remove_callback)(struct GpioDescriptor* descriptor);
/**
* @brief Enables the interrupt for a GPIO pin.
* @param[in] descriptor the pin descriptor
* @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented
*/
error_t (*enable_interrupt)(struct GpioDescriptor* descriptor);
/**
* @brief Disables the interrupt for a GPIO pin.
* @param[in] descriptor the pin descriptor
* @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented
*/
error_t (*disable_interrupt)(struct GpioDescriptor* descriptor);
}; };
struct GpioDescriptor* gpio_descriptor_acquire( struct GpioDescriptor* gpio_descriptor_acquire(
@ -118,6 +148,36 @@ error_t gpio_descriptor_get_flags(struct GpioDescriptor* descriptor, gpio_flags_
*/ */
error_t gpio_descriptor_get_native_pin_number(struct GpioDescriptor* descriptor, void* pin_number); error_t gpio_descriptor_get_native_pin_number(struct GpioDescriptor* descriptor, void* pin_number);
/**
* @brief Adds a callback to be called when a GPIO interrupt occurs.
* @param[in] descriptor the pin descriptor
* @param[in] callback the callback function
* @param[in] arg the argument to pass to the callback
* @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented
*/
error_t gpio_descriptor_add_callback(struct GpioDescriptor* descriptor, void (*callback)(void*), void* arg);
/**
* @brief Removes a callback from a GPIO interrupt.
* @param[in] descriptor the pin descriptor
* @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented
*/
error_t gpio_descriptor_remove_callback(struct GpioDescriptor* descriptor);
/**
* @brief Enables the interrupt for a GPIO pin.
* @param[in] descriptor the pin descriptor
* @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented
*/
error_t gpio_descriptor_enable_interrupt(struct GpioDescriptor* descriptor);
/**
* @brief Disables the interrupt for a GPIO pin.
* @param[in] descriptor the pin descriptor
* @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented
*/
error_t gpio_descriptor_disable_interrupt(struct GpioDescriptor* descriptor);
/** /**
* @brief Gets the number of pins supported by the controller. * @brief Gets the number of pins supported by the controller.
* @param[in] device the GPIO controller device * @param[in] device the GPIO controller device
@ -141,6 +201,15 @@ error_t gpio_controller_init_descriptors(struct Device* device, uint32_t pin_cou
*/ */
error_t gpio_controller_deinit_descriptors(struct Device* device); error_t gpio_controller_deinit_descriptors(struct Device* device);
/**
* Unlike other drivers, a GPIO controller's internal data is created and set by gpio_controller_init_descriptors()
* This means that the specific controller implementation cannot set the device's driver data, as it's already set by the GPIO controller base coded
* When calling init descriptors, the caller can pass a controller_context, which is an optional pointer that holds the implementation's internal data.
* @param device the GPIO controller device
* @return the context void pointer
*/
void* gpio_controller_get_controller_context(struct Device* device);
extern const struct DeviceType GPIO_CONTROLLER_TYPE; extern const struct DeviceType GPIO_CONTROLLER_TYPE;
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -11,6 +11,11 @@ struct GpioDescriptor {
gpio_pin_t pin; gpio_pin_t pin;
/** @brief Current owner */ /** @brief Current owner */
enum GpioOwnerType owner_type; enum GpioOwnerType owner_type;
/** @brief Implementation-specific context (e.g. from esp32 controller internally) */ /**
* @brief Implementation-specific context (e.g. from esp32 controller internally)
* Unlike other drivers, a GPIO controller's internal data is created and set by gpio_controller_init_descriptors()
* This means that the specific controller implementation cannot set the device's driver data, as it's already set by the GPIO controller base coded.
* When calling init descriptors, the caller can pass a controller_context, which is an optional pointer that holds the implementation's internal data.
*/
void* controller_context; void* controller_context;
}; };

View File

@ -0,0 +1,205 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <tactility/error.h>
#ifdef __cplusplus
extern "C" {
#endif
struct Device;
enum WifiAuthenticationType {
WIFI_AUTHENTICATION_TYPE_OPEN = 0,
WIFI_AUTHENTICATION_TYPE_WEP,
WIFI_AUTHENTICATION_TYPE_WPA_PSK,
WIFI_AUTHENTICATION_TYPE_WPA2_PSK,
WIFI_AUTHENTICATION_TYPE_WPA_WPA2_PSK,
WIFI_AUTHENTICATION_TYPE_WPA2_ENTERPRISE,
WIFI_AUTHENTICATION_TYPE_WPA3_PSK,
WIFI_AUTHENTICATION_TYPE_WPA2_WPA3_PSK,
WIFI_AUTHENTICATION_TYPE_WAPI_PSK,
WIFI_AUTHENTICATION_TYPE_OWE,
WIFI_AUTHENTICATION_TYPE_WPA3_ENT_192,
WIFI_AUTHENTICATION_TYPE_WPA3_EXT_PSK,
WIFI_AUTHENTICATION_TYPE_WPA3_EXT_PSK_MIXED_MODE,
WIFI_AUTHENTICATION_TYPE_MAX
};
struct WifiApRecord {
char ssid[33]; // 32 bytes + null terminator
int8_t rssi;
int32_t channel;
enum WifiAuthenticationType authentication_type;
};
enum WifiRadioState {
WIFI_RADIO_STATE_OFF,
WIFI_RADIO_STATE_ON_PENDING,
WIFI_RADIO_STATE_ON,
WIFI_RADIO_STATE_OFF_PENDING,
};
enum WifiStationState {
WIFI_STATION_STATE_DISCONNECTED,
WIFI_STATION_STATE_CONNECTION_PENDING,
WIFI_STATION_STATE_CONNECTED
};
enum WifiAccessPointState {
WIFI_ACCESS_POINT_STATE_STARTED,
WIFI_ACCESS_POINT_STATE_STOPPED,
};
enum WifiEventType {
/** Radio state changed */
WIFI_EVENT_TYPE_RADIO_STATE_CHANGED,
/** WifiStationState changed */
WIFI_EVENT_TYPE_STATION_STATE_CHANGED,
/** WifiAccessPointState changed */
WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT,
/** WifiAccessPointState changed */
WIFI_EVENT_TYPE_ACCESS_POINT_STATE_CHANGED,
/** Started scanning for access points */
WIFI_EVENT_TYPE_SCAN_STARTED,
/** Finished scanning for access points */
WIFI_EVENT_TYPE_SCAN_FINISHED,
};
enum WifiStationConnectionError {
WIFI_STATION_CONNECTION_ERROR_NONE,
/** Wrong password */
WIFI_STATION_CONNECTION_ERROR_WRONG_CREDENTIALS,
/** Failed to connect in a timely manner */
WIFI_STATION_CONNECTION_ERROR_TIMEOUT,
/** SSID not found */
WIFI_STATION_CONNECTION_ERROR_TARGET_NOT_FOUND,
};
struct WifiEvent {
enum WifiEventType type;
union {
enum WifiRadioState radio_state;
enum WifiStationState station_state;
enum WifiAccessPointState access_point_state;
enum WifiStationConnectionError connection_error;
};
};
typedef void (*WifiEventCallback)(struct Device* device, void* callback_context, struct WifiEvent event);
struct WifiApi {
/**
* Get the radio state of the device.
* @param[in] device the wifi device
* @param[out] state the radio state
* @return ERROR_NONE on success
*/
error_t (*get_radio_state)(struct Device* device, enum WifiRadioState* state);
/**
* Get the station state of the device.
* @param[in] device the wifi device
* @param[out] state the station state
* @return ERROR_NONE on success
*/
error_t (*get_station_state)(struct Device* device, enum WifiStationState* state);
/**
* Get the access point state of the device.
* @param[in] device the wifi device
* @param[out] state the access point state
* @return ERROR_NONE on success
*/
error_t (*get_access_point_state)(struct Device* device, enum WifiAccessPointState* state);
/**
* Check if the device is currently scanning for access points.
* @param[in] device the wifi device
* @return true when scanning
*/
bool (*is_scanning)(struct Device* device);
/**
* Start a scan for access points.
* @param[in] device the wifi device
* @return ERROR_NONE on success
*/
error_t (*scan)(struct Device* device);
/**
* Get the scan results of the device.
* @param[in] device the wifi device
* @param[out] results the buffer to store the scan results
* @param[in, out] num_results the number of scan results: it's first used as input to determine the size of the buffer, and then as output to get the actual number of results
* @return ERROR_NONE on success
*/
error_t (*get_scan_results)(struct Device* device, struct WifiApRecord* results, size_t* num_results);
/**
* Get the IPv4 address of the device.
* @param[in] device the device
* @param[out] ipv4 the buffer to store the IPv4 address (must be at least 16 bytes, will be null-terminated)
* @return ERROR_NONE on success
*/
error_t (*station_get_ipv4_address)(struct Device* device, char* ipv4);
/**
* Get the SSID of the access point the device is currently connected to.
* @param[in] device the wifi device
* @param[out] ssid the buffer to store the SSID (must be at least 33 bytes, will be null-terminated)
* @return ERROR_NONE on success
*/
error_t (*station_get_target_ssid)(struct Device* device, char* ssid);
/**
* Connect to an access point.
* @param[in] device the wifi device
* @param[in] ssid the SSID of the access point
* @param[in] password the password of the access point
* @param[in] channel the Wi-Fi channel to connect to (0 means "any" / no preference)
* @return ERROR_NONE on success
*/
error_t (*station_connect)(struct Device* device, const char* ssid, const char* password, int32_t channel);
/**
* Disconnect from the current access point.
* @param[in] device the wifi device
* @return ERROR_NONE on success
*/
error_t (*station_disconnect)(struct Device* device);
/**
* Get the RSSI of the current access point.
* @param[in] device the wifi device
* @param[out] rssi the buffer to store the RSSI
* @return ERROR_NONE on success
*/
error_t (*station_get_rssi)(struct Device* device, int32_t* rssi);
/**
* Add a WifiEvent callback.
* @param[in] device the wifi device
* @param[in] callback_context the context to pass to the callback
* @param[in] callback the callback function
* @return ERROR_NONE on success
*/
error_t (*add_event_callback)(struct Device* device, void* callback_context, WifiEventCallback callback);
/**
* Remove a WifiEvent callback.
* @param[in] device the wifi device
* @param[in] callback the callback function
* @return ERROR_NONE on success
*/
error_t (*remove_event_callback)(struct Device* device, WifiEventCallback callback);
};
extern const struct DeviceType WIFI_TYPE;
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,106 @@
// 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;
/**
* @brief File system API.
*
* Provides a set of function pointers to interact with a specific file system implementation.
*/
struct FileSystemApi {
/**
* @brief Mounts the file system.
* @param[in] data file system private data
* @return ERROR_NONE on success, or an error code
*/
error_t (*mount)(void* data);
/**
* @brief Unmounts the file system.
* @param[in] data file system private data
* @return ERROR_NONE on success, or an error code
*/
error_t (*unmount)(void* data);
/**
* @brief Checks if the file system is mounted.
* @param[in] data file system private data
* @return true if mounted, false otherwise
*/
bool (*is_mounted)(void* data);
/**
* @brief Gets the mount path.
* @param[in] data file system private data
* @param[out] out_path buffer to store the path
* @param[in] out_path_size size of the output buffer
* @return ERROR_NONE on success, or an error code
*/
error_t (*get_path)(void* data, char* out_path, size_t out_path_size);
};
/**
* @brief Registers a new file system.
* @param[in] fs_api the file system API implementation
* @param[in] data private data for the file system
* @return the registered FileSystem object
*/
struct FileSystem* file_system_add(const struct FileSystemApi* fs_api, void* data);
/**
* @brief Removes a registered file system.
* @note The file system must be unmounted before removal.
* @param[in] fs the FileSystem object to remove
*/
void file_system_remove(struct FileSystem* fs);
/**
* @brief Iterates over all registered file systems.
* @param[in] callback_context context passed to the callback
* @param[in] callback function called for each file system. Return true to continue, false to stop.
*/
void file_system_for_each(void* callback_context, bool (*callback)(struct FileSystem* fs, void* context));
/**
* @brief Mounts the file system.
* @param[in] fs the FileSystem object
* @return ERROR_NONE on success, or an error code
*/
error_t file_system_mount(struct FileSystem* fs);
/**
* @warning Unmounting can fail (e.g. when the device is busy), so you might need to retry it.
* @brief Unmounts the file system.
* @param[in] fs the FileSystem object
* @return ERROR_NONE on success, or an error code
*/
error_t file_system_unmount(struct FileSystem* fs);
/**
* @brief Checks if the file system is mounted.
* @param[in] fs the FileSystem object
* @return true if mounted, false otherwise
*/
bool file_system_is_mounted(struct FileSystem* fs);
/**
* @brief Gets the mount path.
* @param[in] fs the FileSystem object
* @param[out] out_path buffer to store the path
* @param[in] out_path_size size of the output buffer
* @return ERROR_NONE on success, or an error code
*/
error_t file_system_get_path(struct FileSystem* fs, char* out_path, size_t out_path_size);
#ifdef __cplusplus
}
#endif

View File

@ -118,6 +118,7 @@ error_t module_construct_add_start(struct Module* module);
/** /**
* @brief Check if the module is started. * @brief Check if the module is started.
* Can be used when module isn't constructed yet.
* @param module module to check * @param module module to check
* @return true if the module is started, false otherwise * @return true if the module is started, false otherwise
*/ */

View File

@ -14,22 +14,25 @@
extern "C" { extern "C" {
struct GpioControllerData { struct GpioControllerData {
struct Mutex mutex {}; Mutex mutex {};
uint32_t pin_count; uint32_t pin_count;
struct GpioDescriptor* descriptors = nullptr; GpioDescriptor* descriptors = nullptr;
void* controller_context;
explicit GpioControllerData(uint32_t pin_count) : pin_count(pin_count) { explicit GpioControllerData(
uint32_t pin_count, void* controller_context
) : pin_count(pin_count), controller_context(controller_context) {
mutex_construct(&mutex); mutex_construct(&mutex);
} }
error_t init_descriptors(Device* device, void* controller_context) { error_t init_descriptors(Device* device) {
descriptors = (struct GpioDescriptor*)calloc(pin_count, sizeof(struct GpioDescriptor)); descriptors = static_cast<GpioDescriptor*>(calloc(pin_count, sizeof(GpioDescriptor)));
if (!descriptors) return ERROR_OUT_OF_MEMORY; if (!descriptors) return ERROR_OUT_OF_MEMORY;
for (uint32_t i = 0; i < pin_count; ++i) { for (uint32_t i = 0; i < pin_count; ++i) {
descriptors[i].controller = device; descriptors[i].controller = device;
descriptors[i].pin = (gpio_pin_t)i; descriptors[i].pin = static_cast<gpio_pin_t>(i);
descriptors[i].owner_type = GPIO_OWNER_NONE; descriptors[i].owner_type = GPIO_OWNER_NONE;
descriptors[i].controller_context = controller_context; descriptors[i].controller_context = this->controller_context;
} }
return ERROR_NONE; return ERROR_NONE;
} }
@ -42,14 +45,14 @@ struct GpioControllerData {
} }
}; };
struct GpioDescriptor* gpio_descriptor_acquire( GpioDescriptor* gpio_descriptor_acquire(
struct Device* controller, Device* controller,
gpio_pin_t pin_number, gpio_pin_t pin_number,
enum GpioOwnerType owner GpioOwnerType owner
) { ) {
check(owner != GPIO_OWNER_NONE); check(owner != GPIO_OWNER_NONE);
auto* data = (struct GpioControllerData*)device_get_driver_data(controller); auto* data = static_cast<struct GpioControllerData*>(device_get_driver_data(controller));
mutex_lock(&data->mutex); mutex_lock(&data->mutex);
if (pin_number >= data->pin_count) { if (pin_number >= data->pin_count) {
@ -57,7 +60,7 @@ struct GpioDescriptor* gpio_descriptor_acquire(
return nullptr; return nullptr;
} }
struct GpioDescriptor* desc = &data->descriptors[pin_number]; GpioDescriptor* desc = &data->descriptors[pin_number];
if (desc->owner_type != GPIO_OWNER_NONE) { if (desc->owner_type != GPIO_OWNER_NONE) {
mutex_unlock(&data->mutex); mutex_unlock(&data->mutex);
return nullptr; return nullptr;
@ -69,22 +72,27 @@ struct GpioDescriptor* gpio_descriptor_acquire(
return desc; return desc;
} }
error_t gpio_descriptor_release(struct GpioDescriptor* descriptor) { error_t gpio_descriptor_release(GpioDescriptor* descriptor) {
auto* data = static_cast<struct GpioControllerData*>(device_get_driver_data(descriptor->controller));
mutex_lock(&data->mutex);
descriptor->owner_type = GPIO_OWNER_NONE; descriptor->owner_type = GPIO_OWNER_NONE;
mutex_unlock(&data->mutex);
return ERROR_NONE; return ERROR_NONE;
} }
error_t gpio_controller_get_pin_count(struct Device* device, uint32_t* count) { error_t gpio_controller_get_pin_count(Device* device, uint32_t* count) {
auto* data = (struct GpioControllerData*)device_get_driver_data(device); auto* data = static_cast<struct GpioControllerData*>(device_get_driver_data(device));
mutex_lock(&data->mutex);
*count = data->pin_count; *count = data->pin_count;
mutex_unlock(&data->mutex);
return ERROR_NONE; return ERROR_NONE;
} }
error_t gpio_controller_init_descriptors(struct Device* device, uint32_t pin_count, void* controller_context) { error_t gpio_controller_init_descriptors(Device* device, uint32_t pin_count, void* controller_context) {
auto* data = new(std::nothrow) GpioControllerData(pin_count); auto* data = new(std::nothrow) GpioControllerData(pin_count, controller_context);
if (!data) return ERROR_OUT_OF_MEMORY; if (!data) return ERROR_OUT_OF_MEMORY;
if (data->init_descriptors(device, controller_context) != ERROR_NONE) { if (data->init_descriptors(device) != ERROR_NONE) {
delete data; delete data;
return ERROR_OUT_OF_MEMORY; return ERROR_OUT_OF_MEMORY;
} }
@ -93,49 +101,82 @@ error_t gpio_controller_init_descriptors(struct Device* device, uint32_t pin_cou
return ERROR_NONE; return ERROR_NONE;
} }
error_t gpio_controller_deinit_descriptors(struct Device* device) { error_t gpio_controller_deinit_descriptors(Device* device) {
auto* data = static_cast<struct GpioControllerData*>(device_get_driver_data(device)); auto* data = static_cast<struct GpioControllerData*>(device_get_driver_data(device));
delete data;
device_set_driver_data(device, nullptr); device_set_driver_data(device, nullptr);
delete data;
return ERROR_NONE; return ERROR_NONE;
} }
error_t gpio_descriptor_set_level(struct GpioDescriptor* descriptor, bool high) { void* gpio_controller_get_controller_context(Device* device) {
auto* data = static_cast<struct GpioControllerData*>(device_get_driver_data(device));
return data->controller_context;
}
error_t gpio_descriptor_set_level(GpioDescriptor* descriptor, bool high) {
const auto* driver = device_get_driver(descriptor->controller); const auto* driver = device_get_driver(descriptor->controller);
return GPIO_INTERNAL_API(driver)->set_level(descriptor, high); return GPIO_INTERNAL_API(driver)->set_level(descriptor, high);
} }
error_t gpio_descriptor_get_level(struct GpioDescriptor* descriptor, bool* high) { error_t gpio_descriptor_get_level(GpioDescriptor* descriptor, bool* high) {
const auto* driver = device_get_driver(descriptor->controller); const auto* driver = device_get_driver(descriptor->controller);
return GPIO_INTERNAL_API(driver)->get_level(descriptor, high); return GPIO_INTERNAL_API(driver)->get_level(descriptor, high);
} }
error_t gpio_descriptor_set_flags(struct GpioDescriptor* descriptor, gpio_flags_t flags) { error_t gpio_descriptor_set_flags(GpioDescriptor* descriptor, gpio_flags_t flags) {
const auto* driver = device_get_driver(descriptor->controller); const auto* driver = device_get_driver(descriptor->controller);
return GPIO_INTERNAL_API(driver)->set_flags(descriptor, flags); return GPIO_INTERNAL_API(driver)->set_flags(descriptor, flags);
} }
error_t gpio_descriptor_get_flags(struct GpioDescriptor* descriptor, gpio_flags_t* flags) { error_t gpio_descriptor_get_flags(GpioDescriptor* descriptor, gpio_flags_t* flags) {
const auto* driver = device_get_driver(descriptor->controller); const auto* driver = device_get_driver(descriptor->controller);
return GPIO_INTERNAL_API(driver)->get_flags(descriptor, flags); return GPIO_INTERNAL_API(driver)->get_flags(descriptor, flags);
} }
error_t gpio_descriptor_get_pin_number(struct GpioDescriptor* descriptor, gpio_pin_t* pin) { error_t gpio_descriptor_get_pin_number(GpioDescriptor* descriptor, gpio_pin_t* pin) {
*pin = descriptor->pin; *pin = descriptor->pin;
return ERROR_NONE; return ERROR_NONE;
} }
error_t gpio_descriptor_get_native_pin_number(struct GpioDescriptor* descriptor, void* pin_number) { error_t gpio_descriptor_get_native_pin_number(GpioDescriptor* descriptor, void* pin_number) {
const auto* driver = device_get_driver(descriptor->controller); const auto* driver = device_get_driver(descriptor->controller);
return GPIO_INTERNAL_API(driver)->get_native_pin_number(descriptor, pin_number); return GPIO_INTERNAL_API(driver)->get_native_pin_number(descriptor, pin_number);
} }
error_t gpio_descriptor_get_owner_type(struct GpioDescriptor* descriptor, GpioOwnerType* owner_type) { error_t gpio_descriptor_add_callback(GpioDescriptor* descriptor, void (*callback)(void*), void* arg) {
const auto* driver = device_get_driver(descriptor->controller);
auto* api = GPIO_INTERNAL_API(driver);
if (!api->add_callback) return ERROR_NOT_SUPPORTED;
return api->add_callback(descriptor, callback, arg);
}
error_t gpio_descriptor_remove_callback(GpioDescriptor* descriptor) {
const auto* driver = device_get_driver(descriptor->controller);
auto* api = GPIO_INTERNAL_API(driver);
if (!api->remove_callback) return ERROR_NOT_SUPPORTED;
return api->remove_callback(descriptor);
}
error_t gpio_descriptor_enable_interrupt(GpioDescriptor* descriptor) {
const auto* driver = device_get_driver(descriptor->controller);
auto* api = GPIO_INTERNAL_API(driver);
if (!api->enable_interrupt) return ERROR_NOT_SUPPORTED;
return api->enable_interrupt(descriptor);
}
error_t gpio_descriptor_disable_interrupt(GpioDescriptor* descriptor) {
const auto* driver = device_get_driver(descriptor->controller);
auto* api = GPIO_INTERNAL_API(driver);
if (!api->disable_interrupt) return ERROR_NOT_SUPPORTED;
return api->disable_interrupt(descriptor);
}
error_t gpio_descriptor_get_owner_type(GpioDescriptor* descriptor, GpioOwnerType* owner_type) {
*owner_type = descriptor->owner_type; *owner_type = descriptor->owner_type;
return ERROR_NONE; return ERROR_NONE;
} }
const struct DeviceType GPIO_CONTROLLER_TYPE { const DeviceType GPIO_CONTROLLER_TYPE {
.name = "gpio-controller" .name = "gpio-controller"
}; };

View File

@ -0,0 +1,93 @@
// SPDX-License-Identifier: Apache-2.0
#include <algorithm>
#include <tactility/concurrent/mutex.h>
#include <tactility/concurrent/recursive_mutex.h>
#include <tactility/filesystem/file_system.h>
#include <vector>
// Define the internal FileSystem structure
struct FileSystem {
const FileSystemApi* api;
void* data;
};
// Global list of file systems and its mutex
struct FileSystemsLedger {
std::vector<FileSystem*> file_systems;
// Use recursive mutex so that file_system_for_each() can lock within the callback
RecursiveMutex mutex {};
FileSystemsLedger() { recursive_mutex_construct(&mutex); }
~FileSystemsLedger() { recursive_mutex_destruct(&mutex); }
void lock() { recursive_mutex_lock(&mutex); }
bool is_locked() { return recursive_mutex_is_locked(&mutex); }
void unlock() { recursive_mutex_unlock(&mutex); }
};
static FileSystemsLedger& get_ledger() {
static FileSystemsLedger ledger;
return ledger;
}
extern "C" {
FileSystem* file_system_add(const FileSystemApi* fs_api, void* data) {
auto& ledger = get_ledger();
check(!ledger.is_locked()); // ensure file_system_for_each() doesn't add a filesystem while iterating
ledger.lock();
auto* fs = new(std::nothrow) struct FileSystem();
check(fs != nullptr);
fs->api = fs_api;
fs->data = data;
ledger.file_systems.push_back(fs);
ledger.unlock();
return fs;
}
void file_system_remove(FileSystem* fs) {
check(!file_system_is_mounted(fs));
auto& ledger = get_ledger();
check(!ledger.is_locked()); // ensure file_system_for_each() doesn't remove a filesystem while iterating
ledger.lock();
auto it = std::ranges::find(ledger.file_systems, fs);
if (it != ledger.file_systems.end()) {
ledger.file_systems.erase(it);
delete fs;
}
ledger.unlock();
}
void file_system_for_each(void* callback_context, bool (*callback)(FileSystem* fs, void* context)) {
auto& ledger = get_ledger();
ledger.lock();
for (auto* fs : ledger.file_systems) {
if (!callback(fs, callback_context)) break;
}
ledger.unlock();
}
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);
}
}

View File

@ -1,3 +1,7 @@
#include <tactility/concurrent/dispatcher.h>
#include <tactility/concurrent/event_group.h>
#include <tactility/concurrent/thread.h>
#include <tactility/concurrent/timer.h>
#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>
@ -6,14 +10,14 @@
#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/concurrent/dispatcher.h>
#include <tactility/concurrent/event_group.h>
#include <tactility/concurrent/thread.h>
#include <tactility/concurrent/timer.h>
#include <tactility/error.h> #include <tactility/error.h>
#include <tactility/log.h> #include <tactility/filesystem/file_system.h>
#include <tactility/module.h> #include <tactility/module.h>
#ifndef ESP_PLATFORM
#include <tactility/log.h>
#endif
/** /**
* This file is a C file instead of C++, so we can import all headers as C code. * This file is a C file instead of C++, so we can import all headers as C code.
* The intent is to catch errors that only show up when compiling as C and not as C++. * The intent is to catch errors that only show up when compiling as C and not as C++.
@ -153,6 +157,11 @@ 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),

View File

@ -12,8 +12,8 @@ struct ModuleInternal {
}; };
struct ModuleLedger { struct ModuleLedger {
std::vector<struct Module*> modules; std::vector<Module*> modules;
struct Mutex mutex {}; Mutex mutex {};
ModuleLedger() { mutex_construct(&mutex); } ModuleLedger() { mutex_construct(&mutex); }
~ModuleLedger() { mutex_destruct(&mutex); } ~ModuleLedger() { mutex_destruct(&mutex); }
@ -23,36 +23,37 @@ static ModuleLedger ledger;
extern "C" { extern "C" {
error_t module_construct(struct Module* module) { error_t module_construct(Module* module) {
module->internal = new (std::nothrow) ModuleInternal(); module->internal = new (std::nothrow) ModuleInternal();
if (module->internal == nullptr) return ERROR_OUT_OF_MEMORY; if (module->internal == nullptr) return ERROR_OUT_OF_MEMORY;
return ERROR_NONE; return ERROR_NONE;
} }
error_t module_destruct(struct Module* module) { error_t module_destruct(Module* module) {
delete static_cast<ModuleInternal*>(module->internal); delete static_cast<ModuleInternal*>(module->internal);
module->internal = nullptr; module->internal = nullptr;
return ERROR_NONE; return ERROR_NONE;
} }
error_t module_add(struct Module* module) { error_t module_add(Module* module) {
mutex_lock(&ledger.mutex); mutex_lock(&ledger.mutex);
ledger.modules.push_back(module); ledger.modules.push_back(module);
mutex_unlock(&ledger.mutex); mutex_unlock(&ledger.mutex);
return ERROR_NONE; return ERROR_NONE;
} }
error_t module_remove(struct Module* module) { error_t module_remove(Module* module) {
mutex_lock(&ledger.mutex); mutex_lock(&ledger.mutex);
ledger.modules.erase(std::remove(ledger.modules.begin(), ledger.modules.end(), module), ledger.modules.end()); ledger.modules.erase(std::remove(ledger.modules.begin(), ledger.modules.end(), module), ledger.modules.end());
mutex_unlock(&ledger.mutex); mutex_unlock(&ledger.mutex);
return ERROR_NONE; return ERROR_NONE;
} }
error_t module_start(struct Module* module) { error_t module_start(Module* module) {
LOG_I(TAG, "start %s", module->name); LOG_I(TAG, "start %s", module->name);
auto* internal = static_cast<ModuleInternal*>(module->internal); auto* internal = module->internal;
if (internal == nullptr) return ERROR_INVALID_STATE;
if (internal->started) return ERROR_NONE; if (internal->started) return ERROR_NONE;
error_t error = module->start(); error_t error = module->start();
@ -61,13 +62,15 @@ error_t module_start(struct Module* module) {
} }
bool module_is_started(struct Module* module) { bool module_is_started(struct Module* module) {
return static_cast<ModuleInternal*>(module->internal)->started; auto* internal = module->internal;
return internal != nullptr && internal->started;
} }
error_t module_stop(struct Module* module) { error_t module_stop(struct Module* module) {
LOG_I(TAG, "stop %s", module->name); LOG_I(TAG, "stop %s", module->name);
auto* internal = static_cast<ModuleInternal*>(module->internal); auto* internal = module->internal;
if (internal == nullptr) return ERROR_INVALID_STATE;
if (!internal->started) return ERROR_NONE; if (!internal->started) return ERROR_NONE;
error_t error = module->stop(); error_t error = module->stop();