Compare commits
150 Commits
un1-3fd30a
...
un1-72713d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c08564d37 | ||
|
|
5b8311cdea | ||
|
|
68429e191d | ||
|
|
5e7bcea29d | ||
|
|
a139f015b9 | ||
|
|
6579576490 | ||
|
|
57251eb028 | ||
|
|
f33ea3ebe0 | ||
|
|
84d12da45a | ||
|
|
72713d6f4e | ||
|
|
468bc1dace | ||
|
|
56f760aa07 | ||
|
|
6db6d123d5 | ||
|
|
68009c6230 | ||
|
|
02c27becb0 | ||
|
|
576dab02a4 | ||
|
|
4942bd2105 | ||
|
|
1ee82ba865 | ||
|
|
6e9658608e | ||
|
|
d49ca17824 | ||
|
|
fc776446de | ||
|
|
5a7fa30199 | ||
|
|
e4cdae4922 | ||
|
|
c7a67c1308 | ||
|
|
fd3e70492d | ||
|
|
a4ee73b470 | ||
|
|
8f5cbdc4c2 | ||
|
|
6d999abb80 | ||
|
|
89d49fdba7 | ||
|
|
04d67ca8ae | ||
|
|
4f7ca617cc | ||
|
|
f61a8fda53 | ||
|
|
5e35e51c57 | ||
|
|
dfbe21e720 | ||
|
|
18e7d2eb41 | ||
|
|
e7aaf3dbb2 | ||
|
|
76aecb597a | ||
|
|
d2018dfa1d | ||
|
|
8ec5527ae4 | ||
|
|
3a50021348 | ||
|
|
20c63664ca | ||
|
|
9b6abd8ef0 | ||
|
|
f96f2e2411 | ||
|
|
6442caa3e4 | ||
|
|
f06930e4ae | ||
|
|
865baed0bb | ||
|
|
f81999ea4a | ||
|
|
89a4b77e73 | ||
|
|
ead9f134f4 | ||
|
|
9ff29d12b2 | ||
|
|
5f3637ca44 | ||
|
|
effcb445ce | ||
|
|
55bad280ee | ||
|
|
b003ede76c | ||
|
|
57362b3eab | ||
|
|
cb0d9ec591 | ||
|
|
dad4772bec | ||
|
|
a16542cda6 | ||
|
|
a5d22154a9 | ||
|
|
ce173fd44c | ||
|
|
d547307357 | ||
|
|
c198a51b1c | ||
|
|
d5b239595f | ||
|
|
947e44ce86 | ||
|
|
55f8beef9f | ||
|
|
e46e6f8ee9 | ||
|
|
ff6c4f9957 | ||
|
|
8fdee1e460 | ||
|
|
558f85603d | ||
|
|
b909321699 | ||
|
|
ff52f7d9ba | ||
|
|
abfe53f156 | ||
|
|
9a2228bfa4 | ||
|
|
da17adb5ee | ||
|
|
7b16de2d6f | ||
|
|
735056628c | ||
|
|
9941df48bb | ||
|
|
8e0e4b5e2c | ||
|
|
220adf2375 | ||
|
|
39cd10f061 | ||
|
|
af1fb2018a | ||
|
|
3dde6089fc | ||
|
|
5f28eafcd2 | ||
|
|
94d2d5c99f | ||
|
|
3a569d4be8 | ||
|
|
db2f4847ba | ||
|
|
50dc2d7389 | ||
|
|
ede3bac799 | ||
|
|
eb4ff3c0fd | ||
|
|
afff1adf8f | ||
|
|
92a738bf77 | ||
|
|
b3d9523322 | ||
|
|
155cbeeb11 | ||
|
|
0d273bd15c | ||
|
|
2e047ff411 | ||
|
|
80a406afd3 | ||
|
|
6b5631e5b0 | ||
|
|
80735d4f51 | ||
|
|
e1e46b89b1 | ||
|
|
b7bdc4c985 | ||
|
|
f429d14ab5 | ||
|
|
744b61ca28 | ||
|
|
8520daf28b | ||
|
|
aa9f958f07 | ||
|
|
b3f96306ed | ||
|
|
022315e93d | ||
|
|
702786078d | ||
|
|
e78d73c35f | ||
|
|
2552278a3d | ||
|
|
34406f0607 | ||
|
|
04f5ad83f8 | ||
|
|
06c0adfe98 | ||
|
|
b0d4146c76 | ||
|
|
be293757c0 | ||
|
|
79e7c87b94 | ||
|
|
b6dfeac7ca | ||
|
|
349a151330 | ||
|
|
40dc80499f | ||
|
|
b0c31da36a | ||
|
|
a76259add9 | ||
|
|
3f3ee1822a | ||
|
|
0714df4a4a | ||
|
|
52361b4adf | ||
|
|
61fe66c178 | ||
|
|
016ebd3afc | ||
|
|
82b9d74b38 | ||
|
|
6d1929af25 | ||
|
|
384397c282 | ||
|
|
0abd54ccc8 | ||
|
|
ea833d891f | ||
|
|
9152299562 | ||
|
|
906124b091 | ||
|
|
9c0391a887 | ||
|
|
7ded162c94 | ||
|
|
382f620aff | ||
|
|
323a56e987 | ||
|
|
b65a2e9c94 | ||
|
|
eed8cd1824 | ||
|
|
500456b03d | ||
|
|
4b8221d813 | ||
|
|
c7a454752a | ||
|
|
b1f8073333 | ||
|
|
b4a3ac468f | ||
|
|
7643fdad7c | ||
|
|
b86f42e7fb | ||
|
|
9ba7e625ca | ||
|
|
1adf76d54d | ||
|
|
7ec4cb4b7a | ||
|
|
0c5146e047 | ||
|
|
127b700642 |
@@ -31,7 +31,6 @@ steps:
|
||||
- name: "Bundle self-update packages"
|
||||
image: kramos/alpine-zip
|
||||
commands:
|
||||
- tar czpf artifacts-default/flipper-z-f7-update-${DRONE_TAG}.tgz -C artifacts-default f7-update-${DRONE_TAG}
|
||||
- cp artifacts-default/flipper-z-f7-update-${DRONE_TAG}.tgz .
|
||||
- zip -r artifacts-default/flipper-z-f7-update-${DRONE_TAG}.zip artifacts-default/f7-update-${DRONE_TAG}
|
||||
- rm -rf artifacts-default/f7-update-${DRONE_TAG}
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -4,5 +4,5 @@ contact_links:
|
||||
url: https://t.me/flipperzero_unofficial
|
||||
about: Unofficial Telegram chat
|
||||
- name: Discord
|
||||
url: https://discord.gg/58D6E8BtTU
|
||||
url: https://discord.unleashedflip.com
|
||||
about: Unofficial Discord Community
|
||||
|
||||
12
CHANGELOG.md
@@ -1,7 +1,12 @@
|
||||
### New changes
|
||||
* SubGHz: Fixed bugs with Decode option for RAW signals (blank screen, broken files)
|
||||
* SubGHz: Allowed usage of hopper with detect raw feature (with attempt to fix) - WARNING -> This feature is very unstable, may cause crashes(with lost of captured signal), use at your own risk, it was enabled due to many requests from users
|
||||
* PR -> Docs, fix description - Slight update for newest dev method (by @UberGuidoZ | PR #93)
|
||||
* PR -> NFC: New mifare classic keys (by @ankris812 | PR #119)
|
||||
* New API version 3.2 -> 4.1 (due to breaking changes in subghz part) (old apps needs to be recompiled, update for extra apps pack is coming soon)
|
||||
* OFW PR: WeatherStation plugin and SubGHz changes (OFW PR 1833 by Skorpionm)
|
||||
* OFW: Allow pins 0 and 1 as RTS/DTR for USB UART Bridge
|
||||
* OFW: Picopass: Read Elite
|
||||
* OFW: SubGhz: CAME Wrong number of bits in key (add protocol Airforce)
|
||||
* OFW: Forced RAW receive option for Infrared CLI
|
||||
* OFW: scripts: fixed c2 bundle format
|
||||
|
||||
#### [🎲 Download extra apps pack](https://download-directory.github.io/?url=https://github.com/UberGuidoZ/Flipper/tree/main/Applications/Unleashed)
|
||||
|
||||
@@ -10,6 +15,7 @@
|
||||
[-> Download qFlipper 1.2.1 (allows .tgz installation) (official link)](https://update.flipperzero.one/builds/qFlipper/1.2.1/)
|
||||
|
||||
## Please support development of the project
|
||||
* Boosty: https://boosty.to/mmxdev
|
||||
* ETH/BSC/ERC20-Tokens: `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`
|
||||
* BTC: `bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9`
|
||||
* DOGE: `D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`
|
||||
|
||||
31
ReadMe.md
@@ -16,7 +16,7 @@ Please help us implement emulation for all subghz dynamic (rolling code) protoco
|
||||
<br>
|
||||
Our Discord Community:
|
||||
<br>
|
||||
<a href="https://discord.gg/flipperzero-unofficial"><img src="https://discordapp.com/api/guilds/937479784148115456/widget.png?style=banner4" alt="Unofficial Discord Community"></a>
|
||||
<a href="https://discord.unleashedflip.com"><img src="https://discordapp.com/api/guilds/937479784148115456/widget.png?style=banner4" alt="Unofficial Discord Community"></a>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
@@ -44,17 +44,25 @@ Our Discord Community:
|
||||
Also check changelog in releases for latest updates!
|
||||
|
||||
### Current modified and new SubGHz protocols list:
|
||||
- HCS101
|
||||
- An-Motors
|
||||
- Keeloq [Not ALL systems supported for decode or emulation yet!] - [Supported manufacturers list](https://0bin.net/paste/VwR2lNJY#WH9vnPgvcp7w6zVKucFCuNREKAcOij8KsJ6vqLfMn3b)
|
||||
- Keeloq: HCS101
|
||||
- Keeloq: AN-Motors
|
||||
- Keeloq: JCM Tech
|
||||
- Keeloq: MHouse
|
||||
- Keeloq: Nice Smilo
|
||||
- Keeloq: DTM Neo
|
||||
- Keeloq: FAAC RC,XT
|
||||
- Keeloq: Mutancode
|
||||
- Keeloq: Normstahl
|
||||
- CAME Atomo
|
||||
- FAAC SLH (Spa) [External seed calculation required]
|
||||
- BFT Mitto [External seed calculation required]
|
||||
- Keeloq [Not ALL systems supported yet!]
|
||||
- Nice Flor S
|
||||
- FAAC SLH (Spa) [External seed calculation required (For info conatct me in Discord: Nano#8998)]
|
||||
- BFT Mitto [External seed calculation required (For info conatct me in Discord: Nano#8998)]
|
||||
- Security+ v1 & v2
|
||||
- Star Line (saving only)
|
||||
|
||||
## Support us so we can buy equipment and develop new features
|
||||
* Boosty: https://boosty.to/mmxdev
|
||||
* ETH/BSC/ERC20-Tokens: `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`
|
||||
* BTC: `bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9`
|
||||
* DOGE: `D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`
|
||||
@@ -77,6 +85,9 @@ Also check changelog in releases for latest updates!
|
||||
- Simple Clock (timer by GMMan / settings by kowalski7cc) [(Original by CompaqDisc)](https://gist.github.com/CompaqDisc/4e329c501bd03c1e801849b81f48ea61)
|
||||
- UniversalRF Remix / Sub-GHz Remote [(by ESurge)](https://github.com/ESurge/flipperzero-firmware-unirfremix)[(updated and all protocol support added by darmiel & xMasterX)](https://github.com/darmiel/flipper-playlist/tree/feat/unirf-protocols)
|
||||
- Spectrum Analyzer (with changes) [(by jolcese)](https://github.com/jolcese/flipperzero-firmware/tree/spectrum/applications/spectrum_analyzer) - [Ultra Narrow mode & scan channels non-consecutively](https://github.com/theY4Kman/flipperzero-firmware/commits?author=theY4Kman)
|
||||
- Metronome [(by panki27)](https://github.com/panki27/Metronome)
|
||||
- DTMF Dolphin [(by litui)](https://github.com/litui/dtmf_dolphin)
|
||||
- **TOTP (Authenticator)** [(by akopachov)](https://github.com/akopachov/flipper-zero_authenticator)
|
||||
|
||||
Games:
|
||||
- DOOM (fixed) [(By p4nic4ttack)](https://github.com/p4nic4ttack/doom-flipper-zero/)
|
||||
@@ -85,6 +96,8 @@ Games:
|
||||
- Arkanoid (refactored by xMasterX) [(by gotnull)](https://github.com/gotnull/flipperzero-firmware-wPlugins)
|
||||
- Tic Tac Toe (refactored by xMasterX) [(by gotnull)](https://github.com/gotnull/flipperzero-firmware-wPlugins)
|
||||
- Tetris (with fixes) [(by jeffplang)](https://github.com/jeffplang/flipperzero-firmware/tree/tetris_game/applications/tetris_game)
|
||||
- Minesweeper [(by panki27)](https://github.com/panki27/minesweeper)
|
||||
- Heap Defence (aka Stack Attack) [(original by wquinoa & Vedmein)](https://github.com/Vedmein/flipperzero-firmware/tree/hd/svisto-perdelki) -> Ported to latest firmware by @xMasterX
|
||||
|
||||
### Other changes
|
||||
|
||||
@@ -93,7 +106,7 @@ Games:
|
||||
- SubGHz -> Detect RAW feature - [(by perspecdev)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/152)
|
||||
- SubGHz -> Save last used frequency and moduluation [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/77)
|
||||
- SubGHz -> Press OK in frequency analyzer to use detected frequency in Read modes [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/77)
|
||||
* SubGHz -> Long press OK button in SubGHz Frequency analyzer to switch to Read menu [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/79)
|
||||
- SubGHz -> Long press OK button in SubGHz Frequency analyzer to switch to Read menu [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/79)
|
||||
|
||||
# Instructions
|
||||
## [- How to install firmware](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md)
|
||||
@@ -110,6 +123,8 @@ Games:
|
||||
|
||||
## [- Configure Sub-GHz Remote App](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemotePlugin.md)
|
||||
|
||||
## [- TOTP (Authenticator) config description](https://github.com/akopachov/flipper-zero_authenticator/blob/master/.github/conf-file_description.md)
|
||||
|
||||
## [- Barcode Generator](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/BarcodeGenerator.md)
|
||||
|
||||
## [- Multi Converter](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/MultiConverter.md)
|
||||
@@ -156,7 +171,7 @@ Games:
|
||||
|
||||
# Links
|
||||
|
||||
* Unofficial Discord: [discord.gg/flipperzero-unofficial](https://discord.gg/flipperzero-unofficial)
|
||||
* Unofficial Discord: [discord.unleashedflip.com](https://discord.unleashedflip.com)
|
||||
* Docs by atmanos / How to write your own app (outdated API): [https://flipper.atmanos.com/docs/overview/intro](https://flipper.atmanos.com/docs/overview/intro)
|
||||
|
||||
* Official Docs: [http://docs.flipperzero.one](http://docs.flipperzero.one)
|
||||
|
||||
63
SConstruct
@@ -7,7 +7,6 @@
|
||||
# construction of certain targets behind command-line options.
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
DefaultEnvironment(tools=[])
|
||||
|
||||
@@ -15,17 +14,22 @@ EnsurePythonVersion(3, 8)
|
||||
|
||||
# Progress(["OwO\r", "owo\r", "uwu\r", "owo\r"], interval=15)
|
||||
|
||||
|
||||
# This environment is created only for loading options & validating file/dir existence
|
||||
fbt_variables = SConscript("site_scons/commandline.scons")
|
||||
cmd_environment = Environment(tools=[], variables=fbt_variables)
|
||||
Help(fbt_variables.GenerateHelpText(cmd_environment))
|
||||
cmd_environment = Environment(
|
||||
toolpath=["#/scripts/fbt_tools"],
|
||||
tools=[
|
||||
("fbt_help", {"vars": fbt_variables}),
|
||||
],
|
||||
variables=fbt_variables,
|
||||
)
|
||||
|
||||
# Building basic environment - tools, utility methods, cross-compilation
|
||||
# settings, gcc flags for Cortex-M4, basic builders and more
|
||||
coreenv = SConscript(
|
||||
"site_scons/environ.scons",
|
||||
exports={"VAR_ENV": cmd_environment},
|
||||
toolpath=["#/scripts/fbt_tools"],
|
||||
)
|
||||
SConscript("site_scons/cc.scons", exports={"ENV": coreenv})
|
||||
|
||||
@@ -35,41 +39,13 @@ coreenv["ROOT_DIR"] = Dir(".")
|
||||
|
||||
# Create a separate "dist" environment and add construction envs to it
|
||||
distenv = coreenv.Clone(
|
||||
tools=["fbt_dist", "openocd", "blackmagic", "jflash"],
|
||||
OPENOCD_GDB_PIPE=[
|
||||
"|openocd -c 'gdb_port pipe; log_output debug/openocd.log' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}"
|
||||
tools=[
|
||||
"fbt_dist",
|
||||
"fbt_debugopts",
|
||||
"openocd",
|
||||
"blackmagic",
|
||||
"jflash",
|
||||
],
|
||||
GDBOPTS_BASE=[
|
||||
"-ex",
|
||||
"target extended-remote ${GDBREMOTE}",
|
||||
"-ex",
|
||||
"set confirm off",
|
||||
"-ex",
|
||||
"set pagination off",
|
||||
],
|
||||
GDBOPTS_BLACKMAGIC=[
|
||||
"-ex",
|
||||
"monitor swdp_scan",
|
||||
"-ex",
|
||||
"monitor debug_bmp enable",
|
||||
"-ex",
|
||||
"attach 1",
|
||||
"-ex",
|
||||
"set mem inaccessible-by-default off",
|
||||
],
|
||||
GDBPYOPTS=[
|
||||
"-ex",
|
||||
"source debug/FreeRTOS/FreeRTOS.py",
|
||||
"-ex",
|
||||
"source debug/flipperapps.py",
|
||||
"-ex",
|
||||
"source debug/PyCortexMDebug/PyCortexMDebug.py",
|
||||
"-ex",
|
||||
"svd_load ${SVD_FILE}",
|
||||
"-ex",
|
||||
"compare-sections",
|
||||
],
|
||||
JFLASHPROJECT="${ROOT_DIR.abspath}/debug/fw.jflash",
|
||||
ENV=os.environ,
|
||||
)
|
||||
|
||||
@@ -166,7 +142,7 @@ basic_dist = distenv.DistCommand("fw_dist", distenv["DIST_DEPENDS"])
|
||||
distenv.Default(basic_dist)
|
||||
|
||||
dist_dir = distenv.GetProjetDirName()
|
||||
plugin_dist = [
|
||||
fap_dist = [
|
||||
distenv.Install(
|
||||
f"#/dist/{dist_dir}/apps/debug_elf",
|
||||
firmware_env["FW_EXTAPPS"]["debug"].values(),
|
||||
@@ -176,9 +152,9 @@ plugin_dist = [
|
||||
for dist_entry in firmware_env["FW_EXTAPPS"]["dist"].values()
|
||||
),
|
||||
]
|
||||
Depends(plugin_dist, firmware_env["FW_EXTAPPS"]["validators"].values())
|
||||
Alias("plugin_dist", plugin_dist)
|
||||
# distenv.Default(plugin_dist)
|
||||
Depends(fap_dist, firmware_env["FW_EXTAPPS"]["validators"].values())
|
||||
Alias("fap_dist", fap_dist)
|
||||
# distenv.Default(fap_dist)
|
||||
|
||||
plugin_resources_dist = list(
|
||||
distenv.Install(f"#/assets/resources/apps/{dist_entry[0]}", dist_entry[1])
|
||||
@@ -189,9 +165,10 @@ distenv.Depends(firmware_env["FW_RESOURCES"], plugin_resources_dist)
|
||||
|
||||
# Target for bundling core2 package for qFlipper
|
||||
copro_dist = distenv.CoproBuilder(
|
||||
distenv.Dir("assets/core2_firmware"),
|
||||
"#/build/core2_firmware.tgz",
|
||||
[],
|
||||
)
|
||||
distenv.AlwaysBuild(copro_dist)
|
||||
distenv.Alias("copro_dist", copro_dist)
|
||||
|
||||
firmware_flash = distenv.AddOpenOCDFlashTarget(firmware_env)
|
||||
|
||||
@@ -7,6 +7,6 @@ App(
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
fap_icon="../../../assets/icons/Archive/keyboard_10px.png",
|
||||
fap_icon="uart_10px.png",
|
||||
fap_category="Misc",
|
||||
)
|
||||
|
||||
BIN
applications/debug/uart_echo/uart_10px.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
@@ -16,6 +16,7 @@
|
||||
#define NFC_TEST_RESOURCES_DIR EXT_PATH("unit_tests/nfc/")
|
||||
#define NFC_TEST_SIGNAL_SHORT_FILE "nfc_nfca_signal_short.nfc"
|
||||
#define NFC_TEST_SIGNAL_LONG_FILE "nfc_nfca_signal_long.nfc"
|
||||
#define NFC_TEST_DICT_PATH EXT_PATH("unit_tests/mf_classic_dict.nfc")
|
||||
|
||||
static const char* nfc_test_file_type = "Flipper NFC test";
|
||||
static const uint32_t nfc_test_file_version = 1;
|
||||
@@ -220,11 +221,78 @@ MU_TEST(mf_classic_dict_test) {
|
||||
furi_string_free(temp_str);
|
||||
}
|
||||
|
||||
MU_TEST(mf_classic_dict_load_test) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
mu_assert(storage != NULL, "storage != NULL assert failed\r\n");
|
||||
|
||||
// Delete unit test dict file if exists
|
||||
if(storage_file_exists(storage, NFC_TEST_DICT_PATH)) {
|
||||
mu_assert(
|
||||
storage_simply_remove(storage, NFC_TEST_DICT_PATH),
|
||||
"remove == true assert failed\r\n");
|
||||
}
|
||||
|
||||
// Create unit test dict file
|
||||
Stream* file_stream = file_stream_alloc(storage);
|
||||
mu_assert(file_stream != NULL, "file_stream != NULL assert failed\r\n");
|
||||
mu_assert(
|
||||
file_stream_open(file_stream, NFC_TEST_DICT_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS),
|
||||
"file_stream_open == true assert failed\r\n");
|
||||
|
||||
// Write unit test dict file
|
||||
char key_str[] = "a0a1a2a3a4a5";
|
||||
mu_assert(
|
||||
stream_write_cstring(file_stream, key_str) == strlen(key_str),
|
||||
"write == true assert failed\r\n");
|
||||
// Close unit test dict file
|
||||
mu_assert(file_stream_close(file_stream), "file_stream_close == true assert failed\r\n");
|
||||
|
||||
// Load unit test dict file
|
||||
MfClassicDict* instance = NULL;
|
||||
instance = mf_classic_dict_alloc(MfClassicDictTypeUnitTest);
|
||||
mu_assert(instance != NULL, "mf_classic_dict_alloc\r\n");
|
||||
uint32_t total_keys = mf_classic_dict_get_total_keys(instance);
|
||||
mu_assert(total_keys == 1, "total_keys == 1 assert failed\r\n");
|
||||
|
||||
// Read key
|
||||
uint64_t key_ref = 0xa0a1a2a3a4a5;
|
||||
uint64_t key_dut = 0;
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
mu_assert(
|
||||
mf_classic_dict_get_next_key_str(instance, temp_str),
|
||||
"get_next_key_str == true assert failed\r\n");
|
||||
mu_assert(furi_string_cmp_str(temp_str, key_str) == 0, "invalid key loaded\r\n");
|
||||
mu_assert(mf_classic_dict_rewind(instance), "mf_classic_dict_rewind == 1 assert failed\r\n");
|
||||
mu_assert(
|
||||
mf_classic_dict_get_next_key(instance, &key_dut),
|
||||
"get_next_key == true assert failed\r\n");
|
||||
mu_assert(key_dut == key_ref, "invalid key loaded\r\n");
|
||||
furi_string_free(temp_str);
|
||||
mf_classic_dict_free(instance);
|
||||
|
||||
// Check that MfClassicDict added new line to the end of the file
|
||||
mu_assert(
|
||||
file_stream_open(file_stream, NFC_TEST_DICT_PATH, FSAM_READ, FSOM_OPEN_EXISTING),
|
||||
"file_stream_open == true assert failed\r\n");
|
||||
mu_assert(stream_seek(file_stream, -1, StreamOffsetFromEnd), "seek == true assert failed\r\n");
|
||||
uint8_t last_char = 0;
|
||||
mu_assert(stream_read(file_stream, &last_char, 1) == 1, "read == true assert failed\r\n");
|
||||
mu_assert(last_char == '\n', "last_char == '\\n' assert failed\r\n");
|
||||
mu_assert(file_stream_close(file_stream), "file_stream_close == true assert failed\r\n");
|
||||
|
||||
// Delete unit test dict file
|
||||
mu_assert(
|
||||
storage_simply_remove(storage, NFC_TEST_DICT_PATH), "remove == true assert failed\r\n");
|
||||
stream_free(file_stream);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(nfc) {
|
||||
nfc_test_alloc();
|
||||
|
||||
MU_RUN_TEST(nfc_digital_signal_test);
|
||||
MU_RUN_TEST(mf_classic_dict_test);
|
||||
MU_RUN_TEST(mf_classic_dict_load_test);
|
||||
|
||||
nfc_test_free();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <lib/subghz/transmitter.h>
|
||||
#include <lib/subghz/subghz_keystore.h>
|
||||
#include <lib/subghz/subghz_file_encoder_worker.h>
|
||||
#include <lib/subghz/protocols/registry.h>
|
||||
#include <lib/subghz/protocols/protocol_items.h>
|
||||
#include <flipper_format/flipper_format_i.h>
|
||||
|
||||
#define TAG "SubGhz TEST"
|
||||
@@ -43,6 +43,8 @@ static void subghz_test_init(void) {
|
||||
environment_handler, CAME_ATOMO_DIR_NAME);
|
||||
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
||||
environment_handler, NICE_FLOR_S_DIR_NAME);
|
||||
subghz_environment_set_protocol_registry(
|
||||
environment_handler, (void*)&subghz_protocol_registry);
|
||||
|
||||
receiver_handler = subghz_receiver_alloc_init(environment_handler);
|
||||
subghz_receiver_set_filter(receiver_handler, SubGhzProtocolFlag_Decodable);
|
||||
@@ -413,11 +415,11 @@ MU_TEST(subghz_decoder_honeywell_wdb_test) {
|
||||
"Test decoder " SUBGHZ_PROTOCOL_HONEYWELL_WDB_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_decoder_magellen_test) {
|
||||
MU_TEST(subghz_decoder_magellan_test) {
|
||||
mu_assert(
|
||||
subghz_decoder_test(
|
||||
EXT_PATH("unit_tests/subghz/magellen_raw.sub"), SUBGHZ_PROTOCOL_MAGELLEN_NAME),
|
||||
"Test decoder " SUBGHZ_PROTOCOL_MAGELLEN_NAME " error\r\n");
|
||||
EXT_PATH("unit_tests/subghz/magellan_raw.sub"), SUBGHZ_PROTOCOL_MAGELLAN_NAME),
|
||||
"Test decoder " SUBGHZ_PROTOCOL_MAGELLAN_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_decoder_intertechno_v3_test) {
|
||||
@@ -545,10 +547,10 @@ MU_TEST(subghz_encoder_honeywell_wdb_test) {
|
||||
"Test encoder " SUBGHZ_PROTOCOL_HONEYWELL_WDB_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_encoder_magellen_test) {
|
||||
MU_TEST(subghz_encoder_magellan_test) {
|
||||
mu_assert(
|
||||
subghz_encoder_test(EXT_PATH("unit_tests/subghz/magellen.sub")),
|
||||
"Test encoder " SUBGHZ_PROTOCOL_MAGELLEN_NAME " error\r\n");
|
||||
subghz_encoder_test(EXT_PATH("unit_tests/subghz/magellan.sub")),
|
||||
"Test encoder " SUBGHZ_PROTOCOL_MAGELLAN_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_encoder_intertechno_v3_test) {
|
||||
@@ -600,7 +602,7 @@ MU_TEST_SUITE(subghz) {
|
||||
MU_RUN_TEST(subghz_decoder_doitrand_test);
|
||||
MU_RUN_TEST(subghz_decoder_phoenix_v2_test);
|
||||
MU_RUN_TEST(subghz_decoder_honeywell_wdb_test);
|
||||
MU_RUN_TEST(subghz_decoder_magellen_test);
|
||||
MU_RUN_TEST(subghz_decoder_magellan_test);
|
||||
MU_RUN_TEST(subghz_decoder_intertechno_v3_test);
|
||||
MU_RUN_TEST(subghz_decoder_clemsa_test);
|
||||
MU_RUN_TEST(subghz_decoder_oregon2_test);
|
||||
@@ -622,7 +624,7 @@ MU_TEST_SUITE(subghz) {
|
||||
MU_RUN_TEST(subghz_encoder_doitrand_test);
|
||||
MU_RUN_TEST(subghz_encoder_phoenix_v2_test);
|
||||
MU_RUN_TEST(subghz_encoder_honeywell_wdb_test);
|
||||
MU_RUN_TEST(subghz_encoder_magellen_test);
|
||||
MU_RUN_TEST(subghz_encoder_magellan_test);
|
||||
MU_RUN_TEST(subghz_encoder_intertechno_v3_test);
|
||||
MU_RUN_TEST(subghz_encoder_clemsa_test);
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
|
||||
/* Magic happens here -- this file is generated by fbt.
|
||||
* Just set fap_icon_assets in application.fam and #include {APPID}_icons.h */
|
||||
#include "example_images_icons.h"
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -237,7 +237,8 @@ static uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) {
|
||||
static int32_t
|
||||
ducky_parse_line(BadUsbScript* bad_usb, FuriString* line, char* error, size_t error_len) {
|
||||
uint32_t line_len = furi_string_size(line);
|
||||
const char* line_tmp = furi_string_get_cstr(line);
|
||||
bool state = false;
|
||||
@@ -270,6 +271,9 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) {
|
||||
if((state) && (delay_val > 0)) {
|
||||
return (int32_t)delay_val;
|
||||
}
|
||||
if(error != NULL) {
|
||||
snprintf(error, error_len, "Invalid number %s", line_tmp);
|
||||
}
|
||||
return SCRIPT_STATE_ERROR;
|
||||
} else if(
|
||||
(strncmp(line_tmp, ducky_cmd_defdelay_1, strlen(ducky_cmd_defdelay_1)) == 0) ||
|
||||
@@ -277,17 +281,26 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) {
|
||||
// DEFAULT_DELAY
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
state = ducky_get_number(line_tmp, &bad_usb->defdelay);
|
||||
if(!state && error != NULL) {
|
||||
snprintf(error, error_len, "Invalid number %s", line_tmp);
|
||||
}
|
||||
return (state) ? (0) : SCRIPT_STATE_ERROR;
|
||||
} else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) {
|
||||
// STRING
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
state = ducky_string(bad_usb, line_tmp);
|
||||
if(!state && error != NULL) {
|
||||
snprintf(error, error_len, "Invalid string %s", line_tmp);
|
||||
}
|
||||
return (state) ? (0) : SCRIPT_STATE_ERROR;
|
||||
} else if(strncmp(line_tmp, ducky_cmd_altchar, strlen(ducky_cmd_altchar)) == 0) {
|
||||
// ALTCHAR
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
ducky_numlock_on();
|
||||
state = ducky_altchar(line_tmp);
|
||||
if(!state && error != NULL) {
|
||||
snprintf(error, error_len, "Invalid altchar %s", line_tmp);
|
||||
}
|
||||
return (state) ? (0) : SCRIPT_STATE_ERROR;
|
||||
} else if(
|
||||
(strncmp(line_tmp, ducky_cmd_altstr_1, strlen(ducky_cmd_altstr_1)) == 0) ||
|
||||
@@ -296,11 +309,17 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) {
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
ducky_numlock_on();
|
||||
state = ducky_altstring(line_tmp);
|
||||
if(!state && error != NULL) {
|
||||
snprintf(error, error_len, "Invalid altstring %s", line_tmp);
|
||||
}
|
||||
return (state) ? (0) : SCRIPT_STATE_ERROR;
|
||||
} else if(strncmp(line_tmp, ducky_cmd_repeat, strlen(ducky_cmd_repeat)) == 0) {
|
||||
// REPEAT
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
state = ducky_get_number(line_tmp, &bad_usb->repeat_cnt);
|
||||
if(!state && error != NULL) {
|
||||
snprintf(error, error_len, "Invalid number %s", line_tmp);
|
||||
}
|
||||
return (state) ? (0) : SCRIPT_STATE_ERROR;
|
||||
} else if(strncmp(line_tmp, ducky_cmd_sysrq, strlen(ducky_cmd_sysrq)) == 0) {
|
||||
// SYSRQ
|
||||
@@ -313,7 +332,12 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) {
|
||||
} else {
|
||||
// Special keys + modifiers
|
||||
uint16_t key = ducky_get_keycode(bad_usb, line_tmp, false);
|
||||
if(key == HID_KEYBOARD_NONE) return SCRIPT_STATE_ERROR;
|
||||
if(key == HID_KEYBOARD_NONE) {
|
||||
if(error != NULL) {
|
||||
snprintf(error, error_len, "No keycode defined for %s", line_tmp);
|
||||
}
|
||||
return SCRIPT_STATE_ERROR;
|
||||
}
|
||||
if((key & 0xFF00) != 0) {
|
||||
// It's a modifier key
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
@@ -323,6 +347,9 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) {
|
||||
furi_hal_hid_kb_release(key);
|
||||
return (0);
|
||||
}
|
||||
if(error != NULL) {
|
||||
strncpy(error, "Unknown error", error_len);
|
||||
}
|
||||
return SCRIPT_STATE_ERROR;
|
||||
}
|
||||
|
||||
@@ -401,7 +428,8 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil
|
||||
|
||||
if(bad_usb->repeat_cnt > 0) {
|
||||
bad_usb->repeat_cnt--;
|
||||
delay_val = ducky_parse_line(bad_usb, bad_usb->line_prev);
|
||||
delay_val = ducky_parse_line(
|
||||
bad_usb, bad_usb->line_prev, bad_usb->st.error, sizeof(bad_usb->st.error));
|
||||
if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line
|
||||
return 0;
|
||||
} else if(delay_val < 0) { // Script error
|
||||
@@ -435,7 +463,9 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil
|
||||
bad_usb->st.line_cur++;
|
||||
bad_usb->buf_len = bad_usb->buf_len + bad_usb->buf_start - (i + 1);
|
||||
bad_usb->buf_start = i + 1;
|
||||
delay_val = ducky_parse_line(bad_usb, bad_usb->line);
|
||||
delay_val = ducky_parse_line(
|
||||
bad_usb, bad_usb->line, bad_usb->st.error, sizeof(bad_usb->st.error));
|
||||
|
||||
if(delay_val < 0) {
|
||||
bad_usb->st.error_line = bad_usb->st.line_cur;
|
||||
FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur);
|
||||
@@ -618,6 +648,7 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path) {
|
||||
bad_usb_script_set_default_keyboard_layout(bad_usb);
|
||||
|
||||
bad_usb->st.state = BadUsbStateInit;
|
||||
bad_usb->st.error[0] = '\0';
|
||||
|
||||
bad_usb->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(bad_usb->thread, "BadUsbWorker");
|
||||
|
||||
@@ -25,6 +25,7 @@ typedef struct {
|
||||
uint16_t line_nb;
|
||||
uint32_t delay_remain;
|
||||
uint16_t error_line;
|
||||
char error[64];
|
||||
} BadUsbState;
|
||||
|
||||
BadUsbScript* bad_usb_script_open(FuriString* file_path);
|
||||
|
||||
@@ -74,6 +74,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
|
||||
furi_string_reset(disp_str);
|
||||
canvas_draw_str_aligned(canvas, 127, 56, AlignRight, AlignBottom, model->state.error);
|
||||
} else if(model->state.state == BadUsbStateIdle) {
|
||||
canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18);
|
||||
canvas_set_font(canvas, FontBigNumbers);
|
||||
|
||||
@@ -3,6 +3,7 @@ App(
|
||||
name="Applications",
|
||||
apptype=FlipperAppType.APP,
|
||||
entry_point="fap_loader_app",
|
||||
cdefines=["APP_FAP_LOADER"],
|
||||
requires=[
|
||||
"gui",
|
||||
"storage",
|
||||
|
||||
@@ -101,7 +101,7 @@ static bool fap_loader_run_selected_app(FapLoader* loader) {
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Loaded in %ums", (size_t)(furi_get_tick() - start));
|
||||
FURI_LOG_I(TAG, "FAP Loader is staring app");
|
||||
FURI_LOG_I(TAG, "FAP Loader is starting app");
|
||||
|
||||
FuriThread* thread = flipper_application_spawn(loader->app, NULL);
|
||||
furi_thread_start(thread);
|
||||
|
||||
@@ -24,6 +24,7 @@ struct GpioApp {
|
||||
Widget* widget;
|
||||
|
||||
VariableItemList* var_item_list;
|
||||
VariableItem* var_item_flow;
|
||||
GpioTest* gpio_test;
|
||||
GpioUsbUart* gpio_usb_uart;
|
||||
UsbUartBridge* usb_uart_bridge;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "../gpio_app_i.h"
|
||||
#include "furi_hal_power.h"
|
||||
#include "furi_hal_usb.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
enum GpioItem {
|
||||
GpioItemUsbUart,
|
||||
@@ -88,6 +89,7 @@ bool gpio_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
} else if(event.event == GpioStartEventUsbUart) {
|
||||
scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, GpioItemUsbUart);
|
||||
if(!furi_hal_usb_is_locked()) {
|
||||
DOLPHIN_DEED(DolphinDeedGpioUartBridge);
|
||||
scene_manager_next_scene(app->scene_manager, GpioSceneUsbUart);
|
||||
} else {
|
||||
scene_manager_next_scene(app->scene_manager, GpioSceneUsbUartCloseRpc);
|
||||
|
||||
@@ -13,7 +13,7 @@ static UsbUartConfig* cfg_set;
|
||||
|
||||
static const char* vcp_ch[] = {"0 (CLI)", "1"};
|
||||
static const char* uart_ch[] = {"13,14", "15,16"};
|
||||
static const char* flow_pins[] = {"None", "2,3", "6,7"};
|
||||
static const char* flow_pins[] = {"None", "2,3", "6,7", "16,15"};
|
||||
static const char* baudrate_mode[] = {"Host"};
|
||||
static const uint32_t baudrate_list[] = {
|
||||
2400,
|
||||
@@ -33,6 +33,24 @@ bool gpio_scene_usb_uart_cfg_on_event(void* context, SceneManagerEvent event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void line_ensure_flow_invariant(GpioApp* app) {
|
||||
// GPIO pins PC0, PC1 (16,15) are unavailable for RTS/DTR when LPUART is
|
||||
// selected. This function enforces that invariant by resetting flow_pins
|
||||
// to None if it is configured to 16,15 when LPUART is selected.
|
||||
|
||||
uint8_t available_flow_pins = cfg_set->uart_ch == FuriHalUartIdLPUART1 ? 3 : 4;
|
||||
VariableItem* item = app->var_item_flow;
|
||||
variable_item_set_values_count(item, available_flow_pins);
|
||||
|
||||
if(cfg_set->flow_pins >= available_flow_pins) {
|
||||
cfg_set->flow_pins = 0;
|
||||
usb_uart_set_config(app->usb_uart_bridge, cfg_set);
|
||||
|
||||
variable_item_set_current_value_index(item, cfg_set->flow_pins);
|
||||
variable_item_set_current_value_text(item, flow_pins[cfg_set->flow_pins]);
|
||||
}
|
||||
}
|
||||
|
||||
static void line_vcp_cb(VariableItem* item) {
|
||||
GpioApp* app = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
@@ -54,6 +72,7 @@ static void line_port_cb(VariableItem* item) {
|
||||
else if(index == 1)
|
||||
cfg_set->uart_ch = FuriHalUartIdLPUART1;
|
||||
usb_uart_set_config(app->usb_uart_bridge, cfg_set);
|
||||
line_ensure_flow_invariant(app);
|
||||
}
|
||||
|
||||
static void line_flow_cb(VariableItem* item) {
|
||||
@@ -116,9 +135,12 @@ void gpio_scene_usb_uart_cfg_on_enter(void* context) {
|
||||
variable_item_set_current_value_index(item, cfg_set->uart_ch);
|
||||
variable_item_set_current_value_text(item, uart_ch[cfg_set->uart_ch]);
|
||||
|
||||
item = variable_item_list_add(var_item_list, "RTS/DTR Pins", 3, line_flow_cb, app);
|
||||
item = variable_item_list_add(
|
||||
var_item_list, "RTS/DTR Pins", COUNT_OF(flow_pins), line_flow_cb, app);
|
||||
variable_item_set_current_value_index(item, cfg_set->flow_pins);
|
||||
variable_item_set_current_value_text(item, flow_pins[cfg_set->flow_pins]);
|
||||
app->var_item_flow = item;
|
||||
line_ensure_flow_invariant(app);
|
||||
|
||||
variable_item_list_set_selected_item(
|
||||
var_item_list, scene_manager_get_scene_state(app->scene_manager, GpioAppViewUsbUartCfg));
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
static const GpioPin* flow_pins[][2] = {
|
||||
{&gpio_ext_pa7, &gpio_ext_pa6}, // 2, 3
|
||||
{&gpio_ext_pb2, &gpio_ext_pc3}, // 6, 7
|
||||
{&gpio_ext_pc0, &gpio_ext_pc1}, // 16, 15
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_i.h>
|
||||
#include <infrared.h>
|
||||
#include <infrared_worker.h>
|
||||
#include <furi_hal_infrared.h>
|
||||
@@ -6,12 +7,24 @@
|
||||
#include <toolbox/args.h>
|
||||
|
||||
#include "infrared_signal.h"
|
||||
#include "infrared_brute_force.h"
|
||||
|
||||
#include "m-dict.h"
|
||||
#include "m-string.h"
|
||||
|
||||
#define INFRARED_CLI_BUF_SIZE 10
|
||||
|
||||
DICT_DEF2(dict_signals, string_t, STRING_OPLIST, int, M_DEFAULT_OPLIST)
|
||||
|
||||
enum RemoteTypes { TV = 0, AC = 1 };
|
||||
|
||||
static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args);
|
||||
static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args);
|
||||
static void infrared_cli_process_decode(Cli* cli, FuriString* args);
|
||||
static void infrared_cli_process_universal(Cli* cli, FuriString* args);
|
||||
static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type);
|
||||
static void
|
||||
infrared_cli_brute_force_signals(Cli* cli, enum RemoteTypes remote_type, FuriString* signal);
|
||||
|
||||
static const struct {
|
||||
const char* cmd;
|
||||
@@ -20,6 +33,7 @@ static const struct {
|
||||
{.cmd = "rx", .process_function = infrared_cli_start_ir_rx},
|
||||
{.cmd = "tx", .process_function = infrared_cli_start_ir_tx},
|
||||
{.cmd = "decode", .process_function = infrared_cli_process_decode},
|
||||
{.cmd = "universal", .process_function = infrared_cli_process_universal},
|
||||
};
|
||||
|
||||
static void signal_received_callback(void* context, InfraredWorkerSignal* received_signal) {
|
||||
@@ -57,25 +71,9 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv
|
||||
}
|
||||
}
|
||||
|
||||
static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
UNUSED(args);
|
||||
InfraredWorker* worker = infrared_worker_alloc();
|
||||
infrared_worker_rx_start(worker);
|
||||
infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, cli);
|
||||
|
||||
printf("Receiving INFRARED...\r\nPress Ctrl+C to abort\r\n");
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
furi_delay_ms(50);
|
||||
}
|
||||
|
||||
infrared_worker_rx_stop(worker);
|
||||
infrared_worker_free(worker);
|
||||
}
|
||||
|
||||
static void infrared_cli_print_usage(void) {
|
||||
printf("Usage:\r\n");
|
||||
printf("\tir rx\r\n");
|
||||
printf("\tir rx [raw]\r\n");
|
||||
printf("\tir tx <protocol> <address> <command>\r\n");
|
||||
printf("\t<command> and <address> are hex-formatted\r\n");
|
||||
printf("\tAvailable protocols:");
|
||||
@@ -90,6 +88,37 @@ static void infrared_cli_print_usage(void) {
|
||||
INFRARED_MIN_FREQUENCY,
|
||||
INFRARED_MAX_FREQUENCY);
|
||||
printf("\tir decode <input_file> [<output_file>]\r\n");
|
||||
printf("\tir universal <tv, ac> <signal name>\r\n");
|
||||
printf("\tir universal list <tv, ac>\r\n");
|
||||
}
|
||||
|
||||
static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
|
||||
bool enable_decoding = true;
|
||||
|
||||
if(!furi_string_empty(args)) {
|
||||
if(!furi_string_cmp_str(args, "raw")) {
|
||||
enable_decoding = false;
|
||||
} else {
|
||||
printf("Wrong arguments.\r\n");
|
||||
infrared_cli_print_usage();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
InfraredWorker* worker = infrared_worker_alloc();
|
||||
infrared_worker_rx_enable_signal_decoding(worker, enable_decoding);
|
||||
infrared_worker_rx_start(worker);
|
||||
infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, cli);
|
||||
|
||||
printf("Receiving %s INFRARED...\r\nPress Ctrl+C to abort\r\n", enable_decoding ? "" : "RAW");
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
furi_delay_ms(50);
|
||||
}
|
||||
|
||||
infrared_worker_rx_stop(worker);
|
||||
infrared_worker_free(worker);
|
||||
}
|
||||
|
||||
static bool infrared_cli_parse_message(const char* str, InfraredSignal* signal) {
|
||||
@@ -328,6 +357,168 @@ static void infrared_cli_process_decode(Cli* cli, FuriString* args) {
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
static void infrared_cli_process_universal(Cli* cli, FuriString* args) {
|
||||
enum RemoteTypes Remote;
|
||||
|
||||
FuriString* command;
|
||||
FuriString* remote;
|
||||
FuriString* signal;
|
||||
command = furi_string_alloc();
|
||||
remote = furi_string_alloc();
|
||||
signal = furi_string_alloc();
|
||||
|
||||
do {
|
||||
if(!args_read_string_and_trim(args, command)) {
|
||||
infrared_cli_print_usage();
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(command, "list") == 0) {
|
||||
args_read_string_and_trim(args, remote);
|
||||
if(furi_string_cmp_str(remote, "tv") == 0) {
|
||||
Remote = TV;
|
||||
} else if(furi_string_cmp_str(remote, "ac") == 0) {
|
||||
Remote = AC;
|
||||
} else {
|
||||
printf("Invalid remote type.\r\n");
|
||||
break;
|
||||
}
|
||||
infrared_cli_list_remote_signals(Remote);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(command, "tv") == 0) {
|
||||
Remote = TV;
|
||||
} else if(furi_string_cmp_str(command, "ac") == 0) {
|
||||
Remote = AC;
|
||||
} else {
|
||||
printf("Invalid remote type.\r\n");
|
||||
break;
|
||||
}
|
||||
|
||||
args_read_string_and_trim(args, signal);
|
||||
if(furi_string_empty(signal)) {
|
||||
printf("Must supply a valid signal for type of remote selected.\r\n");
|
||||
break;
|
||||
}
|
||||
|
||||
infrared_cli_brute_force_signals(cli, Remote, signal);
|
||||
break;
|
||||
|
||||
} while(false);
|
||||
|
||||
furi_string_free(command);
|
||||
furi_string_free(remote);
|
||||
furi_string_free(signal);
|
||||
}
|
||||
|
||||
static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
|
||||
dict_signals_t signals_dict;
|
||||
string_t key;
|
||||
const char* remote_file = NULL;
|
||||
bool success = false;
|
||||
int max = 1;
|
||||
|
||||
switch(remote_type) {
|
||||
case TV:
|
||||
remote_file = EXT_PATH("infrared/assets/tv.ir");
|
||||
break;
|
||||
case AC:
|
||||
remote_file = EXT_PATH("infrared/assets/ac.ir");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
dict_signals_init(signals_dict);
|
||||
string_init(key);
|
||||
|
||||
success = flipper_format_buffered_file_open_existing(ff, remote_file);
|
||||
if(success) {
|
||||
FuriString* signal_name;
|
||||
signal_name = furi_string_alloc();
|
||||
printf("Valid signals:\r\n");
|
||||
while(flipper_format_read_string(ff, "name", signal_name)) {
|
||||
string_set_str(key, furi_string_get_cstr(signal_name));
|
||||
int* v = dict_signals_get(signals_dict, key);
|
||||
if(v != NULL) {
|
||||
(*v)++;
|
||||
max = M_MAX(*v, max);
|
||||
} else {
|
||||
dict_signals_set_at(signals_dict, key, 1);
|
||||
}
|
||||
}
|
||||
dict_signals_it_t it;
|
||||
for(dict_signals_it(it, signals_dict); !dict_signals_end_p(it); dict_signals_next(it)) {
|
||||
const struct dict_signals_pair_s* pair = dict_signals_cref(it);
|
||||
printf("\t%s\r\n", string_get_cstr(pair->key));
|
||||
}
|
||||
furi_string_free(signal_name);
|
||||
}
|
||||
|
||||
string_clear(key);
|
||||
dict_signals_clear(signals_dict);
|
||||
flipper_format_free(ff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
static void
|
||||
infrared_cli_brute_force_signals(Cli* cli, enum RemoteTypes remote_type, FuriString* signal) {
|
||||
InfraredBruteForce* brute_force = infrared_brute_force_alloc();
|
||||
const char* remote_file = NULL;
|
||||
uint32_t i = 0;
|
||||
bool success = false;
|
||||
|
||||
switch(remote_type) {
|
||||
case TV:
|
||||
remote_file = EXT_PATH("infrared/assets/tv.ir");
|
||||
break;
|
||||
case AC:
|
||||
remote_file = EXT_PATH("infrared/assets/ac.ir");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
infrared_brute_force_set_db_filename(brute_force, remote_file);
|
||||
infrared_brute_force_add_record(brute_force, i++, furi_string_get_cstr(signal));
|
||||
|
||||
success = infrared_brute_force_calculate_messages(brute_force);
|
||||
if(success) {
|
||||
uint32_t record_count;
|
||||
uint32_t index = 0;
|
||||
int records_sent = 0;
|
||||
bool running = false;
|
||||
|
||||
running = infrared_brute_force_start(brute_force, index, &record_count);
|
||||
if(record_count <= 0) {
|
||||
printf("Invalid signal.\n");
|
||||
infrared_brute_force_clear_records(brute_force);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Sending %ld codes to the tv.\r\n", record_count);
|
||||
printf("Press Ctrl-C to stop.\r\n");
|
||||
while(running) {
|
||||
running = infrared_brute_force_send_next(brute_force);
|
||||
|
||||
if(cli_cmd_interrupt_received(cli)) break;
|
||||
|
||||
printf("\r%d%% complete.", (int)((float)records_sent++ / (float)record_count * 100));
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
infrared_brute_force_stop(brute_force);
|
||||
} else {
|
||||
printf("Invalid signal.\r\n");
|
||||
}
|
||||
|
||||
infrared_brute_force_clear_records(brute_force);
|
||||
infrared_brute_force_free(brute_force);
|
||||
}
|
||||
|
||||
static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
if(furi_hal_infrared_is_busy()) {
|
||||
|
||||
@@ -70,7 +70,7 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e
|
||||
uint32_t record_count;
|
||||
if(infrared_brute_force_start(
|
||||
brute_force, infrared_custom_event_get_value(event.event), &record_count)) {
|
||||
DOLPHIN_DEED(DolphinDeedIrBruteForce);
|
||||
DOLPHIN_DEED(DolphinDeedIrSend);
|
||||
infrared_scene_universal_common_show_popup(infrared, record_count);
|
||||
} else {
|
||||
scene_manager_next_scene(scene_manager, InfraredSceneErrorDatabases);
|
||||
|
||||
@@ -29,6 +29,7 @@ static void lfrfid_cli_print_usage() {
|
||||
printf("rfid <write | emulate> <key_type> <key_data>\r\n");
|
||||
printf("rfid raw_read <ask | psk> <filename>\r\n");
|
||||
printf("rfid raw_emulate <filename>\r\n");
|
||||
printf("rfid raw_analyze <filename>\r\n");
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -26,7 +26,7 @@ void nfc_scene_detect_reader_callback(void* context) {
|
||||
|
||||
void nfc_scene_detect_reader_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
DOLPHIN_DEED(DolphinDeedNfcEmulate);
|
||||
DOLPHIN_DEED(DolphinDeedNfcDetectReader);
|
||||
|
||||
detect_reader_set_callback(nfc->detect_reader, nfc_scene_detect_reader_callback, nfc);
|
||||
detect_reader_set_nonces_max(nfc->detect_reader, NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "../nfc_i.h"
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexSave,
|
||||
SubmenuIndexInfo,
|
||||
};
|
||||
|
||||
@@ -15,7 +14,6 @@ void nfc_scene_emv_menu_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
Submenu* submenu = nfc->submenu;
|
||||
|
||||
submenu_add_item(submenu, "Save", SubmenuIndexSave, nfc_scene_emv_menu_submenu_callback, nfc);
|
||||
submenu_add_item(submenu, "Info", SubmenuIndexInfo, nfc_scene_emv_menu_submenu_callback, nfc);
|
||||
submenu_set_selected_item(
|
||||
nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmvMenu));
|
||||
@@ -28,13 +26,7 @@ bool nfc_scene_emv_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexSave) {
|
||||
nfc->dev->format = NfcDeviceSaveFormatBankCard;
|
||||
// Clear device name
|
||||
nfc_device_set_name(nfc->dev, "");
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexInfo) {
|
||||
if(event.event == SubmenuIndexInfo) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
@@ -24,12 +24,33 @@ void nfc_scene_emv_read_success_on_enter(void* context) {
|
||||
nfc->widget, GuiButtonTypeRight, "More", nfc_scene_emv_read_success_widget_callback, nfc);
|
||||
|
||||
FuriString* temp_str;
|
||||
temp_str = furi_string_alloc_printf("\e#%s\n", emv_data->name);
|
||||
for(uint8_t i = 0; i < emv_data->number_len; i += 2) {
|
||||
furi_string_cat_printf(
|
||||
temp_str, "%02X%02X ", emv_data->number[i], emv_data->number[i + 1]);
|
||||
if(emv_data->name[0] != '\0') {
|
||||
temp_str = furi_string_alloc_printf("\e#%s\n", emv_data->name);
|
||||
} else {
|
||||
temp_str = furi_string_alloc_printf("\e#Unknown Bank Card\n");
|
||||
}
|
||||
if(emv_data->number_len) {
|
||||
for(uint8_t i = 0; i < emv_data->number_len; i += 2) {
|
||||
furi_string_cat_printf(
|
||||
temp_str, "%02X%02X ", emv_data->number[i], emv_data->number[i + 1]);
|
||||
}
|
||||
furi_string_trim(temp_str);
|
||||
} else if(emv_data->aid_len) {
|
||||
furi_string_cat_printf(temp_str, "Can't parse data from app\n");
|
||||
// Parse AID name
|
||||
FuriString* aid_name;
|
||||
aid_name = furi_string_alloc();
|
||||
if(nfc_emv_parser_get_aid_name(
|
||||
nfc->dev->storage, emv_data->aid, emv_data->aid_len, aid_name)) {
|
||||
furi_string_cat_printf(temp_str, "AID: %s", furi_string_get_cstr(aid_name));
|
||||
} else {
|
||||
furi_string_cat_printf(temp_str, "AID: ");
|
||||
for(uint8_t i = 0; i < emv_data->aid_len; i++) {
|
||||
furi_string_cat_printf(temp_str, "%02X", emv_data->aid[i]);
|
||||
}
|
||||
}
|
||||
furi_string_free(aid_name);
|
||||
}
|
||||
furi_string_trim(temp_str);
|
||||
|
||||
// Add expiration date
|
||||
if(emv_data->exp_mon) {
|
||||
|
||||
@@ -34,8 +34,6 @@ void nfc_scene_mf_classic_keys_on_enter(void* context) {
|
||||
widget_add_string_element(nfc->widget, 0, 32, AlignLeft, AlignTop, FontSecondary, temp_str);
|
||||
widget_add_button_element(
|
||||
nfc->widget, GuiButtonTypeCenter, "Add", nfc_scene_mf_classic_keys_widget_callback, nfc);
|
||||
widget_add_button_element(
|
||||
nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_mf_classic_keys_widget_callback, nfc);
|
||||
widget_add_icon_element(nfc->widget, 87, 13, &I_Keychain_39x36);
|
||||
if(user_dict_keys_total > 0) {
|
||||
widget_add_button_element(
|
||||
@@ -57,9 +55,6 @@ bool nfc_scene_mf_classic_keys_on_event(void* context, SceneManagerEvent event)
|
||||
if(event.event == GuiButtonTypeCenter) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysAdd);
|
||||
consumed = true;
|
||||
} else if(event.event == GuiButtonTypeLeft) {
|
||||
scene_manager_previous_scene(nfc->scene_manager);
|
||||
consumed = true;
|
||||
} else if(event.event == GuiButtonTypeRight) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysList);
|
||||
consumed = true;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "../nfc_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexSave,
|
||||
@@ -35,6 +36,8 @@ bool nfc_scene_mf_classic_menu_on_event(void* context, SceneManagerEvent event)
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexSave) {
|
||||
DOLPHIN_DEED(DolphinDeedNfcMfcAdd);
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
nfc->scene_manager, NfcSceneMfClassicMenu, SubmenuIndexSave);
|
||||
nfc->dev->format = NfcDeviceSaveFormatMifareClassic;
|
||||
|
||||
@@ -14,7 +14,6 @@ void nfc_scene_mf_ultralight_read_auth_result_widget_callback(
|
||||
|
||||
void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
|
||||
|
||||
// Setup dialog view
|
||||
FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data;
|
||||
@@ -38,6 +37,7 @@ void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) {
|
||||
widget_add_string_element(
|
||||
widget, 0, 17, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str));
|
||||
if(mf_ul_data->auth_success) {
|
||||
DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
|
||||
furi_string_printf(
|
||||
temp_str,
|
||||
"Password: %02X %02X %02X %02X",
|
||||
@@ -54,6 +54,8 @@ void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) {
|
||||
config_pages->auth_data.pack.raw[1]);
|
||||
widget_add_string_element(
|
||||
widget, 0, 39, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str));
|
||||
} else {
|
||||
DOLPHIN_DEED(DolphinDeedNfcMfulError);
|
||||
}
|
||||
furi_string_printf(
|
||||
temp_str, "Pages Read: %d/%d", mf_ul_data->data_read / 4, mf_ul_data->data_size / 4);
|
||||
|
||||
@@ -30,7 +30,7 @@ bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcCustomEventByteInputDone) {
|
||||
DOLPHIN_DEED(DolphinDeedNfcAdd);
|
||||
DOLPHIN_DEED(DolphinDeedNfcAddSave);
|
||||
if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) {
|
||||
nfc->dev->dev_data.nfc_data = nfc->dev_edit_data;
|
||||
if(nfc_device_save(nfc->dev, nfc->dev->dev_name)) {
|
||||
|
||||
@@ -24,9 +24,11 @@ typedef enum {
|
||||
SubmenuIndexLinear_300_00,
|
||||
SubmenuIndexLiftMaster_315_00,
|
||||
SubmenuIndexLiftMaster_390_00,
|
||||
SubmenuIndexLiftMaster_433_00,
|
||||
SubmenuIndexSecPlus_v2_310_00,
|
||||
SubmenuIndexSecPlus_v2_315_00,
|
||||
SubmenuIndexSecPlus_v2_390_00,
|
||||
SubmenuIndexSecPlus_v2_433_00,
|
||||
|
||||
//SubGhzCustomEvent
|
||||
SubGhzCustomEventSceneDeleteSuccess = 100,
|
||||
|
||||
@@ -72,18 +72,7 @@ typedef enum {
|
||||
SubGhzViewIdTestPacket,
|
||||
} SubGhzViewId;
|
||||
|
||||
struct SubGhzPresetDefinition {
|
||||
FuriString* name;
|
||||
uint32_t frequency;
|
||||
uint8_t* data;
|
||||
size_t data_size;
|
||||
};
|
||||
|
||||
typedef struct SubGhzPresetDefinition SubGhzPresetDefinition;
|
||||
|
||||
typedef enum {
|
||||
SubGhzViewReceiverModeLive,
|
||||
SubGhzViewReceiverModeFile,
|
||||
} SubGhzViewReceiverMode;
|
||||
|
||||
#define SUBGHZ_HISTORY_REMOVE_SAVED_ITEMS 1
|
||||
|
||||
@@ -14,6 +14,10 @@ void subghz_scene_frequency_analyzer_on_enter(void* context) {
|
||||
DOLPHIN_DEED(DolphinDeedSubGhzFrequencyAnalyzer);
|
||||
subghz_frequency_analyzer_set_callback(
|
||||
subghz->subghz_frequency_analyzer, subghz_scene_frequency_analyzer_callback, subghz);
|
||||
subghz_frequency_analyzer_feedback_level(
|
||||
subghz->subghz_frequency_analyzer,
|
||||
subghz->last_settings->frequency_analyzer_feedback_level,
|
||||
true);
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdFrequencyAnalyzer);
|
||||
}
|
||||
|
||||
@@ -44,4 +48,8 @@ bool subghz_scene_frequency_analyzer_on_event(void* context, SceneManagerEvent e
|
||||
void subghz_scene_frequency_analyzer_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
notification_message(subghz->notifications, &sequence_reset_rgb);
|
||||
|
||||
subghz->last_settings->frequency_analyzer_feedback_level =
|
||||
subghz_frequency_analyzer_feedback_level(subghz->subghz_frequency_analyzer, 0, false);
|
||||
subghz_last_settings_save(subghz->last_settings);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,12 @@ static void subghz_scene_read_raw_update_statusbar(void* context) {
|
||||
frequency_str = furi_string_alloc();
|
||||
modulation_str = furi_string_alloc();
|
||||
|
||||
#ifdef SUBGHZ_EXT_PRESET_NAME
|
||||
subghz_get_frequency_modulation(subghz, frequency_str, NULL);
|
||||
furi_string_printf(modulation_str, "%s", furi_string_get_cstr(subghz->txrx->preset->name));
|
||||
#else
|
||||
subghz_get_frequency_modulation(subghz, frequency_str, modulation_str);
|
||||
#endif
|
||||
subghz_read_raw_add_data_statusbar(
|
||||
subghz->subghz_read_raw,
|
||||
furi_string_get_cstr(frequency_str),
|
||||
|
||||
@@ -46,7 +46,17 @@ static void subghz_scene_receiver_update_statusbar(void* context) {
|
||||
frequency_str = furi_string_alloc();
|
||||
modulation_str = furi_string_alloc();
|
||||
|
||||
#ifdef SUBGHZ_EXT_PRESET_NAME
|
||||
if(subghz_history_get_last_index(subghz->txrx->history) > 0) {
|
||||
subghz_get_frequency_modulation(subghz, frequency_str, modulation_str);
|
||||
} else {
|
||||
subghz_get_frequency_modulation(subghz, frequency_str, NULL);
|
||||
furi_string_printf(
|
||||
modulation_str, "Mod: %s", furi_string_get_cstr(subghz->txrx->preset->name));
|
||||
}
|
||||
#else
|
||||
subghz_get_frequency_modulation(subghz, frequency_str, modulation_str);
|
||||
#endif
|
||||
|
||||
subghz_view_receiver_add_data_statusbar(
|
||||
subghz->subghz_receiver,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <dolphin/dolphin.h>
|
||||
#include <flipper_format/flipper_format_i.h>
|
||||
#include <lib/toolbox/stream/stream.h>
|
||||
#include <lib/subghz/protocols/registry.h>
|
||||
#include <lib/subghz/protocols/protocol_items.h>
|
||||
|
||||
#define TAG "SubGhzSetType"
|
||||
|
||||
@@ -167,6 +167,12 @@ void subghz_scene_set_type_on_enter(void* context) {
|
||||
SubmenuIndexLiftMaster_390_00,
|
||||
subghz_scene_set_type_submenu_callback,
|
||||
subghz);
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"Security+1.0 433MHz",
|
||||
SubmenuIndexLiftMaster_433_00,
|
||||
subghz_scene_set_type_submenu_callback,
|
||||
subghz);
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"Security+2.0 310MHz",
|
||||
@@ -185,6 +191,12 @@ void subghz_scene_set_type_on_enter(void* context) {
|
||||
SubmenuIndexSecPlus_v2_390_00,
|
||||
subghz_scene_set_type_submenu_callback,
|
||||
subghz);
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"Security+2.0 433MHz",
|
||||
SubmenuIndexSecPlus_v2_433_00,
|
||||
subghz_scene_set_type_submenu_callback,
|
||||
subghz);
|
||||
|
||||
submenu_set_selected_item(
|
||||
subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType));
|
||||
@@ -359,6 +371,20 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
|
||||
generated_protocol = true;
|
||||
}
|
||||
break;
|
||||
case SubmenuIndexLiftMaster_433_00:
|
||||
while(!subghz_protocol_secplus_v1_check_fixed(key)) {
|
||||
key = subghz_random_serial();
|
||||
}
|
||||
if(subghz_scene_set_type_submenu_gen_data_protocol(
|
||||
subghz,
|
||||
SUBGHZ_PROTOCOL_SECPLUS_V1_NAME,
|
||||
(uint64_t)key << 32 | 0xE6000000,
|
||||
42,
|
||||
433920000,
|
||||
"AM650")) {
|
||||
generated_protocol = true;
|
||||
}
|
||||
break;
|
||||
case SubmenuIndexSecPlus_v2_310_00:
|
||||
subghz->txrx->transmitter = subghz_transmitter_alloc_init(
|
||||
subghz->txrx->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME);
|
||||
@@ -413,6 +439,24 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
|
||||
}
|
||||
subghz_transmitter_free(subghz->txrx->transmitter);
|
||||
break;
|
||||
case SubmenuIndexSecPlus_v2_433_00:
|
||||
subghz->txrx->transmitter = subghz_transmitter_alloc_init(
|
||||
subghz->txrx->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME);
|
||||
subghz_preset_init(subghz, "AM650", 433920000, NULL, 0);
|
||||
if(subghz->txrx->transmitter) {
|
||||
subghz_protocol_secplus_v2_create_data(
|
||||
subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
|
||||
subghz->txrx->fff_data,
|
||||
key,
|
||||
0x68,
|
||||
0xE500000,
|
||||
subghz->txrx->preset);
|
||||
generated_protocol = true;
|
||||
} else {
|
||||
generated_protocol = false;
|
||||
}
|
||||
subghz_transmitter_free(subghz->txrx->transmitter);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
break;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <subghz/types.h>
|
||||
#include <lib/toolbox/path.h>
|
||||
#include "subghz_i.h"
|
||||
#include <lib/subghz/protocols/protocol_items.h>
|
||||
|
||||
#define TAG "SubGhzApp"
|
||||
|
||||
@@ -243,7 +244,8 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
||||
subghz->txrx->environment, EXT_PATH("subghz/assets/came_atomo"));
|
||||
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
||||
subghz->txrx->environment, EXT_PATH("subghz/assets/nice_flor_s"));
|
||||
|
||||
subghz_environment_set_protocol_registry(
|
||||
subghz->txrx->environment, (void*)&subghz_protocol_registry);
|
||||
subghz->txrx->receiver = subghz_receiver_alloc_init(subghz->txrx->environment);
|
||||
#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING
|
||||
subghz_last_settings_set_detect_raw_values(subghz);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <lib/subghz/receiver.h>
|
||||
#include <lib/subghz/transmitter.h>
|
||||
#include <lib/subghz/subghz_file_encoder_worker.h>
|
||||
#include <lib/subghz/protocols/protocol_items.h>
|
||||
|
||||
#include "helpers/subghz_chat.h"
|
||||
|
||||
@@ -159,6 +160,7 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) {
|
||||
stream_write_cstring(stream, furi_string_get_cstr(flipper_format_string));
|
||||
|
||||
SubGhzEnvironment* environment = subghz_environment_alloc();
|
||||
subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry);
|
||||
|
||||
SubGhzTransmitter* transmitter = subghz_transmitter_alloc_init(environment, "Princeton");
|
||||
subghz_transmitter_deserialize(transmitter, flipper_format);
|
||||
@@ -252,6 +254,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) {
|
||||
environment, EXT_PATH("subghz/assets/came_atomo"));
|
||||
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
||||
environment, EXT_PATH("subghz/assets/nice_flor_s"));
|
||||
subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry);
|
||||
|
||||
SubGhzReceiver* receiver = subghz_receiver_alloc_init(environment);
|
||||
subghz_receiver_set_filter(receiver, SubGhzProtocolFlag_Decodable);
|
||||
@@ -371,6 +374,7 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) {
|
||||
environment, EXT_PATH("subghz/assets/came_atomo"));
|
||||
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
||||
environment, EXT_PATH("subghz/assets/nice_flor_s"));
|
||||
subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry);
|
||||
|
||||
SubGhzReceiver* receiver = subghz_receiver_alloc_init(environment);
|
||||
subghz_receiver_set_filter(receiver, SubGhzProtocolFlag_Decodable);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "subghz_history.h"
|
||||
#include "subghz_history_private.h"
|
||||
#include <lib/subghz/receiver.h>
|
||||
#include <toolbox/path.h>
|
||||
#include <flipper_format/flipper_format_i.h>
|
||||
#include "flipper_format_stream_i.h"
|
||||
#include <inttypes.h>
|
||||
|
||||
#define SUBGHZ_HISTORY_MAX 60
|
||||
|
||||
@@ -11,10 +14,12 @@
|
||||
*/
|
||||
#define SUBGHZ_HISTORY_TMP_DIR EXT_PATH("subghz/tmp_history")
|
||||
#define SUBGHZ_HISTORY_TMP_EXTENSION ".tmp"
|
||||
#define SUBGHZ_HISTORY_TMP_SIGNAL_MAX_LEVEL_DURATION 700
|
||||
#define SUBGHZ_HISTORY_TMP_SIGNAL_MIN_LEVEL_DURATION 100
|
||||
#define SUBGHZ_HISTORY_TMP_SIGNAL_MAX 700
|
||||
#define SUBGHZ_HISTORY_TMP_SIGNAL_MIN 100
|
||||
#define SUBGHZ_HISTORY_TMP_REMOVE_FILES true
|
||||
#define SUBGHZ_HISTORY_TMP_RAW_KEY "RAW_Data"
|
||||
#define MAX_LINE 500
|
||||
const size_t buffer_size = 32;
|
||||
|
||||
#define TAG "SubGhzHistory"
|
||||
|
||||
@@ -304,6 +309,10 @@ bool subghz_history_get_text_space_left(SubGhzHistory* instance, FuriString* out
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t subghz_history_get_last_index(SubGhzHistory* instance) {
|
||||
return instance->last_index_write;
|
||||
}
|
||||
|
||||
void subghz_history_get_text_item_menu(SubGhzHistory* instance, FuriString* output, uint16_t idx) {
|
||||
SubGhzHistoryItem* item = SubGhzHistoryItemArray_get(instance->history->data, idx);
|
||||
furi_string_set(output, item->item_str);
|
||||
@@ -436,10 +445,14 @@ bool subghz_history_add_to_history(
|
||||
#ifdef FURI_DEBUG
|
||||
FURI_LOG_I(TAG, "Save temp file: %s", furi_string_get_cstr(dir_path));
|
||||
#endif
|
||||
if(!subghz_history_tmp_write_file_split(instance, item, dir_path)) {
|
||||
if(!subghz_history_tmp_write_file_split(instance, item, furi_string_get_cstr(dir_path))) {
|
||||
// Plan B!
|
||||
subghz_history_tmp_write_file_full(instance, item, dir_path);
|
||||
}
|
||||
if(item->is_file) {
|
||||
flipper_format_free(item->flipper_string);
|
||||
item->flipper_string = NULL;
|
||||
}
|
||||
furi_string_free(filename);
|
||||
furi_string_free(dir_path);
|
||||
|
||||
@@ -455,19 +468,407 @@ bool subghz_history_add_to_history(
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool is_space_playground(char c) {
|
||||
return c == ' ' || c == '\t' || c == flipper_format_eolr;
|
||||
}
|
||||
|
||||
bool subghz_history_stream_read_valid_key(Stream* stream, FuriString* key) {
|
||||
furi_string_reset(key);
|
||||
uint8_t buffer[buffer_size];
|
||||
|
||||
bool found = false;
|
||||
bool error = false;
|
||||
bool accumulate = true;
|
||||
bool new_line = true;
|
||||
|
||||
while(true) {
|
||||
size_t was_read = stream_read(stream, buffer, buffer_size);
|
||||
if(was_read == 0) break;
|
||||
|
||||
for(size_t i = 0; i < was_read; i++) {
|
||||
uint8_t data = buffer[i];
|
||||
if(data == flipper_format_eoln) {
|
||||
// EOL found, clean data, start accumulating data and set the new_line flag
|
||||
furi_string_reset(key);
|
||||
accumulate = true;
|
||||
new_line = true;
|
||||
} else if(data == flipper_format_eolr) {
|
||||
// ignore
|
||||
} else if(data == flipper_format_comment && new_line) {
|
||||
// if there is a comment character and we are at the beginning of a new line
|
||||
// do not accumulate comment data and reset the new_line flag
|
||||
accumulate = false;
|
||||
new_line = false;
|
||||
} else if(data == flipper_format_delimiter) {
|
||||
if(new_line) {
|
||||
// we are on a "new line" and found the delimiter
|
||||
// this can only be if we have previously found some kind of key, so
|
||||
// clear the data, set the flag that we no longer want to accumulate data
|
||||
// and reset the new_line flag
|
||||
furi_string_reset(key);
|
||||
accumulate = false;
|
||||
new_line = false;
|
||||
} else {
|
||||
// parse the delimiter only if we are accumulating data
|
||||
if(accumulate) {
|
||||
// we found the delimiter, move the rw pointer to the delimiter location
|
||||
// and signal that we have found something
|
||||
if(!stream_seek(stream, i - was_read, StreamOffsetFromCurrent)) {
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// just new symbol, reset the new_line flag
|
||||
new_line = false;
|
||||
if(accumulate) {
|
||||
// and accumulate data if we want
|
||||
furi_string_push_back(key, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(found || error) break;
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
bool subghz_history_stream_seek_to_key(Stream* stream, const char* key, bool strict_mode) {
|
||||
bool found = false;
|
||||
FuriString* read_key;
|
||||
|
||||
read_key = furi_string_alloc();
|
||||
|
||||
while(!stream_eof(stream)) {
|
||||
if(subghz_history_stream_read_valid_key(stream, read_key)) {
|
||||
if(furi_string_cmp_str(read_key, key) == 0) {
|
||||
if(!stream_seek(stream, 2, StreamOffsetFromCurrent)) {
|
||||
break;
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
} else if(strict_mode) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
furi_string_free(read_key);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
bool subghz_history_stream_read_value(Stream* stream, FuriString* value, bool* last) {
|
||||
enum { LeadingSpace, ReadValue, TrailingSpace } state = LeadingSpace;
|
||||
const size_t buffer_size = 32;
|
||||
uint8_t buffer[buffer_size];
|
||||
bool result = false;
|
||||
bool error = false;
|
||||
|
||||
furi_string_reset(value);
|
||||
|
||||
while(true) {
|
||||
size_t was_read = stream_read(stream, buffer, buffer_size);
|
||||
|
||||
if(was_read == 0) {
|
||||
if(state != LeadingSpace && stream_eof(stream)) {
|
||||
result = true;
|
||||
*last = true;
|
||||
} else {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
for(uint16_t i = 0; i < was_read; i++) {
|
||||
const uint8_t data = buffer[i];
|
||||
|
||||
if(state == LeadingSpace) {
|
||||
if(is_space_playground(data)) {
|
||||
continue;
|
||||
} else if(data == flipper_format_eoln) {
|
||||
stream_seek(stream, i - was_read, StreamOffsetFromCurrent);
|
||||
error = true;
|
||||
break;
|
||||
} else {
|
||||
state = ReadValue;
|
||||
furi_string_push_back(value, data);
|
||||
}
|
||||
} else if(state == ReadValue) {
|
||||
if(is_space_playground(data)) {
|
||||
state = TrailingSpace;
|
||||
} else if(data == flipper_format_eoln) {
|
||||
if(!stream_seek(stream, i - was_read, StreamOffsetFromCurrent)) {
|
||||
error = true;
|
||||
} else {
|
||||
result = true;
|
||||
*last = true;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
furi_string_push_back(value, data);
|
||||
}
|
||||
} else if(state == TrailingSpace) {
|
||||
if(is_space_playground(data)) {
|
||||
continue;
|
||||
} else if(!stream_seek(stream, i - was_read, StreamOffsetFromCurrent)) {
|
||||
error = true;
|
||||
} else {
|
||||
*last = (data == flipper_format_eoln);
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(error || result) break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool subghz_history_read_int32(Stream* stream, int32_t* _data, const uint16_t data_size) {
|
||||
bool result = false;
|
||||
result = true;
|
||||
FuriString* value;
|
||||
value = furi_string_alloc();
|
||||
|
||||
for(size_t i = 0; i < data_size; i++) {
|
||||
bool last = false;
|
||||
result = subghz_history_stream_read_value(stream, value, &last);
|
||||
if(result) {
|
||||
int scan_values = 0;
|
||||
|
||||
int32_t* data = _data;
|
||||
scan_values = sscanf(furi_string_get_cstr(value), "%" PRIi32, &data[i]);
|
||||
|
||||
if(scan_values != 1) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if(last && ((i + 1) != data_size)) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_free(value);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t subghz_history_rand_range(uint32_t min, uint32_t max) {
|
||||
// size of range, inclusive
|
||||
const uint32_t length_of_range = max - min + 1;
|
||||
|
||||
// add n so that we don't return a number below our range
|
||||
return (uint32_t)(rand() % length_of_range + min);
|
||||
}
|
||||
|
||||
bool subghz_history_write_file_noise(
|
||||
Stream* file,
|
||||
bool is_negative_start,
|
||||
size_t current_position,
|
||||
bool empty_line) {
|
||||
size_t was_write = 0;
|
||||
if(empty_line) {
|
||||
was_write = stream_write_format(file, "%s: ", SUBGHZ_HISTORY_TMP_RAW_KEY);
|
||||
|
||||
if(was_write <= 0) {
|
||||
FURI_LOG_E(TAG, "Can't write key!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int8_t first;
|
||||
int8_t second;
|
||||
if(is_negative_start) {
|
||||
first = -1;
|
||||
second = 1;
|
||||
} else {
|
||||
first = 1;
|
||||
second = -1;
|
||||
}
|
||||
while(current_position < MAX_LINE) {
|
||||
was_write = stream_write_format(
|
||||
file,
|
||||
"%ld %ld ",
|
||||
subghz_history_rand_range(
|
||||
SUBGHZ_HISTORY_TMP_SIGNAL_MIN, SUBGHZ_HISTORY_TMP_SIGNAL_MAX) *
|
||||
first,
|
||||
subghz_history_rand_range(
|
||||
SUBGHZ_HISTORY_TMP_SIGNAL_MIN, SUBGHZ_HISTORY_TMP_SIGNAL_MAX) *
|
||||
second);
|
||||
|
||||
if(was_write <= 0) {
|
||||
FURI_LOG_E(TAG, "Can't write random values!");
|
||||
return false;
|
||||
}
|
||||
|
||||
current_position += was_write;
|
||||
}
|
||||
|
||||
// Step back to write \n instead of space
|
||||
size_t offset = stream_tell(file);
|
||||
if(stream_seek(file, offset - 1, StreamOffsetFromCurrent)) {
|
||||
FURI_LOG_E(TAG, "Step back failed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return stream_write_char(file, flipper_format_eoln) > 0;
|
||||
}
|
||||
|
||||
bool subghz_history_write_file_data(
|
||||
Stream* src,
|
||||
Stream* file,
|
||||
bool* is_negative_start,
|
||||
size_t* current_position) {
|
||||
size_t offset_file = 0;
|
||||
bool result = false;
|
||||
int32_t value = 0;
|
||||
|
||||
do {
|
||||
if(!subghz_history_read_int32(src, &value, 1)) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
offset_file = stream_tell(file);
|
||||
stream_write_format(file, "%ld ", value);
|
||||
*current_position += stream_tell(file) - offset_file;
|
||||
|
||||
if(*current_position > MAX_LINE) {
|
||||
if((is_negative_start && value > 0) || (!is_negative_start && value < 0)) {
|
||||
// Align values
|
||||
continue;
|
||||
}
|
||||
|
||||
if(stream_write_format(file, "\n%s: ", SUBGHZ_HISTORY_TMP_RAW_KEY) == 0) {
|
||||
FURI_LOG_E(TAG, "Can't write new line!");
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
*current_position = 0;
|
||||
}
|
||||
} while(true);
|
||||
|
||||
*is_negative_start = value < 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool subghz_history_tmp_write_file_split(
|
||||
SubGhzHistory* instance,
|
||||
void* current_item,
|
||||
FuriString* dir_path) {
|
||||
UNUSED(instance);
|
||||
UNUSED(current_item);
|
||||
UNUSED(dir_path);
|
||||
/*furi_assert(instance);
|
||||
const char* dir_path) {
|
||||
furi_assert(instance);
|
||||
furi_assert(current_item);
|
||||
furi_assert(dir_path);*/
|
||||
//SubGhzHistoryItem* item = (SubGhzHistoryItem*)current_item;
|
||||
furi_assert(dir_path);
|
||||
#ifdef FURI_DEBUG
|
||||
FURI_LOG_I(TAG, "Save temp file splitted: %s", dir_path);
|
||||
#endif
|
||||
SubGhzHistoryItem* item = (SubGhzHistoryItem*)current_item;
|
||||
|
||||
return false;
|
||||
uint8_t buffer[buffer_size];
|
||||
Stream* src = flipper_format_get_raw_stream(item->flipper_string);
|
||||
stream_rewind(src);
|
||||
|
||||
FlipperFormat* flipper_format_file = flipper_format_file_alloc(instance->storage);
|
||||
bool result = false;
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
|
||||
do {
|
||||
if(storage_file_exists(instance->storage, dir_path) &&
|
||||
storage_common_remove(instance->storage, dir_path) != FSE_OK) {
|
||||
FURI_LOG_E(TAG, "Can't delete old file!");
|
||||
break;
|
||||
}
|
||||
path_extract_dirname(dir_path, temp_str);
|
||||
FS_Error fs_result =
|
||||
storage_common_mkdir(instance->storage, furi_string_get_cstr(temp_str));
|
||||
if(fs_result != FSE_OK && fs_result != FSE_EXIST) {
|
||||
FURI_LOG_E(TAG, "Can't create dir!");
|
||||
break;
|
||||
}
|
||||
result = flipper_format_file_open_always(flipper_format_file, dir_path);
|
||||
if(!result) {
|
||||
FURI_LOG_E(TAG, "Can't open file for write!");
|
||||
break;
|
||||
}
|
||||
Stream* file = flipper_format_get_raw_stream(flipper_format_file);
|
||||
|
||||
if(!subghz_history_stream_seek_to_key(src, SUBGHZ_HISTORY_TMP_RAW_KEY, false)) {
|
||||
FURI_LOG_E(TAG, "Can't find key!");
|
||||
break;
|
||||
}
|
||||
bool is_negative_start = false;
|
||||
bool found = false;
|
||||
|
||||
size_t offset_start;
|
||||
offset_start = stream_tell(src);
|
||||
|
||||
// Check for negative value at the start and end to align file by correct values
|
||||
size_t was_read = stream_read(src, buffer, 1);
|
||||
if(was_read <= 0) {
|
||||
FURI_LOG_E(TAG, "Can't obtain first mark!");
|
||||
break;
|
||||
}
|
||||
|
||||
is_negative_start = buffer[0] == '-';
|
||||
|
||||
// Ready to write stream to file
|
||||
size_t current_position;
|
||||
stream_rewind(src);
|
||||
current_position = stream_copy(src, file, offset_start);
|
||||
if(current_position != offset_start) {
|
||||
FURI_LOG_E(TAG, "Invalid copy header data from one stream to another!");
|
||||
break;
|
||||
}
|
||||
|
||||
found = true;
|
||||
|
||||
current_position = 0;
|
||||
if(!subghz_history_write_file_noise(file, is_negative_start, current_position, false)) {
|
||||
FURI_LOG_E(TAG, "Add start noise failed!");
|
||||
break;
|
||||
}
|
||||
|
||||
if(stream_write_format(file, "%s: ", SUBGHZ_HISTORY_TMP_RAW_KEY) == 0) {
|
||||
FURI_LOG_E(TAG, "Can't write new line!");
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!subghz_history_write_file_data(src, file, &is_negative_start, ¤t_position)) {
|
||||
FURI_LOG_E(TAG, "Split by lines failed!");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!subghz_history_write_file_noise(file, is_negative_start, current_position, false)) {
|
||||
FURI_LOG_E(TAG, "Add end noise failed!");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!subghz_history_write_file_noise(file, is_negative_start, 0, true)) {
|
||||
FURI_LOG_E(TAG, "Add end noise failed!");
|
||||
break;
|
||||
}
|
||||
|
||||
result = found;
|
||||
} while(false);
|
||||
flipper_format_file_close(flipper_format_file);
|
||||
flipper_format_free(flipper_format_file);
|
||||
furi_string_free(temp_str);
|
||||
|
||||
item->is_file = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void subghz_history_tmp_write_file_full(
|
||||
@@ -482,8 +883,6 @@ void subghz_history_tmp_write_file_full(
|
||||
stream_rewind(dst);
|
||||
if(stream_save_to_file(
|
||||
dst, instance->storage, furi_string_get_cstr(dir_path), FSOM_CREATE_ALWAYS) > 0) {
|
||||
flipper_format_free(item->flipper_string);
|
||||
item->flipper_string = NULL;
|
||||
#ifdef FURI_DEBUG
|
||||
FURI_LOG_I(TAG, "Save done!");
|
||||
#endif
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <lib/flipper_format/flipper_format.h>
|
||||
#include "helpers/subghz_types.h"
|
||||
#include <lib/subghz/types.h>
|
||||
|
||||
typedef struct SubGhzHistory SubGhzHistory;
|
||||
|
||||
@@ -84,6 +84,13 @@ void subghz_history_get_text_item_menu(SubGhzHistory* instance, FuriString* outp
|
||||
*/
|
||||
bool subghz_history_get_text_space_left(SubGhzHistory* instance, FuriString* output);
|
||||
|
||||
/** Return last index
|
||||
*
|
||||
* @param instance - SubGhzHistory instance
|
||||
* @return
|
||||
*/
|
||||
uint16_t subghz_history_get_last_index(SubGhzHistory* instance);
|
||||
|
||||
/** Add protocol to history
|
||||
*
|
||||
* @param instance - SubGhzHistory instance
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "subghz_history.h"
|
||||
#include <toolbox/stream/stream.h>
|
||||
|
||||
/**
|
||||
* @brief Generate filename like 000.tmp
|
||||
@@ -52,7 +53,7 @@ void subghz_history_item_free(void* current_item);
|
||||
void subghz_history_clean_item_array(SubGhzHistory* instance);
|
||||
|
||||
/**
|
||||
* @brief Write temp file fully, without spliting
|
||||
* @brief Write temp file fully, without splitting
|
||||
*
|
||||
* @param instance - SubGhzHistory*
|
||||
* @param current_item - SubGhzHistoryItem*
|
||||
@@ -64,15 +65,99 @@ void subghz_history_tmp_write_file_full(
|
||||
FuriString* dir_path);
|
||||
|
||||
/**
|
||||
* @brief Write temp splited to lines
|
||||
* @brief Write temp spited to lines
|
||||
*
|
||||
* @param instance - SubGhzHistory*
|
||||
* @param current_item - SubGhzHistoryItem*
|
||||
* @param dir_path - full path to file
|
||||
* @return true - file saved
|
||||
* @return false - error occured
|
||||
* @return false - error occurred
|
||||
*/
|
||||
bool subghz_history_tmp_write_file_split(
|
||||
SubGhzHistory* instance,
|
||||
void* current_item,
|
||||
FuriString* dir_path);
|
||||
const char* dir_path);
|
||||
|
||||
/**
|
||||
* @brief generate random value
|
||||
*
|
||||
* @param min - min value
|
||||
* @param max - max value
|
||||
* @return uint32_t
|
||||
*/
|
||||
uint32_t subghz_history_rand_range(uint32_t min, uint32_t max);
|
||||
|
||||
/**
|
||||
* @brief write random noise signals to file applying to max line value
|
||||
*
|
||||
* @param file - Stream*
|
||||
* @param is_negative_start - first value is negative or positive?
|
||||
* @param current_position - 0 if started from begining
|
||||
* @param empty_line - add RAW_Data to this line
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool subghz_history_write_file_noise(
|
||||
Stream* file,
|
||||
bool is_negative_start,
|
||||
size_t current_position,
|
||||
bool empty_line);
|
||||
|
||||
/**
|
||||
* @brief taken from flipper_format_stream_read_value_line but takes only one int32 value
|
||||
*
|
||||
* @param stream - Stream*
|
||||
* @param _data - int32_t* output data
|
||||
* @param data_size - size of data
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool subghz_history_read_int32(Stream* stream, int32_t* _data, const uint16_t data_size);
|
||||
|
||||
/**
|
||||
* @brief write payload to file spliting by lines
|
||||
*
|
||||
* @param src - Stream* of source
|
||||
* @param file - Stream* of file
|
||||
* @param is_negative_start - first value is negative or positive?
|
||||
* @param current_position - by default is 0 but in this value returned last position of payload
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool subghz_history_write_file_data(
|
||||
Stream* src,
|
||||
Stream* file,
|
||||
bool* is_negative_start,
|
||||
size_t* current_position);
|
||||
|
||||
/**
|
||||
* @brief taken from flipper_format_stream_read_valid_key
|
||||
*
|
||||
* @param stream - Stream*
|
||||
* @param key - FuriString* output value
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool subghz_history_stream_read_valid_key(Stream* stream, FuriString* key);
|
||||
|
||||
/**
|
||||
* @brief taken from flipper_format_stream_seek_to_key
|
||||
*
|
||||
* @param stream - Stream*
|
||||
* @param key - key
|
||||
* @param strict_mode - false
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool subghz_history_stream_seek_to_key(Stream* stream, const char* key, bool strict_mode);
|
||||
|
||||
/**
|
||||
* @brief taken from flipper_format_stream_read_value
|
||||
*
|
||||
* @param stream - Stream*
|
||||
* @param value - FuriString* output value
|
||||
* @param last - return position is last flag
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool subghz_history_stream_read_value(Stream* stream, FuriString* value, bool* last);
|
||||
@@ -431,7 +431,7 @@ bool subghz_save_protocol_to_file(
|
||||
do {
|
||||
//removing additional fields
|
||||
flipper_format_delete_key(flipper_format, "Repeat");
|
||||
flipper_format_delete_key(flipper_format, "Manufacture");
|
||||
//flipper_format_delete_key(flipper_format, "Manufacture");
|
||||
|
||||
// Create subghz folder directory if necessary
|
||||
if(!storage_simply_mkdir(storage, furi_string_get_cstr(file_dir))) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "helpers/subghz_types.h"
|
||||
#include <lib/subghz/types.h>
|
||||
#include "subghz.h"
|
||||
#include "views/receiver.h"
|
||||
#include "views/transmitter.h"
|
||||
@@ -12,8 +13,6 @@
|
||||
#include "views/subghz_test_static.h"
|
||||
#include "views/subghz_test_packet.h"
|
||||
#endif
|
||||
// #include <furi.h>
|
||||
// #include <furi_hal.h>
|
||||
#include <gui/gui.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <gui/scene_manager.h>
|
||||
@@ -26,16 +25,13 @@
|
||||
#include <gui/modules/widget.h>
|
||||
|
||||
#include <subghz/scenes/subghz_scene.h>
|
||||
|
||||
#include <lib/subghz/subghz_worker.h>
|
||||
|
||||
#include <lib/subghz/subghz_file_encoder_worker.h>
|
||||
|
||||
#include <lib/subghz/subghz_setting.h>
|
||||
#include <lib/subghz/receiver.h>
|
||||
#include <lib/subghz/transmitter.h>
|
||||
|
||||
#include "subghz_history.h"
|
||||
#include "subghz_setting.h"
|
||||
#include "subghz_last_settings.h"
|
||||
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
@@ -44,6 +40,7 @@
|
||||
#include "rpc/rpc_app.h"
|
||||
|
||||
#define SUBGHZ_MAX_LEN_NAME 64
|
||||
#define SUBGHZ_EXT_PRESET_NAME true
|
||||
|
||||
typedef struct {
|
||||
uint8_t fix[4];
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
// "AM270", "AM650", "FM238", "FM476",
|
||||
#define SUBGHZ_LAST_SETTING_DEFAULT_PRESET 1
|
||||
#define SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY 433920000
|
||||
#define SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_FEEDBACK_LEVEL 2
|
||||
|
||||
#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING
|
||||
#define SUBGHZ_LAST_SETTING_DEFAULT_READ_RAW 0
|
||||
@@ -21,6 +22,7 @@
|
||||
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY "Frequency"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_PRESET "Preset"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_FEEDBACK_LEVEL "FeedbackLevel"
|
||||
|
||||
SubGhzLastSettings* subghz_last_settings_alloc(void) {
|
||||
SubGhzLastSettings* instance = malloc(sizeof(SubGhzLastSettings));
|
||||
@@ -42,7 +44,9 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
|
||||
FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
|
||||
|
||||
uint32_t temp_frequency = 0;
|
||||
uint32_t temp_frequency_analyzer_feedback_level = 0;
|
||||
int32_t temp_preset = 0;
|
||||
bool frequency_analyzer_feedback_level_was_read = false;
|
||||
#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING
|
||||
uint32_t temp_read_raw = 0;
|
||||
#endif
|
||||
@@ -53,6 +57,11 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
|
||||
fff_data_file, SUBGHZ_LAST_SETTING_FIELD_PRESET, (int32_t*)&temp_preset, 1);
|
||||
flipper_format_read_uint32(
|
||||
fff_data_file, SUBGHZ_LAST_SETTING_FIELD_FREQUENCY, (uint32_t*)&temp_frequency, 1);
|
||||
frequency_analyzer_feedback_level_was_read = flipper_format_read_uint32(
|
||||
fff_data_file,
|
||||
SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_FEEDBACK_LEVEL,
|
||||
(uint32_t*)&temp_frequency_analyzer_feedback_level,
|
||||
1);
|
||||
#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING
|
||||
flipper_format_read_uint32(
|
||||
fff_data_file, SUBGHZ_LAST_SETTING_FIELD_DETECT_RAW, (uint32_t*)&temp_read_raw, 1);
|
||||
@@ -65,11 +74,17 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
|
||||
FURI_LOG_W(TAG, "Last used frequency not found or can't be used!");
|
||||
instance->frequency = SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY;
|
||||
instance->preset = SUBGHZ_LAST_SETTING_DEFAULT_PRESET;
|
||||
instance->frequency_analyzer_feedback_level =
|
||||
SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_FEEDBACK_LEVEL;
|
||||
#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING
|
||||
instance->detect_raw = SUBGHZ_LAST_SETTING_DEFAULT_READ_RAW;
|
||||
#endif
|
||||
} else {
|
||||
instance->frequency = temp_frequency;
|
||||
instance->frequency_analyzer_feedback_level =
|
||||
frequency_analyzer_feedback_level_was_read ?
|
||||
temp_frequency_analyzer_feedback_level :
|
||||
SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_FEEDBACK_LEVEL;
|
||||
#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING
|
||||
instance->detect_raw = temp_read_raw;
|
||||
#endif
|
||||
@@ -118,6 +133,13 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) {
|
||||
file, SUBGHZ_LAST_SETTING_FIELD_FREQUENCY, &instance->frequency, 1)) {
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_insert_or_update_uint32(
|
||||
file,
|
||||
SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_FEEDBACK_LEVEL,
|
||||
&instance->frequency_analyzer_feedback_level,
|
||||
1)) {
|
||||
break;
|
||||
}
|
||||
#ifdef SUBGHZ_SAVE_DETECT_RAW_SETTING
|
||||
if(!flipper_format_insert_or_update_uint32(
|
||||
file, SUBGHZ_LAST_SETTING_FIELD_DETECT_RAW, &instance->detect_raw, 1)) {
|
||||
|
||||
@@ -19,6 +19,7 @@ typedef struct {
|
||||
uint32_t detect_raw;
|
||||
#endif
|
||||
int32_t preset;
|
||||
uint32_t frequency_analyzer_feedback_level;
|
||||
} SubGhzLastSettings;
|
||||
|
||||
SubGhzLastSettings* subghz_last_settings_alloc(void);
|
||||
|
||||
@@ -217,7 +217,7 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) {
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
}
|
||||
canvas_draw_icon(canvas, 1, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]);
|
||||
canvas_draw_icon(canvas, 4, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]);
|
||||
canvas_draw_str(canvas, 15, 9 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buff));
|
||||
furi_string_reset(str_buff);
|
||||
}
|
||||
@@ -265,11 +265,30 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) {
|
||||
canvas_draw_icon(canvas, 64, 55, &I_Unlock_7x8);
|
||||
canvas_draw_str(canvas, 74, 62, "Unlocked");
|
||||
break;
|
||||
default:
|
||||
canvas_draw_str(canvas, 44, 62, furi_string_get_cstr(model->frequency_str));
|
||||
default: {
|
||||
const char* frequency_str = furi_string_get_cstr(model->frequency_str);
|
||||
canvas_draw_str(canvas, 44, 62, frequency_str);
|
||||
#ifdef SUBGHZ_EXT_PRESET_NAME
|
||||
if(model->history_item == 0 && model->mode == SubGhzViewReceiverModeLive) {
|
||||
canvas_draw_str(
|
||||
canvas, 44 + canvas_string_width(canvas, frequency_str) + 1, 62, "MHz");
|
||||
const char* str = furi_string_get_cstr(model->preset_str);
|
||||
const uint8_t vertical_offset = 7;
|
||||
const uint8_t horizontal_offset = 3;
|
||||
const uint8_t string_width = canvas_string_width(canvas, str);
|
||||
canvas_draw_str(
|
||||
canvas,
|
||||
canvas_width(canvas) - (string_width + horizontal_offset),
|
||||
vertical_offset,
|
||||
str);
|
||||
} else {
|
||||
canvas_draw_str(canvas, 79, 62, furi_string_get_cstr(model->preset_str));
|
||||
}
|
||||
#else
|
||||
canvas_draw_str(canvas, 79, 62, furi_string_get_cstr(model->preset_str));
|
||||
#endif
|
||||
canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str));
|
||||
break;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -517,4 +517,21 @@ uint32_t subghz_frequency_analyzer_get_frequency_to_save(SubGhzFrequencyAnalyzer
|
||||
false);
|
||||
|
||||
return frequency;
|
||||
}
|
||||
|
||||
uint8_t subghz_frequency_analyzer_feedback_level(
|
||||
SubGhzFrequencyAnalyzer* instance,
|
||||
uint8_t level,
|
||||
bool update) {
|
||||
furi_assert(instance);
|
||||
if(update) {
|
||||
instance->feedback_level = level;
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzFrequencyAnalyzerModel * model,
|
||||
{ model->feedback_level = instance->feedback_level; },
|
||||
true);
|
||||
}
|
||||
|
||||
return instance->feedback_level;
|
||||
}
|
||||
@@ -19,3 +19,8 @@ void subghz_frequency_analyzer_free(SubGhzFrequencyAnalyzer* subghz_static);
|
||||
View* subghz_frequency_analyzer_get_view(SubGhzFrequencyAnalyzer* subghz_static);
|
||||
|
||||
uint32_t subghz_frequency_analyzer_get_frequency_to_save(SubGhzFrequencyAnalyzer* instance);
|
||||
|
||||
uint8_t subghz_frequency_analyzer_feedback_level(
|
||||
SubGhzFrequencyAnalyzer* instance,
|
||||
uint8_t level,
|
||||
bool update);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include <applications/main/subghz/subghz_i.h>
|
||||
|
||||
#include <lib/subghz/protocols/raw.h>
|
||||
#include <lib/subghz/protocols/registry.h>
|
||||
#include <lib/subghz/protocols/protocol_items.h>
|
||||
#include <lib/subghz/types.h>
|
||||
#include <lib/subghz/protocols/keeloq.h>
|
||||
#include <lib/subghz/protocols/star_line.h>
|
||||
@@ -562,7 +562,7 @@ bool unirfremix_save_protocol_to_file(FlipperFormat* fff_file, const char* dev_f
|
||||
path_extract_dirname(dev_file_name, file_dir);
|
||||
do {
|
||||
flipper_format_delete_key(fff_file, "Repeat");
|
||||
flipper_format_delete_key(fff_file, "Manufacture");
|
||||
//flipper_format_delete_key(fff_file, "Manufacture");
|
||||
|
||||
if(!storage_simply_mkdir(storage, furi_string_get_cstr(file_dir))) {
|
||||
FURI_LOG_E(TAG, "(save) Cannot mkdir");
|
||||
@@ -602,10 +602,12 @@ void unirfremix_tx_stop(UniRFRemix* app) {
|
||||
subghz_transmitter_stop(app->tx_transmitter);
|
||||
|
||||
FURI_LOG_D(TAG, "Checking if protocol is dynamic");
|
||||
const SubGhzProtocol* registry =
|
||||
subghz_protocol_registry_get_by_name(furi_string_get_cstr(app->txpreset->protocol));
|
||||
FURI_LOG_D(TAG, "Protocol-TYPE %d", registry->type);
|
||||
if(registry && registry->type == SubGhzProtocolTypeDynamic) {
|
||||
const SubGhzProtocolRegistry* protocol_registry_items =
|
||||
subghz_environment_get_protocol_registry(app->environment);
|
||||
const SubGhzProtocol* proto = subghz_protocol_registry_get_by_name(
|
||||
protocol_registry_items, furi_string_get_cstr(app->txpreset->protocol));
|
||||
FURI_LOG_D(TAG, "Protocol-TYPE %d", proto->type);
|
||||
if(proto && proto->type == SubGhzProtocolTypeDynamic) {
|
||||
FURI_LOG_D(TAG, "Protocol is dynamic. Saving key");
|
||||
unirfremix_save_protocol_to_file(app->tx_fff_data, app->tx_file_path);
|
||||
|
||||
@@ -838,6 +840,7 @@ void unirfremix_subghz_alloc(UniRFRemix* app) {
|
||||
app->environment, EXT_PATH("subghz/assets/came_atomo"));
|
||||
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
||||
app->environment, EXT_PATH("subghz/assets/nice_flor_s"));
|
||||
subghz_environment_set_protocol_registry(app->environment, (void*)&subghz_protocol_registry);
|
||||
|
||||
app->subghz_receiver = subghz_receiver_alloc_init(app->environment);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
provides=[
|
||||
"music_player",
|
||||
"snake_game",
|
||||
"bt_hid",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -75,8 +75,8 @@ int rand_range(int min, int max) {
|
||||
void move_ball(Canvas* canvas, ArkanoidState* st) {
|
||||
st->tick++;
|
||||
|
||||
int current_speed = abs(st->speed-1 - MAX_SPEED);
|
||||
if (st->tick % current_speed != 0 && st->tick % (current_speed + 1) != 0) {
|
||||
int current_speed = abs(st->speed - 1 - MAX_SPEED);
|
||||
if(st->tick % current_speed != 0 && st->tick % (current_speed + 1) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
App(
|
||||
appid="Bluetooth_Remote",
|
||||
name="Bluetooth Remote",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="bt_hid_app",
|
||||
stack_size=1 * 1024,
|
||||
cdefines=["APP_BLE_HID"],
|
||||
requires=[
|
||||
"bt",
|
||||
"gui",
|
||||
],
|
||||
order=10,
|
||||
fap_icon="bt_remote_10px.png",
|
||||
fap_category="Tools",
|
||||
fap_icon="bt_remote_10px.png",
|
||||
fap_icon_assets="assets",
|
||||
)
|
||||
|
||||
BIN
applications/plugins/bt_hid_app/assets/Arr_dwn_7x9.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/plugins/bt_hid_app/assets/Arr_up_7x9.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/plugins/bt_hid_app/assets/Ble_connected_15x15.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/plugins/bt_hid_app/assets/Button_18x18.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/plugins/bt_hid_app/assets/Circles_47x47.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
applications/plugins/bt_hid_app/assets/Left_mouse_icon_9x9.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/plugins/bt_hid_app/assets/Like_def_11x9.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/plugins/bt_hid_app/assets/Like_pressed_17x17.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
applications/plugins/bt_hid_app/assets/Ok_btn_pressed_13x13.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/plugins/bt_hid_app/assets/Pressed_Button_13x13.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/plugins/bt_hid_app/assets/Right_mouse_icon_9x9.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/plugins/bt_hid_app/assets/Space_65x18.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/plugins/bt_hid_app/assets/Voldwn_6x6.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/plugins/bt_hid_app/assets/Volup_8x6.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
@@ -1,6 +1,7 @@
|
||||
#include "bt_hid.h"
|
||||
#include <furi_hal_bt.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
#define TAG "BtHidApp"
|
||||
|
||||
@@ -8,6 +9,7 @@ enum BtDebugSubmenuIndex {
|
||||
BtHidSubmenuIndexKeynote,
|
||||
BtHidSubmenuIndexKeyboard,
|
||||
BtHidSubmenuIndexMedia,
|
||||
BtHidSubmenuIndexTikTok,
|
||||
BtHidSubmenuIndexMouse,
|
||||
};
|
||||
|
||||
@@ -26,6 +28,9 @@ void bt_hid_submenu_callback(void* context, uint32_t index) {
|
||||
} else if(index == BtHidSubmenuIndexMouse) {
|
||||
app->view_id = BtHidViewMouse;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewMouse);
|
||||
} else if(index == BtHidSubmenuIndexTikTok) {
|
||||
app->view_id = BtHidViewTikTok;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +69,7 @@ void bt_hid_connection_status_changed_callback(BtStatus status, void* context) {
|
||||
bt_hid_keyboard_set_connected_status(bt_hid->bt_hid_keyboard, connected);
|
||||
bt_hid_media_set_connected_status(bt_hid->bt_hid_media, connected);
|
||||
bt_hid_mouse_set_connected_status(bt_hid->bt_hid_mouse, connected);
|
||||
bt_hid_tiktok_set_connected_status(bt_hid->bt_hid_tiktok, connected);
|
||||
}
|
||||
|
||||
BtHid* bt_hid_app_alloc() {
|
||||
@@ -90,6 +96,8 @@ BtHid* bt_hid_app_alloc() {
|
||||
submenu_add_item(
|
||||
app->submenu, "Keyboard", BtHidSubmenuIndexKeyboard, bt_hid_submenu_callback, app);
|
||||
submenu_add_item(app->submenu, "Media", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app);
|
||||
submenu_add_item(
|
||||
app->submenu, "TikTok Controller", BtHidSubmenuIndexTikTok, bt_hid_submenu_callback, app);
|
||||
submenu_add_item(app->submenu, "Mouse", BtHidSubmenuIndexMouse, bt_hid_submenu_callback, app);
|
||||
view_set_previous_callback(submenu_get_view(app->submenu), bt_hid_exit);
|
||||
view_dispatcher_add_view(
|
||||
@@ -126,6 +134,13 @@ BtHid* bt_hid_app_alloc() {
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BtHidViewMedia, bt_hid_media_get_view(app->bt_hid_media));
|
||||
|
||||
// TikTok view
|
||||
app->bt_hid_tiktok = bt_hid_tiktok_alloc();
|
||||
view_set_previous_callback(
|
||||
bt_hid_tiktok_get_view(app->bt_hid_tiktok), bt_hid_exit_confirm_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BtHidViewTikTok, bt_hid_tiktok_get_view(app->bt_hid_tiktok));
|
||||
|
||||
// Mouse view
|
||||
app->bt_hid_mouse = bt_hid_mouse_alloc();
|
||||
view_set_previous_callback(bt_hid_mouse_get_view(app->bt_hid_mouse), bt_hid_exit_confirm_view);
|
||||
@@ -158,6 +173,8 @@ void bt_hid_app_free(BtHid* app) {
|
||||
bt_hid_media_free(app->bt_hid_media);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewMouse);
|
||||
bt_hid_mouse_free(app->bt_hid_mouse);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok);
|
||||
bt_hid_tiktok_free(app->bt_hid_tiktok);
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
|
||||
// Close records
|
||||
@@ -185,6 +202,8 @@ int32_t bt_hid_app(void* p) {
|
||||
}
|
||||
furi_hal_bt_start_advertising();
|
||||
|
||||
DOLPHIN_DEED(DolphinDeedPluginStart);
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
bt_set_status_changed_callback(app->bt, NULL, NULL);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "views/bt_hid_keyboard.h"
|
||||
#include "views/bt_hid_media.h"
|
||||
#include "views/bt_hid_mouse.h"
|
||||
#include "views/bt_hid_tiktok.h"
|
||||
|
||||
typedef struct {
|
||||
Bt* bt;
|
||||
@@ -25,6 +26,7 @@ typedef struct {
|
||||
BtHidKeyboard* bt_hid_keyboard;
|
||||
BtHidMedia* bt_hid_media;
|
||||
BtHidMouse* bt_hid_mouse;
|
||||
BtHidTikTok* bt_hid_tiktok;
|
||||
uint32_t view_id;
|
||||
} BtHid;
|
||||
|
||||
@@ -34,5 +36,6 @@ typedef enum {
|
||||
BtHidViewKeyboard,
|
||||
BtHidViewMedia,
|
||||
BtHidViewMouse,
|
||||
BtHidViewTikTok,
|
||||
BtHidViewExitConfirm,
|
||||
} BtHidView;
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <gui/elements.h>
|
||||
#include <gui/icon_i.h>
|
||||
|
||||
#include "Bluetooth_Remote_icons.h"
|
||||
|
||||
struct BtHidKeyboard {
|
||||
View* view;
|
||||
};
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <furi_hal_usb_hid.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
#include "Bluetooth_Remote_icons.h"
|
||||
|
||||
struct BtHidKeynote {
|
||||
View* view;
|
||||
};
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <furi_hal_usb_hid.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
#include "Bluetooth_Remote_icons.h"
|
||||
|
||||
struct BtHidMedia {
|
||||
View* view;
|
||||
};
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <furi_hal_usb_hid.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
#include "Bluetooth_Remote_icons.h"
|
||||
|
||||
struct BtHidMouse {
|
||||
View* view;
|
||||
};
|
||||
|
||||
207
applications/plugins/bt_hid_app/views/bt_hid_tiktok.c
Normal file
@@ -0,0 +1,207 @@
|
||||
#include "bt_hid_tiktok.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal_bt_hid.h>
|
||||
#include <furi_hal_usb_hid.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
#include "Bluetooth_Remote_icons.h"
|
||||
|
||||
struct BtHidTikTok {
|
||||
View* view;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
bool left_pressed;
|
||||
bool up_pressed;
|
||||
bool right_pressed;
|
||||
bool down_pressed;
|
||||
bool ok_pressed;
|
||||
bool connected;
|
||||
} BtHidTikTokModel;
|
||||
|
||||
static void bt_hid_tiktok_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
BtHidTikTokModel* model = context;
|
||||
|
||||
// Header
|
||||
if(model->connected) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||
}
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "TikTok");
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
// Keypad circles
|
||||
canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47);
|
||||
|
||||
// Up
|
||||
if(model->up_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 96, 11, &I_Arr_up_7x9);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Down
|
||||
if(model->down_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 96, 44, &I_Arr_dwn_7x9);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Left
|
||||
if(model->left_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 81, 29, &I_Voldwn_6x6);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Right
|
||||
if(model->right_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 111, 29, &I_Volup_8x6);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Ok
|
||||
if(model->ok_pressed) {
|
||||
canvas_draw_icon(canvas, 91, 23, &I_Like_pressed_17x17);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 94, 27, &I_Like_def_11x9);
|
||||
}
|
||||
// Exit
|
||||
canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit");
|
||||
}
|
||||
|
||||
static void bt_hid_tiktok_process_press(BtHidTikTok* bt_hid_tiktok, InputEvent* event) {
|
||||
with_view_model(
|
||||
bt_hid_tiktok->view,
|
||||
BtHidTikTokModel * model,
|
||||
{
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = true;
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = true;
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = true;
|
||||
furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_DECREMENT);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = true;
|
||||
furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_INCREMENT);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = true;
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static void bt_hid_tiktok_process_release(BtHidTikTok* bt_hid_tiktok, InputEvent* event) {
|
||||
with_view_model(
|
||||
bt_hid_tiktok->view,
|
||||
BtHidTikTokModel * model,
|
||||
{
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = false;
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = false;
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = false;
|
||||
furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_DECREMENT);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = false;
|
||||
furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_INCREMENT);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = false;
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static bool bt_hid_tiktok_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
BtHidTikTok* bt_hid_tiktok = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypePress) {
|
||||
bt_hid_tiktok_process_press(bt_hid_tiktok, event);
|
||||
consumed = true;
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
bt_hid_tiktok_process_release(bt_hid_tiktok, event);
|
||||
consumed = true;
|
||||
} else if(event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyOk) {
|
||||
furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT);
|
||||
furi_delay_ms(50);
|
||||
furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT);
|
||||
furi_delay_ms(50);
|
||||
furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT);
|
||||
furi_delay_ms(50);
|
||||
furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyUp) {
|
||||
// Emulate up swipe
|
||||
furi_hal_bt_hid_mouse_scroll(-6);
|
||||
furi_hal_bt_hid_mouse_scroll(-12);
|
||||
furi_hal_bt_hid_mouse_scroll(-19);
|
||||
furi_hal_bt_hid_mouse_scroll(-12);
|
||||
furi_hal_bt_hid_mouse_scroll(-6);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyDown) {
|
||||
// Emulate down swipe
|
||||
furi_hal_bt_hid_mouse_scroll(6);
|
||||
furi_hal_bt_hid_mouse_scroll(12);
|
||||
furi_hal_bt_hid_mouse_scroll(19);
|
||||
furi_hal_bt_hid_mouse_scroll(12);
|
||||
furi_hal_bt_hid_mouse_scroll(6);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyBack) {
|
||||
furi_hal_bt_hid_consumer_key_release_all();
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
BtHidTikTok* bt_hid_tiktok_alloc() {
|
||||
BtHidTikTok* bt_hid_tiktok = malloc(sizeof(BtHidTikTok));
|
||||
bt_hid_tiktok->view = view_alloc();
|
||||
view_set_context(bt_hid_tiktok->view, bt_hid_tiktok);
|
||||
view_allocate_model(bt_hid_tiktok->view, ViewModelTypeLocking, sizeof(BtHidTikTokModel));
|
||||
view_set_draw_callback(bt_hid_tiktok->view, bt_hid_tiktok_draw_callback);
|
||||
view_set_input_callback(bt_hid_tiktok->view, bt_hid_tiktok_input_callback);
|
||||
|
||||
return bt_hid_tiktok;
|
||||
}
|
||||
|
||||
void bt_hid_tiktok_free(BtHidTikTok* bt_hid_tiktok) {
|
||||
furi_assert(bt_hid_tiktok);
|
||||
view_free(bt_hid_tiktok->view);
|
||||
free(bt_hid_tiktok);
|
||||
}
|
||||
|
||||
View* bt_hid_tiktok_get_view(BtHidTikTok* bt_hid_tiktok) {
|
||||
furi_assert(bt_hid_tiktok);
|
||||
return bt_hid_tiktok->view;
|
||||
}
|
||||
|
||||
void bt_hid_tiktok_set_connected_status(BtHidTikTok* bt_hid_tiktok, bool connected) {
|
||||
furi_assert(bt_hid_tiktok);
|
||||
with_view_model(
|
||||
bt_hid_tiktok->view, BtHidTikTokModel * model, { model->connected = connected; }, true);
|
||||
}
|
||||
13
applications/plugins/bt_hid_app/views/bt_hid_tiktok.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct BtHidTikTok BtHidTikTok;
|
||||
|
||||
BtHidTikTok* bt_hid_tiktok_alloc();
|
||||
|
||||
void bt_hid_tiktok_free(BtHidTikTok* bt_hid_tiktok);
|
||||
|
||||
View* bt_hid_tiktok_get_view(BtHidTikTok* bt_hid_tiktok);
|
||||
|
||||
void bt_hid_tiktok_set_connected_status(BtHidTikTok* bt_hid_tiktok, bool connected);
|
||||
674
applications/plugins/dtmf_dolphin/LICENSE
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
18
applications/plugins/dtmf_dolphin/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||

|
||||
|
||||
[Original Link](https://github.com/litui/dtmf_dolphin)
|
||||
|
||||
## DTMF Dolphin
|
||||
|
||||
DTMF (Dual-Tone Multi-Frequency) dialer, Bluebox, and Redbox.
|
||||
|
||||
Now in a release-ready state for both Dialer, Bluebox, and Redbox (US/UK) functionality!
|
||||
|
||||
Please note that using the current tone output method, the 2600 tone is scaled about 33 Hz higher than it should be. This is a limitation of the current sample rate.
|
||||
|
||||
### Educational Links:
|
||||
|
||||
* http://www.phrack.org/issues/25/7.html#article
|
||||
* https://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling
|
||||
* https://en.wikipedia.org/wiki/Blue_box
|
||||
* https://en.wikipedia.org/wiki/Red_box_(phreaking)
|
||||
16
applications/plugins/dtmf_dolphin/application.fam
Normal file
@@ -0,0 +1,16 @@
|
||||
App(
|
||||
appid="dtmf_dolphin",
|
||||
name="DTMF Dolphin",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="dtmf_dolphin_app",
|
||||
cdefines=["DTMF_DOLPHIN"],
|
||||
requires=[
|
||||
"storage",
|
||||
"gui",
|
||||
"dialogs",
|
||||
],
|
||||
fap_icon="phone.png",
|
||||
stack_size=8 * 1024,
|
||||
order=20,
|
||||
fap_category="Tools",
|
||||
)
|
||||
89
applications/plugins/dtmf_dolphin/dtmf_dolphin.c
Normal file
@@ -0,0 +1,89 @@
|
||||
#include "dtmf_dolphin_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
static bool dtmf_dolphin_app_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
DTMFDolphinApp* app = context;
|
||||
return scene_manager_handle_custom_event(app->scene_manager, event);
|
||||
}
|
||||
|
||||
static bool dtmf_dolphin_app_back_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
DTMFDolphinApp* app = context;
|
||||
return scene_manager_handle_back_event(app->scene_manager);
|
||||
}
|
||||
|
||||
static void dtmf_dolphin_app_tick_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
DTMFDolphinApp* app = context;
|
||||
|
||||
scene_manager_handle_tick_event(app->scene_manager);
|
||||
}
|
||||
|
||||
static DTMFDolphinApp* app_alloc() {
|
||||
DTMFDolphinApp* app = malloc(sizeof(DTMFDolphinApp));
|
||||
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
app->scene_manager = scene_manager_alloc(&dtmf_dolphin_scene_handlers, app);
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
app->view_dispatcher, dtmf_dolphin_app_custom_event_callback);
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
app->view_dispatcher, dtmf_dolphin_app_back_event_callback);
|
||||
view_dispatcher_set_tick_event_callback(
|
||||
app->view_dispatcher, dtmf_dolphin_app_tick_event_callback, 100);
|
||||
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
app->main_menu_list = variable_item_list_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
DTMFDolphinViewMainMenu,
|
||||
variable_item_list_get_view(app->main_menu_list));
|
||||
|
||||
app->dtmf_dolphin_dialer = dtmf_dolphin_dialer_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
DTMFDolphinViewDialer,
|
||||
dtmf_dolphin_dialer_get_view(app->dtmf_dolphin_dialer));
|
||||
|
||||
app->notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
notification_message(app->notification, &sequence_display_backlight_enforce_on);
|
||||
|
||||
scene_manager_next_scene(app->scene_manager, DTMFDolphinSceneStart);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void app_free(DTMFDolphinApp* app) {
|
||||
furi_assert(app);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, DTMFDolphinViewMainMenu);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, DTMFDolphinViewDialer);
|
||||
variable_item_list_free(app->main_menu_list);
|
||||
|
||||
dtmf_dolphin_dialer_free(app->dtmf_dolphin_dialer);
|
||||
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
scene_manager_free(app->scene_manager);
|
||||
|
||||
notification_message(app->notification, &sequence_display_backlight_enforce_auto);
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t dtmf_dolphin_app(void* p) {
|
||||
UNUSED(p);
|
||||
DTMFDolphinApp* app = app_alloc();
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
app_free(app);
|
||||
return 0;
|
||||
}
|
||||
265
applications/plugins/dtmf_dolphin/dtmf_dolphin_audio.c
Normal file
@@ -0,0 +1,265 @@
|
||||
#include "dtmf_dolphin_audio.h"
|
||||
|
||||
DTMFDolphinAudio* current_player;
|
||||
|
||||
static void dtmf_dolphin_audio_dma_isr(void* ctx) {
|
||||
FuriMessageQueue* event_queue = ctx;
|
||||
|
||||
if(LL_DMA_IsActiveFlag_HT1(DMA1)) {
|
||||
LL_DMA_ClearFlag_HT1(DMA1);
|
||||
|
||||
DTMFDolphinCustomEvent event = {.type = DTMFDolphinEventDMAHalfTransfer};
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
}
|
||||
|
||||
if(LL_DMA_IsActiveFlag_TC1(DMA1)) {
|
||||
LL_DMA_ClearFlag_TC1(DMA1);
|
||||
|
||||
DTMFDolphinCustomEvent event = {.type = DTMFDolphinEventDMAFullTransfer};
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void dtmf_dolphin_audio_clear_samples(DTMFDolphinAudio* player) {
|
||||
for(size_t i = 0; i < player->buffer_length; i++) {
|
||||
player->sample_buffer[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
DTMFDolphinOsc* dtmf_dolphin_osc_alloc() {
|
||||
DTMFDolphinOsc* osc = malloc(sizeof(DTMFDolphinOsc));
|
||||
osc->cached_freq = 0;
|
||||
osc->offset = 0;
|
||||
osc->period = 0;
|
||||
osc->lookup_table = NULL;
|
||||
return osc;
|
||||
}
|
||||
|
||||
DTMFDolphinPulseFilter* dtmf_dolphin_pulse_filter_alloc() {
|
||||
DTMFDolphinPulseFilter* pf = malloc(sizeof(DTMFDolphinPulseFilter));
|
||||
pf->duration = 0;
|
||||
pf->period = 0;
|
||||
pf->offset = 0;
|
||||
pf->lookup_table = NULL;
|
||||
return pf;
|
||||
}
|
||||
|
||||
DTMFDolphinAudio* dtmf_dolphin_audio_alloc() {
|
||||
DTMFDolphinAudio* player = malloc(sizeof(DTMFDolphinAudio));
|
||||
player->buffer_length = SAMPLE_BUFFER_LENGTH;
|
||||
player->half_buffer_length = SAMPLE_BUFFER_LENGTH / 2;
|
||||
player->sample_buffer = malloc(sizeof(uint16_t) * player->buffer_length);
|
||||
player->osc1 = dtmf_dolphin_osc_alloc();
|
||||
player->osc2 = dtmf_dolphin_osc_alloc();
|
||||
player->volume = 1.0f;
|
||||
player->queue = furi_message_queue_alloc(10, sizeof(DTMFDolphinCustomEvent));
|
||||
player->filter = dtmf_dolphin_pulse_filter_alloc();
|
||||
player->playing = false;
|
||||
dtmf_dolphin_audio_clear_samples(player);
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
size_t calc_waveform_period(float freq) {
|
||||
if(!freq) {
|
||||
return 0;
|
||||
}
|
||||
// DMA Rate calculation, thanks to Dr_Zlo
|
||||
float dma_rate = CPU_CLOCK_FREQ / 2 / DTMF_DOLPHIN_HAL_DMA_PRESCALER /
|
||||
(DTMF_DOLPHIN_HAL_DMA_AUTORELOAD + 1);
|
||||
|
||||
// Using a constant scaling modifier, which likely represents
|
||||
// the combined system overhead and isr latency.
|
||||
return (uint16_t)dma_rate * 2 / freq * 0.801923;
|
||||
}
|
||||
|
||||
void osc_generate_lookup_table(DTMFDolphinOsc* osc, float freq) {
|
||||
if(osc->lookup_table != NULL) {
|
||||
free(osc->lookup_table);
|
||||
}
|
||||
osc->offset = 0;
|
||||
osc->cached_freq = freq;
|
||||
osc->period = calc_waveform_period(freq);
|
||||
if(!osc->period) {
|
||||
osc->lookup_table = NULL;
|
||||
return;
|
||||
}
|
||||
osc->lookup_table = malloc(sizeof(float) * osc->period);
|
||||
|
||||
for(size_t i = 0; i < osc->period; i++) {
|
||||
osc->lookup_table[i] = sin(i * PERIOD_2_PI / osc->period) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
void filter_generate_lookup_table(
|
||||
DTMFDolphinPulseFilter* pf,
|
||||
uint16_t pulses,
|
||||
uint16_t pulse_ms,
|
||||
uint16_t gap_ms) {
|
||||
if(pf->lookup_table != NULL) {
|
||||
free(pf->lookup_table);
|
||||
}
|
||||
pf->offset = 0;
|
||||
|
||||
uint16_t gap_period = calc_waveform_period(1000 / (float)gap_ms);
|
||||
uint16_t pulse_period = calc_waveform_period(1000 / (float)pulse_ms);
|
||||
pf->period = pulse_period + gap_period;
|
||||
|
||||
if(!pf->period) {
|
||||
pf->lookup_table = NULL;
|
||||
return;
|
||||
}
|
||||
pf->duration = pf->period * pulses;
|
||||
pf->lookup_table = malloc(sizeof(bool) * pf->duration);
|
||||
|
||||
for(size_t i = 0; i < pf->duration; i++) {
|
||||
pf->lookup_table[i] = i % pf->period < pulse_period;
|
||||
}
|
||||
}
|
||||
|
||||
float sample_frame(DTMFDolphinOsc* osc) {
|
||||
float frame = 0.0;
|
||||
|
||||
if(osc->period) {
|
||||
frame = osc->lookup_table[osc->offset];
|
||||
osc->offset = (osc->offset + 1) % osc->period;
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
bool sample_filter(DTMFDolphinPulseFilter* pf) {
|
||||
bool frame = true;
|
||||
|
||||
if(pf->duration) {
|
||||
if(pf->offset < pf->duration) {
|
||||
frame = pf->lookup_table[pf->offset];
|
||||
pf->offset = pf->offset + 1;
|
||||
} else {
|
||||
frame = false;
|
||||
}
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
void dtmf_dolphin_osc_free(DTMFDolphinOsc* osc) {
|
||||
if(osc->lookup_table != NULL) {
|
||||
free(osc->lookup_table);
|
||||
}
|
||||
free(osc);
|
||||
}
|
||||
|
||||
void dtmf_dolphin_filter_free(DTMFDolphinPulseFilter* pf) {
|
||||
if(pf->lookup_table != NULL) {
|
||||
free(pf->lookup_table);
|
||||
}
|
||||
free(pf);
|
||||
}
|
||||
|
||||
void dtmf_dolphin_audio_free(DTMFDolphinAudio* player) {
|
||||
furi_message_queue_free(player->queue);
|
||||
dtmf_dolphin_osc_free(player->osc1);
|
||||
dtmf_dolphin_osc_free(player->osc2);
|
||||
dtmf_dolphin_filter_free(player->filter);
|
||||
free(player->sample_buffer);
|
||||
free(player);
|
||||
current_player = NULL;
|
||||
}
|
||||
|
||||
bool generate_waveform(DTMFDolphinAudio* player, uint16_t buffer_index) {
|
||||
uint16_t* sample_buffer_start = &player->sample_buffer[buffer_index];
|
||||
|
||||
for(size_t i = 0; i < player->half_buffer_length; i++) {
|
||||
float data = 0;
|
||||
if(player->osc2->period) {
|
||||
data = (sample_frame(player->osc1) / 2) + (sample_frame(player->osc2) / 2);
|
||||
} else {
|
||||
data = (sample_frame(player->osc1));
|
||||
}
|
||||
data *= sample_filter(player->filter) ? player->volume : 0.0;
|
||||
data *= UINT8_MAX / 2; // scale -128..127
|
||||
data += UINT8_MAX / 2; // to unsigned
|
||||
|
||||
if(data < 0) {
|
||||
data = 0;
|
||||
}
|
||||
|
||||
if(data > 255) {
|
||||
data = 255;
|
||||
}
|
||||
|
||||
sample_buffer_start[i] = data;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dtmf_dolphin_audio_play_tones(
|
||||
float freq1,
|
||||
float freq2,
|
||||
uint16_t pulses,
|
||||
uint16_t pulse_ms,
|
||||
uint16_t gap_ms) {
|
||||
if(current_player != NULL && current_player->playing) {
|
||||
// Cannot start playing while still playing something else
|
||||
return false;
|
||||
}
|
||||
current_player = dtmf_dolphin_audio_alloc();
|
||||
|
||||
osc_generate_lookup_table(current_player->osc1, freq1);
|
||||
osc_generate_lookup_table(current_player->osc2, freq2);
|
||||
filter_generate_lookup_table(current_player->filter, pulses, pulse_ms, gap_ms);
|
||||
|
||||
generate_waveform(current_player, 0);
|
||||
generate_waveform(current_player, current_player->half_buffer_length);
|
||||
|
||||
dtmf_dolphin_speaker_init();
|
||||
dtmf_dolphin_dma_init((uint32_t)current_player->sample_buffer, current_player->buffer_length);
|
||||
|
||||
furi_hal_interrupt_set_isr(
|
||||
FuriHalInterruptIdDma1Ch1, dtmf_dolphin_audio_dma_isr, current_player->queue);
|
||||
|
||||
dtmf_dolphin_dma_start();
|
||||
dtmf_dolphin_speaker_start();
|
||||
current_player->playing = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dtmf_dolphin_audio_stop_tones() {
|
||||
if(current_player != NULL && !current_player->playing) {
|
||||
// Can't stop a player that isn't playing.
|
||||
return false;
|
||||
}
|
||||
while(current_player->filter->offset > 0 &&
|
||||
current_player->filter->offset < current_player->filter->duration) {
|
||||
// run remaining ticks if needed to complete filter sequence
|
||||
dtmf_dolphin_audio_handle_tick();
|
||||
}
|
||||
dtmf_dolphin_speaker_stop();
|
||||
dtmf_dolphin_dma_stop();
|
||||
|
||||
furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL);
|
||||
|
||||
dtmf_dolphin_audio_free(current_player);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dtmf_dolphin_audio_handle_tick() {
|
||||
bool handled = false;
|
||||
|
||||
if(current_player) {
|
||||
DTMFDolphinCustomEvent event;
|
||||
if(furi_message_queue_get(current_player->queue, &event, 250) == FuriStatusOk) {
|
||||
if(event.type == DTMFDolphinEventDMAHalfTransfer) {
|
||||
generate_waveform(current_player, 0);
|
||||
handled = true;
|
||||
} else if(event.type == DTMFDolphinEventDMAFullTransfer) {
|
||||
generate_waveform(current_player, current_player->half_buffer_length);
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
54
applications/plugins/dtmf_dolphin/dtmf_dolphin_audio.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
// #include "dtmf_dolphin_i.h"
|
||||
#include "dtmf_dolphin_event.h"
|
||||
#include "dtmf_dolphin_hal.h"
|
||||
|
||||
#define SAMPLE_BUFFER_LENGTH 8192
|
||||
#define PERIOD_2_PI 6.2832
|
||||
#define CPU_CLOCK_FREQ 64000000
|
||||
|
||||
typedef struct {
|
||||
float cached_freq;
|
||||
size_t period;
|
||||
float* lookup_table;
|
||||
uint16_t offset;
|
||||
} DTMFDolphinOsc;
|
||||
|
||||
typedef struct {
|
||||
float duration;
|
||||
size_t period;
|
||||
bool* lookup_table;
|
||||
uint16_t offset;
|
||||
} DTMFDolphinPulseFilter;
|
||||
|
||||
typedef struct {
|
||||
size_t buffer_length;
|
||||
size_t half_buffer_length;
|
||||
uint8_t* buffer_buffer;
|
||||
uint16_t* sample_buffer;
|
||||
float volume;
|
||||
FuriMessageQueue* queue;
|
||||
DTMFDolphinOsc* osc1;
|
||||
DTMFDolphinOsc* osc2;
|
||||
DTMFDolphinPulseFilter* filter;
|
||||
bool playing;
|
||||
} DTMFDolphinAudio;
|
||||
|
||||
DTMFDolphinOsc* dtmf_dolphin_osc_alloc();
|
||||
|
||||
DTMFDolphinAudio* dtmf_dolphin_audio_alloc();
|
||||
|
||||
void dtmf_dolphin_audio_free(DTMFDolphinAudio* player);
|
||||
|
||||
void dtmf_dolphin_osc_free(DTMFDolphinOsc* osc);
|
||||
|
||||
bool dtmf_dolphin_audio_play_tones(
|
||||
float freq1,
|
||||
float freq2,
|
||||
uint16_t pulses,
|
||||
uint16_t pulse_ms,
|
||||
uint16_t gap_ms);
|
||||
|
||||
bool dtmf_dolphin_audio_stop_tones();
|
||||
|
||||
bool dtmf_dolphin_audio_handle_tick();
|
||||
207
applications/plugins/dtmf_dolphin/dtmf_dolphin_data.c
Normal file
@@ -0,0 +1,207 @@
|
||||
#include "dtmf_dolphin_data.h"
|
||||
|
||||
typedef struct {
|
||||
const uint8_t row;
|
||||
const uint8_t col;
|
||||
const uint8_t span;
|
||||
} DTMFDolphinTonePos;
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
const float frequency_1;
|
||||
const float frequency_2;
|
||||
const DTMFDolphinTonePos pos;
|
||||
const uint16_t pulses; // for Redbox
|
||||
const uint16_t pulse_ms; // for Redbox
|
||||
const uint16_t gap_duration; // for Redbox
|
||||
} DTMFDolphinTones;
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
DTMFDolphinToneSection block;
|
||||
uint8_t tone_count;
|
||||
DTMFDolphinTones tones[DTMF_DOLPHIN_MAX_TONE_COUNT];
|
||||
} DTMFDolphinSceneData;
|
||||
|
||||
DTMFDolphinSceneData DTMFDolphinSceneDataDialer = {
|
||||
.name = "Dialer",
|
||||
.block = DTMF_DOLPHIN_TONE_BLOCK_DIALER,
|
||||
.tone_count = 16,
|
||||
.tones = {
|
||||
{"1", 697.0, 1209.0, {0, 0, 1}, 0, 0, 0},
|
||||
{"2", 697.0, 1336.0, {0, 1, 1}, 0, 0, 0},
|
||||
{"3", 697.0, 1477.0, {0, 2, 1}, 0, 0, 0},
|
||||
{"A", 697.0, 1633.0, {0, 3, 1}, 0, 0, 0},
|
||||
{"4", 770.0, 1209.0, {1, 0, 1}, 0, 0, 0},
|
||||
{"5", 770.0, 1336.0, {1, 1, 1}, 0, 0, 0},
|
||||
{"6", 770.0, 1477.0, {1, 2, 1}, 0, 0, 0},
|
||||
{"B", 770.0, 1633.0, {1, 3, 1}, 0, 0, 0},
|
||||
{"7", 852.0, 1209.0, {2, 0, 1}, 0, 0, 0},
|
||||
{"8", 852.0, 1336.0, {2, 1, 1}, 0, 0, 0},
|
||||
{"9", 852.0, 1477.0, {2, 2, 1}, 0, 0, 0},
|
||||
{"C", 852.0, 1633.0, {2, 3, 1}, 0, 0, 0},
|
||||
{"*", 941.0, 1209.0, {3, 0, 1}, 0, 0, 0},
|
||||
{"0", 941.0, 1336.0, {3, 1, 1}, 0, 0, 0},
|
||||
{"#", 941.0, 1477.0, {3, 2, 1}, 0, 0, 0},
|
||||
{"D", 941.0, 1633.0, {3, 3, 1}, 0, 0, 0},
|
||||
}};
|
||||
|
||||
DTMFDolphinSceneData DTMFDolphinSceneDataBluebox = {
|
||||
.name = "Bluebox",
|
||||
.block = DTMF_DOLPHIN_TONE_BLOCK_BLUEBOX,
|
||||
.tone_count = 13,
|
||||
.tones = {
|
||||
{"1", 700.0, 900.0, {0, 0, 1}, 0, 0, 0},
|
||||
{"2", 700.0, 1100.0, {0, 1, 1}, 0, 0, 0},
|
||||
{"3", 900.0, 1100.0, {0, 2, 1}, 0, 0, 0},
|
||||
{"4", 700.0, 1300.0, {1, 0, 1}, 0, 0, 0},
|
||||
{"5", 900.0, 1300.0, {1, 1, 1}, 0, 0, 0},
|
||||
{"6", 1100.0, 1300.0, {1, 2, 1}, 0, 0, 0},
|
||||
{"7", 700.0, 1500.0, {2, 0, 1}, 0, 0, 0},
|
||||
{"8", 900.0, 1500.0, {2, 1, 1}, 0, 0, 0},
|
||||
{"9", 1100.0, 1500.0, {2, 2, 1}, 0, 0, 0},
|
||||
{"0", 1300.0, 1500.0, {3, 1, 1}, 0, 0, 0},
|
||||
{"KP", 1100.0, 1700.0, {0, 3, 2}, 0, 0, 0},
|
||||
{"ST", 1500.0, 1700.0, {1, 3, 2}, 0, 0, 0},
|
||||
{"2600", 2600.0, 0.0, {3, 2, 3}, 0, 0, 0},
|
||||
}};
|
||||
|
||||
DTMFDolphinSceneData DTMFDolphinSceneDataRedboxUS = {
|
||||
.name = "Redbox (US)",
|
||||
.block = DTMF_DOLPHIN_TONE_BLOCK_REDBOX_US,
|
||||
.tone_count = 4,
|
||||
.tones = {
|
||||
{"Nickel", 1700.0, 2200.0, {0, 0, 5}, 1, 66, 0},
|
||||
{"Dime", 1700.0, 2200.0, {1, 0, 5}, 2, 66, 66},
|
||||
{"Quarter", 1700.0, 2200.0, {2, 0, 5}, 5, 33, 33},
|
||||
{"Dollar", 1700.0, 2200.0, {3, 0, 5}, 1, 650, 0},
|
||||
}};
|
||||
|
||||
DTMFDolphinSceneData DTMFDolphinSceneDataRedboxUK = {
|
||||
.name = "Redbox (UK)",
|
||||
.block = DTMF_DOLPHIN_TONE_BLOCK_REDBOX_UK,
|
||||
.tone_count = 2,
|
||||
.tones = {
|
||||
{"10p", 1000.0, 0.0, {0, 0, 5}, 1, 200, 0},
|
||||
{"50p", 1000.0, 0.0, {1, 0, 5}, 1, 350, 0},
|
||||
}};
|
||||
|
||||
DTMFDolphinSceneData DTMFDolphinSceneDataMisc = {
|
||||
.name = "Misc",
|
||||
.block = DTMF_DOLPHIN_TONE_BLOCK_MISC,
|
||||
.tone_count = 3,
|
||||
.tones = {
|
||||
{"CCITT 11", 700.0, 1700.0, {0, 0, 5}, 0, 0, 0},
|
||||
{"CCITT 12", 900.0, 1700.0, {1, 0, 5}, 0, 0, 0},
|
||||
{"CCITT KP2", 1300.0, 1700.0, {2, 0, 5}, 0, 0, 0},
|
||||
}};
|
||||
|
||||
DTMFDolphinToneSection current_section;
|
||||
DTMFDolphinSceneData* current_scene_data;
|
||||
|
||||
void dtmf_dolphin_data_set_current_section(DTMFDolphinToneSection section) {
|
||||
current_section = section;
|
||||
|
||||
switch(current_section) {
|
||||
case DTMF_DOLPHIN_TONE_BLOCK_BLUEBOX:
|
||||
current_scene_data = &DTMFDolphinSceneDataBluebox;
|
||||
break;
|
||||
case DTMF_DOLPHIN_TONE_BLOCK_REDBOX_US:
|
||||
current_scene_data = &DTMFDolphinSceneDataRedboxUS;
|
||||
break;
|
||||
case DTMF_DOLPHIN_TONE_BLOCK_REDBOX_UK:
|
||||
current_scene_data = &DTMFDolphinSceneDataRedboxUK;
|
||||
break;
|
||||
case DTMF_DOLPHIN_TONE_BLOCK_MISC:
|
||||
current_scene_data = &DTMFDolphinSceneDataMisc;
|
||||
break;
|
||||
default: // DTMF_DOLPHIN_TONE_BLOCK_DIALER:
|
||||
current_scene_data = &DTMFDolphinSceneDataDialer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DTMFDolphinToneSection dtmf_dolphin_data_get_current_section() {
|
||||
return current_section;
|
||||
}
|
||||
|
||||
DTMFDolphinSceneData* dtmf_dolphin_data_get_current_scene_data() {
|
||||
return current_scene_data;
|
||||
}
|
||||
|
||||
bool dtmf_dolphin_data_get_tone_frequencies(float* freq1, float* freq2, uint8_t row, uint8_t col) {
|
||||
for(size_t i = 0; i < current_scene_data->tone_count; i++) {
|
||||
DTMFDolphinTones tones = current_scene_data->tones[i];
|
||||
if(tones.pos.row == row && tones.pos.col == col) {
|
||||
freq1[0] = tones.frequency_1;
|
||||
freq2[0] = tones.frequency_2;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dtmf_dolphin_data_get_filter_data(
|
||||
uint16_t* pulses,
|
||||
uint16_t* pulse_ms,
|
||||
uint16_t* gap_ms,
|
||||
uint8_t row,
|
||||
uint8_t col) {
|
||||
for(size_t i = 0; i < current_scene_data->tone_count; i++) {
|
||||
DTMFDolphinTones tones = current_scene_data->tones[i];
|
||||
if(tones.pos.row == row && tones.pos.col == col) {
|
||||
pulses[0] = tones.pulses;
|
||||
pulse_ms[0] = tones.pulse_ms;
|
||||
gap_ms[0] = tones.gap_duration;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* dtmf_dolphin_data_get_tone_name(uint8_t row, uint8_t col) {
|
||||
for(size_t i = 0; i < current_scene_data->tone_count; i++) {
|
||||
DTMFDolphinTones tones = current_scene_data->tones[i];
|
||||
if(tones.pos.row == row && tones.pos.col == col) {
|
||||
return tones.name;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* dtmf_dolphin_data_get_current_section_name() {
|
||||
if(current_scene_data) {
|
||||
return current_scene_data->name;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void dtmf_dolphin_tone_get_max_pos(uint8_t* max_rows, uint8_t* max_cols, uint8_t* max_span) {
|
||||
max_rows[0] = 0;
|
||||
max_cols[0] = 0;
|
||||
max_span[0] = 0;
|
||||
uint8_t tmp_rowspan[5] = {0, 0, 0, 0, 0};
|
||||
for(size_t i = 0; i < current_scene_data->tone_count; i++) {
|
||||
DTMFDolphinTones tones = current_scene_data->tones[i];
|
||||
if(tones.pos.row > max_rows[0]) {
|
||||
max_rows[0] = tones.pos.row;
|
||||
}
|
||||
if(tones.pos.col > max_cols[0]) {
|
||||
max_cols[0] = tones.pos.col;
|
||||
}
|
||||
tmp_rowspan[tones.pos.row] += tones.pos.span;
|
||||
if(tmp_rowspan[tones.pos.row] > max_span[0]) max_span[0] = tmp_rowspan[tones.pos.row];
|
||||
}
|
||||
max_rows[0]++;
|
||||
max_cols[0]++;
|
||||
}
|
||||
|
||||
uint8_t dtmf_dolphin_get_tone_span(uint8_t row, uint8_t col) {
|
||||
for(size_t i = 0; i < current_scene_data->tone_count; i++) {
|
||||
DTMFDolphinTones tones = current_scene_data->tones[i];
|
||||
if(tones.pos.row == row && tones.pos.col == col) {
|
||||
return tones.pos.span;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
35
applications/plugins/dtmf_dolphin/dtmf_dolphin_data.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define DTMF_DOLPHIN_MAX_TONE_COUNT 16
|
||||
|
||||
typedef enum {
|
||||
DTMF_DOLPHIN_TONE_BLOCK_DIALER,
|
||||
DTMF_DOLPHIN_TONE_BLOCK_BLUEBOX,
|
||||
DTMF_DOLPHIN_TONE_BLOCK_REDBOX_US,
|
||||
DTMF_DOLPHIN_TONE_BLOCK_REDBOX_UK,
|
||||
DTMF_DOLPHIN_TONE_BLOCK_MISC,
|
||||
} DTMFDolphinToneSection;
|
||||
|
||||
void dtmf_dolphin_data_set_current_section(DTMFDolphinToneSection section);
|
||||
|
||||
DTMFDolphinToneSection dtmf_dolphin_data_get_current_section();
|
||||
|
||||
bool dtmf_dolphin_data_get_tone_frequencies(float* freq1, float* freq2, uint8_t row, uint8_t col);
|
||||
|
||||
bool dtmf_dolphin_data_get_filter_data(
|
||||
uint16_t* pulses,
|
||||
uint16_t* pulse_ms,
|
||||
uint16_t* gap_ms,
|
||||
uint8_t row,
|
||||
uint8_t col);
|
||||
|
||||
const char* dtmf_dolphin_data_get_tone_name(uint8_t row, uint8_t col);
|
||||
|
||||
const char* dtmf_dolphin_data_get_current_section_name();
|
||||
|
||||
void dtmf_dolphin_tone_get_max_pos(uint8_t* max_rows, uint8_t* max_cols, uint8_t* max_span);
|
||||
|
||||
uint8_t dtmf_dolphin_get_tone_span(uint8_t row, uint8_t col);
|
||||
20
applications/plugins/dtmf_dolphin/dtmf_dolphin_event.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
typedef enum {
|
||||
DTMFDolphinEventVolumeUp = 0,
|
||||
DTMFDolphinEventVolumeDown,
|
||||
DTMFDolphinDialerOkCB,
|
||||
DTMFDolphinEventStartDialer,
|
||||
DTMFDolphinEventStartBluebox,
|
||||
DTMFDolphinEventStartRedboxUS,
|
||||
DTMFDolphinEventStartRedboxUK,
|
||||
DTMFDolphinEventStartMisc,
|
||||
DTMFDolphinEventPlayTones,
|
||||
DTMFDolphinEventStopTones,
|
||||
DTMFDolphinEventDMAHalfTransfer,
|
||||
DTMFDolphinEventDMAFullTransfer,
|
||||
} DTMFDolphinEvent;
|
||||
|
||||
typedef struct {
|
||||
DTMFDolphinEvent type;
|
||||
} DTMFDolphinCustomEvent;
|
||||
52
applications/plugins/dtmf_dolphin/dtmf_dolphin_hal.c
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "dtmf_dolphin_hal.h"
|
||||
|
||||
void dtmf_dolphin_speaker_init() {
|
||||
LL_TIM_InitTypeDef TIM_InitStruct = {0};
|
||||
TIM_InitStruct.Prescaler = DTMF_DOLPHIN_HAL_DMA_PRESCALER;
|
||||
TIM_InitStruct.Autoreload = DTMF_DOLPHIN_HAL_DMA_AUTORELOAD;
|
||||
LL_TIM_Init(FURI_HAL_SPEAKER_TIMER, &TIM_InitStruct);
|
||||
|
||||
LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0};
|
||||
TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1;
|
||||
TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE;
|
||||
TIM_OC_InitStruct.CompareValue = 127;
|
||||
LL_TIM_OC_Init(FURI_HAL_SPEAKER_TIMER, FURI_HAL_SPEAKER_CHANNEL, &TIM_OC_InitStruct);
|
||||
}
|
||||
|
||||
void dtmf_dolphin_speaker_start() {
|
||||
LL_TIM_EnableAllOutputs(FURI_HAL_SPEAKER_TIMER);
|
||||
LL_TIM_EnableCounter(FURI_HAL_SPEAKER_TIMER);
|
||||
}
|
||||
|
||||
void dtmf_dolphin_speaker_stop() {
|
||||
LL_TIM_DisableAllOutputs(FURI_HAL_SPEAKER_TIMER);
|
||||
LL_TIM_DisableCounter(FURI_HAL_SPEAKER_TIMER);
|
||||
}
|
||||
|
||||
void dtmf_dolphin_dma_init(uint32_t address, size_t size) {
|
||||
uint32_t dma_dst = (uint32_t) & (FURI_HAL_SPEAKER_TIMER->CCR1);
|
||||
|
||||
LL_DMA_ConfigAddresses(DMA_INSTANCE, address, dma_dst, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
|
||||
LL_DMA_SetDataLength(DMA_INSTANCE, size);
|
||||
|
||||
LL_DMA_SetPeriphRequest(DMA_INSTANCE, LL_DMAMUX_REQ_TIM16_UP);
|
||||
LL_DMA_SetDataTransferDirection(DMA_INSTANCE, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
|
||||
LL_DMA_SetChannelPriorityLevel(DMA_INSTANCE, LL_DMA_PRIORITY_VERYHIGH);
|
||||
LL_DMA_SetMode(DMA_INSTANCE, LL_DMA_MODE_CIRCULAR);
|
||||
LL_DMA_SetPeriphIncMode(DMA_INSTANCE, LL_DMA_PERIPH_NOINCREMENT);
|
||||
LL_DMA_SetMemoryIncMode(DMA_INSTANCE, LL_DMA_MEMORY_INCREMENT);
|
||||
LL_DMA_SetPeriphSize(DMA_INSTANCE, LL_DMA_PDATAALIGN_HALFWORD);
|
||||
LL_DMA_SetMemorySize(DMA_INSTANCE, LL_DMA_MDATAALIGN_HALFWORD);
|
||||
|
||||
LL_DMA_EnableIT_TC(DMA_INSTANCE);
|
||||
LL_DMA_EnableIT_HT(DMA_INSTANCE);
|
||||
}
|
||||
|
||||
void dtmf_dolphin_dma_start() {
|
||||
LL_DMA_EnableChannel(DMA_INSTANCE);
|
||||
LL_TIM_EnableDMAReq_UPDATE(FURI_HAL_SPEAKER_TIMER);
|
||||
}
|
||||
|
||||
void dtmf_dolphin_dma_stop() {
|
||||
LL_DMA_DisableChannel(DMA_INSTANCE);
|
||||
}
|
||||
33
applications/plugins/dtmf_dolphin/dtmf_dolphin_hal.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <stm32wb55xx.h>
|
||||
#include <stm32wbxx_ll_tim.h>
|
||||
#include <stm32wbxx_ll_dma.h>
|
||||
|
||||
#define FURI_HAL_SPEAKER_TIMER TIM16
|
||||
#define FURI_HAL_SPEAKER_CHANNEL LL_TIM_CHANNEL_CH1
|
||||
#define DMA_INSTANCE DMA1, LL_DMA_CHANNEL_1
|
||||
|
||||
#define DTMF_DOLPHIN_HAL_DMA_PRESCALER 4
|
||||
#define DTMF_DOLPHIN_HAL_DMA_AUTORELOAD 255
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void dtmf_dolphin_speaker_init();
|
||||
|
||||
void dtmf_dolphin_speaker_start();
|
||||
|
||||
void dtmf_dolphin_speaker_stop();
|
||||
|
||||
void dtmf_dolphin_dma_init(uint32_t address, size_t size);
|
||||
|
||||
void dtmf_dolphin_dma_start();
|
||||
|
||||
void dtmf_dolphin_dma_stop();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
41
applications/plugins/dtmf_dolphin/dtmf_dolphin_i.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "scenes/dtmf_dolphin_scene.h"
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
// #include <gui/modules/submenu.h>
|
||||
// #include <gui/modules/widget.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <input/input.h>
|
||||
|
||||
#include "dtmf_dolphin_event.h"
|
||||
|
||||
#include "views/dtmf_dolphin_dialer.h"
|
||||
|
||||
#define TAG "DTMFDolphin"
|
||||
|
||||
enum DTMFDolphinSceneState {
|
||||
DTMFDolphinSceneStateDialer,
|
||||
DTMFDolphinSceneStateBluebox,
|
||||
DTMFDolphinSceneStateRedboxUS,
|
||||
DTMFDolphinSceneStateRedboxUK,
|
||||
DTMFDolphinSceneStateMisc,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
ViewDispatcher* view_dispatcher;
|
||||
SceneManager* scene_manager;
|
||||
VariableItemList* main_menu_list;
|
||||
DTMFDolphinDialer* dtmf_dolphin_dialer;
|
||||
|
||||
Gui* gui;
|
||||
// ButtonPanel* dialer_button_panel;
|
||||
// ButtonPanel* bluebox_button_panel;
|
||||
// ButtonPanel* redbox_button_panel;
|
||||
NotificationApp* notification;
|
||||
} DTMFDolphinApp;
|
||||
|
||||
typedef enum { DTMFDolphinViewMainMenu, DTMFDolphinViewDialer } DTMFDolphinView;
|
||||
BIN
applications/plugins/dtmf_dolphin/phone.png
Normal file
|
After Width: | Height: | Size: 306 B |
BIN
applications/plugins/dtmf_dolphin/pics/dialer.jpg
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
@@ -0,0 +1,30 @@
|
||||
#include "dtmf_dolphin_scene.h"
|
||||
|
||||
// Generate scene on_enter handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||
void (*const dtmf_dolphin_scene_on_enter_handlers[])(void*) = {
|
||||
#include "dtmf_dolphin_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 dtmf_dolphin_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||
#include "dtmf_dolphin_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 dtmf_dolphin_scene_on_exit_handlers[])(void* context) = {
|
||||
#include "dtmf_dolphin_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Initialize scene handlers configuration structure
|
||||
const SceneManagerHandlers dtmf_dolphin_scene_handlers = {
|
||||
.on_enter_handlers = dtmf_dolphin_scene_on_enter_handlers,
|
||||
.on_event_handlers = dtmf_dolphin_scene_on_event_handlers,
|
||||
.on_exit_handlers = dtmf_dolphin_scene_on_exit_handlers,
|
||||
.scene_num = DTMFDolphinSceneNum,
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
// Generate scene id and total number
|
||||
#define ADD_SCENE(prefix, name, id) DTMFDolphinScene##id,
|
||||
typedef enum {
|
||||
#include "dtmf_dolphin_scene_config.h"
|
||||
DTMFDolphinSceneNum,
|
||||
} DTMFDolphinScene;
|
||||
#undef ADD_SCENE
|
||||
|
||||
extern const SceneManagerHandlers dtmf_dolphin_scene_handlers;
|
||||
|
||||
// Generate scene on_enter handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||
#include "dtmf_dolphin_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 "dtmf_dolphin_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 "dtmf_dolphin_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
@@ -0,0 +1,2 @@
|
||||
ADD_SCENE(dtmf_dolphin, start, Start)
|
||||
ADD_SCENE(dtmf_dolphin, dialer, Dialer)
|
||||
@@ -0,0 +1,46 @@
|
||||
#include "../dtmf_dolphin_i.h"
|
||||
// #include "../dtmf_dolphin_data.h"
|
||||
// #include "../dtmf_dolphin_audio.h"
|
||||
|
||||
void dtmf_dolphin_scene_dialer_on_enter(void* context) {
|
||||
DTMFDolphinApp* app = context;
|
||||
DTMFDolphinScene scene_id = DTMFDolphinSceneDialer;
|
||||
enum DTMFDolphinSceneState state = scene_manager_get_scene_state(app->scene_manager, scene_id);
|
||||
|
||||
switch(state) {
|
||||
case DTMFDolphinSceneStateBluebox:
|
||||
dtmf_dolphin_data_set_current_section(DTMF_DOLPHIN_TONE_BLOCK_BLUEBOX);
|
||||
break;
|
||||
case DTMFDolphinSceneStateRedboxUS:
|
||||
dtmf_dolphin_data_set_current_section(DTMF_DOLPHIN_TONE_BLOCK_REDBOX_US);
|
||||
break;
|
||||
case DTMFDolphinSceneStateRedboxUK:
|
||||
dtmf_dolphin_data_set_current_section(DTMF_DOLPHIN_TONE_BLOCK_REDBOX_UK);
|
||||
break;
|
||||
case DTMFDolphinSceneStateMisc:
|
||||
dtmf_dolphin_data_set_current_section(DTMF_DOLPHIN_TONE_BLOCK_MISC);
|
||||
break;
|
||||
default:
|
||||
dtmf_dolphin_data_set_current_section(DTMF_DOLPHIN_TONE_BLOCK_DIALER);
|
||||
break;
|
||||
}
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, DTMFDolphinViewDialer);
|
||||
}
|
||||
|
||||
bool dtmf_dolphin_scene_dialer_on_event(void* context, SceneManagerEvent event) {
|
||||
DTMFDolphinApp* app = context;
|
||||
UNUSED(app);
|
||||
UNUSED(event);
|
||||
bool consumed = false;
|
||||
|
||||
// if(event.type == SceneManagerEventTypeTick) {
|
||||
// consumed = true;
|
||||
// }
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void dtmf_dolphin_scene_dialer_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
#include "../dtmf_dolphin_i.h"
|
||||
|
||||
static void dtmf_dolphin_scene_start_main_menu_enter_callback(void* context, uint32_t index) {
|
||||
DTMFDolphinApp* app = context;
|
||||
uint8_t cust_event = 255;
|
||||
switch(index) {
|
||||
case 0:
|
||||
cust_event = DTMFDolphinEventStartDialer;
|
||||
break;
|
||||
case 1:
|
||||
cust_event = DTMFDolphinEventStartBluebox;
|
||||
break;
|
||||
case 2:
|
||||
cust_event = DTMFDolphinEventStartRedboxUS;
|
||||
break;
|
||||
case 3:
|
||||
cust_event = DTMFDolphinEventStartRedboxUK;
|
||||
break;
|
||||
case 4:
|
||||
cust_event = DTMFDolphinEventStartMisc;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, cust_event);
|
||||
}
|
||||
|
||||
void dtmf_dolphin_scene_start_on_enter(void* context) {
|
||||
DTMFDolphinApp* app = context;
|
||||
VariableItemList* var_item_list = app->main_menu_list;
|
||||
|
||||
// VariableItem* item;
|
||||
variable_item_list_set_enter_callback(
|
||||
var_item_list, dtmf_dolphin_scene_start_main_menu_enter_callback, app);
|
||||
|
||||
variable_item_list_add(var_item_list, "Dialer", 0, NULL, context);
|
||||
variable_item_list_add(var_item_list, "Bluebox", 0, NULL, context);
|
||||
variable_item_list_add(var_item_list, "Redbox (US)", 0, NULL, context);
|
||||
variable_item_list_add(var_item_list, "Redbox (UK)", 0, NULL, context);
|
||||
variable_item_list_add(var_item_list, "Misc", 0, NULL, context);
|
||||
|
||||
variable_item_list_set_selected_item(
|
||||
var_item_list, scene_manager_get_scene_state(app->scene_manager, DTMFDolphinSceneStart));
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, DTMFDolphinViewMainMenu);
|
||||
}
|
||||
|
||||
bool dtmf_dolphin_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
DTMFDolphinApp* app = context;
|
||||
UNUSED(app);
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
uint8_t sc_state;
|
||||
|
||||
switch(event.event) {
|
||||
case DTMFDolphinEventStartDialer:
|
||||
sc_state = DTMFDolphinSceneStateDialer;
|
||||
break;
|
||||
case DTMFDolphinEventStartBluebox:
|
||||
sc_state = DTMFDolphinSceneStateBluebox;
|
||||
break;
|
||||
case DTMFDolphinEventStartRedboxUS:
|
||||
sc_state = DTMFDolphinSceneStateRedboxUS;
|
||||
break;
|
||||
case DTMFDolphinEventStartRedboxUK:
|
||||
sc_state = DTMFDolphinSceneStateRedboxUK;
|
||||
break;
|
||||
case DTMFDolphinEventStartMisc:
|
||||
sc_state = DTMFDolphinSceneStateMisc;
|
||||
break;
|
||||
default:
|
||||
return consumed;
|
||||
}
|
||||
scene_manager_set_scene_state(app->scene_manager, DTMFDolphinSceneDialer, sc_state);
|
||||
scene_manager_next_scene(app->scene_manager, DTMFDolphinSceneDialer);
|
||||
|
||||
consumed = true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void dtmf_dolphin_scene_start_on_exit(void* context) {
|
||||
DTMFDolphinApp* app = context;
|
||||
variable_item_list_reset(app->main_menu_list);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include "../dtmf_dolphin_event.h"
|
||||
#include "../dtmf_dolphin_data.h"
|
||||
#include "../dtmf_dolphin_audio.h"
|
||||
|
||||
#define DTMF_DOLPHIN_NUMPAD_X 1
|
||||
#define DTMF_DOLPHIN_NUMPAD_Y 14
|
||||
#define DTMF_DOLPHIN_BUTTON_WIDTH 13
|
||||
#define DTMF_DOLPHIN_BUTTON_HEIGHT 13
|
||||
#define DTMF_DOLPHIN_BUTTON_PADDING 1 // all sides
|
||||
350
applications/plugins/dtmf_dolphin/views/dtmf_dolphin_dialer.c
Normal file
@@ -0,0 +1,350 @@
|
||||
#include "dtmf_dolphin_dialer.h"
|
||||
|
||||
#include <gui/elements.h>
|
||||
|
||||
typedef struct DTMFDolphinDialer {
|
||||
View* view;
|
||||
DTMFDolphinDialerOkCallback callback;
|
||||
void* context;
|
||||
} DTMFDolphinDialer;
|
||||
|
||||
typedef struct {
|
||||
DTMFDolphinToneSection section;
|
||||
uint8_t row;
|
||||
uint8_t col;
|
||||
float freq1;
|
||||
float freq2;
|
||||
bool playing;
|
||||
uint16_t pulses;
|
||||
uint16_t pulse_ms;
|
||||
uint16_t gap_ms;
|
||||
} DTMFDolphinDialerModel;
|
||||
|
||||
static bool dtmf_dolphin_dialer_process_up(DTMFDolphinDialer* dtmf_dolphin_dialer);
|
||||
static bool dtmf_dolphin_dialer_process_down(DTMFDolphinDialer* dtmf_dolphin_dialer);
|
||||
static bool dtmf_dolphin_dialer_process_left(DTMFDolphinDialer* dtmf_dolphin_dialer);
|
||||
static bool dtmf_dolphin_dialer_process_right(DTMFDolphinDialer* dtmf_dolphin_dialer);
|
||||
static bool
|
||||
dtmf_dolphin_dialer_process_ok(DTMFDolphinDialer* dtmf_dolphin_dialer, InputEvent* event);
|
||||
|
||||
void draw_button(Canvas* canvas, uint8_t row, uint8_t col, bool invert) {
|
||||
uint8_t left = DTMF_DOLPHIN_NUMPAD_X + // ((col + 1) * DTMF_DOLPHIN_BUTTON_PADDING) +
|
||||
(col * DTMF_DOLPHIN_BUTTON_WIDTH);
|
||||
// (col * DTMF_DOLPHIN_BUTTON_PADDING);
|
||||
uint8_t top = DTMF_DOLPHIN_NUMPAD_Y + // ((row + 1) * DTMF_DOLPHIN_BUTTON_PADDING) +
|
||||
(row * DTMF_DOLPHIN_BUTTON_HEIGHT);
|
||||
// (row * DTMF_DOLPHIN_BUTTON_PADDING);
|
||||
|
||||
uint8_t span = dtmf_dolphin_get_tone_span(row, col);
|
||||
|
||||
if(span == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
if(invert)
|
||||
canvas_draw_rbox(
|
||||
canvas,
|
||||
left,
|
||||
top,
|
||||
(DTMF_DOLPHIN_BUTTON_WIDTH * span) - (DTMF_DOLPHIN_BUTTON_PADDING * 2),
|
||||
DTMF_DOLPHIN_BUTTON_HEIGHT - (DTMF_DOLPHIN_BUTTON_PADDING * 2),
|
||||
2);
|
||||
else
|
||||
canvas_draw_rframe(
|
||||
canvas,
|
||||
left,
|
||||
top,
|
||||
(DTMF_DOLPHIN_BUTTON_WIDTH * span) - (DTMF_DOLPHIN_BUTTON_PADDING * 2),
|
||||
DTMF_DOLPHIN_BUTTON_HEIGHT - (DTMF_DOLPHIN_BUTTON_PADDING * 2),
|
||||
2);
|
||||
|
||||
if(invert) canvas_invert_color(canvas);
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
// canvas_set_color(canvas, invert ? ColorWhite : ColorBlack);
|
||||
canvas_draw_str_aligned(
|
||||
canvas,
|
||||
left - 1 + (int)((DTMF_DOLPHIN_BUTTON_WIDTH * span) / 2),
|
||||
top + (int)(DTMF_DOLPHIN_BUTTON_HEIGHT / 2),
|
||||
AlignCenter,
|
||||
AlignCenter,
|
||||
dtmf_dolphin_data_get_tone_name(row, col));
|
||||
|
||||
if(invert) canvas_invert_color(canvas);
|
||||
}
|
||||
|
||||
void draw_dialer(Canvas* canvas, void* _model) {
|
||||
DTMFDolphinDialerModel* model = _model;
|
||||
uint8_t max_rows;
|
||||
uint8_t max_cols;
|
||||
uint8_t max_span;
|
||||
dtmf_dolphin_tone_get_max_pos(&max_rows, &max_cols, &max_span);
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
for(int r = 0; r < max_rows; r++) {
|
||||
for(int c = 0; c < max_cols; c++) {
|
||||
if(model->row == r && model->col == c)
|
||||
draw_button(canvas, r, c, true);
|
||||
else
|
||||
draw_button(canvas, r, c, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update_frequencies(DTMFDolphinDialerModel* model) {
|
||||
dtmf_dolphin_data_get_tone_frequencies(&model->freq1, &model->freq2, model->row, model->col);
|
||||
dtmf_dolphin_data_get_filter_data(
|
||||
&model->pulses, &model->pulse_ms, &model->gap_ms, model->row, model->col);
|
||||
}
|
||||
|
||||
static void dtmf_dolphin_dialer_draw_callback(Canvas* canvas, void* _model) {
|
||||
DTMFDolphinDialerModel* model = _model;
|
||||
if(model->playing) {
|
||||
// Leverage the prioritized draw callback to handle
|
||||
// the DMA so that it doesn't skip.
|
||||
dtmf_dolphin_audio_handle_tick();
|
||||
// Don't do any drawing if audio is playing.
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(
|
||||
canvas,
|
||||
canvas_width(canvas) / 2,
|
||||
canvas_height(canvas) / 2,
|
||||
AlignCenter,
|
||||
AlignCenter,
|
||||
"Playing Tones");
|
||||
return;
|
||||
}
|
||||
update_frequencies(model);
|
||||
uint8_t max_rows = 0;
|
||||
uint8_t max_cols = 0;
|
||||
uint8_t max_span = 0;
|
||||
dtmf_dolphin_tone_get_max_pos(&max_rows, &max_cols, &max_span);
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text(canvas, 2, 10, dtmf_dolphin_data_get_current_section_name());
|
||||
canvas_draw_line(
|
||||
canvas,
|
||||
(max_span * DTMF_DOLPHIN_BUTTON_WIDTH) + 1,
|
||||
0,
|
||||
(max_span * DTMF_DOLPHIN_BUTTON_WIDTH) + 1,
|
||||
canvas_height(canvas));
|
||||
elements_multiline_text(canvas, (max_span * DTMF_DOLPHIN_BUTTON_WIDTH) + 4, 10, "Detail");
|
||||
canvas_draw_line(
|
||||
canvas, 0, DTMF_DOLPHIN_NUMPAD_Y - 3, canvas_width(canvas), DTMF_DOLPHIN_NUMPAD_Y - 3);
|
||||
// elements_multiline_text_aligned(canvas, 64, 2, AlignCenter, AlignTop, "Dialer Mode");
|
||||
|
||||
draw_dialer(canvas, model);
|
||||
|
||||
FuriString* output = furi_string_alloc();
|
||||
|
||||
if(model->freq1 && model->freq2) {
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"Dual Tone\nF1: %u Hz\nF2: %u Hz\n",
|
||||
(unsigned int)model->freq1,
|
||||
(unsigned int)model->freq2);
|
||||
} else if(model->freq1) {
|
||||
furi_string_cat_printf(output, "Single Tone\nF: %u Hz\n", (unsigned int)model->freq1);
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
if(model->pulse_ms) {
|
||||
furi_string_cat_printf(output, "P: %u * %u ms\n", model->pulses, model->pulse_ms);
|
||||
}
|
||||
if(model->gap_ms) {
|
||||
furi_string_cat_printf(output, "Gaps: %u ms\n", model->gap_ms);
|
||||
}
|
||||
elements_multiline_text(
|
||||
canvas, (max_span * DTMF_DOLPHIN_BUTTON_WIDTH) + 4, 21, furi_string_get_cstr(output));
|
||||
|
||||
furi_string_free(output);
|
||||
}
|
||||
|
||||
static bool dtmf_dolphin_dialer_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
DTMFDolphinDialer* dtmf_dolphin_dialer = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyRight) {
|
||||
consumed = dtmf_dolphin_dialer_process_right(dtmf_dolphin_dialer);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
consumed = dtmf_dolphin_dialer_process_left(dtmf_dolphin_dialer);
|
||||
} else if(event->key == InputKeyUp) {
|
||||
consumed = dtmf_dolphin_dialer_process_up(dtmf_dolphin_dialer);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
consumed = dtmf_dolphin_dialer_process_down(dtmf_dolphin_dialer);
|
||||
}
|
||||
|
||||
} else if(event->key == InputKeyOk) {
|
||||
consumed = dtmf_dolphin_dialer_process_ok(dtmf_dolphin_dialer, event);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
static bool dtmf_dolphin_dialer_process_up(DTMFDolphinDialer* dtmf_dolphin_dialer) {
|
||||
with_view_model(
|
||||
dtmf_dolphin_dialer->view,
|
||||
DTMFDolphinDialerModel * model,
|
||||
{
|
||||
uint8_t span = 0;
|
||||
uint8_t cursor = model->row;
|
||||
while(span == 0 && cursor > 0) {
|
||||
cursor--;
|
||||
span = dtmf_dolphin_get_tone_span(cursor, model->col);
|
||||
}
|
||||
if(span != 0) {
|
||||
model->row = cursor;
|
||||
}
|
||||
},
|
||||
true);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool dtmf_dolphin_dialer_process_down(DTMFDolphinDialer* dtmf_dolphin_dialer) {
|
||||
uint8_t max_rows = 0;
|
||||
uint8_t max_cols = 0;
|
||||
uint8_t max_span = 0;
|
||||
dtmf_dolphin_tone_get_max_pos(&max_rows, &max_cols, &max_span);
|
||||
|
||||
with_view_model(
|
||||
dtmf_dolphin_dialer->view,
|
||||
DTMFDolphinDialerModel * model,
|
||||
{
|
||||
uint8_t span = 0;
|
||||
uint8_t cursor = model->row;
|
||||
while(span == 0 && cursor < max_rows - 1) {
|
||||
cursor++;
|
||||
span = dtmf_dolphin_get_tone_span(cursor, model->col);
|
||||
}
|
||||
if(span != 0) {
|
||||
model->row = cursor;
|
||||
}
|
||||
},
|
||||
true);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool dtmf_dolphin_dialer_process_left(DTMFDolphinDialer* dtmf_dolphin_dialer) {
|
||||
with_view_model(
|
||||
dtmf_dolphin_dialer->view,
|
||||
DTMFDolphinDialerModel * model,
|
||||
{
|
||||
uint8_t span = 0;
|
||||
uint8_t cursor = model->col;
|
||||
while(span == 0 && cursor > 0) {
|
||||
cursor--;
|
||||
span = dtmf_dolphin_get_tone_span(model->row, cursor);
|
||||
}
|
||||
if(span != 0) {
|
||||
model->col = cursor;
|
||||
}
|
||||
},
|
||||
true);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool dtmf_dolphin_dialer_process_right(DTMFDolphinDialer* dtmf_dolphin_dialer) {
|
||||
uint8_t max_rows = 0;
|
||||
uint8_t max_cols = 0;
|
||||
uint8_t max_span = 0;
|
||||
dtmf_dolphin_tone_get_max_pos(&max_rows, &max_cols, &max_span);
|
||||
|
||||
with_view_model(
|
||||
dtmf_dolphin_dialer->view,
|
||||
DTMFDolphinDialerModel * model,
|
||||
{
|
||||
uint8_t span = 0;
|
||||
uint8_t cursor = model->col;
|
||||
while(span == 0 && cursor < max_cols - 1) {
|
||||
cursor++;
|
||||
span = dtmf_dolphin_get_tone_span(model->row, cursor);
|
||||
}
|
||||
if(span != 0) {
|
||||
model->col = cursor;
|
||||
}
|
||||
},
|
||||
true);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
dtmf_dolphin_dialer_process_ok(DTMFDolphinDialer* dtmf_dolphin_dialer, InputEvent* event) {
|
||||
bool consumed = false;
|
||||
|
||||
with_view_model(
|
||||
dtmf_dolphin_dialer->view,
|
||||
DTMFDolphinDialerModel * model,
|
||||
{
|
||||
if(event->type == InputTypePress) {
|
||||
model->playing = dtmf_dolphin_audio_play_tones(
|
||||
model->freq1, model->freq2, model->pulses, model->pulse_ms, model->gap_ms);
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
model->playing = !dtmf_dolphin_audio_stop_tones();
|
||||
}
|
||||
},
|
||||
true);
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
static void dtmf_dolphin_dialer_enter_callback(void* context) {
|
||||
furi_assert(context);
|
||||
DTMFDolphinDialer* dtmf_dolphin_dialer = context;
|
||||
|
||||
with_view_model(
|
||||
dtmf_dolphin_dialer->view,
|
||||
DTMFDolphinDialerModel * model,
|
||||
{
|
||||
model->col = 0;
|
||||
model->row = 0;
|
||||
model->section = 0;
|
||||
model->freq1 = 0.0;
|
||||
model->freq2 = 0.0;
|
||||
model->playing = false;
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
DTMFDolphinDialer* dtmf_dolphin_dialer_alloc() {
|
||||
DTMFDolphinDialer* dtmf_dolphin_dialer = malloc(sizeof(DTMFDolphinDialer));
|
||||
|
||||
dtmf_dolphin_dialer->view = view_alloc();
|
||||
view_allocate_model(
|
||||
dtmf_dolphin_dialer->view, ViewModelTypeLocking, sizeof(DTMFDolphinDialerModel));
|
||||
|
||||
with_view_model(
|
||||
dtmf_dolphin_dialer->view,
|
||||
DTMFDolphinDialerModel * model,
|
||||
{
|
||||
model->col = 0;
|
||||
model->row = 0;
|
||||
model->section = 0;
|
||||
model->freq1 = 0.0;
|
||||
model->freq2 = 0.0;
|
||||
model->playing = false;
|
||||
},
|
||||
true);
|
||||
|
||||
view_set_context(dtmf_dolphin_dialer->view, dtmf_dolphin_dialer);
|
||||
view_set_draw_callback(dtmf_dolphin_dialer->view, dtmf_dolphin_dialer_draw_callback);
|
||||
view_set_input_callback(dtmf_dolphin_dialer->view, dtmf_dolphin_dialer_input_callback);
|
||||
view_set_enter_callback(dtmf_dolphin_dialer->view, dtmf_dolphin_dialer_enter_callback);
|
||||
return dtmf_dolphin_dialer;
|
||||
}
|
||||
|
||||
void dtmf_dolphin_dialer_free(DTMFDolphinDialer* dtmf_dolphin_dialer) {
|
||||
furi_assert(dtmf_dolphin_dialer);
|
||||
view_free(dtmf_dolphin_dialer->view);
|
||||
free(dtmf_dolphin_dialer);
|
||||
}
|
||||
|
||||
View* dtmf_dolphin_dialer_get_view(DTMFDolphinDialer* dtmf_dolphin_dialer) {
|
||||
furi_assert(dtmf_dolphin_dialer);
|
||||
return dtmf_dolphin_dialer->view;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include "dtmf_dolphin_common.h"
|
||||
|
||||
typedef struct DTMFDolphinDialer DTMFDolphinDialer;
|
||||
typedef void (*DTMFDolphinDialerOkCallback)(InputType type, void* context);
|
||||
|
||||
DTMFDolphinDialer* dtmf_dolphin_dialer_alloc();
|
||||
|
||||
void dtmf_dolphin_dialer_free(DTMFDolphinDialer* dtmf_dolphin_dialer);
|
||||
|
||||
View* dtmf_dolphin_dialer_get_view(DTMFDolphinDialer* dtmf_dolphin_dialer);
|
||||
|
||||
void dtmf_dolphin_dialer_set_ok_callback(
|
||||
DTMFDolphinDialer* dtmf_dolphin_dialer,
|
||||
DTMFDolphinDialerOkCallback callback,
|
||||
void* context);
|
||||
@@ -12,10 +12,10 @@
|
||||
#define FLAPPY_BIRD_WIDTH 10
|
||||
|
||||
#define FLAPPY_PILAR_MAX 6
|
||||
#define FLAPPY_PILAR_DIST 40
|
||||
#define FLAPPY_PILAR_DIST 35
|
||||
|
||||
#define FLAPPY_GAB_HEIGHT 25
|
||||
#define FLAPPY_GAB_WIDTH 5
|
||||
#define FLAPPY_GAB_WIDTH 10
|
||||
|
||||
#define FLAPPY_GRAVITY_JUMP -1.1
|
||||
#define FLAPPY_GRAVITY_TICK 0.15
|
||||
@@ -100,10 +100,6 @@ static void flappy_game_state_init(GameState* const game_state) {
|
||||
flappy_game_random_pilar(game_state);
|
||||
}
|
||||
|
||||
// static void flappy_game_reset(GameState* const game_state) {
|
||||
// FURI_LOG_I(TAG, "Reset Game State\r\n"); // Resetting State
|
||||
// }
|
||||
|
||||
static void flappy_game_tick(GameState* const game_state) {
|
||||
if(game_state->state == GameStateLife) {
|
||||
if(!game_state->debug) {
|
||||
@@ -136,11 +132,12 @@ static void flappy_game_tick(GameState* const game_state) {
|
||||
}
|
||||
if(pilar->point.x < -FLAPPY_GAB_WIDTH) pilar->visible = 0;
|
||||
|
||||
// Checking out of bounds
|
||||
if(game_state->bird.point.y < 0 - FLAPPY_BIRD_WIDTH ||
|
||||
game_state->bird.point.y > FLIPPER_LCD_HEIGHT) {
|
||||
game_state->state = GameStateGameOver;
|
||||
break;
|
||||
if(game_state->bird.point.y <= 0 - FLAPPY_BIRD_WIDTH) {
|
||||
game_state->bird.point.y = 64;
|
||||
}
|
||||
|
||||
if(game_state->bird.point.y > 64 - FLAPPY_BIRD_WIDTH) {
|
||||
game_state->bird.point.y = FLIPPER_LCD_HEIGHT - FLAPPY_BIRD_WIDTH;
|
||||
}
|
||||
|
||||
// Bird inbetween pipes
|
||||
@@ -237,10 +234,6 @@ static void flappy_game_render_callback(Canvas* const canvas, void* ctx) {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 37, 31, "Game Over");
|
||||
|
||||
/*if(game_state->points != 0 && game_state->points % 5 == 0) {
|
||||
DOLPHIN_DEED(getRandomDeed());
|
||||
}*/
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
char buffer[12];
|
||||
snprintf(buffer, sizeof(buffer), "Score: %u", game_state->points);
|
||||
@@ -304,16 +297,16 @@ int32_t flappy_game_app(void* p) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
switch(event.input.key) {
|
||||
case InputKeyUp:
|
||||
game_state->bird.point.y--;
|
||||
if(game_state->state == GameStateLife) {
|
||||
flappy_game_flap(game_state);
|
||||
}
|
||||
|
||||
break;
|
||||
case InputKeyDown:
|
||||
game_state->bird.point.y++;
|
||||
break;
|
||||
case InputKeyRight:
|
||||
game_state->bird.point.x++;
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
game_state->bird.point.x--;
|
||||
break;
|
||||
case InputKeyOk:
|
||||
if(game_state->state == GameStateGameOver) {
|
||||
|
||||
@@ -532,15 +532,19 @@ void flipfrid_scene_run_attack_on_event(FlipFridEvent event, FlipFridState* cont
|
||||
}
|
||||
break;
|
||||
case InputKeyBack:
|
||||
context->is_attacking = false;
|
||||
context->attack_step = 0;
|
||||
counter = 0;
|
||||
|
||||
if(context->attack == FlipFridAttackLoadFileCustomUids) {
|
||||
furi_string_reset(context->data_str);
|
||||
stream_rewind(context->uids_stream);
|
||||
buffered_file_stream_close(context->uids_stream);
|
||||
}
|
||||
|
||||
context->attack_step = 0;
|
||||
context->is_attacking = false;
|
||||
furi_string_reset(context->notification_msg);
|
||||
context->current_scene = SceneEntryPoint;
|
||||
notification_message(context->notify, &sequence_blink_stop);
|
||||
context->current_scene = SceneEntryPoint;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
11
applications/plugins/heap_defence_game/application.fam
Normal file
@@ -0,0 +1,11 @@
|
||||
App(
|
||||
appid="heap_defence",
|
||||
name="Heap Defence",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="heap_defence_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
fap_category="Games",
|
||||
fap_icon="box.png",
|
||||
fap_icon_assets="assets_images",
|
||||
)
|
||||
|
After Width: | Height: | Size: 872 B |