Compare commits
No commits in common. "8ae18b208f8e56a47ea34597cc93a92856410f73" and "0f8380e8fea4a52f87cd95835ee25abf803f15bc" have entirely different histories.
8ae18b208f
...
0f8380e8fe
2
.gitignore
vendored
2
.gitignore
vendored
@ -16,5 +16,3 @@ sdkconfig.old
|
|||||||
managed_components/
|
managed_components/
|
||||||
dependencies.lock
|
dependencies.lock
|
||||||
|
|
||||||
.vscode/
|
|
||||||
|
|
||||||
|
|||||||
@ -19,9 +19,4 @@ dependencies:
|
|||||||
version: "1.7.6~1"
|
version: "1.7.6~1"
|
||||||
rules:
|
rules:
|
||||||
- if: "target == esp32s3"
|
- if: "target == esp32s3"
|
||||||
jgromes/radiolib:
|
|
||||||
version: "7.2.1"
|
|
||||||
rules:
|
|
||||||
- if: "target in [esp32s3, esp32p4]"
|
|
||||||
|
|
||||||
idf: '5.5'
|
idf: '5.5'
|
||||||
|
|||||||
@ -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 ILI934x XPT2046SoftSPI PwmBacklight driver vfs fatfs
|
REQUIRES Tactility esp_lvgl_port ILI934x XPT2046 PwmBacklight driver vfs fatfs
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
#include "CYD2432S028R.h"
|
#include "CYD2432S028R.h"
|
||||||
#include "hal/YellowDisplay.h"
|
#include "hal/YellowDisplay.h"
|
||||||
#include "hal/YellowConstants.h"
|
#include "hal/YellowConstants.h"
|
||||||
#include "hal/YellowSdCard.h"
|
|
||||||
#include <Tactility/lvgl/LvglSync.h>
|
#include <Tactility/lvgl/LvglSync.h>
|
||||||
#include <PwmBacklight.h>
|
#include <PwmBacklight.h>
|
||||||
#include <Tactility/hal/Configuration.h>
|
#include <Tactility/hal/Configuration.h>
|
||||||
@ -25,7 +24,7 @@ bool initBoot() {
|
|||||||
const Configuration cyd_2432s028r_config = {
|
const Configuration cyd_2432s028r_config = {
|
||||||
.initBoot = initBoot,
|
.initBoot = initBoot,
|
||||||
.createDisplay = createDisplay,
|
.createDisplay = createDisplay,
|
||||||
.sdcard = createYellowSdCard(),
|
.sdcard = nullptr,
|
||||||
.power = nullptr,
|
.power = nullptr,
|
||||||
.i2c = {},
|
.i2c = {},
|
||||||
.spi {
|
.spi {
|
||||||
@ -54,14 +53,14 @@ const Configuration cyd_2432s028r_config = {
|
|||||||
.lock = tt::lvgl::getSyncLock()
|
.lock = tt::lvgl::getSyncLock()
|
||||||
},
|
},
|
||||||
|
|
||||||
// SDCard
|
// Touch
|
||||||
spi::Configuration {
|
spi::Configuration {
|
||||||
.device = CYD2432S028R_SDCARD_SPI_HOST,
|
.device = CYD2432S028R_TOUCH_SPI_HOST,
|
||||||
.dma = SPI_DMA_CH_AUTO,
|
.dma = SPI_DMA_CH_AUTO,
|
||||||
.config = {
|
.config = {
|
||||||
.mosi_io_num = GPIO_NUM_23,
|
.mosi_io_num = GPIO_NUM_32,
|
||||||
.miso_io_num = GPIO_NUM_19,
|
.miso_io_num = GPIO_NUM_39,
|
||||||
.sclk_io_num = GPIO_NUM_18,
|
.sclk_io_num = GPIO_NUM_25,
|
||||||
.quadwp_io_num = GPIO_NUM_NC,
|
.quadwp_io_num = GPIO_NUM_NC,
|
||||||
.quadhd_io_num = GPIO_NUM_NC,
|
.quadhd_io_num = GPIO_NUM_NC,
|
||||||
.data4_io_num = GPIO_NUM_NC,
|
.data4_io_num = GPIO_NUM_NC,
|
||||||
|
|||||||
@ -16,16 +16,9 @@
|
|||||||
#define CYD2432S028R_TOUCH_SPI_HOST SPI3_HOST
|
#define CYD2432S028R_TOUCH_SPI_HOST SPI3_HOST
|
||||||
#define CYD2432S028R_TOUCH_PIN_CS GPIO_NUM_33
|
#define CYD2432S028R_TOUCH_PIN_CS GPIO_NUM_33
|
||||||
|
|
||||||
// Touch (Software SPI)
|
|
||||||
#define CYD_TOUCH_MISO_PIN GPIO_NUM_39
|
|
||||||
#define CYD_TOUCH_MOSI_PIN GPIO_NUM_32
|
|
||||||
#define CYD_TOUCH_SCK_PIN GPIO_NUM_25
|
|
||||||
#define CYD_TOUCH_CS_PIN GPIO_NUM_33
|
|
||||||
#define CYD_TOUCH_IRQ_PIN GPIO_NUM_36
|
|
||||||
|
|
||||||
// SDCard
|
// SDCard
|
||||||
#define CYD2432S028R_SDCARD_SPI_HOST SPI3_HOST
|
#define SDCARD_SPI_HOST SPI3_HOST
|
||||||
#define CYD2432S028R_SDCARD_PIN_CS GPIO_NUM_5
|
#define SDCARD_PIN_CS GPIO_NUM_5
|
||||||
|
|
||||||
// SPI Transfer
|
// SPI Transfer
|
||||||
#define CYD_SPI_TRANSFER_SIZE_LIMIT (CYD2432S028R_LCD_DRAW_BUFFER_SIZE * LV_COLOR_DEPTH / 8)
|
#define CYD_SPI_TRANSFER_SIZE_LIMIT (CYD2432S028R_LCD_DRAW_BUFFER_SIZE * LV_COLOR_DEPTH / 8)
|
||||||
|
|||||||
@ -1,39 +1,21 @@
|
|||||||
#include "YellowDisplay.h"
|
#include "YellowDisplay.h"
|
||||||
#include "Xpt2046SoftSpi.h"
|
#include "Xpt2046Touch.h"
|
||||||
#include "YellowConstants.h"
|
#include "YellowConstants.h"
|
||||||
#include <Ili934xDisplay.h>
|
#include <Ili934xDisplay.h>
|
||||||
#include <PwmBacklight.h>
|
#include <PwmBacklight.h>
|
||||||
|
|
||||||
static const char* TAG = "YellowDisplay";
|
|
||||||
|
|
||||||
// Global to hold reference (only needed if calling stop() later)
|
|
||||||
static std::unique_ptr<Xpt2046SoftSpi> touch;
|
|
||||||
|
|
||||||
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
|
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
|
||||||
auto configuration = std::make_unique<Xpt2046SoftSpi::Configuration>(
|
auto configuration = std::make_unique<Xpt2046Touch::Configuration>(
|
||||||
CYD_TOUCH_MOSI_PIN,
|
CYD2432S028R_TOUCH_SPI_HOST,
|
||||||
CYD_TOUCH_MISO_PIN,
|
CYD2432S028R_TOUCH_PIN_CS,
|
||||||
CYD_TOUCH_SCK_PIN,
|
240,
|
||||||
CYD_TOUCH_CS_PIN,
|
320,
|
||||||
CYD2432S028R_LCD_HORIZONTAL_RESOLUTION, // 240
|
false,
|
||||||
CYD2432S028R_LCD_VERTICAL_RESOLUTION, // 320
|
true,
|
||||||
false, // swapXY
|
false
|
||||||
true, // mirrorX
|
|
||||||
false // mirrorY
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Allocate the driver
|
return std::make_shared<Xpt2046Touch>(std::move(configuration));
|
||||||
touch = std::make_unique<Xpt2046SoftSpi>(std::move(configuration));
|
|
||||||
|
|
||||||
// Start the driver
|
|
||||||
if (!touch->start()) {
|
|
||||||
ESP_LOGE(TAG, "Touch driver start failed");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::shared_ptr<tt::hal::touch::TouchDevice>(touch.get(), [](tt::hal::touch::TouchDevice*) {
|
|
||||||
// No delete needed; `touch` is managed above
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
|
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
|
||||||
@ -46,9 +28,9 @@ std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
|
|||||||
CYD2432S028R_LCD_HORIZONTAL_RESOLUTION,
|
CYD2432S028R_LCD_HORIZONTAL_RESOLUTION,
|
||||||
CYD2432S028R_LCD_VERTICAL_RESOLUTION,
|
CYD2432S028R_LCD_VERTICAL_RESOLUTION,
|
||||||
touch,
|
touch,
|
||||||
false, // swapXY
|
false,
|
||||||
true, // mirrorX
|
true,
|
||||||
false, // mirrorY
|
false,
|
||||||
false,
|
false,
|
||||||
CYD2432S028R_LCD_DRAW_BUFFER_SIZE
|
CYD2432S028R_LCD_DRAW_BUFFER_SIZE
|
||||||
);
|
);
|
||||||
|
|||||||
@ -7,14 +7,14 @@ using tt::hal::sdcard::SpiSdCardDevice;
|
|||||||
|
|
||||||
std::shared_ptr<SdCardDevice> createYellowSdCard() {
|
std::shared_ptr<SdCardDevice> createYellowSdCard() {
|
||||||
auto* configuration = new SpiSdCardDevice::Config(
|
auto* configuration = new SpiSdCardDevice::Config(
|
||||||
CYD2432S028R_SDCARD_PIN_CS,
|
SDCARD_PIN_CS,
|
||||||
GPIO_NUM_NC,
|
GPIO_NUM_NC,
|
||||||
GPIO_NUM_NC,
|
GPIO_NUM_NC,
|
||||||
GPIO_NUM_NC,
|
GPIO_NUM_NC,
|
||||||
SdCardDevice::MountBehaviour::AtBoot,
|
SdCardDevice::MountBehaviour::AtBoot,
|
||||||
std::make_shared<tt::Mutex>(),
|
std::make_shared<tt::Mutex>(),
|
||||||
std::vector<gpio_num_t>(),
|
std::vector<gpio_num_t>(),
|
||||||
CYD2432S028R_SDCARD_SPI_HOST
|
SDCARD_SPI_HOST
|
||||||
);
|
);
|
||||||
|
|
||||||
auto* sdcard = (SdCardDevice*) new SpiSdCardDevice(
|
auto* sdcard = (SdCardDevice*) new SpiSdCardDevice(
|
||||||
|
|||||||
@ -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_lcd ST7796 BQ25896 BQ27220 TCA8418 DRV2605 SX126x PwmBacklight driver esp_adc
|
REQUIRES Tactility esp_lcd ST7796 BQ25896 BQ27220 TCA8418 DRV2605 PwmBacklight driver esp_adc
|
||||||
)
|
)
|
||||||
|
|||||||
@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
#include <Bq25896.h>
|
#include <Bq25896.h>
|
||||||
#include <Drv2605.h>
|
#include <Drv2605.h>
|
||||||
#include <Sx1262.h>
|
|
||||||
#include <Tactility/hal/Configuration.h>
|
#include <Tactility/hal/Configuration.h>
|
||||||
|
|
||||||
#define TPAGER_SPI_TRANSFER_SIZE_LIMIT (TPAGER_LCD_HORIZONTAL_RESOLUTION * TPAGER_LCD_SPI_TRANSFER_HEIGHT * (LV_COLOR_DEPTH / 8))
|
#define TPAGER_SPI_TRANSFER_SIZE_LIMIT (TPAGER_LCD_HORIZONTAL_RESOLUTION * TPAGER_LCD_SPI_TRANSFER_HEIGHT * (LV_COLOR_DEPTH / 8))
|
||||||
@ -24,17 +23,6 @@ DeviceVector createDevices() {
|
|||||||
auto tca8418 = std::make_shared<Tca8418>(I2C_NUM_0);
|
auto tca8418 = std::make_shared<Tca8418>(I2C_NUM_0);
|
||||||
auto keyboard = std::make_shared<TpagerKeyboard>(tca8418);
|
auto keyboard = std::make_shared<TpagerKeyboard>(tca8418);
|
||||||
|
|
||||||
auto sx1262 = std::make_shared<Sx1262>("SX1262", Sx1262::Configuration{
|
|
||||||
.spiHostDevice = SPI2_HOST,
|
|
||||||
.spiFrequency = 10'000'000,
|
|
||||||
.csPin = GPIO_NUM_36,
|
|
||||||
.resetPin = GPIO_NUM_47,
|
|
||||||
.busyPin = GPIO_NUM_48,
|
|
||||||
.irqPin = GPIO_NUM_14,
|
|
||||||
.tcxoVoltage = 3.0,
|
|
||||||
.useRegulatorLdo = false
|
|
||||||
});
|
|
||||||
|
|
||||||
return std::vector<std::shared_ptr<Device>> {
|
return std::vector<std::shared_ptr<Device>> {
|
||||||
tca8418,
|
tca8418,
|
||||||
std::make_shared<Bq25896>(I2C_NUM_0),
|
std::make_shared<Bq25896>(I2C_NUM_0),
|
||||||
@ -44,8 +32,7 @@ DeviceVector createDevices() {
|
|||||||
createTpagerSdCard(),
|
createTpagerSdCard(),
|
||||||
createDisplay(),
|
createDisplay(),
|
||||||
keyboard,
|
keyboard,
|
||||||
std::make_shared<TpagerEncoder>(),
|
std::make_shared<TpagerEncoder>()
|
||||||
sx1262
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +0,0 @@
|
|||||||
idf_component_register(
|
|
||||||
SRC_DIRS "Source"
|
|
||||||
INCLUDE_DIRS "Source"
|
|
||||||
REQUIRES Tactility radiolib
|
|
||||||
)
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
# RadioLibCompat
|
|
||||||
|
|
||||||
A set of helper classes to implement `RadioLib` drivers in Tactility.
|
|
||||||
@ -1,162 +0,0 @@
|
|||||||
#include "RadiolibTactilityHal.h"
|
|
||||||
|
|
||||||
#include "hal/gpio_hal.h"
|
|
||||||
#include "esp_timer.h"
|
|
||||||
|
|
||||||
constexpr const char* TAG = "RadiolibTactilityHal";
|
|
||||||
|
|
||||||
void RadiolibTactilityHal::init() {
|
|
||||||
// we only need to init the SPI here
|
|
||||||
spiBegin();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RadiolibTactilityHal::term() {
|
|
||||||
// we only need to stop the SPI here
|
|
||||||
spiEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RadiolibTactilityHal::pinMode(uint32_t pin, uint32_t mode) {
|
|
||||||
if(pin == RADIOLIB_NC) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gpio_hal_context_t gpiohal;
|
|
||||||
gpiohal.dev = GPIO_LL_GET_HW(GPIO_PORT_0);
|
|
||||||
|
|
||||||
gpio_config_t conf = {
|
|
||||||
.pin_bit_mask = (1ULL<<pin),
|
|
||||||
.mode = (gpio_mode_t)mode,
|
|
||||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
|
||||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
|
||||||
.intr_type = (gpio_int_type_t)gpiohal.dev->pin[pin].int_type,
|
|
||||||
};
|
|
||||||
gpio_config(&conf);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RadiolibTactilityHal::digitalWrite(uint32_t pin, uint32_t value) {
|
|
||||||
if(pin == RADIOLIB_NC) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gpio_set_level((gpio_num_t)pin, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t RadiolibTactilityHal::digitalRead(uint32_t pin) {
|
|
||||||
if(pin == RADIOLIB_NC) {
|
|
||||||
return(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return(gpio_get_level((gpio_num_t)pin));
|
|
||||||
}
|
|
||||||
|
|
||||||
void RadiolibTactilityHal::attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) {
|
|
||||||
if(interruptNum == RADIOLIB_NC) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gpio_install_isr_service((int)ESP_INTR_FLAG_IRAM);
|
|
||||||
gpio_set_intr_type((gpio_num_t)interruptNum, (gpio_int_type_t)(mode & 0x7));
|
|
||||||
|
|
||||||
// this uses function typecasting, which is not defined when the functions have different signatures
|
|
||||||
// untested and might not work
|
|
||||||
gpio_isr_handler_add((gpio_num_t)interruptNum, (void (*)(void*))interruptCb, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RadiolibTactilityHal::detachInterrupt(uint32_t interruptNum) {
|
|
||||||
if(interruptNum == RADIOLIB_NC) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gpio_isr_handler_remove((gpio_num_t)interruptNum);
|
|
||||||
gpio_wakeup_disable((gpio_num_t)interruptNum);
|
|
||||||
gpio_set_intr_type((gpio_num_t)interruptNum, GPIO_INTR_DISABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RadiolibTactilityHal::delay(unsigned long ms) {
|
|
||||||
vTaskDelay(ms / portTICK_PERIOD_MS);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RadiolibTactilityHal::delayMicroseconds(unsigned long us) {
|
|
||||||
uint64_t m = (uint64_t)esp_timer_get_time();
|
|
||||||
if(us) {
|
|
||||||
uint64_t e = (m + us);
|
|
||||||
if(m > e) { // overflow
|
|
||||||
while((uint64_t)esp_timer_get_time() > e);
|
|
||||||
}
|
|
||||||
while((uint64_t)esp_timer_get_time() < e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long RadiolibTactilityHal::millis() {
|
|
||||||
return((unsigned long)(esp_timer_get_time() / 1000ULL));
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long RadiolibTactilityHal::micros() {
|
|
||||||
return((unsigned long)(esp_timer_get_time()));
|
|
||||||
}
|
|
||||||
|
|
||||||
long RadiolibTactilityHal::pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) {
|
|
||||||
if(pin == RADIOLIB_NC) {
|
|
||||||
return(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->pinMode(pin, GPIO_MODE_INPUT);
|
|
||||||
uint32_t start = this->micros();
|
|
||||||
uint32_t curtick = this->micros();
|
|
||||||
|
|
||||||
while(this->digitalRead(pin) == state) {
|
|
||||||
if((this->micros() - curtick) > timeout) {
|
|
||||||
return(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return(this->micros() - start);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RadiolibTactilityHal::spiBegin() {
|
|
||||||
if (!spiInitialized) {
|
|
||||||
TT_LOG_I(TAG, "SPI Begin!");
|
|
||||||
spi_device_interface_config_t devcfg = {};
|
|
||||||
devcfg.clock_speed_hz = spiFrequency;
|
|
||||||
devcfg.mode = 0;
|
|
||||||
devcfg.spics_io_num = csPin;
|
|
||||||
devcfg.queue_size = 1;
|
|
||||||
esp_err_t ret = spi_bus_add_device(spiHostDevice, &devcfg, &spiDeviceHandle);
|
|
||||||
if (ret != ESP_OK) {
|
|
||||||
TT_LOG_E(TAG, "Failed to add SPI device: %s", esp_err_to_name(ret));
|
|
||||||
}
|
|
||||||
spiInitialized = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RadiolibTactilityHal::spiBeginTransaction() {
|
|
||||||
// This function is used to set up the transaction (speed, bit order, mode, ...).
|
|
||||||
// With the ESP-IDF HAL this is automatically done, so no code needed.
|
|
||||||
}
|
|
||||||
|
|
||||||
void RadiolibTactilityHal::spiTransfer(uint8_t* out, size_t len, uint8_t* in) {
|
|
||||||
spi_transaction_t t;
|
|
||||||
|
|
||||||
auto lock = getLock()->asScopedLock();
|
|
||||||
bool locked = lock.lock(portMAX_DELAY);
|
|
||||||
if (!locked) {
|
|
||||||
TT_LOG_E(TAG, "Failed to aquire SPI lock");
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(&t, 0, sizeof(t)); // Zero out the transaction
|
|
||||||
t.length = len * 8; // Length is in bits
|
|
||||||
t.tx_buffer = out; // The data to send
|
|
||||||
t.rx_buffer = in; // The data to receive
|
|
||||||
spi_device_polling_transmit(spiDeviceHandle, &t);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RadiolibTactilityHal::spiEndTransaction() {
|
|
||||||
// nothing needs to be done here
|
|
||||||
}
|
|
||||||
|
|
||||||
void RadiolibTactilityHal::spiEnd() {
|
|
||||||
if (spiInitialized) {
|
|
||||||
spi_bus_remove_device(spiDeviceHandle);
|
|
||||||
spiInitialized = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Tactility/Lock.h>
|
|
||||||
#include <Tactility/hal/spi/Spi.h>
|
|
||||||
|
|
||||||
#include <freertos/FreeRTOS.h>
|
|
||||||
#include <freertos/task.h>
|
|
||||||
|
|
||||||
#include <RadioLib.h>
|
|
||||||
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
#include <driver/spi_master.h>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
class RadiolibTactilityHal : public RadioLibHal {
|
|
||||||
private:
|
|
||||||
spi_host_device_t spiHostDevice;
|
|
||||||
int spiFrequency;
|
|
||||||
gpio_num_t csPin;
|
|
||||||
spi_device_handle_t spiDeviceHandle;
|
|
||||||
std::shared_ptr<tt::Lock> lock;
|
|
||||||
bool spiInitialized;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit RadiolibTactilityHal(spi_host_device_t spiHostDevice, int spiFrequency, gpio_num_t csPin, std::shared_ptr<tt::Lock> spiLock)
|
|
||||||
: RadioLibHal(
|
|
||||||
GPIO_MODE_INPUT,
|
|
||||||
GPIO_MODE_OUTPUT,
|
|
||||||
0, // LOW
|
|
||||||
1, // HIGH
|
|
||||||
GPIO_INTR_POSEDGE,
|
|
||||||
GPIO_INTR_NEGEDGE)
|
|
||||||
, spiHostDevice(spiHostDevice)
|
|
||||||
, spiFrequency(spiFrequency)
|
|
||||||
, csPin(csPin)
|
|
||||||
, lock(spiLock)
|
|
||||||
, spiInitialized(false) {
|
|
||||||
if (!lock) lock = tt::hal::spi::getLock(spiHostDevice);
|
|
||||||
}
|
|
||||||
|
|
||||||
void init() override;
|
|
||||||
void term() override;
|
|
||||||
|
|
||||||
void pinMode(uint32_t pin, uint32_t mode) override;
|
|
||||||
void digitalWrite(uint32_t pin, uint32_t value) override;
|
|
||||||
uint32_t digitalRead(uint32_t pin) override;
|
|
||||||
void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override;
|
|
||||||
void detachInterrupt(uint32_t interruptNum) override;
|
|
||||||
|
|
||||||
void delay(unsigned long ms) override;
|
|
||||||
void delayMicroseconds(unsigned long us) override;
|
|
||||||
unsigned long millis() override;
|
|
||||||
unsigned long micros() override;
|
|
||||||
long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override;
|
|
||||||
|
|
||||||
void spiBegin() override;
|
|
||||||
void spiBeginTransaction() override;
|
|
||||||
void spiTransfer(uint8_t* out, size_t len, uint8_t* in) override;
|
|
||||||
void spiEndTransaction() override;
|
|
||||||
void spiEnd();
|
|
||||||
|
|
||||||
std::shared_ptr<tt::Lock> getLock() const { return lock; }
|
|
||||||
};
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
idf_component_register(
|
|
||||||
SRC_DIRS "Source"
|
|
||||||
INCLUDE_DIRS "Source"
|
|
||||||
REQUIRES Tactility driver RadioLibCompat radiolib
|
|
||||||
)
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
# SX126x
|
|
||||||
|
|
||||||
Radio with LoRa/(G)FSK capabilities.
|
|
||||||
|
|
||||||
## SX1262
|
|
||||||
|
|
||||||
- [Product Information](https://www.semtech.com/products/wireless-rf/lora-connect/sx1262)
|
|
||||||
@ -1,236 +0,0 @@
|
|||||||
#include "Sx1262.h"
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include "hal/gpio_hal.h"
|
|
||||||
|
|
||||||
constexpr const char* TAG = "Sx1262";
|
|
||||||
|
|
||||||
void IRAM_ATTR dio1handler(void* context) {
|
|
||||||
((Sx1262*)context)->dio1Event();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Sx1262::configure(const Parameter parameter, const float value) {
|
|
||||||
using enum Parameter;
|
|
||||||
|
|
||||||
switch (parameter) {
|
|
||||||
case Power:
|
|
||||||
power = value;
|
|
||||||
TT_LOG_I(TAG, "Configure %s=%d", toString(parameter), power);
|
|
||||||
return true;
|
|
||||||
case Frequency:
|
|
||||||
frequency = value;
|
|
||||||
TT_LOG_I(TAG, "Configure %s=%f", toString(parameter), frequency);
|
|
||||||
return true;
|
|
||||||
case Bandwidth:
|
|
||||||
bandwidth = value;
|
|
||||||
TT_LOG_I(TAG, "Configure %s=%f", toString(parameter), bandwidth);
|
|
||||||
return true;
|
|
||||||
case SpreadFactor:
|
|
||||||
spreadFactor = value;
|
|
||||||
TT_LOG_I(TAG, "Configure %s=%d", toString(parameter), spreadFactor);
|
|
||||||
return true;
|
|
||||||
case CodingRate:
|
|
||||||
codingRate = value;
|
|
||||||
TT_LOG_I(TAG, "Configure %s=%d", toString(parameter), codingRate);
|
|
||||||
return true;
|
|
||||||
case SyncWord:
|
|
||||||
syncWord = value;
|
|
||||||
TT_LOG_I(TAG, "Configure %s=%X", toString(parameter), syncWord);
|
|
||||||
return true;
|
|
||||||
case PreambleLength:
|
|
||||||
preambleLength = value;
|
|
||||||
TT_LOG_I(TAG, "Configure %s=%d", toString(parameter), preambleLength);
|
|
||||||
return true;
|
|
||||||
case DataRate:
|
|
||||||
bitRate = value;
|
|
||||||
return true;
|
|
||||||
case FrequencyDeviation:
|
|
||||||
frequencyDeviation = value;
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Sx1262::registerDio1Isr() {
|
|
||||||
hal.pinMode(GPIO_NUM_9, GPIO_MODE_OUTPUT);
|
|
||||||
gpio_set_level(GPIO_NUM_9, 0);
|
|
||||||
|
|
||||||
gpio_hal_context_t gpiohal;
|
|
||||||
gpiohal.dev = GPIO_LL_GET_HW(GPIO_PORT_0);
|
|
||||||
|
|
||||||
gpio_config_t conf = {
|
|
||||||
.pin_bit_mask = (1ULL<<configuration.irqPin),
|
|
||||||
.mode = (gpio_mode_t)GPIO_MODE_INPUT,
|
|
||||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
|
||||||
.pull_down_en = GPIO_PULLDOWN_ENABLE,
|
|
||||||
.intr_type = (gpio_int_type_t)gpiohal.dev->pin[configuration.irqPin].int_type,
|
|
||||||
};
|
|
||||||
gpio_config(&conf);
|
|
||||||
|
|
||||||
// We cannot use the RadioLib API to register this action,
|
|
||||||
// as it does not have the capability to pass an instance pointer via context.
|
|
||||||
// A trampoline has been tried, but is not linkable to be in IRAM_ATTR (dangerous relocation).
|
|
||||||
gpio_install_isr_service((int)ESP_INTR_FLAG_IRAM);
|
|
||||||
gpio_set_intr_type(configuration.irqPin, GPIO_INTR_POSEDGE);
|
|
||||||
gpio_isr_handler_add(configuration.irqPin, dio1handler, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Sx1262::unregisterDio1Isr() {
|
|
||||||
gpio_isr_handler_remove(configuration.irqPin);
|
|
||||||
gpio_wakeup_disable(configuration.irqPin);
|
|
||||||
gpio_set_intr_type(configuration.irqPin, GPIO_INTR_DISABLE);
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
void IRAM_ATTR Sx1262::dio1Event() {
|
|
||||||
static const auto DRAM_ATTR rxbit = RADIO_RECEIVED_BIT;
|
|
||||||
static const auto DRAM_ATTR txbit = RADIO_TRANSMITTED_BIT;
|
|
||||||
|
|
||||||
switch (exchangeState) {
|
|
||||||
case ExchangeState::Receive:
|
|
||||||
getEventFlag().set(rxbit);
|
|
||||||
break;
|
|
||||||
case ExchangeState::TransmitWaiting:
|
|
||||||
getEventFlag().set(txbit);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
gpio_set_level(GPIO_NUM_9, 1);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
|
||||||
void IRAM_ATTR Sx1262::dio1Event() {
|
|
||||||
static const auto DRAM_ATTR bit = SX1262_DIO1_EVENT_BIT;
|
|
||||||
getEventFlag().set(bit);
|
|
||||||
|
|
||||||
gpio_set_level(GPIO_NUM_9, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t Sx1262::threadMain(const Modulation modulation) {
|
|
||||||
using enum ExchangeState;
|
|
||||||
|
|
||||||
uint16_t rc = RADIOLIB_ERR_NONE;
|
|
||||||
|
|
||||||
if (modulation == Modulation::LoRa) {
|
|
||||||
rc = radio.begin(
|
|
||||||
frequency,
|
|
||||||
bandwidth,
|
|
||||||
spreadFactor,
|
|
||||||
codingRate,
|
|
||||||
syncWord,
|
|
||||||
power,
|
|
||||||
preambleLength,
|
|
||||||
configuration.tcxoVoltage,
|
|
||||||
configuration.useRegulatorLdo
|
|
||||||
);
|
|
||||||
/*
|
|
||||||
radio.forceLDRO(false);
|
|
||||||
radio.setCRC(true);
|
|
||||||
radio.invertIQ(false);
|
|
||||||
radio.setWhitening(true, 0x00FF);
|
|
||||||
radio.explicitHeader();*/
|
|
||||||
|
|
||||||
} else if (modulation == Modulation::Fsk) {
|
|
||||||
rc = radio.beginFSK(
|
|
||||||
frequency,
|
|
||||||
bitRate,
|
|
||||||
frequencyDeviation,
|
|
||||||
bandwidth,
|
|
||||||
power,
|
|
||||||
preambleLength,
|
|
||||||
configuration.tcxoVoltage,
|
|
||||||
configuration.useRegulatorLdo
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
TT_LOG_E(TAG, "SX1262 not capable of modulation \"%s\"", toString(modulation));
|
|
||||||
setState(State::Error);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rc != RADIOLIB_ERR_NONE) {
|
|
||||||
TT_LOG_E(TAG, "Radiolib init failed with code %hi", rc);
|
|
||||||
setState(State::Error);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
registerDio1Isr();
|
|
||||||
setState(State::On);
|
|
||||||
|
|
||||||
TT_LOG_I(TAG, "SX1262 device ready to receive!");
|
|
||||||
exchangeState = Receive;
|
|
||||||
|
|
||||||
while (!isThreadInterrupted()) {
|
|
||||||
radio.startReceive();
|
|
||||||
TT_LOG_I(TAG, "WAIT FLAG");
|
|
||||||
auto eventFlags = getEventFlag().wait(RADIO_TERMINATE_BIT | SX1262_DIO1_EVENT_BIT | RADIO_TRANSMIT_QUEUED_BIT);
|
|
||||||
TT_LOG_W(TAG, "Event, flag=%X", eventFlags);
|
|
||||||
|
|
||||||
// Thread might've been interrupted in the meanwhile
|
|
||||||
if (isThreadInterrupted()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((eventFlags & RADIO_TRANSMIT_QUEUED_BIT) && (getTxQueueSize() > 0)) {
|
|
||||||
currentTx = popNextQueuedTx();
|
|
||||||
radio.standby();
|
|
||||||
uint16_t rc = radio.startTransmit(currentTx.packet.data.data(), currentTx.packet.data.size());
|
|
||||||
if (rc == RADIOLIB_ERR_NONE) {
|
|
||||||
exchangeState = TransmitWaiting;
|
|
||||||
currentTx.callback(currentTx.id, TransmissionState::PendingTransmit);
|
|
||||||
|
|
||||||
TT_LOG_I(TAG, "WAIT TX FLAG");
|
|
||||||
auto txEventFlags = getEventFlag().wait(RADIO_TERMINATE_BIT | SX1262_DIO1_EVENT_BIT, tt::EventFlag::WaitAny, pdMS_TO_TICKS(2000));
|
|
||||||
TT_LOG_W(TAG, "Event, flag=%X", txEventFlags);
|
|
||||||
|
|
||||||
// Thread might've been interrupted in the meanwhile
|
|
||||||
if (isThreadInterrupted()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (txEventFlags & SX1262_DIO1_EVENT_BIT) {
|
|
||||||
currentTx.callback(currentTx.id, TransmissionState::Transmitted);
|
|
||||||
} else {
|
|
||||||
currentTx.callback(currentTx.id, TransmissionState::Timeout);
|
|
||||||
}
|
|
||||||
exchangeState = Receive;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
TT_LOG_E(TAG, "Error transmitting id=%d, rc=%hi", currentTx.id, rc);
|
|
||||||
currentTx.callback(currentTx.id, TransmissionState::Error);
|
|
||||||
exchangeState = Receive;
|
|
||||||
}
|
|
||||||
} else if (eventFlags & SX1262_DIO1_EVENT_BIT) {
|
|
||||||
uint16_t rxSize = radio.getPacketLength(true);
|
|
||||||
std::vector<uint8_t> data(rxSize);
|
|
||||||
uint16_t rc = radio.readData(data.data(), rxSize);
|
|
||||||
if (rc != RADIOLIB_ERR_NONE) {
|
|
||||||
TT_LOG_E(TAG, "Error receiving data, RadioLib returned %hi", rc);
|
|
||||||
} else if(rxSize == 0) {
|
|
||||||
//TT_LOG_W(TAG, "Received data length 0");
|
|
||||||
} else {
|
|
||||||
float rssi = radio.getRSSI();
|
|
||||||
float snr = radio.getSNR();
|
|
||||||
TT_LOG_I(TAG, "LoRa RX size=%d RSSI=%f SNR=%f", rxSize, rssi, snr);
|
|
||||||
auto rxPacket = tt::hal::radio::RxPacket {
|
|
||||||
.data = data,
|
|
||||||
.rssi = rssi,
|
|
||||||
.snr = snr
|
|
||||||
};
|
|
||||||
|
|
||||||
publishRx(rxPacket);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A delay is needed before a new command
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(100));
|
|
||||||
gpio_set_level(GPIO_NUM_9, 0);
|
|
||||||
} else {
|
|
||||||
TT_LOG_W(TAG, "Unhandled event, flag=%X", eventFlags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unregisterDio1Isr();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Tactility/hal/radio/RadioDevice.h>
|
|
||||||
#include <Tactility/hal/spi/Spi.h>
|
|
||||||
|
|
||||||
#include <RadioLib.h>
|
|
||||||
#include "RadiolibTactilityHal.h"
|
|
||||||
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
class Sx1262 final : public tt::hal::radio::RadioDevice {
|
|
||||||
|
|
||||||
public:
|
|
||||||
struct Configuration {
|
|
||||||
spi_host_device_t spiHostDevice;
|
|
||||||
int spiFrequency;
|
|
||||||
gpio_num_t csPin;
|
|
||||||
gpio_num_t resetPin;
|
|
||||||
gpio_num_t busyPin;
|
|
||||||
gpio_num_t irqPin;
|
|
||||||
float tcxoVoltage;
|
|
||||||
bool useRegulatorLdo;
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
static constexpr auto SX1262_DIO1_EVENT_BIT = BIT8;
|
|
||||||
|
|
||||||
enum class ExchangeState {
|
|
||||||
Idle,
|
|
||||||
Receive,
|
|
||||||
//TransmitInitiated,
|
|
||||||
TransmitWaiting
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string name;
|
|
||||||
const Configuration configuration;
|
|
||||||
std::shared_ptr<tt::Lock> lock;
|
|
||||||
RadiolibTactilityHal hal;
|
|
||||||
Module radioModule;
|
|
||||||
SX1262 radio;
|
|
||||||
TxItem currentTx;
|
|
||||||
ExchangeState exchangeState;
|
|
||||||
|
|
||||||
int8_t power = 0;
|
|
||||||
float frequency = 0.0;
|
|
||||||
float bandwidth = 0.0;
|
|
||||||
uint8_t spreadFactor = 0.0;
|
|
||||||
uint8_t codingRate = 0;
|
|
||||||
uint8_t syncWord = 0;
|
|
||||||
uint16_t preambleLength = 0;
|
|
||||||
float bitRate = 0.0;
|
|
||||||
float frequencyDeviation = 0.0;
|
|
||||||
|
|
||||||
int32_t threadMain();
|
|
||||||
void registerDio1Isr();
|
|
||||||
void unregisterDio1Isr();
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
explicit Sx1262(const std::string& name, const Configuration& configuration, std::shared_ptr<tt::Lock> lock = nullptr)
|
|
||||||
: RadioDevice(name, 4096)
|
|
||||||
, name(name)
|
|
||||||
, configuration(configuration)
|
|
||||||
, hal(configuration.spiHostDevice, configuration.spiFrequency, configuration.csPin, lock)
|
|
||||||
, radioModule(&hal, configuration.csPin, configuration.irqPin, configuration.resetPin, configuration.busyPin)
|
|
||||||
, radio(&radioModule)
|
|
||||||
, exchangeState(ExchangeState::Idle) {}
|
|
||||||
|
|
||||||
~Sx1262() override = default;
|
|
||||||
|
|
||||||
std::string getName() const override { return name; }
|
|
||||||
|
|
||||||
std::string getDescription() const override { return "SX1262 LoRa and FSK capable radio"; }
|
|
||||||
|
|
||||||
bool configure(const Parameter parameter, const float value) override;
|
|
||||||
|
|
||||||
bool isCapableOf(const Modulation modulation) {
|
|
||||||
return (modulation == Modulation::Fsk) || (modulation == Modulation::LoRa);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasReceived();
|
|
||||||
|
|
||||||
void dio1Event();
|
|
||||||
//void IRAM_ATTR setRxEvent() { rxFlag = true; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
int32_t threadMain(const Modulation modulation) override;
|
|
||||||
};
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
idf_component_register(
|
|
||||||
SRC_DIRS "Source"
|
|
||||||
INCLUDE_DIRS "Source"
|
|
||||||
REQUIRES Tactility driver esp_lvgl_port
|
|
||||||
)
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
# XPT2046_SoftSPI Driver
|
|
||||||
|
|
||||||
XPT2046_SoftSPI is a driver for the XPT2046 resistive touchscreen controller that uses SoftSPI.
|
|
||||||
Inspiration from: https://github.com/ddxfish/XPT2046_Bitbang_Arduino_Library/
|
|
||||||
@ -1,371 +0,0 @@
|
|||||||
#include "Xpt2046SoftSpi.h"
|
|
||||||
|
|
||||||
#include <Tactility/Log.h>
|
|
||||||
#include <Tactility/lvgl/LvglSync.h>
|
|
||||||
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
#include <esp_err.h>
|
|
||||||
#include <esp_lvgl_port.h>
|
|
||||||
#include <freertos/FreeRTOS.h>
|
|
||||||
#include <freertos/task.h>
|
|
||||||
#include <inttypes.h>
|
|
||||||
#include <nvs.h>
|
|
||||||
#include <nvs_flash.h>
|
|
||||||
#include <rom/ets_sys.h>
|
|
||||||
|
|
||||||
#define TAG "Xpt2046SoftSpi"
|
|
||||||
|
|
||||||
#define RERUN_CALIBRATE false
|
|
||||||
#define CMD_READ_Y 0x90 // Try different commands if these don't work
|
|
||||||
#define CMD_READ_X 0xD0 // Alternative: 0x98 for Y, 0xD8 for X
|
|
||||||
|
|
||||||
struct Calibration {
|
|
||||||
int xMin;
|
|
||||||
int xMax;
|
|
||||||
int yMin;
|
|
||||||
int yMax;
|
|
||||||
};
|
|
||||||
|
|
||||||
Calibration cal = {
|
|
||||||
.xMin = 100,
|
|
||||||
.xMax = 1900,
|
|
||||||
.yMin = 100,
|
|
||||||
.yMax = 1900
|
|
||||||
};
|
|
||||||
|
|
||||||
Xpt2046SoftSpi* Xpt2046SoftSpi::instance = nullptr;
|
|
||||||
|
|
||||||
Xpt2046SoftSpi::Xpt2046SoftSpi(std::unique_ptr<Configuration> inConfiguration)
|
|
||||||
: configuration(std::move(inConfiguration)) {
|
|
||||||
assert(configuration != nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defensive check for NVS, put here just in case NVS is init after touch setup.
|
|
||||||
static void ensureNvsInitialized() {
|
|
||||||
static bool initialized = false;
|
|
||||||
if (initialized) return;
|
|
||||||
|
|
||||||
esp_err_t result = nvs_flash_init();
|
|
||||||
if (result == ESP_ERR_NVS_NO_FREE_PAGES || result == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
|
||||||
nvs_flash_erase(); // ignore error for safety
|
|
||||||
result = nvs_flash_init();
|
|
||||||
}
|
|
||||||
|
|
||||||
initialized = (result == ESP_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Xpt2046SoftSpi::start(lv_display_t* display) {
|
|
||||||
ensureNvsInitialized();
|
|
||||||
|
|
||||||
TT_LOG_I(TAG, "Starting Xpt2046SoftSpi touch driver");
|
|
||||||
|
|
||||||
// Configure GPIO pins
|
|
||||||
gpio_config_t io_conf = {};
|
|
||||||
|
|
||||||
// Configure MOSI, CLK, CS as outputs
|
|
||||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
|
||||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
|
||||||
io_conf.pin_bit_mask = (1ULL << configuration->mosiPin) |
|
|
||||||
(1ULL << configuration->clkPin) |
|
|
||||||
(1ULL << configuration->csPin);
|
|
||||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
|
||||||
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
|
|
||||||
|
|
||||||
if (gpio_config(&io_conf) != ESP_OK) {
|
|
||||||
TT_LOG_E(TAG, "Failed to configure output pins");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure MISO as input
|
|
||||||
io_conf.mode = GPIO_MODE_INPUT;
|
|
||||||
io_conf.pin_bit_mask = (1ULL << configuration->misoPin);
|
|
||||||
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
|
||||||
|
|
||||||
if (gpio_config(&io_conf) != ESP_OK) {
|
|
||||||
TT_LOG_E(TAG, "Failed to configure input pin");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize pin states
|
|
||||||
gpio_set_level(configuration->csPin, 1); // CS high
|
|
||||||
gpio_set_level(configuration->clkPin, 0); // CLK low
|
|
||||||
gpio_set_level(configuration->mosiPin, 0); // MOSI low
|
|
||||||
|
|
||||||
TT_LOG_I(TAG, "GPIO configured: MOSI=%d, MISO=%d, CLK=%d, CS=%d", configuration->mosiPin, configuration->misoPin, configuration->clkPin, configuration->csPin);
|
|
||||||
|
|
||||||
// Load or perform calibration
|
|
||||||
bool calibrationValid = true; //loadCalibration() && !RERUN_CALIBRATE;
|
|
||||||
if (calibrationValid) {
|
|
||||||
// Check if calibration values are valid (xMin != xMax, yMin != yMax)
|
|
||||||
if (cal.xMin == cal.xMax || cal.yMin == cal.yMax) {
|
|
||||||
TT_LOG_W(TAG, "Invalid calibration detected: xMin=%d, xMax=%d, yMin=%d, yMax=%d", cal.xMin, cal.xMax, cal.yMin, cal.yMax);
|
|
||||||
calibrationValid = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!calibrationValid) {
|
|
||||||
TT_LOG_W(TAG, "Calibration data not found, invalid, or forced recalibration");
|
|
||||||
calibrate();
|
|
||||||
saveCalibration();
|
|
||||||
} else {
|
|
||||||
TT_LOG_I(TAG, "Loaded calibration: xMin=%d, yMin=%d, xMax=%d, yMax=%d", cal.xMin, cal.yMin, cal.xMax, cal.yMax);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create LVGL input device
|
|
||||||
deviceHandle = lv_indev_create();
|
|
||||||
if (!deviceHandle) {
|
|
||||||
TT_LOG_E(TAG, "Failed to create LVGL input device");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
lv_indev_set_type(deviceHandle, LV_INDEV_TYPE_POINTER);
|
|
||||||
lv_indev_set_read_cb(deviceHandle, touchReadCallback);
|
|
||||||
lv_indev_set_user_data(deviceHandle, this);
|
|
||||||
|
|
||||||
instance = this;
|
|
||||||
TT_LOG_I(TAG, "Xpt2046SoftSpi touch driver started successfully");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Xpt2046SoftSpi::stop() {
|
|
||||||
TT_LOG_I(TAG, "Stopping Xpt2046SoftSpi touch driver");
|
|
||||||
instance = nullptr;
|
|
||||||
cleanup();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Xpt2046SoftSpi::cleanup() {
|
|
||||||
if (deviceHandle != nullptr) {
|
|
||||||
lv_indev_delete(deviceHandle);
|
|
||||||
deviceHandle = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int Xpt2046SoftSpi::readSPI(uint8_t command) {
|
|
||||||
int result = 0;
|
|
||||||
|
|
||||||
// Pull CS low for this transaction
|
|
||||||
gpio_set_level(configuration->csPin, 0);
|
|
||||||
ets_delay_us(1);
|
|
||||||
|
|
||||||
// Send 8-bit command
|
|
||||||
for (int i = 7; i >= 0; i--) {
|
|
||||||
gpio_set_level(configuration->mosiPin, command & (1 << i));
|
|
||||||
gpio_set_level(configuration->clkPin, 1);
|
|
||||||
ets_delay_us(1);
|
|
||||||
gpio_set_level(configuration->clkPin, 0);
|
|
||||||
ets_delay_us(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 11; i >= 0; i--) {
|
|
||||||
gpio_set_level(configuration->clkPin, 1);
|
|
||||||
ets_delay_us(1);
|
|
||||||
if (gpio_get_level(configuration->misoPin)) {
|
|
||||||
result |= (1 << i);
|
|
||||||
}
|
|
||||||
gpio_set_level(configuration->clkPin, 0);
|
|
||||||
ets_delay_us(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pull CS high for this transaction
|
|
||||||
gpio_set_level(configuration->csPin, 1);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Xpt2046SoftSpi::calibrate() {
|
|
||||||
const int samples = 8; // More samples for better accuracy
|
|
||||||
|
|
||||||
TT_LOG_I(TAG, "Calibration starting...");
|
|
||||||
|
|
||||||
TT_LOG_I(TAG, "Touch TOP-LEFT corner");
|
|
||||||
|
|
||||||
while (!isTouched()) {
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(50));
|
|
||||||
}
|
|
||||||
|
|
||||||
int sumX = 0, sumY = 0;
|
|
||||||
for (int i = 0; i < samples; i++) {
|
|
||||||
sumX += readSPI(CMD_READ_X);
|
|
||||||
sumY += readSPI(CMD_READ_Y);
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(10));
|
|
||||||
}
|
|
||||||
cal.xMin = sumX / samples;
|
|
||||||
cal.yMin = sumY / samples;
|
|
||||||
|
|
||||||
TT_LOG_I(TAG, "Top-left calibrated: xMin=%d, yMin=%d", cal.xMin, cal.yMin);
|
|
||||||
|
|
||||||
TT_LOG_I(TAG, "Touch BOTTOM-RIGHT corner");
|
|
||||||
|
|
||||||
while (!isTouched()) {
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(50));
|
|
||||||
}
|
|
||||||
|
|
||||||
sumX = sumY = 0;
|
|
||||||
for (int i = 0; i < samples; i++) {
|
|
||||||
sumX += readSPI(CMD_READ_X);
|
|
||||||
sumY += readSPI(CMD_READ_Y);
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(10));
|
|
||||||
}
|
|
||||||
cal.xMax = sumX / samples;
|
|
||||||
cal.yMax = sumY / samples;
|
|
||||||
|
|
||||||
TT_LOG_I(TAG, "Bottom-right calibrated: xMax=%d, yMax=%d", cal.xMax, cal.yMax);
|
|
||||||
|
|
||||||
TT_LOG_I(TAG, "Calibration completed! xMin=%d, yMin=%d, xMax=%d, yMax=%d", cal.xMin, cal.yMin, cal.xMax, cal.yMax);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Xpt2046SoftSpi::loadCalibration() {
|
|
||||||
TT_LOG_W(TAG, "Calibration load disabled (using fresh calibration only).");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Xpt2046SoftSpi::saveCalibration() {
|
|
||||||
nvs_handle_t handle;
|
|
||||||
esp_err_t err = nvs_open("xpt2046", NVS_READWRITE, &handle);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
TT_LOG_E(TAG, "Failed to open NVS for writing (%s)", esp_err_to_name(err));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = nvs_set_blob(handle, "cal", &cal, sizeof(cal));
|
|
||||||
if (err == ESP_OK) {
|
|
||||||
nvs_commit(handle);
|
|
||||||
TT_LOG_I(TAG, "Calibration saved to NVS");
|
|
||||||
} else {
|
|
||||||
TT_LOG_E(TAG, "Failed to write calibration data to NVS (%s)", esp_err_to_name(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
nvs_close(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Xpt2046SoftSpi::setCalibration(int xMin, int yMin, int xMax, int yMax) {
|
|
||||||
cal.xMin = xMin;
|
|
||||||
cal.yMin = yMin;
|
|
||||||
cal.xMax = xMax;
|
|
||||||
cal.yMax = yMax;
|
|
||||||
TT_LOG_I(TAG, "Manual calibration set: xMin=%d, yMin=%d, xMax=%d, yMax=%d", xMin, yMin, xMax, yMax);
|
|
||||||
}
|
|
||||||
|
|
||||||
Point Xpt2046SoftSpi::getTouch() {
|
|
||||||
|
|
||||||
const int samples = 8; // More samples for better accuracy
|
|
||||||
int totalX = 0, totalY = 0;
|
|
||||||
int validSamples = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < samples; i++) {
|
|
||||||
int rawX = readSPI(CMD_READ_X);
|
|
||||||
int rawY = readSPI(CMD_READ_Y);
|
|
||||||
|
|
||||||
// Only use valid readings
|
|
||||||
if (rawX > 100 && rawX < 3900 && rawY > 100 && rawY < 3900) {
|
|
||||||
totalX += rawX;
|
|
||||||
totalY += rawY;
|
|
||||||
validSamples++;
|
|
||||||
}
|
|
||||||
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validSamples == 0) {
|
|
||||||
return Point {0, 0};
|
|
||||||
}
|
|
||||||
|
|
||||||
int rawX = totalX / validSamples;
|
|
||||||
int rawY = totalY / validSamples;
|
|
||||||
|
|
||||||
const int xRange = cal.xMax - cal.xMin;
|
|
||||||
const int yRange = cal.yMax - cal.yMin;
|
|
||||||
|
|
||||||
if (xRange <= 0 || yRange <= 0) {
|
|
||||||
TT_LOG_W(TAG, "Invalid calibration: xRange=%d, yRange=%d", xRange, yRange);
|
|
||||||
return Point {0, 0};
|
|
||||||
}
|
|
||||||
|
|
||||||
int x = (rawX - cal.xMin) * configuration->xMax / xRange;
|
|
||||||
int y = (rawY - cal.yMin) * configuration->yMax / yRange;
|
|
||||||
|
|
||||||
if (configuration->swapXy) std::swap(x, y);
|
|
||||||
if (configuration->mirrorX) x = configuration->xMax - x;
|
|
||||||
if (configuration->mirrorY) y = configuration->yMax - y;
|
|
||||||
|
|
||||||
x = std::clamp(x, 0, (int)configuration->xMax);
|
|
||||||
y = std::clamp(y, 0, (int)configuration->yMax);
|
|
||||||
|
|
||||||
return Point {x, y};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Xpt2046SoftSpi::isTouched() {
|
|
||||||
const int samples = 3;
|
|
||||||
int xTotal = 0, yTotal = 0;
|
|
||||||
int validSamples = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < samples; i++) {
|
|
||||||
int x = readSPI(CMD_READ_X);
|
|
||||||
int y = readSPI(CMD_READ_Y);
|
|
||||||
|
|
||||||
// Basic validity check - XPT2046 typically returns values in range 100-3900 when touched
|
|
||||||
if (x > 100 && x < 3900 && y > 100 && y < 3900) {
|
|
||||||
xTotal += x;
|
|
||||||
yTotal += y;
|
|
||||||
validSamples++;
|
|
||||||
}
|
|
||||||
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(1)); // Small delay between samples
|
|
||||||
}
|
|
||||||
gpio_set_level(configuration->csPin, 1);
|
|
||||||
|
|
||||||
// Consider touched if we got valid readings
|
|
||||||
bool touched = validSamples >= 2;
|
|
||||||
|
|
||||||
// Debug logging (remove this once working)
|
|
||||||
if (touched) {
|
|
||||||
TT_LOG_I(TAG, "Touch detected: validSamples=%d, avgX=%d, avgY=%d", validSamples, xTotal / validSamples, yTotal / validSamples);
|
|
||||||
}
|
|
||||||
|
|
||||||
return touched;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Xpt2046SoftSpi::touchReadCallback(lv_indev_t* indev, lv_indev_data_t* data) {
|
|
||||||
Xpt2046SoftSpi* touch = static_cast<Xpt2046SoftSpi*>(lv_indev_get_user_data(indev));
|
|
||||||
|
|
||||||
if (touch && touch->isTouched()) {
|
|
||||||
Point point = touch->getTouch();
|
|
||||||
data->point.x = point.x;
|
|
||||||
data->point.y = point.y;
|
|
||||||
data->state = LV_INDEV_STATE_PRESSED;
|
|
||||||
} else {
|
|
||||||
data->state = LV_INDEV_STATE_RELEASED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zero-argument start
|
|
||||||
bool Xpt2046SoftSpi::start() {
|
|
||||||
// Default to LVGL-less startup if needed
|
|
||||||
return startLvgl(nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Whether this device supports LVGL
|
|
||||||
bool Xpt2046SoftSpi::supportsLvgl() const {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start with LVGL display
|
|
||||||
bool Xpt2046SoftSpi::startLvgl(lv_display_t* display) {
|
|
||||||
return start(display);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop LVGL
|
|
||||||
bool Xpt2046SoftSpi::stopLvgl() {
|
|
||||||
cleanup();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Supports a separate touch driver? Yes/No
|
|
||||||
bool Xpt2046SoftSpi::supportsTouchDriver() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return driver instance if any
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDriver> Xpt2046SoftSpi::getTouchDriver() {
|
|
||||||
return nullptr; // replace with actual driver later
|
|
||||||
}
|
|
||||||
@ -1,101 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Tactility/hal/touch/TouchDevice.h"
|
|
||||||
#include "Tactility/hal/touch/TouchDriver.h"
|
|
||||||
#include <Tactility/TactilityCore.h>
|
|
||||||
#include "lvgl.h"
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
#include <esp_err.h>
|
|
||||||
#include <freertos/FreeRTOS.h>
|
|
||||||
#include <freertos/task.h>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#ifndef TFT_WIDTH
|
|
||||||
#define TFT_WIDTH 240
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef TFT_HEIGHT
|
|
||||||
#define TFT_HEIGHT 320
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct Point {
|
|
||||||
int x;
|
|
||||||
int y;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Xpt2046SoftSpi : public tt::hal::touch::TouchDevice {
|
|
||||||
public:
|
|
||||||
class Configuration {
|
|
||||||
public:
|
|
||||||
Configuration(
|
|
||||||
gpio_num_t mosiPin,
|
|
||||||
gpio_num_t misoPin,
|
|
||||||
gpio_num_t clkPin,
|
|
||||||
gpio_num_t csPin,
|
|
||||||
uint16_t xMax = TFT_WIDTH,
|
|
||||||
uint16_t yMax = TFT_HEIGHT,
|
|
||||||
bool swapXy = false,
|
|
||||||
bool mirrorX = false,
|
|
||||||
bool mirrorY = false
|
|
||||||
) : mosiPin(mosiPin),
|
|
||||||
misoPin(misoPin),
|
|
||||||
clkPin(clkPin),
|
|
||||||
csPin(csPin),
|
|
||||||
xMax(xMax),
|
|
||||||
yMax(yMax),
|
|
||||||
swapXy(swapXy),
|
|
||||||
mirrorX(mirrorX),
|
|
||||||
mirrorY(mirrorY)
|
|
||||||
{}
|
|
||||||
|
|
||||||
gpio_num_t mosiPin;
|
|
||||||
gpio_num_t misoPin;
|
|
||||||
gpio_num_t clkPin;
|
|
||||||
gpio_num_t csPin;
|
|
||||||
uint16_t xMax;
|
|
||||||
uint16_t yMax;
|
|
||||||
bool swapXy;
|
|
||||||
bool mirrorX;
|
|
||||||
bool mirrorY;
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
static Xpt2046SoftSpi* instance;
|
|
||||||
std::unique_ptr<Configuration> configuration;
|
|
||||||
lv_indev_t* deviceHandle = nullptr;
|
|
||||||
|
|
||||||
int readSPI(uint8_t command);
|
|
||||||
void cleanup();
|
|
||||||
bool loadCalibration();
|
|
||||||
void saveCalibration();
|
|
||||||
static void touchReadCallback(lv_indev_t* indev, lv_indev_data_t* data);
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit Xpt2046SoftSpi(std::unique_ptr<Configuration> inConfiguration);
|
|
||||||
|
|
||||||
// TouchDevice interface
|
|
||||||
std::string getName() const final { return "Xpt2046SoftSpi"; }
|
|
||||||
std::string getDescription() const final { return "Xpt2046 Soft SPI touch driver"; }
|
|
||||||
|
|
||||||
bool start() override; // zero-arg start
|
|
||||||
bool supportsLvgl() const override;
|
|
||||||
bool startLvgl(lv_display_t* display) override;
|
|
||||||
bool stopLvgl() override;
|
|
||||||
bool stop() override;
|
|
||||||
bool supportsTouchDriver() override;
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDriver> getTouchDriver() override;
|
|
||||||
lv_indev_t* getLvglIndev() override { return deviceHandle; }
|
|
||||||
|
|
||||||
// Original LVGL-specific start
|
|
||||||
bool start(lv_display_t* display);
|
|
||||||
|
|
||||||
// XPT2046-specific methods
|
|
||||||
Point getTouch();
|
|
||||||
void calibrate();
|
|
||||||
void setCalibration(int xMin, int yMin, int xMax, int yMax);
|
|
||||||
bool isTouched();
|
|
||||||
|
|
||||||
// Static instance access
|
|
||||||
static Xpt2046SoftSpi* getInstance() { return instance; }
|
|
||||||
};
|
|
||||||
@ -22,8 +22,7 @@ public:
|
|||||||
Keyboard,
|
Keyboard,
|
||||||
Encoder,
|
Encoder,
|
||||||
Power,
|
Power,
|
||||||
Gps,
|
Gps
|
||||||
Radio
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef uint32_t Id;
|
typedef uint32_t Id;
|
||||||
|
|||||||
@ -1,41 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "RadioDevice.h"
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
namespace tt::hal::radio {
|
|
||||||
|
|
||||||
class ParameterSet {
|
|
||||||
private:
|
|
||||||
using Map = std::unordered_map<RadioDevice::Parameter, float, std::hash<int> >;
|
|
||||||
Map parameters;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit ParameterSet() {}
|
|
||||||
explicit ParameterSet(const ParameterSet& other) { parameters = other.parameters; }
|
|
||||||
~ParameterSet() override = default;
|
|
||||||
|
|
||||||
float get(const RadioDevice::Parameter parameter) { return parameters[parameter]; }
|
|
||||||
void set(const RadioDevice::Parameter parameter, const float value) { parameters[parameter] = value; }
|
|
||||||
bool has(const RadioDevice::Parameter parameter) { return parameters.contains(parameter); }
|
|
||||||
bool erase(const RadioDevice::Parameter parameter) {
|
|
||||||
if (has(parameter)) {
|
|
||||||
parameters.erase(parameter);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool apply(RadioDevice &radio) {
|
|
||||||
bool successful = true;
|
|
||||||
for (const auto& [parameter, value] : parameters) {
|
|
||||||
// No break on error chosen to apply all parameters,
|
|
||||||
// a bad one doesn't make the successive tries any more invalid
|
|
||||||
successful &= radio.configure(parameter, value);
|
|
||||||
}
|
|
||||||
return successful;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,175 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Device.h"
|
|
||||||
|
|
||||||
#include <Tactility/EventFlag.h>
|
|
||||||
#include <Tactility/Lock.h>
|
|
||||||
#include <Tactility/Mutex.h>
|
|
||||||
#include <Tactility/Thread.h>
|
|
||||||
|
|
||||||
#include <deque>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
namespace tt::hal::radio {
|
|
||||||
|
|
||||||
struct RxPacket {
|
|
||||||
std::vector<uint8_t> data;
|
|
||||||
float rssi;
|
|
||||||
float snr;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TxPacket {
|
|
||||||
std::vector<uint8_t> data;
|
|
||||||
uint32_t address; // FSK only
|
|
||||||
};
|
|
||||||
|
|
||||||
class RadioDevice : public Device {
|
|
||||||
|
|
||||||
public:
|
|
||||||
enum class Modulation {
|
|
||||||
Fsk,
|
|
||||||
LoRa,
|
|
||||||
LrFhss
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class Parameter {
|
|
||||||
Power,
|
|
||||||
Frequency,
|
|
||||||
Bandwidth,
|
|
||||||
SpreadFactor,
|
|
||||||
CodingRate,
|
|
||||||
SyncWord,
|
|
||||||
PreambleLength,
|
|
||||||
FrequencyDeviation,
|
|
||||||
DataRate,
|
|
||||||
AddressWidth
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef int RxSubscriptionId;
|
|
||||||
typedef int TxId;
|
|
||||||
|
|
||||||
|
|
||||||
enum class State {
|
|
||||||
PendingOn,
|
|
||||||
On,
|
|
||||||
Error,
|
|
||||||
PendingOff,
|
|
||||||
Off
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class TransmissionState {
|
|
||||||
Queued,
|
|
||||||
PendingTransmit,
|
|
||||||
Transmitted,
|
|
||||||
Timeout,
|
|
||||||
Error
|
|
||||||
};
|
|
||||||
|
|
||||||
using TxStateCallback = std::function<void(TxId id, TransmissionState state)>;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
struct TxItem {
|
|
||||||
TxId id;
|
|
||||||
TxPacket packet;
|
|
||||||
TxStateCallback callback;
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct RxSubscription {
|
|
||||||
RxSubscriptionId id;
|
|
||||||
std::shared_ptr<std::function<void(Device::Id id, const RxPacket&)>> onData;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string threadName;
|
|
||||||
size_t threadSize;
|
|
||||||
State state;
|
|
||||||
EventFlag events;
|
|
||||||
Mutex mutex = Mutex(Mutex::Type::Recursive);
|
|
||||||
std::unique_ptr<Thread> _Nullable thread;
|
|
||||||
bool threadInterrupted = false;
|
|
||||||
std::vector<RxSubscription> rxSubscriptions;
|
|
||||||
std::deque<TxItem> txQueue;
|
|
||||||
TxId lastTxId = 0;
|
|
||||||
RxSubscriptionId lastRxSubscriptionId = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
static constexpr auto RADIO_TERMINATE_BIT = BIT0;
|
|
||||||
static constexpr auto RADIO_TRANSMIT_QUEUED_BIT = BIT1;
|
|
||||||
static constexpr auto RADIO_RECEIVED_BIT = BIT2;
|
|
||||||
static constexpr auto RADIO_TRANSMITTED_BIT = BIT3;
|
|
||||||
|
|
||||||
virtual int32_t threadMain(const Modulation modulation) = 0;
|
|
||||||
Mutex &getMutex() { return mutex; }
|
|
||||||
EventFlag &getEventFlag() { return events; }
|
|
||||||
bool isThreadInterrupted() const;
|
|
||||||
void setState(State newState);
|
|
||||||
|
|
||||||
size_t getTxQueueSize() const {
|
|
||||||
auto lock = mutex.asScopedLock();
|
|
||||||
lock.lock();
|
|
||||||
const auto size = txQueue.size();
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
TxItem popNextQueuedTx() {
|
|
||||||
auto lock = mutex.asScopedLock();
|
|
||||||
lock.lock();
|
|
||||||
|
|
||||||
auto tx = std::move(txQueue.front());
|
|
||||||
txQueue.pop_front();
|
|
||||||
|
|
||||||
return tx;
|
|
||||||
}
|
|
||||||
|
|
||||||
void publishRx(const RxPacket& packet);
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit RadioDevice(const std::string& threadName, const size_t threadSize)
|
|
||||||
: threadName(threadName)
|
|
||||||
, threadSize(threadSize)
|
|
||||||
, state(State::Off) {}
|
|
||||||
|
|
||||||
~RadioDevice() override = default;
|
|
||||||
|
|
||||||
Type getType() const override { return Type::Radio; }
|
|
||||||
|
|
||||||
virtual bool configure(const Parameter parameter, const float value) = 0;
|
|
||||||
virtual bool isCapableOf(const Modulation modulation) = 0;
|
|
||||||
|
|
||||||
bool start(const Modulation modulation);
|
|
||||||
bool stop();
|
|
||||||
|
|
||||||
TxId transmit(const TxPacket& packet, TxStateCallback callback) {
|
|
||||||
auto lock = mutex.asScopedLock();
|
|
||||||
lock.lock();
|
|
||||||
const auto txId = lastTxId;
|
|
||||||
txQueue.push_back(TxItem{.id = txId, .packet = packet, .callback = callback});
|
|
||||||
callback(txId, TransmissionState::Queued);
|
|
||||||
lastTxId++;
|
|
||||||
getEventFlag().set(RADIO_TRANSMIT_QUEUED_BIT);
|
|
||||||
return txId;
|
|
||||||
}
|
|
||||||
|
|
||||||
RxSubscriptionId subscribeRx(const std::function<void(Device::Id id, const RxPacket&)>& onData) {
|
|
||||||
auto lock = mutex.asScopedLock();
|
|
||||||
lock.lock();
|
|
||||||
rxSubscriptions.push_back({
|
|
||||||
.id = ++lastRxSubscriptionId,
|
|
||||||
.onData = std::make_shared<std::function<void(Device::Id, const RxPacket&)>>(onData)
|
|
||||||
});
|
|
||||||
return lastRxSubscriptionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void unsubscribeRx(RxSubscriptionId subscriptionId) {
|
|
||||||
auto lock = mutex.asScopedLock();
|
|
||||||
lock.lock();
|
|
||||||
std::erase_if(rxSubscriptions, [subscriptionId](auto& subscription) { return subscription.id == subscriptionId; });
|
|
||||||
}
|
|
||||||
|
|
||||||
State getState() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
const char* toString(RadioDevice::Modulation modulation);
|
|
||||||
const char* toString(RadioDevice::Parameter parameter);
|
|
||||||
}
|
|
||||||
@ -1,858 +0,0 @@
|
|||||||
//#ifdef ESP_PLATFORM
|
|
||||||
|
|
||||||
#include <Tactility/app/AppManifest.h>
|
|
||||||
#include <Tactility/lvgl/Toolbar.h>
|
|
||||||
#include <Tactility/Assets.h>
|
|
||||||
#include <Tactility/StringUtils.h>
|
|
||||||
#include <Tactility/hal/radio/RadioDevice.h>
|
|
||||||
|
|
||||||
#include "Tactility/lvgl/LvglSync.h"
|
|
||||||
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstring>
|
|
||||||
#include <vector>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <ctime>
|
|
||||||
#include <sstream>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cctype>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <sstream>
|
|
||||||
#include <vector>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <lvgl.h>
|
|
||||||
|
|
||||||
extern const lv_obj_class_t lv_label_class;
|
|
||||||
|
|
||||||
namespace tt::app::chirp {
|
|
||||||
|
|
||||||
constexpr const char* TAG = "ChirpChatterApp";
|
|
||||||
|
|
||||||
enum CCViews
|
|
||||||
{
|
|
||||||
CCView_Msgs,
|
|
||||||
CCView_LoraSettings,
|
|
||||||
CCView_ProtoSettings
|
|
||||||
};
|
|
||||||
|
|
||||||
class ChirpChatterApp;
|
|
||||||
|
|
||||||
template<CCViews view>
|
|
||||||
static void changeViewHandler(lv_event_t* e) {
|
|
||||||
auto* self = static_cast<ChirpChatterApp*>(lv_event_get_user_data(e));
|
|
||||||
TT_LOG_I(TAG, "Clicked %d", view);
|
|
||||||
self->changeView(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void buttonRecolorFocus(lv_event_t *event) {
|
|
||||||
lv_obj_t *image = (lv_obj_t *)lv_event_get_user_data(event);
|
|
||||||
if (image != NULL) {
|
|
||||||
lv_obj_set_style_image_recolor(image, lv_palette_main(LV_PALETTE_YELLOW), LV_STATE_DEFAULT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void buttonRecolorDefocus(lv_event_t *event) {
|
|
||||||
lv_obj_t *image = (lv_obj_t *)lv_event_get_user_data(event);
|
|
||||||
if (image != NULL) {
|
|
||||||
lv_obj_set_style_image_recolor(image, lv_theme_get_color_primary(image), LV_STATE_DEFAULT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void debugFocus(lv_event_t *event) {
|
|
||||||
lv_obj_t *target = (lv_obj_t *)lv_event_get_current_target(event);
|
|
||||||
if (target != NULL) {
|
|
||||||
lv_obj_set_style_bg_color(target, lv_color_hex(0xFF0000), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void debugDefocus(lv_event_t *event) {
|
|
||||||
lv_obj_t *target = (lv_obj_t *)lv_event_get_current_target(event);
|
|
||||||
if (target != NULL) {
|
|
||||||
lv_obj_set_style_bg_color(target, lv_color_hex(0x00FF00), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isPrintableData(const std::vector<uint8_t>& data) {
|
|
||||||
return std::all_of(data.begin(), data.end(),
|
|
||||||
[](uint8_t byte) {
|
|
||||||
char c = static_cast<char>(byte);
|
|
||||||
return std::isprint(static_cast<unsigned char>(c));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string hexdump(const std::vector<uint8_t>& data) {
|
|
||||||
std::ostringstream oss;
|
|
||||||
for (size_t i = 0; i < data.size(); ++i) {
|
|
||||||
oss << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
|
|
||||||
<< static_cast<unsigned>(data[i]);
|
|
||||||
if (i + 1 != data.size()) oss << ' ';
|
|
||||||
}
|
|
||||||
return oss.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class LoraView {
|
|
||||||
public:
|
|
||||||
using DeviceActivationCallback = std::function<void(std::shared_ptr<tt::hal::radio::RadioDevice>)>;
|
|
||||||
private:
|
|
||||||
//using LoraParameters = tt::hal::lora::LoraParameters;
|
|
||||||
|
|
||||||
std::vector<std::string> loraDevNames;
|
|
||||||
std::vector<std::shared_ptr<tt::hal::radio::RadioDevice>> loraDevs;
|
|
||||||
std::shared_ptr<tt::hal::radio::RadioDevice> loraDevice;
|
|
||||||
|
|
||||||
DeviceActivationCallback cbDevActive;
|
|
||||||
DeviceActivationCallback cbDevInactive;
|
|
||||||
|
|
||||||
void queryLoraDevs() {
|
|
||||||
auto radios = tt::hal::findDevices<tt::hal::radio::RadioDevice>(tt::hal::Device::Type::Radio);
|
|
||||||
loraDevNames.clear();
|
|
||||||
loraDevs.clear();
|
|
||||||
|
|
||||||
for (const auto& radio: radios) {
|
|
||||||
if (radio->isCapableOf(tt::hal::radio::RadioDevice::Modulation::LoRa)) {
|
|
||||||
loraDevNames.push_back(radio->getName());
|
|
||||||
loraDevs.push_back(radio);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lv_obj_t* initDropdownInput(int row, const char* const label, const char* const items) {
|
|
||||||
lv_obj_t* label_obj = lv_label_create(container);
|
|
||||||
lv_label_set_text(label_obj, label);
|
|
||||||
lv_obj_set_grid_cell(label_obj,
|
|
||||||
LV_GRID_ALIGN_STRETCH, 0, 1,
|
|
||||||
LV_GRID_ALIGN_CENTER, row, 1);
|
|
||||||
lv_obj_set_size(label_obj, lv_pct(100), LV_SIZE_CONTENT);
|
|
||||||
|
|
||||||
lv_obj_t* input = lv_dropdown_create(container);
|
|
||||||
lv_obj_set_grid_cell(input,
|
|
||||||
LV_GRID_ALIGN_STRETCH, 1, 1,
|
|
||||||
LV_GRID_ALIGN_CENTER, row, 1);
|
|
||||||
lv_obj_set_size(input, lv_pct(100), LV_SIZE_CONTENT);
|
|
||||||
lv_dropdown_set_options(input, items);
|
|
||||||
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
lv_obj_t* initFormInput(int row, const char* const label, const char* const defval, const char* const unit) {
|
|
||||||
lv_obj_t* label_obj = lv_label_create(container);
|
|
||||||
lv_label_set_text(label_obj, label);
|
|
||||||
lv_obj_set_grid_cell(label_obj,
|
|
||||||
LV_GRID_ALIGN_STRETCH, 0, 1,
|
|
||||||
LV_GRID_ALIGN_CENTER, row, 1);
|
|
||||||
lv_obj_set_size(label_obj, lv_pct(100), LV_SIZE_CONTENT);
|
|
||||||
|
|
||||||
lv_obj_t* input = lv_textarea_create(container);
|
|
||||||
lv_obj_set_grid_cell(input,
|
|
||||||
LV_GRID_ALIGN_STRETCH, 1, 1,
|
|
||||||
LV_GRID_ALIGN_CENTER, row, 1);
|
|
||||||
lv_obj_set_size(input, lv_pct(100), LV_SIZE_CONTENT);
|
|
||||||
lv_textarea_set_text(input, defval);
|
|
||||||
lv_textarea_set_one_line(input, true);
|
|
||||||
|
|
||||||
lv_obj_t* unit_obj = lv_label_create(container);
|
|
||||||
lv_label_set_text(unit_obj, unit);
|
|
||||||
lv_obj_set_grid_cell(unit_obj,
|
|
||||||
LV_GRID_ALIGN_STRETCH, 2, 1,
|
|
||||||
LV_GRID_ALIGN_CENTER, row, 1);
|
|
||||||
lv_obj_set_size(unit_obj, lv_pct(100), LV_SIZE_CONTENT);
|
|
||||||
lv_obj_set_style_text_align(unit_obj , LV_TEXT_ALIGN_CENTER, 0);
|
|
||||||
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
lv_obj_t* initSliderInput(int row, const char* const label, int min, int max, int defval) {
|
|
||||||
lv_obj_t* label_obj = lv_label_create(container);
|
|
||||||
lv_label_set_text(label_obj, label);
|
|
||||||
lv_obj_set_grid_cell(label_obj,
|
|
||||||
LV_GRID_ALIGN_STRETCH, 0, 1,
|
|
||||||
LV_GRID_ALIGN_CENTER, row, 1);
|
|
||||||
lv_obj_set_size(label_obj, lv_pct(100), LV_SIZE_CONTENT);
|
|
||||||
|
|
||||||
lv_obj_t* input = lv_slider_create(container);
|
|
||||||
lv_obj_set_grid_cell(input,
|
|
||||||
LV_GRID_ALIGN_STRETCH, 1, 1,
|
|
||||||
LV_GRID_ALIGN_CENTER, row, 1);
|
|
||||||
lv_obj_set_size(input, lv_pct(100), 10);
|
|
||||||
lv_slider_set_range(input, min, max);
|
|
||||||
|
|
||||||
lv_obj_t* number_obj = lv_label_create(container);
|
|
||||||
//lv_label_set_text(number_obj, unit);
|
|
||||||
lv_obj_set_grid_cell(number_obj,
|
|
||||||
LV_GRID_ALIGN_STRETCH, 2, 1,
|
|
||||||
LV_GRID_ALIGN_CENTER, row, 1);
|
|
||||||
lv_obj_set_size(number_obj, lv_pct(100), LV_SIZE_CONTENT);
|
|
||||||
lv_obj_set_style_text_align(number_obj , LV_TEXT_ALIGN_CENTER, 0);
|
|
||||||
char buf[8] = {0};
|
|
||||||
lv_snprintf(buf, sizeof(buf), "%d", defval);
|
|
||||||
lv_label_set_text(number_obj, buf);
|
|
||||||
|
|
||||||
lv_obj_add_event_cb(input, [](lv_event_t * e) {
|
|
||||||
lv_obj_t* slider = lv_event_get_target_obj(e);
|
|
||||||
lv_obj_t* label = (lv_obj_t*)lv_event_get_user_data(e);
|
|
||||||
char buf[8] = {0};
|
|
||||||
lv_snprintf(buf, sizeof(buf), "%d", (int)lv_slider_get_value(slider));
|
|
||||||
lv_label_set_text(label, buf);
|
|
||||||
}, LV_EVENT_VALUE_CHANGED, number_obj);
|
|
||||||
|
|
||||||
lv_slider_set_value(input, defval, LV_ANIM_OFF);
|
|
||||||
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
void initUi(lv_obj_t *parent) {
|
|
||||||
container = lv_obj_create(parent);
|
|
||||||
lv_obj_set_size(container, lv_pct(100), lv_pct(100));
|
|
||||||
lv_obj_set_style_pad_all(container, 0, 0);
|
|
||||||
|
|
||||||
int grid_row_size = 40;
|
|
||||||
static lv_coord_t lora_col_dsc[] = {LV_GRID_FR(3), LV_GRID_FR(2), 45, LV_GRID_TEMPLATE_LAST};
|
|
||||||
static lv_coord_t lora_row_dsc[] = {
|
|
||||||
grid_row_size,
|
|
||||||
grid_row_size,
|
|
||||||
grid_row_size,
|
|
||||||
grid_row_size,
|
|
||||||
grid_row_size,
|
|
||||||
grid_row_size,
|
|
||||||
grid_row_size,
|
|
||||||
grid_row_size,
|
|
||||||
LV_GRID_TEMPLATE_LAST};
|
|
||||||
lv_obj_set_grid_dsc_array(container, lora_col_dsc, lora_row_dsc);
|
|
||||||
|
|
||||||
std::string dropdown_items = string::join(loraDevNames, "\n");
|
|
||||||
loraDeviceInput = initDropdownInput(0, "LoRa Device", dropdown_items.c_str());
|
|
||||||
loraDeviceOn = lv_switch_create(container);
|
|
||||||
lv_obj_set_grid_cell(loraDeviceOn,
|
|
||||||
LV_GRID_ALIGN_STRETCH, 2, 1,
|
|
||||||
LV_GRID_ALIGN_CENTER, 0, 1);
|
|
||||||
lv_obj_set_size(loraDeviceOn, lv_pct(100), 20);
|
|
||||||
|
|
||||||
frequencyInput = initFormInput(1, "Frequency", "869.525", "MHz");
|
|
||||||
bandwidthInput = initFormInput(2, "Bandwidth", "250", "kHz");
|
|
||||||
syncwordInput = initFormInput(3, "Sync Word", "2B", "hex");
|
|
||||||
deBitsInput = initSliderInput(4, "Coding Rate", 4, 8, 5);
|
|
||||||
sfInput = initSliderInput(5, "Spread Factor", 7, 12, 11);
|
|
||||||
preambleChirpsInput = initSliderInput(6, "Preamble Chirps", 4, 32, 16);
|
|
||||||
txPowInput = initFormInput(7, "TX Power", "27", "dBm");
|
|
||||||
/*
|
|
||||||
lv_obj_add_event_cb(frequencyInput, [](lv_event_t * e) {
|
|
||||||
lv_obj_t* input = lv_event_get_target_obj(e);
|
|
||||||
LoraParameters* params = (LoraParameters*)lv_event_get_user_data(e);
|
|
||||||
std::string buf(lv_textarea_get_text(input));
|
|
||||||
if (!buf.empty()) {
|
|
||||||
params->frequency = std::stof(buf);
|
|
||||||
}
|
|
||||||
}, LV_EVENT_VALUE_CHANGED, &loraParams);
|
|
||||||
|
|
||||||
lv_obj_add_event_cb(bandwidthInput, [](lv_event_t * e) {
|
|
||||||
lv_obj_t* input = lv_event_get_target_obj(e);
|
|
||||||
LoraParameters* params = (LoraParameters*)lv_event_get_user_data(e);
|
|
||||||
std::string buf(lv_textarea_get_text(input));
|
|
||||||
if (!buf.empty()) {
|
|
||||||
params->bandwidth = std::stof(buf);
|
|
||||||
}
|
|
||||||
}, LV_EVENT_VALUE_CHANGED, &loraParams);
|
|
||||||
|
|
||||||
lv_obj_add_event_cb(syncwordInput, [](lv_event_t * e) {
|
|
||||||
lv_obj_t* input = lv_event_get_target_obj(e);
|
|
||||||
LoraParameters* params = (LoraParameters*)lv_event_get_user_data(e);
|
|
||||||
std::string buf(lv_textarea_get_text(input));
|
|
||||||
if (!buf.empty()) {
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << std::hex << buf;
|
|
||||||
ss >> params->syncWord;
|
|
||||||
}
|
|
||||||
}, LV_EVENT_VALUE_CHANGED, &loraParams);
|
|
||||||
|
|
||||||
lv_obj_add_event_cb(preambleChirpsInput, [](lv_event_t * e) {
|
|
||||||
lv_obj_t* input = lv_event_get_target_obj(e);
|
|
||||||
LoraParameters* params = (LoraParameters*)lv_event_get_user_data(e);
|
|
||||||
params->preambleLength = lv_slider_get_value(input);
|
|
||||||
}, LV_EVENT_VALUE_CHANGED, &loraParams);
|
|
||||||
|
|
||||||
lv_obj_add_event_cb(deBitsInput, [](lv_event_t * e) {
|
|
||||||
lv_obj_t* input = lv_event_get_target_obj(e);
|
|
||||||
LoraParameters* params = (LoraParameters*)lv_event_get_user_data(e);
|
|
||||||
params->deBits = lv_slider_get_value(input);
|
|
||||||
}, LV_EVENT_VALUE_CHANGED, &loraParams);
|
|
||||||
|
|
||||||
lv_obj_add_event_cb(sfInput, [](lv_event_t * e) {
|
|
||||||
lv_obj_t* input = lv_event_get_target_obj(e);
|
|
||||||
LoraParameters* params = (LoraParameters*)lv_event_get_user_data(e);
|
|
||||||
params->spreadFactor = lv_slider_get_value(input);
|
|
||||||
}, LV_EVENT_VALUE_CHANGED, &loraParams);
|
|
||||||
|
|
||||||
lv_obj_add_event_cb(txPowInput, [](lv_event_t * e) {
|
|
||||||
lv_obj_t* input = lv_event_get_target_obj(e);
|
|
||||||
LoraParameters* params = (LoraParameters*)lv_event_get_user_data(e);
|
|
||||||
std::string buf(lv_textarea_get_text(input));
|
|
||||||
if (!buf.empty()) {
|
|
||||||
params->power = std::stoi(buf);
|
|
||||||
}
|
|
||||||
}, LV_EVENT_VALUE_CHANGED, &loraParams);
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
if (loraDevNames.size() > 0) {
|
|
||||||
loraDevice = loraService->getDevice(loraDevNames[0]);
|
|
||||||
if (loraDevice)
|
|
||||||
{
|
|
||||||
using State = hal::lora::LoraDevice::State;
|
|
||||||
switch (loraDevice->getState()) {
|
|
||||||
case State::PendingOn:
|
|
||||||
case State::On:
|
|
||||||
setParameters(loraDevice->getParameters());
|
|
||||||
disableForm();
|
|
||||||
lv_obj_add_state(loraDeviceOn, LV_STATE_CHECKED);
|
|
||||||
break;
|
|
||||||
case State::Error:
|
|
||||||
case State::PendingOff:
|
|
||||||
case State::Off:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
TT_LOG_E(TAG, "Attempted to load device \"%s\", what is happening!?", loraDevNames[0].c_str());
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
lv_obj_add_event_cb(loraDeviceOn, [](lv_event_t * e) {
|
|
||||||
lv_obj_t* input = lv_event_get_target_obj(e);
|
|
||||||
LoraView* self = (LoraView*)lv_event_get_user_data(e);
|
|
||||||
if (lv_obj_has_state(input, LV_STATE_CHECKED)) {
|
|
||||||
self->enableDevice();
|
|
||||||
} else {
|
|
||||||
self->disableDevice();
|
|
||||||
}
|
|
||||||
}, LV_EVENT_VALUE_CHANGED, this);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void disableForm()
|
|
||||||
{
|
|
||||||
lv_obj_add_state(loraDeviceInput, LV_STATE_DISABLED);
|
|
||||||
lv_obj_add_state(frequencyInput, LV_STATE_DISABLED);
|
|
||||||
lv_obj_add_state(bandwidthInput, LV_STATE_DISABLED);
|
|
||||||
lv_obj_add_state(syncwordInput, LV_STATE_DISABLED);
|
|
||||||
lv_obj_add_state(deBitsInput, LV_STATE_DISABLED);
|
|
||||||
lv_obj_add_state(preambleChirpsInput, LV_STATE_DISABLED);
|
|
||||||
lv_obj_add_state(txPowInput, LV_STATE_DISABLED);
|
|
||||||
}
|
|
||||||
|
|
||||||
void enableForm()
|
|
||||||
{
|
|
||||||
lv_obj_clear_state(loraDeviceInput, LV_STATE_DISABLED);
|
|
||||||
lv_obj_clear_state(frequencyInput, LV_STATE_DISABLED);
|
|
||||||
lv_obj_clear_state(bandwidthInput, LV_STATE_DISABLED);
|
|
||||||
lv_obj_clear_state(syncwordInput, LV_STATE_DISABLED);
|
|
||||||
lv_obj_clear_state(deBitsInput, LV_STATE_DISABLED);
|
|
||||||
lv_obj_clear_state(preambleChirpsInput, LV_STATE_DISABLED);
|
|
||||||
lv_obj_clear_state(txPowInput, LV_STATE_DISABLED);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
//std::shared_ptr<hal::lora::LoraDevice> loraDevice;
|
|
||||||
lv_obj_t* container;
|
|
||||||
lv_obj_t* loraDeviceOn;
|
|
||||||
lv_obj_t* loraDeviceInput;
|
|
||||||
lv_obj_t* frequencyInput;
|
|
||||||
lv_obj_t* bandwidthInput;
|
|
||||||
lv_obj_t* syncwordInput;
|
|
||||||
lv_obj_t* deBitsInput;
|
|
||||||
lv_obj_t* sfInput;
|
|
||||||
lv_obj_t* preambleChirpsInput;
|
|
||||||
lv_obj_t* txPowInput;
|
|
||||||
|
|
||||||
LoraView(lv_obj_t *parent) {
|
|
||||||
queryLoraDevs();
|
|
||||||
initUi(parent);
|
|
||||||
setParameters(
|
|
||||||
869.525,
|
|
||||||
250.0,
|
|
||||||
0x2B,
|
|
||||||
16,
|
|
||||||
5,
|
|
||||||
11,
|
|
||||||
22
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onDeviceActivation(DeviceActivationCallback cb) {
|
|
||||||
cbDevActive = cb;
|
|
||||||
}
|
|
||||||
|
|
||||||
void onDeviceDeactivation(DeviceActivationCallback cb) {
|
|
||||||
cbDevInactive = cb;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setParameters(float frequency, float bandwidth, uint8_t syncWord, uint16_t preambleLength, uint8_t codingRate, uint8_t spreadFactor, int8_t power) {
|
|
||||||
std::string buf;
|
|
||||||
|
|
||||||
buf = std::format("{:.6f}", frequency);
|
|
||||||
lv_textarea_set_text(frequencyInput, buf.c_str());
|
|
||||||
buf = std::format("{:.2f}", bandwidth);
|
|
||||||
lv_textarea_set_text(bandwidthInput, buf.c_str());
|
|
||||||
buf = std::format("{:X}", syncWord);
|
|
||||||
lv_textarea_set_text(syncwordInput, buf.c_str());
|
|
||||||
lv_slider_set_value(preambleChirpsInput, preambleLength, LV_ANIM_OFF);
|
|
||||||
lv_slider_set_value(deBitsInput, codingRate, LV_ANIM_OFF);
|
|
||||||
lv_slider_set_value(sfInput, spreadFactor, LV_ANIM_OFF);
|
|
||||||
buf = std::format("{:d}", power);
|
|
||||||
lv_textarea_set_text(txPowInput, buf.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void configureFromForm() {
|
|
||||||
using enum tt::hal::radio::RadioDevice::Parameter;
|
|
||||||
|
|
||||||
std::string buffer;
|
|
||||||
int value = 0;
|
|
||||||
bool configured = true;
|
|
||||||
buffer = lv_textarea_get_text(frequencyInput);
|
|
||||||
if (!buffer.empty()) {
|
|
||||||
configured &= loraDevice->configure(Frequency, std::stof(buffer));
|
|
||||||
}
|
|
||||||
buffer = lv_textarea_get_text(bandwidthInput);
|
|
||||||
if (!buffer.empty()) {
|
|
||||||
configured &= loraDevice->configure(Bandwidth, std::stof(buffer));
|
|
||||||
}
|
|
||||||
buffer = lv_textarea_get_text(syncwordInput);
|
|
||||||
if (!buffer.empty()) {
|
|
||||||
uint8_t syncWord = 0;
|
|
||||||
std::stringstream ss(buffer);
|
|
||||||
ss >> std::hex >> syncWord;
|
|
||||||
|
|
||||||
configured &= loraDevice->configure(SyncWord, std::stoi(buffer, nullptr, 16));
|
|
||||||
}
|
|
||||||
value = lv_slider_get_value(deBitsInput);
|
|
||||||
configured &= loraDevice->configure(CodingRate, value);
|
|
||||||
|
|
||||||
value = lv_slider_get_value(sfInput);
|
|
||||||
configured &= loraDevice->configure(SpreadFactor, value);
|
|
||||||
|
|
||||||
value = lv_slider_get_value(preambleChirpsInput);
|
|
||||||
configured &= loraDevice->configure(PreambleLength, value);
|
|
||||||
|
|
||||||
buffer = lv_textarea_get_text(txPowInput);
|
|
||||||
if (!buffer.empty()) {
|
|
||||||
configured &= loraDevice->configure(Power, std::stof(buffer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void enableDevice()
|
|
||||||
{
|
|
||||||
loraDevice = loraDevs[lv_dropdown_get_selected(loraDeviceInput)];
|
|
||||||
if (loraDevice) {
|
|
||||||
disableForm();
|
|
||||||
configureFromForm();
|
|
||||||
loraDevice->start(tt::hal::radio::RadioDevice::Modulation::LoRa);
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(500));
|
|
||||||
if (loraDevice->getState() != tt::hal::radio::RadioDevice::State::On) {
|
|
||||||
lv_obj_clear_state(loraDeviceOn, LV_STATE_CHECKED);
|
|
||||||
enableForm();
|
|
||||||
} else {
|
|
||||||
cbDevActive(loraDevice);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lv_obj_clear_state(loraDeviceOn, LV_STATE_CHECKED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void disableDevice()
|
|
||||||
{
|
|
||||||
if (loraDevice) {
|
|
||||||
loraDevice->stop();
|
|
||||||
cbDevInactive(loraDevice);
|
|
||||||
}
|
|
||||||
enableForm();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class ChirpChatterApp : public App {
|
|
||||||
|
|
||||||
lv_obj_t* sidebar = nullptr;
|
|
||||||
lv_obj_t* mainView = nullptr;
|
|
||||||
lv_obj_t* progressBar = nullptr;
|
|
||||||
lv_obj_t* progressText = nullptr;
|
|
||||||
|
|
||||||
lv_obj_t* messageList = nullptr;
|
|
||||||
lv_obj_t* inputField = nullptr;
|
|
||||||
|
|
||||||
lv_obj_t* messageView = nullptr;
|
|
||||||
LoraView* loraView = nullptr;
|
|
||||||
|
|
||||||
hal::radio::RadioDevice::RxSubscriptionId rxSubId;
|
|
||||||
std::shared_ptr<tt::hal::radio::RadioDevice> loraDevice;
|
|
||||||
|
|
||||||
template<CCViews T>
|
|
||||||
lv_obj_t* createSidebarButton(lv_obj_t* parent, const char* image_file) {
|
|
||||||
auto* sidebar_button = lv_button_create(parent);
|
|
||||||
|
|
||||||
lv_obj_set_size(sidebar_button, 32, 32);
|
|
||||||
lv_obj_align(sidebar_button, LV_ALIGN_TOP_MID, 0, 0);
|
|
||||||
lv_obj_set_style_pad_all(sidebar_button, 0, 0);
|
|
||||||
//lv_obj_set_style_pad_top(sidebar_button, 36, 0);
|
|
||||||
lv_obj_set_style_shadow_width(sidebar_button, 0, 0);
|
|
||||||
lv_obj_set_style_border_width(sidebar_button, 0, 0);
|
|
||||||
lv_obj_set_style_bg_opa(sidebar_button, 0, LV_PART_MAIN);
|
|
||||||
|
|
||||||
auto* button_image = lv_image_create(sidebar_button);
|
|
||||||
lv_image_set_src(button_image, image_file);
|
|
||||||
lv_obj_set_style_image_recolor(button_image, lv_theme_get_color_primary(parent), LV_STATE_DEFAULT);
|
|
||||||
lv_obj_set_style_image_recolor_opa(button_image, LV_OPA_COVER, LV_STATE_DEFAULT);
|
|
||||||
// Ensure buttons are still tappable when asset fails to load
|
|
||||||
lv_obj_set_size(button_image, 32, 32);
|
|
||||||
|
|
||||||
static lv_style_t style_focus;
|
|
||||||
lv_style_init(&style_focus);
|
|
||||||
lv_style_set_outline_width(&style_focus, 0);
|
|
||||||
lv_obj_add_style(sidebar_button, &style_focus, LV_PART_MAIN | LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY);
|
|
||||||
lv_obj_add_style(button_image, &style_focus, LV_PART_MAIN | LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY);
|
|
||||||
|
|
||||||
lv_obj_add_event_cb(button_image, changeViewHandler<T>, LV_EVENT_SHORT_CLICKED, (void*)this);
|
|
||||||
lv_obj_add_event_cb(sidebar_button, changeViewHandler<T>, LV_EVENT_SHORT_CLICKED, (void*)this);
|
|
||||||
|
|
||||||
lv_obj_add_event_cb(sidebar_button, buttonRecolorFocus, LV_EVENT_FOCUSED, button_image);
|
|
||||||
lv_obj_add_event_cb(sidebar_button, buttonRecolorDefocus, LV_EVENT_DEFOCUSED, button_image);
|
|
||||||
|
|
||||||
return sidebar_button;
|
|
||||||
}
|
|
||||||
|
|
||||||
void addDummyMessage(const char* const message) {
|
|
||||||
auto* msg_container = lv_obj_create(messageList);
|
|
||||||
lv_obj_set_flex_flow(msg_container, LV_FLEX_FLOW_COLUMN);
|
|
||||||
lv_obj_set_flex_grow(msg_container, 0);
|
|
||||||
lv_obj_set_style_pad_all(msg_container, 1, 0);
|
|
||||||
lv_obj_add_flag(msg_container, LV_OBJ_FLAG_CLICKABLE);
|
|
||||||
|
|
||||||
lv_obj_t* msg_label = lv_label_create(msg_container);
|
|
||||||
lv_label_set_text(msg_label, message);
|
|
||||||
lv_obj_set_width(msg_label, lv_pct(100));
|
|
||||||
lv_label_set_long_mode(msg_label, LV_LABEL_LONG_WRAP);
|
|
||||||
lv_obj_set_style_text_align(msg_label, LV_TEXT_ALIGN_LEFT, 0);
|
|
||||||
lv_obj_set_style_pad_all(msg_label, 0, 0);
|
|
||||||
|
|
||||||
lv_obj_t* msg_info = lv_label_create(msg_container);
|
|
||||||
lv_label_set_text(msg_info, "RX/2024-07-06+15:04");
|
|
||||||
lv_obj_set_width(msg_info, lv_pct(100));
|
|
||||||
lv_label_set_long_mode(msg_info, LV_LABEL_LONG_WRAP);
|
|
||||||
lv_obj_set_style_text_align(msg_info, LV_TEXT_ALIGN_LEFT, 0);
|
|
||||||
lv_obj_set_style_pad_all(msg_info, 0, 0);
|
|
||||||
lv_obj_set_style_text_font(msg_info, &lv_font_montserrat_10, 0);
|
|
||||||
|
|
||||||
|
|
||||||
lv_obj_set_width(msg_container, lv_pct(100));
|
|
||||||
lv_obj_set_height(msg_container, LV_SIZE_CONTENT);
|
|
||||||
|
|
||||||
lv_obj_scroll_to_y(messageList, lv_obj_get_scroll_y(messageList) + 1000, LV_ANIM_ON);
|
|
||||||
|
|
||||||
/*auto* group = lv_group_get_default();
|
|
||||||
lv_group_add_obj(group, msg_container);*/
|
|
||||||
}
|
|
||||||
|
|
||||||
void addPacketMessage(const hal::radio::RxPacket& packet) {
|
|
||||||
auto* msg_container = lv_obj_create(messageList);
|
|
||||||
lv_obj_set_flex_flow(msg_container, LV_FLEX_FLOW_COLUMN);
|
|
||||||
lv_obj_set_flex_grow(msg_container, 0);
|
|
||||||
lv_obj_set_style_pad_all(msg_container, 1, 0);
|
|
||||||
lv_obj_add_flag(msg_container, LV_OBJ_FLAG_CLICKABLE);
|
|
||||||
|
|
||||||
lv_obj_t* msg_label = lv_label_create(msg_container);
|
|
||||||
|
|
||||||
std::vector<uint8_t> data(packet.data, packet.data + packet.size);
|
|
||||||
std::string messageBuf = "";
|
|
||||||
if (isPrintableData(data)) {
|
|
||||||
messageBuf = std::string((char*)packet.data, packet.size);
|
|
||||||
} else {
|
|
||||||
messageBuf = hexdump(data);
|
|
||||||
}
|
|
||||||
lv_label_set_text(msg_label, messageBuf.c_str());
|
|
||||||
lv_obj_set_width(msg_label, lv_pct(100));
|
|
||||||
lv_label_set_long_mode(msg_label, LV_LABEL_LONG_WRAP);
|
|
||||||
lv_obj_set_style_text_align(msg_label, LV_TEXT_ALIGN_LEFT, 0);
|
|
||||||
lv_obj_set_style_pad_all(msg_label, 0, 0);
|
|
||||||
|
|
||||||
lv_obj_t* msg_info = lv_label_create(msg_container);
|
|
||||||
|
|
||||||
auto t = std::time(nullptr);
|
|
||||||
auto tm = *std::localtime(&t);
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "RX/RAW ";
|
|
||||||
ss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
|
|
||||||
ss << " ";
|
|
||||||
ss << "RSSI:" << packet.rssi;
|
|
||||||
ss << " ";
|
|
||||||
ss << "SNR:" << packet.snr;
|
|
||||||
|
|
||||||
lv_label_set_text(msg_info, ss.str().c_str());
|
|
||||||
lv_obj_set_width(msg_info, lv_pct(100));
|
|
||||||
lv_label_set_long_mode(msg_info, LV_LABEL_LONG_WRAP);
|
|
||||||
lv_obj_set_style_text_align(msg_info, LV_TEXT_ALIGN_LEFT, 0);
|
|
||||||
lv_obj_set_style_pad_all(msg_info, 0, 0);
|
|
||||||
lv_obj_set_style_text_font(msg_info, &lv_font_montserrat_10, 0);
|
|
||||||
|
|
||||||
|
|
||||||
lv_obj_set_width(msg_container, lv_pct(100));
|
|
||||||
lv_obj_set_height(msg_container, LV_SIZE_CONTENT);
|
|
||||||
|
|
||||||
lv_obj_scroll_to_y(messageList, lv_obj_get_scroll_y(messageList) + 1000, LV_ANIM_ON);
|
|
||||||
|
|
||||||
/*auto* group = lv_group_get_default();
|
|
||||||
* lv_group_add_obj(group, msg_container);*/
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
void onCreate(AppContext& appContext) override {
|
|
||||||
#ifdef ESP_PLATFORM
|
|
||||||
esp_log_level_set("*", ESP_LOG_DEBUG);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void onDestroy(AppContext& appContext) override {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void onShow(AppContext& context, lv_obj_t* parent) override {
|
|
||||||
|
|
||||||
static lv_coord_t grid_col_dsc[] = {36, LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST};
|
|
||||||
static lv_coord_t grid_row_dsc[] = {40, LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST};
|
|
||||||
|
|
||||||
lv_obj_t * grid = lv_obj_create(parent);
|
|
||||||
lv_obj_set_grid_dsc_array(grid, grid_col_dsc, grid_row_dsc);
|
|
||||||
lv_obj_set_size(grid, lv_pct(100), lv_pct(100));
|
|
||||||
|
|
||||||
static lv_style_t style_grid;
|
|
||||||
lv_style_init(&style_grid);
|
|
||||||
lv_style_set_pad_row(&style_grid, 0);
|
|
||||||
lv_style_set_pad_column(&style_grid, 0);
|
|
||||||
lv_style_set_pad_all(&style_grid, 0);
|
|
||||||
|
|
||||||
lv_obj_add_style(grid, &style_grid, LV_PART_MAIN);
|
|
||||||
|
|
||||||
// Create toolbar
|
|
||||||
auto* toolbar = tt::lvgl::toolbar_create(grid, "Welcome to ChirpChatter!");
|
|
||||||
lv_obj_set_size(toolbar, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
|
|
||||||
lv_obj_set_grid_cell(toolbar, LV_GRID_ALIGN_STRETCH, 0, 2,
|
|
||||||
LV_GRID_ALIGN_STRETCH, 0, 1);
|
|
||||||
|
|
||||||
progressText = lv_obj_get_child_by_type(toolbar, 0, &lv_label_class);
|
|
||||||
lv_obj_align(progressText, LV_ALIGN_TOP_LEFT, 0, 0);
|
|
||||||
lv_obj_set_style_text_font(progressText, &lv_font_montserrat_12, 0);
|
|
||||||
lv_obj_set_size(progressText, lv_pct(75), LV_SIZE_CONTENT);
|
|
||||||
|
|
||||||
// Create sidebar
|
|
||||||
sidebar = lv_obj_create(grid);
|
|
||||||
lv_obj_set_size(sidebar, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
|
|
||||||
lv_obj_set_grid_cell(sidebar, LV_GRID_ALIGN_START, 0, 1,
|
|
||||||
LV_GRID_ALIGN_START, 1, 1);
|
|
||||||
|
|
||||||
lv_obj_set_scrollbar_mode(sidebar, LV_SCROLLBAR_MODE_OFF);
|
|
||||||
lv_obj_set_style_pad_all(sidebar, 2, 0);
|
|
||||||
lv_obj_set_style_border_width(sidebar, 0, 0);
|
|
||||||
lv_obj_set_flex_flow(sidebar, LV_FLEX_FLOW_COLUMN);
|
|
||||||
|
|
||||||
// Create progress bar
|
|
||||||
progressBar = lv_bar_create(toolbar);
|
|
||||||
//lv_obj_set_flex_flow(toolbar, LV_FLEX_FLOW_COLUMN);
|
|
||||||
lv_obj_align(progressBar, LV_ALIGN_RIGHT_MID, 0, 0);
|
|
||||||
lv_obj_set_size(progressBar, lv_pct(25), 36);
|
|
||||||
lv_obj_set_style_radius(progressBar, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
|
|
||||||
lv_obj_set_style_radius(progressBar, 0, LV_PART_INDICATOR | LV_STATE_DEFAULT);
|
|
||||||
lv_bar_set_range(progressBar, 0, 100);
|
|
||||||
lv_bar_set_value(progressBar, 100, LV_ANIM_OFF);
|
|
||||||
|
|
||||||
auto paths = context.getPaths();
|
|
||||||
auto icon_msgs_path = paths->getSystemPathLvgl("icon_msgs.png");
|
|
||||||
createSidebarButton<CCView_Msgs>(sidebar, icon_msgs_path.c_str());
|
|
||||||
auto icon_lora_path = paths->getSystemPathLvgl("icon_lora.png");
|
|
||||||
createSidebarButton<CCView_LoraSettings>(sidebar, icon_lora_path.c_str());
|
|
||||||
auto icon_proto_path = paths->getSystemPathLvgl("icon_proto.png");
|
|
||||||
createSidebarButton<CCView_ProtoSettings>(sidebar, icon_proto_path.c_str());
|
|
||||||
|
|
||||||
|
|
||||||
// Main view
|
|
||||||
/*mainView = lv_obj_create(grid);
|
|
||||||
lv_obj_set_size(mainView, lv_pct(100), lv_pct(100));
|
|
||||||
lv_obj_set_grid_cell(mainView, LV_GRID_ALIGN_STRETCH, 1, 1,
|
|
||||||
LV_GRID_ALIGN_STRETCH, 1, 1);
|
|
||||||
//lv_obj_set_flex_flow(mainView, LV_FLEX_FLOW_COLUMN);
|
|
||||||
lv_obj_set_style_bg_color(mainView, lv_color_hex(0x00FF00), 0);
|
|
||||||
lv_obj_set_style_border_width(mainView, 0, 0);
|
|
||||||
lv_obj_set_style_pad_all(mainView, 0, 0);
|
|
||||||
*/
|
|
||||||
// Message view
|
|
||||||
messageView = lv_obj_create(grid);
|
|
||||||
//lv_obj_set_size(messageView, lv_disp_get_hor_res(NULL) - 40, lv_disp_get_ver_res(NULL) - toolbar_height*2);
|
|
||||||
lv_obj_set_size(messageView, lv_pct(100), lv_pct(100));
|
|
||||||
//lv_obj_set_flex_flow(messageView, LV_FLEX_FLOW_COLUMN);
|
|
||||||
//lv_obj_set_flex_grow(messageView, 1);
|
|
||||||
lv_obj_set_size(messageView, lv_pct(100), lv_pct(100));
|
|
||||||
lv_obj_set_grid_cell(messageView, LV_GRID_ALIGN_STRETCH, 1, 1,
|
|
||||||
LV_GRID_ALIGN_STRETCH, 1, 1);
|
|
||||||
|
|
||||||
lv_obj_set_style_pad_all(messageView, 0, 0);
|
|
||||||
|
|
||||||
|
|
||||||
messageList = lv_obj_create(messageView);
|
|
||||||
lv_obj_set_size(messageList, lv_pct(100), lv_pct(80));
|
|
||||||
lv_obj_set_flex_flow(messageList, LV_FLEX_FLOW_COLUMN);
|
|
||||||
lv_obj_align(messageList, LV_ALIGN_TOP_MID, 0, 0);
|
|
||||||
///lv_obj_set_style_bg_color(mainView, lv_color_hex(0xFF0000), 0);
|
|
||||||
lv_obj_set_style_border_width(messageList, 0, 0);
|
|
||||||
lv_obj_set_style_pad_all(messageList, 0, 0);
|
|
||||||
lv_obj_add_flag(messageList, (lv_obj_flag_t)(LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLL_ON_FOCUS));
|
|
||||||
|
|
||||||
auto* group = lv_group_get_default();
|
|
||||||
lv_group_add_obj(group, messageList);
|
|
||||||
|
|
||||||
lv_obj_add_event_cb(messageList, buttonRecolorFocus, LV_EVENT_FOCUSED, nullptr);
|
|
||||||
lv_obj_add_event_cb(messageList, buttonRecolorDefocus, LV_EVENT_DEFOCUSED, nullptr);
|
|
||||||
|
|
||||||
/*
|
|
||||||
messageList = lv_page_create(messageView, nullptr);
|
|
||||||
lv_obj_set_size(messageList, lv_pct(100), lv_pct(80));
|
|
||||||
lv_obj_set_style_border_width(messageList, 0, 0);
|
|
||||||
lv_obj_set_style_pad_all(messageList, 0, 0);
|
|
||||||
*/
|
|
||||||
// Input panel
|
|
||||||
auto* input_panel = lv_obj_create(messageView);
|
|
||||||
lv_obj_set_flex_flow(input_panel, LV_FLEX_FLOW_ROW);
|
|
||||||
lv_obj_set_size(input_panel, lv_pct(100), LV_SIZE_CONTENT);
|
|
||||||
lv_obj_align(input_panel, LV_ALIGN_BOTTOM_MID, 0, 0);
|
|
||||||
lv_obj_set_flex_align(input_panel, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
|
||||||
lv_obj_set_style_pad_all(input_panel, 5, 0);
|
|
||||||
|
|
||||||
// Input field
|
|
||||||
inputField = lv_textarea_create(input_panel);
|
|
||||||
lv_obj_set_flex_grow(inputField, 1);
|
|
||||||
lv_obj_set_height(inputField, LV_PCT(100));
|
|
||||||
lv_textarea_set_placeholder_text(inputField, "Type a message...");
|
|
||||||
lv_textarea_set_one_line(inputField, true);
|
|
||||||
|
|
||||||
// Send button
|
|
||||||
auto* send_btn = lv_btn_create(input_panel);
|
|
||||||
lv_obj_set_size(send_btn, 50, LV_SIZE_CONTENT);
|
|
||||||
//lv_obj_add_event_cb(send_btn, onSendClicked, LV_EVENT_CLICKED, this);
|
|
||||||
|
|
||||||
auto* btn_label = lv_label_create(send_btn);
|
|
||||||
lv_label_set_text(btn_label, "SEND");
|
|
||||||
lv_obj_center(btn_label);
|
|
||||||
|
|
||||||
lv_obj_set_flex_grow(messageList, 1);
|
|
||||||
lv_obj_set_flex_grow(input_panel, 0);
|
|
||||||
|
|
||||||
//lv_obj_set_style_bg_color(messageList, lv_color_hex(0xFF0000), 0);
|
|
||||||
//lv_obj_set_style_bg_color(input_panel, lv_color_hex(0x00FF00), 0);
|
|
||||||
|
|
||||||
//addDummyMessage("HELLO CHIRPCHAT!");
|
|
||||||
//addDummyMessage("How's biz?");
|
|
||||||
//addDummyMessage("Test");
|
|
||||||
//addDummyMessage("Test empfangen in Linz");
|
|
||||||
|
|
||||||
// LoRa settings view
|
|
||||||
loraView = new LoraView(grid);
|
|
||||||
lv_obj_set_grid_cell(loraView->container, LV_GRID_ALIGN_STRETCH, 1, 1,
|
|
||||||
LV_GRID_ALIGN_STRETCH, 1, 1);
|
|
||||||
|
|
||||||
//loraView->onDeviceActivation(std::bind(&ChirpChatterApp::onDeviceActivation, this));
|
|
||||||
//loraView->onDeviceDeactivation(std::bind(&ChirpChatterApp::onDeviceDeactivation, this));
|
|
||||||
|
|
||||||
loraView->onDeviceActivation([this](std::shared_ptr<tt::hal::radio::RadioDevice> dev) { this->onDeviceActivation(dev); });
|
|
||||||
loraView->onDeviceDeactivation([this](std::shared_ptr<tt::hal::radio::RadioDevice> dev) { this->onDeviceDeactivation(dev); });
|
|
||||||
|
|
||||||
lv_obj_add_event_cb(send_btn, [](lv_event_t * e) {
|
|
||||||
lv_obj_t* input = lv_event_get_target_obj(e);
|
|
||||||
ChirpChatterApp* self = (ChirpChatterApp*)lv_event_get_user_data(e);
|
|
||||||
self->sendMessage();
|
|
||||||
}, LV_EVENT_SHORT_CLICKED, (void*)this);
|
|
||||||
|
|
||||||
changeView(CCView_Msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onRxPacket(hal::Device::Id id, const hal::radio::RxPacket& packet) {
|
|
||||||
addPacketMessage(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onDeviceActivation(std::shared_ptr<tt::hal::radio::RadioDevice> dev) {
|
|
||||||
rxSubId = dev->subscribeRx([this](hal::Device::Id id, const hal::radio::RxPacket& packet) {
|
|
||||||
this->onRxPacket(id, packet);
|
|
||||||
});
|
|
||||||
loraDevice = dev;
|
|
||||||
std::ostringstream oss;
|
|
||||||
oss << "Device \"" << dev->getName() << "\" online";
|
|
||||||
|
|
||||||
lv_label_set_text(progressText, oss.str().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void onDeviceDeactivation(std::shared_ptr<tt::hal::radio::RadioDevice> dev) {
|
|
||||||
dev->unsubscribeRx(rxSubId);
|
|
||||||
loraDevice = nullptr;
|
|
||||||
|
|
||||||
lv_label_set_text(progressText, "Offline");
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendMessage() {
|
|
||||||
std::string message = lv_textarea_get_text(inputField);
|
|
||||||
std::vector<uint8_t> data(message.begin(), message.end());
|
|
||||||
loraDevice->transmit(data, [this](hal::radio::RadioDevice::TxId id, hal::radio::RadioDevice::TransmissionState state) {
|
|
||||||
this->onTxStatus(id, state);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void onTxStatus(hal::radio::RadioDevice::TxId id, hal::radio::RadioDevice::TransmissionState state) {
|
|
||||||
using enum hal::radio::RadioDevice::TransmissionState;
|
|
||||||
|
|
||||||
switch (state) {
|
|
||||||
case Queued:
|
|
||||||
lv_label_set_text(progressText, "Message queued...");
|
|
||||||
lv_bar_set_value(progressBar, 25, LV_ANIM_ON);
|
|
||||||
break;
|
|
||||||
case PendingTransmit:
|
|
||||||
lv_label_set_text(progressText, "Message transmitting...");
|
|
||||||
lv_bar_set_value(progressBar, 50, LV_ANIM_ON);
|
|
||||||
break;
|
|
||||||
case Transmitted:
|
|
||||||
lv_label_set_text(progressText, "Message transmitted!\nReturn to receive.");
|
|
||||||
lv_bar_set_value(progressBar, 100, LV_ANIM_ON);
|
|
||||||
break;
|
|
||||||
case Timeout:
|
|
||||||
lv_label_set_text(progressText, "Message transmit timed out!");
|
|
||||||
lv_bar_set_value(progressBar, 100, LV_ANIM_ON);
|
|
||||||
break;
|
|
||||||
case Error:
|
|
||||||
lv_label_set_text(progressText, "Error transmitting message!");
|
|
||||||
lv_bar_set_value(progressBar, 100, LV_ANIM_ON);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void changeView(const CCViews view) {
|
|
||||||
lv_obj_add_flag(messageView, LV_OBJ_FLAG_HIDDEN);
|
|
||||||
lv_obj_add_flag(loraView->container, LV_OBJ_FLAG_HIDDEN);
|
|
||||||
|
|
||||||
switch (view) {
|
|
||||||
case CCView_Msgs:
|
|
||||||
lv_obj_clear_flag(messageView, LV_OBJ_FLAG_HIDDEN);
|
|
||||||
break;
|
|
||||||
case CCView_LoraSettings:
|
|
||||||
lv_obj_clear_flag(loraView->container, LV_OBJ_FLAG_HIDDEN);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
~ChirpChatterApp() override = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern const AppManifest manifest = {
|
|
||||||
.id = "ChirpChatter",
|
|
||||||
.name = "ChirpChatter",
|
|
||||||
.icon = TT_ASSETS_APP_ICON_CHAT,
|
|
||||||
.createApp = create<ChirpChatterApp>
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endif
|
|
||||||
@ -1,138 +0,0 @@
|
|||||||
#include "Tactility/hal/radio/RadioDevice.h"
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
namespace tt::hal::radio {
|
|
||||||
|
|
||||||
constexpr const char* TAG = "RadioDevice";
|
|
||||||
|
|
||||||
bool RadioDevice::start(const Modulation modulation) {
|
|
||||||
auto lock = mutex.asScopedLock();
|
|
||||||
|
|
||||||
if (!isCapableOf(modulation)) {
|
|
||||||
TT_LOG_E(TAG, "Can't start device \"%s\", not capable of modulation \"%s\"", getName().c_str(), toString(modulation));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock.lock();
|
|
||||||
|
|
||||||
if (thread != nullptr && thread->getState() != Thread::State::Stopped) {
|
|
||||||
TT_LOG_W(TAG, "Already started");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
threadInterrupted = false;
|
|
||||||
|
|
||||||
TT_LOG_I(TAG, "Starting thread");
|
|
||||||
setState(State::PendingOn);
|
|
||||||
|
|
||||||
thread = std::make_unique<Thread>(
|
|
||||||
threadName,
|
|
||||||
threadSize,
|
|
||||||
[this, modulation]() {
|
|
||||||
return this->threadMain(modulation);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
thread->setPriority(tt::Thread::Priority::High);
|
|
||||||
thread->start();
|
|
||||||
|
|
||||||
TT_LOG_I(TAG, "Starting finished");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RadioDevice::stop() {
|
|
||||||
auto lock = mutex.asScopedLock();
|
|
||||||
lock.lock();
|
|
||||||
|
|
||||||
setState(State::PendingOff);
|
|
||||||
|
|
||||||
if (thread != nullptr) {
|
|
||||||
threadInterrupted = true;
|
|
||||||
getEventFlag().set(RADIO_TERMINATE_BIT);
|
|
||||||
|
|
||||||
// Detach thread, it will auto-delete when leaving the current scope
|
|
||||||
auto old_thread = std::move(thread);
|
|
||||||
|
|
||||||
if (old_thread->getState() != Thread::State::Stopped) {
|
|
||||||
// Unlock so thread can lock
|
|
||||||
lock.unlock();
|
|
||||||
// Wait for thread to finish
|
|
||||||
old_thread->join();
|
|
||||||
// Re-lock to continue logic below
|
|
||||||
lock.lock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(State::Off);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RadioDevice::isThreadInterrupted() const {
|
|
||||||
auto lock = mutex.asScopedLock();
|
|
||||||
lock.lock();
|
|
||||||
return threadInterrupted;
|
|
||||||
}
|
|
||||||
|
|
||||||
RadioDevice::State RadioDevice::getState() const {
|
|
||||||
auto lock = mutex.asScopedLock();
|
|
||||||
lock.lock();
|
|
||||||
return state; // Make copy because of thread safety
|
|
||||||
}
|
|
||||||
|
|
||||||
void RadioDevice::setState(State newState) {
|
|
||||||
auto lock = mutex.asScopedLock();
|
|
||||||
lock.lock();
|
|
||||||
state = newState;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RadioDevice::publishRx(const RxPacket& packet) {
|
|
||||||
mutex.lock();
|
|
||||||
for (auto& subscription : rxSubscriptions) {
|
|
||||||
(*subscription.onData)(getId(), packet);
|
|
||||||
}
|
|
||||||
mutex.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* toString(RadioDevice::Modulation modulation) {
|
|
||||||
using enum RadioDevice::Modulation;
|
|
||||||
switch (modulation) {
|
|
||||||
case Fsk:
|
|
||||||
return "FSK";
|
|
||||||
case LoRa:
|
|
||||||
return "LoRa";
|
|
||||||
case LrFhss:
|
|
||||||
return "LR-FHSS";
|
|
||||||
default:
|
|
||||||
return "Unkown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* toString(RadioDevice::Parameter parameter) {
|
|
||||||
using enum RadioDevice::Parameter;
|
|
||||||
switch (parameter) {
|
|
||||||
case Power:
|
|
||||||
return TT_STRINGIFY(Power);
|
|
||||||
case Frequency:
|
|
||||||
return TT_STRINGIFY(Frequency);
|
|
||||||
case Bandwidth:
|
|
||||||
return TT_STRINGIFY(Bandwidth);
|
|
||||||
case SpreadFactor:
|
|
||||||
return TT_STRINGIFY(SpreadFactor);
|
|
||||||
case CodingRate:
|
|
||||||
return TT_STRINGIFY(CodingRate);
|
|
||||||
case SyncWord:
|
|
||||||
return TT_STRINGIFY(SyncWord);
|
|
||||||
case PreambleLength:
|
|
||||||
return TT_STRINGIFY(PreambleLength);
|
|
||||||
case FrequencyDeviation:
|
|
||||||
return TT_STRINGIFY(FrequencyDeviation);
|
|
||||||
case DataRate:
|
|
||||||
return TT_STRINGIFY(DataRate);
|
|
||||||
case AddressWidth:
|
|
||||||
return TT_STRINGIFY(AddressWidth);
|
|
||||||
default:
|
|
||||||
return "Unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace tt::hal::radio
|
|
||||||
Loading…
x
Reference in New Issue
Block a user