From 81afec9ea4dbb13a85a24309e6021ad24dfb622d Mon Sep 17 00:00:00 2001 From: Pezsma <159525557+Pezsma@users.noreply.github.com> Date: Thu, 23 Oct 2025 13:09:48 +0200 Subject: [PATCH] Added Adult toy controller external app (#2840) --- firmware/application/bitmap.hpp | 38 ++ .../external/adult_toys_controller/main.cpp | 85 ++++ .../ui_adult_toys_controller.cpp | 373 ++++++++++++++++++ .../ui_adult_toys_controller.hpp | 245 ++++++++++++ firmware/application/external/external.cmake | 5 + firmware/application/external/external.ld | 7 +- .../external/morse_practice/main.cpp | 1 - firmware/graphics/icon_adulttoys.png | Bin 0 -> 215 bytes 8 files changed, 752 insertions(+), 2 deletions(-) create mode 100644 firmware/application/external/adult_toys_controller/main.cpp create mode 100644 firmware/application/external/adult_toys_controller/ui_adult_toys_controller.cpp create mode 100644 firmware/application/external/adult_toys_controller/ui_adult_toys_controller.hpp create mode 100644 firmware/graphics/icon_adulttoys.png diff --git a/firmware/application/bitmap.hpp b/firmware/application/bitmap.hpp index f3e1767d2..417cfdac9 100644 --- a/firmware/application/bitmap.hpp +++ b/firmware/application/bitmap.hpp @@ -415,6 +415,44 @@ static constexpr Bitmap bitmap_icon_adsb{ {16, 16}, bitmap_icon_adsb_data}; +static constexpr uint8_t bitmap_icon_adulttoys_data[] = { + 0x00, + 0x00, + 0x30, + 0x0C, + 0x78, + 0x1E, + 0x7C, + 0x3E, + 0xFE, + 0x7F, + 0xFE, + 0x7F, + 0xFE, + 0x7F, + 0xFE, + 0x7F, + 0xFC, + 0x3F, + 0xFC, + 0x3F, + 0xF8, + 0x1F, + 0xF0, + 0x0F, + 0xE0, + 0x07, + 0xC0, + 0x03, + 0x80, + 0x01, + 0x00, + 0x00, +}; +static constexpr Bitmap bitmap_icon_adulttoys{ + {16, 16}, + bitmap_icon_adulttoys_data}; + static constexpr uint8_t bitmap_icon_ais_data[] = { 0x00, 0x01, diff --git a/firmware/application/external/adult_toys_controller/main.cpp b/firmware/application/external/adult_toys_controller/main.cpp new file mode 100644 index 000000000..bf8e06cdc --- /dev/null +++ b/firmware/application/external/adult_toys_controller/main.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2025 Pezsma + * + * 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_adult_toys_controller.hpp" +#include "ui_navigation.hpp" +#include "external_app.hpp" + +namespace ui::external_app::adult_toys_controller { + +void initialize_app(::ui::NavigationView& nav) { + nav.push(); +} + +} // namespace ui::external_app::adult_toys_controller + +extern "C" { + +__attribute__((section(".external_app.app_adult_toys_controller.application_information"), used)) application_information_t _application_information_adult_toys_controller = { + /*.memory_location = */ (uint8_t*)0x00000000, + /*.externalAppEntry = */ ui::external_app::adult_toys_controller::initialize_app, + /*.header_version = */ CURRENT_HEADER_VERSION, + /*.app_version = */ VERSION_MD5, + + /*.app_name = */ "Adult Toys", + /*.bitmap_data = */ { + 0x00, + 0x00, + 0x30, + 0x0C, + 0x78, + 0x1E, + 0x7C, + 0x3E, + 0xFE, + 0x7F, + 0xFE, + 0x7F, + 0xFE, + 0x7F, + 0xFE, + 0x7F, + 0xFC, + 0x3F, + 0xFC, + 0x3F, + 0xF8, + 0x1F, + 0xF0, + 0x0F, + 0xE0, + 0x07, + 0xC0, + 0x03, + 0x80, + 0x01, + 0x00, + 0x00, + }, + /*.icon_color = */ ui::Color::yellow().v, + /*.menu_location = */ app_location_t::TX, + /*.desired_menu_position = */ -1, + + /*.m4_app_tag = portapack::spi_flash::image_tag_btle_tx */ {'P', 'B', 'T', 'T'}, + /*.m4_app_offset = */ 0x00000000, // will be filled at compile time +}; +} diff --git a/firmware/application/external/adult_toys_controller/ui_adult_toys_controller.cpp b/firmware/application/external/adult_toys_controller/ui_adult_toys_controller.cpp new file mode 100644 index 000000000..1187b33e5 --- /dev/null +++ b/firmware/application/external/adult_toys_controller/ui_adult_toys_controller.cpp @@ -0,0 +1,373 @@ + +#include "ui_adult_toys_controller.hpp" + +#include "ui_modemsetup.hpp" +#include "modems.hpp" +#include "audio.hpp" +#include "baseband_api.hpp" +#include "string_format.hpp" +#include "portapack_persistent_memory.hpp" +#include "ui_navigation.hpp" + +using namespace portapack; +using namespace modems; +using namespace ui; + +namespace ui::external_app::adult_toys_controller { + +void AdultToysView::focus() { + button_on.focus(); +} + +AdultToysView::AdultToysView(NavigationView& nav) + : nav_{nav} { + add_children({ + &button_on, + &btn_adult, + &btn_child, + &btn_mode_decrease, + &btn_mode_increase, + &btn_pcknum_inc, + &btn_pcknum_dec, + &chk_mode_rnd, + &chk_mode_play, + &chk_mode_stop, + &chk_mode_ncnt, + &chk_mode_infinite, + &field_frequency, + &txt_selected_mode, + &options_target, + &labels, + &message_num, + &tx_view, + &screen_message_1, + &screen_message_2, + &screen_message_3, + &screen_message_4, + &screen_message_5, + &screen_message_6, + &screen_message_7, + &screen_message_8, + &screen_message_9, + &screen_message_10, + }); + + btn_adult.on_select = [this](Button&) { + start_screen = true; + hidden_program(!start_screen); + }; + + btn_child.on_select = [this](Button&) { + nav_.pop(); + }; + + button_on.on_select = [this](Button&) { + if (play_running || stop_running) { + if (button_onstate) { + button_onstate = false; + stop(); + button_on.set_text(LanguageHelper::currentMessages[LANG_START]); + } else { + button_onstate = true; + printCurrentModes(); + start(); + button_on.set_text(LanguageHelper::currentMessages[LANG_STOP]); + } + } // if play_running || stop_running + }; + + btn_mode_decrease.on_select = [this](Button&) { + if (toy_packet > 0) { + toy_packet--; + printCurrentModes(); + } + }; + + btn_mode_increase.on_select = [this](Button&) { + if (stop_running || play_running) { + if (play_running && (toy_packet < max_plays - 1)) { + toy_packet++; + } else { + if (stop_running && toy_packet < max_stops - 1) { + toy_packet++; + } + } + printCurrentModes(); + } + }; + + btn_pcknum_inc.on_select = [this](Button&) { + if (!button_onstate) { + chk_mode_ncnt.set_value(true); + if (n_message_set < n_message_max) { + n_message_set++; + } else { + n_message_set = 0; + } + message_num.set_value(n_message_set); + n_message_current = n_message_set; + } + }; + + btn_pcknum_dec.on_select = [this](Button&) { + if (!button_onstate) { + chk_mode_ncnt.set_value(true); + if (n_message_set > 0) { + n_message_set--; + } else { + n_message_set = n_message_max; + } + message_num.set_value(n_message_set); + n_message_current = n_message_set; + } + }; + + message_num.on_change = [this](int32_t v) { + if (!button_onstate) { + chk_mode_ncnt.set_value(true); + n_message_set = static_cast(v); + n_message_current = n_message_set; + } else { + if (!button_onstate) message_num.set_value(n_message_current); + } + }; + + chk_mode_rnd.on_select = [this](Checkbox&, bool v) { + random_running = v; + }; + + chk_mode_play.on_select = [this](Checkbox&, bool v) { + play_running = v; + if (play_running && stop_running) { + toy_packet = 0; + stop_running = false; + chk_mode_stop.set_value(stop_running); + } + if (play_running) printCurrentModes(); + }; + + chk_mode_stop.on_select = [this](Checkbox&, bool v) { + stop_running = v; + if (play_running && stop_running) { + toy_packet = 0; + play_running = false; + chk_mode_play.set_value(play_running); + } + if (stop_running) printCurrentModes(); + }; + + chk_mode_ncnt.on_select = [this](Checkbox&, bool v) { + n_message_running = v; + if (n_message_running && inf_message_running) { + inf_message_running = false; + chk_mode_infinite.set_value(inf_message_running); + } + }; + + chk_mode_infinite.on_select = [this](Checkbox&, bool v) { + inf_message_running = v; + if (n_message_running && inf_message_running) { + n_message_running = false; + chk_mode_ncnt.set_value(n_message_running); + } + }; + options_target.on_change = [this](size_t, int32_t i) { + target = (uint8_t)i; + }; + + chk_mode_infinite.set_value(true); + chk_mode_play.set_value(true); +} + +void AdultToysView::hidden_program(bool hide) { + options_target.hidden(hide); + labels.hidden(hide); + button_on.hidden(hide); + btn_mode_decrease.hidden(hide); + btn_mode_increase.hidden(hide); + btn_pcknum_inc.hidden(hide); + btn_pcknum_dec.hidden(hide); + chk_mode_rnd.hidden(hide); + chk_mode_play.hidden(hide); + chk_mode_stop.hidden(hide); + chk_mode_ncnt.hidden(hide); + chk_mode_infinite.hidden(hide); + field_frequency.hidden(hide); + txt_selected_mode.hidden(hide); + message_num.hidden(hide); + tx_view.hidden(hide); + btn_adult.hidden(!hide); + btn_child.hidden(!hide); + screen_message_1.hidden(!hide); + screen_message_2.hidden(!hide); + screen_message_3.hidden(!hide); + screen_message_4.hidden(!hide); + screen_message_5.hidden(!hide); + screen_message_6.hidden(!hide); + screen_message_7.hidden(!hide); + screen_message_8.hidden(!hide); + screen_message_9.hidden(!hide); + screen_message_10.hidden(!hide); + set_dirty(); + if (hide) + btn_child.focus(); + else + button_on.focus(); +} + +void AdultToysView::on_show() { + hidden_program(!start_screen); +} + +void AdultToysView::createPacket(bool regenerate) { + if (regenerate) randomizeMac(); + randomChn(); + if (target == 1 || 1) { // now only lovespouse supported, in the future, need to separee them + uint32_t mode = play_running ? plays[toy_packet].value : stops[toy_packet].value; + + const uint8_t size = 22; + uint8_t packet[size]; + uint8_t i = 0; + + // 1. AD Flags + packet[i++] = 2; + packet[i++] = 0x01; + packet[i++] = 0x1A; + + // 2. Manufacturer Specific + packet[i++] = 14; + packet[i++] = 0xFF; + packet[i++] = 0xFF; + packet[i++] = 0x00; + packet[i++] = 0x6D; + packet[i++] = 0xB6; + packet[i++] = 0x43; + packet[i++] = 0xCE; + packet[i++] = 0x97; + packet[i++] = 0xFE; + packet[i++] = 0x42; + packet[i++] = 0x7C; + packet[i++] = (mode >> 0x10) & 0xFF; + packet[i++] = (mode >> 0x8) & 0xFF; + packet[i++] = (mode >> 0x0) & 0xFF; + + // 3. Service UUID List + packet[i++] = 3; + packet[i++] = 0x03; + packet[i++] = 0x8F; + packet[i++] = 0xAE; + + std::string res = to_string_hex_array(packet, i); + + memset(advertisementData, 0, sizeof(advertisementData)); + std::copy(res.begin(), res.end(), advertisementData); + } +} + +AdultToysView::~AdultToysView() { + stop(); +} + +void AdultToysView::stop() { + transmitter_model.disable(); + baseband::shutdown(); +} + +void AdultToysView::start() { + baseband::run_image(portapack::spi_flash::image_tag_btle_tx); + transmitter_model.enable(); + on_tx_progress(true); +} + +void AdultToysView::reset() { + stop(); + start(); +} + +uint8_t AdultToysView::randomize(uint8_t max) { + return static_cast(rand() % max); +} + +void AdultToysView::printCurrentModes() { + uint8_t max_val = play_running ? max_plays : max_stops; + auto name = play_running ? plays[toy_packet].name : stops[toy_packet].name; + std::string current = to_string_dec_uint(toy_packet + 1); + std::string max_val_str = to_string_dec_uint(max_val); + + std::string result_str = current; + result_str += "/"; + result_str += max_val_str; + result_str += " "; + result_str += name; + txt_selected_mode.set(result_str.c_str()); +} + +uint64_t AdultToysView::get_freq_by_channel_number(uint8_t channel_number) { + uint64_t freq_hz; + + switch (channel_number) { + case 37: + freq_hz = 2'402'000'000ull; + break; + case 38: + freq_hz = 2'426'000'000ull; + break; + case 39: + freq_hz = 2'480'000'000ull; + break; + case 0 ... 10: + freq_hz = 2'404'000'000ull + channel_number * 2'000'000ull; + break; + case 11 ... 36: + freq_hz = 2'428'000'000ull + (channel_number - 11) * 2'000'000ull; + break; + default: + freq_hz = UINT64_MAX; + } + + return freq_hz; +} + +void AdultToysView::randomizeMac() { + const char hexDigits[] = "0123456789ABCDEF"; + // Generate 12 random hexadecimal characters + for (int i = 0; i < 12; ++i) { + int randomIndex = rand() % 16; + mac[i] = hexDigits[randomIndex]; + } + mac[12] = '\0'; // Null-terminate the string +} + +void AdultToysView::randomChn() { + channel_number = 37 + std::rand() % (39 - 37 + 1); + field_frequency.set_value(get_freq_by_channel_number(channel_number)); +} + +void AdultToysView::on_tx_progress(const bool done) { + if (!done) return; + if (!button_onstate) return; + bool regenerate = (counter++ % 600) == 0; + if (random_running && regenerate) { + toy_packet = randomize(play_running ? max_plays : max_stops); + } + createPacket(regenerate); + printCurrentModes(); + + if (n_message_running) { + if (n_message_current > 0) { + n_message_current--; + message_num.set_value(n_message_current); + } else { + stop(); + message_num.set_value(n_message_set); + button_on.set_text(LanguageHelper::currentMessages[LANG_START]); + button_onstate = false; + n_message_current = n_message_set; + return; + } + } // if n_message + + baseband::set_btletx(channel_number, mac, advertisementData, pduType); +} + +} // namespace ui::external_app::adult_toys_controller diff --git a/firmware/application/external/adult_toys_controller/ui_adult_toys_controller.hpp b/firmware/application/external/adult_toys_controller/ui_adult_toys_controller.hpp new file mode 100644 index 000000000..7a8a087b9 --- /dev/null +++ b/firmware/application/external/adult_toys_controller/ui_adult_toys_controller.hpp @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2025 Pezsma + * + * 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. + */ + +// Code from https://github.com/Next-Flip/Momentum-Apps/blob/89d493daa1ea0cb86906fa0042c41dd4edc22a6a/ble_spam/protocols/lovespouse.c#L93 +// Thanks for the work of the original creators! + +#ifndef __UI_ADULT_TOYS_H__ +#define __UI_ADULT_TOYS_H__ + +#include "ui.hpp" +#include "ui_language.hpp" +#include "ui_navigation.hpp" +#include "ui_transmitter.hpp" +#include "ui_freq_field.hpp" +#include "ui_record_view.hpp" +#include "app_settings.hpp" +#include "radio_state.hpp" +#include "log_file.hpp" +#include "utility.hpp" + +using namespace ui; + +namespace ui::external_app::adult_toys_controller { + +enum PKT_TYPE { + PKT_TYPE_INVALID_TYPE, + PKT_TYPE_RAW, + PKT_TYPE_DISCOVERY, + PKT_TYPE_IBEACON, + PKT_TYPE_ADV_IND, + PKT_TYPE_ADV_DIRECT_IND, + PKT_TYPE_ADV_NONCONN_IND, + PKT_TYPE_ADV_SCAN_IND, + PKT_TYPE_SCAN_REQ, + PKT_TYPE_SCAN_RSP, + PKT_TYPE_CONNECT_REQ, + PKT_TYPE_LL_DATA, + PKT_TYPE_LL_CONNECTION_UPDATE_REQ, + PKT_TYPE_LL_CHANNEL_MAP_REQ, + PKT_TYPE_LL_TERMINATE_IND, + PKT_TYPE_LL_ENC_REQ, + PKT_TYPE_LL_ENC_RSP, + PKT_TYPE_LL_START_ENC_REQ, + PKT_TYPE_LL_START_ENC_RSP, + PKT_TYPE_LL_UNKNOWN_RSP, + PKT_TYPE_LL_FEATURE_REQ, + PKT_TYPE_LL_FEATURE_RSP, + PKT_TYPE_LL_PAUSE_ENC_REQ, + PKT_TYPE_LL_PAUSE_ENC_RSP, + PKT_TYPE_LL_VERSION_IND, + PKT_TYPE_LL_REJECT_IND, + PKT_TYPE_NUM_PKT_TYPE +}; + +class AdultToysView : public ui::View { + public: + AdultToysView(NavigationView& nav); + ~AdultToysView(); + + void focus() override; + void on_show() override; + + std::string title() const override { + return "Adult Toys"; + }; + + struct LovespouseMode { + uint32_t value; + std::string name; + }; + + private: + NavigationView& nav_; + TxRadioState radio_state_{ + 2'402'000'000 /* frequency */, + 4'000'000 /* bandwidth */, + 4'000'000 /* sampling rate */ + }; + + TxFrequencyField field_frequency{ + {UI_POS_X(0), UI_POS_Y(0)}, + nav_}; + + TransmitterView2 tx_view{ + {UI_POS_X(11), UI_POS_Y(0)}, + /*short_ui*/ true}; + + app_settings::SettingsManager settings_{ + "Adult Toys", app_settings::Mode::TX}; + + OptionsField options_target{ + {UI_POS_X(6), UI_POS_Y(1)}, + 11, + {{"LoveSpouse", 1}}}; + + Button btn_adult{{UI_POS_X(0), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(10), UI_POS_HEIGHT(3)}, "adult"}; + + Button btn_child{{UI_POS_X_RIGHT(10), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(10), UI_POS_HEIGHT(3)}, "child"}; + + Button button_on{ + {UI_POS_X(0), UI_POS_Y(3), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)}, + LanguageHelper::currentMessages[LANG_START]}; + + Button btn_mode_decrease{ + {UI_POS_X(0), UI_POS_Y(13), UI_POS_WIDTH(3), UI_POS_HEIGHT(2)}, + "<-"}; + + Button btn_mode_increase{ + {UI_POS_X_RIGHT(3), UI_POS_Y(13), UI_POS_WIDTH(3), UI_POS_HEIGHT(2)}, + "->"}; + + Text txt_selected_mode{{UI_POS_X_CENTER(22), UI_POS_Y(13.5), UI_POS_WIDTH(22), UI_POS_HEIGHT(1)}, ""}; + + Button btn_pcknum_inc{ + {UI_POS_X(7), UI_POS_Y(9), UI_POS_WIDTH(2), UI_POS_HEIGHT(1.5)}, + "+"}; + + Button btn_pcknum_dec{ + {UI_POS_X(0), UI_POS_Y(9), UI_POS_WIDTH(2), UI_POS_HEIGHT(1.5)}, + "-"}; + + NumberField message_num{{UI_POS_X(3), UI_POS_Y(9.2)}, 3, {0, 999}, 1, ' ', true}; + + Checkbox chk_mode_rnd{{UI_POS_X_RIGHT(15), UI_POS_Y(3)}, 12, "Random Msg.", false}; + Checkbox chk_mode_play{{UI_POS_X_RIGHT(15), UI_POS_Y(5)}, 10, "Play Mode", false}; + Checkbox chk_mode_stop{{UI_POS_X_RIGHT(15), UI_POS_Y(7)}, 10, "Stop Mode", false}; + Checkbox chk_mode_ncnt{{UI_POS_X_RIGHT(15), UI_POS_Y(9)}, 7, "N Msg.", false}; + Checkbox chk_mode_infinite{{UI_POS_X_RIGHT(15), UI_POS_Y(11)}, 10, "Inf. Msg.", false}; + + Text screen_message_1{{0, 0, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "The application is intended"}; + Text screen_message_2{{0, 25, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "for controlling Love Spouse"}; + Text screen_message_3{{0, 50, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "auxiliary tools/devices."}; + Text screen_message_4{{0, 75, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "It is only for use by adults."}; + Text screen_message_5{{0, 100, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "Are you an adult?"}; + + Text screen_message_6{{0, 140, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "To use the application, "}; + Text screen_message_7{{0, 165, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "I assume full responsibility"}; + Text screen_message_8{{0, 190, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "for the \"I do not assume"}; + Text screen_message_9{{0, 215, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "responsibility\"."}; + Text screen_message_10{{UI_POS_X_RIGHT(16), 230, UI_POS_WIDTH(16), UI_POS_HEIGHT(1)}, "(The developer)"}; + + Labels labels{ + {{UI_POS_X(0), UI_POS_Y(1)}, "Type:", Theme::getInstance()->fg_light->foreground}}; + + bool button_onstate{false}; + bool play_running{false}; + bool stop_running{false}; + bool random_running{false}; + bool n_message_running{false}; + bool inf_message_running{false}; + bool start_screen{false}; + + uint8_t target = 1; // lovespouse + + char mac[13] = "010203040506"; + uint8_t channel_number = 37; + char advertisementData[63] = {"03032CFE06162CFED5A59E020AB4\0"}; + + PKT_TYPE pduType = {PKT_TYPE_DISCOVERY}; + + uint8_t toy_packet = 0; + uint16_t n_message_max = 999; + uint16_t n_message_set = 0; + uint16_t n_message_current = 0; + static const size_t max_plays = 27; + static const size_t max_stops = 3; + + uint16_t counter = 599; + + void createPacket(bool regenerate); + void start(); + void stop(); + void reset(); + void printCurrentModes(); + void on_tx_progress(const bool done); + void hidden_program(bool hide); + void randomizeMac(); + void randomChn(); + uint8_t randomize(uint8_t max); + uint64_t get_freq_by_channel_number(uint8_t channel_number); + + MessageHandlerRegistration message_handler_tx_progress{ + Message::ID::TXProgress, + [this](const Message* const p) { + const auto message = *reinterpret_cast(p); + this->on_tx_progress(message.done); + }}; + + LovespouseMode plays[max_plays] = { + {0xE49C6C, "Classic 1"}, + {0xE7075E, "Classic 2"}, + {0xE68E4F, "Classic 3"}, + {0xE1313B, "Classic 4"}, + {0xE0B82A, "Classic 5"}, + {0xE32318, "Classic 6"}, + {0xE2AA09, "Classic 7"}, + {0xED5DF1, "Classic 8"}, + {0xECD4E0, "Classic 9"}, + {0xD41F5D, "Independent 1-1"}, + {0xD7846F, "Independent 1-2"}, + {0xD60D7E, "Independent 1-3"}, + {0xD1B20A, "Independent 1-4"}, + {0xD0B31B, "Independent 1-5"}, + {0xD3A029, "Independent 1-6"}, + {0xD22938, "Independent 1-7"}, + {0xDDDEC0, "Independent 1-8"}, + {0xDC57D1, "Independent 1-9"}, + {0xA4982E, "Independent 2-1"}, + {0xA7031C, "Independent 2-2"}, + {0xA68A0D, "Independent 2-3"}, + {0xA13579, "Independent 2-4"}, + {0xA0BC68, "Independent 2-5"}, + {0xA3275A, "Independent 2-6"}, + {0xA2AE4B, "Independent 2-7"}, + {0xAD59B3, "Independent 2-8"}, + {0xACD0A2, "Independent 2-9"}, + }; + + LovespouseMode stops[max_stops] = { + {0xE5157D, "Classic Stop"}, + {0xD5964C, "Independent 1 Stop"}, + {0xA5113F, "Independent 2 Stop"}, + }; +}; +}; // namespace ui::external_app::adult_toys_controller + +#endif // ui_adult_toys_controller_hpp \ No newline at end of file diff --git a/firmware/application/external/external.cmake b/firmware/application/external/external.cmake index 64a999bd5..52642f24d 100644 --- a/firmware/application/external/external.cmake +++ b/firmware/application/external/external.cmake @@ -258,6 +258,10 @@ set(EXTCPPSRC #morse_practice external/morse_practice/main.cpp external/morse_practice/ui_morse_practice.cpp + + #adult_toys_controller + external/adult_toys_controller/main.cpp + external/adult_toys_controller/ui_adult_toys_controller.cpp ) set(EXTAPPLIST @@ -323,4 +327,5 @@ set(EXTAPPLIST game2048 bht_tx morse_practice + adult_toys_controller ) diff --git a/firmware/application/external/external.ld b/firmware/application/external/external.ld index 0ad3d8c7e..0f22bd8bd 100644 --- a/firmware/application/external/external.ld +++ b/firmware/application/external/external.ld @@ -85,7 +85,7 @@ MEMORY ram_external_app_game2048 (rwx) : org = 0xADEC0000, len = 32k ram_external_app_bht_tx (rwx) : org = 0xADED0000, len = 32k ram_external_app_morse_practice (rwx) : org = 0xADEE0000, len = 32k - + ram_external_app_adult_toys_controller (rwx) : org = 0xADEF0000, len = 32k } @@ -465,6 +465,11 @@ SECTIONS *(*ui*external_app*morse_practice*); } > ram_external_app_morse_practice + .external_app_adult_toys_controller : ALIGN(4) SUBALIGN(4) + { + KEEP(*(.external_app.app_adult_toys_controller.application_information)); + *(*ui*external_app*adult_toys_controller*); + } > ram_external_app_adult_toys_controller } diff --git a/firmware/application/external/morse_practice/main.cpp b/firmware/application/external/morse_practice/main.cpp index c93674631..e0327e8ac 100644 --- a/firmware/application/external/morse_practice/main.cpp +++ b/firmware/application/external/morse_practice/main.cpp @@ -34,7 +34,6 @@ void initialize_app(NavigationView& nav) { extern "C" { -// Az alkalmazás információ C-linkage-ként, hogy a firmware hívhassa __attribute__((section(".external_app.app_morse_practice.application_information"), used)) application_information_t _application_information_morse_practice = { /*.memory_location = */ (uint8_t*)0x00000000, diff --git a/firmware/graphics/icon_adulttoys.png b/firmware/graphics/icon_adulttoys.png new file mode 100644 index 0000000000000000000000000000000000000000..7640cbda2b139f1106b86bdf1864358906aa24c5 GIT binary patch literal 215 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85o30K$!7fntTONFvruyF~q|E?4*ZW2NXD3&hPziy?c`Gx=SZ+ z^w%nMm&sMBFl@PEc>Qzkid9!VO72T;NO&HT$u_T=C0n=I$5>gZm@Tp{DWLr1r*Ade zozl3jl?lI@(K9Qvb!zAL^JfnTyB$mHxU*r-sw+3G%^IXnPf$^OarVkipuG&9u6{1- HoD!M