diff --git a/firmware/application/app_settings.cpp b/firmware/application/app_settings.cpp index bbae046f9..51c7a13f2 100644 --- a/firmware/application/app_settings.cpp +++ b/firmware/application/app_settings.cpp @@ -50,6 +50,9 @@ void BoundSetting::parse(std::string_view value) { case SettingType::I64: parse_int(value, as()); break; + case SettingType::U64: + parse_int(value, as()); + break; case SettingType::I32: parse_int(value, as()); break; @@ -87,6 +90,9 @@ void BoundSetting::write(File& file) const { case SettingType::I64: file.write(to_string_dec_int(as(), buffer, length), length); break; + case SettingType::U64: + file.write(to_string_dec_uint(as(), buffer, length), length); + break; case SettingType::I32: file.write(to_string_dec_int(as(), buffer, length), length); break; @@ -97,9 +103,8 @@ void BoundSetting::write(File& file) const { file.write(to_string_dec_uint(as(), buffer, length), length); break; case SettingType::String: { - const auto& str = as(); - file.write(str.data(), str.length()); - break; + file.write_line(as()); + return; } case SettingType::Bool: file.write(as() ? "1" : "0", 1); diff --git a/firmware/application/app_settings.hpp b/firmware/application/app_settings.hpp index 75cd5c44a..27b82db63 100644 --- a/firmware/application/app_settings.hpp +++ b/firmware/application/app_settings.hpp @@ -48,6 +48,7 @@ class BoundSetting { /* The type of bound setting. */ enum class SettingType : uint8_t { I64, + U64, I32, U32, U8, @@ -60,6 +61,9 @@ class BoundSetting { BoundSetting(std::string_view name, int64_t* target) : name_{name}, target_{target}, type_{SettingType::I64} {} + BoundSetting(std::string_view name, uint64_t* target) + : name_{name}, target_{target}, type_{SettingType::U64} {} + BoundSetting(std::string_view name, int32_t* target) : name_{name}, target_{target}, type_{SettingType::I32} {} diff --git a/firmware/application/external/external.cmake b/firmware/application/external/external.cmake index c2af860b2..54b4b562e 100644 --- a/firmware/application/external/external.cmake +++ b/firmware/application/external/external.cmake @@ -327,7 +327,7 @@ set(EXTCPPSRC external/rtty_tx/ui_rtty_tx.cpp external/rtty_tx/baudot.cpp - #pocsag_tx + #pocsag_tx external/pocsag_tx/main.cpp external/pocsag_tx/ui_pocsag_tx.cpp @@ -362,6 +362,11 @@ set(EXTCPPSRC #hard_reset external/hard_reset/main.cpp external/hard_reset/ui_hard_reset.cpp + + #secplustx + external/secplustx/main.cpp + external/secplustx/ui_secplustx.cpp + external/secplustx/secplustx.cpp ) set(EXTAPPLIST @@ -451,6 +456,7 @@ set(EXTAPPLIST two_tone_pager two_tone_rx hard_reset + secplustx ) # sdusb has type conflicts with PRALINE (HackRF Pro) - add only for non-PRALINE builds diff --git a/firmware/application/external/external.ld b/firmware/application/external/external.ld index b37a60518..cb93f8a9f 100644 --- a/firmware/application/external/external.ld +++ b/firmware/application/external/external.ld @@ -110,6 +110,7 @@ MEMORY ram_external_app_two_tone_rx (rwx) : org = 0xAE050000, len = 32k ram_external_app_flex_tx (rwx) : org = 0xAE060000, len = 32k ram_external_app_hard_reset (rwx) : org = 0xAE070000, len = 32k + ram_external_app_secplustx (rwx) : org = 0xAE080000, len = 32k } SECTIONS @@ -635,4 +636,10 @@ SECTIONS KEEP(*(.external_app.app_hard_reset.application_information)); *(*ui*external_app*hard_reset*); } > ram_external_app_hard_reset + + .external_app_secplustx : ALIGN(4) SUBALIGN(4) + { + KEEP(*(.external_app.app_secplustx.application_information)); + *(*ui*external_app*secplustx*); + } > ram_external_app_secplustx } diff --git a/firmware/application/external/secplustx/main.cpp b/firmware/application/external/secplustx/main.cpp new file mode 100644 index 000000000..71299c994 --- /dev/null +++ b/firmware/application/external/secplustx/main.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2026 Synray + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ui.hpp" +#include "ui_navigation.hpp" +#include "ui_secplustx.hpp" +#include "external_app.hpp" + +namespace ui::external_app::ui_secplustx { +void initialize_app(ui::NavigationView& nav) { + nav.push(); +} +} // namespace ui::external_app::ui_secplustx + +extern "C" { + +__attribute__((section(".external_app.app_secplustx.application_information"), used)) application_information_t _application_information_secplustx = { + /*.memory_location = */ (uint8_t*)0x00000000, + /*.externalAppEntry = */ ui::external_app::ui_secplustx::initialize_app, + /*.header_version = */ CURRENT_HEADER_VERSION, + /*.app_version = */ VERSION_MD5, + /*.app_name = */ "Security+", + /*.bitmap_data = */ { + 0b00100000, + 0b00000000, + 0b00100000, + 0b00100000, + 0b00100000, + 0b01110000, + 0b00100000, + 0b00100000, + 0b11100000, + 0b00000111, + 0b11110000, + 0b00001111, + 0b00110000, + 0b00001100, + 0b00110000, + 0b00001100, + 0b11110000, + 0b00001111, + 0b11110000, + 0b00001111, + 0b01110000, + 0b00001101, + 0b10110000, + 0b00001110, + 0b01110000, + 0b00001101, + 0b10110000, + 0b00001110, + 0b11110000, + 0b00001111, + 0b11100000, + 0b00000111, + }, + /*.icon_color = */ ui::Color::yellow().v, + /*.menu_location = */ app_location_t::TX, + /*.desired_menu_position = */ -1, + /*.m4_app_tag = portapack::spi_flash::image_tag_ook */ {'P', 'O', 'O', 'K'}, + /*.m4_app_offset = */ 0x00000000, // will be filled at compile time +}; +} diff --git a/firmware/application/external/secplustx/secplustx.cpp b/firmware/application/external/secplustx/secplustx.cpp new file mode 100644 index 000000000..59200f1fe --- /dev/null +++ b/firmware/application/external/secplustx/secplustx.cpp @@ -0,0 +1,150 @@ +/* + * Copyright 2022 Clayton Smith (argilo@gmail.com) + * + * This file is part of secplus. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "secplustx.hpp" + +namespace ui::external_app::ui_secplustx { + +static void v2_calc_parity(const uint64_t fixed, uint32_t* data) { + uint32_t parity = (fixed >> 32) & 0xf; + int8_t offset; + + *data &= 0xffff0fff; + for (offset = 0; offset < 32; offset += 4) { + parity ^= ((*data >> offset) & 0xf); + } + *data |= (parity << 12); +} + +static void encode_v2_rolling(const uint32_t rolling, + uint32_t* rolling_halves) { + uint32_t rolling_reversed = 0; + int8_t i, half; + + for (i = 0; i < 28; i++) { + rolling_reversed |= ((rolling >> i) & 1) << (28 - i - 1); + } + + rolling_halves[0] = 0; + rolling_halves[1] = 0; + + for (half = 0; half < 2; half++) { + for (i = 0; i < 8; i += 2) { + rolling_halves[half] |= rolling_reversed % 3 << i; + rolling_reversed /= 3; + } + } + + for (half = 0; half < 2; half++) { + for (i = 10; i < 18; i += 2) { + rolling_halves[half] |= rolling_reversed % 3 << i; + rolling_reversed /= 3; + } + } + + rolling_halves[0] |= (rolling_reversed % 3) << 8; + rolling_reversed /= 3; + + rolling_halves[1] |= (rolling_reversed % 3) << 8; +} + +static const int8_t ORDER[16] = {9, 33, 6, -1, 24, 18, 36, -1, + 24, 36, 6, -1, -1, -1, -1, -1}; +static const int8_t INVERT[16] = {6, 2, 1, -1, 7, 5, 3, -1, + 4, 0, 5, -1, -1, -1, -1, -1}; + +static void v2_scramble(const uint32_t* parts, const uint8_t frame_type, uint8_t* packet_half) { + const int8_t order = ORDER[packet_half[0] >> 4]; + const int8_t invert = INVERT[packet_half[0] & 0xf]; + int8_t i; + uint8_t out_offset = 10; + int8_t end; + uint32_t parts_permuted[3]; + + end = (frame_type == 0 ? 5 : 8); + for (i = 1; i < end; i++) { + packet_half[i] = 0; + } + + parts_permuted[0] = + (invert & 4) ? ~parts[(order >> 4) & 3] : parts[(order >> 4) & 3]; + parts_permuted[1] = + (invert & 2) ? ~parts[(order >> 2) & 3] : parts[(order >> 2) & 3]; + parts_permuted[2] = (invert & 1) ? ~parts[order & 3] : parts[order & 3]; + + end = (frame_type == 0 ? 8 : 0); + for (i = 18 - 1; i >= end; i--) { + packet_half[out_offset >> 3] |= ((parts_permuted[0] >> i) & 1) + << (7 - (out_offset % 8)); + out_offset++; + packet_half[out_offset >> 3] |= ((parts_permuted[1] >> i) & 1) + << (7 - (out_offset % 8)); + out_offset++; + packet_half[out_offset >> 3] |= ((parts_permuted[2] >> i) & 1) + << (7 - (out_offset % 8)); + out_offset++; + } +} + +static void encode_v2_half_parts(const uint32_t rolling, const uint32_t fixed, const uint16_t data, const uint8_t frame_type, uint8_t* packet_half) { + uint32_t parts[3]; + + parts[0] = ((fixed >> 10) << 8) | (data >> 8); + parts[1] = ((fixed & 0x3ff) << 8) | (data & 0xff); + parts[2] = rolling; + + packet_half[0] = (uint8_t)rolling; + + v2_scramble(parts, frame_type, packet_half); +} + +static int8_t v2_check_limits(const uint32_t rolling, const uint64_t fixed) { + if ((rolling >> 28) != 0) { + return -1; + } + + if ((fixed >> 40) != 0) { + return -1; + } + + return 0; +} + +static void encode_v2_half(const uint32_t rolling, const uint32_t fixed, const uint16_t data, const uint8_t frame_type, uint8_t* packet_half) { + encode_v2_half_parts(rolling, fixed, data, frame_type, packet_half); + + /* shift indicator two bits to the right */ + packet_half[1] |= (packet_half[0] & 0x3) << 6; + packet_half[0] >>= 2; + + /* set frame type */ + packet_half[0] |= (frame_type << 6); +} + +int8_t encode_v2(const uint32_t rolling, const uint64_t fixed, uint32_t data, const uint8_t frame_type, uint8_t* packet1, uint8_t* packet2) { + int8_t err = 0; + uint32_t rolling_halves[2]; + + err = v2_check_limits(rolling, fixed); + if (err < 0) { + return err; + } + + encode_v2_rolling(rolling, rolling_halves); + v2_calc_parity(fixed, &data); + + encode_v2_half(rolling_halves[0], fixed >> 20, data >> 16, frame_type, + packet1); + encode_v2_half(rolling_halves[1], fixed & 0xfffff, data & 0xffff, frame_type, + packet2); + + return 0; +} + +}; // namespace ui::external_app::ui_secplustx diff --git a/firmware/application/external/secplustx/secplustx.hpp b/firmware/application/external/secplustx/secplustx.hpp new file mode 100644 index 000000000..0cbb52eae --- /dev/null +++ b/firmware/application/external/secplustx/secplustx.hpp @@ -0,0 +1,19 @@ +/* + * Copyright 2022 Clayton Smith (argilo@gmail.com) + * + * This file is part of secplus. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef __SECPLUSTX__ +#define __SECPLUSTX__ + +#include + +namespace ui::external_app::ui_secplustx { +int8_t encode_v2(const uint32_t rolling, const uint64_t fixed, uint32_t data, const uint8_t frame_type, uint8_t* packet1, uint8_t* packet2); +}; + +#endif diff --git a/firmware/application/external/secplustx/ui_secplustx.cpp b/firmware/application/external/secplustx/ui_secplustx.cpp new file mode 100644 index 000000000..d84f82764 --- /dev/null +++ b/firmware/application/external/secplustx/ui_secplustx.cpp @@ -0,0 +1,227 @@ +#include "ui_secplustx.hpp" + +#include +#include +#include +#include + +#include "secplustx.hpp" +#include "ui_fileman.hpp" +#include "file_reader.hpp" +#include "baseband_api.hpp" +#include "string_format.hpp" +#include "optional.hpp" + +using namespace portapack; +using namespace ui; + +namespace fs = std::filesystem; + +namespace ui::external_app::ui_secplustx { + +static constexpr uint8_t secplus_repeat_count = 3; +static constexpr float secplus_sample_rate = OOK_SAMPLERATE / 4000.0f; + +Optional SecplusTXView::read_secplus_file(const fs::path& file_path) { + File file{}; + if (auto error = file.open(file_path)) return {}; + + std::string raw = *FileLineReader{file}.begin(); + std::vector chunks = split_string(raw, ';'); + if (chunks.empty()) return {}; + + SecplusData data{}; + data.version = static_cast(std::strtoul(chunks[0].data(), NULL, 10)); + switch (data.version) { + case SecplusVersion::V1: + return {}; // unimplemented + case SecplusVersion::V2: { + if (chunks.size() != 6) return {}; + data.name = std::string{chunks[1]}; + if (data.name.empty()) data.name = "Remote"; + data.has_data = std::strtoul(chunks[2].data(), NULL, 10); + data.fixed_code = std::strtoull(chunks[3].data(), NULL, 16); + data.rolling_code = std::strtoul(chunks[4].data(), NULL, 16); + data.data = std::strtoul(chunks[5].data(), NULL, 16); + return data; + } + default: + return {}; + } +} + +bool SecplusTXView::write_secplus_file(const fs::path& file_path, const SecplusData& data) { + ensure_directory(secplus_dir); + File file{}; + if (auto error = file.create(file_path)) { + return false; + } + + switch (data.version) { + case SecplusVersion::V1: + return false; // unimplemented + case SecplusVersion::V2: { + std::string formatted = to_string_dec_uint(static_cast(data.version)); + formatted += ';'; + formatted += data.name; + formatted += ';'; + formatted += to_string_dec_uint(data.has_data); + formatted += ';'; + formatted += to_string_hex(data.fixed_code); + formatted += ';'; + formatted += to_string_hex(data.rolling_code); + formatted += ';'; + formatted += to_string_hex(data.data); + file.write_line(formatted); + file.close(); + return true; + } + default: + return false; + } +} + +SecplusTXView::SecplusTXView(NavigationView& nav) + : nav_{nav} { + if (!fs::file_exists(file_path)) write_secplus_file(file_path, data); + baseband::run_image(portapack::spi_flash::image_tag_ook); + + add_children({&button_open, + &button_save, + &field_name, + &labels, + &field_fixed, + &field_rolling, + &has_data, + &field_data, + &learn_mode, + &autosave, + &progressbar, + &tx_view}); + + button_open.on_select = [this](const Button&) { + ensure_directory(secplus_dir); + auto open_view = nav_.push(".SECPLUS"); + open_view->push_dir(secplus_dir); + open_view->on_changed = [this](fs::path path) { file_path = path.string(); }; + nav_.set_on_pop([this]() { reload_data(); }); + }; + button_save.on_select = [this](const Button&) { write_secplus_file(file_path, data); }; + + field_name.on_select = [this, &nav](TextField&) { + buffer = data.name; + text_prompt(nav_, buffer, field_name.parent_rect().width() / UI_POS_DEFAULT_WIDTH, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& new_name) { + data.name = new_name; + field_name.set_text(new_name); + save_data(); + }); + }; + field_fixed.on_change = [this](SymField& field) { data.fixed_code = field.to_integer(); }; + field_rolling.on_change = [this](SymField& field) { data.rolling_code = field.to_integer(); }; + field_data.on_change = [this](SymField& field) { data.data = field.to_integer(); }; + has_data.on_select = [this](Checkbox& checkbox, bool value) { + data.has_data = value; + // fade if inactive, otherwise use default style + const Style* new_style = value ? &style() : Theme::getInstance()->fg_medium; + checkbox.set_style(new_style); + field_data.set_style(new_style); + field_data.set_focusable(value); + }; + tx_view.on_edit_frequency = [this]() { + auto new_view = nav_.push(transmitter_model.target_frequency()); + new_view->on_changed = [](rf::Frequency f) { + transmitter_model.set_target_frequency(f); + }; + }; + + tx_view.on_start = [this]() { start_tx(); }; + tx_view.on_stop = [this]() { + baseband::kill_ook(); + stop_tx(); + }; + + autosave.set_value(true); + reload_data(); +} + +void SecplusTXView::save_data() { + if (autosave.value()) write_secplus_file(file_path, data); +} + +void SecplusTXView::reload_data() { + if (auto result = read_secplus_file(file_path)) data = std::move(*result); + // always update data, use default values if read failed + field_name.set_text(data.name); + has_data.set_value(data.has_data); + field_rolling.set_value(data.rolling_code); + field_fixed.set_value(data.fixed_code); + field_data.set_value(data.data); +} + +void SecplusTXView::start_tx() { + uint8_t packet1[8]{}; + uint8_t packet2[8]{}; + size_t bitstream_length = 0; + + constexpr uint8_t packet1_indicator = 0b00; + constexpr uint8_t packet2_indicator = 0b01; + auto encode_packet = [&bitstream_length](uint8_t indicator, auto& packet, bool has_data) { + constexpr uint32_t preamble = 0b0000000000000000'1111; + constexpr uint32_t blank_size = 24; + auto manchester_encode = [&bitstream_length](auto& x, uint32_t size) { + for (uint32_t i = 0; i < size; ++i) { + bool bit = (x >> (size - 1 - i)) & 1; + encoders::bitstream_append(bitstream_length, 2, bit ? 0b01 : 0b10); + } + }; + manchester_encode(preamble, 20); + manchester_encode(indicator, 2); + for (uint8_t byte = 0; byte < (has_data ? 8 : 5); ++byte) manchester_encode(packet[byte], 8); + encoders::bitstream_append(bitstream_length, blank_size, 0); + }; + + if (encode_v2(data.rolling_code, data.fixed_code, data.data, data.has_data, packet1, packet2) < 0) { + nav_.display_modal("Error", "Invalid rolling/fixed code"); + return; + } + encode_packet(packet1_indicator, packet1, has_data.value()); + encode_packet(packet2_indicator, packet2, has_data.value()); + + if (!learn_mode.value()) { + field_rolling.set_value(++data.rolling_code); + field_rolling.set_dirty(); + } + + progressbar.set_max(secplus_repeat_count); + progressbar.set_value(1); + + tx_view.set_transmitting(true); + transmitter_model.enable(); + baseband::set_ook_data( + bitstream_length, + secplus_sample_rate, + secplus_repeat_count, + 0); +} + +void SecplusTXView::stop_tx() { + transmitter_model.disable(); + progressbar.set_value(0); + tx_view.set_transmitting(false); +} + +void SecplusTXView::on_tx_progress(uint32_t progress, bool done) { + progressbar.set_value(progress + 1); + if (done) { + stop_tx(); + save_data(); + } +} + +SecplusTXView::~SecplusTXView() { + transmitter_model.disable(); + baseband::shutdown(); + save_data(); +} + +} // namespace ui::external_app::ui_secplustx diff --git a/firmware/application/external/secplustx/ui_secplustx.hpp b/firmware/application/external/secplustx/ui_secplustx.hpp new file mode 100644 index 000000000..cc89a54c8 --- /dev/null +++ b/firmware/application/external/secplustx/ui_secplustx.hpp @@ -0,0 +1,96 @@ +#ifndef __UI_SECPLUSTX__ +#define __UI_SECPLUSTX__ + +#include +#include + +#include "ui.hpp" +#include "file.hpp" +#include "ui_transmitter.hpp" +#include "transmitter_model.hpp" +#include "file_path.hpp" +#include "app_settings.hpp" +#include "radio_state.hpp" +#include "encoders.hpp" +#include "string_format.hpp" + +namespace ui::external_app::ui_secplustx { + +enum class SecplusVersion : uint8_t { + V1, + V2, +}; + +struct SecplusData { + SecplusVersion version; + std::string name; + bool has_data; + uint64_t fixed_code; + uint32_t rolling_code; + uint32_t data; +}; + +class SecplusTXView : public View { + public: + void focus() override { button_open.focus(); } + SecplusTXView(NavigationView& nav); + ~SecplusTXView(); + + std::string title() const override { + return "Security+"; + } + + private: + std::string file_path = (secplus_dir / "DEFAULT.SECPLUS").string(); + SecplusData data{SecplusVersion::V2, "Remote", false, 0, 0, 0}; + std::string buffer{}; + + NavigationView& nav_; + TxRadioState radio_state_{ + 315000000, + 1750000, + OOK_SAMPLERATE}; + + app_settings::SettingsManager settings_{"tx_secplus", app_settings::Mode::TX, {{"file_path"sv, &file_path}}}; + + Button button_open{{UI_POS_X(1), UI_POS_Y(1), screen_width / 2 - UI_POS_X(1), UI_POS_HEIGHT(2)}, "Open"}; + Button button_save{{screen_width / 2, UI_POS_Y(1), screen_width / 2 - UI_POS_X(1), UI_POS_HEIGHT(2)}, "Save"}; + + // remote data + TextField field_name{{UI_POS_X(1), UI_POS_Y(3), UI_POS_WIDTH_REMAINING(2), UI_POS_HEIGHT(1)}, "Remote"}; + Labels labels{ + {{UI_POS_X(1), UI_POS_Y(4)}, "Fixed:", Theme::getInstance()->fg_medium->foreground}, + {{UI_POS_X(1), UI_POS_Y(5)}, "Rolling:", Theme::getInstance()->fg_medium->foreground}}; + SymField field_fixed{{UI_POS_X(10), UI_POS_Y(4)}, 10, SymField::Type::Hex, true}; + SymField field_rolling{{UI_POS_X(10), UI_POS_Y(5)}, 7, SymField::Type::Hex, true}; + Checkbox has_data{{UI_POS_X(1), UI_POS_Y(6)}, 5, "Data:", true}; + SymField field_data{{UI_POS_X(10), UI_POS_Y(6)}, 8, SymField::Type::Hex, true}; + + // options + Checkbox learn_mode{{UI_POS_X(1), UI_POS_Y(7)}, 5, "Learn", true}; + Checkbox autosave{{UI_POS_X(1), UI_POS_Y(8)}, 8, "Autosave", true}; + + ProgressBar progressbar{{UI_POS_X(2), UI_POS_Y_BOTTOM(5.75), UI_POS_WIDTH_REMAINING(4), UI_POS_HEIGHT(1)}}; + + TransmitterView tx_view{UI_POS_Y_BOTTOM(4), 1000000, 0}; + + Optional read_secplus_file(const std::filesystem::path& file_path); + bool write_secplus_file(const std::filesystem::path& file_path, const SecplusData& data); + void save_data(); + void reload_data(); + void start_tx(); + void stop_tx(); + + void on_tx_progress(uint32_t progress, bool done); + + MessageHandlerRegistration message_handler_tx_progress{ + Message::ID::TXProgress, + [this](const Message* const p) { + const auto message = *reinterpret_cast(p); + this->on_tx_progress(message.progress, message.done); + }}; +}; + +} // namespace ui::external_app::ui_secplustx + +#endif diff --git a/firmware/application/file_path.cpp b/firmware/application/file_path.cpp index 95e504957..982d82b72 100644 --- a/firmware/application/file_path.cpp +++ b/firmware/application/file_path.cpp @@ -57,4 +57,5 @@ const std::filesystem::path macaddress_dir = u"MACADDRESS"; const std::filesystem::path splash_dot_bmp = u"/splash.bmp"; const std::filesystem::path keeloq_keys_dir = u"KEELOQKEYS"; const std::filesystem::path keeloq_remotes_dir = u"KEELOQREMOTES"; +const std::filesystem::path secplus_dir = u"SECPLUS"; const std::filesystem::path epirb_dir = u"EPIRB"; diff --git a/firmware/application/file_path.hpp b/firmware/application/file_path.hpp index af244c6eb..006e778ea 100644 --- a/firmware/application/file_path.hpp +++ b/firmware/application/file_path.hpp @@ -58,6 +58,7 @@ extern const std::filesystem::path waterfalls_dir; extern const std::filesystem::path macaddress_dir; extern const std::filesystem::path keeloq_keys_dir; extern const std::filesystem::path keeloq_remotes_dir; +extern const std::filesystem::path secplus_dir; extern const std::filesystem::path epirb_dir; extern const std::filesystem::path splash_dot_bmp; diff --git a/firmware/common/ui.hpp b/firmware/common/ui.hpp index 578087cf9..7f71100cf 100644 --- a/firmware/common/ui.hpp +++ b/firmware/common/ui.hpp @@ -26,6 +26,9 @@ namespace ui { +using Coord = int16_t; +using Dim = int16_t; + // Positioning helpers PER CHARACTERS (8*16) // EACH parameters must be used az CHAR position, not PX coordinates. So If you wanna use the 8,16 coordinates (that is the second character in X and second character in Y you must use UI_POS_X(1) UI_POS_Y(1) (since we count from 0) @@ -36,31 +39,31 @@ namespace ui { // default font width #define UI_POS_DEFAULT_WIDTH 8 // px position of the linenum-th character (Y) -#define UI_POS_Y(linenum) ((int)((linenum) * UI_POS_DEFAULT_HEIGHT)) +#define UI_POS_Y(linenum) ((Coord)((linenum) * UI_POS_DEFAULT_HEIGHT)) // px position of the linenum-th character from the bottom of the screen (Y) (please calculate the +1 line top-bar to it too if that is visible!) -#define UI_POS_Y_BOTTOM(linenum) ((int)(screen_height - (linenum) * UI_POS_DEFAULT_HEIGHT)) +#define UI_POS_Y_BOTTOM(linenum) ((Coord)(screen_height - (int)((linenum) * UI_POS_DEFAULT_HEIGHT))) // px position of the linenum-th character from the left of the screen (X) -#define UI_POS_X(charnum) ((int)((charnum) * UI_POS_DEFAULT_WIDTH)) +#define UI_POS_X(charnum) ((Coord)((charnum) * UI_POS_DEFAULT_WIDTH)) // px position of the linenum-th character from the right of the screen (X) -#define UI_POS_X_RIGHT(charnum) ((int)(screen_width - ((charnum) * UI_POS_DEFAULT_WIDTH))) +#define UI_POS_X_RIGHT(charnum) ((Coord)(screen_width - (int)((charnum) * UI_POS_DEFAULT_WIDTH))) // px position of the left character from the center of the screen (X) (for N character wide string) -#define UI_POS_X_CENTER(charnum) ((int)((screen_width / 2) - ((charnum) * UI_POS_DEFAULT_WIDTH / 2))) -// px position of the currcol in a table with colnum number of columns, where one coloumn is charnum characters wide maximum -#define UI_POS_X_TABLE(colnum, currcol) ((currcol) * (screen_width / (colnum))) +#define UI_POS_X_CENTER(charnum) ((Coord)((screen_width / 2) - (int)((charnum) * UI_POS_DEFAULT_WIDTH / 2))) +// px position of the currcol in a table with colnum number of columns, where one column is charnum characters wide maximum +#define UI_POS_X_TABLE(colnum, currcol) ((Coord)((currcol) * (screen_width / (colnum)))) // px width of N characters -#define UI_POS_WIDTH(charnum) ((int)((charnum) * UI_POS_DEFAULT_WIDTH)) +#define UI_POS_WIDTH(charnum) ((Dim)((charnum) * UI_POS_DEFAULT_WIDTH)) // px width of the screen #define UI_POS_MAXWIDTH (screen_width) // px height of N line -#define UI_POS_HEIGHT(linecount) ((int)((linecount) * UI_POS_DEFAULT_HEIGHT)) +#define UI_POS_HEIGHT(linecount) ((Dim)((linecount) * UI_POS_DEFAULT_HEIGHT)) // px height of the screen's percent -#define UI_POS_HEIGHT_PERCENT(percent) ((int)(screen_height * (percent) / 100)) +#define UI_POS_HEIGHT_PERCENT(percent) ((Dim)(screen_height * (percent) / 100)) // remaining px from the linenum-th line to the bottom of the screen. (please calculate the +1 line top-bar to it too if that is visible!) -#define UI_POS_HEIGHT_REMAINING(linenum) ((int)(screen_height - ((linenum) * UI_POS_DEFAULT_HEIGHT))) +#define UI_POS_HEIGHT_REMAINING(linenum) ((Dim)(screen_height - (int)((linenum) * UI_POS_DEFAULT_HEIGHT))) // remaining px from the charnum-th character to the right of the screen -#define UI_POS_WIDTH_REMAINING(charnum) ((int)(screen_width - ((charnum) * UI_POS_DEFAULT_WIDTH))) +#define UI_POS_WIDTH_REMAINING(charnum) ((Dim)(screen_width - (int)((charnum) * UI_POS_DEFAULT_WIDTH))) // px width of the screen's percent -#define UI_POS_WIDTH_PERCENT(percent) ((int)(screen_width * (percent) / 100)) +#define UI_POS_WIDTH_PERCENT(percent) ((Dim)(screen_width * (percent) / 100)) // px width of the screen #define UI_POS_MAXHEIGHT (screen_height) @@ -85,9 +88,6 @@ namespace ui { #define DEG_TO_RAD(d) (d * (2 * pi) / 360.0) -using Coord = int16_t; -using Dim = int16_t; - extern uint16_t screen_width; extern uint16_t screen_height; diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index 2f66fcf7a..bb7ce4d6d 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -206,7 +206,11 @@ void Widget::set_style(const Style* new_style) { } const Style& Widget::style() const { - return style_ ? *style_ : parent()->style(); + for (const Widget* curr = this; curr; curr = curr->parent()) { + if (curr->style_) return *curr->style_; + } + // default style should always be set by SystemView + return *style_; } void Widget::drawn(bool v) { diff --git a/firmware/standalone/common/ui/ui_widget.cpp b/firmware/standalone/common/ui/ui_widget.cpp index c29560479..f92336022 100644 --- a/firmware/standalone/common/ui/ui_widget.cpp +++ b/firmware/standalone/common/ui/ui_widget.cpp @@ -201,15 +201,11 @@ void Widget::set_style(const Style* new_style) { } const Style& Widget::style() const { - if (style_ != nullptr) - return *style_; - else { - auto p = parent(); - if (p == nullptr) - // TODO: debug - while (true); - return p->style(); + for (const Widget* curr = this; curr; curr = curr->parent()) { + if (curr->style_) return *curr->style_; } + // default style should always be set by SystemView + return *style_; } void Widget::drawn(bool v) { diff --git a/firmware/tools/external_app_info.py b/firmware/tools/external_app_info.py index d8b31abf1..da9db4d78 100644 --- a/firmware/tools/external_app_info.py +++ b/firmware/tools/external_app_info.py @@ -24,4 +24,4 @@ # external app address ranges below must match those in linker file "external.ld" maximum_application_size = 32*1024 external_apps_address_start = 0xADB00000 -external_apps_address_end = 0xAE060000 +external_apps_address_end = 0xAE090000