mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-07-04 22:33:36 -07:00
Merge remote-tracking branch 'mntm/dev' into js-app-internal
This commit is contained in:
@@ -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,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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -65,3 +65,6 @@ PVS-Studio.log
|
||||
.gdbinit
|
||||
|
||||
/fbt_options_local.py
|
||||
|
||||
# JS packages
|
||||
node_modules/
|
||||
|
||||
+26
-5
@@ -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
Submodule applications/external updated: d0300c7a5a...68bb084ec9
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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"],
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ App(
|
||||
provides=[
|
||||
"passport",
|
||||
"system_settings",
|
||||
"clock_settings",
|
||||
"about",
|
||||
],
|
||||
)
|
||||
|
||||
+1
-1
@@ -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);
|
||||
@@ -5,7 +5,6 @@ App(
|
||||
provides=[
|
||||
"updater_app",
|
||||
"js_app",
|
||||
"js_app_start",
|
||||
"findmy_startup",
|
||||
# "archive",
|
||||
],
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -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
Reference in New Issue
Block a user