mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-14 10:39:30 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bf18851b6b | |||
| 81afec9ea4 | |||
| 60a5222058 | |||
| 7041d507ca |
@@ -681,35 +681,27 @@ SetEncoderDialView::SetEncoderDialView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&field_encoder_dial_sensitivity,
|
||||
&field_encoder_rate_multiplier,
|
||||
&field_encoder_dial_direction,
|
||||
&field_encoder_consecutive_hits,
|
||||
&field_encoder_cooldown_ms,
|
||||
&field_encoder_debounce_ms,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
&button_dial_sensitivity_plus,
|
||||
&button_dial_sensitivity_minus,
|
||||
&button_rate_multiplier_plus,
|
||||
&button_rate_multiplier_minus,
|
||||
&field_encoder_dial_direction});
|
||||
&button_cancel});
|
||||
|
||||
field_encoder_dial_sensitivity.set_by_value(pmem::encoder_dial_sensitivity());
|
||||
field_encoder_rate_multiplier.set_value(pmem::encoder_rate_multiplier());
|
||||
field_encoder_dial_direction.set_by_value(pmem::encoder_dial_direction());
|
||||
|
||||
button_dial_sensitivity_plus.on_select = [this](Button&) {
|
||||
field_encoder_dial_sensitivity.on_encoder(1);
|
||||
};
|
||||
button_dial_sensitivity_minus.on_select = [this](Button&) {
|
||||
field_encoder_dial_sensitivity.on_encoder(-1);
|
||||
};
|
||||
button_rate_multiplier_plus.on_select = [this](Button&) {
|
||||
field_encoder_rate_multiplier.on_encoder(1);
|
||||
};
|
||||
button_rate_multiplier_minus.on_select = [this](Button&) {
|
||||
field_encoder_rate_multiplier.on_encoder(-1);
|
||||
};
|
||||
field_encoder_consecutive_hits.set_value(pmem::encoder_consecutive_hits());
|
||||
field_encoder_cooldown_ms.set_value(pmem::encoder_cooldown_ms());
|
||||
field_encoder_debounce_ms.set_value(pmem::encoder_debounce_ms());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
pmem::set_encoder_dial_sensitivity(field_encoder_dial_sensitivity.selected_index_value());
|
||||
pmem::set_encoder_rate_multiplier(field_encoder_rate_multiplier.value());
|
||||
pmem::set_encoder_dial_direction(field_encoder_dial_direction.selected_index_value());
|
||||
pmem::set_encoder_consecutive_hits(field_encoder_consecutive_hits.value());
|
||||
pmem::set_encoder_cooldown_ms(field_encoder_cooldown_ms.value());
|
||||
pmem::set_encoder_debounce_ms(field_encoder_debounce_ms.value());
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
|
||||
@@ -559,58 +559,63 @@ class SetEncoderDialView : public View {
|
||||
|
||||
private:
|
||||
Labels labels{
|
||||
{{UI_POS_X(0), UI_POS_Y(0)}, "Sensitivity to dial rotation", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 1 * 16}, "position (x steps per 360):", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 3 * 16}, "Sensitivity:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 7 * 16}, "Rotation rate (default 1", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 8 * 16}, "means no rate dependency):", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 10 * 16}, "Rate multiplier:", Theme::getInstance()->fg_light->foreground},
|
||||
{{4 * 8, 14 * 16}, "Direction:", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 1 * 16}, "Sensitivity:", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 4 * 16}, "Rate mult:", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 7 * 16}, "Direction:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 9 * 16}, "--- Debounce (noisy dial) ---", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 10 * 16}, "Consec hits:", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 11 * 16}, "Cooldown ms:", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 12 * 16}, "Debounce ms:", Theme::getInstance()->fg_light->foreground},
|
||||
|
||||
};
|
||||
|
||||
OptionsField field_encoder_dial_sensitivity{
|
||||
{20 * 8, 3 * 16},
|
||||
{14 * 8, 1 * 16},
|
||||
6,
|
||||
{{"LOW", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOW},
|
||||
{"NORMAL", encoder_dial_sensitivity::DIAL_SENSITIVITY_NORMAL},
|
||||
{"HIGH", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGH}}};
|
||||
|
||||
NumberField field_encoder_rate_multiplier{
|
||||
{20 * 8, 10 * 16},
|
||||
{14 * 8, 4 * 16},
|
||||
2,
|
||||
{1, 15},
|
||||
1,
|
||||
' '};
|
||||
|
||||
OptionsField field_encoder_dial_direction{
|
||||
{18 * 8, 14 * 16},
|
||||
{14 * 8, 7 * 16},
|
||||
7,
|
||||
{{"NORMAL", false},
|
||||
{"REVERSE", true}}};
|
||||
|
||||
Button button_dial_sensitivity_plus{
|
||||
{20 * 8, 2 * 16, 16, 16},
|
||||
"+"};
|
||||
NumberField field_encoder_consecutive_hits{
|
||||
{14 * 8, 10 * 16},
|
||||
2,
|
||||
{1, 10},
|
||||
1,
|
||||
' '};
|
||||
|
||||
Button button_dial_sensitivity_minus{
|
||||
{20 * 8, 4 * 16, 16, 16},
|
||||
"-"};
|
||||
NumberField field_encoder_cooldown_ms{
|
||||
{14 * 8, 11 * 16},
|
||||
3,
|
||||
{0, 255},
|
||||
5,
|
||||
' '};
|
||||
|
||||
Button button_rate_multiplier_plus{
|
||||
{20 * 8, 9 * 16, 16, 16},
|
||||
"+"};
|
||||
|
||||
Button button_rate_multiplier_minus{
|
||||
{20 * 8, 11 * 16, 16, 16},
|
||||
"-"};
|
||||
NumberField field_encoder_debounce_ms{
|
||||
{14 * 8, 12 * 16},
|
||||
2,
|
||||
{4, 32},
|
||||
2,
|
||||
' '};
|
||||
|
||||
Button button_save{
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
|
||||
{2 * 8, 14 * 16, 12 * 8, 24},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
|
||||
{16 * 8, 14 * 16, 12 * 8, 24},
|
||||
"Cancel",
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<AdultToysView>();
|
||||
}
|
||||
|
||||
} // 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
|
||||
};
|
||||
}
|
||||
+373
@@ -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<uint16_t>(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<uint8_t>(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
|
||||
+245
@@ -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<const TXProgressMessage*>(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
|
||||
@@ -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
|
||||
)
|
||||
|
||||
+6
-1
@@ -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
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -163,15 +163,28 @@ uint8_t EncoderDebounce::rotation_rate() {
|
||||
|
||||
// Returns TRUE if encoder position phase bits changed (after debouncing)
|
||||
bool EncoderDebounce::feed(const uint8_t phase_bits) {
|
||||
// Shift in new 2-bit sample into 32-bit history (16 samples total)
|
||||
history_ = (history_ << 2) | phase_bits;
|
||||
|
||||
// If both inputs have been stable for the last 4 ticks, history_ should equal 0x00, 0x55, 0xAA, or 0xFF.
|
||||
uint8_t expected_stable_history = phase_bits * 0b01010101;
|
||||
// For very noisy encoders: require BOTH bits stable for N consecutive ticks
|
||||
// Get configurable debounce window (4-32ms)
|
||||
uint8_t debounce_samples = portapack::persistent_memory::encoder_debounce_ms();
|
||||
|
||||
// But, checking for equal seems to cause issues with at least 1 user's encoder, so we're treating the input
|
||||
// as "stable" if at least ONE input bit is consistent for 4 ticks...
|
||||
uint8_t diff = (history_ ^ expected_stable_history);
|
||||
if (((diff & 0b01010101) == 0) || ((diff & 0b10101010) == 0)) {
|
||||
// Build expected pattern: phase_bits repeated debounce_samples times
|
||||
// For phase_bits=0b00: 0x00000000, 0b01: 0x55555555, 0b10: 0xAAAAAAAA, 0b11: 0xFFFFFFFF
|
||||
uint32_t expected_stable_history = 0;
|
||||
for (uint8_t i = 0; i < debounce_samples; i++) {
|
||||
expected_stable_history = (expected_stable_history << 2) | phase_bits;
|
||||
}
|
||||
|
||||
// Create mask for the number of samples we're checking
|
||||
uint32_t mask = 0;
|
||||
for (uint8_t i = 0; i < debounce_samples; i++) {
|
||||
mask = (mask << 2) | 0x3;
|
||||
}
|
||||
|
||||
// Require exact match - both bits must be stable for configured ms
|
||||
if ((history_ & mask) == expected_stable_history) {
|
||||
// Has the debounced input value changed?
|
||||
if (state_ != phase_bits) {
|
||||
state_ = phase_bits;
|
||||
|
||||
@@ -78,7 +78,7 @@ class EncoderDebounce {
|
||||
uint8_t rotation_rate(); // returns last rotation rate
|
||||
|
||||
private:
|
||||
uint8_t history_{0}; // shift register of previous reads from encoder
|
||||
uint32_t history_{0}; // shift register of previous reads from encoder (16 samples @ 2 bits each)
|
||||
|
||||
uint8_t state_{0}; // actual encoder output state (after debounce logic)
|
||||
|
||||
|
||||
@@ -62,21 +62,41 @@ int_fast8_t Encoder::update(const uint_fast8_t phase_bits) {
|
||||
|
||||
int_fast8_t direction = transition_map[state];
|
||||
|
||||
// Require 2 state changes in same direction to register movement -- for additional level of contact switch debouncing
|
||||
if (direction == prev_direction) {
|
||||
if ((sensitivity_map[portapack::persistent_memory::encoder_dial_sensitivity()] & (1 << state)) == 0)
|
||||
return 0;
|
||||
|
||||
// false: normal, true: reverse
|
||||
if (portapack::persistent_memory::encoder_dial_direction())
|
||||
direction = -direction;
|
||||
|
||||
return direction;
|
||||
// Decrement cooldown timer
|
||||
if (direction_cooldown > 0) {
|
||||
direction_cooldown--;
|
||||
}
|
||||
|
||||
// It's normal for transition map to return 0 between every +1/-1, so discarding those
|
||||
if (direction != 0)
|
||||
prev_direction = direction;
|
||||
// Require N consecutive state changes in same direction (configurable for noisy encoders)
|
||||
if (direction == prev_direction && direction != 0) {
|
||||
direction_stability_count++;
|
||||
|
||||
// Need N consecutive same-direction changes AND cooldown expired
|
||||
uint8_t required_hits = portapack::persistent_memory::encoder_consecutive_hits();
|
||||
if (direction_stability_count >= required_hits && direction_cooldown == 0) {
|
||||
if ((sensitivity_map[portapack::persistent_memory::encoder_dial_sensitivity()] & (1 << state)) == 0)
|
||||
return 0;
|
||||
|
||||
// Successfully registered movement - reset stability and set cooldown
|
||||
direction_stability_count = 0;
|
||||
direction_cooldown = portapack::persistent_memory::encoder_cooldown_ms();
|
||||
|
||||
// false: normal, true: reverse
|
||||
if (portapack::persistent_memory::encoder_dial_direction())
|
||||
direction = -direction;
|
||||
|
||||
return direction;
|
||||
}
|
||||
} else if (direction != 0 && direction != prev_direction) {
|
||||
// Direction changed - only accept if cooldown expired (prevents bounce-induced reversals)
|
||||
if (direction_cooldown == 0) {
|
||||
prev_direction = direction;
|
||||
direction_stability_count = 1; // Start counting this new direction
|
||||
} else {
|
||||
// During cooldown, completely ignore opposite direction - reset stability count
|
||||
direction_stability_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ class Encoder {
|
||||
private:
|
||||
uint_fast8_t state{0};
|
||||
int_fast8_t prev_direction{0};
|
||||
uint8_t direction_stability_count{0}; // count consecutive same-direction changes
|
||||
uint8_t direction_cooldown{0}; // prevent rapid direction reversals
|
||||
};
|
||||
|
||||
#endif /*__ENCODER_H__*/
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
* @init
|
||||
*/
|
||||
void i2cInit(void) {
|
||||
i2c_lld_init();
|
||||
i2c_lld_init();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,21 +79,20 @@ void i2cInit(void) {
|
||||
*
|
||||
* @init
|
||||
*/
|
||||
void i2cObjectInit(I2CDriver *i2cp) {
|
||||
|
||||
i2cp->state = I2C_STOP;
|
||||
i2cp->config = NULL;
|
||||
void i2cObjectInit(I2CDriver* i2cp) {
|
||||
i2cp->state = I2C_STOP;
|
||||
i2cp->config = NULL;
|
||||
|
||||
#if I2C_USE_MUTUAL_EXCLUSION
|
||||
#if CH_USE_MUTEXES
|
||||
chMtxInit(&i2cp->mutex);
|
||||
chMtxInit(&i2cp->mutex);
|
||||
#else
|
||||
chSemInit(&i2cp->semaphore, 1);
|
||||
chSemInit(&i2cp->semaphore, 1);
|
||||
#endif /* CH_USE_MUTEXES */
|
||||
#endif /* I2C_USE_MUTUAL_EXCLUSION */
|
||||
|
||||
#if defined(I2C_DRIVER_EXT_INIT_HOOK)
|
||||
I2C_DRIVER_EXT_INIT_HOOK(i2cp);
|
||||
I2C_DRIVER_EXT_INIT_HOOK(i2cp);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -105,19 +104,18 @@ void i2cObjectInit(I2CDriver *i2cp) {
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
void i2cStart(I2CDriver *i2cp, const I2CConfig *config) {
|
||||
void i2cStart(I2CDriver* i2cp, const I2CConfig* config) {
|
||||
chDbgCheck((i2cp != NULL) && (config != NULL), "i2cStart");
|
||||
chDbgAssert((i2cp->state == I2C_STOP) || (i2cp->state == I2C_READY) ||
|
||||
(i2cp->state == I2C_LOCKED),
|
||||
"i2cStart(), #1",
|
||||
"invalid state");
|
||||
|
||||
chDbgCheck((i2cp != NULL) && (config != NULL), "i2cStart");
|
||||
chDbgAssert((i2cp->state == I2C_STOP) || (i2cp->state == I2C_READY) ||
|
||||
(i2cp->state == I2C_LOCKED),
|
||||
"i2cStart(), #1",
|
||||
"invalid state");
|
||||
|
||||
chSysLock();
|
||||
i2cp->config = config;
|
||||
i2c_lld_start(i2cp);
|
||||
i2cp->state = I2C_READY;
|
||||
chSysUnlock();
|
||||
chSysLock();
|
||||
i2cp->config = config;
|
||||
i2c_lld_start(i2cp);
|
||||
i2cp->state = I2C_READY;
|
||||
chSysUnlock();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,18 +125,17 @@ void i2cStart(I2CDriver *i2cp, const I2CConfig *config) {
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
void i2cStop(I2CDriver *i2cp) {
|
||||
void i2cStop(I2CDriver* i2cp) {
|
||||
chDbgCheck(i2cp != NULL, "i2cStop");
|
||||
chDbgAssert((i2cp->state == I2C_STOP) || (i2cp->state == I2C_READY) ||
|
||||
(i2cp->state == I2C_LOCKED),
|
||||
"i2cStop(), #1",
|
||||
"invalid state");
|
||||
|
||||
chDbgCheck(i2cp != NULL, "i2cStop");
|
||||
chDbgAssert((i2cp->state == I2C_STOP) || (i2cp->state == I2C_READY) ||
|
||||
(i2cp->state == I2C_LOCKED),
|
||||
"i2cStop(), #1",
|
||||
"invalid state");
|
||||
|
||||
chSysLock();
|
||||
i2c_lld_stop(i2cp);
|
||||
i2cp->state = I2C_STOP;
|
||||
chSysUnlock();
|
||||
chSysLock();
|
||||
i2c_lld_stop(i2cp);
|
||||
i2cp->state = I2C_STOP;
|
||||
chSysUnlock();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,11 +146,10 @@ void i2cStop(I2CDriver *i2cp) {
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
i2cflags_t i2cGetErrors(I2CDriver *i2cp) {
|
||||
i2cflags_t i2cGetErrors(I2CDriver* i2cp) {
|
||||
chDbgCheck(i2cp != NULL, "i2cGetErrors");
|
||||
|
||||
chDbgCheck(i2cp != NULL, "i2cGetErrors");
|
||||
|
||||
return i2c_lld_get_errors(i2cp);
|
||||
return i2c_lld_get_errors(i2cp);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,35 +178,37 @@ i2cflags_t i2cGetErrors(I2CDriver *i2cp) {
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
msg_t i2cMasterTransmitTimeout(I2CDriver *i2cp,
|
||||
msg_t i2cMasterTransmitTimeout(I2CDriver* i2cp,
|
||||
i2caddr_t addr,
|
||||
const uint8_t *txbuf,
|
||||
const uint8_t* txbuf,
|
||||
size_t txbytes,
|
||||
uint8_t *rxbuf,
|
||||
uint8_t* rxbuf,
|
||||
size_t rxbytes,
|
||||
systime_t timeout) {
|
||||
msg_t rdymsg;
|
||||
msg_t rdymsg;
|
||||
|
||||
chDbgCheck((i2cp != NULL) && (addr != 0) &&
|
||||
(txbytes > 0) && (txbuf != NULL) &&
|
||||
((rxbytes == 0) || ((rxbytes > 0) && (rxbuf != NULL))) &&
|
||||
(timeout != TIME_IMMEDIATE),
|
||||
"i2cMasterTransmitTimeout");
|
||||
chDbgCheck((i2cp != NULL) && (addr != 0) &&
|
||||
(txbytes > 0) && (txbuf != NULL) &&
|
||||
((rxbytes == 0) || ((rxbytes > 0) && (rxbuf != NULL))) &&
|
||||
(timeout != TIME_IMMEDIATE),
|
||||
"i2cMasterTransmitTimeout");
|
||||
|
||||
chDbgAssert(i2cp->state == I2C_READY,
|
||||
"i2cMasterTransmitTimeout(), #1", "not ready");
|
||||
// chDbgAssert(i2cp->state == I2C_READY, "i2cMasterTransmitTimeout(), #1", "not ready");
|
||||
if (i2cp->state != I2C_READY) {
|
||||
return RDY_TIMEOUT;
|
||||
}
|
||||
|
||||
chSysLock();
|
||||
i2cp->errors = I2CD_NO_ERROR;
|
||||
i2cp->state = I2C_ACTIVE_TX;
|
||||
rdymsg = i2c_lld_master_transmit_timeout(i2cp, addr, txbuf, txbytes,
|
||||
rxbuf, rxbytes, timeout);
|
||||
if (rdymsg == RDY_TIMEOUT)
|
||||
i2cp->state = I2C_LOCKED;
|
||||
else
|
||||
i2cp->state = I2C_READY;
|
||||
chSysUnlock();
|
||||
return rdymsg;
|
||||
chSysLock();
|
||||
i2cp->errors = I2CD_NO_ERROR;
|
||||
i2cp->state = I2C_ACTIVE_TX;
|
||||
rdymsg = i2c_lld_master_transmit_timeout(i2cp, addr, txbuf, txbytes,
|
||||
rxbuf, rxbytes, timeout);
|
||||
if (rdymsg == RDY_TIMEOUT)
|
||||
i2cp->state = I2C_LOCKED;
|
||||
else
|
||||
i2cp->state = I2C_READY;
|
||||
chSysUnlock();
|
||||
return rdymsg;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,32 +231,33 @@ msg_t i2cMasterTransmitTimeout(I2CDriver *i2cp,
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
msg_t i2cMasterReceiveTimeout(I2CDriver *i2cp,
|
||||
msg_t i2cMasterReceiveTimeout(I2CDriver* i2cp,
|
||||
i2caddr_t addr,
|
||||
uint8_t *rxbuf,
|
||||
uint8_t* rxbuf,
|
||||
size_t rxbytes,
|
||||
systime_t timeout){
|
||||
systime_t timeout) {
|
||||
msg_t rdymsg;
|
||||
|
||||
msg_t rdymsg;
|
||||
chDbgCheck((i2cp != NULL) && (addr != 0) &&
|
||||
(rxbytes > 0) && (rxbuf != NULL) &&
|
||||
(timeout != TIME_IMMEDIATE),
|
||||
"i2cMasterReceiveTimeout");
|
||||
|
||||
chDbgCheck((i2cp != NULL) && (addr != 0) &&
|
||||
(rxbytes > 0) && (rxbuf != NULL) &&
|
||||
(timeout != TIME_IMMEDIATE),
|
||||
"i2cMasterReceiveTimeout");
|
||||
// chDbgAssert(i2cp->state == I2C_READY, "i2cMasterReceive(), #1", "not ready");
|
||||
if (i2cp->state != I2C_READY) {
|
||||
return RDY_TIMEOUT;
|
||||
}
|
||||
|
||||
chDbgAssert(i2cp->state == I2C_READY,
|
||||
"i2cMasterReceive(), #1", "not ready");
|
||||
|
||||
chSysLock();
|
||||
i2cp->errors = I2CD_NO_ERROR;
|
||||
i2cp->state = I2C_ACTIVE_RX;
|
||||
rdymsg = i2c_lld_master_receive_timeout(i2cp, addr, rxbuf, rxbytes, timeout);
|
||||
if (rdymsg == RDY_TIMEOUT)
|
||||
i2cp->state = I2C_LOCKED;
|
||||
else
|
||||
i2cp->state = I2C_READY;
|
||||
chSysUnlock();
|
||||
return rdymsg;
|
||||
chSysLock();
|
||||
i2cp->errors = I2CD_NO_ERROR;
|
||||
i2cp->state = I2C_ACTIVE_RX;
|
||||
rdymsg = i2c_lld_master_receive_timeout(i2cp, addr, rxbuf, rxbytes, timeout);
|
||||
if (rdymsg == RDY_TIMEOUT)
|
||||
i2cp->state = I2C_LOCKED;
|
||||
else
|
||||
i2cp->state = I2C_READY;
|
||||
chSysUnlock();
|
||||
return rdymsg;
|
||||
}
|
||||
|
||||
#if I2C_USE_MUTUAL_EXCLUSION || defined(__DOXYGEN__)
|
||||
@@ -273,14 +272,13 @@ msg_t i2cMasterReceiveTimeout(I2CDriver *i2cp,
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
void i2cAcquireBus(I2CDriver *i2cp) {
|
||||
|
||||
chDbgCheck(i2cp != NULL, "i2cAcquireBus");
|
||||
void i2cAcquireBus(I2CDriver* i2cp) {
|
||||
chDbgCheck(i2cp != NULL, "i2cAcquireBus");
|
||||
|
||||
#if CH_USE_MUTEXES
|
||||
chMtxLock(&i2cp->mutex);
|
||||
chMtxLock(&i2cp->mutex);
|
||||
#elif CH_USE_SEMAPHORES
|
||||
chSemWait(&i2cp->semaphore);
|
||||
chSemWait(&i2cp->semaphore);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -293,14 +291,13 @@ void i2cAcquireBus(I2CDriver *i2cp) {
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
void i2cReleaseBus(I2CDriver *i2cp) {
|
||||
|
||||
chDbgCheck(i2cp != NULL, "i2cReleaseBus");
|
||||
void i2cReleaseBus(I2CDriver* i2cp) {
|
||||
chDbgCheck(i2cp != NULL, "i2cReleaseBus");
|
||||
|
||||
#if CH_USE_MUTEXES
|
||||
chMtxUnlock();
|
||||
chMtxUnlock();
|
||||
#elif CH_USE_SEMAPHORES
|
||||
chSemSignal(&i2cp->semaphore);
|
||||
chSemSignal(&i2cp->semaphore);
|
||||
#endif
|
||||
}
|
||||
#endif /* I2C_USE_MUTUAL_EXCLUSION */
|
||||
|
||||
@@ -234,6 +234,11 @@ struct data_t {
|
||||
|
||||
uint16_t UNUSED : 4;
|
||||
|
||||
// Encoder debounce parameters for noisy hardware
|
||||
uint8_t encoder_consecutive_hits; // Number of consecutive same-direction hits required (1-10)
|
||||
uint8_t encoder_cooldown_ms; // Cooldown period in ms after movement (0-255ms)
|
||||
uint8_t encoder_debounce_ms; // Debounce stability window in ms (4-32ms)
|
||||
|
||||
// Headphone volume in centibels.
|
||||
int16_t headphone_volume_cb;
|
||||
|
||||
@@ -304,6 +309,10 @@ struct data_t {
|
||||
encoder_rate_multiplier(1),
|
||||
UNUSED(0),
|
||||
|
||||
encoder_consecutive_hits(3), // Default: 3 hits (Version 1 setting)
|
||||
encoder_cooldown_ms(20), // Default: 20ms (Version 1 setting)
|
||||
encoder_debounce_ms(16), // Default: 16ms stability window
|
||||
|
||||
headphone_volume_cb(-600),
|
||||
misc_config(),
|
||||
ui_config2(),
|
||||
@@ -1121,6 +1130,38 @@ void set_encoder_dial_direction(bool v) {
|
||||
data->encoder_dial_direction = v;
|
||||
}
|
||||
|
||||
// Encoder debounce parameters for noisy hardware
|
||||
uint8_t encoder_consecutive_hits() {
|
||||
uint8_t v = data->encoder_consecutive_hits;
|
||||
if (v == 0) v = 3; // default to 3 if not set
|
||||
if (v > 10) v = 10; // cap at 10
|
||||
return v;
|
||||
}
|
||||
void set_encoder_consecutive_hits(uint8_t v) {
|
||||
if (v < 1) v = 1; // minimum 1
|
||||
if (v > 10) v = 10; // maximum 10
|
||||
data->encoder_consecutive_hits = v;
|
||||
}
|
||||
|
||||
uint8_t encoder_cooldown_ms() {
|
||||
return data->encoder_cooldown_ms;
|
||||
}
|
||||
void set_encoder_cooldown_ms(uint8_t v) {
|
||||
data->encoder_cooldown_ms = v;
|
||||
}
|
||||
|
||||
uint8_t encoder_debounce_ms() {
|
||||
uint8_t v = data->encoder_debounce_ms;
|
||||
if (v < 4) v = 16; // default to 16ms if not set or too low
|
||||
if (v > 32) v = 32; // cap at 32ms
|
||||
return v;
|
||||
}
|
||||
void set_encoder_debounce_ms(uint8_t v) {
|
||||
if (v < 4) v = 4; // minimum 4ms
|
||||
if (v > 32) v = 32; // maximum 32ms
|
||||
data->encoder_debounce_ms = v;
|
||||
}
|
||||
|
||||
// Recovery mode magic value storage
|
||||
static data_t* data_direct_access = reinterpret_cast<data_t*>(memory::map::backup_ram.base());
|
||||
|
||||
|
||||
@@ -259,6 +259,13 @@ void set_encoder_rate_multiplier(uint8_t v);
|
||||
bool encoder_dial_direction();
|
||||
void set_encoder_dial_direction(bool v);
|
||||
|
||||
uint8_t encoder_consecutive_hits();
|
||||
void set_encoder_consecutive_hits(uint8_t v);
|
||||
uint8_t encoder_cooldown_ms();
|
||||
void set_encoder_cooldown_ms(uint8_t v);
|
||||
uint8_t encoder_debounce_ms();
|
||||
void set_encoder_debounce_ms(uint8_t v);
|
||||
|
||||
uint32_t config_mode_storage_direct();
|
||||
void set_config_mode_storage_direct(uint32_t v);
|
||||
bool config_disable_config_mode_direct();
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 215 B |
@@ -23,11 +23,24 @@
|
||||
#include "pacman.hpp"
|
||||
#include <memory>
|
||||
|
||||
void notouch(int, int, uint32_t) {
|
||||
// do nothing
|
||||
}
|
||||
void nothing() {
|
||||
// do nothing
|
||||
}
|
||||
bool noencoder(int32_t) {
|
||||
return false;
|
||||
}
|
||||
bool nokeyboard(uint8_t) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const standalone_application_api_t* _api;
|
||||
|
||||
extern "C" {
|
||||
__attribute__((section(".standalone_application_information"), used)) standalone_application_information_t _standalone_application_information = {
|
||||
/*.header_version = */ 1,
|
||||
/*.header_version = */ 2,
|
||||
|
||||
/*.app_name = */ "Pac-Man",
|
||||
/*.bitmap_data = */ {
|
||||
@@ -70,18 +83,17 @@ __attribute__((section(".standalone_application_information"), used)) standalone
|
||||
/*.initialize_app = */ initialize,
|
||||
/*.on_event = */ on_event,
|
||||
/*.shutdown = */ shutdown,
|
||||
/*PaintViewMirror */ NULL,
|
||||
/*OnTouchEvent */ NULL,
|
||||
/*OnFocus */ NULL,
|
||||
/*OnKeyEvent */ NULL,
|
||||
/*OnEncoder */ NULL,
|
||||
/*OnKeyboard */ NULL};
|
||||
/*PaintViewMirror */ nothing,
|
||||
/*OnTouchEvent */ notouch,
|
||||
/*OnFocus */ nothing,
|
||||
/*OnKeyEvent */ on_key_event,
|
||||
/*OnEncoder */ noencoder,
|
||||
/*OnKeyboard */ nokeyboard};
|
||||
}
|
||||
|
||||
/* Implementing abort() eliminates requirement for _getpid(), _kill(), _exit(). */
|
||||
extern "C" void abort() {
|
||||
while (true)
|
||||
;
|
||||
while (true);
|
||||
}
|
||||
|
||||
// replace memory allocations to use heap from chibios
|
||||
|
||||
@@ -39,8 +39,36 @@ void initialize(const standalone_application_api_t& api) {
|
||||
_api = &api;
|
||||
}
|
||||
|
||||
bool on_key_event(uint8_t key_val) {
|
||||
ui::KeyEvent key = (ui::KeyEvent)key_val;
|
||||
if (key == ui::KeyEvent::Right) {
|
||||
but_RIGHT = true;
|
||||
but_LEFT = false;
|
||||
but_DOWN = false;
|
||||
but_UP = false;
|
||||
} else if (key == ui::KeyEvent::Left) {
|
||||
but_LEFT = true;
|
||||
but_RIGHT = false;
|
||||
but_DOWN = false;
|
||||
but_UP = false;
|
||||
} else if (key == ui::KeyEvent::Down) {
|
||||
but_DOWN = true;
|
||||
but_RIGHT = false;
|
||||
but_LEFT = false;
|
||||
but_UP = false;
|
||||
} else if (key == ui::KeyEvent::Up) {
|
||||
but_UP = true;
|
||||
but_RIGHT = false;
|
||||
but_LEFT = false;
|
||||
but_DOWN = false;
|
||||
} else if (key == ui::KeyEvent::Select) {
|
||||
but_A = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void on_event(const uint32_t& events) {
|
||||
static bool wait_for_button_release{false};
|
||||
// static bool wait_for_button_release{false};
|
||||
|
||||
if (!_playfield) {
|
||||
_playfield = std::make_unique<Playfield>();
|
||||
@@ -48,29 +76,30 @@ void on_event(const uint32_t& events) {
|
||||
}
|
||||
|
||||
if (events & 1) {
|
||||
auto switches_raw = _api->swizzled_switches() & ((1 << (int)Switch::Right) | (1 << (int)Switch::Left) | (1 << (int)Switch::Down) | (1 << (int)Switch::Up) | (1 << (int)Switch::Sel) | (1 << (int)Switch::Dfu));
|
||||
auto switches_debounced = _api->get_switches_state();
|
||||
/*
|
||||
auto switches_raw = _api->swizzled_switches() & ((1 << (int)Switch::Right) | (1 << (int)Switch::Left) | (1 << (int)Switch::Down) | (1 << (int)Switch::Up) | (1 << (int)Switch::Sel) | (1 << (int)Switch::Dfu));
|
||||
auto switches_debounced = _api->get_switches_state();
|
||||
|
||||
// For the Select (Start/Pause) button, wait for release to avoid repeat
|
||||
uint8_t buttons_to_wait_for = (1 << (int)Switch::Sel);
|
||||
if (wait_for_button_release) {
|
||||
if ((switches_debounced & buttons_to_wait_for) == 0)
|
||||
wait_for_button_release = false;
|
||||
switches_debounced &= ~buttons_to_wait_for;
|
||||
} else {
|
||||
if (switches_debounced & buttons_to_wait_for)
|
||||
wait_for_button_release = true;
|
||||
}
|
||||
// For the Select (Start/Pause) button, wait for release to avoid repeat
|
||||
uint8_t buttons_to_wait_for = (1 << (int)Switch::Sel);
|
||||
if (wait_for_button_release) {
|
||||
if ((switches_debounced & buttons_to_wait_for) == 0)
|
||||
wait_for_button_release = false;
|
||||
switches_debounced &= ~buttons_to_wait_for;
|
||||
} else {
|
||||
if (switches_debounced & buttons_to_wait_for)
|
||||
wait_for_button_release = true;
|
||||
}
|
||||
|
||||
// For the directional buttons, use the raw inputs for fastest response time
|
||||
but_RIGHT = (switches_raw & (1 << (int)Switch::Right)) != 0;
|
||||
but_LEFT = (switches_raw & (1 << (int)Switch::Left)) != 0;
|
||||
but_DOWN = (switches_raw & (1 << (int)Switch::Down)) != 0;
|
||||
but_UP = (switches_raw & (1 << (int)Switch::Up)) != 0;
|
||||
|
||||
// For the pause button, use the debounced input to avoid glitches, and OR in the value to make sure that we don't clear it before it's seen
|
||||
but_A |= (switches_debounced & (1 << (int)Switch::Sel)) != 0;
|
||||
// For the directional buttons, use the raw inputs for fastest response time
|
||||
but_RIGHT = (switches_raw & (1 << (int)Switch::Right)) != 0;
|
||||
but_LEFT = (switches_raw & (1 << (int)Switch::Left)) != 0;
|
||||
but_DOWN = (switches_raw & (1 << (int)Switch::Down)) != 0;
|
||||
but_UP = (switches_raw & (1 << (int)Switch::Up)) != 0;
|
||||
|
||||
// For the pause button, use the debounced input to avoid glitches, and OR in the value to make sure that we don't clear it before it's seen
|
||||
but_A |= (switches_debounced & (1 << (int)Switch::Sel)) != 0;
|
||||
*/
|
||||
_playfield->Step();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
void initialize(const standalone_application_api_t& api);
|
||||
void on_event(const uint32_t& events);
|
||||
bool on_key_event(uint8_t key_val);
|
||||
void shutdown();
|
||||
|
||||
extern const standalone_application_api_t* _api;
|
||||
|
||||
@@ -133,39 +133,111 @@ const byte _initSprites[] =
|
||||
// Ghost colors
|
||||
const byte _palette2[] =
|
||||
{
|
||||
0, 11, 1, 15, // BINKY red
|
||||
0, 11, 3, 15, // PINKY pink
|
||||
0, 11, 5, 15, // INKY cyan
|
||||
0, 11, 7, 15, // CLYDE brown
|
||||
0, 11, 9, 9, // PACMAN yellow
|
||||
0, 11, 15, 15, // FRIGHTENED
|
||||
0, 11, 0, 15, // DEADEYES
|
||||
0,
|
||||
11,
|
||||
1,
|
||||
15, // BINKY red
|
||||
0,
|
||||
11,
|
||||
3,
|
||||
15, // PINKY pink
|
||||
0,
|
||||
11,
|
||||
5,
|
||||
15, // INKY cyan
|
||||
0,
|
||||
11,
|
||||
7,
|
||||
15, // CLYDE brown
|
||||
0,
|
||||
11,
|
||||
9,
|
||||
9, // PACMAN yellow
|
||||
0,
|
||||
11,
|
||||
15,
|
||||
15, // FRIGHTENED
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
15, // DEADEYES
|
||||
|
||||
0, 1, 15, 2, // cherry
|
||||
0, 1, 15, 12, // strawberry
|
||||
0, 7, 2, 12, // peach
|
||||
0, 9, 15, 0, // bell
|
||||
0,
|
||||
1,
|
||||
15,
|
||||
2, // cherry
|
||||
0,
|
||||
1,
|
||||
15,
|
||||
12, // strawberry
|
||||
0,
|
||||
7,
|
||||
2,
|
||||
12, // peach
|
||||
0,
|
||||
9,
|
||||
15,
|
||||
0, // bell
|
||||
|
||||
0, 15, 1, 2, // apple
|
||||
0, 12, 15, 5, // grape
|
||||
0, 11, 9, 1, // galaxian
|
||||
0, 5, 15, 15, // key
|
||||
0,
|
||||
15,
|
||||
1,
|
||||
2, // apple
|
||||
0,
|
||||
12,
|
||||
15,
|
||||
5, // grape
|
||||
0,
|
||||
11,
|
||||
9,
|
||||
1, // galaxian
|
||||
0,
|
||||
5,
|
||||
15,
|
||||
15, // key
|
||||
|
||||
};
|
||||
|
||||
const byte _paletteIcon2[] =
|
||||
{
|
||||
0, 9, 9, 9, // PACMAN
|
||||
0,
|
||||
9,
|
||||
9,
|
||||
9, // PACMAN
|
||||
|
||||
0, 2, 15, 1, // cherry
|
||||
0, 12, 15, 1, // strawberry
|
||||
0, 12, 2, 7, // peach
|
||||
0, 0, 15, 9, // bell
|
||||
0,
|
||||
2,
|
||||
15,
|
||||
1, // cherry
|
||||
0,
|
||||
12,
|
||||
15,
|
||||
1, // strawberry
|
||||
0,
|
||||
12,
|
||||
2,
|
||||
7, // peach
|
||||
0,
|
||||
0,
|
||||
15,
|
||||
9, // bell
|
||||
|
||||
0, 2, 15, 1, // apple
|
||||
0, 12, 15, 5, // grape
|
||||
0, 1, 9, 11, // galaxian
|
||||
0, 5, 15, 15, // key
|
||||
0,
|
||||
2,
|
||||
15,
|
||||
1, // apple
|
||||
0,
|
||||
12,
|
||||
15,
|
||||
5, // grape
|
||||
0,
|
||||
1,
|
||||
9,
|
||||
11, // galaxian
|
||||
0,
|
||||
5,
|
||||
15,
|
||||
15, // key
|
||||
};
|
||||
|
||||
#define PACMANICON 1
|
||||
@@ -571,7 +643,7 @@ class Playfield {
|
||||
}
|
||||
#endif
|
||||
|
||||
x += (240 - 224) / 2;
|
||||
x += (*(_api->screen_width) - 224) / 2;
|
||||
y += (320 - 288) / 2 + 8;
|
||||
|
||||
// Should be a direct Graphics call
|
||||
|
||||
Reference in New Issue
Block a user