mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-07-26 10:38:52 +00:00
Add Security+ 2.0 TX App (#3217)
* Add U64 BoundSetting * Add secplus-tx * Add secplus license * Update icon * UI update * Add filesystem::path BoundSetting * Add file UI * Rename file * Add save button * Fix warnings, add integer conversions to UI macros * UI update * Copilot suggestions, name edit UI * Fix buffer length * Style update * Avoid recursion in Widget::style() * Apply suggestions from code review * Move secplustx.cpp to external * Shorten title * Space optimizations, remove FilePath setting * Remove assertions * Change namespace * Change title to match
This commit is contained in:
@@ -50,6 +50,9 @@ void BoundSetting::parse(std::string_view value) {
|
||||
case SettingType::I64:
|
||||
parse_int(value, as<int64_t>());
|
||||
break;
|
||||
case SettingType::U64:
|
||||
parse_int(value, as<uint64_t>());
|
||||
break;
|
||||
case SettingType::I32:
|
||||
parse_int(value, as<int32_t>());
|
||||
break;
|
||||
@@ -87,6 +90,9 @@ void BoundSetting::write(File& file) const {
|
||||
case SettingType::I64:
|
||||
file.write(to_string_dec_int(as<int64_t>(), buffer, length), length);
|
||||
break;
|
||||
case SettingType::U64:
|
||||
file.write(to_string_dec_uint(as<uint64_t>(), buffer, length), length);
|
||||
break;
|
||||
case SettingType::I32:
|
||||
file.write(to_string_dec_int(as<int32_t>(), buffer, length), length);
|
||||
break;
|
||||
@@ -97,9 +103,8 @@ void BoundSetting::write(File& file) const {
|
||||
file.write(to_string_dec_uint(as<uint8_t>(), buffer, length), length);
|
||||
break;
|
||||
case SettingType::String: {
|
||||
const auto& str = as<std::string>();
|
||||
file.write(str.data(), str.length());
|
||||
break;
|
||||
file.write_line(as<std::string>());
|
||||
return;
|
||||
}
|
||||
case SettingType::Bool:
|
||||
file.write(as<bool>() ? "1" : "0", 1);
|
||||
|
||||
@@ -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} {}
|
||||
|
||||
|
||||
+7
-1
@@ -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
|
||||
|
||||
+7
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<SecplusTXView>();
|
||||
}
|
||||
} // 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
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 <cstdint>
|
||||
|
||||
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
|
||||
@@ -0,0 +1,227 @@
|
||||
#include "ui_secplustx.hpp"
|
||||
|
||||
#include <string_view>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#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<SecplusData> 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<SecplusVersion>(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<uint8_t>(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<FileLoadView>(".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<FrequencyKeypadView>(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
|
||||
@@ -0,0 +1,96 @@
|
||||
#ifndef __UI_SECPLUSTX__
|
||||
#define __UI_SECPLUSTX__
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#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<SecplusData> 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<const TXProgressMessage*>(p);
|
||||
this->on_tx_progress(message.progress, message.done);
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::ui_secplustx
|
||||
|
||||
#endif
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
+16
-16
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user