* SdCard HAL refactored (#135) - Refactor SdCard HAL - introduce Lockable * Screenshot and FatFS improvements (#136) - Fix screenshots on ESP32 - Improve Screenshot service - Convert Screenshot app to class-based instead of structs - Screenshot app now automatically updates when task is finished - Enable FatFS long filename support * Re-use common log messages (#138) For consistency and binary size reduction * Toolbar spinner should get margin to the right * More TactilityC features (#139) * Rewrote Loader - Simplified Loader by removing custom threa - Created DispatcherThread - Move auto-starting apps to Boot app - Fixed Dispatcher bug where it could get stuck not processing new messages * Hide AP settings if the AP is not saved * Missing from previous commit * Replace LV_EVENT_CLICKED with LV_EVENT_SHORT_CLICKED * Refactored files app and created InputDialog (#140) - Changed Files app so that it has a View and State - Files app now allows for long-pressing on files to perform actions - Files app now has rename and delete actions - Created InputDialog app - Improved AlertDialog app layout
195 lines
6.4 KiB
C++
195 lines
6.4 KiB
C++
#include "I2cScanner.h"
|
|
#include "I2cScannerThread.h"
|
|
#include "I2cHelpers.h"
|
|
|
|
#include "Assets.h"
|
|
#include "Tactility.h"
|
|
#include "app/AppContext.h"
|
|
#include "lvgl/LvglSync.h"
|
|
#include "lvgl/Toolbar.h"
|
|
#include "service/loader/Loader.h"
|
|
|
|
#define START_SCAN_TEXT "Scan"
|
|
#define STOP_SCAN_TEXT "Stop scan"
|
|
|
|
namespace tt::app::i2cscanner {
|
|
|
|
static void updateViews(std::shared_ptr<Data> data);
|
|
extern const AppManifest manifest;
|
|
|
|
/** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */
|
|
std::shared_ptr<Data> _Nullable optData() {
|
|
app::AppContext* app = service::loader::getCurrentApp();
|
|
if (app->getManifest().id == manifest.id) {
|
|
return std::static_pointer_cast<Data>(app->getData());
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
static void onSelectBus(lv_event_t* event) {
|
|
auto data = optData();
|
|
if (data == nullptr) {
|
|
return;
|
|
}
|
|
|
|
auto* dropdown = static_cast<lv_obj_t*>(lv_event_get_target(event));
|
|
uint32_t selected = lv_dropdown_get_selected(dropdown);
|
|
auto i2c_devices = tt::getConfiguration()->hardware->i2c;
|
|
assert(selected < i2c_devices.size());
|
|
|
|
if (data->mutex.acquire(100 / portTICK_PERIOD_MS) == TtStatusOk) {
|
|
data->scannedAddresses.clear();
|
|
data->port = i2c_devices[selected].port;
|
|
data->scanState = ScanStateInitial;
|
|
tt_check(data->mutex.release() == TtStatusOk);
|
|
|
|
updateViews(data);
|
|
}
|
|
|
|
TT_LOG_I(TAG, "Selected %ld", selected);
|
|
}
|
|
|
|
static void onPressScan(lv_event_t* event) {
|
|
auto data = optData();
|
|
if (data != nullptr) {
|
|
if (data->scanState == ScanStateScanning) {
|
|
stopScanning(data);
|
|
} else {
|
|
startScanning(data);
|
|
}
|
|
updateViews(data);
|
|
}
|
|
}
|
|
|
|
static void updateViews(std::shared_ptr<Data> data) {
|
|
if (data->mutex.acquire(100 / portTICK_PERIOD_MS) == TtStatusOk) {
|
|
if (data->scanState == ScanStateScanning) {
|
|
lv_label_set_text(data->scanButtonLabelWidget, STOP_SCAN_TEXT);
|
|
lv_obj_remove_flag(data->portDropdownWidget, LV_OBJ_FLAG_CLICKABLE);
|
|
} else {
|
|
lv_label_set_text(data->scanButtonLabelWidget, START_SCAN_TEXT);
|
|
lv_obj_add_flag(data->portDropdownWidget, LV_OBJ_FLAG_CLICKABLE);
|
|
}
|
|
|
|
lv_obj_clean(data->scanListWidget);
|
|
if (data->scanState == ScanStateStopped) {
|
|
lv_obj_remove_flag(data->scanListWidget, LV_OBJ_FLAG_HIDDEN);
|
|
if (!data->scannedAddresses.empty()) {
|
|
for (auto address: data->scannedAddresses) {
|
|
std::string address_text = getAddressText(address);
|
|
lv_list_add_text(data->scanListWidget, address_text.c_str());
|
|
}
|
|
} else {
|
|
lv_list_add_text(data->scanListWidget, "No devices found");
|
|
}
|
|
} else {
|
|
lv_obj_add_flag(data->scanListWidget, LV_OBJ_FLAG_HIDDEN);
|
|
}
|
|
|
|
tt_check(data->mutex.release() == TtStatusOk);
|
|
} else {
|
|
TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "updateViews");
|
|
}
|
|
}
|
|
|
|
static void updateViewsSafely(std::shared_ptr<Data> data) {
|
|
if (lvgl::lock(100 / portTICK_PERIOD_MS)) {
|
|
updateViews(data);
|
|
lvgl::unlock();
|
|
} else {
|
|
TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "updateViewsSafely");
|
|
}
|
|
}
|
|
|
|
void onScanTimerFinished(std::shared_ptr<Data> data) {
|
|
if (data->mutex.acquire(100 / portTICK_PERIOD_MS) == TtStatusOk) {
|
|
if (data->scanState == ScanStateScanning) {
|
|
data->scanState = ScanStateStopped;
|
|
updateViewsSafely(data);
|
|
}
|
|
tt_check(data->mutex.release() == TtStatusOk);
|
|
} else {
|
|
TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "onScanTimerFinished");
|
|
}
|
|
}
|
|
|
|
static void onShow(AppContext& app, lv_obj_t* parent) {
|
|
auto data = std::static_pointer_cast<Data>(app.getData());
|
|
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
|
|
|
lvgl::toolbar_create(parent, app);
|
|
|
|
lv_obj_t* main_wrapper = lv_obj_create(parent);
|
|
lv_obj_set_flex_flow(main_wrapper, LV_FLEX_FLOW_COLUMN);
|
|
lv_obj_set_width(main_wrapper, LV_PCT(100));
|
|
lv_obj_set_flex_grow(main_wrapper, 1);
|
|
|
|
lv_obj_t* wrapper = lv_obj_create(main_wrapper);
|
|
lv_obj_set_width(wrapper, LV_PCT(100));
|
|
lv_obj_set_height(wrapper, LV_SIZE_CONTENT);
|
|
lv_obj_set_style_pad_all(wrapper, 0, 0);
|
|
lv_obj_set_style_border_width(wrapper, 0, 0);
|
|
|
|
lv_obj_t* scan_button = lv_button_create(wrapper);
|
|
lv_obj_set_width(scan_button, LV_PCT(48));
|
|
lv_obj_align(scan_button, LV_ALIGN_TOP_LEFT, 0, 1); // Shift 1 pixel to align with selection box
|
|
lv_obj_add_event_cb(scan_button, &onPressScan, LV_EVENT_SHORT_CLICKED, nullptr);
|
|
lv_obj_t* scan_button_label = lv_label_create(scan_button);
|
|
lv_obj_align(scan_button_label, LV_ALIGN_CENTER, 0, 0);
|
|
lv_label_set_text(scan_button_label, START_SCAN_TEXT);
|
|
data->scanButtonLabelWidget = scan_button_label;
|
|
|
|
lv_obj_t* port_dropdown = lv_dropdown_create(wrapper);
|
|
std::string dropdown_items = getPortNamesForDropdown();
|
|
lv_dropdown_set_options(port_dropdown, dropdown_items.c_str());
|
|
lv_obj_set_width(port_dropdown, LV_PCT(48));
|
|
lv_obj_align(port_dropdown, LV_ALIGN_TOP_RIGHT, 0, 0);
|
|
lv_obj_add_event_cb(port_dropdown, onSelectBus, LV_EVENT_VALUE_CHANGED, nullptr);
|
|
lv_dropdown_set_selected(port_dropdown, 0);
|
|
data->portDropdownWidget = port_dropdown;
|
|
|
|
lv_obj_t* scan_list = lv_list_create(main_wrapper);
|
|
lv_obj_set_style_margin_top(scan_list, 8, 0);
|
|
lv_obj_set_width(scan_list, LV_PCT(100));
|
|
lv_obj_set_height(scan_list, LV_SIZE_CONTENT);
|
|
lv_obj_add_flag(scan_list, LV_OBJ_FLAG_HIDDEN);
|
|
data->scanListWidget = scan_list;
|
|
}
|
|
|
|
static void onHide(AppContext& app) {
|
|
auto data = std::static_pointer_cast<Data>(app.getData());
|
|
|
|
bool isRunning = false;
|
|
if (data->mutex.acquire(250 / portTICK_PERIOD_MS) == TtStatusOk) {
|
|
auto* timer = data->scanTimer.get();
|
|
if (timer != nullptr) {
|
|
isRunning = timer->isRunning();
|
|
}
|
|
data->mutex.release();
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
if (isRunning) {
|
|
stopScanning(data);
|
|
}
|
|
}
|
|
|
|
static void onStart(AppContext& app) {
|
|
auto data = std::make_shared<Data>();
|
|
app.setData(data);
|
|
}
|
|
|
|
extern const AppManifest manifest = {
|
|
.id = "I2cScanner",
|
|
.name = "I2C Scanner",
|
|
.icon = TT_ASSETS_APP_ICON_I2C_SETTINGS,
|
|
.type = TypeSystem,
|
|
.onStart = onStart,
|
|
.onShow = onShow,
|
|
.onHide = onHide
|
|
};
|
|
|
|
} // namespace
|