V49 Release Candidate Changes (#321)
2
.github/workflow_data/hotfix.py
vendored
@@ -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")
|
||||
|
||||
21
.github/workflow_data/release.md
vendored
@@ -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**
|
||||
|
||||
4
.github/workflows/build.yml
vendored
@@ -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"
|
||||
|
||||
4
.github/workflows/hotfix.yml
vendored
@@ -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"
|
||||
|
||||
4
.github/workflows/release.yml
vendored
@@ -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"
|
||||
|
||||
4
.github/workflows/sonarcloud.yaml
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
15
ReadMe.md
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -12,5 +12,6 @@ App(
|
||||
"display_test",
|
||||
"text_box_test",
|
||||
"file_browser_test",
|
||||
"speaker_debug",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -11,5 +11,4 @@ App(
|
||||
stack_size=1 * 1024,
|
||||
order=130,
|
||||
fap_category="Debug",
|
||||
fap_libs=["assets"],
|
||||
)
|
||||
|
||||
10
applications/debug/crash_test/application.fam
Normal 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",
|
||||
)
|
||||
128
applications/debug/crash_test/crash_test.c
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
11
applications/debug/speaker_debug/application.fam
Normal 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"],
|
||||
)
|
||||
120
applications/debug/speaker_debug/speaker_debug.c
Normal 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;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
App(
|
||||
appid="UART_Echo",
|
||||
appid="uart_echo",
|
||||
name="[GPIO] UART Echo",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="uart_echo_app",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
12
applications/external/.mass_storage/application.fam
vendored
Normal 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",
|
||||
)
|
||||
220
applications/external/.mass_storage/helpers/mass_storage_scsi.c
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
56
applications/external/.mass_storage/helpers/mass_storage_scsi.h
vendored
Normal 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);
|
||||
473
applications/external/.mass_storage/helpers/mass_storage_usb.c
vendored
Normal 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
|
||||
}
|
||||
9
applications/external/.mass_storage/helpers/mass_storage_usb.h
vendored
Normal 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);
|
||||
124
applications/external/.mass_storage/mass_storage_app.c
vendored
Normal 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;
|
||||
}
|
||||
11
applications/external/.mass_storage/mass_storage_app.h
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct MassStorageApp MassStorageApp;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
44
applications/external/.mass_storage/mass_storage_app_i.h
vendored
Normal 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;
|
||||
30
applications/external/.mass_storage/scenes/mass_storage_scene.c
vendored
Normal 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,
|
||||
};
|
||||
29
applications/external/.mass_storage/scenes/mass_storage_scene.h
vendored
Normal 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
|
||||
3
applications/external/.mass_storage/scenes/mass_storage_scene_config.h
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
ADD_SCENE(mass_storage, file_select, FileSelect)
|
||||
ADD_SCENE(mass_storage, work, Work)
|
||||
ADD_SCENE(mass_storage, error, Error)
|
||||
54
applications/external/.mass_storage/scenes/mass_storage_scene_error.c
vendored
Normal 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);
|
||||
}
|
||||
47
applications/external/.mass_storage/scenes/mass_storage_scene_file_select.c
vendored
Normal 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;
|
||||
}
|
||||
104
applications/external/.mass_storage/scenes/mass_storage_scene_work.c
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
53
applications/external/.mass_storage/views/mass_storage_view.c
vendored
Normal 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);
|
||||
}
|
||||
13
applications/external/.mass_storage/views/mass_storage_view.h
vendored
Normal 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
@@ -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;
|
||||
}
|
||||
BIN
applications/external/4inrow/4inrow_10px.png
vendored
Normal file
|
After Width: | Height: | Size: 200 B |
17
applications/external/4inrow/application.fam
vendored
Normal 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",
|
||||
)
|
||||
6
applications/external/airmouse/air_mouse.c
vendored
@@ -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);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
App(
|
||||
appid="Air_Mouse",
|
||||
appid="air_mouse",
|
||||
name="[BMI160] Air Mouse",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="air_mouse_app",
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
|
||||
11
applications/external/asteroids/app.c
vendored
@@ -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));
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
12
applications/external/barcode_gen/barcode_app.c
vendored
@@ -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));
|
||||
|
||||
|
||||
15
applications/external/barcode_gen/barcode_app.h
vendored
@@ -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"
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
4
applications/external/blackjack/blackjack.c
vendored
@@ -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);
|
||||
|
||||
5
applications/external/blackjack/util.c
vendored
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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 >
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
2
applications/external/bpmtapper/bpm.c
vendored
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
4
applications/external/brainfuck/brainfuck.c
vendored
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
1
applications/external/counter/counter.c
vendored
@@ -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
|
||||
|
||||
|
Before Width: | Height: | Size: 233 B After Width: | Height: | Size: 233 B |
@@ -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=[
|
||||
|
||||
3
applications/external/dap_link/dap_link.c
vendored
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 3.8 KiB |
3
applications/external/doom/doom.c
vendored
@@ -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);
|
||||
|
||||
@@ -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.",
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
App(
|
||||
appid="MAYHEM_Camera",
|
||||
appid="mayhem_camera",
|
||||
name="[MAYHEM] Camera",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="camera_app",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
App(
|
||||
appid="MAYHEM_Marauder",
|
||||
appid="mayhem_marauder",
|
||||
name="[MAYHEM] Marauder",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="wifi_marauder_app",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]",
|
||||
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |