V49 Release Candidate Changes (#321)

This commit is contained in:
github-actions[bot]
2023-07-04 22:07:53 +00:00
committed by GitHub
1414 changed files with 80557 additions and 9624 deletions

View File

@@ -82,6 +82,6 @@ if __name__ == "__main__":
print(f"{req.url = }\n{req.status_code = }\n{req.content = }")
sys.exit(1)
changelog = body.split("## 🚀 Changelog", 1)[1].rsplit("## ❤️ Support", 1)[0]
changelog = body.split("## 🚀 Changelog", 1)[1]
with open(os.environ["ARTIFACT_TGZ"].removesuffix(".tgz") + ".md", "w") as f:
f.write(changelog.strip() + "\n\n")

View File

@@ -7,6 +7,17 @@
**Check the [install guide](https://github.com/ClaraCrazy/Flipper-Xtreme#install) if you're not sure, or [join our Discord](https://discord.gg/flipper-xtreme) if you have questions or encounter issues!**
## ❤️ Support
If you like what you're seeing, **please consider donating to us**. We won't ever put this behind a paywall, but we'd still appreciate a few bucks!
- **[Patreon](https://patreon.com/CynthiaLabs)**: ❤️ Account needed, subscription with perks across my entire org.
- **[Wire-transfer](https://bunq.me/ClaraK)**: No account needed, one-time
- **[Paypal](https://paypal.me/RdX2020)**: Account needed, one-time
- **[ko-fi](https://ko-fi.com/cynthialabs)**: No account needed, one-time
- **Monero**: `41kyWeeoVdK4quzQ4M9ikVGs6tCQCLfdx8jLExTNsAu2SF1QAyDqRdjfGM6EL8L9NpXwt89HJeAoGf1aoArk7nDr4AMMV4T`
**Thanks for all your support <3**
## 🚀 Changelog
{CHANGELOG}
@@ -17,13 +28,3 @@
**If you have any of the above issues, please re-download and re-install!**
<HOTFIXES> -->
## ❤️ Support
If you like what you're seeing, **please consider donating to us**. We won't ever put this behind a paywall, but we'd still appreciate a few bucks!
- **[Direct Wire-transfer](https://bunq.me/ClaraK)**: No account needed, just specify amount and hit send
- **[Patreon](https://patreon.com/CynthiaLabs)**
- **[Paypal](https://paypal.me/RdX2020)**
- **Monero**: `41kyWeeoVdK4quzQ4M9ikVGs6tCQCLfdx8jLExTNsAu2SF1QAyDqRdjfGM6EL8L9NpXwt89HJeAoGf1aoArk7nDr4AMMV4T`
**Thanks for all your support <3**

View File

@@ -28,8 +28,8 @@ jobs:
run: |
set -e
for TARGET in ${TARGETS}; do
TARGET="$(echo "${TARGET}" | sed 's/f//')"; \
./fbt TARGET_HW=$TARGET updater_package
TARGET_HW="$(echo "${TARGET}" | sed 's/f//')"; \
./fbt TARGET_HW=$TARGET_HW updater_package
done
- name: "Check for uncommitted changes"

View File

@@ -35,8 +35,8 @@ jobs:
run: |
set -e
for TARGET in ${TARGETS}; do
TARGET="$(echo "${TARGET}" | sed 's/f//')"; \
./fbt TARGET_HW=$TARGET updater_package
TARGET_HW="$(echo "${TARGET}" | sed 's/f//')"; \
./fbt TARGET_HW=$TARGET_HW FORCE_NO_DIRTY=1 updater_package
done
- name: "Check for uncommitted changes"

View File

@@ -35,8 +35,8 @@ jobs:
run: |
set -e
for TARGET in ${TARGETS}; do
TARGET="$(echo "${TARGET}" | sed 's/f//')"; \
./fbt TARGET_HW=$TARGET updater_package
TARGET_HW="$(echo "${TARGET}" | sed 's/f//')"; \
./fbt TARGET_HW=$TARGET_HW FORCE_NO_DIRTY=1 updater_package
done
- name: "Check for uncommitted changes"

View File

@@ -53,8 +53,8 @@ jobs:
mkdir ${{ env.BUILD_WRAPPER_OUT_DIR }}
set -e
for TARGET in ${TARGETS}; do
TARGET="$(echo "${TARGET}" | sed 's/f//')"; \
build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} ./sonar-build "./fbt TARGET_HW=$TARGET updater_package"
TARGET_HW="$(echo "${TARGET}" | sed 's/f//')"; \
build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} ./sonar-build "./fbt TARGET_HW=$TARGET_HW updater_package"
done
- name: Run sonar-scanner

View File

@@ -48,7 +48,7 @@ Almost everything in flipper firmware is built around this concept.
# C coding style
- Tab is 4 spaces
- Use `fbt format` to reformat source code and check style guide before commit
- Use `./fbt format` to reformat source code and check style guide before commit
## Naming

View File

@@ -183,13 +183,13 @@ There are 3 methods to install Xtreme, we recommend you use the **Web Updater**,
<br>
**If you have issues or crashes with the install process, you can try to use `Settings > Storage > Factory Reset` then retry the install.**
**Doing that will NOT remove your saved files, it will only forget some settings and paired devices.**
----
<br>
<h2 align="center">Build it yourself:</h2>
> **Warning**
> We will not give basic support for compiling in our server. This is intended for people that already *know* what they are doing!
```bash
To download the needed tools:
$ git clone --recursive https://github.com/ClaraCrazy/Flipper-Xtreme.git
@@ -222,12 +222,13 @@ $ ./fbt resources icons dolphin_ext
## ❤️ Support
If you like what you're seeing, **please consider donating to us**. We won't ever put this behind a paywall, but we'd still appreciate a few bucks!
- **[Direct Wire-transfer](https://bunq.me/ClaraK)**: No account needed, just specify amount and hit send
- **[Patreon](https://patreon.com/CynthiaLabs)**
- **[Paypal](https://paypal.me/RdX2020)**
- **[Patreon](https://patreon.com/CynthiaLabs)**: ❤️ Account needed, subscription with perks across my entire org.
- **[Wire-transfer](https://bunq.me/ClaraK)**: No account needed, one-time
- **[Paypal](https://paypal.me/RdX2020)**: Account needed, one-time
- **[ko-fi](https://ko-fi.com/cynthialabs)**: No account needed, one-time
- **Monero**: `41kyWeeoVdK4quzQ4M9ikVGs6tCQCLfdx8jLExTNsAu2SF1QAyDqRdjfGM6EL8L9NpXwt89HJeAoGf1aoArk7nDr4AMMV4T`
**Thanks for all your support <3**
----
<p align="center"> "What we do for ourselves dies with us. What we do for others and the world remains and is immortal.” ― Albert Pine </p>
<p align="center"> "What we do for ourselves dies with us. What we do for others and the world remains and is immortal.” ― Albert Pine </p>

View File

@@ -171,7 +171,7 @@ distenv.Depends(firmware_env["FW_RESOURCES"], external_apps_artifacts.resources_
fap_deploy = distenv.PhonyTarget(
"fap_deploy",
"${PYTHON3} ${ROOT_DIR}/scripts/storage.py send ${SOURCE} /ext/apps",
"${PYTHON3} ${FBT_SCRIPT_DIR}/storage.py -p ${FLIP_PORT} send ${SOURCE} /ext/apps",
source=Dir("#/assets/resources/apps"),
)
@@ -323,7 +323,9 @@ distenv.PhonyTarget(
)
# Start Flipper CLI via PySerial's miniterm
distenv.PhonyTarget("cli", "${PYTHON3} ${FBT_SCRIPT_DIR}/serial_cli.py")
distenv.PhonyTarget(
"cli", "${PYTHON3} ${FBT_SCRIPT_DIR}/serial_cli.py -p ${FLIP_PORT}"
)
# Find blackmagic probe

View File

@@ -26,7 +26,6 @@ Applications for main Flipper menu.
- `archive` - Archive and file manager
- `bad_kb` - Bad KB application
- `fap_loader` - External applications loader
- `gpio` - GPIO application: includes USART bridge and GPIO control
- `ibutton` - iButton application, onewire keys and more
- `infrared` - Infrared application, controls your IR devices

View File

@@ -12,5 +12,6 @@ App(
"display_test",
"text_box_test",
"file_browser_test",
"speaker_debug",
],
)

View File

@@ -11,5 +11,4 @@ App(
stack_size=1 * 1024,
order=130,
fap_category="Debug",
fap_libs=["assets"],
)

View File

@@ -0,0 +1,10 @@
App(
appid="crash_test",
name="Crash Test",
apptype=FlipperAppType.DEBUG,
entry_point="crash_test_app",
cdefines=["APP_CRASH_TEST"],
requires=["gui"],
stack_size=1 * 1024,
fap_category="Debug",
)

View File

@@ -0,0 +1,128 @@
#include <furi_hal.h>
#include <furi.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/modules/submenu.h>
#define TAG "CrashTest"
typedef struct {
Gui* gui;
ViewDispatcher* view_dispatcher;
Submenu* submenu;
} CrashTest;
typedef enum {
CrashTestViewSubmenu,
} CrashTestView;
typedef enum {
CrashTestSubmenuCheck,
CrashTestSubmenuCheckMessage,
CrashTestSubmenuAssert,
CrashTestSubmenuAssertMessage,
CrashTestSubmenuCrash,
CrashTestSubmenuHalt,
} CrashTestSubmenu;
static void crash_test_submenu_callback(void* context, uint32_t index) {
CrashTest* instance = (CrashTest*)context;
UNUSED(instance);
switch(index) {
case CrashTestSubmenuCheck:
furi_check(false);
break;
case CrashTestSubmenuCheckMessage:
furi_check(false, "Crash test: furi_check with message");
break;
case CrashTestSubmenuAssert:
furi_assert(false);
break;
case CrashTestSubmenuAssertMessage:
furi_assert(false, "Crash test: furi_assert with message");
break;
case CrashTestSubmenuCrash:
furi_crash("Crash test: furi_crash");
break;
case CrashTestSubmenuHalt:
furi_halt("Crash test: furi_halt");
break;
default:
furi_crash("Programming error");
}
}
static uint32_t crash_test_exit_callback(void* context) {
UNUSED(context);
return VIEW_NONE;
}
CrashTest* crash_test_alloc() {
CrashTest* instance = malloc(sizeof(CrashTest));
View* view = NULL;
instance->gui = furi_record_open(RECORD_GUI);
instance->view_dispatcher = view_dispatcher_alloc();
view_dispatcher_enable_queue(instance->view_dispatcher);
view_dispatcher_attach_to_gui(
instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
// Menu
instance->submenu = submenu_alloc();
view = submenu_get_view(instance->submenu);
view_set_previous_callback(view, crash_test_exit_callback);
view_dispatcher_add_view(instance->view_dispatcher, CrashTestViewSubmenu, view);
submenu_add_item(
instance->submenu, "Check", CrashTestSubmenuCheck, crash_test_submenu_callback, instance);
submenu_add_item(
instance->submenu,
"Check with message",
CrashTestSubmenuCheckMessage,
crash_test_submenu_callback,
instance);
submenu_add_item(
instance->submenu, "Assert", CrashTestSubmenuAssert, crash_test_submenu_callback, instance);
submenu_add_item(
instance->submenu,
"Assert with message",
CrashTestSubmenuAssertMessage,
crash_test_submenu_callback,
instance);
submenu_add_item(
instance->submenu, "Crash", CrashTestSubmenuCrash, crash_test_submenu_callback, instance);
submenu_add_item(
instance->submenu, "Halt", CrashTestSubmenuHalt, crash_test_submenu_callback, instance);
return instance;
}
void crash_test_free(CrashTest* instance) {
view_dispatcher_remove_view(instance->view_dispatcher, CrashTestViewSubmenu);
submenu_free(instance->submenu);
view_dispatcher_free(instance->view_dispatcher);
furi_record_close(RECORD_GUI);
free(instance);
}
int32_t crash_test_run(CrashTest* instance) {
view_dispatcher_switch_to_view(instance->view_dispatcher, CrashTestViewSubmenu);
view_dispatcher_run(instance->view_dispatcher);
return 0;
}
int32_t crash_test_app(void* p) {
UNUSED(p);
CrashTest* instance = crash_test_alloc();
int32_t ret = crash_test_run(instance);
crash_test_free(instance);
return ret;
}

View File

@@ -6,6 +6,11 @@ static void comparator_trigger_callback(bool level, void* comp_ctx) {
furi_hal_gpio_write(&gpio_ext_pa7, !level);
}
void lfrfid_debug_view_tune_callback(void* context) {
LfRfidDebug* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, 0xBA);
}
void lfrfid_debug_scene_tune_on_enter(void* context) {
LfRfidDebug* app = context;
@@ -16,6 +21,8 @@ void lfrfid_debug_scene_tune_on_enter(void* context) {
furi_hal_rfid_tim_read_start(125000, 0.5);
lfrfid_debug_view_tune_set_callback(app->tune_view, lfrfid_debug_view_tune_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidDebugViewTune);
}

View File

@@ -13,6 +13,8 @@ typedef struct {
uint32_t ARR;
uint32_t CCR;
int pos;
void (*update_callback)(void* context);
void* update_context;
} LfRfidTuneViewModel;
static void lfrfid_debug_view_tune_draw_callback(Canvas* canvas, void* _model) {
@@ -151,6 +153,18 @@ static bool lfrfid_debug_view_tune_input_callback(InputEvent* event, void* conte
consumed = false;
break;
}
if(event->key == InputKeyLeft || event->key == InputKeyRight) {
with_view_model(
tune_view->view,
LfRfidTuneViewModel * model,
{
if(model->update_callback) {
model->update_callback(model->update_context);
}
},
false);
}
}
return consumed;
@@ -161,19 +175,7 @@ LfRfidTuneView* lfrfid_debug_view_tune_alloc() {
tune_view->view = view_alloc();
view_set_context(tune_view->view, tune_view);
view_allocate_model(tune_view->view, ViewModelTypeLocking, sizeof(LfRfidTuneViewModel));
with_view_model(
tune_view->view,
LfRfidTuneViewModel * model,
{
model->dirty = true;
model->fine = false;
model->ARR = 511;
model->CCR = 255;
model->pos = 0;
},
true);
lfrfid_debug_view_tune_clean(tune_view);
view_set_draw_callback(tune_view->view, lfrfid_debug_view_tune_draw_callback);
view_set_input_callback(tune_view->view, lfrfid_debug_view_tune_input_callback);
@@ -199,6 +201,8 @@ void lfrfid_debug_view_tune_clean(LfRfidTuneView* tune_view) {
model->ARR = 511;
model->CCR = 255;
model->pos = 0;
model->update_callback = NULL;
model->update_context = NULL;
},
true);
}
@@ -232,3 +236,17 @@ uint32_t lfrfid_debug_view_tune_get_ccr(LfRfidTuneView* tune_view) {
return result;
}
void lfrfid_debug_view_tune_set_callback(
LfRfidTuneView* tune_view,
void (*callback)(void* context),
void* context) {
with_view_model(
tune_view->view,
LfRfidTuneViewModel * model,
{
model->update_callback = callback;
model->update_context = context;
},
false);
}

View File

@@ -16,3 +16,8 @@ bool lfrfid_debug_view_tune_is_dirty(LfRfidTuneView* tune_view);
uint32_t lfrfid_debug_view_tune_get_arr(LfRfidTuneView* tune_view);
uint32_t lfrfid_debug_view_tune_get_ccr(LfRfidTuneView* tune_view);
void lfrfid_debug_view_tune_set_callback(
LfRfidTuneView* tune_view,
void (*callback)(void* context),
void* context);

View File

@@ -0,0 +1,11 @@
App(
appid="speaker_debug",
name="Speaker Debug",
apptype=FlipperAppType.DEBUG,
entry_point="speaker_debug_app",
requires=["gui", "notification"],
stack_size=2 * 1024,
order=10,
fap_category="Debug",
fap_libs=["music_worker"],
)

View File

@@ -0,0 +1,120 @@
#include <furi.h>
#include <notification/notification.h>
#include <music_worker/music_worker.h>
#include <cli/cli.h>
#include <toolbox/args.h>
#define TAG "SpeakerDebug"
#define CLI_COMMAND "speaker_debug"
typedef enum {
SpeakerDebugAppMessageTypeStop,
} SpeakerDebugAppMessageType;
typedef struct {
SpeakerDebugAppMessageType type;
} SpeakerDebugAppMessage;
typedef struct {
MusicWorker* music_worker;
FuriMessageQueue* message_queue;
Cli* cli;
} SpeakerDebugApp;
static SpeakerDebugApp* speaker_app_alloc() {
SpeakerDebugApp* app = (SpeakerDebugApp*)malloc(sizeof(SpeakerDebugApp));
app->music_worker = music_worker_alloc();
app->message_queue = furi_message_queue_alloc(8, sizeof(SpeakerDebugAppMessage));
app->cli = furi_record_open(RECORD_CLI);
return app;
}
static void speaker_app_free(SpeakerDebugApp* app) {
music_worker_free(app->music_worker);
furi_message_queue_free(app->message_queue);
furi_record_close(RECORD_CLI);
free(app);
}
static void speaker_app_cli(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
SpeakerDebugApp* app = (SpeakerDebugApp*)context;
SpeakerDebugAppMessage message;
FuriString* cmd = furi_string_alloc();
if(!args_read_string_and_trim(args, cmd)) {
furi_string_free(cmd);
printf("Usage:\r\n");
printf("\t" CLI_COMMAND " stop\r\n");
return;
}
if(furi_string_cmp(cmd, "stop") == 0) {
message.type = SpeakerDebugAppMessageTypeStop;
FuriStatus status = furi_message_queue_put(app->message_queue, &message, 100);
if(status != FuriStatusOk) {
printf("Failed to send message\r\n");
} else {
printf("Stopping\r\n");
}
} else {
printf("Usage:\r\n");
printf("\t" CLI_COMMAND " stop\r\n");
}
furi_string_free(cmd);
}
static bool speaker_app_music_play(SpeakerDebugApp* app, const char* rtttl) {
if(music_worker_is_playing(app->music_worker)) {
music_worker_stop(app->music_worker);
}
if(!music_worker_load_rtttl_from_string(app->music_worker, rtttl)) {
FURI_LOG_E(TAG, "Failed to load RTTTL");
return false;
}
music_worker_set_volume(app->music_worker, 1.0f);
music_worker_start(app->music_worker);
return true;
}
static void speaker_app_music_stop(SpeakerDebugApp* app) {
if(music_worker_is_playing(app->music_worker)) {
music_worker_stop(app->music_worker);
}
}
static void speaker_app_run(SpeakerDebugApp* app, const char* arg) {
if(!arg || !speaker_app_music_play(app, arg)) {
FURI_LOG_E(TAG, "Provided RTTTL is invalid");
return;
}
cli_add_command(app->cli, CLI_COMMAND, CliCommandFlagParallelSafe, speaker_app_cli, app);
SpeakerDebugAppMessage message;
FuriStatus status;
while(true) {
status = furi_message_queue_get(app->message_queue, &message, FuriWaitForever);
if(status == FuriStatusOk) {
if(message.type == SpeakerDebugAppMessageTypeStop) {
speaker_app_music_stop(app);
break;
}
}
}
cli_delete_command(app->cli, CLI_COMMAND);
}
int32_t speaker_debug_app(void* arg) {
SpeakerDebugApp* app = speaker_app_alloc();
speaker_app_run(app, arg);
speaker_app_free(app);
return 0;
}

View File

@@ -1,5 +1,5 @@
App(
appid="UART_Echo",
appid="uart_echo",
name="[GPIO] UART Echo",
apptype=FlipperAppType.DEBUG,
entry_point="uart_echo_app",

View File

@@ -10,6 +10,8 @@
#define LINES_ON_SCREEN 6
#define COLUMNS_ON_SCREEN 21
#define TAG "UartEcho"
#define DEFAULT_BAUD_RATE 230400
typedef struct UartDumpModel UartDumpModel;
@@ -179,7 +181,7 @@ static int32_t uart_echo_worker(void* context) {
return 0;
}
static UartEchoApp* uart_echo_app_alloc() {
static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) {
UartEchoApp* app = malloc(sizeof(UartEchoApp));
app->rx_stream = furi_stream_buffer_alloc(2048, 1);
@@ -220,7 +222,7 @@ static UartEchoApp* uart_echo_app_alloc() {
// Enable uart listener
furi_hal_console_disable();
furi_hal_uart_set_br(FuriHalUartIdUSART1, 115200);
furi_hal_uart_set_br(FuriHalUartIdUSART1, baudrate);
furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, uart_echo_on_irq_cb, app);
return app;
@@ -263,8 +265,18 @@ static void uart_echo_app_free(UartEchoApp* app) {
}
int32_t uart_echo_app(void* p) {
UNUSED(p);
UartEchoApp* app = uart_echo_app_alloc();
uint32_t baudrate = DEFAULT_BAUD_RATE;
if(p) {
const char* baudrate_str = p;
if(sscanf(baudrate_str, "%lu", &baudrate) != 1) {
FURI_LOG_E(TAG, "Invalid baudrate: %s", baudrate_str);
baudrate = DEFAULT_BAUD_RATE;
}
}
FURI_LOG_I(TAG, "Using baudrate: %lu", baudrate);
UartEchoApp* app = uart_echo_app_alloc(baudrate);
view_dispatcher_run(app->view_dispatcher);
uart_echo_app_free(app);
return 0;

View File

@@ -0,0 +1,32 @@
#include <dialogs/dialogs.h>
#include "../minunit.h"
MU_TEST(test_dialog_file_browser_set_basic_options_should_init_all_fields) {
mu_assert(
sizeof(DialogsFileBrowserOptions) == 28,
"Changes to `DialogsFileBrowserOptions` should also be reflected in `dialog_file_browser_set_basic_options`");
DialogsFileBrowserOptions options;
dialog_file_browser_set_basic_options(&options, ".fap", NULL);
// note: this assertions can safely be changed, their primary purpose is to remind the maintainer
// to update `dialog_file_browser_set_basic_options` by including all structure fields in it
mu_assert_string_eq(".fap", options.extension);
mu_assert_null(options.base_path);
mu_assert(options.skip_assets, "`skip_assets` should default to `true");
mu_assert(options.hide_dot_files, "`hide_dot_files` should default to `true");
mu_assert_null(options.icon);
mu_assert(options.hide_ext, "`hide_ext` should default to `true");
mu_assert_null(options.item_loader_callback);
mu_assert_null(options.item_loader_context);
}
MU_TEST_SUITE(dialogs_file_browser_options) {
MU_RUN_TEST(test_dialog_file_browser_set_basic_options_should_init_all_fields);
}
int run_minunit_test_dialogs_file_browser_options() {
MU_RUN_SUITE(dialogs_file_browser_options);
return MU_EXIT_CODE;
}

View File

@@ -43,7 +43,7 @@ static RpcSessionContext rpc_session[TEST_RPC_SESSIONS];
#define TAG "UnitTestsRpc"
#define MAX_RECEIVE_OUTPUT_TIMEOUT 3000
#define MAX_NAME_LENGTH 255
#define MAX_NAME_LENGTH 254
#define MAX_DATA_SIZE 512u // have to be exact as in rpc_storage.c
#define TEST_DIR TEST_DIR_NAME "/"
#define TEST_DIR_NAME EXT_PATH("unit_tests_tmp")
@@ -186,7 +186,7 @@ static void clean_directory(Storage* fs_api, const char* clean_dir) {
File* dir = storage_file_alloc(fs_api);
if(storage_dir_open(dir, clean_dir)) {
FileInfo fileinfo;
char* name = malloc(MAX_NAME_LENGTH + 1);
char* name = malloc(MAX_NAME_LENGTH);
while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) {
size_t size = strlen(clean_dir) + strlen(name) + 1 + 1;
char* fullname = malloc(size);
@@ -598,7 +598,7 @@ static void test_rpc_storage_list_create_expected_list(
while(!finish) {
FileInfo fileinfo;
char* name = malloc(MAX_NAME_LENGTH + 1);
char* name = malloc(MAX_NAME_LENGTH);
if(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) {
if(i == COUNT_OF(list->file)) {
list->file_count = i;

View File

@@ -27,6 +27,7 @@ int run_minunit_test_nfc();
int run_minunit_test_bit_lib();
int run_minunit_test_float_tools();
int run_minunit_test_bt();
int run_minunit_test_dialogs_file_browser_options();
typedef int (*UnitTestEntry)();
@@ -55,6 +56,8 @@ const UnitTest unit_tests[] = {
{.name = "bit_lib", .entry = run_minunit_test_bit_lib},
{.name = "float_tools", .entry = run_minunit_test_float_tools},
{.name = "bt", .entry = run_minunit_test_bt},
{.name = "dialogs_file_browser_options",
.entry = run_minunit_test_dialogs_file_browser_options},
};
void minunit_print_progress() {

View File

@@ -7,6 +7,6 @@ App(
requires=["gui"],
stack_size=1 * 1024,
order=60,
fap_icon="../../external/mousejacker/mouse_10px.png",
fap_icon="mouse_10px.png",
fap_category="Debug",
)

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,12 @@
App(
appid="mass_storage",
name="USB Mass Storage",
apptype=FlipperAppType.EXTERNAL,
entry_point="mass_storage_app",
cdefines=["APP_MASS_STORAGE"],
requires=["gui"],
stack_size=1 * 1024,
order=2,
# fap_icon="",
fap_category="Misc",
)

View File

@@ -0,0 +1,220 @@
#include "mass_storage_scsi.h"
#define TAG "MassStorageSCSI"
#define SCSI_TEST_UNIT_READY (0x00)
#define SCSI_REQUEST_SENSE (0x03)
#define SCSI_INQUIRY (0x12)
#define SCSI_READ_CAPACITY_6 (0x25)
#define SCSI_MODE_SENSE_6 (0x1A)
#define SCSI_READ_10 (0x28)
#define SCSI_PREVENT_MEDIUM_REMOVAL (0x1E)
#define SCSI_START_STOP_UNIT (0x1B)
#define SCSI_WRITE_10 (0x2A)
bool scsi_cmd_start(SCSISession* scsi, uint8_t* cmd, uint8_t len) {
if(!len) {
scsi->sk = SCSI_SK_ILLEGAL_REQUEST;
scsi->asc = SCSI_ASC_INVALID_COMMAND_OPERATION_CODE;
return false;
}
// FURI_LOG_I(TAG, "START %02x", cmd[0]);
scsi->cmd = cmd;
scsi->cmd_len = len;
scsi->rx_done = false;
scsi->tx_done = false;
switch(cmd[0]) {
case SCSI_WRITE_10: {
if(len < 10) return false;
scsi->write_10.lba = cmd[2] << 24 | cmd[3] << 16 | cmd[4] << 8 | cmd[5];
scsi->write_10.count = cmd[7] << 8 | cmd[8];
FURI_LOG_I(TAG, "SCSI_WRITE_10 %08lx %04x", scsi->write_10.lba, scsi->write_10.count);
return true;
}; break;
case SCSI_READ_10: {
if(len < 10) return false;
scsi->read_10.lba = cmd[2] << 24 | cmd[3] << 16 | cmd[4] << 8 | cmd[5];
scsi->read_10.count = cmd[7] << 8 | cmd[8];
FURI_LOG_I(TAG, "SCSI_READ_10 %08lx %04x", scsi->read_10.lba, scsi->read_10.count);
return true;
}; break;
}
return true;
}
bool scsi_cmd_rx_data(SCSISession* scsi, uint8_t* data, uint32_t len) {
// FURI_LOG_I(TAG, "RX %02x len %d", scsi->cmd[0], len);
if(scsi->rx_done) return false;
switch(scsi->cmd[0]) {
case SCSI_WRITE_10: {
uint32_t block_size = SCSI_BLOCK_SIZE;
uint16_t blocks = len / block_size;
bool result =
scsi->fn.write(scsi->fn.ctx, scsi->write_10.lba, blocks, data, blocks * block_size);
scsi->write_10.lba += blocks;
scsi->write_10.count -= blocks;
if(!scsi->write_10.count) {
scsi->rx_done = true;
}
return result;
}; break;
default: {
FURI_LOG_W(TAG, "unexpected scsi rx data cmd=%02x", scsi->cmd[0]);
scsi->sk = SCSI_SK_ILLEGAL_REQUEST;
scsi->asc = SCSI_ASC_INVALID_COMMAND_OPERATION_CODE;
return false;
}; break;
}
}
bool scsi_cmd_tx_data(SCSISession* scsi, uint8_t* data, uint32_t* len, uint32_t cap) {
// FURI_LOG_I(TAG, "TX %02x cap %d", scsi->cmd[0], cap);
if(scsi->tx_done) return false;
switch(scsi->cmd[0]) {
case SCSI_REQUEST_SENSE: {
FURI_LOG_I(TAG, "SCSI_REQUEST_SENSE");
if(cap < 18) return false;
memset(data, 0, cap);
data[0] = 0x70; // fixed format sense data
data[1] = 0; // obsolete
data[2] = scsi->sk; // sense key
data[3] = 0; // information
data[4] = 0; // information
data[5] = 0; // information
data[6] = 0; // information
data[7] = 10; // additional sense length (len-8)
data[8] = 0; // command specific information
data[9] = 0; // command specific information
data[10] = 0; // command specific information
data[11] = 0; // command specific information
data[12] = scsi->asc; // additional sense code
data[13] = 0; // additional sense code qualifier
data[14] = 0; // field replaceable unit code
data[15] = 0; // sense key specific information
data[16] = 0; // sense key specific information
data[17] = 0; // sense key specific information
*len = 18;
scsi->sk = 0;
scsi->asc = 0;
scsi->tx_done = true;
return true;
}; break;
case SCSI_INQUIRY: {
FURI_LOG_I(TAG, "SCSI_INQUIRY");
if(scsi->cmd_len < 5) return false;
if(cap < 36) return false;
bool evpd = scsi->cmd[1] & 1;
uint8_t page_code = scsi->cmd[2];
// uint16_t alloc_len = scsi->cmd[3] << 8 | scsi->cmd[4];
if(evpd) return false;
if(page_code) return false;
data[0] = 0x00; // device type: direct access block device
data[1] = 0x80; // removable: true
data[2] = 0x04; // version
data[3] = 0x02; // response data format
data[4] = 31; // additional length (len - 5)
data[5] = 0; // flags
data[6] = 0; // flags
data[7] = 0; // flags
memcpy(data + 8, "Flipper ", 8); // vendor id
memcpy(data + 16, "Mass Storage ", 16); // product id
memcpy(data + 32, "0001", 4); // product revision level
*len = 36;
scsi->tx_done = true;
return true;
}; break;
case SCSI_READ_CAPACITY_6: {
FURI_LOG_I(TAG, "SCSI_READ_CAPACITY_6");
if(cap < 8) return false;
uint32_t n_blocks = scsi->fn.num_blocks(scsi->fn.ctx);
uint32_t block_size = SCSI_BLOCK_SIZE;
data[0] = (n_blocks - 1) >> 24;
data[1] = (n_blocks - 1) >> 16;
data[2] = (n_blocks - 1) >> 8;
data[3] = (n_blocks - 1);
data[4] = block_size >> 24;
data[5] = block_size >> 16;
data[6] = block_size >> 8;
data[7] = block_size;
*len = 8;
scsi->tx_done = true;
return true;
}; break;
case SCSI_MODE_SENSE_6: {
FURI_LOG_I(TAG, "SCSI_MODE_SENSE_6 %ld", cap);
if(cap < 4) return false;
data[0] = 3; // mode data length (len - 1)
data[1] = 0; // medium type
data[2] = 0; // device-specific parameter
data[3] = 0; // block descriptor length
*len = 4;
scsi->tx_done = true;
return true;
}; break;
case SCSI_READ_10: {
uint32_t block_size = SCSI_BLOCK_SIZE;
bool result =
scsi->fn.read(scsi->fn.ctx, scsi->read_10.lba, scsi->read_10.count, data, len, cap);
*len -= *len % block_size;
uint16_t blocks = *len / block_size;
scsi->read_10.lba += blocks;
scsi->read_10.count -= blocks;
if(!scsi->read_10.count) {
scsi->tx_done = true;
}
return result;
}; break;
default: {
FURI_LOG_W(TAG, "unexpected scsi tx data cmd=%02x", scsi->cmd[0]);
scsi->sk = SCSI_SK_ILLEGAL_REQUEST;
scsi->asc = SCSI_ASC_INVALID_COMMAND_OPERATION_CODE;
return false;
}; break;
}
}
bool scsi_cmd_end(SCSISession* scsi) {
// FURI_LOG_I(TAG, "END %02x", scsi->cmd[0]);
uint8_t* cmd = scsi->cmd;
uint8_t len = scsi->cmd_len;
scsi->cmd = NULL;
scsi->cmd_len = 0;
switch(cmd[0]) {
case SCSI_WRITE_10:
return scsi->rx_done;
case SCSI_REQUEST_SENSE:
case SCSI_INQUIRY:
case SCSI_READ_CAPACITY_6:
case SCSI_MODE_SENSE_6:
case SCSI_READ_10:
return scsi->tx_done;
case SCSI_TEST_UNIT_READY: {
FURI_LOG_I(TAG, "SCSI_TEST_UNIT_READY");
return true;
}; break;
case SCSI_PREVENT_MEDIUM_REMOVAL: {
if(len < 6) return false;
bool prevent = cmd[5];
FURI_LOG_I(TAG, "SCSI_PREVENT_MEDIUM_REMOVAL prevent=%d", prevent);
return !prevent;
}; break;
case SCSI_START_STOP_UNIT: {
if(len < 6) return false;
bool eject = (cmd[4] & 2) != 0;
bool start = (cmd[4] & 1) != 0;
FURI_LOG_I(TAG, "SCSI_START_STOP_UNIT eject=%d start=%d", eject, start);
if(eject) {
scsi->fn.eject(scsi->fn.ctx);
}
return true;
}; break;
default: {
FURI_LOG_W(TAG, "unexpected scsi cmd=%02x", cmd[0]);
scsi->sk = SCSI_SK_ILLEGAL_REQUEST;
scsi->asc = SCSI_ASC_INVALID_COMMAND_OPERATION_CODE;
return false;
}; break;
}
}

View File

@@ -0,0 +1,56 @@
#pragma once
#include <furi.h>
#define SCSI_BLOCK_SIZE (0x200u)
#define SCSI_SK_ILLEGAL_REQUEST (5)
#define SCSI_ASC_INVALID_COMMAND_OPERATION_CODE (0x20)
#define SCSI_ASC_LBA_OOB (0x21)
#define SCSI_ASC_INVALID_FIELD_IN_CDB (0x24)
typedef struct {
void* ctx;
bool (*read)(
void* ctx,
uint32_t lba,
uint16_t count,
uint8_t* out,
uint32_t* out_len,
uint32_t out_cap);
bool (*write)(void* ctx, uint32_t lba, uint16_t count, uint8_t* buf, uint32_t len);
uint32_t (*num_blocks)(void* ctx);
void (*eject)(void* ctx);
} SCSIDeviceFunc;
typedef struct {
SCSIDeviceFunc fn;
uint8_t* cmd;
uint8_t cmd_len;
bool rx_done;
bool tx_done;
uint8_t sk; // sense key
uint8_t asc; // additional sense code
// command-specific data
// valid from cmd_start to cmd_end
union {
struct {
uint16_t count;
uint32_t lba;
} read_10; // SCSI_READ_10
struct {
uint16_t count;
uint32_t lba;
} write_10; // SCSI_WRITE_10
};
} SCSISession;
bool scsi_cmd_start(SCSISession* scsi, uint8_t* cmd, uint8_t len);
bool scsi_cmd_rx_data(SCSISession* scsi, uint8_t* data, uint32_t len);
bool scsi_cmd_tx_data(SCSISession* scsi, uint8_t* data, uint32_t* len, uint32_t cap);
bool scsi_cmd_end(SCSISession* scsi);

View File

@@ -0,0 +1,473 @@
#include "mass_storage_usb.h"
#include <furi_hal.h>
#define TAG "MassStorageUsb"
#define USB_MSC_RX_EP (0x01)
#define USB_MSC_TX_EP (0x82)
#define USB_MSC_RX_EP_SIZE (64)
#define USB_MSC_TX_EP_SIZE (64u)
#define USB_MSC_BOT_GET_MAX_LUN (0xFE)
#define USB_MSC_BOT_RESET (0xFF)
#define CBW_SIG (0x43425355)
#define CBW_FLAGS_DEVICE_TO_HOST (0x80)
#define CSW_SIG (0x53425355)
#define CSW_STATUS_OK (0)
#define CSW_STATUS_NOK (1)
#define CSW_STATUS_PHASE_ERROR (2)
// must be SCSI_BLOCK_SIZE aligned
// larger than 0x10000 exceeds size_t, storage_file_* ops fail
#define USB_MSC_BUF_MAX (0x10000u - SCSI_BLOCK_SIZE)
static usbd_respond usb_ep_config(usbd_device* dev, uint8_t cfg);
static usbd_respond usb_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback);
typedef enum {
EventExit = 1 << 0,
EventReset = 1 << 1,
EventRxTx = 1 << 2,
EventAll = EventExit | EventReset | EventRxTx,
} MassStorageEvent;
typedef struct {
uint32_t sig;
uint32_t tag;
uint32_t len;
uint8_t flags;
uint8_t lun;
uint8_t cmd_len;
uint8_t cmd[16];
} __attribute__((packed)) CBW;
typedef struct {
uint32_t sig;
uint32_t tag;
uint32_t residue;
uint8_t status;
} __attribute__((packed)) CSW;
struct MassStorageUsb {
FuriHalUsbInterface usb;
FuriHalUsbInterface* usb_prev;
FuriThread* thread;
usbd_device* dev;
SCSIDeviceFunc fn;
};
static int32_t mass_thread_worker(void* context) {
MassStorageUsb* mass = context;
usbd_device* dev = mass->dev;
SCSISession scsi = {
.fn = mass->fn,
};
CBW cbw = {0};
CSW csw = {0};
uint8_t* buf = NULL;
uint32_t buf_len = 0, buf_cap = 0, buf_sent = 0;
enum {
StateReadCBW,
StateReadData,
StateWriteData,
StateBuildCSW,
StateWriteCSW,
} state = StateReadCBW;
while(true) {
uint32_t flags = furi_thread_flags_wait(EventAll, FuriFlagWaitAny, FuriFlagWaitAny);
if(flags & EventExit) {
FURI_LOG_I(TAG, "exit");
free(buf);
return 0;
}
if(flags & EventReset) {
FURI_LOG_I(TAG, "reset");
scsi.sk = 0;
scsi.asc = 0;
memset(&cbw, 0, sizeof(cbw));
memset(&csw, 0, sizeof(csw));
free(buf);
buf = NULL;
buf_len = buf_cap = buf_sent = 0;
state = StateReadCBW;
}
if(flags & EventRxTx) do {
switch(state) {
case StateReadCBW: {
// FURI_LOG_I(TAG, "StateReadCBW");
int32_t len = usbd_ep_read(dev, USB_MSC_RX_EP, &cbw, sizeof(cbw));
if(len <= 0) {
// FURI_LOG_I(TAG, "cbw not ready");
break;
}
if(len != sizeof(cbw) || cbw.sig != CBW_SIG) {
FURI_LOG_W(TAG, "bad cbw sig=%08lx", cbw.sig);
usbd_ep_stall(dev, USB_MSC_TX_EP);
usbd_ep_stall(dev, USB_MSC_RX_EP);
continue;
}
if(!scsi_cmd_start(&scsi, cbw.cmd, cbw.cmd_len)) {
FURI_LOG_W(TAG, "bad cmd");
usbd_ep_stall(dev, USB_MSC_RX_EP);
csw.sig = CSW_SIG;
csw.tag = cbw.tag;
csw.status = CSW_STATUS_NOK;
state = StateWriteCSW;
continue;
}
if(cbw.flags & CBW_FLAGS_DEVICE_TO_HOST) {
buf_len = 0;
buf_sent = 0;
state = StateWriteData;
} else {
buf_len = 0;
state = StateReadData;
}
continue;
}; break;
case StateReadData: {
// FURI_LOG_I(TAG, "StateReadData %d/%d", buf_len, cbw.len);
if(!cbw.len) {
state = StateBuildCSW;
continue;
}
uint32_t buf_clamp = MIN(cbw.len, USB_MSC_BUF_MAX);
if(buf_clamp > buf_cap) {
// FURI_LOG_I(TAG, "growing buf %d -> %d", buf_cap, buf_clamp);
free(buf);
buf_cap = buf_clamp;
buf = malloc(buf_cap);
}
if(buf_len < buf_clamp) {
int32_t len =
usbd_ep_read(dev, USB_MSC_RX_EP, buf + buf_len, buf_clamp - buf_len);
if(len < 0) {
// FURI_LOG_I(TAG, "rx not ready %d", len);
break;
}
// FURI_LOG_I(TAG, "clamp %ld len %d", buf_clamp, len);
buf_len += len;
}
if(buf_len == buf_clamp) {
if(!scsi_cmd_rx_data(&scsi, buf, buf_len)) {
FURI_LOG_W(TAG, "short rx");
usbd_ep_stall(dev, USB_MSC_RX_EP);
csw.sig = CSW_SIG;
csw.tag = cbw.tag;
csw.status = CSW_STATUS_NOK;
csw.residue = cbw.len;
state = StateWriteCSW;
continue;
}
cbw.len -= buf_len;
buf_len = 0;
}
continue;
}; break;
case StateWriteData: {
// FURI_LOG_I(TAG, "StateWriteData %d", cbw.len);
if(!cbw.len) {
state = StateBuildCSW;
continue;
}
uint32_t buf_clamp = MIN(cbw.len, USB_MSC_BUF_MAX);
if(buf_clamp > buf_cap) {
// FURI_LOG_I(TAG, "growing buf %d -> %d", buf_cap, buf_clamp);
free(buf);
buf_cap = buf_clamp;
buf = malloc(buf_cap);
}
if(!buf_len && !scsi_cmd_tx_data(&scsi, buf, &buf_len, buf_clamp)) {
FURI_LOG_W(TAG, "short tx");
// usbd_ep_stall(dev, USB_MSC_TX_EP);
state = StateBuildCSW;
continue;
}
int32_t len = usbd_ep_write(
dev,
USB_MSC_TX_EP,
buf + buf_sent,
MIN(USB_MSC_TX_EP_SIZE, buf_len - buf_sent));
if(len < 0) {
// FURI_LOG_I(TAG, "tx not ready %d", len);
break;
}
buf_sent += len;
if(buf_sent == buf_len) {
cbw.len -= buf_len;
buf_len = 0;
buf_sent = 0;
}
continue;
}; break;
case StateBuildCSW: {
// FURI_LOG_I(TAG, "StateBuildCSW");
csw.sig = CSW_SIG;
csw.tag = cbw.tag;
if(scsi_cmd_end(&scsi)) {
csw.status = CSW_STATUS_OK;
} else {
csw.status = CSW_STATUS_NOK;
}
csw.residue = cbw.len;
state = StateWriteCSW;
continue;
}; break;
case StateWriteCSW: {
// FURI_LOG_I(TAG, "StateWriteCSW");
if(csw.status) {
FURI_LOG_W(
TAG,
"csw sig=%08lx tag=%08lx residue=%08lx status=%02x",
csw.sig,
csw.tag,
csw.residue,
csw.status);
}
int32_t len = usbd_ep_write(dev, USB_MSC_TX_EP, &csw, sizeof(csw));
if(len < 0) {
// FURI_LOG_I(TAG, "csw not ready");
break;
}
if(len != sizeof(csw)) {
FURI_LOG_W(TAG, "bad csw write %ld", len);
usbd_ep_stall(dev, USB_MSC_TX_EP);
break;
}
memset(&cbw, 0, sizeof(cbw));
memset(&csw, 0, sizeof(csw));
state = StateReadCBW;
continue;
}; break;
}
break;
} while(true);
}
}
// needed in usb_deinit, usb_suspend, usb_rxtx_ep_callback, usb_control,
// where if_ctx isn't passed
static MassStorageUsb* mass_cur = NULL;
static void usb_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx) {
UNUSED(intf);
MassStorageUsb* mass = ctx;
mass_cur = mass;
mass->dev = dev;
usbd_reg_config(dev, usb_ep_config);
usbd_reg_control(dev, usb_control);
usbd_connect(dev, true);
mass->thread = furi_thread_alloc();
furi_thread_set_name(mass->thread, "MassStorageUsb");
furi_thread_set_stack_size(mass->thread, 1024);
furi_thread_set_context(mass->thread, ctx);
furi_thread_set_callback(mass->thread, mass_thread_worker);
furi_thread_start(mass->thread);
}
static void usb_deinit(usbd_device* dev) {
usbd_reg_config(dev, NULL);
usbd_reg_control(dev, NULL);
MassStorageUsb* mass = mass_cur;
if(!mass || mass->dev != dev) {
FURI_LOG_E(TAG, "deinit mass_cur leak");
return;
}
mass_cur = NULL;
furi_assert(mass->thread);
furi_thread_flags_set(furi_thread_get_id(mass->thread), EventExit);
furi_thread_join(mass->thread);
furi_thread_free(mass->thread);
mass->thread = NULL;
free(mass->usb.str_prod_descr);
mass->usb.str_prod_descr = NULL;
free(mass->usb.str_serial_descr);
mass->usb.str_serial_descr = NULL;
free(mass);
}
static void usb_wakeup(usbd_device* dev) {
UNUSED(dev);
}
static void usb_suspend(usbd_device* dev) {
MassStorageUsb* mass = mass_cur;
if(!mass || mass->dev != dev) return;
furi_thread_flags_set(furi_thread_get_id(mass->thread), EventReset);
}
static void usb_rxtx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) {
UNUSED(event);
UNUSED(ep);
MassStorageUsb* mass = mass_cur;
if(!mass || mass->dev != dev) return;
furi_thread_flags_set(furi_thread_get_id(mass->thread), EventRxTx);
}
static usbd_respond usb_ep_config(usbd_device* dev, uint8_t cfg) {
switch(cfg) {
case 0: // deconfig
usbd_ep_deconfig(dev, USB_MSC_RX_EP);
usbd_ep_deconfig(dev, USB_MSC_TX_EP);
usbd_reg_endpoint(dev, USB_MSC_RX_EP, NULL);
usbd_reg_endpoint(dev, USB_MSC_TX_EP, NULL);
return usbd_ack;
case 1: // config
usbd_ep_config(
dev, USB_MSC_RX_EP, USB_EPTYPE_BULK /* | USB_EPTYPE_DBLBUF*/, USB_MSC_RX_EP_SIZE);
usbd_ep_config(
dev, USB_MSC_TX_EP, USB_EPTYPE_BULK /* | USB_EPTYPE_DBLBUF*/, USB_MSC_TX_EP_SIZE);
usbd_reg_endpoint(dev, USB_MSC_RX_EP, usb_rxtx_ep_callback);
usbd_reg_endpoint(dev, USB_MSC_TX_EP, usb_rxtx_ep_callback);
return usbd_ack;
}
return usbd_fail;
}
static usbd_respond usb_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback) {
UNUSED(callback);
if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) !=
(USB_REQ_INTERFACE | USB_REQ_CLASS)) {
return usbd_fail;
}
switch(req->bRequest) {
case USB_MSC_BOT_GET_MAX_LUN: {
static uint8_t max_lun = 0;
dev->status.data_ptr = &max_lun;
dev->status.data_count = 1;
return usbd_ack;
}; break;
case USB_MSC_BOT_RESET: {
MassStorageUsb* mass = mass_cur;
if(!mass || mass->dev != dev) return usbd_fail;
furi_thread_flags_set(furi_thread_get_id(mass->thread), EventReset);
return usbd_ack;
}; break;
}
return usbd_fail;
}
static const struct usb_string_descriptor dev_manuf_desc = USB_STRING_DESC("Flipper Devices Inc.");
struct MassStorageDescriptor {
struct usb_config_descriptor config;
struct usb_interface_descriptor intf;
struct usb_endpoint_descriptor ep_rx;
struct usb_endpoint_descriptor ep_tx;
} __attribute__((packed));
static const struct usb_device_descriptor usb_mass_dev_descr = {
.bLength = sizeof(struct usb_device_descriptor),
.bDescriptorType = USB_DTYPE_DEVICE,
.bcdUSB = VERSION_BCD(2, 0, 0),
.bDeviceClass = USB_CLASS_PER_INTERFACE,
.bDeviceSubClass = USB_SUBCLASS_NONE,
.bDeviceProtocol = USB_PROTO_NONE,
.bMaxPacketSize0 = 8, // USB_EP0_SIZE
.idVendor = 0x0483,
.idProduct = 0x5720,
.bcdDevice = VERSION_BCD(1, 0, 0),
.iManufacturer = 1, // UsbDevManuf
.iProduct = 2, // UsbDevProduct
.iSerialNumber = 3, // UsbDevSerial
.bNumConfigurations = 1,
};
static const struct MassStorageDescriptor usb_mass_cfg_descr = {
.config =
{
.bLength = sizeof(struct usb_config_descriptor),
.bDescriptorType = USB_DTYPE_CONFIGURATION,
.wTotalLength = sizeof(struct MassStorageDescriptor),
.bNumInterfaces = 1,
.bConfigurationValue = 1,
.iConfiguration = NO_DESCRIPTOR,
.bmAttributes = USB_CFG_ATTR_RESERVED | USB_CFG_ATTR_SELFPOWERED,
.bMaxPower = USB_CFG_POWER_MA(100),
},
.intf =
{
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = USB_DTYPE_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 2,
.bInterfaceClass = USB_CLASS_MASS_STORAGE,
.bInterfaceSubClass = 0x06, // scsi transparent
.bInterfaceProtocol = 0x50, // bulk only
.iInterface = NO_DESCRIPTOR,
},
.ep_rx =
{
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = USB_DTYPE_ENDPOINT,
.bEndpointAddress = USB_MSC_RX_EP,
.bmAttributes = USB_EPTYPE_BULK,
.wMaxPacketSize = USB_MSC_RX_EP_SIZE,
.bInterval = 0,
},
.ep_tx =
{
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = USB_DTYPE_ENDPOINT,
.bEndpointAddress = USB_MSC_TX_EP,
.bmAttributes = USB_EPTYPE_BULK,
.wMaxPacketSize = USB_MSC_TX_EP_SIZE,
.bInterval = 0,
},
};
MassStorageUsb* mass_storage_usb_start(const char* filename, SCSIDeviceFunc fn) {
MassStorageUsb* mass = malloc(sizeof(MassStorageUsb));
mass->usb_prev = furi_hal_usb_get_config();
mass->usb.init = usb_init;
mass->usb.deinit = usb_deinit;
mass->usb.wakeup = usb_wakeup;
mass->usb.suspend = usb_suspend;
mass->usb.dev_descr = (struct usb_device_descriptor*)&usb_mass_dev_descr;
mass->usb.str_manuf_descr = (void*)&dev_manuf_desc;
mass->usb.str_prod_descr = NULL;
mass->usb.str_serial_descr = NULL;
mass->usb.cfg_descr = (void*)&usb_mass_cfg_descr;
const char* name = furi_hal_version_get_device_name_ptr();
if(!name) name = "Flipper Zero";
size_t len = strlen(name);
struct usb_string_descriptor* str_prod_descr = malloc(len * 2 + 2);
str_prod_descr->bLength = len * 2 + 2;
str_prod_descr->bDescriptorType = USB_DTYPE_STRING;
for(uint8_t i = 0; i < len; i++) str_prod_descr->wString[i] = name[i];
mass->usb.str_prod_descr = str_prod_descr;
len = strlen(filename);
struct usb_string_descriptor* str_serial_descr = malloc(len * 2 + 2);
str_serial_descr->bLength = len * 2 + 2;
str_serial_descr->bDescriptorType = USB_DTYPE_STRING;
for(uint8_t i = 0; i < len; i++) str_serial_descr->wString[i] = filename[i];
mass->usb.str_serial_descr = str_serial_descr;
mass->fn = fn;
if(!furi_hal_usb_set_config(&mass->usb, mass)) {
FURI_LOG_E(TAG, "USB locked, cannot start Mass Storage");
free(mass->usb.str_prod_descr);
free(mass->usb.str_serial_descr);
free(mass);
return NULL;
}
return mass;
}
void mass_storage_usb_stop(MassStorageUsb* mass) {
furi_hal_usb_set_config(mass->usb_prev, NULL);
// freed by usb_deinit asynchronously from usb thread
}

View File

@@ -0,0 +1,9 @@
#pragma once
#include <storage/storage.h>
#include "mass_storage_scsi.h"
typedef struct MassStorageUsb MassStorageUsb;
MassStorageUsb* mass_storage_usb_start(const char* filename, SCSIDeviceFunc fn);
void mass_storage_usb_stop(MassStorageUsb* mass);

View File

@@ -0,0 +1,124 @@
#include "mass_storage_app_i.h"
#include <furi.h>
#include <storage/storage.h>
#include <lib/toolbox/path.h>
static bool mass_storage_app_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
MassStorageApp* app = context;
return scene_manager_handle_custom_event(app->scene_manager, event);
}
static bool mass_storage_app_back_event_callback(void* context) {
furi_assert(context);
MassStorageApp* app = context;
return scene_manager_handle_back_event(app->scene_manager);
}
static void mass_storage_app_tick_event_callback(void* context) {
furi_assert(context);
MassStorageApp* app = context;
scene_manager_handle_tick_event(app->scene_manager);
}
static bool mass_storage_check_assets(Storage* fs_api) {
File* dir = storage_file_alloc(fs_api);
bool ret = false;
if(storage_dir_open(dir, MASS_STORAGE_APP_PATH_FOLDER)) {
ret = true;
}
storage_dir_close(dir);
storage_file_free(dir);
return ret;
}
MassStorageApp* mass_storage_app_alloc(char* arg) {
MassStorageApp* app = malloc(sizeof(MassStorageApp));
memset(app, 0, sizeof(MassStorageApp));
if(arg != NULL) {
FuriString* filename = furi_string_alloc_set(arg);
if(furi_string_start_with_str(filename, MASS_STORAGE_APP_PATH_FOLDER)) {
furi_string_right(filename, strlen(MASS_STORAGE_APP_PATH_FOLDER) + 1);
}
strlcpy(app->file_name, furi_string_get_cstr(filename), MASS_STORAGE_FILE_NAME_LEN);
furi_string_free(filename);
}
app->gui = furi_record_open(RECORD_GUI);
app->fs_api = furi_record_open(RECORD_STORAGE);
app->notifications = furi_record_open(RECORD_NOTIFICATION);
app->dialogs = furi_record_open(RECORD_DIALOGS);
storage_simply_mkdir(app->fs_api, MASS_STORAGE_APP_PATH_FOLDER);
app->view_dispatcher = view_dispatcher_alloc();
view_dispatcher_enable_queue(app->view_dispatcher);
app->scene_manager = scene_manager_alloc(&mass_storage_scene_handlers, app);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_tick_event_callback(
app->view_dispatcher, mass_storage_app_tick_event_callback, 500);
view_dispatcher_set_custom_event_callback(
app->view_dispatcher, mass_storage_app_custom_event_callback);
view_dispatcher_set_navigation_event_callback(
app->view_dispatcher, mass_storage_app_back_event_callback);
// Custom Widget
app->widget = widget_alloc();
view_dispatcher_add_view(
app->view_dispatcher, MassStorageAppViewError, widget_get_view(app->widget));
app->mass_storage_view = mass_storage_alloc();
view_dispatcher_add_view(
app->view_dispatcher,
MassStorageAppViewWork,
mass_storage_get_view(app->mass_storage_view));
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
if(*app->file_name != '\0') {
scene_manager_next_scene(app->scene_manager, MassStorageSceneWork);
} else if(mass_storage_check_assets(app->fs_api)) {
scene_manager_next_scene(app->scene_manager, MassStorageSceneFileSelect);
} else {
scene_manager_next_scene(app->scene_manager, MassStorageSceneError);
}
return app;
}
void mass_storage_app_free(MassStorageApp* app) {
furi_assert(app);
// Views
view_dispatcher_remove_view(app->view_dispatcher, MassStorageAppViewWork);
mass_storage_free(app->mass_storage_view);
// Custom Widget
view_dispatcher_remove_view(app->view_dispatcher, MassStorageAppViewError);
widget_free(app->widget);
// View dispatcher
view_dispatcher_free(app->view_dispatcher);
scene_manager_free(app->scene_manager);
// Close records
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_STORAGE);
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_DIALOGS);
free(app);
}
int32_t mass_storage_app(void* p) {
MassStorageApp* mass_storage_app = mass_storage_app_alloc((char*)p);
view_dispatcher_run(mass_storage_app->view_dispatcher);
mass_storage_app_free(mass_storage_app);
return 0;
}

View File

@@ -0,0 +1,11 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
typedef struct MassStorageApp MassStorageApp;
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,44 @@
#pragma once
#include "mass_storage_app.h"
#include "scenes/mass_storage_scene.h"
#include "helpers/mass_storage_usb.h"
#include <furi_hal.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <gui/modules/submenu.h>
#include <dialogs/dialogs.h>
#include <notification/notification_messages.h>
#include <gui/modules/variable_item_list.h>
#include <gui/modules/widget.h>
#include <storage/storage.h>
#include "views/mass_storage_view.h"
#define MASS_STORAGE_APP_PATH_FOLDER STORAGE_APP_DATA_PATH_PREFIX
// #define MASS_STORAGE_APP_EXTENSION ".iso"
#define MASS_STORAGE_FILE_NAME_LEN 40
struct MassStorageApp {
Gui* gui;
Storage* fs_api;
ViewDispatcher* view_dispatcher;
SceneManager* scene_manager;
NotificationApp* notifications;
DialogsApp* dialogs;
Widget* widget;
char file_name[MASS_STORAGE_FILE_NAME_LEN];
File* file;
MassStorage* mass_storage_view;
FuriMutex* usb_mutex;
MassStorageUsb* usb;
};
typedef enum {
MassStorageAppViewError,
MassStorageAppViewFileSelect,
MassStorageAppViewWork,
} MassStorageAppView;

View File

@@ -0,0 +1,30 @@
#include "mass_storage_scene.h"
// Generate scene on_enter handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
void (*const mass_storage_scene_on_enter_handlers[])(void*) = {
#include "mass_storage_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_event handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
bool (*const mass_storage_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
#include "mass_storage_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_exit handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
void (*const mass_storage_scene_on_exit_handlers[])(void* context) = {
#include "mass_storage_scene_config.h"
};
#undef ADD_SCENE
// Initialize scene handlers configuration structure
const SceneManagerHandlers mass_storage_scene_handlers = {
.on_enter_handlers = mass_storage_scene_on_enter_handlers,
.on_event_handlers = mass_storage_scene_on_event_handlers,
.on_exit_handlers = mass_storage_scene_on_exit_handlers,
.scene_num = MassStorageSceneNum,
};

View File

@@ -0,0 +1,29 @@
#pragma once
#include <gui/scene_manager.h>
// Generate scene id and total number
#define ADD_SCENE(prefix, name, id) MassStorageScene##id,
typedef enum {
#include "mass_storage_scene_config.h"
MassStorageSceneNum,
} MassStorageScene;
#undef ADD_SCENE
extern const SceneManagerHandlers mass_storage_scene_handlers;
// Generate scene on_enter handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
#include "mass_storage_scene_config.h"
#undef ADD_SCENE
// Generate scene on_event handlers declaration
#define ADD_SCENE(prefix, name, id) \
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
#include "mass_storage_scene_config.h"
#undef ADD_SCENE
// Generate scene on_exit handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
#include "mass_storage_scene_config.h"
#undef ADD_SCENE

View File

@@ -0,0 +1,3 @@
ADD_SCENE(mass_storage, file_select, FileSelect)
ADD_SCENE(mass_storage, work, Work)
ADD_SCENE(mass_storage, error, Error)

View File

@@ -0,0 +1,54 @@
#include "../mass_storage_app_i.h"
#include <assets_icons.h>
typedef enum {
SubghzCustomEventErrorBack,
} MassStorageCustomEvent;
static void
mass_storage_scene_error_event_callback(GuiButtonType result, InputType type, void* context) {
furi_assert(context);
MassStorageApp* app = context;
if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) {
view_dispatcher_send_custom_event(app->view_dispatcher, SubghzCustomEventErrorBack);
}
}
void mass_storage_scene_error_on_enter(void* context) {
MassStorageApp* app = context;
widget_add_icon_element(app->widget, 0, 0, &I_SDQuestion_35x43);
widget_add_string_multiline_element(
app->widget,
81,
4,
AlignCenter,
AlignTop,
FontSecondary,
"No SD card or\napp data found.\nThis app requires a\nmass_storage/\ndirectory.");
widget_add_button_element(
app->widget, GuiButtonTypeLeft, "Back", mass_storage_scene_error_event_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, MassStorageAppViewError);
}
bool mass_storage_scene_error_on_event(void* context, SceneManagerEvent event) {
MassStorageApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubghzCustomEventErrorBack) {
view_dispatcher_stop(app->view_dispatcher);
consumed = true;
}
}
return consumed;
}
void mass_storage_scene_error_on_exit(void* context) {
MassStorageApp* app = context;
widget_reset(app->widget);
}

View File

@@ -0,0 +1,47 @@
#include "../mass_storage_app_i.h"
#include "furi_hal_power.h"
static bool mass_storage_file_select(MassStorageApp* mass_storage) {
furi_assert(mass_storage);
FuriString* file_path = furi_string_alloc();
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, "*", NULL);
browser_options.base_path = MASS_STORAGE_APP_PATH_FOLDER;
furi_string_set(file_path, MASS_STORAGE_APP_PATH_FOLDER);
bool res =
dialog_file_browser_show(mass_storage->dialogs, file_path, file_path, &browser_options);
if(res) {
strlcpy(
mass_storage->file_name,
furi_string_get_cstr(file_path),
sizeof(mass_storage->file_name));
}
furi_string_free(file_path);
return res;
}
void mass_storage_scene_file_select_on_enter(void* context) {
MassStorageApp* mass_storage = context;
if(mass_storage_file_select(mass_storage)) {
scene_manager_next_scene(mass_storage->scene_manager, MassStorageSceneWork);
} else {
scene_manager_previous_scene(mass_storage->scene_manager);
view_dispatcher_stop(mass_storage->view_dispatcher);
}
}
bool mass_storage_scene_file_select_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
// MassStorageApp* mass_storage = context;
return false;
}
void mass_storage_scene_file_select_on_exit(void* context) {
UNUSED(context);
// MassStorageApp* mass_storage = context;
}

View File

@@ -0,0 +1,104 @@
#include "../mass_storage_app_i.h"
#include "../views/mass_storage_view.h"
#include "../helpers/mass_storage_usb.h"
#define TAG "MassStorageSceneWork"
static bool file_read(
void* ctx,
uint32_t lba,
uint16_t count,
uint8_t* out,
uint32_t* out_len,
uint32_t out_cap) {
MassStorageApp* app = ctx;
// FURI_LOG_I(TAG, "file_read lba=%08lx count=%04x out_cap=%04x", lba, count, out_cap);
if(!storage_file_seek(app->file, lba * SCSI_BLOCK_SIZE, true)) {
FURI_LOG_W(TAG, "seek failed");
return false;
}
uint16_t clamp = MIN(out_cap, count * SCSI_BLOCK_SIZE);
*out_len = storage_file_read(app->file, out, clamp);
// FURI_LOG_I(TAG, "%d/%d", *out_len, count * SCSI_BLOCK_SIZE);
return *out_len == clamp;
}
static bool file_write(void* ctx, uint32_t lba, uint16_t count, uint8_t* buf, uint32_t len) {
MassStorageApp* app = ctx;
// FURI_LOG_I(TAG, "file_write lba=%08lx count=%04x len=%04x", lba, count, len);
if(len != count * SCSI_BLOCK_SIZE) {
FURI_LOG_W(TAG, "bad write params count=%d len=%ld", count, len);
return false;
}
if(!storage_file_seek(app->file, lba * SCSI_BLOCK_SIZE, true)) {
FURI_LOG_W(TAG, "seek failed");
return false;
}
return storage_file_write(app->file, buf, len) == len;
}
static uint32_t file_num_blocks(void* ctx) {
MassStorageApp* app = ctx;
return storage_file_size(app->file) / SCSI_BLOCK_SIZE;
}
static void file_eject(void* ctx) {
MassStorageApp* app = ctx;
FURI_LOG_I(TAG, "EJECT");
furi_check(furi_mutex_acquire(app->usb_mutex, FuriWaitForever) == FuriStatusOk);
mass_storage_usb_stop(app->usb);
app->usb = NULL;
furi_check(furi_mutex_release(app->usb_mutex) == FuriStatusOk);
}
bool mass_storage_scene_work_on_event(void* context, SceneManagerEvent event) {
MassStorageApp* app = context;
if(event.type == SceneManagerEventTypeTick) {
bool ejected;
furi_check(furi_mutex_acquire(app->usb_mutex, FuriWaitForever) == FuriStatusOk);
ejected = app->usb == NULL;
furi_check(furi_mutex_release(app->usb_mutex) == FuriStatusOk);
if(ejected) scene_manager_previous_scene(app->scene_manager);
}
return false;
}
void mass_storage_scene_work_on_enter(void* context) {
MassStorageApp* app = context;
app->usb_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
FuriString* file_name = furi_string_alloc();
mass_storage_set_file_name(app->mass_storage_view, app->file_name);
furi_string_printf(file_name, "%s/%s", MASS_STORAGE_APP_PATH_FOLDER, app->file_name);
app->file = storage_file_alloc(app->fs_api);
furi_assert(storage_file_open(
app->file, furi_string_get_cstr(file_name), FSAM_READ | FSAM_WRITE, FSOM_OPEN_EXISTING));
furi_string_free(file_name);
SCSIDeviceFunc fn = {
.ctx = app,
.read = file_read,
.write = file_write,
.num_blocks = file_num_blocks,
.eject = file_eject,
};
app->usb = mass_storage_usb_start(app->file_name, fn);
view_dispatcher_switch_to_view(app->view_dispatcher, MassStorageAppViewWork);
}
void mass_storage_scene_work_on_exit(void* context) {
MassStorageApp* app = context;
furi_mutex_free(app->usb_mutex);
if(app->usb) {
mass_storage_usb_stop(app->usb);
app->usb = NULL;
}
if(app->file) {
storage_file_free(app->file);
app->file = NULL;
}
}

View File

@@ -0,0 +1,53 @@
#include "mass_storage_view.h"
#include <gui/elements.h>
#include <assets_icons.h>
struct MassStorage {
View* view;
};
typedef struct {
char* file_name;
} MassStorageModel;
static void mass_storage_draw_callback(Canvas* canvas, void* _model) {
MassStorageModel* model = _model;
FuriString* disp_str = furi_string_alloc_set(model->file_name);
elements_string_fit_width(canvas, disp_str, 128 - 2);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 2, 8, furi_string_get_cstr(disp_str));
furi_string_reset(disp_str);
canvas_draw_icon(canvas, 40, 20, &I_UsbTree_48x22);
furi_string_free(disp_str);
}
MassStorage* mass_storage_alloc() {
MassStorage* mass_storage = malloc(sizeof(MassStorage));
mass_storage->view = view_alloc();
view_allocate_model(mass_storage->view, ViewModelTypeLocking, sizeof(MassStorageModel));
view_set_context(mass_storage->view, mass_storage);
view_set_draw_callback(mass_storage->view, mass_storage_draw_callback);
return mass_storage;
}
void mass_storage_free(MassStorage* mass_storage) {
furi_assert(mass_storage);
view_free(mass_storage->view);
free(mass_storage);
}
View* mass_storage_get_view(MassStorage* mass_storage) {
furi_assert(mass_storage);
return mass_storage->view;
}
void mass_storage_set_file_name(MassStorage* mass_storage, char* name) {
furi_assert(name);
with_view_model(
mass_storage->view, MassStorageModel * model, { model->file_name = name; }, true);
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include <gui/view.h>
typedef struct MassStorage MassStorage;
MassStorage* mass_storage_alloc();
void mass_storage_free(MassStorage* mass_storage);
View* mass_storage_get_view(MassStorage* mass_storage);
void mass_storage_set_file_name(MassStorage* mass_storage, char* name);

320
applications/external/4inrow/4inrow.c vendored Normal file
View File

@@ -0,0 +1,320 @@
#include <stdio.h>
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
static int matrix[6][7] = {0};
static int cursorx = 3;
static int cursory = 5;
static int player = 1;
static int scoreX = 0;
static int scoreO = 0;
typedef struct {
FuriMutex* mutex;
} FourInRowState;
void init() {
for(size_t i = 0; i < 6; i++) {
for(size_t j = 0; j < 7; j++) {
matrix[i][j] = 0;
}
}
cursorx = 3;
cursory = 5;
player = 1;
}
const NotificationSequence end = {
&message_vibro_on,
&message_note_ds4,
&message_delay_10,
&message_sound_off,
&message_delay_10,
&message_note_ds4,
&message_delay_10,
&message_sound_off,
&message_delay_10,
&message_note_ds4,
&message_delay_10,
&message_sound_off,
&message_delay_10,
&message_vibro_off,
NULL,
};
void intToStr(int num, char* str) {
int i = 0, sign = 0;
if(num < 0) {
num = -num;
sign = 1;
}
do {
str[i++] = num % 10 + '0';
num /= 10;
} while(num > 0);
if(sign) {
str[i++] = '-';
}
str[i] = '\0';
// Reverse the string
int j, len = i;
char temp;
for(j = 0; j < len / 2; j++) {
temp = str[j];
str[j] = str[len - j - 1];
str[len - j - 1] = temp;
}
}
int next_height(int x) {
if(matrix[0][x] != 0) {
return -1;
}
for(size_t y = 1; y < 6; y++) {
if(matrix[y][x] != 0) {
return y - 1;
}
}
return 5;
}
int wincheck() {
for(size_t y = 0; y <= 2; y++) {
for(size_t x = 0; x <= 6; x++) {
if(matrix[y][x] != 0 && matrix[y][x] == matrix[y + 1][x] &&
matrix[y][x] == matrix[y + 2][x] && matrix[y][x] == matrix[y + 3][x]) {
return matrix[y][x];
}
}
}
for(size_t y = 0; y <= 5; y++) {
for(size_t x = 0; x <= 3; x++) {
if(matrix[y][x] != 0 && matrix[y][x] == matrix[y][x + 1] &&
matrix[y][x] == matrix[y][x + 2] && matrix[y][x] == matrix[y][x + 3]) {
return matrix[y][x];
}
}
}
for(size_t y = 0; y <= 2; y++) {
for(size_t x = 0; x <= 3; x++) {
if(matrix[y][x] != 0 && matrix[y][x] == matrix[y + 1][x + 1] &&
matrix[y][x] == matrix[y + 2][x + 2] && matrix[y][x] == matrix[y + 3][x + 3]) {
return matrix[y][x];
}
}
}
for(size_t y = 3; y <= 5; y++) {
for(size_t x = 0; x <= 3; x++) {
if(matrix[y][x] != 0 && matrix[y][x] == matrix[y - 1][x + 1] &&
matrix[y][x] == matrix[y - 2][x + 2] && matrix[y][x] == matrix[y - 3][x + 3]) {
return matrix[y][x];
}
}
}
bool tf = true;
for(size_t y = 0; y < 6; y++) {
for(size_t x = 0; x < 7; x++) {
if(matrix[y][x] == 0) {
tf = false;
}
}
}
if(tf) {
return 0;
}
return -1;
}
static void draw_callback(Canvas* canvas, void* ctx) {
furi_assert(ctx);
const FourInRowState* fourinrow_state = ctx;
furi_mutex_acquire(fourinrow_state->mutex, FuriWaitForever);
canvas_clear(canvas);
if(wincheck() != -1) {
canvas_set_font(canvas, FontPrimary);
if(wincheck() == 0) {
canvas_draw_str(canvas, 30, 35, "Draw! O_o");
}
if(wincheck() == 1) {
canvas_draw_str(canvas, 30, 35, "Player X win!");
}
if(wincheck() == 2) {
canvas_draw_str(canvas, 30, 35, "Player O win!");
}
furi_mutex_release(fourinrow_state->mutex);
return;
}
for(size_t i = 0; i < 6; i++) {
for(size_t j = 0; j < 7; j++) {
char el[2];
switch(matrix[i][j]) {
case 0:
strcpy(el, "_\0");
break;
case 1:
strcpy(el, "X\0");
break;
case 2:
strcpy(el, "O\0");
break;
}
canvas_draw_str(canvas, j * 10 + 10, i * 10 + 10, el);
}
}
canvas_draw_str(canvas, cursorx * 10 + 8, cursory * 10 + 10, "[ ]");
if(player == 1) {
canvas_draw_str(canvas, 80, 10, "Turn: X");
}
if(player == 2) {
canvas_draw_str(canvas, 80, 10, "Turn: O");
}
char scX[1];
intToStr(scoreX, scX);
char scO[1];
intToStr(scoreO, scO);
canvas_draw_str(canvas, 80, 20, "X:");
canvas_draw_str(canvas, 90, 20, scX);
canvas_draw_str(canvas, 80, 30, "O:");
canvas_draw_str(canvas, 90, 30, scO);
furi_mutex_release(fourinrow_state->mutex);
}
static void input_callback(InputEvent* input_event, void* ctx) {
// Проверяем, что контекст не нулевой
furi_assert(ctx);
FuriMessageQueue* event_queue = ctx;
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
}
int32_t four_in_row_app(void* p) {
UNUSED(p);
// Текущее событие типа InputEvent
InputEvent event;
// Очередь событий на 8 элементов размера InputEvent
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
FourInRowState* fourinrow_state = malloc(sizeof(FourInRowState));
fourinrow_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); // Alloc Mutex
if(!fourinrow_state->mutex) {
FURI_LOG_E("4inRow", "cannot create mutex\r\n");
furi_message_queue_free(event_queue);
free(fourinrow_state);
return 255;
}
// dolphin_deed(DolphinDeedPluginGameStart);
// Создаем новый view port
ViewPort* view_port = view_port_alloc();
// Создаем callback отрисовки, без контекста
view_port_draw_callback_set(view_port, draw_callback, fourinrow_state);
// Создаем callback нажатий на клавиши, в качестве контекста передаем
// нашу очередь сообщений, чтоб запихивать в неё эти события
view_port_input_callback_set(view_port, input_callback, event_queue);
// Создаем GUI приложения
Gui* gui = furi_record_open(RECORD_GUI);
// Подключаем view port к GUI в полноэкранном режиме
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_message_block(notification, &sequence_display_backlight_enforce_on);
// Бесконечный цикл обработки очереди событий
while(1) {
// Выбираем событие из очереди в переменную event (ждем бесконечно долго, если очередь пуста)
// и проверяем, что у нас получилось это сделать
furi_check(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk);
furi_mutex_acquire(fourinrow_state->mutex, FuriWaitForever);
// Если нажата кнопка "назад", то выходим из цикла, а следовательно и из приложения
if(wincheck() != -1) {
notification_message(notification, &end);
furi_delay_ms(1000);
if(wincheck() == 1) {
scoreX++;
}
if(wincheck() == 2) {
scoreO++;
}
init();
}
if(event.type == InputTypePress) {
if(event.key == InputKeyOk) {
int nh = next_height(cursorx);
if(nh != -1) {
matrix[nh][cursorx] = player;
player = 3 - player;
}
}
if(event.key == InputKeyUp) {
//cursory--;
}
if(event.key == InputKeyDown) {
//cursory++;
}
if(event.key == InputKeyLeft) {
if(cursorx > 0) {
cursorx--;
}
}
if(event.key == InputKeyRight) {
if(cursorx < 6) {
cursorx++;
}
}
if(event.key == InputKeyBack) {
break;
}
}
view_port_update(view_port);
furi_mutex_release(fourinrow_state->mutex);
}
// Clear notification
notification_message_block(notification, &sequence_display_backlight_enforce_auto);
furi_record_close(RECORD_NOTIFICATION);
// Специальная очистка памяти, занимаемой очередью
furi_message_queue_free(event_queue);
// Чистим созданные объекты, связанные с интерфейсом
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_mutex_free(fourinrow_state->mutex);
furi_record_close(RECORD_GUI);
free(fourinrow_state);
return 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

View File

@@ -0,0 +1,17 @@
App(
appid="4inrow",
name="4 in a Row",
apptype=FlipperAppType.EXTERNAL,
entry_point="four_in_row_app",
requires=[
"gui",
],
stack_size=1 * 1024,
order=90,
fap_icon="4inrow_10px.png",
fap_category="Games",
fap_author="leo-need-more-coffee",
fap_weburl="https://github.com/leo-need-more-coffee/flipperzero-4inrow",
fap_version="1.0",
fap_description="4 in row Game",
)

View File

@@ -1,4 +1,5 @@
#include "air_mouse.h"
#include <storage/storage.h>
#include <furi.h>
@@ -52,6 +53,11 @@ uint32_t air_mouse_exit(void* context) {
AirMouse* air_mouse_app_alloc() {
AirMouse* app = malloc(sizeof(AirMouse));
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_common_migrate(
storage, EXT_PATH(".calibration.data"), APP_DATA_PATH("calibration.data"));
furi_record_close(RECORD_STORAGE);
// Gui
app->gui = furi_record_open(RECORD_GUI);

View File

@@ -1,5 +1,5 @@
App(
appid="Air_Mouse",
appid="air_mouse",
name="[BMI160] Air Mouse",
apptype=FlipperAppType.EXTERNAL,
entry_point="air_mouse_app",

View File

@@ -7,8 +7,7 @@
#include "util/vector.h"
#define CALIBRATION_DATA_VER (1)
#define CALIBRATION_DATA_FILE_NAME ".calibration.data"
#define CALIBRATION_DATA_PATH EXT_PATH(CALIBRATION_DATA_FILE_NAME)
#define CALIBRATION_DATA_PATH APP_DATA_PATH("calibration.data")
#define CALIBRATION_DATA_MAGIC (0x23)
#define CALIBRATION_DATA_SAVE(x) \

View File

@@ -8,4 +8,7 @@ App(
order=20,
fap_icon="arkanoid_10px.png",
fap_category="Games",
fap_author="@xMasterX & @gotnull",
fap_version="1.0",
fap_description="Arkanoid Game",
)

View File

@@ -397,6 +397,9 @@ int32_t arkanoid_game_app(void* p) {
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
// Call dolphin deed on game start
// dolphin_deed(DolphinDeedPluginGameStart);
GameEvent event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);

View File

@@ -28,7 +28,8 @@
#define MAXPOWERUPS 3 /* Max powerups allowed on screen */
#define POWERUPSTTL 400 /* Max powerup time to live, in ticks. */
#define SHIP_HIT_ANIMATION_LEN 15
#define SAVING_DIRECTORY "/ext/apps/Games"
// Tick runs in thread, cant use APP_DATA_PATH()
#define SAVING_DIRECTORY EXT_PATH("apps_data/asteroids")
#define SAVING_FILENAME SAVING_DIRECTORY "/game_asteroids.save"
#ifndef PI
#define PI 3.14159265358979f
@@ -1146,6 +1147,8 @@ void game_tick(void* ctx) {
bool load_game(AsteroidsApp* app) {
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_common_mkdir(storage, SAVING_DIRECTORY);
storage_common_migrate(storage, EXT_PATH("apps/Games/game_asteroids.save"), SAVING_FILENAME);
File* file = storage_file_alloc(storage);
uint16_t bytes_readed = 0;
@@ -1163,12 +1166,6 @@ bool load_game(AsteroidsApp* app) {
void save_game(AsteroidsApp* app) {
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_common_stat(storage, SAVING_DIRECTORY, NULL) == FSE_NOT_EXIST) {
if(!storage_simply_mkdir(storage, SAVING_DIRECTORY)) {
return;
}
}
File* file = storage_file_alloc(storage);
if(storage_file_open(file, SAVING_FILENAME, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
storage_file_write(file, app, sizeof(AsteroidsApp));

View File

@@ -8,9 +8,10 @@ App(
stack_size=8 * 1024,
order=50,
fap_icon="appicon.png",
fap_icon_assets="assets",
fap_category="Games",
fap_icon_assets="assets", # Image assets to compile for this application
fap_description="An implementation of the classic arcade game Asteroids",
fap_author="antirez, SimplyMinimal",
fap_weburl="https://github.com/SimplyMinimal/FlipperZero-Asteroids",
fap_author="@antirez & @SimplyMinimal",
fap_weburl="https://github.com/antirez/flipper-asteroids",
fap_version="1.0",
fap_description="Asteroids game",
)

View File

@@ -41,4 +41,4 @@ typedef struct {
AvrIspError error;
} AvrIspApp;
bool avr_isp_load_from_file(AvrIspApp* app);
bool avr_isp_load_from_file(AvrIspApp* app);

View File

@@ -60,7 +60,9 @@ static int32_t avr_isp_worker_rw_thread(void* context) {
AvrIspWorkerRW* instance = context;
/* start PWM on &gpio_ext_pa4 */
furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50);
if(!furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4)) {
furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50);
}
FURI_LOG_D(TAG, "Start");
@@ -122,7 +124,9 @@ static int32_t avr_isp_worker_rw_thread(void* context) {
}
FURI_LOG_D(TAG, "Stop");
furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4);
if(furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4)) {
furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4);
}
return 0;
}
@@ -136,7 +140,12 @@ bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance) {
instance->chip_arr_ind = avr_isp_chip_arr_size + 1;
/* start PWM on &gpio_ext_pa4 */
furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50);
bool was_pwm_enabled = false;
if(!furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4)) {
furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50);
} else {
was_pwm_enabled = true;
}
do {
if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) {
@@ -200,7 +209,9 @@ bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance) {
} while(0);
furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4);
if(furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4) && !was_pwm_enabled) {
furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4);
}
if(instance->callback) {
if(instance->chip_arr_ind > avr_isp_chip_arr_size) {

View File

@@ -1,8 +1,6 @@
#include "../avr_isp_app_i.h"
#include <gui/modules/validators.h>
#define MAX_TEXT_INPUT_LEN 22
void avr_isp_scene_input_name_text_callback(void* context) {
furi_assert(context);
@@ -46,7 +44,7 @@ void avr_isp_scene_input_name_on_enter(void* context) {
avr_isp_scene_input_name_text_callback,
app,
app->file_name_tmp,
MAX_TEXT_INPUT_LEN, // buffer size
AVR_ISP_MAX_LEN_NAME, // buffer size
dev_name_empty);
ValidatorIsFile* validator_is_file =

View File

@@ -8,4 +8,9 @@ App(
fap_category="Misc",
fap_icon="images/barcode_10.png",
fap_icon_assets="images",
fap_icon_assets_symbol="barcode_app",
fap_author="@Kingal1337",
fap_weburl="https://github.com/Kingal1337/flipper-barcode-generator",
fap_version="1.0",
fap_description="App allows you to display various barcodes on flipper screen",
)

View File

@@ -13,7 +13,7 @@
static bool select_file(const char* folder, FuriString* file_path) {
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, BARCODE_EXTENSION, &I_barcode_10);
dialog_file_browser_set_basic_options(&browser_options, "", &I_barcode_10);
browser_options.base_path = DEFAULT_USER_BARCODES;
furi_string_set(file_path, folder);
@@ -65,7 +65,7 @@ bool get_file_name_from_path(FuriString* file_path, FuriString* file_name, bool
if(file_path == NULL || file_name == NULL) {
return false;
}
uint slash_index = furi_string_search_rchar(file_path, '/', 0);
uint32_t slash_index = furi_string_search_rchar(file_path, '/', 0);
if(slash_index == FURI_STRING_FAILURE || slash_index >= (furi_string_size(file_path) - 1)) {
return false;
}
@@ -73,7 +73,7 @@ bool get_file_name_from_path(FuriString* file_path, FuriString* file_name, bool
furi_string_set(file_name, file_path);
furi_string_right(file_name, slash_index + 1);
if(remove_extension) {
uint ext_index = furi_string_search_rchar(file_name, '.', 0);
uint32_t ext_index = furi_string_search_rchar(file_name, '.', 0);
if(ext_index != FURI_STRING_FAILURE && ext_index < (furi_string_size(file_path))) {
furi_string_left(file_name, ext_index);
}
@@ -239,6 +239,11 @@ void submenu_callback(void* context, uint32_t index) {
}
}
uint32_t create_view_callback(void* context) {
UNUSED(context);
return CreateBarcodeView;
}
uint32_t main_menu_callback(void* context) {
UNUSED(context);
return MainMenuView;
@@ -305,6 +310,7 @@ int32_t barcode_main(void* p) {
* Creating Text Input View
******************************/
app->text_input = text_input_alloc();
view_set_previous_callback(text_input_get_view(app->text_input), create_view_callback);
view_dispatcher_add_view(
app->view_dispatcher, TextInputView, text_input_get_view(app->text_input));

View File

@@ -15,7 +15,7 @@
#include "barcode_utils.h"
#define TAG "BARCODE"
#define VERSION "1.0"
#define VERSION "1.1"
#define FILE_VERSION "1"
#define TEXT_BUFFER_SIZE 128
@@ -23,10 +23,11 @@
#define BARCODE_HEIGHT 50
#define BARCODE_Y_START 3
#define APPS_DATA EXT_PATH("apps_data")
//the folder where the encodings are located
#define BARCODE_DATA_FILE_DIR_PATH APPS_DATA "/barcode_data"
#define BARCODE_DATA_FILE_DIR_PATH EXT_PATH("apps_data/barcode_data")
//the folder where the codabar encoding table is located
#define CODABAR_DICT_FILE_PATH BARCODE_DATA_FILE_DIR_PATH "/codabar_encodings.txt"
//the folder where the code 39 encoding table is located
#define CODE39_DICT_FILE_PATH BARCODE_DATA_FILE_DIR_PATH "/code39_encodings.txt"
@@ -35,11 +36,11 @@
#define CODE128_DICT_FILE_PATH BARCODE_DATA_FILE_DIR_PATH "/code128_encodings.txt"
//the folder where the user stores their barcodes
#define DEFAULT_USER_BARCODES EXT_PATH("barcodes")
#define DEFAULT_USER_BARCODES EXT_PATH("apps_data/barcodes")
//The extension barcode files use
#define BARCODE_EXTENSION ".barcode"
#define BARCODE_EXTENSION_LENGTH 8
#define BARCODE_EXTENSION ".txt"
#define BARCODE_EXTENSION_LENGTH 4
#include "views/barcode_view.h"
#include "views/create_view.h"

View File

@@ -43,6 +43,14 @@ void init_types() {
code_128->start_pos = 0;
barcode_type_objs[CODE128] = code_128;
BarcodeTypeObj* codabar = malloc(sizeof(BarcodeTypeObj));
codabar->name = "Codabar";
codabar->type = CODABAR;
codabar->min_digits = 1;
codabar->max_digits = -1;
codabar->start_pos = 0;
barcode_type_objs[CODABAR] = codabar;
BarcodeTypeObj* unknown = malloc(sizeof(BarcodeTypeObj));
unknown->name = "Unknown";
unknown->type = UNKNOWN;
@@ -74,6 +82,9 @@ BarcodeTypeObj* get_type(FuriString* type_string) {
if(furi_string_cmp_str(type_string, "CODE-128") == 0) {
return barcode_type_objs[CODE128];
}
if(furi_string_cmp_str(type_string, "Codabar") == 0) {
return barcode_type_objs[CODABAR];
}
return barcode_type_objs[UNKNOWN];
}
@@ -98,7 +109,7 @@ const char* get_error_code_name(ErrorCode error_code) {
return "OK";
default:
return "Unknown Code";
}
};
}
const char* get_error_code_message(ErrorCode error_code) {
@@ -121,5 +132,5 @@ const char* get_error_code_message(ErrorCode error_code) {
return "OK";
default:
return "Could not read barcode data";
}
};
}

View File

@@ -3,7 +3,7 @@
#include <furi.h>
#include <furi_hal.h>
#define NUMBER_OF_BARCODE_TYPES 6
#define NUMBER_OF_BARCODE_TYPES 7
typedef enum {
WrongNumberOfDigits, //There is too many or too few digits in the barcode
@@ -22,6 +22,7 @@ typedef enum {
EAN13,
CODE39,
CODE128,
CODABAR,
UNKNOWN
} BarcodeType;

View File

@@ -13,6 +13,9 @@ void barcode_loader(BarcodeData* barcode_data) {
case CODE128:
code_128_loader(barcode_data);
break;
case CODABAR:
codabar_loader(barcode_data);
break;
case UNKNOWN:
barcode_data->reason = UnsupportedType;
barcode_data->valid = false;
@@ -36,6 +39,7 @@ int calculate_check_digit(BarcodeData* barcode_data) {
break;
case CODE39:
case CODE128:
case CODABAR:
case UNKNOWN:
default:
break;
@@ -130,7 +134,6 @@ void code_39_loader(BarcodeData* barcode_data) {
int barcode_length = furi_string_size(barcode_data->raw_data);
int min_digits = barcode_data->type_obj->min_digits;
// int max_digit = barcode_data->type_obj->max_digits;
//check the length of the barcode, must contain atleast a character,
//this can have as many characters as it wants, it might not fit on the screen
@@ -215,7 +218,6 @@ void code_128_loader(BarcodeData* barcode_data) {
const char* stop_code_bits = "1100011101011";
int min_digits = barcode_data->type_obj->min_digits;
// int max_digit = barcode_data->type_obj->max_digits;
/**
* A sum of all of the characters values
@@ -342,3 +344,60 @@ void code_128_loader(BarcodeData* barcode_data) {
furi_string_cat(barcode_data->correct_data, barcode_bits);
furi_string_free(barcode_bits);
}
void codabar_loader(BarcodeData* barcode_data) {
int barcode_length = furi_string_size(barcode_data->raw_data);
int min_digits = barcode_data->type_obj->min_digits;
//check the length of the barcode, must contain atleast a character,
//this can have as many characters as it wants, it might not fit on the screen
if(barcode_length < min_digits) {
barcode_data->reason = WrongNumberOfDigits;
barcode_data->valid = false;
return;
}
FuriString* barcode_bits = furi_string_alloc();
barcode_length = furi_string_size(barcode_data->raw_data);
//Open Storage
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* ff = flipper_format_file_alloc(storage);
if(!flipper_format_file_open_existing(ff, CODABAR_DICT_FILE_PATH)) {
FURI_LOG_E(TAG, "Could not open file %s", CODABAR_DICT_FILE_PATH);
barcode_data->reason = MissingEncodingTable;
barcode_data->valid = false;
} else {
FuriString* char_bits = furi_string_alloc();
for(int i = 0; i < barcode_length; i++) {
char barcode_char = toupper(furi_string_get_char(barcode_data->raw_data, i));
//convert a char into a string so it used in flipper_format_read_string
char current_character[2];
snprintf(current_character, 2, "%c", barcode_char);
if(!flipper_format_read_string(ff, current_character, char_bits)) {
FURI_LOG_E(TAG, "Could not read \"%c\" string", barcode_char);
barcode_data->reason = InvalidCharacters;
barcode_data->valid = false;
break;
} else {
FURI_LOG_I(
TAG, "\"%c\" string: %s", barcode_char, furi_string_get_cstr(char_bits));
furi_string_cat(barcode_bits, char_bits);
}
flipper_format_rewind(ff);
}
furi_string_free(char_bits);
}
//Close Storage
flipper_format_free(ff);
furi_record_close(RECORD_STORAGE);
furi_string_cat(barcode_data->correct_data, barcode_bits);
furi_string_free(barcode_bits);
}

View File

@@ -10,4 +10,5 @@ void ean_8_loader(BarcodeData* barcode_data);
void ean_13_loader(BarcodeData* barcode_data);
void code_39_loader(BarcodeData* barcode_data);
void code_128_loader(BarcodeData* barcode_data);
void codabar_loader(BarcodeData* barcode_data);
void barcode_loader(BarcodeData* barcode_data);

View File

@@ -322,6 +322,68 @@ static void draw_code_128(Canvas* canvas, BarcodeData* barcode_data) {
canvas, 62, y + height + 8, AlignCenter, AlignBottom, furi_string_get_cstr(raw_data));
}
static void draw_codabar(Canvas* canvas, BarcodeData* barcode_data) {
FuriString* raw_data = barcode_data->raw_data;
FuriString* barcode_digits = barcode_data->correct_data;
//BarcodeTypeObj* type_obj = barcode_data->type_obj;
int barcode_length = furi_string_size(barcode_digits);
int total_pixels = 0;
for(int i = 0; i < barcode_length; i++) {
//1 for wide, 0 for narrow
char wide_or_narrow = furi_string_get_char(barcode_digits, i);
int wn_digit = wide_or_narrow - '0'; //wide(1) or narrow(0) digit
if(wn_digit == 1) {
total_pixels += 3;
} else {
total_pixels += 1;
}
if((i + 1) % 7 == 0) {
total_pixels += 1;
}
}
int x = (128 - total_pixels) / 2;
int y = BARCODE_Y_START;
int width = 1;
int height = BARCODE_HEIGHT;
bool filled_in = true;
//set the canvas color to black to print the digit
canvas_set_color(canvas, ColorBlack);
// canvas_draw_str_aligned(canvas, 62, 30, AlignCenter, AlignCenter, error);
canvas_draw_str_aligned(
canvas, 62, y + height + 8, AlignCenter, AlignBottom, furi_string_get_cstr(raw_data));
for(int i = 0; i < barcode_length; i++) {
//1 for wide, 0 for narrow
char wide_or_narrow = furi_string_get_char(barcode_digits, i);
int wn_digit = wide_or_narrow - '0'; //wide(1) or narrow(0) digit
if(filled_in) {
if(wn_digit == 1) {
x = draw_bits(canvas, "111", x, y, width, height);
} else {
x = draw_bits(canvas, "1", x, y, width, height);
}
filled_in = false;
} else {
if(wn_digit == 1) {
x = draw_bits(canvas, "000", x, y, width, height);
} else {
x = draw_bits(canvas, "0", x, y, width, height);
}
filled_in = true;
}
if((i + 1) % 7 == 0) {
x = draw_bits(canvas, "0", x, y, width, height);
filled_in = true;
}
}
}
static void barcode_draw_callback(Canvas* canvas, void* ctx) {
furi_assert(ctx);
BarcodeModel* barcode_model = ctx;
@@ -346,6 +408,9 @@ static void barcode_draw_callback(Canvas* canvas, void* ctx) {
case CODE128:
draw_code_128(canvas, data);
break;
case CODABAR:
draw_codabar(canvas, data);
break;
case UNKNOWN:
default:
break;

View File

@@ -448,7 +448,8 @@ void save_barcode(CreateView* create_view_object) {
flipper_format_write_string_cstr(ff, "Version", FILE_VERSION);
flipper_format_write_comment_cstr(ff, "Types - UPC-A, EAN-8, EAN-13, CODE-39, CODE-128");
flipper_format_write_comment_cstr(
ff, "Types - UPC-A, EAN-8, EAN-13, CODE-39, CODE-128, Codabar");
flipper_format_write_string_cstr(ff, "Type", barcode_type->name);

View File

@@ -53,15 +53,9 @@ MessageView* message_view_allocate(BarcodeApp* barcode_app) {
return message_view_object;
}
void message_view_free_model(MessageView* message_view_object) {
with_view_model(
message_view_object->view, MessageViewModel * model, { UNUSED(model); }, true);
}
void message_view_free(MessageView* message_view_object) {
furi_assert(message_view_object);
message_view_free_model(message_view_object);
view_free(message_view_object->view);
free(message_view_object);
}
@@ -69,4 +63,4 @@ void message_view_free(MessageView* message_view_object) {
View* message_get_view(MessageView* message_view_object) {
furi_assert(message_view_object);
return message_view_object->view;
}
}

View File

@@ -9,4 +9,7 @@ App(
fap_icon="blackjack_10px.png",
fap_category="Games",
fap_icon_assets="assets",
fap_author="@teeebor",
fap_version="1.0",
fap_description="Blackjack Game",
)

View File

@@ -275,6 +275,7 @@ void dealer_tick(GameState* game_state) {
if(dealer_score >= DEALER_MAX) {
if(dealer_score > 21 || dealer_score < player_score) {
// dolphin_deed(DolphinDeedPluginGameWin);
enqueue(
&(game_state->queue_state),
game_state,
@@ -568,6 +569,9 @@ int32_t blackjack_app(void* p) {
AppEvent event;
// Call dolphin deed on game start
// dolphin_deed(DolphinDeedPluginGameStart);
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
furi_mutex_acquire(game_state->mutex, FuriWaitForever);

View File

@@ -1,7 +1,7 @@
#include <storage/storage.h>
#include "util.h"
const char* CONFIG_FILE_PATH = EXT_PATH(".blackjack.settings");
const char* CONFIG_FILE_PATH = APP_DATA_PATH("blackjack.settings");
void save_settings(Settings settings) {
Storage* storage = furi_record_open(RECORD_STORAGE);
@@ -59,6 +59,7 @@ Settings load_settings() {
FURI_LOG_D(APP_NAME, "Opening storage");
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_common_migrate(storage, EXT_PATH(".blackjack.settings"), CONFIG_FILE_PATH);
FURI_LOG_D(APP_NAME, "Allocating file");
FlipperFormat* file = flipper_format_file_alloc(storage);
@@ -120,4 +121,4 @@ Settings load_settings() {
flipper_format_free(file);
furi_record_close(RECORD_STORAGE);
return settings;
}
}

View File

@@ -11,4 +11,7 @@ App(
fap_icon="bomb.png",
fap_category="Games",
fap_icon_assets="assets",
fap_author="@leo-need-more-coffee & @xMasterX",
fap_version="1.0",
fap_description="Bomberduck(Bomberman) Game",
)

View File

@@ -382,6 +382,7 @@ int32_t bomberduck_app(void* p) {
return 255;
}
// dolphin_deed(DolphinDeedPluginGameStart);
// Создаем новый view port
ViewPort* view_port = view_port_alloc();
// Создаем callback отрисовки, без контекста
@@ -455,6 +456,9 @@ int32_t bomberduck_app(void* p) {
notification_message(notification, &end);
world.running = 0;
world.level += 1;
// if(world.level % 5 == 0) {
// dolphin_deed(DolphinDeedPluginGameWin);
// }
}
for(int i = 0; i < world.bombs_count; i++) {
if(furi_get_tick() - world.bombs[i].planted >

View File

@@ -1,5 +1,5 @@
App(
appid="BPM_Tapper",
appid="bpm_tapper",
name="BPM Tapper",
apptype=FlipperAppType.EXTERNAL,
entry_point="bpm_tapper_app",
@@ -7,7 +7,10 @@ App(
requires=["gui"],
stack_size=2 * 1024,
fap_icon="bpm_10px.png",
fap_category="Music",
fap_icon_assets="icons",
fap_category="Media",
order=15,
fap_author="@panki27",
fap_weburl="https://github.com/panki27/bpm-tapper",
fap_version="1.0",
fap_description="Tap center button to measure BPM",
)

View File

@@ -4,7 +4,7 @@
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#include "BPM_Tapper_icons.h"
#include "assets_icons.h"
typedef enum {
EventTypeTick,

View File

@@ -1,5 +1,5 @@
App(
appid="Brainfuck",
appid="brainfuck",
name="Brainfuck",
apptype=FlipperAppType.EXTERNAL,
entry_point="brainfuck_app",
@@ -11,4 +11,8 @@ App(
fap_icon="bfico.png",
fap_category="Misc",
fap_icon_assets="icons",
fap_author="@nymda",
fap_weburl="https://github.com/nymda/FlipperZeroBrainfuck",
fap_version="1.0",
fap_description="Brainfuck language interpreter",
)

View File

@@ -137,7 +137,7 @@ int32_t brainfuck_app(void* p) {
}
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_simply_mkdir(storage, "/ext/brainfuck");
storage_common_migrate(storage, EXT_PATH("brainfuck"), STORAGE_APP_DATA_PATH_PREFIX);
scene_manager_next_scene(brainfuck->scene_manager, brainfuckSceneStart);
@@ -146,4 +146,4 @@ int32_t brainfuck_app(void* p) {
brainfuck_free(brainfuck);
return 0;
}
}

View File

@@ -29,7 +29,8 @@ typedef unsigned char byte;
#include <storage/storage.h>
#include <lib/toolbox/path.h>
#include <Brainfuck_icons.h>
#include <brainfuck_icons.h>
#include <assets_icons.h>
#include <storage/storage.h>
#include <stream/stream.h>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -25,7 +25,7 @@ bool brainfuck_scene_file_create_on_event(void* context, SceneManagerEvent event
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == brainfuckCustomEventTextInputDone) {
furi_string_cat_printf(app->BF_file_path, "/ext/brainfuck/%s.b", tmpName);
furi_string_cat_printf(app->BF_file_path, APP_DATA_PATH("%s.b"), tmpName);
//remove old file
Storage* storage = furi_record_open(RECORD_STORAGE);

View File

@@ -6,11 +6,11 @@ void brainfuck_scene_file_select_on_enter(void* context) {
DialogsApp* dialogs = furi_record_open("dialogs");
FuriString* path;
path = furi_string_alloc();
furi_string_set(path, "/ext/brainfuck");
furi_string_set(path, STORAGE_APP_DATA_PATH_PREFIX);
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, ".b", &I_bfico);
browser_options.base_path = "/ext/brainfuck";
browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
browser_options.hide_ext = false;
bool selected = dialog_file_browser_show(dialogs, path, path, &browser_options);

View File

@@ -1,5 +1,5 @@
App(
appid="Caesar_Cipher",
appid="caesar_cipher",
name="Caesar Cipher",
apptype=FlipperAppType.EXTERNAL,
entry_point="caesar_cipher_app",
@@ -11,4 +11,8 @@ App(
fap_icon="caesar_cipher_icon.png",
fap_category="Misc",
order=20,
fap_author="@panki27",
fap_weburl="https://github.com/panki27/caesar-cipher",
fap_version="1.0",
fap_description="Encrypt and decrypt text using Caesar Cipher",
)

View File

@@ -1,5 +1,5 @@
App(
appid="Calculator",
appid="calculator",
name="Calculator",
apptype=FlipperAppType.EXTERNAL,
entry_point="calculator_app",
@@ -9,4 +9,8 @@ App(
order=45,
fap_icon="calcIcon.png",
fap_category="Misc",
fap_author="@n-o-T-I-n-s-a-n-e",
fap_weburl="https://github.com/n-o-T-I-n-s-a-n-e",
fap_version="1.0",
fap_description="Calculator, that can calculate simple expressions",
)

View File

@@ -13,4 +13,8 @@ App(
order=20,
fap_icon="cntdown_timer.png",
fap_category="Misc",
fap_author="@0w0mewo",
fap_weburl="https://github.com/0w0mewo/fpz_cntdown_timer",
fap_version="1.0",
fap_description="Simple count down timer",
)

View File

@@ -7,6 +7,9 @@ App(
"gui",
],
fap_category="Misc",
fap_icon="icons/counter_icon.png",
fap_icon_assets="icons",
fap_icon="counter_icon.png",
fap_author="@Krulknul",
fap_weburl="https://github.com/Krulknul/dolphin-counter",
fap_version="1.0",
fap_description="Simple counter",
)

View File

@@ -2,7 +2,6 @@
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#include <counter_icons.h>
#define MAX_COUNT 99
#define BOXTIME 2

View File

Before

Width:  |  Height:  |  Size: 233 B

After

Width:  |  Height:  |  Size: 233 B

View File

@@ -1,6 +1,6 @@
App(
appid="dap_link",
name="[GPIO] DAP Link",
name="[SWD-JTAG] DAP Link",
apptype=FlipperAppType.EXTERNAL,
entry_point="dap_link_app",
requires=[

View File

@@ -15,6 +15,7 @@
#include "usb/dap_v2_usb.h"
#include <dialogs/dialogs.h>
#include "dap_link_icons.h"
#include "assets_icons.h"
/***************************************************************************/
/****************************** DAP COMMON *********************************/
@@ -524,4 +525,4 @@ int32_t dap_link_app(void* p) {
dap_app_free(app);
return 0;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -995,6 +995,9 @@ int32_t doom_app() {
music_player_worker_load_rtttl_from_string(plugin_state->music_instance->worker, dsintro);
music_player_worker_start(plugin_state->music_instance->worker);
#endif
// Call dolphin deed on game start
// dolphin_deed(DolphinDeedPluginGameStart);
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
furi_mutex_acquire(plugin_state->mutex, FuriWaitForever);

View File

@@ -12,4 +12,7 @@ App(
stack_size=8 * 1024,
order=20,
fap_category="Tools",
fap_author="@litui & @xMasterX",
fap_version="1.0",
fap_description="DTMF (Dual-Tone Multi-Frequency) dialer, Bluebox, and Redbox.",
)

View File

@@ -83,7 +83,7 @@ int32_t dtmf_dolphin_app(void* p) {
UNUSED(p);
DTMFDolphinApp* app = app_alloc();
DOLPHIN_DEED(DolphinDeedPluginStart);
dolphin_deed(DolphinDeedPluginStart);
view_dispatcher_run(app->view_dispatcher);
app_free(app);

View File

@@ -1,5 +1,5 @@
App(
appid="MAYHEM_Camera",
appid="mayhem_camera",
name="[MAYHEM] Camera",
apptype=FlipperAppType.EXTERNAL,
entry_point="camera_app",

View File

@@ -1,5 +1,5 @@
App(
appid="MAYHEM_Marauder",
appid="mayhem_marauder",
name="[MAYHEM] Marauder",
apptype=FlipperAppType.EXTERNAL,
entry_point="wifi_marauder_app",

View File

@@ -158,11 +158,12 @@ void wifi_marauder_app_free(WifiMarauderApp* app) {
int32_t wifi_marauder_app(void* p) {
UNUSED(p);
furi_hal_power_disable_external_3_3v();
furi_hal_power_disable_otg();
uint8_t attempts = 0;
while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
furi_hal_power_enable_otg();
furi_delay_ms(10);
}
furi_delay_ms(200);
furi_hal_power_enable_external_3_3v();
furi_hal_power_enable_otg();
for(int i = 0; i < 2; i++) {
furi_delay_ms(500);
furi_hal_uart_tx(FuriHalUartIdUSART1, (uint8_t[1]){'w'}, 1);
@@ -181,7 +182,9 @@ int32_t wifi_marauder_app(void* p) {
wifi_marauder_app_free(wifi_marauder_app);
furi_hal_power_disable_otg();
if(furi_hal_power_is_otg_enabled()) {
furi_hal_power_disable_otg();
}
return 0;
}

View File

@@ -25,7 +25,7 @@
#define WIFI_MARAUDER_TEXT_INPUT_STORE_SIZE (512)
#define MARAUDER_APP_FOLDER_USER "apps_data/marauder"
#define MARAUDER_APP_FOLDER ANY_PATH(MARAUDER_APP_FOLDER_USER)
#define MARAUDER_APP_FOLDER EXT_PATH(MARAUDER_APP_FOLDER_USER)
#define MARAUDER_APP_FOLDER_PCAPS MARAUDER_APP_FOLDER "/pcaps"
#define MARAUDER_APP_FOLDER_LOGS MARAUDER_APP_FOLDER "/logs"
#define MARAUDER_APP_FOLDER_USER_PCAPS MARAUDER_APP_FOLDER_USER "/pcaps"

View File

@@ -106,6 +106,9 @@ void wifi_marauder_uart_free(WifiMarauderUart* uart) {
furi_thread_free(uart->rx_thread);
furi_hal_uart_set_irq_cb(uart->channel, NULL, NULL);
if(uart->channel == FuriHalUartIdLPUART1) {
furi_hal_uart_deinit(uart->channel);
}
furi_hal_console_enable();
free(uart);

View File

@@ -1,5 +1,5 @@
App(
appid="MAYHEM_MorseFlash",
appid="mayhem_morseflash",
name="[MAYHEM] Morse Flasher",
apptype=FlipperAppType.EXTERNAL,
entry_point="uart_terminal_app",
@@ -7,7 +7,6 @@ App(
requires=["gui"],
stack_size=1 * 1024,
order=90,
fap_icon_assets="assets",
fap_icon="icon.png",
fap_category="GPIO",
fap_description="ESP32-CAM app to stream a message in morse using the powerful flashlight. [Unplug the USB cable to test with Mayhem]",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Some files were not shown because too many files have changed in this diff Show More