diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0a97a015..7729c810 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -397,6 +397,7 @@ list(APPEND SOURCE_FILES displayapp/screens/Alarm.cpp displayapp/screens/Styles.cpp displayapp/screens/WeatherSymbols.cpp + displayapp/screens/ASM.cpp displayapp/Colors.cpp displayapp/widgets/Counter.cpp displayapp/widgets/PageIndicator.cpp diff --git a/src/components/fs/FS.cpp b/src/components/fs/FS.cpp index 95b40824..6064a5fa 100644 --- a/src/components/fs/FS.cpp +++ b/src/components/fs/FS.cpp @@ -70,7 +70,11 @@ int FS::FileWrite(lfs_file_t* file_p, const uint8_t* buff, uint32_t size) { } int FS::FileSeek(lfs_file_t* file_p, uint32_t pos) { - return lfs_file_seek(&lfs, file_p, pos, LFS_SEEK_SET); + return FileSeek(file_p, pos, LFS_SEEK_SET); +} + +int FS::FileSeek(lfs_file_t* file_p, uint32_t pos, int whence) { + return lfs_file_seek(&lfs, file_p, pos, whence); } int FS::FileDelete(const char* fileName) { diff --git a/src/components/fs/FS.h b/src/components/fs/FS.h index aba30509..9daa1d39 100644 --- a/src/components/fs/FS.h +++ b/src/components/fs/FS.h @@ -17,6 +17,7 @@ namespace Pinetime { int FileRead(lfs_file_t* file_p, uint8_t* buff, uint32_t size); int FileWrite(lfs_file_t* file_p, const uint8_t* buff, uint32_t size); int FileSeek(lfs_file_t* file_p, uint32_t pos); + int FileSeek(lfs_file_t* file_p, uint32_t pos, int whence); int FileDelete(const char* fileName); diff --git a/src/displayapp/UserApps.h b/src/displayapp/UserApps.h index 67bbfa7d..d88d4842 100644 --- a/src/displayapp/UserApps.h +++ b/src/displayapp/UserApps.h @@ -2,6 +2,7 @@ #include "displayapp/apps/Apps.h" #include "Controllers.h" +#include "displayapp/screens/ASM.h" #include "displayapp/screens/Alarm.h" #include "displayapp/screens/Dice.h" #include "displayapp/screens/Timer.h" diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index 2104a267..fd4bd70a 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -42,7 +42,8 @@ namespace Pinetime { SettingChimes, SettingShakeThreshold, SettingBluetooth, - Error + Error, + ASM }; enum class WatchFace : uint8_t { diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index d7858760..824671c5 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -2,6 +2,7 @@ if(DEFINED ENABLE_USERAPPS) set(USERAPP_TYPES ${ENABLE_USERAPPS} CACHE STRING "List of user apps to build into the firmware") else () set(DEFAULT_USER_APP_TYPES "Apps::StopWatch") + set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::ASM") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Alarm") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Timer") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Steps") diff --git a/src/displayapp/screens/ASM.cpp b/src/displayapp/screens/ASM.cpp new file mode 100644 index 00000000..5c5a1302 --- /dev/null +++ b/src/displayapp/screens/ASM.cpp @@ -0,0 +1,564 @@ +#include "ASM.h" + +#include +#include +#include + +using namespace Pinetime::Applications::Screens; + +constexpr lv_font_t* fonts[] = { + &fontawesome_weathericons, + &jetbrains_mono_42, + &jetbrains_mono_76, + &jetbrains_mono_bold_20, + &jetbrains_mono_extrabold_compressed, + &lv_font_sys_48, + &open_sans_light, +}; +constexpr int num_fonts = sizeof(fonts) / sizeof(fonts[0]); + +constexpr uint32_t handler_return_pc_mark = 1 << 31; + +struct CallbackInfo { + ASM* instance; + uint32_t callback_pc; +}; + +static void event_handler(lv_obj_t* obj, lv_event_t event) { + CallbackInfo* cbInfo = static_cast(lv_obj_get_user_data(obj)); + cbInfo->instance->OnObjectEvent(obj, event); +} + +ASM::ASM(Controllers::DateTime& dateTimeController, + const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + Controllers::FS& fsController) + : dateTimeController(dateTimeController), batteryController(batteryController), bleController(bleController), fs(fsController) { + + int result = fsController.FileOpen(&file, "program.bin", LFS_O_RDONLY); + asm_assert(result >= 0); + + result = fsController.FileSeek(&file, 0, LFS_SEEK_END); + asm_assert(result >= 0); + program_size = result; + fsController.FileSeek(&file, 0, LFS_SEEK_SET); + + populate_cache(0); + + Refresh(); +} + +ASM::~ASM() { + if (taskRefresh != nullptr) { + lv_task_del(taskRefresh); + } + if (statusIcons) { + lv_obj_del(statusIcons->GetObject()); + } + + fs.FileClose(&file); + + // We don't need to clean the screen since all objects are deleted when their shared_ptr is dropped + // lv_obj_clean(lv_scr_act()); +} + +void ASM::populate_cache(size_t pos) { + int result = fs.FileSeek(&file, pos); + asm_assert(result >= 0); + + result = fs.FileRead(&file, cache, cache_size); + asm_assert(result >= 0); + + cache_start = pos; +} + +void ASM::run() { + bool stop = false; + + while (!stop && pc < program_size) { + OpcodeShort opcode = static_cast(read_byte(pc)); + if (static_cast(opcode) & (1 << 7)) { + // Long opcode + OpcodeLong opcode = static_cast(read_u16(pc)); + + pc += 2; + + switch (opcode) { + default: + NRF_LOG_ERROR("Unknown opcode: 0x%04X", opcode); + break; + } + } else { + pc++; + + switch (opcode) { + case OpcodeShort::WaitRefresh: + stop = true; + break; + + case OpcodeShort::Push0: + push(std::make_shared(0)); + break; + + case OpcodeShort::PushU8: + push(std::make_shared(read_byte(pc))); + pc++; + break; + + case OpcodeShort::PushU16: + push(std::make_shared(read_u16(pc))); + pc += 2; + break; + + case OpcodeShort::PushU24: + push(std::make_shared(read_u24(pc))); + pc += 3; + break; + + case OpcodeShort::PushU32: + push(std::make_shared(read_u32(pc))); + pc += 4; + break; + + case OpcodeShort::PushEmptyString: + push(std::make_shared(new char[1] {0}, 1)); + break; + + case OpcodeShort::Duplicate: + push(stack[stack_pointer - 1]); + break; + + case OpcodeShort::Pop: + pop(); + break; + + case OpcodeShort::LoadString: { + uint32_t ptr = pop_uint32(); + + int length = read_byte(ptr); + char* text = new char[length + 1]; + text[length] = '\0'; + + for (int i = 0; i < length; i++) { + text[i] = read_byte(ptr + 1 + i); + } + + push(std::make_shared(text, length + 1)); + break; + } + + case OpcodeShort::StoreLocal: + locals[read_byte(pc++)] = pop(); + break; + + case OpcodeShort::LoadLocal: + push(locals[read_byte(pc++)]); + break; + + case OpcodeShort::Branch: { + if (doBranch(pop_uint32())) + stop = true; + break; + } + + case OpcodeShort::BranchIfTrue: { + uint32_t to = pop_uint32(); + auto cond = pop(); + + if (cond->isTruthy() && doBranch(to)) + stop = true; + break; + } + + case OpcodeShort::Call: { + uint32_t next = pc; + pc = pop_uint32(); + push(std::make_shared(next)); + break; + } + + case OpcodeShort::StartPeriodicRefresh: + if (taskRefresh == nullptr) { + taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); + } + break; + + case OpcodeShort::StopPeriodicRefresh: + if (taskRefresh != nullptr) { + lv_task_del(taskRefresh); + taskRefresh = nullptr; + } + break; + + case OpcodeShort::SetLabelText: { + auto str = pop(String); + auto obj = pop(LvglObject); + + lv_label_set_text(obj->obj, str->str); + push(obj); + break; + } + + case OpcodeShort::SetArcRange: { + uint32_t max = pop_uint32(); + uint32_t min = pop_uint32(); + auto obj = pop(LvglObject); + lv_arc_set_range(obj->obj, min, max); + push(obj); + break; + } + + case OpcodeShort::SetArcRotation: { + uint32_t rot = pop_uint32(); + auto obj = pop(LvglObject); + lv_arc_set_rotation(obj->obj, rot); + push(obj); + break; + } + + case OpcodeShort::SetArcBgAngles: { + uint32_t end = pop_uint32(); + uint32_t start = pop_uint32(); + auto obj = pop(LvglObject); + lv_arc_set_bg_angles(obj->obj, start, end); + push(obj); + break; + } + + case OpcodeShort::SetArcAdjustable: { + auto val = pop(); + auto obj = pop(LvglObject); + lv_arc_set_adjustable(obj->obj, val->isTruthy()); + push(obj); + break; + } + + case OpcodeShort::SetArcStartAngle: { + uint32_t angle = pop_uint32(); + auto obj = pop(LvglObject); + lv_arc_set_start_angle(obj->obj, angle); + push(obj); + break; + } + + case OpcodeShort::SetArcValue: { + uint32_t value = pop_uint32(); + auto obj = pop(LvglObject); + lv_arc_set_value(obj->obj, value); + push(obj); + break; + } + + case OpcodeShort::CreateLabel: + push(std::make_shared(lv_label_create(lv_scr_act(), NULL))); + break; + + case OpcodeShort::CreateButton: + push(std::make_shared(lv_btn_create(lv_scr_act(), NULL))); + break; + + case OpcodeShort::CreateArc: + push(std::make_shared(lv_arc_create(lv_scr_act(), NULL))); + break; + + case OpcodeShort::SetObjectAlign: { + int16_t y = pop_uint32(); + int16_t x = pop_uint32(); + uint8_t align = pop_uint32(); + auto obj = pop(LvglObject); + lv_obj_align(obj->obj, lv_scr_act(), align, x, y); + push(obj); + break; + } + + case OpcodeShort::SetObjectSize: { + int16_t h = pop_uint32(); + int16_t w = pop_uint32(); + auto obj = pop(LvglObject); + lv_obj_set_size(obj->obj, w, h); + push(obj); + break; + } + + case OpcodeShort::SetObjectParent: { + auto parent = pop(LvglObject); + auto child = pop(LvglObject); + lv_obj_set_parent(child->obj, parent->obj); + push(child); + break; + } + + case OpcodeShort::SetStyleLocalInt: + case OpcodeShort::SetStyleLocalFont: + case OpcodeShort::SetStyleLocalColor: { + uint32_t value = pop_uint32(); + uint32_t prop = pop_uint32(); + uint32_t part = pop_uint32(); + auto obj = pop(LvglObject); + + switch (opcode) { + case OpcodeShort::SetStyleLocalInt: + _lv_obj_set_style_local_int(obj->obj, part, prop, value); + break; + + case OpcodeShort::SetStyleLocalColor: + _lv_obj_set_style_local_color(obj->obj, part, prop, lv_color_hex(value)); + break; + + case OpcodeShort::SetStyleLocalFont: { + if (value < num_fonts) { + _lv_obj_set_style_local_ptr(obj->obj, part, prop, fonts[value]); + } + break; + } + + default: + break; + } + + push(obj); + break; + } + + case OpcodeShort::SetEventHandler: { + uint32_t cb_pc = pop_uint32(); + auto obj = pop(LvglObject); + CallbackInfo* cb = new CallbackInfo {this, cb_pc}; + + lv_obj_set_user_data(obj->obj, cb); + lv_obj_set_event_cb(obj->obj, event_handler); + push(obj); + break; + } + + case OpcodeShort::Add: + push(std::make_shared(pop_uint32() + pop_uint32())); + break; + + case OpcodeShort::Subtract: { + uint32_t b = pop_uint32(); + uint32_t a = pop_uint32(); + push(std::make_shared(a - b)); + break; + } + + case OpcodeShort::Multiply: + push(std::make_shared(pop_uint32() * pop_uint32())); + break; + + case OpcodeShort::Divide: { + uint32_t b = pop_uint32(); + uint32_t a = pop_uint32(); + push(std::make_shared(a / b)); + break; + } + + case OpcodeShort::GrowString: { + auto len = pop_uint32(); + auto str = pop(String); + + size_t new_cap = len + str->capacity; + + char* new_str = new char[new_cap]; + memcpy(new_str, str->str, str->capacity); + + push(std::make_shared(new_str, new_cap)); + break; + } + + case OpcodeShort::ClearString: { + auto str = pop(String); + if (str->capacity > 0) + str->str[0] = '\0'; + + push(str); + break; + } + + case OpcodeShort::Concat: { + auto b = pop(); + auto a = pop(); + + if (a->type() == String && b->type() == String) { + auto aString = static_cast(a.get()); + auto bString = static_cast(b.get()); + + int len_a = strlen(aString->str); + int len_b = strlen(bString->str); + + size_t new_len = len_a + len_b + 1; + + if (aString->capacity >= new_len) { + strcat(aString->str, bString->str); + + push(a); + } else { + char* s = new char[new_len + 1]; + strcpy(s, aString->str); + strcat(s, bString->str); + + push(std::make_shared(s, new_len + 1)); + } + } else if (a->type() == String && b->type() == Integer) { + auto aString = static_cast(a.get()); + auto bInt = static_cast(b.get()); + + size_t aLen = strlen(aString->str); + size_t need_cap = aLen + 12 + 1; + + if (aString->capacity - aLen >= need_cap) { + snprintf(aString->str + aLen, aString->capacity - aLen, "%lu", bInt->i); + + push(a); + } else { + char* s = new char[need_cap]; + memcpy(s, aString->str, aLen); + snprintf(s + aLen, need_cap - aLen, "%lu", bInt->i); + + push(std::make_shared(s, need_cap)); + } + } else { + asm_assert(false); + } + break; + } + + case OpcodeShort::PushCurrentDateTime: { + auto time = dateTimeController.CurrentDateTime(); + std::tm tm { + .tm_sec = dateTimeController.Seconds(), + .tm_min = dateTimeController.Minutes(), + .tm_hour = dateTimeController.Hours(), + .tm_mday = dateTimeController.Day(), + .tm_mon = static_cast(dateTimeController.Month()) - 1, + .tm_year = dateTimeController.Year() - 1900, + .tm_wday = static_cast(dateTimeController.DayOfWeek()), + .tm_yday = dateTimeController.DayOfYear() - 1, + }; + + push(std::make_shared(time, tm)); + break; + } + + case OpcodeShort::PushCurrentTicks: + push(std::make_shared((xTaskGetTickCount() * configTICK_RATE_HZ) / 1000)); + break; + + case OpcodeShort::FormatDateTime: { + auto fmt = pop(String); + auto time = pop(DateTime); + + constexpr int max_len = 16; + char* str = new char[max_len]; // TODO: Allow user to reuse string in stack + + strftime(str, max_len, fmt->str, &time->tm); + + push(std::make_shared(str, max_len)); + break; + } + + case OpcodeShort::RealignObject: + lv_obj_realign(pop(LvglObject)->obj); + break; + + case OpcodeShort::ShowStatusIcons: + if (!statusIcons) { + statusIcons = std::make_unique(batteryController, bleController); + statusIcons->Create(); + } + break; + + case OpcodeShort::Equals: { + auto b = pop(); + auto a = pop(); + push(std::make_shared(a.get()->compare(b.get()) == 0 ? 1 : 0)); + break; + } + + case OpcodeShort::Greater: { + auto b = pop(); + auto a = pop(); + push(std::make_shared(a.get()->compare(b.get()) > 0 ? 1 : 0)); + break; + } + + case OpcodeShort::Lesser: { + auto b = pop(); + auto a = pop(); + push(std::make_shared(a.get()->compare(b.get()) < 0 ? 1 : 0)); + break; + } + + case OpcodeShort::Negate: + push(std::make_shared(pop().get()->isTruthy() ? 0 : 1)); + break; + + case OpcodeShort::GetDateTimeHour: { + auto time = pop(DateTime); + push(std::make_shared(time->tm.tm_hour)); + break; + } + + case OpcodeShort::GetDateTimeMinute: { + auto time = pop(DateTime); + push(std::make_shared(time->tm.tm_min)); + break; + } + + case OpcodeShort::GetDateTimeSecond: { + auto time = pop(DateTime); + push(std::make_shared(time->tm.tm_sec)); + break; + } + + default: + NRF_LOG_ERROR("Unknown opcode: 0x%02X", opcode); + break; + } + } + } +} + +void ASM::Refresh() { + run(); + + if (statusIcons) { + statusIcons->Update(); + } +} + +void ASM::_asm_assert(bool condition, const char* msg) { + if (!condition) { + // TODO: Handle better + + if (msg) + NRF_LOG_ERROR("Assertion failed: %s", msg); + + for (;;) { + } + } +} + +bool ASM::doBranch(uint32_t to) { + if ((to & handler_return_pc_mark) != 0) { + pc = to & ~handler_return_pc_mark; + return true; + } + + pc = to; + return false; +} + +void ASM::OnObjectEvent(lv_obj_t* obj, lv_event_t event) { + if (event != LV_EVENT_CLICKED) + return; + + CallbackInfo* cb = static_cast(lv_obj_get_user_data(obj)); + + if (cb) { + push(std::make_shared(pc | handler_return_pc_mark)); + pc = cb->callback_pc; + + run(); + } +} diff --git a/src/displayapp/screens/ASM.h b/src/displayapp/screens/ASM.h new file mode 100644 index 00000000..ae257f29 --- /dev/null +++ b/src/displayapp/screens/ASM.h @@ -0,0 +1,319 @@ +#pragma once + +#include "displayapp/screens/Screen.h" +#include "displayapp/apps/Apps.h" +#include "displayapp/Controllers.h" +#include "displayapp/widgets/StatusIcons.h" +#include "components/datetime/DateTimeController.h" +#include "Symbols.h" + +#include +#include +#include + +#if DEBUG + #define STRINGIZE_DETAIL(x) #x + #define STRINGIZE(x) STRINGIZE_DETAIL(x) + #define asm_assert(condition) _asm_assert(condition, __FILE__ ":" STRINGIZE(__LINE__) " " #condition) +#else + #define asm_assert(condition) _asm_assert(condition, NULL) +#endif + +namespace Pinetime { + namespace Applications { + namespace Screens { + class ASM : public Screen { + public: + ASM(Controllers::DateTime&, const Controllers::Battery&, const Controllers::Ble&, Controllers::FS&); + ~ASM(); + + void Refresh() override; + + void OnObjectEvent(lv_obj_t* obj, lv_event_t event); + + private: + static constexpr int num_slots = 16; + static constexpr int max_locals = 16; + static constexpr int stack_size = 32; + static constexpr int cache_size = 16; + + enum DataType : uint8_t { Integer, String, LvglObject, DateTime }; + + // TODO: Use fancy C++ type stuff + struct Value { + virtual DataType type() = 0; + virtual int compare(Value* other) = 0; + virtual bool isTruthy() = 0; + }; + + struct ValueInteger : public Value { + uint32_t i; + + ValueInteger(uint32_t i) : i(i) { + } + + DataType type() override { + return Integer; + } + + int compare(Value* other) override { + if (other->type() != Integer) { + return -1; + } + + auto otherInt = static_cast(other)->i; + + if (i < otherInt) { + return -1; + } else if (i > otherInt) { + return 1; + } + + return 0; + } + + bool isTruthy() override { + return i != 0; + } + }; + + struct ValueString : public Value { + char* str; + uint16_t capacity; + + ValueString(char* str, uint16_t cap) : str(str), capacity(cap) { + } + + ~ValueString() { + delete[] str; + } + + DataType type() override { + return String; + } + + int compare(Value* other) override { + if (other->type() != String) { + return -1; + } + + return strcmp(str, static_cast(other)->str); + } + + bool isTruthy() override { + return capacity > 0 && str[0] != '\0'; + } + }; + + struct ValueLvglObject : public Value { + lv_obj_t* obj; + + ValueLvglObject(lv_obj_t* obj) : obj(obj) { + } + + ~ValueLvglObject() { + lv_obj_del(obj); + } + + DataType type() override { + return LvglObject; + } + + int compare(Value*) override { + return -1; + } + + bool isTruthy() override { + return obj != nullptr; + } + }; + + struct ValueDateTime : public Value { + std::chrono::time_point time; + std::tm tm; + + ValueDateTime(std::chrono::time_point time, std::tm tm) + : time(time), tm(tm) { + } + + DataType type() override { + return DateTime; + } + + int compare(Value* other) override { + if (other->type() != DateTime) { + return -1; + } + + auto otherTime = static_cast(other)->time; + + if (time < otherTime) { + return -1; + } else if (time > otherTime) { + return 1; + } + + return 0; + } + + bool isTruthy() override { + return true; + } + }; + + enum class OpcodeShort : uint8_t { + StoreLocal, + LoadLocal, + Branch, + BranchIfTrue, + Call, + Push0, + PushU8, + PushU16, + PushU24, + PushU32, + PushEmptyString, + PushCurrentDateTime, + PushCurrentTicks, + Duplicate, + Pop, + LoadString, + + StartPeriodicRefresh, + StopPeriodicRefresh, + ShowStatusIcons, + + CreateLabel, + CreateButton, + CreateArc, + SetLabelText, + SetArcRange, + SetArcRotation, + SetArcBgAngles, + SetArcAdjustable, + SetArcStartAngle, + SetArcValue, + SetObjectAlign, + SetObjectSize, + SetObjectParent, + SetStyleLocalInt, + SetStyleLocalColor, + SetStyleLocalOpa, + SetStyleLocalFont, + SetEventHandler, + RealignObject, + WaitRefresh, + + Add, + Subtract, + Multiply, + Divide, + Equals, + Greater, + Lesser, + Negate, + + GrowString, + ClearString, + Concat, + FormatDateTime, + + GetDateTimeHour, + GetDateTimeMinute, + GetDateTimeSecond + }; + + enum class OpcodeLong : uint16_t {}; + + void populate_cache(size_t pos); + + uint8_t read_byte(size_t pos) { + if (pos < cache_start || pos >= cache_start + cache_size) { + populate_cache(pos); + } + + return cache[pos - cache_start]; + } + + uint16_t read_u16(size_t pos) { + return static_cast(read_byte(pos + 1) << 8 | read_byte(pos)); + } + + uint32_t read_u24(size_t pos) { + return static_cast(read_byte(pos + 2) << 16 | read_byte(pos + 1) << 8 | read_byte(pos)); + } + + uint32_t read_u32(size_t pos) { + return static_cast(read_byte(pos + 3) << 24 | read_byte(pos + 2) << 16 | read_byte(pos + 1) << 8 | read_byte(pos)); + } + + lfs_file_t file; + uint8_t cache[cache_size]; + size_t cache_start; + size_t program_size; + size_t pc = 0; + + std::shared_ptr locals[max_locals] = {}; + + std::shared_ptr stack[stack_size] = {}; + uint8_t stack_pointer = 0; + + lv_task_t* taskRefresh = nullptr; + + Controllers::DateTime& dateTimeController; + const Controllers::Battery& batteryController; + const Controllers::Ble& bleController; + Controllers::FS& fs; + std::unique_ptr statusIcons; + + void run(); + void _asm_assert(bool condition, const char* msg); + bool doBranch(uint32_t to); + + std::shared_ptr pop() { + asm_assert(stack_pointer > 0); + + stack_pointer--; + + auto v = stack[stack_pointer]; + stack[stack_pointer] = nullptr; + + return v; + } + + template + std::shared_ptr pop(DataType wantType) + requires(std::is_base_of_v) + { + auto v = pop(); + asm_assert(v->type() == wantType); + + return std::static_pointer_cast(v); + } + + uint32_t pop_uint32() { + return pop(Integer)->i; + } + + void push(std::shared_ptr v) { + asm_assert(stack_pointer < stack_size); + + stack[stack_pointer++] = v; + } + }; + } + + template <> + struct AppTraits { + static constexpr Apps app = Apps::ASM; + static constexpr const char* icon = Screens::Symbols::eye; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::ASM(controllers.dateTimeController, + controllers.batteryController, + controllers.bleController, + controllers.filesystem); + }; + }; + }; +} \ No newline at end of file