Merge remote-tracking branch 'mntm/dev' into js-app-internal

This commit is contained in:
Willy-JL
2024-11-04 08:46:04 +00:00
191 changed files with 6288 additions and 1351 deletions
+24 -17
View File
@@ -4,8 +4,9 @@ import requests
import json
import os
artifact_tgz = f"{os.environ['INDEXER_URL']}/firmware/dev/{os.environ['ARTIFACT_TAG']}.tgz"
artifact_sdk = f"{os.environ['INDEXER_URL']}/firmware/dev/{os.environ['ARTIFACT_TAG'].replace('update', 'sdk')}.zip"
base_url = f"{os.environ['INDEXER_URL']}/builds/firmware/dev"
artifact_tgz = f"{base_url}/{os.environ['ARTIFACT_TAG']}.tgz"
artifact_sdk = f"{base_url}/{os.environ['ARTIFACT_TAG'].replace('update', 'sdk')}.zip"
artifact_lab = f"https://lab.flipper.net/?url={artifact_tgz}&channel=dev-cfw&version={os.environ['VERSION_TAG']}"
@@ -41,28 +42,34 @@ if __name__ == "__main__":
"fields": [
{
"name": "Code Diff:",
"value": "\n".join([
f"[From last release ({release} to {after[:8]})]({compare}/{release}...{after})",
f"[From last build ({before[:8]} to {after[:8]})]({compare}/{before}...{after})",
])
"value": "\n".join(
[
f"[From last release ({release} to {after[:8]})]({compare}/{release}...{after})",
f"[From last build ({before[:8]} to {after[:8]})]({compare}/{before}...{after})",
]
),
},
{
"name": "Changelog:",
"value": "\n".join([
f"[Since last release ({release})]({event['repository']['html_url']}/blob/{after}/CHANGELOG.md)",
])
"value": "\n".join(
[
f"[Since last release ({release})]({event['repository']['html_url']}/blob/{after}/CHANGELOG.md)",
]
),
},
{
"name": "Firmware Artifacts:",
"value": "\n".join([
f"- [🖥️ Install with Web Updater](https://momentum-fw.dev/update)",
f"- [ Open in Flipper Lab/App]({artifact_lab})",
f"- [🐬 Download Firmware TGZ]({artifact_tgz})",
f"- [🛠️ SDK (for development)]({artifact_sdk})",
])
}
"value": "\n".join(
[
f"- [🖥 Install with Web Updater](https://momentum-fw.dev/update)",
f"- [☁️ Open in Flipper Lab/App]({artifact_lab})",
f"- [🐬 Download Firmware TGZ]({artifact_tgz})",
f"- [🛠️ SDK (for development)]({artifact_sdk})",
]
),
},
],
"timestamp": dt.datetime.utcnow().isoformat()
"timestamp": dt.datetime.utcnow().isoformat(),
}
],
},
+1 -1
View File
@@ -1,7 +1,7 @@
## ⬇️ Download
>### [🖥️ Web Updater (chrome)](https://momentum-fw.dev/update) [recommended]
>### [☁️ Flipper Lab/App (chrome/mobile)](https://lab.flipper.net/?url=https://up.momentum-fw.dev/firmware/{VERSION_TAG}/flipper-z-f7-update-{VERSION_TAG}.tgz&channel=release-cfw&version={VERSION_TAG})
>### [☁️ Flipper Lab/App (chrome/mobile)](https://lab.flipper.net/?url=https://up.momentum-fw.dev/builds/firmware/{VERSION_TAG}/flipper-z-f7-update-{VERSION_TAG}.tgz&channel=release-cfw&version={VERSION_TAG})
>### [🐬 qFlipper Package (.tgz)](https://github.com/Next-Flip/Momentum-Firmware/releases/download/{VERSION_TAG}/flipper-z-f7-update-{VERSION_TAG}.tgz)
+17 -2
View File
@@ -119,6 +119,20 @@ jobs:
tar czpf "artifacts/flipper-z-any-scripts-${SUFFIX}.tgz" scripts
cp build/core2_firmware.tgz "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz"
- name: "Calculate DFU sizes"
env:
INDEXER_URL: ${{ secrets.INDEXER_URL }}
if: ${{ env.INDEXER_URL != '' && github.event.pull_request && matrix.target == env.DEFAULT_TARGET }}
run: |
curl -L "${{ secrets.INDEXER_URL }}"/firmware/development/f7/full_dfu -o dev.dfu
dfu_size_new=$(du --apparent-size -B 1 artifacts/flipper-z-${TARGET}-full-*.dfu | cut -f1)
dfu_size_dev=$(du --apparent-size -B 1 dev.dfu | cut -f1)
dfu_size_diff=$((dfu_size_new - dfu_size_dev))
DFU_SIZE=$(echo ${dfu_size_new}B | sed -r 's/^([0-9]+)([0-9]{2})([0-9])B/\1.\2K/')
DFU_DIFF=$(echo ${dfu_size_diff}B | sed -r 's/^(-?[0-9]+)([0-9]{2})([0-9])B/\1.\2K/' | sed -r 's/^([^-])/+\1/')
echo "DFU_SIZE=$DFU_SIZE" >> $GITHUB_ENV
echo "DFU_DIFF=$DFU_DIFF" >> $GITHUB_ENV
- name: "Upload artifacts to update server"
env:
INDEXER_URL: ${{ secrets.INDEXER_URL }}
@@ -153,8 +167,9 @@ jobs:
issue-number: ${{ github.event.pull_request.number }}
body: |
**Compiled ${{ matrix.target }} firmware for commit `${{steps.names.outputs.commit_sha}}`:**
- [☁️ Flipper Lab/App](https://lab.flipper.net/?url=${{secrets.INDEXER_URL}}/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=mntm-${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}})
- [📦 qFlipper Package](${{secrets.INDEXER_URL}}/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz)
- [☁️ Flipper Lab/App](https://lab.flipper.net/?url=${{secrets.INDEXER_URL}}/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=mntm-${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}})
- [📦 qFlipper Package](${{secrets.INDEXER_URL}}/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz)
- DFU Size: `${{ env.DFU_SIZE }}` (`${{ env.DFU_DIFF }}` from dev)
edit-mode: replace
- name: Send devbuild webhook
+3 -3
View File
@@ -25,9 +25,9 @@ jobs:
- name: "Download release assets from tag build"
run: |
set -e
wget "${{ secrets.INDEXER_URL }}"/firmware/${{ github.event.release.tag_name }}/flipper-z-f7-update-${{ github.event.release.tag_name }}.tgz
wget "${{ secrets.INDEXER_URL }}"/firmware/${{ github.event.release.tag_name }}/flipper-z-f7-update-${{ github.event.release.tag_name }}.zip
wget "${{ secrets.INDEXER_URL }}"/firmware/${{ github.event.release.tag_name }}/flipper-z-f7-sdk-${{ github.event.release.tag_name }}.zip
wget "${{ secrets.INDEXER_URL }}"/builds/firmware/${{ github.event.release.tag_name }}/flipper-z-f7-update-${{ github.event.release.tag_name }}.tgz
wget "${{ secrets.INDEXER_URL }}"/builds/firmware/${{ github.event.release.tag_name }}/flipper-z-f7-update-${{ github.event.release.tag_name }}.zip
wget "${{ secrets.INDEXER_URL }}"/builds/firmware/${{ github.event.release.tag_name }}/flipper-z-f7-sdk-${{ github.event.release.tag_name }}.zip
- name: "Update release with assets and notes"
uses: "softprops/action-gh-release@v1"
+3
View File
@@ -65,3 +65,6 @@ PVS-Studio.log
.gdbinit
/fbt_options_local.py
# JS packages
node_modules/
+26 -5
View File
@@ -46,7 +46,7 @@
- `to_upper_case()` and `to_lower_case()` renamed and moved to string class as `s.toUpperCase()` and `s.toLowerCase()`
- effort required to update old scripts using these: minimal
- Added type definitions (typescript files for type checking in IDE, Flipper does not run typescript)
- Documentation is incomplete and deprecated, from now on you should refer to type definitions (`applications/system/js_app/types`), those will always be correct
- Documentation is incomplete and deprecated, from now on you should refer to type definitions (`applications/system/js_app/packages/fz-sdk`), those will always be correct
- Type definitions for extra modules we have that OFW doesn't will come later
- GUI: Refactored TextInput illegal symbols (by @Willy-JL)
- If your app used `text_input_add_illegal_symbols(text_input)` it should change to `text_input_show_illegal_symbols(text_input, true)`
@@ -64,7 +64,7 @@
- UL: Add GangQi protocol (static 34 bit) with button parsing and add manually (by @xMasterX & @Skorpionm)
- UL: Add Hollarm protocol (static 42 bit) with button parsing and add manually (by @xMasterX & @Skorpionm)
- UL: Add Hay21 protocol (dynamic 21 bit) with button parsing (by @xMasterX)
- UL: Add Keeloq Monarch full support, with add manually (by @ashphx)
- UL: Add Keeloq Monarch full support, with add manually (by @ashphx & @xMasterX)
- UL: Princeton custom buttons support (by @xMasterX)
- NFC:
- OFW: MIFARE Classic Key Recovery Improvements (PR 3822 by @noproto)
@@ -80,9 +80,20 @@
- Option to "Load from Library File" for Universal Remotes (#255 by @zxkmm)
- Updater: New Yappy themed icon while updating (#253 by @the1anonlypr3 & @Kuronons & @nescap)
- JS:
- OFW: JS: Modules backport & overhaul (by @portasynthinca3)
- See above for list of breaking changes, here are listed strictly new functionalities
- New `event_loop` module for event-driven interactivity
- Interrupt and callback support for `gpio` module
- New `gui` module that allows much more developed interfaces, also new `gui/loading` and `gui/empty_screen` views
- Directory operations and many more file operations for `storage` module
- OFW: Full-fledged JS SDK + npm packages (by @portasynthinca3)
- CFWs can have their own JS SDKs too! Check ours out at [`@next-flip/fz-sdk-mntm`](https://www.npmjs.com/package/@next-flip/fz-sdk-mntm)
- New `i2c` module (#259 by @jamisonderek)
- New `spi` module (#272 by @jamisonderek)
- Added `illegalSymbols` prop for `gui/text_input` view (#290 by @Willy-JL)
- Added typedocs for all extra JS modules in Momentum (by @Willy-JL)
- RPC: Added ASCII event support (#284 by @Willy-JL)
- OFW: Settings: Clock editing & Alarm function (目覚め時計) (by @skotopes)
- BadKB:
- OFW: Add linux/gnome badusb demo files (by @thomasnemer)
- Add older qFlipper install demos for windows and macos (by @DXVVAY & @grugnoymeme)
@@ -95,7 +106,10 @@
- OFW: Extended icon draw function in Canvas (by @RebornedBrain)
- OFW: RPC: Support 5V on GPIO control for ext. modules (by @gsurkov)
- OFW: Toolbox: Proper integer parsing library `strint` (by @portasynthinca3)
- OFW: Furi: Put errno into TCB (by @portasynthinca3)
- Furi:
- OFW: Add FuriEventLoop support for FuriEventFlag, simplify API (by @Skorpionm)
- OFW: Put errno into TCB, better integration with libc (by @portasynthinca3)
- OFW: FuriHalRtc Alarm support (目覚め時計) (by @skotopes)
### Updated:
- Apps:
@@ -111,13 +125,17 @@
- Seader: Enable T=1, show error for timeout, fix wrong LRC logging, fix crash scanning NTAG215 with MFC option (by @bettse)
- BLE Spam: Fix menu index callback (by @Willy-JL)
- Solitaire: App rewrite, Added quick solve, New effects and sounds, Removed hacky canvas manipulation (by @doofy-dev)
- Flappy Bird: Yappy Bird mode, highscore system (by @jaylikesbunda & @the1anonlypr3)
- CLI-GUI Bridge: Add more symbols to keyboard (#222 by @Willy-JL)
- NRF24 Batch: Add Aeropac SN board txt file (by @vad7)
- UL: Sub-GHz Bruteforcer: Add new protocols for existing dump option (by @xMasterX), use FW functions for top buttons (by @DerSkythe)
- UL: NRF24 Apps: Use string library compatible with OFW SDK (by @xMasterX)
- UL: W5500 Ethernet: Various fixes and improvements (by @xMasterX)
- OFW: SPI Mem Manager: Fixed UI rendering bug related to line breaks (by @portasynthinca3)
- OFW: USB/BT Remote: Mouse clicker option to click as fast as possible (by @sumukhj1219)
- CLI: Print plugin name on load fail (by @Willy-JL)
- CLI:
- Print plugin name on load fail (by @Willy-JL)
- Move more commands as plugins on SD, refactor plugin wrapper (#276 by @Willy-JL)
- NFC:
- NDEF Parser:
- Mifare Classic support (#265 by @luu176), protocol-agnostic rewrite and more improvements (#265 by @Willy-JL)
@@ -132,6 +150,7 @@
- OFW: Moscow social card parser (by @assasinfil)
- OFW: Fixes and improvements to iso14443_4a listener and poller (by @RebornedBrain)
- OFW: Update BART station codes in Clipper plugin (by @ted-logan)
- OFW: Add Caltrain zones for Clipper parser (by @tomholford)
- Sub-GHz:
- UL: Frequency analyzer fixes and improvements (by @xMasterX):
- Enforce int module (like in OFW) usage due to lack of required hardware on external boards (PathIsolate (+rf switch for multiple paths)) and incorrect usage and/or understanding the purpose of frequency analyzer app by users, it should be used only to get frequency of the remote placed around 1-10cm around flipper's left corner
@@ -162,6 +181,7 @@
- OFW: Update and cleanup (by @rnadyrshin)
- OFW: Improve bit_buffer.h docs (by @Astrrra)
- OFW: Wi-Fi Devboard documentation rework (by @rnadyrshin)
- OFW: Update unit tests docs (by @portasynthinca3)
### Fixed:
- RFID:
@@ -173,6 +193,7 @@
- Sub-GHz:
- Fix GPS "Latitute" typo, switch to "Lat" and "Lon" in .sub files (#246 by @m7i-org)
- UL: Fix zero issues in Princeton (by @xMasterX)
- UL: Code cleanup and fix for rare dupicated "Data" field cases (by @xMasterX)
- Power: Suppress Shutdown on Idle While Charging / Plugged In (#244 by @luu176)
- Storage:
- Fallback SD format prompt when storage settings is unavailable (by @Willy-JL)
@@ -181,10 +202,10 @@
- RPC: Fixed apps not updating and staying at 100% (by @Willy-JL)
- OFW: Loader: Warn about missing SD card for main apps (by @Willy-JL)
- NFC:
- UL: Read Ultralight block by block (by @mishamyte)
- OFW: Fix crash on Ultralight unlock (by @Astrrra)
- OFW: FeliCa anti-collision fix (by @RebornedBrain)
- OFW: Emulation freeze fixed when pressing OK repeatedly (by @RebornedBrain)
- OFW: Fixed bug with reading pwd locked MFULs (by @mishamyte)
- OFW: RPC: Broken file interaction fixes (by @RebornedBrain)
- OFW: GPIO: Fix USB-UART bridge exit screen stopping the bridge prematurely (by @portasynthinca3)
- OFW: GUI: Fix dialog_ex NULL ptr crash (by @Willy-JL)
@@ -82,7 +82,7 @@ static void view_port_input_callback(InputEvent* input_event, void* context) {
furi_message_queue_put(app->input_queue, input_event, 0);
}
static bool input_queue_callback(FuriEventLoopObject* object, void* context) {
static void input_queue_callback(FuriEventLoopObject* object, void* context) {
FuriMessageQueue* queue = object;
EventLoopBlinkTestApp* app = context;
@@ -107,8 +107,6 @@ static bool input_queue_callback(FuriEventLoopObject* object, void* context) {
furi_event_loop_stop(app->event_loop);
}
}
return true;
}
static void blink_timer_callback(void* context) {
@@ -1,4 +1,15 @@
let tests = require("tests");
let flipper = require("flipper");
tests.assert_eq(1337, 1337);
tests.assert_eq("hello", "hello");
tests.assert_eq("compatible", sdkCompatibilityStatus(0, 1));
tests.assert_eq("firmwareTooOld", sdkCompatibilityStatus(100500, 0));
tests.assert_eq("firmwareTooNew", sdkCompatibilityStatus(-100500, 0));
tests.assert_eq(true, doesSdkSupport(["baseline"]));
tests.assert_eq(false, doesSdkSupport(["abobus", "other-nonexistent-feature"]));
tests.assert_eq("momentum", flipper.firmwareVendor);
tests.assert_eq(0, flipper.jsSdkVersion[0]);
tests.assert_eq(1, flipper.jsSdkVersion[1]);
@@ -1,205 +0,0 @@
#include "../test.h"
#include <furi.h>
#include <furi_hal.h>
#include <FreeRTOS.h>
#include <task.h>
#define TAG "TestFuriEventLoop"
#define EVENT_LOOP_EVENT_COUNT (256u)
typedef struct {
FuriMessageQueue* mq;
FuriEventLoop* producer_event_loop;
uint32_t producer_counter;
FuriEventLoop* consumer_event_loop;
uint32_t consumer_counter;
} TestFuriData;
bool test_furi_event_loop_producer_mq_callback(FuriEventLoopObject* object, void* context) {
furi_check(context);
TestFuriData* data = context;
furi_check(data->mq == object, "Invalid queue");
FURI_LOG_I(
TAG, "producer_mq_callback: %lu %lu", data->producer_counter, data->consumer_counter);
if(data->producer_counter == EVENT_LOOP_EVENT_COUNT / 2) {
furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
furi_event_loop_subscribe_message_queue(
data->producer_event_loop,
data->mq,
FuriEventLoopEventOut,
test_furi_event_loop_producer_mq_callback,
data);
}
if(data->producer_counter == EVENT_LOOP_EVENT_COUNT) {
furi_event_loop_stop(data->producer_event_loop);
return false;
}
data->producer_counter++;
furi_check(
furi_message_queue_put(data->mq, &data->producer_counter, 0) == FuriStatusOk,
"furi_message_queue_put failed");
furi_delay_us(furi_hal_random_get() % 1000);
return true;
}
int32_t test_furi_event_loop_producer(void* p) {
furi_check(p);
TestFuriData* data = p;
FURI_LOG_I(TAG, "producer start 1st run");
data->producer_event_loop = furi_event_loop_alloc();
furi_event_loop_subscribe_message_queue(
data->producer_event_loop,
data->mq,
FuriEventLoopEventOut,
test_furi_event_loop_producer_mq_callback,
data);
furi_event_loop_run(data->producer_event_loop);
// 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags
xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits);
furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
furi_event_loop_free(data->producer_event_loop);
FURI_LOG_I(TAG, "producer start 2nd run");
data->producer_counter = 0;
data->producer_event_loop = furi_event_loop_alloc();
furi_event_loop_subscribe_message_queue(
data->producer_event_loop,
data->mq,
FuriEventLoopEventOut,
test_furi_event_loop_producer_mq_callback,
data);
furi_event_loop_run(data->producer_event_loop);
furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
furi_event_loop_free(data->producer_event_loop);
FURI_LOG_I(TAG, "producer end");
return 0;
}
bool test_furi_event_loop_consumer_mq_callback(FuriEventLoopObject* object, void* context) {
furi_check(context);
TestFuriData* data = context;
furi_check(data->mq == object);
furi_delay_us(furi_hal_random_get() % 1000);
furi_check(furi_message_queue_get(data->mq, &data->consumer_counter, 0) == FuriStatusOk);
FURI_LOG_I(
TAG, "consumer_mq_callback: %lu %lu", data->producer_counter, data->consumer_counter);
if(data->consumer_counter == EVENT_LOOP_EVENT_COUNT / 2) {
furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
furi_event_loop_subscribe_message_queue(
data->consumer_event_loop,
data->mq,
FuriEventLoopEventIn,
test_furi_event_loop_consumer_mq_callback,
data);
}
if(data->consumer_counter == EVENT_LOOP_EVENT_COUNT) {
furi_event_loop_stop(data->consumer_event_loop);
return false;
}
return true;
}
int32_t test_furi_event_loop_consumer(void* p) {
furi_check(p);
TestFuriData* data = p;
FURI_LOG_I(TAG, "consumer start 1st run");
data->consumer_event_loop = furi_event_loop_alloc();
furi_event_loop_subscribe_message_queue(
data->consumer_event_loop,
data->mq,
FuriEventLoopEventIn,
test_furi_event_loop_consumer_mq_callback,
data);
furi_event_loop_run(data->consumer_event_loop);
// 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags
xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits);
furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
furi_event_loop_free(data->consumer_event_loop);
FURI_LOG_I(TAG, "consumer start 2nd run");
data->consumer_counter = 0;
data->consumer_event_loop = furi_event_loop_alloc();
furi_event_loop_subscribe_message_queue(
data->consumer_event_loop,
data->mq,
FuriEventLoopEventIn,
test_furi_event_loop_consumer_mq_callback,
data);
furi_event_loop_run(data->consumer_event_loop);
furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
furi_event_loop_free(data->consumer_event_loop);
FURI_LOG_I(TAG, "consumer end");
return 0;
}
void test_furi_event_loop(void) {
TestFuriData data = {};
data.mq = furi_message_queue_alloc(16, sizeof(uint32_t));
FuriThread* producer_thread = furi_thread_alloc();
furi_thread_set_name(producer_thread, "producer_thread");
furi_thread_set_stack_size(producer_thread, 1 * 1024);
furi_thread_set_callback(producer_thread, test_furi_event_loop_producer);
furi_thread_set_context(producer_thread, &data);
furi_thread_start(producer_thread);
FuriThread* consumer_thread = furi_thread_alloc();
furi_thread_set_name(consumer_thread, "consumer_thread");
furi_thread_set_stack_size(consumer_thread, 1 * 1024);
furi_thread_set_callback(consumer_thread, test_furi_event_loop_consumer);
furi_thread_set_context(consumer_thread, &data);
furi_thread_start(consumer_thread);
// Wait for thread to complete their tasks
furi_thread_join(producer_thread);
furi_thread_join(consumer_thread);
// The test itself
mu_assert_int_eq(data.producer_counter, data.consumer_counter);
mu_assert_int_eq(data.producer_counter, EVENT_LOOP_EVENT_COUNT);
// Release memory
furi_thread_free(consumer_thread);
furi_thread_free(producer_thread);
furi_message_queue_free(data.mq);
}
@@ -0,0 +1,490 @@
#include "../test.h"
#include <furi.h>
#include <furi_hal.h>
#include <FreeRTOS.h>
#include <task.h>
#define TAG "TestFuriEventLoop"
#define MESSAGE_COUNT (256UL)
#define EVENT_FLAG_COUNT (23UL)
#define PRIMITIVE_COUNT (4UL)
#define RUN_COUNT (2UL)
typedef struct {
FuriEventLoop* event_loop;
uint32_t message_queue_count;
uint32_t stream_buffer_count;
uint32_t event_flag_count;
uint32_t semaphore_count;
uint32_t primitives_tested;
} TestFuriEventLoopThread;
typedef struct {
FuriMessageQueue* message_queue;
FuriStreamBuffer* stream_buffer;
FuriEventFlag* event_flag;
FuriSemaphore* semaphore;
TestFuriEventLoopThread producer;
TestFuriEventLoopThread consumer;
} TestFuriEventLoopData;
static void test_furi_event_loop_pending_callback(void* context) {
furi_check(context);
TestFuriEventLoopThread* test_thread = context;
furi_check(test_thread->primitives_tested < PRIMITIVE_COUNT);
test_thread->primitives_tested++;
FURI_LOG_I(TAG, "primitives tested: %lu", test_thread->primitives_tested);
if(test_thread->primitives_tested == PRIMITIVE_COUNT) {
furi_event_loop_stop(test_thread->event_loop);
}
}
static void test_furi_event_loop_thread_init(TestFuriEventLoopThread* test_thread) {
memset(test_thread, 0, sizeof(TestFuriEventLoopThread));
test_thread->event_loop = furi_event_loop_alloc();
}
static void test_furi_event_loop_thread_run_and_cleanup(TestFuriEventLoopThread* test_thread) {
furi_event_loop_run(test_thread->event_loop);
// 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags
xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits);
furi_event_loop_free(test_thread->event_loop);
}
static void test_furi_event_loop_producer_message_queue_callback(
FuriEventLoopObject* object,
void* context) {
furi_check(context);
TestFuriEventLoopData* data = context;
furi_check(data->message_queue == object);
FURI_LOG_I(
TAG,
"producer MessageQueue: %lu %lu",
data->producer.message_queue_count,
data->consumer.message_queue_count);
if(data->producer.message_queue_count == MESSAGE_COUNT / 2) {
furi_event_loop_unsubscribe(data->producer.event_loop, data->message_queue);
furi_event_loop_subscribe_message_queue(
data->producer.event_loop,
data->message_queue,
FuriEventLoopEventOut,
test_furi_event_loop_producer_message_queue_callback,
data);
} else if(data->producer.message_queue_count == MESSAGE_COUNT) {
furi_event_loop_unsubscribe(data->producer.event_loop, data->message_queue);
furi_event_loop_pend_callback(
data->producer.event_loop, test_furi_event_loop_pending_callback, &data->producer);
return;
}
data->producer.message_queue_count++;
furi_check(
furi_message_queue_put(data->message_queue, &data->producer.message_queue_count, 0) ==
FuriStatusOk);
furi_delay_us(furi_hal_random_get() % 100);
}
static void test_furi_event_loop_producer_stream_buffer_callback(
FuriEventLoopObject* object,
void* context) {
furi_check(context);
TestFuriEventLoopData* data = context;
furi_check(data->stream_buffer == object);
TestFuriEventLoopThread* producer = &data->producer;
TestFuriEventLoopThread* consumer = &data->consumer;
FURI_LOG_I(
TAG,
"producer StreamBuffer: %lu %lu",
producer->stream_buffer_count,
consumer->stream_buffer_count);
if(producer->stream_buffer_count == MESSAGE_COUNT / 2) {
furi_event_loop_unsubscribe(producer->event_loop, data->stream_buffer);
furi_event_loop_subscribe_stream_buffer(
producer->event_loop,
data->stream_buffer,
FuriEventLoopEventOut,
test_furi_event_loop_producer_stream_buffer_callback,
data);
} else if(producer->stream_buffer_count == MESSAGE_COUNT) {
furi_event_loop_unsubscribe(producer->event_loop, data->stream_buffer);
furi_event_loop_pend_callback(
producer->event_loop, test_furi_event_loop_pending_callback, producer);
return;
}
producer->stream_buffer_count++;
furi_check(
furi_stream_buffer_send(
data->stream_buffer, &producer->stream_buffer_count, sizeof(uint32_t), 0) ==
sizeof(uint32_t));
furi_delay_us(furi_hal_random_get() % 100);
}
static void
test_furi_event_loop_producer_event_flag_callback(FuriEventLoopObject* object, void* context) {
furi_check(context);
TestFuriEventLoopData* data = context;
furi_check(data->event_flag == object);
const uint32_t producer_flags = (1UL << data->producer.event_flag_count);
const uint32_t consumer_flags = (1UL << data->consumer.event_flag_count);
FURI_LOG_I(TAG, "producer EventFlag: 0x%06lX 0x%06lX", producer_flags, consumer_flags);
furi_check(furi_event_flag_set(data->event_flag, producer_flags) & producer_flags);
if(data->producer.event_flag_count == EVENT_FLAG_COUNT / 2) {
furi_event_loop_unsubscribe(data->producer.event_loop, data->event_flag);
furi_event_loop_subscribe_event_flag(
data->producer.event_loop,
data->event_flag,
FuriEventLoopEventOut,
test_furi_event_loop_producer_event_flag_callback,
data);
} else if(data->producer.event_flag_count == EVENT_FLAG_COUNT) {
furi_event_loop_unsubscribe(data->producer.event_loop, data->event_flag);
furi_event_loop_pend_callback(
data->producer.event_loop, test_furi_event_loop_pending_callback, &data->producer);
return;
}
data->producer.event_flag_count++;
furi_delay_us(furi_hal_random_get() % 100);
}
static void
test_furi_event_loop_producer_semaphore_callback(FuriEventLoopObject* object, void* context) {
furi_check(context);
TestFuriEventLoopData* data = context;
furi_check(data->semaphore == object);
TestFuriEventLoopThread* producer = &data->producer;
TestFuriEventLoopThread* consumer = &data->consumer;
FURI_LOG_I(
TAG, "producer Semaphore: %lu %lu", producer->semaphore_count, consumer->semaphore_count);
furi_check(furi_semaphore_release(data->semaphore) == FuriStatusOk);
if(producer->semaphore_count == MESSAGE_COUNT / 2) {
furi_event_loop_unsubscribe(producer->event_loop, data->semaphore);
furi_event_loop_subscribe_semaphore(
producer->event_loop,
data->semaphore,
FuriEventLoopEventOut,
test_furi_event_loop_producer_semaphore_callback,
data);
} else if(producer->semaphore_count == MESSAGE_COUNT) {
furi_event_loop_unsubscribe(producer->event_loop, data->semaphore);
furi_event_loop_pend_callback(
producer->event_loop, test_furi_event_loop_pending_callback, producer);
return;
}
data->producer.semaphore_count++;
furi_delay_us(furi_hal_random_get() % 100);
}
static int32_t test_furi_event_loop_producer(void* p) {
furi_check(p);
TestFuriEventLoopData* data = p;
TestFuriEventLoopThread* producer = &data->producer;
for(uint32_t i = 0; i < RUN_COUNT; ++i) {
FURI_LOG_I(TAG, "producer start run %lu", i);
test_furi_event_loop_thread_init(producer);
furi_event_loop_subscribe_message_queue(
producer->event_loop,
data->message_queue,
FuriEventLoopEventOut,
test_furi_event_loop_producer_message_queue_callback,
data);
furi_event_loop_subscribe_stream_buffer(
producer->event_loop,
data->stream_buffer,
FuriEventLoopEventOut,
test_furi_event_loop_producer_stream_buffer_callback,
data);
furi_event_loop_subscribe_event_flag(
producer->event_loop,
data->event_flag,
FuriEventLoopEventOut,
test_furi_event_loop_producer_event_flag_callback,
data);
furi_event_loop_subscribe_semaphore(
producer->event_loop,
data->semaphore,
FuriEventLoopEventOut,
test_furi_event_loop_producer_semaphore_callback,
data);
test_furi_event_loop_thread_run_and_cleanup(producer);
}
FURI_LOG_I(TAG, "producer end");
return 0;
}
static void test_furi_event_loop_consumer_message_queue_callback(
FuriEventLoopObject* object,
void* context) {
furi_check(context);
TestFuriEventLoopData* data = context;
furi_check(data->message_queue == object);
furi_delay_us(furi_hal_random_get() % 100);
furi_check(
furi_message_queue_get(data->message_queue, &data->consumer.message_queue_count, 0) ==
FuriStatusOk);
FURI_LOG_I(
TAG,
"consumer MessageQueue: %lu %lu",
data->producer.message_queue_count,
data->consumer.message_queue_count);
if(data->consumer.message_queue_count == MESSAGE_COUNT / 2) {
furi_event_loop_unsubscribe(data->consumer.event_loop, data->message_queue);
furi_event_loop_subscribe_message_queue(
data->consumer.event_loop,
data->message_queue,
FuriEventLoopEventIn,
test_furi_event_loop_consumer_message_queue_callback,
data);
} else if(data->consumer.message_queue_count == MESSAGE_COUNT) {
furi_event_loop_unsubscribe(data->consumer.event_loop, data->message_queue);
furi_event_loop_pend_callback(
data->consumer.event_loop, test_furi_event_loop_pending_callback, &data->consumer);
}
}
static void test_furi_event_loop_consumer_stream_buffer_callback(
FuriEventLoopObject* object,
void* context) {
furi_check(context);
TestFuriEventLoopData* data = context;
furi_check(data->stream_buffer == object);
TestFuriEventLoopThread* producer = &data->producer;
TestFuriEventLoopThread* consumer = &data->consumer;
furi_delay_us(furi_hal_random_get() % 100);
furi_check(
furi_stream_buffer_receive(
data->stream_buffer, &consumer->stream_buffer_count, sizeof(uint32_t), 0) ==
sizeof(uint32_t));
FURI_LOG_I(
TAG,
"consumer StreamBuffer: %lu %lu",
producer->stream_buffer_count,
consumer->stream_buffer_count);
if(consumer->stream_buffer_count == MESSAGE_COUNT / 2) {
furi_event_loop_unsubscribe(consumer->event_loop, data->stream_buffer);
furi_event_loop_subscribe_stream_buffer(
consumer->event_loop,
data->stream_buffer,
FuriEventLoopEventIn,
test_furi_event_loop_consumer_stream_buffer_callback,
data);
} else if(consumer->stream_buffer_count == MESSAGE_COUNT) {
furi_event_loop_unsubscribe(data->consumer.event_loop, data->stream_buffer);
furi_event_loop_pend_callback(
consumer->event_loop, test_furi_event_loop_pending_callback, consumer);
}
}
static void
test_furi_event_loop_consumer_event_flag_callback(FuriEventLoopObject* object, void* context) {
furi_check(context);
TestFuriEventLoopData* data = context;
furi_check(data->event_flag == object);
furi_delay_us(furi_hal_random_get() % 100);
const uint32_t producer_flags = (1UL << data->producer.event_flag_count);
const uint32_t consumer_flags = (1UL << data->consumer.event_flag_count);
furi_check(
furi_event_flag_wait(data->event_flag, consumer_flags, FuriFlagWaitAny, 0) &
consumer_flags);
FURI_LOG_I(TAG, "consumer EventFlag: 0x%06lX 0x%06lX", producer_flags, consumer_flags);
if(data->consumer.event_flag_count == EVENT_FLAG_COUNT / 2) {
furi_event_loop_unsubscribe(data->consumer.event_loop, data->event_flag);
furi_event_loop_subscribe_event_flag(
data->consumer.event_loop,
data->event_flag,
FuriEventLoopEventIn,
test_furi_event_loop_consumer_event_flag_callback,
data);
} else if(data->consumer.event_flag_count == EVENT_FLAG_COUNT) {
furi_event_loop_unsubscribe(data->consumer.event_loop, data->event_flag);
furi_event_loop_pend_callback(
data->consumer.event_loop, test_furi_event_loop_pending_callback, &data->consumer);
return;
}
data->consumer.event_flag_count++;
}
static void
test_furi_event_loop_consumer_semaphore_callback(FuriEventLoopObject* object, void* context) {
furi_check(context);
TestFuriEventLoopData* data = context;
furi_check(data->semaphore == object);
furi_delay_us(furi_hal_random_get() % 100);
TestFuriEventLoopThread* producer = &data->producer;
TestFuriEventLoopThread* consumer = &data->consumer;
furi_check(furi_semaphore_acquire(data->semaphore, 0) == FuriStatusOk);
FURI_LOG_I(
TAG, "consumer Semaphore: %lu %lu", producer->semaphore_count, consumer->semaphore_count);
if(consumer->semaphore_count == MESSAGE_COUNT / 2) {
furi_event_loop_unsubscribe(consumer->event_loop, data->semaphore);
furi_event_loop_subscribe_semaphore(
consumer->event_loop,
data->semaphore,
FuriEventLoopEventIn,
test_furi_event_loop_consumer_semaphore_callback,
data);
} else if(consumer->semaphore_count == MESSAGE_COUNT) {
furi_event_loop_unsubscribe(consumer->event_loop, data->semaphore);
furi_event_loop_pend_callback(
consumer->event_loop, test_furi_event_loop_pending_callback, consumer);
return;
}
data->consumer.semaphore_count++;
}
static int32_t test_furi_event_loop_consumer(void* p) {
furi_check(p);
TestFuriEventLoopData* data = p;
TestFuriEventLoopThread* consumer = &data->consumer;
for(uint32_t i = 0; i < RUN_COUNT; ++i) {
FURI_LOG_I(TAG, "consumer start run %lu", i);
test_furi_event_loop_thread_init(consumer);
furi_event_loop_subscribe_message_queue(
consumer->event_loop,
data->message_queue,
FuriEventLoopEventIn,
test_furi_event_loop_consumer_message_queue_callback,
data);
furi_event_loop_subscribe_stream_buffer(
consumer->event_loop,
data->stream_buffer,
FuriEventLoopEventIn,
test_furi_event_loop_consumer_stream_buffer_callback,
data);
furi_event_loop_subscribe_event_flag(
consumer->event_loop,
data->event_flag,
FuriEventLoopEventIn,
test_furi_event_loop_consumer_event_flag_callback,
data);
furi_event_loop_subscribe_semaphore(
consumer->event_loop,
data->semaphore,
FuriEventLoopEventIn,
test_furi_event_loop_consumer_semaphore_callback,
data);
test_furi_event_loop_thread_run_and_cleanup(consumer);
}
FURI_LOG_I(TAG, "consumer end");
return 0;
}
void test_furi_event_loop(void) {
TestFuriEventLoopData data = {};
data.message_queue = furi_message_queue_alloc(16, sizeof(uint32_t));
data.stream_buffer = furi_stream_buffer_alloc(16, sizeof(uint32_t));
data.event_flag = furi_event_flag_alloc();
data.semaphore = furi_semaphore_alloc(8, 0);
FuriThread* producer_thread =
furi_thread_alloc_ex("producer_thread", 1 * 1024, test_furi_event_loop_producer, &data);
furi_thread_start(producer_thread);
FuriThread* consumer_thread =
furi_thread_alloc_ex("consumer_thread", 1 * 1024, test_furi_event_loop_consumer, &data);
furi_thread_start(consumer_thread);
// Wait for thread to complete their tasks
furi_thread_join(producer_thread);
furi_thread_join(consumer_thread);
TestFuriEventLoopThread* producer = &data.producer;
TestFuriEventLoopThread* consumer = &data.consumer;
// The test itself
mu_assert_int_eq(producer->message_queue_count, consumer->message_queue_count);
mu_assert_int_eq(producer->message_queue_count, MESSAGE_COUNT);
mu_assert_int_eq(producer->stream_buffer_count, consumer->stream_buffer_count);
mu_assert_int_eq(producer->stream_buffer_count, MESSAGE_COUNT);
mu_assert_int_eq(producer->event_flag_count, consumer->event_flag_count);
mu_assert_int_eq(producer->event_flag_count, EVENT_FLAG_COUNT);
mu_assert_int_eq(producer->semaphore_count, consumer->semaphore_count);
mu_assert_int_eq(producer->semaphore_count, MESSAGE_COUNT);
// Release memory
furi_thread_free(consumer_thread);
furi_thread_free(producer_thread);
furi_message_queue_free(data.message_queue);
furi_stream_buffer_free(data.stream_buffer);
furi_event_flag_free(data.event_flag);
furi_semaphore_free(data.semaphore);
}
@@ -0,0 +1,103 @@
#include <furi.h>
#include "../test.h" // IWYU pragma: keep
#define MESSAGE_QUEUE_CAPACITY (16U)
#define MESSAGE_QUEUE_ELEMENT_SIZE (sizeof(uint32_t))
#define STREAM_BUFFER_SIZE (32U)
#define STREAM_BUFFER_TRG_LEVEL (STREAM_BUFFER_SIZE / 2U)
typedef struct {
FuriMessageQueue* message_queue;
FuriStreamBuffer* stream_buffer;
} TestFuriPrimitivesData;
static void test_furi_message_queue(TestFuriPrimitivesData* data) {
FuriMessageQueue* message_queue = data->message_queue;
mu_assert_int_eq(0, furi_message_queue_get_count(message_queue));
mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY, furi_message_queue_get_space(message_queue));
mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY, furi_message_queue_get_capacity(message_queue));
mu_assert_int_eq(
MESSAGE_QUEUE_ELEMENT_SIZE, furi_message_queue_get_message_size(message_queue));
for(uint32_t i = 0;; ++i) {
mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY - i, furi_message_queue_get_space(message_queue));
mu_assert_int_eq(i, furi_message_queue_get_count(message_queue));
if(furi_message_queue_put(message_queue, &i, 0) != FuriStatusOk) {
break;
}
}
mu_assert_int_eq(0, furi_message_queue_get_space(message_queue));
mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY, furi_message_queue_get_count(message_queue));
for(uint32_t i = 0;; ++i) {
mu_assert_int_eq(i, furi_message_queue_get_space(message_queue));
mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY - i, furi_message_queue_get_count(message_queue));
uint32_t value;
if(furi_message_queue_get(message_queue, &value, 0) != FuriStatusOk) {
break;
}
mu_assert_int_eq(i, value);
}
mu_assert_int_eq(0, furi_message_queue_get_count(message_queue));
mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY, furi_message_queue_get_space(message_queue));
}
static void test_furi_stream_buffer(TestFuriPrimitivesData* data) {
FuriStreamBuffer* stream_buffer = data->stream_buffer;
mu_assert(furi_stream_buffer_is_empty(stream_buffer), "Must be empty");
mu_assert(!furi_stream_buffer_is_full(stream_buffer), "Must be not full");
mu_assert_int_eq(0, furi_stream_buffer_bytes_available(stream_buffer));
mu_assert_int_eq(STREAM_BUFFER_SIZE, furi_stream_buffer_spaces_available(stream_buffer));
for(uint8_t i = 0;; ++i) {
mu_assert_int_eq(i, furi_stream_buffer_bytes_available(stream_buffer));
mu_assert_int_eq(
STREAM_BUFFER_SIZE - i, furi_stream_buffer_spaces_available(stream_buffer));
if(furi_stream_buffer_send(stream_buffer, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
break;
}
}
mu_assert(!furi_stream_buffer_is_empty(stream_buffer), "Must be not empty");
mu_assert(furi_stream_buffer_is_full(stream_buffer), "Must be full");
mu_assert_int_eq(STREAM_BUFFER_SIZE, furi_stream_buffer_bytes_available(stream_buffer));
mu_assert_int_eq(0, furi_stream_buffer_spaces_available(stream_buffer));
for(uint8_t i = 0;; ++i) {
mu_assert_int_eq(
STREAM_BUFFER_SIZE - i, furi_stream_buffer_bytes_available(stream_buffer));
mu_assert_int_eq(i, furi_stream_buffer_spaces_available(stream_buffer));
uint8_t value;
if(furi_stream_buffer_receive(stream_buffer, &value, sizeof(uint8_t), 0) !=
sizeof(uint8_t)) {
break;
}
mu_assert_int_eq(i, value);
}
}
// This is a stub that needs expanding
void test_furi_primitives(void) {
TestFuriPrimitivesData data = {
.message_queue =
furi_message_queue_alloc(MESSAGE_QUEUE_CAPACITY, MESSAGE_QUEUE_ELEMENT_SIZE),
.stream_buffer = furi_stream_buffer_alloc(STREAM_BUFFER_SIZE, STREAM_BUFFER_TRG_LEVEL),
};
test_furi_message_queue(&data);
test_furi_stream_buffer(&data);
furi_message_queue_free(data.message_queue);
furi_stream_buffer_free(data.stream_buffer);
}
@@ -9,6 +9,7 @@ void test_furi_pubsub(void);
void test_furi_memmgr(void);
void test_furi_event_loop(void);
void test_errno_saving(void);
void test_furi_primitives(void);
static int foo = 0;
@@ -47,6 +48,10 @@ MU_TEST(mu_test_errno_saving) {
test_errno_saving();
}
MU_TEST(mu_test_furi_primitives) {
test_furi_primitives();
}
MU_TEST_SUITE(test_suite) {
MU_SUITE_CONFIGURE(&test_setup, &test_teardown);
MU_RUN_TEST(test_check);
@@ -57,6 +62,7 @@ MU_TEST_SUITE(test_suite) {
MU_RUN_TEST(mu_test_furi_memmgr);
MU_RUN_TEST(mu_test_furi_event_loop);
MU_RUN_TEST(mu_test_errno_saving);
MU_RUN_TEST(mu_test_furi_primitives);
}
int run_minunit_test_furi(void) {
@@ -1,3 +1,12 @@
App(
appid="example_event_loop_event_flags",
name="Example: Event Loop Event Flags",
apptype=FlipperAppType.EXTERNAL,
sources=["example_event_loop_event_flags.c"],
entry_point="example_event_loop_event_flags_app",
fap_category="Examples",
)
App(
appid="example_event_loop_timer",
name="Example: Event Loop Timer",
@@ -0,0 +1,173 @@
/**
* @file example_event_loop_event_flags.c
* @brief Example application demonstrating the use of the FuriEventFlag primitive in FuriEventLoop instances.
*
* This application receives keystrokes from the input service and sets the appropriate flags,
* which are subsequently processed in the event loop
*/
#include <furi.h>
#include <gui/gui.h>
#include <gui/view_port.h>
#include <furi_hal_random.h>
#define TAG "ExampleEventLoopEventFlags"
typedef struct {
Gui* gui;
ViewPort* view_port;
FuriEventLoop* event_loop;
FuriEventFlag* event_flag;
} EventLoopEventFlagsApp;
typedef enum {
EventLoopEventFlagsOk = (1 << 0),
EventLoopEventFlagsUp = (1 << 1),
EventLoopEventFlagsDown = (1 << 2),
EventLoopEventFlagsLeft = (1 << 3),
EventLoopEventFlagsRight = (1 << 4),
EventLoopEventFlagsBack = (1 << 5),
EventLoopEventFlagsExit = (1 << 6),
} EventLoopEventFlags;
#define EVENT_LOOP_EVENT_FLAGS_MASK \
(EventLoopEventFlagsOk | EventLoopEventFlagsUp | EventLoopEventFlagsDown | \
EventLoopEventFlagsLeft | EventLoopEventFlagsRight | EventLoopEventFlagsBack | \
EventLoopEventFlagsExit)
// This function is executed in the GUI context each time an input event occurs (e.g. the user pressed a key)
static void event_loop_event_flags_app_input_callback(InputEvent* event, void* context) {
furi_assert(context);
EventLoopEventFlagsApp* app = context;
UNUSED(app);
if(event->type == InputTypePress) {
if(event->key == InputKeyOk) {
furi_event_flag_set(app->event_flag, EventLoopEventFlagsOk);
} else if(event->key == InputKeyUp) {
furi_event_flag_set(app->event_flag, EventLoopEventFlagsUp);
} else if(event->key == InputKeyDown) {
furi_event_flag_set(app->event_flag, EventLoopEventFlagsDown);
} else if(event->key == InputKeyLeft) {
furi_event_flag_set(app->event_flag, EventLoopEventFlagsLeft);
} else if(event->key == InputKeyRight) {
furi_event_flag_set(app->event_flag, EventLoopEventFlagsRight);
} else if(event->key == InputKeyBack) {
furi_event_flag_set(app->event_flag, EventLoopEventFlagsBack);
}
} else if(event->type == InputTypeLong) {
if(event->key == InputKeyBack) {
furi_event_flag_set(app->event_flag, EventLoopEventFlagsExit);
}
}
}
// This function is executed each time a new event flag is inserted in the input event flag.
static void
event_loop_event_flags_app_event_flags_callback(FuriEventLoopObject* object, void* context) {
furi_assert(context);
EventLoopEventFlagsApp* app = context;
furi_assert(object == app->event_flag);
EventLoopEventFlags events =
furi_event_flag_wait(app->event_flag, EVENT_LOOP_EVENT_FLAGS_MASK, FuriFlagWaitAny, 0);
furi_check((events) != 0);
if(events & EventLoopEventFlagsOk) {
FURI_LOG_I(TAG, "Press \"Ok\"");
}
if(events & EventLoopEventFlagsUp) {
FURI_LOG_I(TAG, "Press \"Up\"");
}
if(events & EventLoopEventFlagsDown) {
FURI_LOG_I(TAG, "Press \"Down\"");
}
if(events & EventLoopEventFlagsLeft) {
FURI_LOG_I(TAG, "Press \"Left\"");
}
if(events & EventLoopEventFlagsRight) {
FURI_LOG_I(TAG, "Press \"Right\"");
}
if(events & EventLoopEventFlagsBack) {
FURI_LOG_I(TAG, "Press \"Back\"");
}
if(events & EventLoopEventFlagsExit) {
FURI_LOG_I(TAG, "Exit App");
furi_event_loop_stop(app->event_loop);
}
}
static EventLoopEventFlagsApp* event_loop_event_flags_app_alloc(void) {
EventLoopEventFlagsApp* app = malloc(sizeof(EventLoopEventFlagsApp));
// Create event loop instances.
app->event_loop = furi_event_loop_alloc();
// Create event flag instances.
app->event_flag = furi_event_flag_alloc();
// Create GUI instance.
app->gui = furi_record_open(RECORD_GUI);
app->view_port = view_port_alloc();
// Gain exclusive access to the input events
view_port_input_callback_set(app->view_port, event_loop_event_flags_app_input_callback, app);
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
// Notify the event loop about incoming messages in the event flag
furi_event_loop_subscribe_event_flag(
app->event_loop,
app->event_flag,
FuriEventLoopEventIn | FuriEventLoopEventFlagEdge,
event_loop_event_flags_app_event_flags_callback,
app);
return app;
}
static void event_loop_event_flags_app_free(EventLoopEventFlagsApp* app) {
gui_remove_view_port(app->gui, app->view_port);
furi_record_close(RECORD_GUI);
app->gui = NULL;
// Delete all instances
view_port_free(app->view_port);
app->view_port = NULL;
// IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
// Failure to do so will result in a crash.
furi_event_loop_unsubscribe(app->event_loop, app->event_flag);
furi_event_flag_free(app->event_flag);
app->event_flag = NULL;
furi_event_loop_free(app->event_loop);
app->event_loop = NULL;
free(app);
}
static void event_loop_event_flags_app_run(EventLoopEventFlagsApp* app) {
FURI_LOG_I(TAG, "Press keys to see them printed here.");
FURI_LOG_I(TAG, "Quickly press different keys to generate events.");
FURI_LOG_I(TAG, "Long press \"Back\" to exit app.");
// Run the application event loop. This call will block until the application is about to exit.
furi_event_loop_run(app->event_loop);
}
/*******************************************************************
* vvv START HERE vvv
*
* The application's entry point - referenced in application.fam
*******************************************************************/
int32_t example_event_loop_event_flags_app(void* arg) {
UNUSED(arg);
EventLoopEventFlagsApp* app = event_loop_event_flags_app_alloc();
event_loop_event_flags_app_run(app);
event_loop_event_flags_app_free(app);
return 0;
}
@@ -52,7 +52,7 @@ typedef struct {
*/
// This function is executed each time the data is taken out of the stream buffer. It is used to restart the worker timer.
static bool
static void
event_loop_multi_app_stream_buffer_worker_callback(FuriEventLoopObject* object, void* context) {
furi_assert(context);
EventLoopMultiAppWorker* worker = context;
@@ -62,8 +62,6 @@ static bool
FURI_LOG_I(TAG, "Data was removed from buffer");
// Restart the timer to generate another block of random data.
furi_event_loop_timer_start(worker->timer, WORKER_DATA_INTERVAL_MS);
return true;
}
// This function is executed when the worker timer expires. The timer will NOT restart automatically
@@ -152,7 +150,7 @@ static void event_loop_multi_app_input_callback(InputEvent* event, void* context
}
// This function is executed each time new data is available in the stream buffer.
static bool
static void
event_loop_multi_app_stream_buffer_callback(FuriEventLoopObject* object, void* context) {
furi_assert(context);
EventLoopMultiApp* app = context;
@@ -172,12 +170,10 @@ static bool
FURI_LOG_I(TAG, "Received data: %s", furi_string_get_cstr(tmp_str));
furi_string_free(tmp_str);
return true;
}
// This function is executed each time a new message is inserted in the input queue.
static bool event_loop_multi_app_input_queue_callback(FuriEventLoopObject* object, void* context) {
static void event_loop_multi_app_input_queue_callback(FuriEventLoopObject* object, void* context) {
furi_assert(context);
EventLoopMultiApp* app = context;
@@ -222,8 +218,6 @@ static bool event_loop_multi_app_input_queue_callback(FuriEventLoopObject* objec
// Not a long press, just print the key's name.
FURI_LOG_I(TAG, "Short press: %s", input_get_key_name(event.key));
}
return true;
}
// This function is executed each time the countdown timer expires.
@@ -59,7 +59,7 @@ static int32_t event_loop_mutex_app_worker_thread(void* context) {
}
// This function is being run each time when the mutex gets released
static bool event_loop_mutex_app_event_callback(FuriEventLoopObject* object, void* context) {
static void event_loop_mutex_app_event_callback(FuriEventLoopObject* object, void* context) {
furi_assert(context);
EventLoopMutexApp* app = context;
@@ -82,8 +82,6 @@ static bool event_loop_mutex_app_event_callback(FuriEventLoopObject* object, voi
MUTEX_EVENT_AND_FLAGS,
event_loop_mutex_app_event_callback,
app);
return true;
}
static EventLoopMutexApp* event_loop_mutex_app_alloc(void) {
@@ -54,7 +54,7 @@ static int32_t event_loop_stream_buffer_app_worker_thread(void* context) {
}
// This function is being run each time when the number of bytes in the buffer is above its trigger level.
static bool
static void
event_loop_stream_buffer_app_event_callback(FuriEventLoopObject* object, void* context) {
furi_assert(context);
EventLoopStreamBufferApp* app = context;
@@ -76,8 +76,6 @@ static bool
FURI_LOG_I(TAG, "Received data: %s", furi_string_get_cstr(tmp_str));
furi_string_free(tmp_str);
return true;
}
static EventLoopStreamBufferApp* event_loop_stream_buffer_app_alloc(void) {
+1 -1
View File
@@ -25,6 +25,6 @@ App(
apptype=FlipperAppType.STARTUP,
targets=["f7"],
entry_point="ibutton_on_system_start",
sources=["ibutton_start.c"],
sources=["ibutton_cli.c"],
order=60,
)
+16 -13
View File
@@ -8,6 +8,22 @@
#include <ibutton/ibutton_worker.h>
#include <ibutton/ibutton_protocols.h>
static void ibutton_cli(Cli* cli, FuriString* args, void* context);
#include <cli/cli_i.h>
CLI_PLUGIN_WRAPPER("ibutton", ibutton_cli)
// app cli function
void ibutton_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "ikey", CliCommandFlagDefault, ibutton_cli_wrapper, cli);
furi_record_close(RECORD_CLI);
#else
UNUSED(ibutton_cli);
#endif
}
static void ibutton_cli_print_usage(void) {
printf("Usage:\r\n");
printf("ikey read\r\n");
@@ -239,16 +255,3 @@ void ibutton_cli(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd);
}
#include <flipper_application/flipper_application.h>
#include <cli/cli_i.h>
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = CLI_PLUGIN_APP_ID,
.ep_api_version = CLI_PLUGIN_API_VERSION,
.entry_point = &ibutton_cli,
};
const FlipperAppPluginDescriptor* ibutton_cli_plugin_ep(void) {
return &plugin_descriptor;
}
-11
View File
@@ -1,11 +0,0 @@
#include <cli/cli_i.h>
static void ibutton_cli_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("ibutton", cli, args, context);
}
void ibutton_on_system_start(void) {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "ikey", CliCommandFlagDefault, ibutton_cli_wrapper, cli);
furi_record_close(RECORD_CLI);
}
+2 -2
View File
@@ -17,7 +17,7 @@ App(
appid="infrared_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="infrared_cli_plugin_ep",
entry_point="infrared_cli_start_ir_plugin_ep",
requires=["cli"],
sources=[
"infrared_cli.c",
@@ -31,6 +31,6 @@ App(
apptype=FlipperAppType.STARTUP,
targets=["f7"],
entry_point="infrared_on_system_start",
sources=["infrared_start.c"],
sources=["infrared_cli.c"],
order=20,
)
+9 -9
View File
@@ -555,15 +555,15 @@ static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) {
furi_string_free(command);
}
#include <flipper_application/flipper_application.h>
#include <cli/cli_i.h>
CLI_PLUGIN_WRAPPER("infrared", infrared_cli_start_ir)
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = CLI_PLUGIN_APP_ID,
.ep_api_version = CLI_PLUGIN_API_VERSION,
.entry_point = &infrared_cli_start_ir,
};
const FlipperAppPluginDescriptor* infrared_cli_plugin_ep(void) {
return &plugin_descriptor;
void infrared_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = (Cli*)furi_record_open(RECORD_CLI);
cli_add_command(cli, "ir", CliCommandFlagDefault, infrared_cli_start_ir_wrapper, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(infrared_cli_start_ir);
#endif
}
@@ -1,11 +0,0 @@
#include <cli/cli_i.h>
static void infrared_cli_start_ir_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("infrared", cli, args, context);
}
void infrared_on_system_start(void) {
Cli* cli = (Cli*)furi_record_open(RECORD_CLI);
cli_add_command(cli, "ir", CliCommandFlagDefault, infrared_cli_start_ir_wrapper, NULL);
furi_record_close(RECORD_CLI);
}
+1 -1
View File
@@ -25,6 +25,6 @@ App(
targets=["f7"],
apptype=FlipperAppType.STARTUP,
entry_point="lfrfid_on_system_start",
sources=["lfrfid_start.c"],
sources=["lfrfid_cli.c"],
order=50,
)
+12 -13
View File
@@ -14,6 +14,18 @@
#include <lfrfid/lfrfid_raw_file.h>
#include <toolbox/pulse_protocols/pulse_glue.h>
static void lfrfid_cli(Cli* cli, FuriString* args, void* context);
#include <cli/cli_i.h>
CLI_PLUGIN_WRAPPER("lfrfid", lfrfid_cli)
// app cli function
void lfrfid_on_system_start(void) {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "rfid", CliCommandFlagDefault, lfrfid_cli_wrapper, NULL);
furi_record_close(RECORD_CLI);
}
static void lfrfid_cli_print_usage(void) {
printf("Usage:\r\n");
printf("rfid read <optional: normal | indala> - read in ASK/PSK mode\r\n");
@@ -568,16 +580,3 @@ static void lfrfid_cli(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd);
}
#include <flipper_application/flipper_application.h>
#include <cli/cli_i.h>
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = CLI_PLUGIN_APP_ID,
.ep_api_version = CLI_PLUGIN_API_VERSION,
.entry_point = &lfrfid_cli,
};
const FlipperAppPluginDescriptor* lfrfid_cli_plugin_ep(void) {
return &plugin_descriptor;
}
-11
View File
@@ -1,11 +0,0 @@
#include <cli/cli_i.h>
static void lfrfid_cli_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("lfrfid", cli, args, context);
}
void lfrfid_on_system_start(void) {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "rfid", CliCommandFlagDefault, lfrfid_cli_wrapper, NULL);
furi_record_close(RECORD_CLI);
}
+1 -1
View File
@@ -352,6 +352,6 @@ App(
targets=["f7"],
apptype=FlipperAppType.STARTUP,
entry_point="nfc_on_system_start",
sources=["nfc_start.c"],
sources=["nfc_cli.c"],
order=30,
)
+9 -9
View File
@@ -63,15 +63,15 @@ static void nfc_cli(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd);
}
#include <flipper_application/flipper_application.h>
#include <cli/cli_i.h>
CLI_PLUGIN_WRAPPER("nfc", nfc_cli)
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = CLI_PLUGIN_APP_ID,
.ep_api_version = CLI_PLUGIN_API_VERSION,
.entry_point = &nfc_cli,
};
const FlipperAppPluginDescriptor* nfc_cli_plugin_ep(void) {
return &plugin_descriptor;
void nfc_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "nfc", CliCommandFlagDefault, nfc_cli_wrapper, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(nfc_cli);
#endif
}
-11
View File
@@ -1,11 +0,0 @@
#include <cli/cli_i.h>
static void nfc_cli_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("nfc", cli, args, context);
}
void nfc_on_system_start(void) {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "nfc", CliCommandFlagDefault, nfc_cli_wrapper, NULL);
furi_record_close(RECORD_CLI);
}
@@ -140,6 +140,19 @@ static const IdMapping actransit_zones[] = {
};
static const size_t kNumACTransitZones = COUNT(actransit_zones);
// Instead of persisting individual Station IDs, Caltrain saves Zone numbers.
// https://www.caltrain.com/stations-zones
static const IdMapping caltrain_zones[] = {
{.id = 0x0001, .name = "Zone 1"},
{.id = 0x0002, .name = "Zone 2"},
{.id = 0x0003, .name = "Zone 3"},
{.id = 0x0004, .name = "Zone 4"},
{.id = 0x0005, .name = "Zone 5"},
{.id = 0x0006, .name = "Zone 6"},
};
static const size_t kNumCaltrainZones = COUNT(caltrain_zones);
//
// Full agency+zone mapping.
//
@@ -150,6 +163,7 @@ static const struct {
} agency_zone_map[] = {
{.agency_id = 0x0001, .zone_map = actransit_zones, .zone_count = kNumACTransitZones},
{.agency_id = 0x0004, .zone_map = bart_zones, .zone_count = kNumBARTZones},
{.agency_id = 0x0006, .zone_map = caltrain_zones, .zone_count = kNumCaltrainZones},
{.agency_id = 0x0012, .zone_map = muni_zones, .zone_count = kNumMUNIZones}};
static const size_t kNumAgencyZoneMaps = COUNT(agency_zone_map);
@@ -582,7 +582,7 @@ bool ndef_parse_record(
NdefTnf tnf,
const char* type,
uint8_t type_len) {
FURI_LOG_D(TAG, "payload type: %.*s len: %d", type_len, type, len);
FURI_LOG_D(TAG, "payload type: %.*s len: %hu", type_len, type, len);
if(!len) {
furi_string_cat(ndef->output, "Empty\n");
return true;
@@ -702,9 +702,9 @@ bool ndef_parse_message(Ndef* ndef, size_t pos, size_t len, size_t message_num,
pos += id_len;
if(smart_poster) {
furi_string_cat_printf(ndef->output, "\e*> SP-R%d: ", record_num);
furi_string_cat_printf(ndef->output, "\e*> SP-R%zu: ", record_num);
} else {
furi_string_cat_printf(ndef->output, "\e*> M%d-R%d: ", message_num, record_num);
furi_string_cat_printf(ndef->output, "\e*> M%zu-R%zu: ", message_num, record_num);
}
if(!ndef_parse_record(ndef, pos, payload_len, flags_tnf.type_name_format, type, type_len)) {
if(type_was_allocated) free(type);
@@ -721,7 +721,7 @@ bool ndef_parse_message(Ndef* ndef, size_t pos, size_t len, size_t message_num,
if(smart_poster) {
furi_string_cat(ndef->output, "\e*> SP: Empty\n\n");
} else {
furi_string_cat_printf(ndef->output, "\e*> M%d: Empty\n\n", message_num);
furi_string_cat_printf(ndef->output, "\e*> M%zu: Empty\n\n", message_num);
}
}
@@ -949,7 +949,7 @@ static bool ndef_mfc_parse(const NfcDevice* device, FuriString* parsed_data) {
} else {
data_block = 93 + (sector - 32) * 15;
}
FURI_LOG_D(TAG, "data_block: %d", data_block);
FURI_LOG_D(TAG, "data_block: %zu", data_block);
size_t data_start = data_block * MF_CLASSIC_BLOCK_SIZE;
size_t parsed = ndef_parse_tlv(&ndef, data_start, data_size - data_start, total_parsed);
@@ -982,7 +982,7 @@ static bool ndef_slix_parse(const NfcDevice* device, FuriString* parsed_data) {
const uint16_t block_count = iso15693_3_get_block_count(data);
const uint8_t* blocks = simple_array_cget_data(data->block_data);
// TODO: Find some way to check for other iso15693 NDEF cards and
// TODO(-nofl): Find some way to check for other iso15693 NDEF cards and
// split this to also support non-slix iso15693 NDEF tags
// Rest of the code works on iso15693 too, but uses SLIX layout assumptions
if(block_size != SLIX_BLOCK_SIZE) {
@@ -5,8 +5,8 @@
#define TAG "NfcMfClassicDictAttack"
// TODO: Fix lag when leaving the dictionary attack view after Hardnested
// TODO: Re-enters backdoor detection between user and system dictionary if no backdoor is found
// TODO FL-3926: Fix lag when leaving the dictionary attack view after Hardnested
// TODO FL-3926: Re-enters backdoor detection between user and system dictionary if no backdoor is found
typedef enum {
DictAttackStateCUIDDictInProgress,
+1 -1
View File
@@ -11,6 +11,6 @@ App(
appid="onewire_start",
apptype=FlipperAppType.STARTUP,
entry_point="onewire_on_system_start",
sources=["onewire_start.c"],
sources=["onewire_cli.c"],
order=60,
)
+15 -13
View File
@@ -6,6 +6,21 @@
#include <one_wire/one_wire_host.h>
static void onewire_cli(Cli* cli, FuriString* args, void* context);
#include <cli/cli_i.h>
CLI_PLUGIN_WRAPPER("onewire", onewire_cli)
void onewire_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "onewire", CliCommandFlagDefault, onewire_cli_wrapper, cli);
furi_record_close(RECORD_CLI);
#else
UNUSED(onewire_cli);
#endif
}
static void onewire_cli_print_usage(void) {
printf("Usage:\r\n");
printf("onewire search\r\n");
@@ -58,16 +73,3 @@ void onewire_cli(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd);
}
#include <flipper_application/flipper_application.h>
#include <cli/cli_i.h>
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = CLI_PLUGIN_APP_ID,
.ep_api_version = CLI_PLUGIN_API_VERSION,
.entry_point = &onewire_cli,
};
const FlipperAppPluginDescriptor* onewire_cli_plugin_ep(void) {
return &plugin_descriptor;
}
-11
View File
@@ -1,11 +0,0 @@
#include <cli/cli_i.h>
static void onewire_cli_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("onewire", cli, args, context);
}
void onewire_on_system_start(void) {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "onewire", CliCommandFlagDefault, onewire_cli_wrapper, cli);
furi_record_close(RECORD_CLI);
}
+2 -2
View File
@@ -48,7 +48,7 @@ App(
appid="subghz_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="subghz_cli_plugin_ep",
entry_point="subghz_cli_command_plugin_ep",
requires=["cli"],
sources=["subghz_cli.c", "helpers/subghz_chat.c"],
)
@@ -59,7 +59,7 @@ App(
targets=["f7"],
apptype=FlipperAppType.STARTUP,
entry_point="subghz_on_system_start",
# sources=["subghz_start.c"],
# sources=["subghz_cli.c"],
order=40,
)
+18 -9
View File
@@ -1185,15 +1185,24 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd);
}
#include <flipper_application/flipper_application.h>
#include <cli/cli_i.h>
CLI_PLUGIN_WRAPPER("subghz", subghz_cli_command)
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = CLI_PLUGIN_APP_ID,
.ep_api_version = CLI_PLUGIN_API_VERSION,
.entry_point = &subghz_cli_command,
};
const FlipperAppPluginDescriptor* subghz_cli_plugin_ep(void) {
return &plugin_descriptor;
static void subghz_cli_command_chat_wrapper(Cli* cli, FuriString* args, void* context) {
furi_string_replace_at(args, 0, 0, "chat ");
subghz_cli_command_wrapper(cli, args, context);
}
void subghz_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "subghz", CliCommandFlagDefault, subghz_cli_command_wrapper, NULL);
cli_add_command(cli, "chat", CliCommandFlagDefault, subghz_cli_command_chat_wrapper, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(subghz_cli_command);
UNUSED(subghz_cli_command_chat_wrapper);
#endif
}
-113
View File
@@ -1,113 +0,0 @@
#include <cli/cli_i.h>
#include <storage/storage.h>
#include <flipper.pb.h>
#include <pb_decode.h>
#define SUBGHZ_REGION_FILENAME "/int/.region_data"
static void subghz_cli_command_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("subghz", cli, args, context);
}
static void subghz_cli_command_chat_wrapper(Cli* cli, FuriString* args, void* context) {
furi_string_replace_at(args, 0, 0, "chat ");
subghz_cli_command_wrapper(cli, args, context);
}
static bool
subghz_on_system_start_istream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) {
File* file = istream->state;
size_t ret = storage_file_read(file, buf, count);
return (count == ret);
}
static bool subghz_on_system_start_istream_decode_band(
pb_istream_t* stream,
const pb_field_t* field,
void** arg) {
(void)field;
FuriHalRegion* region = *arg;
PB_Region_Band band = {0};
if(!pb_decode(stream, PB_Region_Band_fields, &band)) {
FURI_LOG_E("SubGhzOnStart", "PB Region band decode error: %s", PB_GET_ERROR(stream));
return false;
}
region->bands_count += 1;
region = realloc( //-V701
region,
sizeof(FuriHalRegion) + sizeof(FuriHalRegionBand) * region->bands_count);
size_t pos = region->bands_count - 1;
region->bands[pos].start = band.start;
region->bands[pos].end = band.end;
region->bands[pos].power_limit = band.power_limit;
region->bands[pos].duty_cycle = band.duty_cycle;
*arg = region;
FURI_LOG_I(
"SubGhzOnStart",
"Add allowed band: start %luHz, stop %luHz, power_limit %ddBm, duty_cycle %u%%",
band.start,
band.end,
band.power_limit,
band.duty_cycle);
return true;
}
void subghz_on_system_start(void) {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "subghz", CliCommandFlagDefault, subghz_cli_command_wrapper, NULL);
cli_add_command(cli, "chat", CliCommandFlagDefault, subghz_cli_command_chat_wrapper, NULL);
furi_record_close(RECORD_CLI);
#ifdef SRV_STORAGE
Storage* storage = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(storage);
FileInfo fileinfo = {0};
PB_Region pb_region = {0};
pb_region.bands.funcs.decode = subghz_on_system_start_istream_decode_band;
do {
if(storage_common_stat(storage, SUBGHZ_REGION_FILENAME, &fileinfo) != FSE_OK ||
fileinfo.size == 0) {
FURI_LOG_W("SubGhzOnStart", "Region data is missing or empty");
break;
}
if(!storage_file_open(file, SUBGHZ_REGION_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) {
FURI_LOG_E("SubGhzOnStart", "Unable to open region data");
break;
}
pb_istream_t istream = {
.callback = subghz_on_system_start_istream_read,
.state = file,
.errmsg = NULL,
.bytes_left = fileinfo.size,
};
pb_region.bands.arg = malloc(sizeof(FuriHalRegion));
if(!pb_decode(&istream, PB_Region_fields, &pb_region)) {
FURI_LOG_E("SubGhzOnStart", "Invalid region data");
free(pb_region.bands.arg);
break;
}
FuriHalRegion* region = pb_region.bands.arg;
memcpy(
region->country_code,
pb_region.country_code->bytes,
pb_region.country_code->size < 4 ? pb_region.country_code->size : 3);
furi_hal_region_set(region);
} while(0);
pb_release(PB_Region_fields, &pb_region);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
#else
UNUSED(subghz_on_system_start_istream_decode_band);
UNUSED(subghz_on_system_start_istream_read);
#endif
}
+1 -1
View File
@@ -23,7 +23,7 @@ App(
apptype=FlipperAppType.PLUGIN,
entry_point="bt_cli_plugin_ep",
requires=["cli"],
sources=["bt_cli.c"],
sources=["bt_cli.c", "bt_service/bt_settings_api.c"],
)
App(
+9 -9
View File
@@ -227,15 +227,15 @@ static void bt_cli(Cli* cli, FuriString* args, void* context) {
furi_record_close(RECORD_BT);
}
#include <flipper_application/flipper_application.h>
#include <cli/cli_i.h>
CLI_PLUGIN_WRAPPER("bt", bt_cli)
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = CLI_PLUGIN_APP_ID,
.ep_api_version = CLI_PLUGIN_API_VERSION,
.entry_point = &bt_cli,
};
const FlipperAppPluginDescriptor* bt_cli_plugin_ep(void) {
return &plugin_descriptor;
void bt_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, RECORD_BT, CliCommandFlagDefault, bt_cli_wrapper, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(bt_cli);
#endif
}
@@ -77,39 +77,3 @@ void bt_keys_storage_set_default_path(Bt* bt) {
bt_keys_storage_set_file_path(bt->keys_storage, BT_KEYS_STORAGE_PATH);
}
/*
* Private API for the Settings app
*/
void bt_get_settings(Bt* bt, BtSettings* settings) {
furi_assert(bt);
furi_assert(settings);
BtMessage message = {
.lock = api_lock_alloc_locked(),
.type = BtMessageTypeGetSettings,
.data.settings = settings,
};
furi_check(
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
api_lock_wait_unlock_and_free(message.lock);
}
void bt_set_settings(Bt* bt, const BtSettings* settings) {
furi_assert(bt);
furi_assert(settings);
BtMessage message = {
.lock = api_lock_alloc_locked(),
.type = BtMessageTypeSetSettings,
.data.csettings = settings,
};
furi_check(
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
api_lock_wait_unlock_and_free(message.lock);
}
@@ -0,0 +1,37 @@
#include "bt_i.h"
/*
* Private API for the Settings app
*/
void bt_get_settings(Bt* bt, BtSettings* settings) {
furi_assert(bt);
furi_assert(settings);
BtMessage message = {
.lock = api_lock_alloc_locked(),
.type = BtMessageTypeGetSettings,
.data.settings = settings,
};
furi_check(
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
api_lock_wait_unlock_and_free(message.lock);
}
void bt_set_settings(Bt* bt, const BtSettings* settings) {
furi_assert(bt);
furi_assert(settings);
BtMessage message = {
.lock = api_lock_alloc_locked(),
.type = BtMessageTypeSetSettings,
.data.csettings = settings,
};
furi_check(
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
api_lock_wait_unlock_and_free(message.lock);
}
-12
View File
@@ -1,12 +0,0 @@
#include <cli/cli_i.h>
#include "bt_service/bt.h"
static void bt_cli_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("bt", cli, args, context);
}
void bt_on_system_start(void) {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, RECORD_BT, CliCommandFlagDefault, bt_cli_wrapper, NULL);
furi_record_close(RECORD_CLI);
}
+99
View File
@@ -8,3 +8,102 @@ App(
order=30,
sdk_headers=["cli.h", "cli_vcp.h"],
)
App(
appid="info_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="cli_command_info_plugin_ep",
requires=["cli"],
sources=["cli_commands.c"],
)
App(
appid="src_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="cli_command_src_plugin_ep",
requires=["cli"],
sources=["cli_commands.c"],
)
App(
appid="neofetch_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="cli_command_neofetch_plugin_ep",
requires=["cli"],
sources=["cli_commands.c"],
)
App(
appid="help_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="cli_command_help_plugin_ep",
requires=["cli"],
sources=["cli_commands.c"],
)
App(
appid="uptime_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="cli_command_uptime_plugin_ep",
requires=["cli"],
sources=["cli_commands.c"],
)
App(
appid="date_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="cli_command_date_plugin_ep",
requires=["cli"],
sources=["cli_commands.c"],
)
App(
appid="sysctl_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="cli_command_sysctl_plugin_ep",
requires=["cli"],
sources=["cli_commands.c"],
)
App(
appid="vibro_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="cli_command_vibro_plugin_ep",
requires=["cli"],
sources=["cli_commands.c"],
)
App(
appid="led_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="cli_command_led_plugin_ep",
requires=["cli"],
sources=["cli_commands.c"],
)
App(
appid="gpio_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="cli_command_gpio_plugin_ep",
requires=["cli"],
sources=["cli_commands.c", "cli_command_gpio.c"],
)
App(
appid="i2c_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="cli_command_i2c_plugin_ep",
requires=["cli"],
sources=["cli_commands.c"],
)
+28 -15
View File
@@ -682,33 +682,46 @@ void cli_command_i2c(Cli* cli, FuriString* args, void* context) {
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
}
CLI_PLUGIN_WRAPPER("info", cli_command_info)
CLI_PLUGIN_WRAPPER("src", cli_command_src)
CLI_PLUGIN_WRAPPER("neofetch", cli_command_neofetch)
CLI_PLUGIN_WRAPPER("help", cli_command_help)
CLI_PLUGIN_WRAPPER("uptime", cli_command_uptime)
CLI_PLUGIN_WRAPPER("date", cli_command_date)
CLI_PLUGIN_WRAPPER("sysctl", cli_command_sysctl)
CLI_PLUGIN_WRAPPER("vibro", cli_command_vibro)
CLI_PLUGIN_WRAPPER("led", cli_command_led)
CLI_PLUGIN_WRAPPER("gpio", cli_command_gpio)
CLI_PLUGIN_WRAPPER("i2c", cli_command_i2c)
void cli_commands_init(Cli* cli) {
cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
cli_add_command(cli, "info", CliCommandFlagParallelSafe, cli_command_info, NULL);
cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
cli_add_command(cli, "source", CliCommandFlagParallelSafe, cli_command_src, NULL);
cli_add_command(cli, "src", CliCommandFlagParallelSafe, cli_command_src, NULL);
cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info_wrapper, (void*)true);
cli_add_command(cli, "info", CliCommandFlagParallelSafe, cli_command_info_wrapper, NULL);
cli_add_command(
cli, "device_info", CliCommandFlagParallelSafe, cli_command_info_wrapper, (void*)true);
cli_add_command(cli, "source", CliCommandFlagParallelSafe, cli_command_src_wrapper, NULL);
cli_add_command(cli, "src", CliCommandFlagParallelSafe, cli_command_src_wrapper, NULL);
cli_add_command(
cli,
"neofetch",
CliCommandFlagParallelSafe | CliCommandFlagHidden,
cli_command_neofetch,
cli_command_neofetch_wrapper,
NULL);
cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL);
cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL);
cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help_wrapper, NULL);
cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help_wrapper, NULL);
cli_add_command(cli, "uptime", CliCommandFlagDefault, cli_command_uptime, NULL);
cli_add_command(cli, "date", CliCommandFlagParallelSafe, cli_command_date, NULL);
cli_add_command(cli, "uptime", CliCommandFlagDefault, cli_command_uptime_wrapper, NULL);
cli_add_command(cli, "date", CliCommandFlagParallelSafe, cli_command_date_wrapper, NULL);
cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL);
cli_add_command(cli, "l", CliCommandFlagParallelSafe, cli_command_log, NULL);
cli_add_command(cli, "sysctl", CliCommandFlagDefault, cli_command_sysctl, NULL);
cli_add_command(cli, "sysctl", CliCommandFlagDefault, cli_command_sysctl_wrapper, NULL);
cli_add_command(cli, "top", CliCommandFlagParallelSafe, cli_command_top, NULL);
cli_add_command(cli, "free", CliCommandFlagParallelSafe, cli_command_free, NULL);
cli_add_command(cli, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL);
cli_add_command(cli, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL);
cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led, NULL);
cli_add_command(cli, "gpio", CliCommandFlagDefault, cli_command_gpio, NULL);
cli_add_command(cli, "i2c", CliCommandFlagDefault, cli_command_i2c, NULL);
cli_add_command(cli, "vibro", CliCommandFlagDefault, cli_command_vibro_wrapper, NULL);
cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led_wrapper, NULL);
cli_add_command(cli, "gpio", CliCommandFlagDefault, cli_command_gpio_wrapper, NULL);
cli_add_command(cli, "i2c", CliCommandFlagDefault, cli_command_i2c_wrapper, NULL);
}
+20 -4
View File
@@ -62,12 +62,28 @@ void cli_putc(Cli* cli, char c);
void cli_stdout_callback(void* _cookie, const char* data, size_t size);
// Wraps CLI commands to load from plugin file
// Must call from CLI context, like dummy CLI command callback
// You need to setup the plugin to compile correctly separately
// CLI command wrapping to load from plugin file on SD card
// Just need to:
// - Use CLI_PLUGIN_WRAPPER("name", cmd_callback)
// - Replace callback usages with cmd_callback_wrapper
// - Add "name_cli" entry in app manifest to build as plugin
void cli_plugin_wrapper(const char* name, Cli* cli, FuriString* args, void* context);
#include <flipper_application/flipper_application.h>
#define CLI_PLUGIN_APP_ID "cli"
#define CLI_PLUGIN_API_VERSION 1
void cli_plugin_wrapper(const char* name, Cli* cli, FuriString* args, void* context);
#define CLI_PLUGIN_WRAPPER(plugin_name_without_cli_suffix, cli_command_callback) \
void cli_command_callback##_wrapper(Cli* cli, FuriString* args, void* context) { \
cli_plugin_wrapper(plugin_name_without_cli_suffix, cli, args, context); \
} \
static const FlipperAppPluginDescriptor cli_command_callback##_plugin_descriptor = { \
.appid = CLI_PLUGIN_APP_ID, \
.ep_api_version = CLI_PLUGIN_API_VERSION, \
.entry_point = &cli_command_callback, \
}; \
const FlipperAppPluginDescriptor* cli_command_callback##_plugin_ep(void) { \
UNUSED(cli_command_callback##_wrapper); \
return &cli_command_callback##_plugin_descriptor; \
}
#ifdef __cplusplus
}
+1 -1
View File
@@ -11,6 +11,6 @@ App(
appid="crypto_start",
apptype=FlipperAppType.STARTUP,
entry_point="crypto_on_system_start",
sources=["crypto_start.c"],
sources=["crypto_cli.c"],
order=10,
)
+9 -9
View File
@@ -317,15 +317,15 @@ static void crypto_cli(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd);
}
#include <flipper_application/flipper_application.h>
#include <cli/cli_i.h>
CLI_PLUGIN_WRAPPER("crypto", crypto_cli)
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = CLI_PLUGIN_APP_ID,
.ep_api_version = CLI_PLUGIN_API_VERSION,
.entry_point = &crypto_cli,
};
const FlipperAppPluginDescriptor* crypto_cli_plugin_ep(void) {
return &plugin_descriptor;
void crypto_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "crypto", CliCommandFlagDefault, crypto_cli_wrapper, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(crypto_cli);
#endif
}
@@ -1,11 +0,0 @@
#include <cli/cli_i.h>
static void crypto_cli_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("crypto", cli, args, context);
}
void crypto_on_system_start(void) {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "crypto", CliCommandFlagDefault, crypto_cli_wrapper, NULL);
furi_record_close(RECORD_CLI);
}
+1 -3
View File
@@ -231,7 +231,7 @@ static void dolphin_reset_butthurt_timer(Dolphin* dolphin) {
}
}
static bool dolphin_process_event(FuriEventLoopObject* object, void* context) {
static void dolphin_process_event(FuriEventLoopObject* object, void* context) {
UNUSED(object);
Dolphin* dolphin = context;
@@ -284,8 +284,6 @@ static bool dolphin_process_event(FuriEventLoopObject* object, void* context) {
}
dolphin_event_release(&event);
return true;
}
static void dolphin_storage_callback(const void* message, void* context) {
+3 -9
View File
@@ -442,7 +442,7 @@ void view_dispatcher_update(View* view, void* context) {
}
}
bool view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* context) {
void view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* context) {
furi_assert(context);
ViewDispatcher* instance = context;
furi_assert(instance->event_queue == object);
@@ -450,11 +450,9 @@ bool view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* conte
uint32_t event;
furi_check(furi_message_queue_get(instance->event_queue, &event, 0) == FuriStatusOk);
view_dispatcher_handle_custom_event(instance, event);
return true;
}
bool view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* context) {
void view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* context) {
furi_assert(context);
ViewDispatcher* instance = context;
furi_assert(instance->input_queue == object);
@@ -462,11 +460,9 @@ bool view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* conte
InputEvent input;
furi_check(furi_message_queue_get(instance->input_queue, &input, 0) == FuriStatusOk);
view_dispatcher_handle_input(instance, &input);
return true;
}
bool view_dispatcher_run_ascii_callback(FuriEventLoopObject* object, void* context) {
void view_dispatcher_run_ascii_callback(FuriEventLoopObject* object, void* context) {
furi_assert(context);
ViewDispatcher* instance = context;
furi_assert(instance->ascii_queue == object);
@@ -474,6 +470,4 @@ bool view_dispatcher_run_ascii_callback(FuriEventLoopObject* object, void* conte
AsciiEvent ascii;
furi_check(furi_message_queue_get(instance->ascii_queue, &ascii, 0) == FuriStatusOk);
view_dispatcher_handle_ascii(instance, &ascii);
return true;
}
@@ -65,10 +65,10 @@ void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* vie
void view_dispatcher_update(View* view, void* context);
/** ViewDispatcher run event loop event callback */
bool view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* context);
void view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* context);
/** ViewDispatcher run event loop input callback */
bool view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* context);
void view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* context);
/** ViewDispatcher run event loop ascii callback */
bool view_dispatcher_run_ascii_callback(FuriEventLoopObject* object, void* context);
void view_dispatcher_run_ascii_callback(FuriEventLoopObject* object, void* context);
+1 -9
View File
@@ -25,7 +25,7 @@ typedef struct {
} InputPinState;
/** Input CLI command handler */
void input_cli(Cli* cli, FuriString* args, void* context);
void input_cli_wrapper(Cli* cli, FuriString* args, void* context);
// #define INPUT_DEBUG
@@ -79,12 +79,6 @@ const char* input_get_type_name(InputType type) {
}
}
#include <cli/cli_i.h>
static void input_cli_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("input", cli, args, context);
}
int32_t input_srv(void* p) {
UNUSED(p);
@@ -102,8 +96,6 @@ int32_t input_srv(void* p) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "input", CliCommandFlagParallelSafe, input_cli_wrapper, event_pubsub);
#else
UNUSED(input_cli_wrapper);
#endif
InputPinState pin_states[input_pins_count];
+1 -11
View File
@@ -226,15 +226,5 @@ void input_cli(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd);
}
#include <flipper_application/flipper_application.h>
#include <cli/cli_i.h>
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = CLI_PLUGIN_APP_ID,
.ep_api_version = CLI_PLUGIN_API_VERSION,
.entry_point = &input_cli,
};
const FlipperAppPluginDescriptor* input_cli_plugin_ep(void) {
return &plugin_descriptor;
}
CLI_PLUGIN_WRAPPER("input", input_cli)
+9 -9
View File
@@ -141,15 +141,15 @@ static void loader_cli(Cli* cli, FuriString* args, void* context) {
furi_record_close(RECORD_LOADER);
}
#include <flipper_application/flipper_application.h>
#include <cli/cli_i.h>
CLI_PLUGIN_WRAPPER("loader", loader_cli)
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = CLI_PLUGIN_APP_ID,
.ep_api_version = CLI_PLUGIN_API_VERSION,
.entry_point = &loader_cli,
};
const FlipperAppPluginDescriptor* loader_cli_plugin_ep(void) {
return &plugin_descriptor;
void loader_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli_wrapper, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(loader_cli);
#endif
}
@@ -1,12 +0,0 @@
#include <cli/cli_i.h>
#include "loader.h"
static void loader_cli_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("loader", cli, args, context);
}
void loader_on_system_start(void) {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli_wrapper, NULL);
furi_record_close(RECORD_CLI);
}
@@ -593,3 +593,7 @@ const NotificationSequence sequence_lcd_contrast_update = {
&message_lcd_contrast_update,
NULL,
};
const NotificationSequence sequence_empty = {
NULL,
};
@@ -145,6 +145,9 @@ extern const NotificationSequence sequence_audiovisual_alert;
// LCD
extern const NotificationSequence sequence_lcd_contrast_update;
// Wait for notification queue become empty
extern const NotificationSequence sequence_empty;
#ifdef __cplusplus
}
#endif
+10 -8
View File
@@ -108,15 +108,17 @@ void power_cli(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd);
}
#include <flipper_application/flipper_application.h>
#include <cli/cli_i.h>
CLI_PLUGIN_WRAPPER("power", power_cli)
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = CLI_PLUGIN_APP_ID,
.ep_api_version = CLI_PLUGIN_API_VERSION,
.entry_point = &power_cli,
};
void power_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
const FlipperAppPluginDescriptor* power_cli_plugin_ep(void) {
return &plugin_descriptor;
cli_add_command(cli, "power", CliCommandFlagParallelSafe, power_cli_wrapper, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(power_cli);
#endif
}
@@ -499,7 +499,7 @@ static void power_handle_reboot(PowerBootMode mode) {
furi_hal_power_reset();
}
static bool power_message_callback(FuriEventLoopObject* object, void* context) {
static void power_message_callback(FuriEventLoopObject* object, void* context) {
furi_assert(context);
Power* power = context;
@@ -545,8 +545,6 @@ static bool power_message_callback(FuriEventLoopObject* object, void* context) {
if(msg.lock) {
api_lock_unlock(msg.lock);
}
return true;
}
static void power_tick_callback(void* context) {
-11
View File
@@ -1,11 +0,0 @@
#include <cli/cli_i.h>
static void power_cli_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("power", cli, args, context);
}
void power_on_system_start(void) {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "power", CliCommandFlagParallelSafe, power_cli_wrapper, NULL);
furi_record_close(RECORD_CLI);
}
+1
View File
@@ -5,6 +5,7 @@ App(
provides=[
"passport",
"system_settings",
"clock_settings",
"about",
],
)
@@ -4,4 +4,4 @@
// Then, we still use the Header from original code as if nothing happened
// bt_get_settings(), bt_set_settings()
#include <applications/services/bt/bt_service/bt_api.c>
#include <applications/services/bt/bt_service/bt_settings_api.c>
@@ -0,0 +1,18 @@
App(
appid="clock_settings",
name="Clock & Alarm",
apptype=FlipperAppType.SETTINGS,
entry_point="clock_settings",
requires=["gui"],
provides=["clock_settings_start"],
stack_size=1 * 1024,
order=90,
fap_category="assets",
)
App(
appid="clock_settings_start",
apptype=FlipperAppType.STARTUP,
entry_point="clock_settings_start",
order=1000,
)
@@ -0,0 +1,71 @@
#include "clock_settings.h"
#include <furi.h>
#include <furi_hal.h>
static bool clock_settings_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
ClockSettings* app = context;
return scene_manager_handle_custom_event(app->scene_manager, event);
}
static bool clock_settings_back_event_callback(void* context) {
furi_assert(context);
ClockSettings* app = context;
return scene_manager_handle_back_event(app->scene_manager);
}
ClockSettings* clock_settings_alloc() {
ClockSettings* app = malloc(sizeof(ClockSettings));
app->gui = furi_record_open(RECORD_GUI);
app->view_dispatcher = view_dispatcher_alloc();
app->scene_manager = scene_manager_alloc(&clock_settings_scene_handlers, app);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_custom_event_callback(
app->view_dispatcher, clock_settings_custom_event_callback);
view_dispatcher_set_navigation_event_callback(
app->view_dispatcher, clock_settings_back_event_callback);
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
app->pwm_view =
clock_settings_module_alloc(view_dispatcher_get_event_loop(app->view_dispatcher));
view_dispatcher_add_view(
app->view_dispatcher, ClockSettingsViewPwm, clock_settings_module_get_view(app->pwm_view));
scene_manager_next_scene(app->scene_manager, ClockSettingsSceneStart);
return app;
}
void clock_settings_free(ClockSettings* app) {
furi_assert(app);
// Views
view_dispatcher_remove_view(app->view_dispatcher, ClockSettingsViewPwm);
clock_settings_module_free(app->pwm_view);
// View dispatcher
view_dispatcher_free(app->view_dispatcher);
scene_manager_free(app->scene_manager);
// Close records
furi_record_close(RECORD_GUI);
free(app);
}
int32_t clock_settings(void* p) {
UNUSED(p);
ClockSettings* clock_settings = clock_settings_alloc();
view_dispatcher_run(clock_settings->view_dispatcher);
clock_settings_free(clock_settings);
return 0;
}
@@ -0,0 +1,31 @@
#pragma once
#include "scenes/clock_settings_scene.h"
#include <furi_hal_clock.h>
#include <furi_hal_pwm.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <gui/modules/submenu.h>
#include <gui/modules/variable_item_list.h>
#include <gui/modules/submenu.h>
#include "views/clock_settings_module.h"
typedef struct ClockSettings ClockSettings;
struct ClockSettings {
Gui* gui;
ViewDispatcher* view_dispatcher;
SceneManager* scene_manager;
ClockSettingsModule* pwm_view;
};
typedef enum {
ClockSettingsViewPwm,
} ClockSettingsView;
typedef enum {
ClockSettingsCustomEventNone,
} ClockSettingsCustomEvent;
@@ -0,0 +1,177 @@
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <gui/view_port.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include <assets_icons.h>
#define TAG "ClockSettingsAlarm"
typedef struct {
DateTime now;
IconAnimation* icon;
} ClockSettingsAlramModel;
const NotificationSequence sequence_alarm = {
&message_force_speaker_volume_setting_1f,
&message_force_vibro_setting_on,
&message_force_display_brightness_setting_1f,
&message_vibro_on,
&message_display_backlight_on,
&message_note_c7,
&message_delay_250,
&message_display_backlight_off,
&message_note_c4,
&message_delay_250,
&message_display_backlight_on,
&message_note_c7,
&message_delay_250,
&message_display_backlight_off,
&message_note_c4,
&message_delay_250,
&message_sound_off,
&message_vibro_off,
NULL,
};
static void clock_settings_alarm_draw_callback(Canvas* canvas, void* ctx) {
ClockSettingsAlramModel* model = ctx;
char buffer[64] = {};
canvas_draw_icon_animation(canvas, 5, 6, model->icon);
canvas_set_font(canvas, FontBigNumbers);
snprintf(buffer, sizeof(buffer), "%02u:%02u", model->now.hour, model->now.minute);
canvas_draw_str(canvas, 58, 32, buffer);
canvas_set_font(canvas, FontPrimary);
snprintf(
buffer,
sizeof(buffer),
"%02u.%02u.%04u",
model->now.day,
model->now.month,
model->now.year);
canvas_draw_str(canvas, 60, 44, buffer);
}
static void clock_settings_alarm_input_callback(InputEvent* input_event, void* ctx) {
furi_assert(ctx);
FuriMessageQueue* event_queue = ctx;
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
}
void clock_settings_alarm_animation_callback(IconAnimation* instance, void* context) {
UNUSED(instance);
ViewPort* view_port = context;
view_port_update(view_port);
}
int32_t clock_settings_alarm(void* p) {
UNUSED(p);
// View Model
ClockSettingsAlramModel model;
furi_hal_rtc_get_datetime(&model.now);
model.icon = icon_animation_alloc(&A_Alarm_47x39);
// Alloc message queue
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
// Configure view port
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, clock_settings_alarm_draw_callback, &model);
view_port_input_callback_set(view_port, clock_settings_alarm_input_callback, event_queue);
// Register view port in GUI
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_message(notification, &sequence_alarm);
icon_animation_set_update_callback(
model.icon, clock_settings_alarm_animation_callback, view_port);
icon_animation_start(model.icon);
// Process events
InputEvent event;
bool running = true;
while(running) {
if(furi_message_queue_get(event_queue, &event, 2000) == FuriStatusOk) {
if(event.type == InputTypePress) {
running = false;
}
} else {
notification_message(notification, &sequence_alarm);
furi_hal_rtc_get_datetime(&model.now);
view_port_update(view_port);
}
}
icon_animation_stop(model.icon);
notification_message_block(notification, &sequence_empty);
furi_record_close(RECORD_NOTIFICATION);
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_message_queue_free(event_queue);
furi_record_close(RECORD_GUI);
icon_animation_free(model.icon);
return 0;
}
FuriThread* clock_settings_alarm_thread = NULL;
static void clock_settings_alarm_thread_state_callback(
FuriThread* thread,
FuriThreadState state,
void* context) {
furi_assert(clock_settings_alarm_thread == thread);
UNUSED(context);
if(state == FuriThreadStateStopped) {
furi_thread_free(thread);
clock_settings_alarm_thread = NULL;
}
}
static void clock_settings_alarm_start(void* context, uint32_t arg) {
UNUSED(context);
UNUSED(arg);
FURI_LOG_I(TAG, "spawning alarm thread");
if(clock_settings_alarm_thread) return;
clock_settings_alarm_thread =
furi_thread_alloc_ex("ClockAlarm", 1024, clock_settings_alarm, NULL);
furi_thread_set_state_callback(
clock_settings_alarm_thread, clock_settings_alarm_thread_state_callback);
furi_thread_start(clock_settings_alarm_thread);
}
static void clock_settings_alarm_isr(void* context) {
UNUSED(context);
furi_timer_pending_callback(clock_settings_alarm_start, NULL, 0);
}
void clock_settings_start(void) {
#ifndef FURI_RAM_EXEC
furi_hal_rtc_set_alarm_callback(clock_settings_alarm_isr, NULL);
#endif
}
@@ -0,0 +1,30 @@
#include "../clock_settings.h"
// Generate scene on_enter handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
void (*const clock_settings_scene_on_enter_handlers[])(void*) = {
#include "clock_settings_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 clock_settings_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
#include "clock_settings_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 clock_settings_scene_on_exit_handlers[])(void* context) = {
#include "clock_settings_scene_config.h"
};
#undef ADD_SCENE
// Initialize scene handlers configuration structure
const SceneManagerHandlers clock_settings_scene_handlers = {
.on_enter_handlers = clock_settings_scene_on_enter_handlers,
.on_event_handlers = clock_settings_scene_on_event_handlers,
.on_exit_handlers = clock_settings_scene_on_exit_handlers,
.scene_num = ClockSettingsSceneNum,
};
@@ -0,0 +1,29 @@
#pragma once
#include <gui/scene_manager.h>
// Generate scene id and total number
#define ADD_SCENE(prefix, name, id) ClockSettingsScene##id,
typedef enum {
#include "clock_settings_scene_config.h"
ClockSettingsSceneNum,
} ClockSettingsScene;
#undef ADD_SCENE
extern const SceneManagerHandlers clock_settings_scene_handlers;
// Generate scene on_enter handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
#include "clock_settings_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 "clock_settings_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 "clock_settings_scene_config.h"
#undef ADD_SCENE
@@ -0,0 +1 @@
ADD_SCENE(clock_settings, start, Start)
@@ -0,0 +1,32 @@
#include "../clock_settings.h"
#include <furi_hal.h>
#define TAG "SceneStart"
typedef enum {
SubmenuIndexPwm,
SubmenuIndexClockOutput,
} SubmenuIndex;
void clock_settings_scene_start_submenu_callback(void* context, uint32_t index) {
ClockSettings* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, index);
}
void clock_settings_scene_start_on_enter(void* context) {
ClockSettings* app = context;
view_dispatcher_switch_to_view(app->view_dispatcher, ClockSettingsViewPwm);
}
bool clock_settings_scene_start_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
return false;
}
void clock_settings_scene_start_on_exit(void* context) {
UNUSED(context);
}
@@ -0,0 +1,438 @@
#include "clock_settings_module.h"
#include <gui/elements.h>
#include <assets_icons.h>
#include <locale/locale.h>
#define TAG "ClockSettingsModule"
struct ClockSettingsModule {
FuriEventLoopTimer* timer;
View* view;
};
typedef struct {
DateTime current;
DateTime alarm;
bool alarm_enabled;
bool editing;
uint8_t row;
uint8_t column;
} ClockSettingsModuleViewModel;
typedef enum {
EditStateNone,
EditStateActive,
EditStateActiveEditing,
} EditState;
#define get_state(m, r, c) \
((m)->row == (r) && (m)->column == (c) ? \
((m)->editing ? EditStateActiveEditing : EditStateActive) : \
EditStateNone)
#define ROW_0_Y (4)
#define ROW_0_H (20)
#define ROW_1_Y (30)
#define ROW_1_H (12)
#define ROW_2_Y (48)
#define ROW_2_H (12)
#define ROW_COUNT 3
#define COLUMN_COUNT 3
static inline void clock_settings_module_cleanup_date(DateTime* dt) {
uint8_t day_per_month =
datetime_get_days_per_month(datetime_is_leap_year(dt->year), dt->month);
if(dt->day > day_per_month) {
dt->day = day_per_month;
}
}
static inline void clock_settings_module_draw_block(
Canvas* canvas,
int32_t x,
int32_t y,
size_t w,
size_t h,
Font font,
EditState state,
const char* text) {
canvas_set_color(canvas, ColorBlack);
if(state != EditStateNone) {
if(state == EditStateActiveEditing) {
canvas_draw_icon(canvas, x + w / 2 - 2, y - 1 - 3, &I_SmallArrowUp_3x5);
canvas_draw_icon(canvas, x + w / 2 - 2, y + h + 1, &I_SmallArrowDown_3x5);
}
canvas_draw_rbox(canvas, x, y, w, h, 1);
canvas_set_color(canvas, ColorWhite);
} else {
canvas_draw_rframe(canvas, x, y, w, h, 1);
}
canvas_set_font(canvas, font);
canvas_draw_str_aligned(canvas, x + w / 2, y + h / 2, AlignCenter, AlignCenter, text);
if(state != EditStateNone) {
canvas_set_color(canvas, ColorBlack);
}
}
static void
clock_settings_module_draw_time_callback(Canvas* canvas, ClockSettingsModuleViewModel* model) {
char buffer[64];
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 0, ROW_0_Y + 15, "Time");
snprintf(buffer, sizeof(buffer), "%02u", model->current.hour);
clock_settings_module_draw_block(
canvas, 32, ROW_0_Y, 28, ROW_0_H, FontBigNumbers, get_state(model, 0, 0), buffer);
canvas_draw_box(canvas, 62, ROW_0_Y + ROW_0_H - 7, 2, 2);
canvas_draw_box(canvas, 62, ROW_0_Y + ROW_0_H - 7 - 6, 2, 2);
snprintf(buffer, sizeof(buffer), "%02u", model->current.minute);
clock_settings_module_draw_block(
canvas, 66, ROW_0_Y, 28, ROW_0_H, FontBigNumbers, get_state(model, 0, 1), buffer);
canvas_draw_box(canvas, 96, ROW_0_Y + ROW_0_H - 7, 2, 2);
canvas_draw_box(canvas, 96, ROW_0_Y + ROW_0_H - 7 - 6, 2, 2);
snprintf(buffer, sizeof(buffer), "%02u", model->current.second);
clock_settings_module_draw_block(
canvas, 100, ROW_0_Y, 28, ROW_0_H, FontBigNumbers, get_state(model, 0, 2), buffer);
}
static void
clock_settings_module_draw_date_callback(Canvas* canvas, ClockSettingsModuleViewModel* model) {
char buffer[64];
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 0, ROW_1_Y + 9, "Date");
// Day
snprintf(buffer, sizeof(buffer), "%02u", model->current.day);
clock_settings_module_draw_block(
canvas, 44, ROW_1_Y, 17, ROW_1_H, FontPrimary, get_state(model, 1, 0), buffer);
canvas_draw_box(canvas, 71 - 6, ROW_1_Y + ROW_1_H - 4, 2, 2);
// Month
snprintf(buffer, sizeof(buffer), "%02u", model->current.month);
clock_settings_module_draw_block(
canvas, 71, ROW_1_Y, 17, ROW_1_H, FontPrimary, get_state(model, 1, 1), buffer);
canvas_draw_box(canvas, 98 - 6, ROW_1_Y + ROW_1_H - 4, 2, 2);
// Year
snprintf(buffer, sizeof(buffer), "%04u", model->current.year);
clock_settings_module_draw_block(
canvas, 98, ROW_1_Y, 30, ROW_1_H, FontPrimary, get_state(model, 1, 2), buffer);
}
static void
clock_settings_module_draw_alarm_callback(Canvas* canvas, ClockSettingsModuleViewModel* model) {
char buffer[64];
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 0, ROW_2_Y + 9, "Alarm");
snprintf(buffer, sizeof(buffer), "%02u", model->alarm.hour);
clock_settings_module_draw_block(
canvas, 58, ROW_2_Y, 17, ROW_2_H, FontPrimary, get_state(model, 2, 0), buffer);
canvas_draw_box(canvas, 81 - 4, ROW_2_Y + ROW_2_H - 4, 2, 2);
canvas_draw_box(canvas, 81 - 4, ROW_2_Y + ROW_2_H - 4 - 4, 2, 2);
snprintf(buffer, sizeof(buffer), "%02u", model->alarm.minute);
clock_settings_module_draw_block(
canvas, 81, ROW_2_Y, 17, ROW_2_H, FontPrimary, get_state(model, 2, 1), buffer);
clock_settings_module_draw_block(
canvas,
106,
ROW_2_Y,
22,
ROW_2_H,
FontPrimary,
get_state(model, 2, 2),
model->alarm_enabled ? "On" : "Off");
}
static void clock_settings_module_draw_callback(Canvas* canvas, void* _model) {
ClockSettingsModuleViewModel* model = _model;
clock_settings_module_draw_time_callback(canvas, model);
clock_settings_module_draw_date_callback(canvas, model);
clock_settings_module_draw_alarm_callback(canvas, model);
}
static bool clock_settings_module_input_navigation_callback(
InputEvent* event,
ClockSettingsModuleViewModel* model) {
if(event->key == InputKeyUp) {
if(model->row > 0) model->row--;
} else if(event->key == InputKeyDown) {
if(model->row < ROW_COUNT - 1) model->row++;
} else if(event->key == InputKeyOk) {
model->editing = !model->editing;
} else if(event->key == InputKeyRight) {
if(model->column < COLUMN_COUNT - 1) model->column++;
} else if(event->key == InputKeyLeft) {
if(model->column > 0) model->column--;
} else if(event->key == InputKeyBack && model->editing) {
model->editing = false;
} else {
return false;
}
return true;
}
static bool clock_settings_module_input_time_callback(
InputEvent* event,
ClockSettingsModuleViewModel* model) {
if(event->key == InputKeyUp) {
if(model->column == 0) {
model->current.hour++;
model->current.hour = model->current.hour % 24;
} else if(model->column == 1) {
model->current.minute++;
model->current.minute = model->current.minute % 60;
} else if(model->column == 2) {
model->current.second++;
model->current.second = model->current.second % 60;
} else {
furi_crash();
}
} else if(event->key == InputKeyDown) {
if(model->column == 0) {
if(model->current.hour > 0) {
model->current.hour--;
} else {
model->current.hour = 23;
}
model->current.hour = model->current.hour % 24;
} else if(model->column == 1) {
if(model->current.minute > 0) {
model->current.minute--;
} else {
model->current.minute = 59;
}
model->current.minute = model->current.minute % 60;
} else if(model->column == 2) {
if(model->current.second > 0) {
model->current.second--;
} else {
model->current.second = 59;
}
model->current.second = model->current.second % 60;
} else {
furi_crash();
}
} else {
return clock_settings_module_input_navigation_callback(event, model);
}
return true;
}
static bool clock_settings_module_input_date_callback(
InputEvent* event,
ClockSettingsModuleViewModel* model) {
if(event->key == InputKeyUp) {
if(model->column == 0) {
if(model->current.day < 31) model->current.day++;
} else if(model->column == 1) {
if(model->current.month < 12) {
model->current.month++;
}
} else if(model->column == 2) {
if(model->current.year < 2099) {
model->current.year++;
}
} else {
furi_crash();
}
} else if(event->key == InputKeyDown) {
if(model->column == 0) {
if(model->current.day > 1) {
model->current.day--;
}
} else if(model->column == 1) {
if(model->current.month > 1) {
model->current.month--;
}
} else if(model->column == 2) {
if(model->current.year > 2000) {
model->current.year--;
}
} else {
furi_crash();
}
} else {
return clock_settings_module_input_navigation_callback(event, model);
}
clock_settings_module_cleanup_date(&model->current);
return true;
}
static bool clock_settings_module_input_alarm_callback(
InputEvent* event,
ClockSettingsModuleViewModel* model) {
if(event->key == InputKeyUp) {
if(model->column == 0) {
model->alarm.hour++;
model->alarm.hour = model->alarm.hour % 24;
} else if(model->column == 1) {
model->alarm.minute++;
model->alarm.minute = model->alarm.minute % 60;
} else if(model->column == 2) {
model->alarm_enabled = !model->alarm_enabled;
} else {
furi_crash();
}
} else if(event->key == InputKeyDown) {
if(model->column == 0) {
if(model->alarm.hour > 0) {
model->alarm.hour--;
} else {
model->alarm.hour = 23;
}
model->alarm.hour = model->alarm.hour % 24;
} else if(model->column == 1) {
if(model->alarm.minute > 0) {
model->alarm.minute--;
} else {
model->alarm.minute = 59;
}
model->alarm.minute = model->alarm.minute % 60;
} else if(model->column == 2) {
model->alarm_enabled = !model->alarm_enabled;
} else {
furi_crash();
}
} else {
return clock_settings_module_input_navigation_callback(event, model);
}
return true;
}
static bool clock_settings_module_input_callback(InputEvent* event, void* context) {
furi_assert(context);
ClockSettingsModule* instance = context;
bool consumed = false;
with_view_model(
instance->view,
ClockSettingsModuleViewModel * model,
{
if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
bool previous_editing = model->editing;
if(model->editing) {
if(model->row == 0) {
consumed = clock_settings_module_input_time_callback(event, model);
} else if(model->row == 1) {
consumed = clock_settings_module_input_date_callback(event, model);
} else if(model->row == 2) {
consumed = clock_settings_module_input_alarm_callback(event, model);
} else {
furi_crash();
}
} else {
consumed = clock_settings_module_input_navigation_callback(event, model);
}
// Switching between navigate/edit
if(model->editing != previous_editing) {
if(model->row == 2) {
if(!model->editing) {
// Disable alarm
furi_hal_rtc_set_alarm(NULL, false);
// Set new alarm
furi_hal_rtc_set_alarm(&model->alarm, model->alarm_enabled);
// Confirm
model->alarm_enabled = furi_hal_rtc_get_alarm(&model->alarm);
}
} else {
if(model->editing) {
// stop timer to prevent mess with current date time
furi_event_loop_timer_stop(instance->timer);
} else {
// save date time and restart timer
furi_hal_rtc_set_datetime(&model->current);
furi_event_loop_timer_start(instance->timer, 1000);
}
}
}
}
},
true);
return consumed;
}
static void clock_settings_module_timer_callback(void* context) {
furi_assert(context);
ClockSettingsModule* instance = context;
DateTime dt;
furi_hal_rtc_get_datetime(&dt);
with_view_model(
instance->view, ClockSettingsModuleViewModel * model, { model->current = dt; }, true);
}
static void clock_settings_module_view_enter_callback(void* context) {
furi_assert(context);
ClockSettingsModule* instance = context;
clock_settings_module_timer_callback(context);
DateTime alarm;
bool enabled = furi_hal_rtc_get_alarm(&alarm);
with_view_model(
instance->view,
ClockSettingsModuleViewModel * model,
{
model->alarm = alarm;
model->alarm_enabled = enabled;
},
true);
furi_event_loop_timer_start(instance->timer, 1000);
}
static void clock_settings_module_view_exit_callback(void* context) {
furi_assert(context);
ClockSettingsModule* instance = context;
furi_event_loop_timer_stop(instance->timer);
}
ClockSettingsModule* clock_settings_module_alloc(FuriEventLoop* event_loop) {
ClockSettingsModule* instance = malloc(sizeof(ClockSettingsModule));
instance->timer = furi_event_loop_timer_alloc(
event_loop, clock_settings_module_timer_callback, FuriEventLoopTimerTypePeriodic, instance);
instance->view = view_alloc();
view_set_enter_callback(instance->view, clock_settings_module_view_enter_callback);
view_set_exit_callback(instance->view, clock_settings_module_view_exit_callback);
view_allocate_model(
instance->view, ViewModelTypeLocking, sizeof(ClockSettingsModuleViewModel));
with_view_model(
instance->view, ClockSettingsModuleViewModel * model, { model->row = 0; }, false);
view_set_context(instance->view, instance);
view_set_draw_callback(instance->view, clock_settings_module_draw_callback);
view_set_input_callback(instance->view, clock_settings_module_input_callback);
return instance;
}
void clock_settings_module_free(ClockSettingsModule* instance) {
furi_assert(instance);
view_free(instance->view);
free(instance);
}
View* clock_settings_module_get_view(ClockSettingsModule* instance) {
furi_assert(instance);
return instance->view;
}
@@ -0,0 +1,24 @@
#pragma once
#include <furi_hal.h>
#include <gui/view.h>
typedef struct ClockSettingsModule ClockSettingsModule;
typedef void (*ClockSettingsModuleViewCallback)(
uint8_t channel_id,
uint32_t freq,
uint8_t duty,
void* context);
ClockSettingsModule* clock_settings_module_alloc(FuriEventLoop* event_loop);
void clock_settings_module_free(ClockSettingsModule* instance);
View* clock_settings_module_get_view(ClockSettingsModule* instance);
void clock_settings_module_set(
ClockSettingsModule* instance,
const DateTime* datetime,
bool enabled);
bool clock_settings_module_get(ClockSettingsModule* instance, DateTime* datetime);
-1
View File
@@ -5,7 +5,6 @@ App(
provides=[
"updater_app",
"js_app",
"js_app_start",
"findmy_startup",
# "archive",
],
+21 -13
View File
@@ -2,31 +2,38 @@ App(
appid="js_app",
name="JS Runner",
apptype=FlipperAppType.SYSTEM,
entry_point="js_app",
# cdefines=["JS_RUNNER_FAP"],
# Sources separation breaks linking when internal, comment as needed
# sources=[
# "*.c*",
# "!modules",
# "modules/js_flipper.c",
# ],
fap_icon="icon.png",
fap_category="assets",
entry_point="js_app",
stack_size=2 * 1024,
resources="examples",
order=0,
fap_icon="icon.png",
fap_category="assets",
provides=["js_app_start"],
sources=[
"js_app.c",
"js_modules.c",
"js_thread.c",
"plugin_api/app_api_table.cpp",
"views/console_view.c",
"modules/js_flipper.c",
"modules/js_tests.c",
],
)
App(
appid="js_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="js_cli_plugin_ep",
entry_point="js_cli_execute_plugin_ep",
requires=["cli"],
sources=[
"*.c*",
"!modules",
"js_app.c",
"js_modules.c",
"js_thread.c",
"plugin_api/app_api_table.cpp",
"modules/js_flipper.c",
"modules/js_tests.c",
],
)
@@ -34,8 +41,8 @@ App(
appid="js_app_start",
apptype=FlipperAppType.STARTUP,
entry_point="js_app_on_system_start",
# sources=["js_start.c"],
order=160,
sources=["js_app.c"],
)
App(
@@ -119,6 +126,7 @@ App(
entry_point="js_gui_file_picker_ep",
requires=["js_app"],
sources=["modules/js_gui/file_picker.c"],
fap_libs=[],
)
App(
@@ -1,3 +1,6 @@
// Script cannot work without blebeacon module so check before
checkSdkFeatures(["blebeacon"]);
let blebeacon = require("blebeacon");
// Stop if previous background beacon is active
@@ -19,7 +19,7 @@ eventLoop.subscribe(eventLoop.timer("periodic", 1000), function (_, _item, led,
// read potentiometer when button is pressed
print("Press the button (PC1)");
eventLoop.subscribe(button.interrupt(), function (_, _item, pot) {
print("PC0 is at", pot.read_analog(), "mV");
print("PC0 is at", pot.readAnalog(), "mV");
}, pot);
// the program will just exit unless this is here
@@ -45,6 +45,12 @@ let views = {
}),
};
// Enable illegal filename symbols since we're not choosing filenames, gives more flexibility
// Not available in all firmwares, good idea to check if it is supported
if (doesSdkSupport(["gui-textinput-illegalsymbols"])) {
views.keyboard.set("illegalSymbols", true);
}
// demo selector
eventLoop.subscribe(views.demos.chosen, function (_sub, index, gui, eventLoop, views) {
if (index === 0) {
@@ -1,3 +1,6 @@
// Script cannot work without i2c module so check before
checkSdkFeatures(["i2c"]);
// Connect an 24C32N EEPROM to the I2C bus of the board. SDA=pin 15, SCL=pin 16, VCC=pin 9, GND=pin 8.
let i2c = require("i2c");
@@ -1,3 +1,6 @@
// Script cannot work without spi module so check before
checkSdkFeatures(["spi"]);
// Connect a w25q32 SPI device to the Flipper Zero.
// D1=pin 2 (MOSI), SLK=pin 5 (SCK), GND=pin 8 (GND), D0=pin 3 (MISO), CS=pin 4 (CS), VCC=pin 9 (3V3)
let spi = require("spi");
@@ -5,8 +8,9 @@ let spi = require("spi");
// Display textbox so user can scroll to see all output.
let eventLoop = require("event_loop");
let gui = require("gui");
let textBoxView = require("gui/text_box");
let text = "SPI demo\n";
let textBox = require("gui/text_box").makeWith({
let textBox = textBoxView.makeWith({
focus: "end",
font: "text",
text: text,
@@ -26,4 +26,16 @@ storage.remove(path);
print("Done")
// You don't need to close the file after each operation, this is just to show some different ways to use the API
// There's also many more functions and options, check type definitions in firmware repo
// There's also many more functions and options, check type definitions in firmware repo
// There is also virtual API, which is not available in all firmwares
// Allows you to mount disk images (like mass storage) and read/modify their contents
// If you want to use this as an optional feature, not essential to your script:
// if (doesSdkSupport(["storage-virtual"])) {
// storage.virtualInit("/ext/disk_image.img");
// storage.virtualMount();
// // Read/modify data from /mnt filesystem
// storage.virtualQuit();
// }
// If instead virtual API is essential to your script, put this near beginning of script:
// checkSdkFeatures(["storage-virtual"]);
@@ -1,3 +1,6 @@
// Script cannot work without subghz module so check before
checkSdkFeatures(["subghz"]);
let subghz = require("subghz");
subghz.setup();
@@ -1,12 +1,39 @@
// Script cannot work without usbdisk module so check before
checkSdkFeatures(["usbdisk"]);
let usbdisk = require("usbdisk");
// print("Creating image...");
// usbdisk.createImage("/ext/apps_data/mass_storage/128MB.img", 128 * 1024 * 1024);
let storage = require("storage");
let imagePath = "/ext/apps_data/mass_storage/128MB.img";
let imageSize = 128 * 1024 * 1024;
let imageExisted = storage.fileExists(imagePath);
if (imageExisted) {
print("Disk image '128MB' already exists");
} else {
// CreateImage isn't necessary to overall function, check when its used not at script start
if (doesSdkSupport(["usbdisk-createimage"])) {
print("Creating disk image '128MB'...");
usbdisk.createImage(imagePath, imageSize);
} else {
die("Disk image '128MB' not present, can't auto-create");
}
}
print("Starting UsbDisk...");
usbdisk.start("/ext/apps_data/mass_storage/128MB.img");
print("Started, waiting until ejected...");
while (!usbdisk.wasEjected()) {
delay(1000);
}
print("Ejected, stopping UsbDisk...");
usbdisk.stop();
if (!imageExisted) {
print("Removing disk image...");
storage.remove(imagePath);
}
print("Done");
@@ -1,17 +1,20 @@
// Script cannot work without widget module so check before
checkSdkFeatures(["widget"]);
let widget = require("widget");
let demo_seconds = 30;
print("Loading file", __filepath);
print("From directory", __dirpath);
print("Loading file", __filename);
print("From directory", __dirname);
// addText supports "Primary" and "Secondary" font sizes.
widget.addText(10, 10, "Primary", "Example JS widget");
widget.addText(10, 20, "Secondary", "Example widget from JS!");
// load a Xbm file from the same directory as this script.
widget.addText(0, 30, "Secondary", __filepath);
let logo = widget.loadImageXbm(__dirpath + "/widget-js.fxbm");
widget.addText(0, 30, "Secondary", __filename);
let logo = widget.loadImageXbm(__dirname + "/widget-js.fxbm");
// add a line (x1, y1, x2, y2)
widget.addLine(10, 35, 120, 35);
@@ -34,8 +37,12 @@ widget.addDot(102, 44);
widget.addDot(104, 43);
// add an icon (x, y, icon)
widget.addIcon(100, 50, "ButtonUp_7x4");
widget.addIcon(100, 55, "ButtonDown_7x4");
// not available in all firmwares, but not essential for this script's
// functionality, so we just check at runtime and use it if it is available
if (doesSdkSupport(["widget-addicon"])) {
widget.addIcon(100, 50, "ButtonUp_7x4");
widget.addIcon(100, 55, "ButtonDown_7x4");
}
// add a glyph (x, y, glyph)
widget.addGlyph(115, 50, "#".charCodeAt(0));
@@ -32,6 +32,12 @@ let views = {
loading: loading.make(),
};
// Enable illegal filename symbols since we're not choosing filenames, gives more flexibility
// Not available in all firmwares, good idea to check if it is supported
if (doesSdkSupport(["gui-textinput-illegalsymbols"])) {
views.textInput.set("illegalSymbols", true);
}
eventLoop.subscribe(views.dialog.input, function (_sub, button, gui, views) {
if (button === "center") {
gui.viewDispatcher.switchTo(views.textInput);
+7 -9
View File
@@ -204,15 +204,13 @@ void js_cli_execute(Cli* cli, FuriString* args, void* context) {
furi_record_close(RECORD_STORAGE);
}
#include <flipper_application/flipper_application.h>
#include <cli/cli_i.h>
CLI_PLUGIN_WRAPPER("js", js_cli_execute)
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = CLI_PLUGIN_APP_ID,
.ep_api_version = CLI_PLUGIN_API_VERSION,
.entry_point = &js_cli_execute,
};
const FlipperAppPluginDescriptor* js_cli_plugin_ep(void) {
return &plugin_descriptor;
void js_app_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "js", CliCommandFlagDefault, js_cli_execute_wrapper, NULL);
furi_record_close(RECORD_CLI);
#endif
}
+153
View File
@@ -1,6 +1,8 @@
#include <core/common_defines.h>
#include "js_modules.h"
#include <m-array.h>
#include <dialogs/dialogs.h>
#include <assets_icons.h>
#include "modules/js_flipper.h"
#ifdef FW_CFG_unit_tests
@@ -76,6 +78,12 @@ JsModuleData* js_find_loaded_module(JsModules* instance, const char* name) {
}
mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len) {
// Ignore the initial part of the module name
const char* optional_module_prefix = "@" JS_SDK_VENDOR "/fz-sdk/";
if(strncmp(name, optional_module_prefix, strlen(optional_module_prefix)) == 0) {
name += strlen(optional_module_prefix);
}
// Check if module is already installed
JsModuleData* module_inst = js_find_loaded_module(modules, name);
if(module_inst) { //-V547
@@ -175,3 +183,148 @@ void* js_module_get(JsModules* modules, const char* name) {
furi_string_free(module_name);
return module_inst ? module_inst->context : NULL;
}
typedef enum {
JsSdkCompatStatusCompatible,
JsSdkCompatStatusFirmwareTooOld,
JsSdkCompatStatusFirmwareTooNew,
} JsSdkCompatStatus;
/**
* @brief Checks compatibility between the firmware and the JS SDK version
* expected by the script
*/
static JsSdkCompatStatus
js_internal_sdk_compatibility_status(int32_t exp_major, int32_t exp_minor) {
if(exp_major < JS_SDK_MAJOR) return JsSdkCompatStatusFirmwareTooNew;
if(exp_major > JS_SDK_MAJOR || exp_minor > JS_SDK_MINOR)
return JsSdkCompatStatusFirmwareTooOld;
return JsSdkCompatStatusCompatible;
}
#define JS_SDK_COMPAT_ARGS \
int32_t major, minor; \
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&major), JS_ARG_INT32(&minor));
void js_sdk_compatibility_status(struct mjs* mjs) {
JS_SDK_COMPAT_ARGS;
JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor);
switch(status) {
case JsSdkCompatStatusCompatible:
mjs_return(mjs, mjs_mk_string(mjs, "compatible", ~0, 0));
return;
case JsSdkCompatStatusFirmwareTooOld:
mjs_return(mjs, mjs_mk_string(mjs, "firmwareTooOld", ~0, 0));
return;
case JsSdkCompatStatusFirmwareTooNew:
mjs_return(mjs, mjs_mk_string(mjs, "firmwareTooNew", ~0, 0));
return;
}
}
void js_is_sdk_compatible(struct mjs* mjs) {
JS_SDK_COMPAT_ARGS;
JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor);
mjs_return(mjs, mjs_mk_boolean(mjs, status == JsSdkCompatStatusCompatible));
}
/**
* @brief Asks the user whether to continue executing an incompatible script
*/
static bool js_internal_compat_ask_user(const char* message) {
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
DialogMessage* dialog = dialog_message_alloc();
dialog_message_set_header(dialog, message, 64, 0, AlignCenter, AlignTop);
dialog_message_set_text(
dialog, "This script may not\nwork as expected", 79, 32, AlignCenter, AlignCenter);
dialog_message_set_icon(dialog, &I_Warning_30x23, 0, 18);
dialog_message_set_buttons(dialog, "Go back", NULL, "Run anyway");
DialogMessageButton choice = dialog_message_show(dialogs, dialog);
dialog_message_free(dialog);
furi_record_close(RECORD_DIALOGS);
return choice == DialogMessageButtonRight;
}
void js_check_sdk_compatibility(struct mjs* mjs) {
JS_SDK_COMPAT_ARGS;
JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor);
if(status != JsSdkCompatStatusCompatible) {
FURI_LOG_E(
TAG,
"Script requests JS SDK %ld.%ld, firmware provides JS SDK %d.%d",
major,
minor,
JS_SDK_MAJOR,
JS_SDK_MINOR);
const char* message = (status == JsSdkCompatStatusFirmwareTooOld) ? "Outdated Firmware" :
"Outdated Script";
if(!js_internal_compat_ask_user(message)) {
JS_ERROR_AND_RETURN(mjs, MJS_NOT_IMPLEMENTED_ERROR, "Incompatible script");
}
}
}
static const char* extra_features[] = {
"baseline", // dummy "feature"
// extra modules
"blebeacon",
"i2c",
"spi",
"subghz",
"usbdisk",
"vgm",
"widget",
// extra features
"gui-textinput-illegalsymbols",
"storage-virtual",
"usbdisk-createimage",
"widget-addicon",
};
/**
* @brief Determines whether a feature is supported
*/
static bool js_internal_supports(const char* feature) {
for(size_t i = 0; i < COUNT_OF(extra_features); i++) { // -V1008
if(strcmp(feature, extra_features[i]) == 0) return true;
}
return false;
}
/**
* @brief Determines whether all of the requested features are supported
*/
static bool js_internal_supports_all_of(struct mjs* mjs, mjs_val_t feature_arr) {
furi_assert(mjs_is_array(feature_arr));
for(size_t i = 0; i < mjs_array_length(mjs, feature_arr); i++) {
mjs_val_t feature = mjs_array_get(mjs, feature_arr, i);
const char* feature_str = mjs_get_string(mjs, &feature, NULL);
if(!feature_str) return false;
if(!js_internal_supports(feature_str)) return false;
}
return true;
}
void js_does_sdk_support(struct mjs* mjs) {
mjs_val_t features;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ARR(&features));
mjs_return(mjs, mjs_mk_boolean(mjs, js_internal_supports_all_of(mjs, features)));
}
void js_check_sdk_features(struct mjs* mjs) {
mjs_val_t features;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ARR(&features));
if(!js_internal_supports_all_of(mjs, features)) {
FURI_LOG_E(TAG, "Script requests unsupported features");
if(!js_internal_compat_ask_user("Unsupported Feature")) {
JS_ERROR_AND_RETURN(mjs, MJS_NOT_IMPLEMENTED_ERROR, "Incompatible script");
}
}
}
+30
View File
@@ -9,6 +9,11 @@
#define PLUGIN_APP_ID "js"
#define PLUGIN_API_VERSION 1
#define JS_SDK_VENDOR_FIRMWARE "momentum"
#define JS_SDK_VENDOR "flipperdevices"
#define JS_SDK_MAJOR 0
#define JS_SDK_MINOR 1
/**
* @brief Returns the foreign pointer in `obj["_"]`
*/
@@ -275,3 +280,28 @@ mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_le
* @returns Pointer to module context, NULL if the module is not instantiated
*/
void* js_module_get(JsModules* modules, const char* name);
/**
* @brief `sdkCompatibilityStatus` function
*/
void js_sdk_compatibility_status(struct mjs* mjs);
/**
* @brief `isSdkCompatible` function
*/
void js_is_sdk_compatible(struct mjs* mjs);
/**
* @brief `checkSdkCompatibility` function
*/
void js_check_sdk_compatibility(struct mjs* mjs);
/**
* @brief `doesSdkSupport` function
*/
void js_does_sdk_support(struct mjs* mjs);
/**
* @brief `checkSdkFeatures` function
*/
void js_check_sdk_features(struct mjs* mjs);
-11
View File
@@ -1,11 +0,0 @@
#include <cli/cli_i.h>
static void js_cli_execute_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("js", cli, args, context);
}
void js_app_on_system_start(void) {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "js", CliCommandFlagDefault, js_cli_execute_wrapper, NULL);
furi_record_close(RECORD_CLI);
}
+23 -11
View File
@@ -244,6 +244,8 @@ static int32_t js_thread(void* arg) {
struct mjs* mjs = mjs_create(worker);
worker->modules = js_modules_create(mjs, worker->resolver);
mjs_val_t global = mjs_get_global(mjs);
mjs_val_t console_obj = mjs_mk_object(mjs);
if(worker->path) {
FuriString* dirpath = furi_string_alloc();
path_extract_dirname(furi_string_get_cstr(worker->path), dirpath);
@@ -262,18 +264,28 @@ static int32_t js_thread(void* arg) {
mjs_mk_string(mjs, furi_string_get_cstr(dirpath), furi_string_size(dirpath), true));
furi_string_free(dirpath);
}
mjs_set(mjs, global, "print", ~0, MJS_MK_FN(js_print));
mjs_set(mjs, global, "delay", ~0, MJS_MK_FN(js_delay));
mjs_set(mjs, global, "ffi_address", ~0, MJS_MK_FN(js_ffi_address));
mjs_set(mjs, global, "require", ~0, MJS_MK_FN(js_require));
mjs_set(mjs, global, "parseInt", ~0, MJS_MK_FN(js_parse_int));
mjs_val_t console_obj = mjs_mk_object(mjs);
mjs_set(mjs, console_obj, "log", ~0, MJS_MK_FN(js_console_log));
mjs_set(mjs, console_obj, "warn", ~0, MJS_MK_FN(js_console_warn));
mjs_set(mjs, console_obj, "error", ~0, MJS_MK_FN(js_console_error));
mjs_set(mjs, console_obj, "debug", ~0, MJS_MK_FN(js_console_debug));
mjs_set(mjs, global, "console", ~0, console_obj);
JS_ASSIGN_MULTI(mjs, global) {
JS_FIELD("print", MJS_MK_FN(js_print));
JS_FIELD("delay", MJS_MK_FN(js_delay));
JS_FIELD("parseInt", MJS_MK_FN(js_parse_int));
JS_FIELD("ffi_address", MJS_MK_FN(js_ffi_address));
JS_FIELD("require", MJS_MK_FN(js_require));
JS_FIELD("console", console_obj);
JS_FIELD("sdkCompatibilityStatus", MJS_MK_FN(js_sdk_compatibility_status));
JS_FIELD("isSdkCompatible", MJS_MK_FN(js_is_sdk_compatible));
JS_FIELD("checkSdkCompatibility", MJS_MK_FN(js_check_sdk_compatibility));
JS_FIELD("doesSdkSupport", MJS_MK_FN(js_does_sdk_support));
JS_FIELD("checkSdkFeatures", MJS_MK_FN(js_check_sdk_features));
}
JS_ASSIGN_MULTI(mjs, console_obj) {
JS_FIELD("log", MJS_MK_FN(js_console_log));
JS_FIELD("warn", MJS_MK_FN(js_console_warn));
JS_FIELD("error", MJS_MK_FN(js_console_error));
JS_FIELD("debug", MJS_MK_FN(js_console_debug));
}
mjs_set_ffi_resolver(mjs, js_dlsym, worker->resolver);
@@ -80,7 +80,7 @@ static void js_event_loop_callback_generic(void* param) {
/**
* @brief Handles non-timer events
*/
static bool js_event_loop_callback(void* object, void* param) {
static void js_event_loop_callback(void* object, void* param) {
JsEventLoopCallbackContext* context = param;
if(context->transformer) {
@@ -102,8 +102,6 @@ static bool js_event_loop_callback(void* object, void* param) {
}
js_event_loop_callback_generic(param);
return true;
}
/**
@@ -27,11 +27,19 @@ static void js_flipper_get_battery(struct mjs* mjs) {
void* js_flipper_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
UNUSED(modules);
mjs_val_t sdk_vsn = mjs_mk_array(mjs);
mjs_array_push(mjs, sdk_vsn, mjs_mk_number(mjs, JS_SDK_MAJOR));
mjs_array_push(mjs, sdk_vsn, mjs_mk_number(mjs, JS_SDK_MINOR));
mjs_val_t flipper_obj = mjs_mk_object(mjs);
mjs_set(mjs, flipper_obj, "getModel", ~0, MJS_MK_FN(js_flipper_get_model));
mjs_set(mjs, flipper_obj, "getName", ~0, MJS_MK_FN(js_flipper_get_name));
mjs_set(mjs, flipper_obj, "getBatteryCharge", ~0, MJS_MK_FN(js_flipper_get_battery));
*object = flipper_obj;
JS_ASSIGN_MULTI(mjs, flipper_obj) {
JS_FIELD("getModel", MJS_MK_FN(js_flipper_get_model));
JS_FIELD("getName", MJS_MK_FN(js_flipper_get_name));
JS_FIELD("getBatteryCharge", MJS_MK_FN(js_flipper_get_battery));
JS_FIELD("firmwareVendor", mjs_mk_string(mjs, JS_SDK_VENDOR_FIRMWARE, ~0, false));
JS_FIELD("jsSdkVersion", sdk_vsn);
}
return (void*)1;
}
+2 -2
View File
@@ -220,7 +220,7 @@ static void js_gpio_interrupt(struct mjs* mjs) {
* let gpio = require("gpio");
* let pot = gpio.get("pc0");
* pot.init({ direction: "in", inMode: "analog" });
* print("voltage:" pot.read_analog(), "mV");
* print("voltage:" pot.readAnalog(), "mV");
* ```
*/
static void js_gpio_read_analog(struct mjs* mjs) {
@@ -273,7 +273,7 @@ static void js_gpio_get(struct mjs* mjs) {
mjs_set(mjs, manager, "init", ~0, MJS_MK_FN(js_gpio_init));
mjs_set(mjs, manager, "write", ~0, MJS_MK_FN(js_gpio_write));
mjs_set(mjs, manager, "read", ~0, MJS_MK_FN(js_gpio_read));
mjs_set(mjs, manager, "read_analog", ~0, MJS_MK_FN(js_gpio_read_analog));
mjs_set(mjs, manager, "readAnalog", ~0, MJS_MK_FN(js_gpio_read_analog));
mjs_set(mjs, manager, "interrupt", ~0, MJS_MK_FN(js_gpio_interrupt));
mjs_return(mjs, manager);
@@ -85,7 +85,7 @@ static bool default_text_assign(
context->buffer = realloc(context->buffer, context->buffer_size); //-V701
}
// Also trim excess previous data with strlcpy()
strlcpy(context->buffer, value.string, context->buffer_size);
strlcpy(context->buffer, value.string, context->buffer_size); //-V575
text_input_set_result_callback(
input,
(TextInputCallback)input_callback,
@@ -115,6 +115,18 @@ static bool default_text_clear_assign(
return true;
}
static bool illegal_symbols_assign(
struct mjs* mjs,
TextInput* input,
JsViewPropValue value,
JsKbdContext* context) {
UNUSED(mjs);
UNUSED(context);
text_input_show_illegal_symbols(input, value.boolean);
return true;
}
static JsKbdContext* ctx_make(struct mjs* mjs, TextInput* input, mjs_val_t view_obj) {
JsKbdContext* context = malloc(sizeof(JsKbdContext));
*context = (JsKbdContext){
@@ -135,7 +147,6 @@ static JsKbdContext* ctx_make(struct mjs* mjs, TextInput* input, mjs_val_t view_
.transformer_context = context,
},
};
text_input_show_illegal_symbols(input, true); // Temporary until prop is available
text_input_set_result_callback(
input,
(TextInputCallback)input_callback,
@@ -162,7 +173,7 @@ static const JsViewDescriptor view_descriptor = {
.get_view = (JsViewGetView)text_input_get_view,
.custom_make = (JsViewCustomMake)ctx_make,
.custom_destroy = (JsViewCustomDestroy)ctx_destroy,
.prop_cnt = 5,
.prop_cnt = 6,
.props = {
(JsViewPropDescriptor){
.name = "header",
@@ -184,6 +195,10 @@ static const JsViewDescriptor view_descriptor = {
.name = "defaultTextClear",
.type = JsViewPropTypeBool,
.assign = (JsViewPropAssign)default_text_clear_assign},
(JsViewPropDescriptor){
.name = "illegalSymbols",
.type = JsViewPropTypeBool,
.assign = (JsViewPropAssign)illegal_symbols_assign},
}};
JS_GUI_VIEW_DEF(text_input, &view_descriptor);
@@ -223,7 +223,7 @@ static void js_subghz_transmit_file(struct mjs* mjs) {
// - "repeat" as variable and loop in this code applies to RAW files only
// parsed files handle repeat in protocol layer instead
// We keep 0 as default, or literal value if specified by user
// If user did not specify, -1 is detected below, and we use:
// If user did not specify, 0 is detected below, and we use:
// - 1 repeat for RAW
// - 10 repeats for parsed, which is passed to protocol, and we loop once here
uint32_t repeat = 0;
@@ -0,0 +1,24 @@
# Momentum FW JavaScript SDK Wizard
This package contains an interactive wizard that lets you scaffold a JavaScript
application for Flipper Zero using the Momentum Firmware JS SDK.
This is a fork of the [Official Flipper Zero JS SDK Wizard](https://www.npmjs.com/package/@flipperdevices/create-fz-app),
configured to use the [Momentum JavaScript SDK]((https://www.npmjs.com/package/@next-flip/fz-sdk-mntm)) instead.
No other changes are included.
## Getting started
Create your application using the interactive wizard:
```shell
npx @next-flip/create-fz-app-mntm@latest
```
Then, enter the directory with your application and launch it:
```shell
cd my-flip-app
npm start
```
You are free to use `pnpm` or `yarn` instead of `npm`.
## Documentation
Check out the [JavaScript section in the Developer Documentation](https://developer.flipper.net/flipperzero/doxygen/js.html)
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env node
import prompts from "prompts";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "url";
import { spawnSync } from "node:child_process";
import { replaceInFileSync } from "replace-in-file";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
(async () => {
const { name, pkgManager, confirm } = await prompts([
{
type: "text",
name: "name",
message: "What is the name of your project?",
initial: "my-flip-app"
},
{
type: "select",
name: "pkgManager",
message: "What package manager should your project use?",
choices: [
{ title: "npm", value: "npm" },
{ title: "pnpm", value: "pnpm" },
{ title: "yarn", value: "yarn" },
],
},
{
type: "confirm",
name: "confirm",
message: "Create project?",
initial: true,
},
]);
if (!confirm)
return;
if (fs.existsSync(name)) {
const { replace } = await prompts([
{
type: "confirm",
name: "replace",
message: `File or directory \`${name}\` already exists. Continue anyway?`,
initial: false,
},
]);
if (!replace)
return;
}
fs.rmSync(name, { recursive: true, force: true });
console.log("Copying files...");
fs.cpSync(path.resolve(__dirname, "template"), name, { recursive: true });
replaceInFileSync({ files: `${name}/**/*`, from: /<app_name>/g, to: name });
console.log("Installing packages...");
spawnSync("bash", ["-c", `cd ${name} && ${pkgManager} install`], {
cwd: process.cwd(),
detached: true,
stdio: "inherit",
});
console.log(`Done! Created ${name}. Run \`cd ${name} && ${pkgManager} start\` to run it on your Flipper.`);
})();
@@ -0,0 +1,25 @@
{
"name": "@next-flip/create-fz-app-mntm",
"version": "0.1.1",
"description": "Template package for JS apps for Flipper Zero using Momentum Custom Firmware JS SDK",
"bin": "index.js",
"type": "module",
"keywords": [
"momentum",
"momentum firmware",
"next-flip",
"flipper",
"flipper zero"
],
"author": "Flipper Devices",
"license": "GPL-3.0-only",
"repository": {
"type": "git",
"url": "git+https://github.com/Next-Flip/Momentum-Firmware.git",
"directory": "applications/system/js_app/packages/create-fz-app"
},
"dependencies": {
"prompts": "^2.4.2",
"replace-in-file": "^8.2.0"
}
}
@@ -0,0 +1,373 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
prompts:
specifier: ^2.4.2
version: 2.4.2
replace-in-file:
specifier: ^8.2.0
version: 8.2.0
packages:
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
ansi-regex@6.1.0:
resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
engines: {node: '>=12'}
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
ansi-styles@6.2.1:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
engines: {node: '>=12'}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
brace-expansion@2.0.1:
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
chalk@5.3.0:
resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
foreground-child@3.3.0:
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
engines: {node: '>=14'}
get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
glob@10.4.5:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
hasBin: true
is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
kleur@3.0.3:
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
engines: {node: '>=6'}
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
minipass@7.1.2:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
engines: {node: '>=16 || 14 >=14.17'}
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
path-scurry@1.11.1:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'}
prompts@2.4.2:
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
engines: {node: '>= 6'}
replace-in-file@8.2.0:
resolution: {integrity: sha512-hMsQtdYHwWviQT5ZbNsgfu0WuCiNlcUSnnD+aHAL081kbU9dPkPocDaHlDvAHKydTWWpx1apfcEcmvIyQk3CpQ==}
engines: {node: '>=18'}
hasBin: true
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
shebang-regex@3.0.0:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
signal-exit@4.1.0:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
sisteransi@1.0.5:
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
string-width@5.1.2:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
strip-ansi@7.1.0:
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
engines: {node: '>=12'}
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
hasBin: true
wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
wrap-ansi@8.1.0:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
snapshots:
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
string-width-cjs: string-width@4.2.3
strip-ansi: 7.1.0
strip-ansi-cjs: strip-ansi@6.0.1
wrap-ansi: 8.1.0
wrap-ansi-cjs: wrap-ansi@7.0.0
'@pkgjs/parseargs@0.11.0':
optional: true
ansi-regex@5.0.1: {}
ansi-regex@6.1.0: {}
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
ansi-styles@6.2.1: {}
balanced-match@1.0.2: {}
brace-expansion@2.0.1:
dependencies:
balanced-match: 1.0.2
chalk@5.3.0: {}
cliui@8.0.1:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
color-name@1.1.4: {}
cross-spawn@7.0.3:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
eastasianwidth@0.2.0: {}
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
escalade@3.2.0: {}
foreground-child@3.3.0:
dependencies:
cross-spawn: 7.0.3
signal-exit: 4.1.0
get-caller-file@2.0.5: {}
glob@10.4.5:
dependencies:
foreground-child: 3.3.0
jackspeak: 3.4.3
minimatch: 9.0.5
minipass: 7.1.2
package-json-from-dist: 1.0.1
path-scurry: 1.11.1
is-fullwidth-code-point@3.0.0: {}
isexe@2.0.0: {}
jackspeak@3.4.3:
dependencies:
'@isaacs/cliui': 8.0.2
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
kleur@3.0.3: {}
lru-cache@10.4.3: {}
minimatch@9.0.5:
dependencies:
brace-expansion: 2.0.1
minipass@7.1.2: {}
package-json-from-dist@1.0.1: {}
path-key@3.1.1: {}
path-scurry@1.11.1:
dependencies:
lru-cache: 10.4.3
minipass: 7.1.2
prompts@2.4.2:
dependencies:
kleur: 3.0.3
sisteransi: 1.0.5
replace-in-file@8.2.0:
dependencies:
chalk: 5.3.0
glob: 10.4.5
yargs: 17.7.2
require-directory@2.1.1: {}
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
shebang-regex@3.0.0: {}
signal-exit@4.1.0: {}
sisteransi@1.0.5: {}
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
string-width@5.1.2:
dependencies:
eastasianwidth: 0.2.0
emoji-regex: 9.2.2
strip-ansi: 7.1.0
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
strip-ansi@7.1.0:
dependencies:
ansi-regex: 6.1.0
which@2.0.2:
dependencies:
isexe: 2.0.0
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi@8.1.0:
dependencies:
ansi-styles: 6.2.1
string-width: 5.1.2
strip-ansi: 7.1.0
y18n@5.0.8: {}
yargs-parser@21.1.1: {}
yargs@17.7.2:
dependencies:
cliui: 8.0.1
escalade: 3.2.0
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 21.1.1

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