Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create the Shopping Cart Lock app #2326

Merged
merged 7 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion firmware/application/external/external.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,10 @@ set(EXTCPPSRC
#acars
external/acars_rx/main.cpp
external/acars_rx/acars_app.cpp


#shoppingcart_lock
external/shoppingcart_lock/main.cpp
external/shoppingcart_lock/shoppingcart_lock.cpp
)

set(EXTAPPLIST
Expand Down Expand Up @@ -135,4 +138,5 @@ set(EXTAPPLIST
sstvtx
random_password
#acars_rx
shoppingcart_lock
)
7 changes: 6 additions & 1 deletion firmware/application/external/external.ld
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ MEMORY
ram_external_app_sstvtx(rwx) : org = 0xADC70000, len = 32k
ram_external_app_random_password(rwx) : org = 0xADC80000, len = 32k
ram_external_app_acars_rx(rwx) : org = 0xADC90000, len = 32k
ram_external_app_shoppingcart_lock(rwx) : org = 0xADCA0000, len = 32k
}

SECTIONS
Expand Down Expand Up @@ -166,7 +167,6 @@ SECTIONS
*(*ui*external_app*tpmsrx*);
} > ram_external_app_tpmsrx


.external_app_protoview : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_protoview.application_information));
Expand Down Expand Up @@ -204,6 +204,11 @@ SECTIONS
*(*ui*external_app*acars_rx*);
} > ram_external_app_acars_rx

.external_app_shoppingcart_lock : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_shoppingcart_lock.application_information));
*(*ui*external_app*shoppingcart_lock*);
} > ram_external_app_shoppingcart_lock



Expand Down
63 changes: 63 additions & 0 deletions firmware/application/external/shoppingcart_lock/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@

// RocketGod's Shopping Cart Lock app
// https://betaskynet.com
#include "ui.hpp"
#include "shoppingcart_lock.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"

namespace ui::external_app::shoppingcart_lock {
void initialize_app(NavigationView& nav) {
baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
nav.push<ShoppingCartLock>();
}
} // namespace ui::external_app::shoppingcart_lock

extern "C" {

__attribute__((section(".external_app.app_shoppingcart_lock.application_information"), used)) application_information_t _application_information_shoppingcart_lock = {
(uint8_t*)0x00000000,
ui::external_app::shoppingcart_lock::initialize_app,
CURRENT_HEADER_VERSION,
VERSION_MD5,
"Cart Lock",
{
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x7E,
0x7E,
0x00,
0x00,
0x00,
0x00,
0x00,
0x7E,
0x81,
0x81,
0x7E,
0x00,
0x00,
0x00,
0x7E,
0x81,
0x81,
0x81,
0x81,
0x7E,
0x00,
},
ui::Color::red().v,
app_location_t::UTILITIES,
{'P', 'A', 'T', 'X'},
0x00000000,
};
}
235 changes: 235 additions & 0 deletions firmware/application/external/shoppingcart_lock/shoppingcart_lock.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
// RocketGod's Shopping Cart Lock app
// https://betaskynet.com
#include "shoppingcart_lock.hpp"

using namespace portapack;

namespace ui::external_app::shoppingcart_lock {

void ShoppingCartLock::log_event(const std::string& message) {
static const size_t MAX_LOG_LINES = 50;
static std::vector<std::string> message_history;

message_history.push_back(message);
if (message_history.size() > MAX_LOG_LINES) {
message_history.erase(message_history.begin());
menu_view.clear();
for (const auto& msg : message_history) {
menu_view.add_item({msg,
ui::Theme::getInstance()->fg_green->foreground,
nullptr,
[](KeyEvent) {}});
}
} else {
menu_view.add_item({message,
ui::Theme::getInstance()->fg_green->foreground,
nullptr,
[](KeyEvent) {}});
}

menu_view.set_highlighted(menu_view.item_count() - 1);
}

bool ShoppingCartLock::is_active() const {
return (bool)replay_thread;
}

void ShoppingCartLock::focus() {
menu_view.focus();
}

void ShoppingCartLock::stop() {
log_event(">>> STOP_SEQUENCE_START");
if (is_active()) {
log_event("... Resetting Replay Thread");
replay_thread.reset();
}
log_event("... Stopping Audio Output");
audio::output::stop();

log_event("... Resetting State Variables");
transmitter_model.disable();
ready_signal = false;
thread_sync_complete = false;
looping = false;
current_file = "";
log_event("<<< STOP_SEQUENCE_COMPLETE");
}

std::string ShoppingCartLock::list_wav_files() {
log_event(">>> WAV_SCAN_START");
auto reader = std::make_unique<WAVFileReader>();
bool found_lock = false;
bool found_unlock = false;

for (const auto& entry : std::filesystem::directory_iterator(wav_dir, u"*")) {
if (std::filesystem::is_regular_file(entry.status())) {
auto filename = entry.path().filename().string();
std::transform(filename.begin(), filename.end(), filename.begin(), ::tolower);

if (filename == shoppingcart_lock_file || filename == shoppingcart_unlock_file) {
std::string file_path = (wav_dir / filename).string();
if (reader->open(file_path)) {
if (filename == shoppingcart_lock_file) {
found_lock = true;
log_event("... Found: " + shoppingcart_lock_file);
log_event("Sample Rate: " + std::to_string(reader->sample_rate()));
log_event("Channels: " + std::to_string(reader->channels()));
log_event("Bits/Sample: " + std::to_string(reader->bits_per_sample()));
}
if (filename == shoppingcart_unlock_file) {
found_unlock = true;
log_event("... Found: " + shoppingcart_unlock_file);
log_event("Sample Rate: " + std::to_string(reader->sample_rate()));
log_event("Channels: " + std::to_string(reader->channels()));
log_event("Bits/Sample: " + std::to_string(reader->bits_per_sample()));
}
}
}

if (found_lock && found_unlock) {
break;
}
}
}

if (!found_lock || !found_unlock) {
log_event("!!! Missing Required Files:");
if (!found_lock) log_event("!!! Missing: " + shoppingcart_lock_file);
if (!found_unlock) log_event("!!! Missing: " + shoppingcart_unlock_file);
menu_view.hidden(true);
text_empty.hidden(false);
} else {
log_event("... All Required Files Found");
menu_view.hidden(false);
text_empty.hidden(true);
}

log_event("<<< WAV_SCAN_COMPLETE");
return found_lock && found_unlock ? "Required WAV files found" : "Missing required WAV files";
}

void ShoppingCartLock::wait_for_thread() {
uint32_t timeout = 100;
while (!ready_signal && timeout > 0) {
chThdYield();
timeout--;
}
}

void ShoppingCartLock::restart_playback() {
auto reader = std::make_unique<WAVFileReader>();
std::string file_path = (wav_dir / current_file).string();

if (!reader->open(file_path)) return;

replay_thread = std::make_unique<ReplayThread>(
std::move(reader),
BUFFER_SIZE,
NUM_BUFFERS,
&ready_signal,
[](uint32_t return_code) {
ReplayThreadDoneMessage message{return_code};
EventDispatcher::send_message(message);
});

log_event(">> SENDING <<");
audio::output::start();
transmitter_model.enable();
}

void ShoppingCartLock::play_audio(const std::string& filename, bool loop) {
auto reader = std::make_unique<WAVFileReader>();
stop();

std::string file_path = (wav_dir / filename).string();
if (!reader->open(file_path)) {
nav_.display_modal("Error", "Cannot open " + filename);
return;
}

const uint32_t wav_sample_rate = reader->sample_rate();
const uint16_t wav_bits_per_sample = reader->bits_per_sample();

current_file = filename;
looping = loop;

replay_thread = std::make_unique<ReplayThread>(
std::move(reader),
BUFFER_SIZE,
NUM_BUFFERS,
&ready_signal,
[](uint32_t return_code) {
ReplayThreadDoneMessage message{return_code};
EventDispatcher::send_message(message);
});

wait_for_thread();

log_event("... Configuring Baseband");

const uint32_t bb_sample_rate = 1536000;
const uint32_t decimation = bb_sample_rate / wav_sample_rate;

baseband::set_audiotx_config(
bb_sample_rate / decimation,
0.0f,
5.0f,
wav_bits_per_sample,
wav_bits_per_sample,
0,
true,
false,
false,
false);

baseband::set_sample_rate(wav_sample_rate);

log_event("... Starting Audio Output");
audio::output::start();
log_event("... Setting Max Volume");
audio::headphone::set_volume(audio::headphone::volume_range().max);

transmitter_model.enable();

log_event(">>> Playback Started <<<");
}

ShoppingCartLock::ShoppingCartLock(NavigationView& nav)
: nav_{nav} {
add_children({&menu_view,
&text_empty,
&button_lock,
&button_unlock,
&button_stop});

button_lock.on_select = [this](Button&) {
if (is_active()) stop();
log_event(">>> LOCK_SEQUENCE_START");
play_audio(shoppingcart_lock_file, true);
};

button_unlock.on_select = [this](Button&) {
if (is_active()) stop();
log_event(">>> UNLOCK_SEQUENCE_START");
play_audio(shoppingcart_unlock_file, true);
};

button_stop.on_select = [this](Button&) {
log_event(">>> STOPPING AUDIO");
stop();
};

list_wav_files();

log_event("[+] INITIALIZATION COMPLETE");
log_event("[+] PORTAPACK ARMED");
log_event("[*] STATUS: READY");
}

ShoppingCartLock::~ShoppingCartLock() {
stop();
baseband::shutdown();
}

} // namespace ui::external_app::shoppingcart_lock
Loading
Loading