V51 Release (#374)
2
.github/workflows/build.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
||||
run: bash .github/workflow_data/package.sh
|
||||
|
||||
- name: Send devbuild webhook
|
||||
if: "github.event_name == 'push' && github.ref_name == 'dev'"
|
||||
if: "github.event_name == 'push' && github.ref_name == 'dev' && !contains(github.event.head_commit.message, '--nobuild')"
|
||||
env:
|
||||
NC_HOST: "https://cloud.cynthialabs.net/"
|
||||
NC_USER: "${{ secrets.NC_USER }}"
|
||||
|
||||
4
.gitmodules
vendored
@@ -10,6 +10,7 @@
|
||||
[submodule "assets/protobuf"]
|
||||
path = assets/protobuf
|
||||
url = https://github.com/flipperdevices/flipperzero-protobuf.git
|
||||
shallow = false
|
||||
[submodule "lib/libusb_stm32"]
|
||||
path = lib/libusb_stm32
|
||||
url = https://github.com/flipperdevices/libusb_stm32.git
|
||||
@@ -40,3 +41,6 @@
|
||||
[submodule "lib/stm32wb_copro"]
|
||||
path = lib/stm32wb_copro
|
||||
url = https://github.com/flipperdevices/stm32wb_copro.git
|
||||
[submodule "applications/external/totp/lib/wolfssl"]
|
||||
path = applications/external/totp/lib/wolfssl
|
||||
url = https://github.com/wolfSSL/wolfssl.git
|
||||
|
||||
9
.vscode/.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
./c_cpp_properties.json
|
||||
./launch.json
|
||||
./settings.json
|
||||
./tasks.json
|
||||
/c_cpp_properties.json
|
||||
/extensions.json
|
||||
/launch.json
|
||||
/settings.json
|
||||
/tasks.json
|
||||
|
||||
19
.vscode/example/clangd/extensions.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
"recommendations": [
|
||||
"ms-python.black-formatter",
|
||||
"llvm-vs-code-extensions.vscode-clangd",
|
||||
"amiralizadeh9480.cpp-helper",
|
||||
"marus25.cortex-debug",
|
||||
"zxh404.vscode-proto3",
|
||||
"augustocdias.tasks-shell-input"
|
||||
],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": [
|
||||
"twxs.cmake",
|
||||
"ms-vscode.cpptools",
|
||||
"ms-vscode.cmake-tools"
|
||||
]
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": [
|
||||
"llvm-vs-code-extensions.vscode-clangd",
|
||||
"twxs.cmake",
|
||||
"ms-vscode.cmake-tools"
|
||||
]
|
||||
9
.vscode/example/settings.json
vendored
@@ -21,5 +21,10 @@
|
||||
"SConscript": "python",
|
||||
"SConstruct": "python",
|
||||
"*.fam": "python",
|
||||
}
|
||||
}
|
||||
},
|
||||
"clangd.arguments": [
|
||||
// We might be able to tighten this a bit more to only include the correct toolchain.
|
||||
"--query-driver=**",
|
||||
"--compile-commands-dir=${workspaceFolder}/build/latest"
|
||||
]
|
||||
}
|
||||
16
.vscode/example/tasks.json
vendored
@@ -28,29 +28,17 @@
|
||||
"command": "./fbt -c"
|
||||
},
|
||||
{
|
||||
"label": "[Release] Flash (ST-Link)",
|
||||
"label": "[Release] Flash (SWD)",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt COMPACT=1 DEBUG=0 FORCE=1 flash"
|
||||
},
|
||||
{
|
||||
"label": "[Debug] Flash (ST-Link)",
|
||||
"label": "[Debug] Flash (SWD)",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt FORCE=1 flash"
|
||||
},
|
||||
{
|
||||
"label": "[Release] Flash (blackmagic)",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt COMPACT=1 DEBUG=0 FORCE=1 flash_blackmagic"
|
||||
},
|
||||
{
|
||||
"label": "[Debug] Flash (blackmagic)",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt FORCE=1 flash_blackmagic"
|
||||
},
|
||||
{
|
||||
"label": "[Release] Flash (JLink)",
|
||||
"group": "build",
|
||||
|
||||
33
SConstruct
@@ -45,6 +45,7 @@ distenv = coreenv.Clone(
|
||||
],
|
||||
ENV=os.environ,
|
||||
UPDATE_BUNDLE_DIR="dist/${DIST_DIR}/f${TARGET_HW}-update-${DIST_SUFFIX}",
|
||||
VSCODE_LANG_SERVER=ARGUMENTS.get("LANG_SERVER", "cpptools"),
|
||||
)
|
||||
|
||||
firmware_env = distenv.AddFwProject(
|
||||
@@ -184,27 +185,15 @@ copro_dist = distenv.CoproBuilder(
|
||||
distenv.AlwaysBuild(copro_dist)
|
||||
distenv.Alias("copro_dist", copro_dist)
|
||||
|
||||
firmware_flash = distenv.AddOpenOCDFlashTarget(firmware_env)
|
||||
|
||||
firmware_flash = distenv.AddFwFlashTarget(firmware_env)
|
||||
distenv.Alias("flash", firmware_flash)
|
||||
|
||||
# To be implemented in fwflash.py
|
||||
firmware_jflash = distenv.AddJFlashTarget(firmware_env)
|
||||
distenv.Alias("jflash", firmware_jflash)
|
||||
|
||||
firmware_bm_flash = distenv.PhonyTarget(
|
||||
"flash_blackmagic",
|
||||
"$GDB $GDBOPTS $SOURCES $GDBFLASH",
|
||||
source=firmware_env["FW_ELF"],
|
||||
GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
|
||||
GDBREMOTE="${BLACKMAGIC_ADDR}",
|
||||
GDBFLASH=[
|
||||
"-ex",
|
||||
"load",
|
||||
"-ex",
|
||||
"quit",
|
||||
],
|
||||
)
|
||||
|
||||
gdb_backtrace_all_threads = distenv.PhonyTarget(
|
||||
distenv.PhonyTarget(
|
||||
"gdb_trace_all",
|
||||
"$GDB $GDBOPTS $SOURCES $GDBFLASH",
|
||||
source=firmware_env["FW_ELF"],
|
||||
@@ -327,6 +316,9 @@ distenv.PhonyTarget(
|
||||
"cli", "${PYTHON3} ${FBT_SCRIPT_DIR}/serial_cli.py -p ${FLIP_PORT}"
|
||||
)
|
||||
|
||||
# Update WiFi devboard firmware
|
||||
distenv.PhonyTarget("devboard_flash", "${PYTHON3} ${FBT_SCRIPT_DIR}/wifi_board.py")
|
||||
|
||||
|
||||
# Find blackmagic probe
|
||||
distenv.PhonyTarget(
|
||||
@@ -345,7 +337,14 @@ distenv.PhonyTarget(
|
||||
)
|
||||
|
||||
# Prepare vscode environment
|
||||
vscode_dist = distenv.Install("#.vscode", distenv.Glob("#.vscode/example/*"))
|
||||
VSCODE_LANG_SERVER = cmd_environment["LANG_SERVER"]
|
||||
vscode_dist = distenv.Install(
|
||||
"#.vscode",
|
||||
[
|
||||
distenv.Glob("#.vscode/example/*.json"),
|
||||
distenv.Glob(f"#.vscode/example/{VSCODE_LANG_SERVER}/*.json"),
|
||||
],
|
||||
)
|
||||
distenv.Precious(vscode_dist)
|
||||
distenv.NoClean(vscode_dist)
|
||||
distenv.Alias("vscode_dist", vscode_dist)
|
||||
|
||||
@@ -174,7 +174,7 @@ bool WIEGAND::DoWiegandConversion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Handle validation failure case!
|
||||
// TODO FL-3490: Handle validation failure case!
|
||||
} else if(4 == _bitCount) {
|
||||
// 4-bit Wiegand codes have no data integrity check so we just
|
||||
// read the LOW nibble.
|
||||
|
||||
@@ -56,7 +56,6 @@ static void subghz_test_packet_rx_callback(bool level, uint32_t duration, void*
|
||||
subghz_decoder_princeton_for_testing_parse(instance->decoder, level, duration);
|
||||
}
|
||||
|
||||
//todo
|
||||
static void subghz_test_packet_rx_pt_callback(SubGhzDecoderPrinceton* parser, void* context) {
|
||||
UNUSED(parser);
|
||||
furi_assert(context);
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
App(
|
||||
appid="uart_echo",
|
||||
name="[GPIO] UART Echo",
|
||||
name="UART Echo",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="uart_echo_app",
|
||||
cdefines=["APP_UART_ECHO"],
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
fap_icon="uart_10px.png",
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -3,6 +3,7 @@ App(
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="unit_tests_on_system_start",
|
||||
cdefines=["APP_UNIT_TESTS"],
|
||||
requires=["system_settings"],
|
||||
provides=["delay_test"],
|
||||
order=100,
|
||||
)
|
||||
|
||||
@@ -26,7 +26,7 @@ void test_furi_memmgr() {
|
||||
mu_assert_int_eq(66, ((uint8_t*)ptr)[i]);
|
||||
}
|
||||
|
||||
// TODO: fix realloc to copy only old size, and write testcase that leftover of reallocated memory is zero-initialized
|
||||
// TODO FL-3492: fix realloc to copy only old size, and write testcase that leftover of reallocated memory is zero-initialized
|
||||
free(ptr);
|
||||
|
||||
// allocate and zero-initialize array (calloc)
|
||||
|
||||
@@ -69,7 +69,7 @@ MU_TEST(mu_test_furi_string_mem) {
|
||||
mu_check(string != NULL);
|
||||
mu_check(!furi_string_empty(string));
|
||||
|
||||
// TODO: how to test furi_string_reserve?
|
||||
// TODO FL-3493: how to test furi_string_reserve?
|
||||
|
||||
// test furi_string_reset
|
||||
furi_string_reset(string);
|
||||
|
||||
602
applications/debug/unit_tests/furi_hal/furi_hal_crypto_tests.c
Normal file
@@ -0,0 +1,602 @@
|
||||
#include <stdio.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include "../minunit.h"
|
||||
|
||||
static const uint8_t key_ctr_1[32] = {
|
||||
0x77, 0x6B, 0xEF, 0xF2, 0x85, 0x1D, 0xB0, 0x6F, 0x4C, 0x8A, 0x05, 0x42, 0xC8, 0x69, 0x6F, 0x6C,
|
||||
0x6A, 0x81, 0xAF, 0x1E, 0xEC, 0x96, 0xB4, 0xD3, 0x7F, 0xC1, 0xD6, 0x89, 0xE6, 0xC1, 0xC1, 0x04,
|
||||
};
|
||||
static const uint8_t iv_ctr_1[16] = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x60,
|
||||
0xDB,
|
||||
0x56,
|
||||
0x72,
|
||||
0xC9,
|
||||
0x7A,
|
||||
0xA8,
|
||||
0xF0,
|
||||
0xB2,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
};
|
||||
static const uint8_t pt_ctr_1[16] = {
|
||||
0x53,
|
||||
0x69,
|
||||
0x6E,
|
||||
0x67,
|
||||
0x6C,
|
||||
0x65,
|
||||
0x20,
|
||||
0x62,
|
||||
0x6C,
|
||||
0x6F,
|
||||
0x63,
|
||||
0x6B,
|
||||
0x20,
|
||||
0x6D,
|
||||
0x73,
|
||||
0x67,
|
||||
};
|
||||
static const uint8_t tv_ctr_ct_1[16] = {
|
||||
0x14,
|
||||
0x5A,
|
||||
0xD0,
|
||||
0x1D,
|
||||
0xBF,
|
||||
0x82,
|
||||
0x4E,
|
||||
0xC7,
|
||||
0x56,
|
||||
0x08,
|
||||
0x63,
|
||||
0xDC,
|
||||
0x71,
|
||||
0xE3,
|
||||
0xE0,
|
||||
0xC0,
|
||||
};
|
||||
|
||||
static const uint8_t key_ctr_2[32] = {
|
||||
0x77, 0x6B, 0xEF, 0xF2, 0x85, 0x1D, 0xB0, 0x6F, 0x4C, 0x8A, 0x05, 0x42, 0xC8, 0x69, 0x6F, 0x6C,
|
||||
0x6A, 0x81, 0xAF, 0x1E, 0xEC, 0x96, 0xB4, 0xD3, 0x7F, 0xC1, 0xD6, 0x89, 0xE6, 0xC1, 0xC1, 0x04,
|
||||
};
|
||||
static const uint8_t iv_ctr_2[16] = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x60,
|
||||
0xDB,
|
||||
0x56,
|
||||
0x72,
|
||||
0xC9,
|
||||
0x7A,
|
||||
0xA8,
|
||||
0xF0,
|
||||
0xB2,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
};
|
||||
static const uint8_t pt_ctr_2[0] = {};
|
||||
//static const uint8_t tv_ctr_ct_2[0] = {};
|
||||
|
||||
static const uint8_t key_ctr_3[32] = {
|
||||
0xF6, 0xD6, 0x6D, 0x6B, 0xD5, 0x2D, 0x59, 0xBB, 0x07, 0x96, 0x36, 0x58, 0x79, 0xEF, 0xF8, 0x86,
|
||||
0xC6, 0x6D, 0xD5, 0x1A, 0x5B, 0x6A, 0x99, 0x74, 0x4B, 0x50, 0x59, 0x0C, 0x87, 0xA2, 0x38, 0x84,
|
||||
};
|
||||
static const uint8_t iv_ctr_3[16] = {
|
||||
0x00,
|
||||
0xFA,
|
||||
0xAC,
|
||||
0x24,
|
||||
0xC1,
|
||||
0x58,
|
||||
0x5E,
|
||||
0xF1,
|
||||
0x5A,
|
||||
0x43,
|
||||
0xD8,
|
||||
0x75,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
};
|
||||
static const uint8_t pt_ctr_3[32] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
||||
};
|
||||
static const uint8_t tv_ctr_ct_3[32] = {
|
||||
0xF0, 0x5E, 0x23, 0x1B, 0x38, 0x94, 0x61, 0x2C, 0x49, 0xEE, 0x00, 0x0B, 0x80, 0x4E, 0xB2, 0xA9,
|
||||
0xB8, 0x30, 0x6B, 0x50, 0x8F, 0x83, 0x9D, 0x6A, 0x55, 0x30, 0x83, 0x1D, 0x93, 0x44, 0xAF, 0x1C,
|
||||
};
|
||||
|
||||
static const uint8_t key_ctr_4[32] = {
|
||||
0xFF, 0x7A, 0x61, 0x7C, 0xE6, 0x91, 0x48, 0xE4, 0xF1, 0x72, 0x6E, 0x2F, 0x43, 0x58, 0x1D, 0xE2,
|
||||
0xAA, 0x62, 0xD9, 0xF8, 0x05, 0x53, 0x2E, 0xDF, 0xF1, 0xEE, 0xD6, 0x87, 0xFB, 0x54, 0x15, 0x3D,
|
||||
};
|
||||
static const uint8_t iv_ctr_4[16] = {
|
||||
0x00,
|
||||
0x1C,
|
||||
0xC5,
|
||||
0xB7,
|
||||
0x51,
|
||||
0xA5,
|
||||
0x1D,
|
||||
0x70,
|
||||
0xA1,
|
||||
0xC1,
|
||||
0x11,
|
||||
0x48,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
};
|
||||
static const uint8_t pt_ctr_4[36] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
|
||||
0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23,
|
||||
};
|
||||
static const uint8_t tv_ctr_ct_4[36] = {
|
||||
0xEB, 0x6C, 0x52, 0x82, 0x1D, 0x0B, 0xBB, 0xF7, 0xCE, 0x75, 0x94, 0x46,
|
||||
0x2A, 0xCA, 0x4F, 0xAA, 0xB4, 0x07, 0xDF, 0x86, 0x65, 0x69, 0xFD, 0x07,
|
||||
0xF4, 0x8C, 0xC0, 0xB5, 0x83, 0xD6, 0x07, 0x1F, 0x1E, 0xC0, 0xE6, 0xB8,
|
||||
};
|
||||
|
||||
static const uint8_t key_ctr_5[32] = {
|
||||
0xFF, 0x7A, 0x61, 0x7C, 0xE6, 0x91, 0x48, 0xE4, 0xF1, 0x72, 0x6E, 0x2F, 0x43, 0x58, 0x1D, 0xE2,
|
||||
0xAA, 0x62, 0xD9, 0xF8, 0x05, 0x53, 0x2E, 0xDF, 0xF1, 0xEE, 0xD6, 0x87, 0xFB, 0x54, 0x15, 0x3D,
|
||||
};
|
||||
static const uint8_t iv_ctr_5[16] = {
|
||||
0x00,
|
||||
0x1C,
|
||||
0xC5,
|
||||
0xB7,
|
||||
0x51,
|
||||
0xA5,
|
||||
0x1D,
|
||||
0x70,
|
||||
0xA1,
|
||||
0xC1,
|
||||
0x11,
|
||||
0x48,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
};
|
||||
static const uint8_t pt_ctr_5[0] = {};
|
||||
//static const uint8_t tv_ctr_ct_5[0] = {};
|
||||
|
||||
static const uint8_t key_gcm_1[32] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
static const uint8_t iv_gcm_1[16] = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
static const uint8_t pt_gcm_1[0] = {};
|
||||
//static const uint8_t tv_gcm_ct_1[0] = {};
|
||||
static const uint8_t aad_gcm_1[0] = {};
|
||||
static const uint8_t tv_gcm_tag_1[16] = {
|
||||
0x53,
|
||||
0x0F,
|
||||
0x8A,
|
||||
0xFB,
|
||||
0xC7,
|
||||
0x45,
|
||||
0x36,
|
||||
0xB9,
|
||||
0xA9,
|
||||
0x63,
|
||||
0xB4,
|
||||
0xF1,
|
||||
0xC4,
|
||||
0xCB,
|
||||
0x73,
|
||||
0x8B,
|
||||
};
|
||||
|
||||
static const uint8_t key_gcm_2[32] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
static const uint8_t iv_gcm_2[16] = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
static const uint8_t pt_gcm_2[16] = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
static const uint8_t tv_gcm_ct_2[16] = {
|
||||
0xCE,
|
||||
0xA7,
|
||||
0x40,
|
||||
0x3D,
|
||||
0x4D,
|
||||
0x60,
|
||||
0x6B,
|
||||
0x6E,
|
||||
0x07,
|
||||
0x4E,
|
||||
0xC5,
|
||||
0xD3,
|
||||
0xBA,
|
||||
0xF3,
|
||||
0x9D,
|
||||
0x18,
|
||||
};
|
||||
static const uint8_t aad_gcm_2[0] = {};
|
||||
static const uint8_t tv_gcm_tag_2[16] = {
|
||||
0xD0,
|
||||
0xD1,
|
||||
0xC8,
|
||||
0xA7,
|
||||
0x99,
|
||||
0x99,
|
||||
0x6B,
|
||||
0xF0,
|
||||
0x26,
|
||||
0x5B,
|
||||
0x98,
|
||||
0xB5,
|
||||
0xD4,
|
||||
0x8A,
|
||||
0xB9,
|
||||
0x19,
|
||||
};
|
||||
|
||||
static const uint8_t key_gcm_3[32] = {
|
||||
0xFE, 0xFF, 0xE9, 0x92, 0x86, 0x65, 0x73, 0x1C, 0x6D, 0x6A, 0x8F, 0x94, 0x67, 0x30, 0x83, 0x08,
|
||||
0xFE, 0xFF, 0xE9, 0x92, 0x86, 0x65, 0x73, 0x1C, 0x6D, 0x6A, 0x8F, 0x94, 0x67, 0x30, 0x83, 0x08,
|
||||
};
|
||||
static const uint8_t iv_gcm_3[16] = {
|
||||
0xCA,
|
||||
0xFE,
|
||||
0xBA,
|
||||
0xBE,
|
||||
0xFA,
|
||||
0xCE,
|
||||
0xDB,
|
||||
0xAD,
|
||||
0xDE,
|
||||
0xCA,
|
||||
0xF8,
|
||||
0x88,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
static const uint8_t pt_gcm_3[64] = {
|
||||
0xD9, 0x31, 0x32, 0x25, 0xF8, 0x84, 0x06, 0xE5, 0xA5, 0x59, 0x09, 0xC5, 0xAF, 0xF5, 0x26, 0x9A,
|
||||
0x86, 0xA7, 0xA9, 0x53, 0x15, 0x34, 0xF7, 0xDA, 0x2E, 0x4C, 0x30, 0x3D, 0x8A, 0x31, 0x8A, 0x72,
|
||||
0x1C, 0x3C, 0x0C, 0x95, 0x95, 0x68, 0x09, 0x53, 0x2F, 0xCF, 0x0E, 0x24, 0x49, 0xA6, 0xB5, 0x25,
|
||||
0xB1, 0x6A, 0xED, 0xF5, 0xAA, 0x0D, 0xE6, 0x57, 0xBA, 0x63, 0x7B, 0x39, 0x1A, 0xAF, 0xD2, 0x55,
|
||||
};
|
||||
static const uint8_t tv_gcm_ct_3[64] = {
|
||||
0x52, 0x2D, 0xC1, 0xF0, 0x99, 0x56, 0x7D, 0x07, 0xF4, 0x7F, 0x37, 0xA3, 0x2A, 0x84, 0x42, 0x7D,
|
||||
0x64, 0x3A, 0x8C, 0xDC, 0xBF, 0xE5, 0xC0, 0xC9, 0x75, 0x98, 0xA2, 0xBD, 0x25, 0x55, 0xD1, 0xAA,
|
||||
0x8C, 0xB0, 0x8E, 0x48, 0x59, 0x0D, 0xBB, 0x3D, 0xA7, 0xB0, 0x8B, 0x10, 0x56, 0x82, 0x88, 0x38,
|
||||
0xC5, 0xF6, 0x1E, 0x63, 0x93, 0xBA, 0x7A, 0x0A, 0xBC, 0xC9, 0xF6, 0x62, 0x89, 0x80, 0x15, 0xAD,
|
||||
};
|
||||
static const uint8_t aad_gcm_3[0] = {};
|
||||
static const uint8_t tv_gcm_tag_3[16] = {
|
||||
0xB0,
|
||||
0x94,
|
||||
0xDA,
|
||||
0xC5,
|
||||
0xD9,
|
||||
0x34,
|
||||
0x71,
|
||||
0xBD,
|
||||
0xEC,
|
||||
0x1A,
|
||||
0x50,
|
||||
0x22,
|
||||
0x70,
|
||||
0xE3,
|
||||
0xCC,
|
||||
0x6C,
|
||||
};
|
||||
|
||||
static const uint8_t key_gcm_4[32] = {
|
||||
0xFE, 0xFF, 0xE9, 0x92, 0x86, 0x65, 0x73, 0x1C, 0x6D, 0x6A, 0x8F, 0x94, 0x67, 0x30, 0x83, 0x08,
|
||||
0xFE, 0xFF, 0xE9, 0x92, 0x86, 0x65, 0x73, 0x1C, 0x6D, 0x6A, 0x8F, 0x94, 0x67, 0x30, 0x83, 0x08,
|
||||
};
|
||||
static const uint8_t iv_gcm_4[16] = {
|
||||
0xCA,
|
||||
0xFE,
|
||||
0xBA,
|
||||
0xBE,
|
||||
0xFA,
|
||||
0xCE,
|
||||
0xDB,
|
||||
0xAD,
|
||||
0xDE,
|
||||
0xCA,
|
||||
0xF8,
|
||||
0x88,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
static const uint8_t pt_gcm_4[60] = {
|
||||
0xD9, 0x31, 0x32, 0x25, 0xF8, 0x84, 0x06, 0xE5, 0xA5, 0x59, 0x09, 0xC5, 0xAF, 0xF5, 0x26,
|
||||
0x9A, 0x86, 0xA7, 0xA9, 0x53, 0x15, 0x34, 0xF7, 0xDA, 0x2E, 0x4C, 0x30, 0x3D, 0x8A, 0x31,
|
||||
0x8A, 0x72, 0x1C, 0x3C, 0x0C, 0x95, 0x95, 0x68, 0x09, 0x53, 0x2F, 0xCF, 0x0E, 0x24, 0x49,
|
||||
0xA6, 0xB5, 0x25, 0xB1, 0x6A, 0xED, 0xF5, 0xAA, 0x0D, 0xE6, 0x57, 0xBA, 0x63, 0x7B, 0x39,
|
||||
};
|
||||
static const uint8_t tv_gcm_ct_4[60] = {
|
||||
0x52, 0x2D, 0xC1, 0xF0, 0x99, 0x56, 0x7D, 0x07, 0xF4, 0x7F, 0x37, 0xA3, 0x2A, 0x84, 0x42,
|
||||
0x7D, 0x64, 0x3A, 0x8C, 0xDC, 0xBF, 0xE5, 0xC0, 0xC9, 0x75, 0x98, 0xA2, 0xBD, 0x25, 0x55,
|
||||
0xD1, 0xAA, 0x8C, 0xB0, 0x8E, 0x48, 0x59, 0x0D, 0xBB, 0x3D, 0xA7, 0xB0, 0x8B, 0x10, 0x56,
|
||||
0x82, 0x88, 0x38, 0xC5, 0xF6, 0x1E, 0x63, 0x93, 0xBA, 0x7A, 0x0A, 0xBC, 0xC9, 0xF6, 0x62,
|
||||
};
|
||||
static const uint8_t aad_gcm_4[20] = {
|
||||
0xFE, 0xED, 0xFA, 0xCE, 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED,
|
||||
0xFA, 0xCE, 0xDE, 0xAD, 0xBE, 0xEF, 0xAB, 0xAD, 0xDA, 0xD2,
|
||||
};
|
||||
static const uint8_t tv_gcm_tag_4[16] = {
|
||||
0x76,
|
||||
0xFC,
|
||||
0x6E,
|
||||
0xCE,
|
||||
0x0F,
|
||||
0x4E,
|
||||
0x17,
|
||||
0x68,
|
||||
0xCD,
|
||||
0xDF,
|
||||
0x88,
|
||||
0x53,
|
||||
0xBB,
|
||||
0x2D,
|
||||
0x55,
|
||||
0x1B,
|
||||
};
|
||||
|
||||
static void furi_hal_crypto_ctr_setup() {
|
||||
}
|
||||
|
||||
static void furi_hal_crypto_ctr_teardown() {
|
||||
}
|
||||
|
||||
static void furi_hal_crypto_gcm_setup() {
|
||||
}
|
||||
|
||||
static void furi_hal_crypto_gcm_teardown() {
|
||||
}
|
||||
|
||||
MU_TEST(furi_hal_crypto_ctr_1) {
|
||||
bool ret = false;
|
||||
uint8_t ct[sizeof(pt_ctr_1)];
|
||||
|
||||
ret = furi_hal_crypto_ctr(key_ctr_1, iv_ctr_1, pt_ctr_1, ct, sizeof(pt_ctr_1));
|
||||
mu_assert(ret, "CTR 1 failed");
|
||||
mu_assert_mem_eq(tv_ctr_ct_1, ct, sizeof(pt_ctr_1));
|
||||
}
|
||||
|
||||
MU_TEST(furi_hal_crypto_ctr_2) {
|
||||
bool ret = false;
|
||||
uint8_t ct[sizeof(pt_ctr_2)];
|
||||
|
||||
ret = furi_hal_crypto_ctr(key_ctr_2, iv_ctr_2, pt_ctr_2, ct, sizeof(pt_ctr_2));
|
||||
mu_assert(ret, "CTR 2 failed");
|
||||
//mu_assert_mem_eq(tv_ctr_ct_2, ct, sizeof(pt_ctr_2));
|
||||
}
|
||||
|
||||
MU_TEST(furi_hal_crypto_ctr_3) {
|
||||
bool ret = false;
|
||||
uint8_t ct[sizeof(pt_ctr_3)];
|
||||
|
||||
ret = furi_hal_crypto_ctr(key_ctr_3, iv_ctr_3, pt_ctr_3, ct, sizeof(pt_ctr_3));
|
||||
mu_assert(ret, "CTR 3 failed");
|
||||
mu_assert_mem_eq(tv_ctr_ct_3, ct, sizeof(pt_ctr_3));
|
||||
}
|
||||
|
||||
MU_TEST(furi_hal_crypto_ctr_4) {
|
||||
bool ret = false;
|
||||
uint8_t ct[sizeof(pt_ctr_4)];
|
||||
|
||||
ret = furi_hal_crypto_ctr(key_ctr_4, iv_ctr_4, pt_ctr_4, ct, sizeof(pt_ctr_4));
|
||||
mu_assert(ret, "CTR 4 failed");
|
||||
mu_assert_mem_eq(tv_ctr_ct_4, ct, sizeof(pt_ctr_4));
|
||||
}
|
||||
|
||||
MU_TEST(furi_hal_crypto_ctr_5) {
|
||||
bool ret = false;
|
||||
uint8_t ct[sizeof(pt_ctr_5)];
|
||||
|
||||
ret = furi_hal_crypto_ctr(key_ctr_5, iv_ctr_5, pt_ctr_5, ct, sizeof(pt_ctr_5));
|
||||
mu_assert(ret, "CTR 5 failed");
|
||||
//mu_assert_mem_eq(tv_ctr_ct_5, ct, sizeof(pt_ctr_5));
|
||||
}
|
||||
|
||||
MU_TEST(furi_hal_crypto_gcm_1) {
|
||||
bool ret = false;
|
||||
uint8_t pt[sizeof(pt_gcm_1)];
|
||||
uint8_t ct[sizeof(pt_gcm_1)];
|
||||
uint8_t tag_enc[16];
|
||||
uint8_t tag_dec[16];
|
||||
|
||||
ret = furi_hal_crypto_gcm(
|
||||
key_gcm_1,
|
||||
iv_gcm_1,
|
||||
aad_gcm_1,
|
||||
sizeof(aad_gcm_1),
|
||||
pt_gcm_1,
|
||||
ct,
|
||||
sizeof(pt_gcm_1),
|
||||
tag_enc,
|
||||
false);
|
||||
mu_assert(ret, "GCM 1 encryption failed");
|
||||
//mu_assert_mem_eq(tv_gcm_ct_1, ct, sizeof(pt_gcm_1));
|
||||
mu_assert_mem_eq(tv_gcm_tag_1, tag_enc, 16);
|
||||
|
||||
ret = furi_hal_crypto_gcm(
|
||||
key_gcm_1, iv_gcm_1, aad_gcm_1, sizeof(aad_gcm_1), ct, pt, sizeof(pt_gcm_1), tag_dec, true);
|
||||
mu_assert(ret, "GCM 1 decryption failed");
|
||||
//mu_assert_mem_eq(pt_gcm_1, pt, sizeof(pt_gcm_1));
|
||||
mu_assert_mem_eq(tv_gcm_tag_1, tag_dec, 16);
|
||||
}
|
||||
|
||||
MU_TEST(furi_hal_crypto_gcm_2) {
|
||||
bool ret = false;
|
||||
uint8_t pt[sizeof(pt_gcm_2)];
|
||||
uint8_t ct[sizeof(pt_gcm_2)];
|
||||
uint8_t tag_enc[16];
|
||||
uint8_t tag_dec[16];
|
||||
|
||||
ret = furi_hal_crypto_gcm(
|
||||
key_gcm_2,
|
||||
iv_gcm_2,
|
||||
aad_gcm_2,
|
||||
sizeof(aad_gcm_2),
|
||||
pt_gcm_2,
|
||||
ct,
|
||||
sizeof(pt_gcm_2),
|
||||
tag_enc,
|
||||
false);
|
||||
mu_assert(ret, "GCM 2 encryption failed");
|
||||
mu_assert_mem_eq(tv_gcm_ct_2, ct, sizeof(pt_gcm_2));
|
||||
mu_assert_mem_eq(tv_gcm_tag_2, tag_enc, 16);
|
||||
|
||||
ret = furi_hal_crypto_gcm(
|
||||
key_gcm_2, iv_gcm_2, aad_gcm_2, sizeof(aad_gcm_2), ct, pt, sizeof(pt_gcm_2), tag_dec, true);
|
||||
mu_assert(ret, "GCM 2 decryption failed");
|
||||
mu_assert_mem_eq(pt_gcm_2, pt, sizeof(pt_gcm_2));
|
||||
mu_assert_mem_eq(tv_gcm_tag_2, tag_dec, 16);
|
||||
}
|
||||
|
||||
MU_TEST(furi_hal_crypto_gcm_3) {
|
||||
bool ret = false;
|
||||
uint8_t pt[sizeof(pt_gcm_3)];
|
||||
uint8_t ct[sizeof(pt_gcm_3)];
|
||||
uint8_t tag_enc[16];
|
||||
uint8_t tag_dec[16];
|
||||
|
||||
ret = furi_hal_crypto_gcm(
|
||||
key_gcm_3,
|
||||
iv_gcm_3,
|
||||
aad_gcm_3,
|
||||
sizeof(aad_gcm_3),
|
||||
pt_gcm_3,
|
||||
ct,
|
||||
sizeof(pt_gcm_3),
|
||||
tag_enc,
|
||||
false);
|
||||
mu_assert(ret, "GCM 3 encryption failed");
|
||||
mu_assert_mem_eq(tv_gcm_ct_3, ct, sizeof(pt_gcm_3));
|
||||
mu_assert_mem_eq(tv_gcm_tag_3, tag_enc, 16);
|
||||
|
||||
ret = furi_hal_crypto_gcm(
|
||||
key_gcm_3, iv_gcm_3, aad_gcm_3, sizeof(aad_gcm_3), ct, pt, sizeof(pt_gcm_3), tag_dec, true);
|
||||
mu_assert(ret, "GCM 3 decryption failed");
|
||||
mu_assert_mem_eq(pt_gcm_3, pt, sizeof(pt_gcm_3));
|
||||
mu_assert_mem_eq(tv_gcm_tag_3, tag_dec, 16);
|
||||
}
|
||||
|
||||
MU_TEST(furi_hal_crypto_gcm_4) {
|
||||
bool ret = false;
|
||||
uint8_t pt[sizeof(pt_gcm_4)];
|
||||
uint8_t ct[sizeof(pt_gcm_4)];
|
||||
uint8_t tag_enc[16];
|
||||
uint8_t tag_dec[16];
|
||||
|
||||
ret = furi_hal_crypto_gcm(
|
||||
key_gcm_4,
|
||||
iv_gcm_4,
|
||||
aad_gcm_4,
|
||||
sizeof(aad_gcm_4),
|
||||
pt_gcm_4,
|
||||
ct,
|
||||
sizeof(pt_gcm_4),
|
||||
tag_enc,
|
||||
false);
|
||||
mu_assert(ret, "GCM 4 encryption failed");
|
||||
mu_assert_mem_eq(tv_gcm_ct_4, ct, sizeof(pt_gcm_4));
|
||||
mu_assert_mem_eq(tv_gcm_tag_4, tag_enc, 16);
|
||||
|
||||
ret = furi_hal_crypto_gcm(
|
||||
key_gcm_4, iv_gcm_4, aad_gcm_4, sizeof(aad_gcm_4), ct, pt, sizeof(pt_gcm_4), tag_dec, true);
|
||||
mu_assert(ret, "GCM 4 decryption failed");
|
||||
mu_assert_mem_eq(pt_gcm_4, pt, sizeof(pt_gcm_4));
|
||||
mu_assert_mem_eq(tv_gcm_tag_4, tag_dec, 16);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(furi_hal_crypto_ctr_test) {
|
||||
MU_SUITE_CONFIGURE(&furi_hal_crypto_ctr_setup, &furi_hal_crypto_ctr_teardown);
|
||||
MU_RUN_TEST(furi_hal_crypto_ctr_1);
|
||||
MU_RUN_TEST(furi_hal_crypto_ctr_2);
|
||||
MU_RUN_TEST(furi_hal_crypto_ctr_3);
|
||||
MU_RUN_TEST(furi_hal_crypto_ctr_4);
|
||||
MU_RUN_TEST(furi_hal_crypto_ctr_5);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(furi_hal_crypto_gcm_test) {
|
||||
MU_SUITE_CONFIGURE(&furi_hal_crypto_gcm_setup, &furi_hal_crypto_gcm_teardown);
|
||||
MU_RUN_TEST(furi_hal_crypto_gcm_1);
|
||||
MU_RUN_TEST(furi_hal_crypto_gcm_2);
|
||||
MU_RUN_TEST(furi_hal_crypto_gcm_3);
|
||||
MU_RUN_TEST(furi_hal_crypto_gcm_4);
|
||||
}
|
||||
|
||||
int run_minunit_test_furi_hal_crypto() {
|
||||
MU_RUN_SUITE(furi_hal_crypto_ctr_test);
|
||||
MU_RUN_SUITE(furi_hal_crypto_gcm_test);
|
||||
return MU_EXIT_CODE;
|
||||
}
|
||||
@@ -311,7 +311,7 @@ MU_TEST(test_bit_lib_test_parity) {
|
||||
}
|
||||
|
||||
MU_TEST(test_bit_lib_remove_bit_every_nth) {
|
||||
// TODO: more tests
|
||||
// TODO FL-3494: more tests
|
||||
uint8_t data_i[1] = {0b00001111};
|
||||
uint8_t data_o[1] = {0b00011111};
|
||||
size_t length;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include <pb.h>
|
||||
#include <pb_encode.h>
|
||||
#include <m-list.h>
|
||||
#include <lib/toolbox/md5.h>
|
||||
#include <lib/toolbox/md5_calc.h>
|
||||
#include <lib/toolbox/path.h>
|
||||
#include <cli/cli.h>
|
||||
#include <loader/loader.h>
|
||||
@@ -287,7 +287,8 @@ static void test_rpc_create_simple_message(
|
||||
PB_Main* message,
|
||||
uint16_t tag,
|
||||
const char* str,
|
||||
uint32_t command_id) {
|
||||
uint32_t command_id,
|
||||
bool flag) {
|
||||
furi_check(message);
|
||||
|
||||
char* str_copy = NULL;
|
||||
@@ -308,6 +309,7 @@ static void test_rpc_create_simple_message(
|
||||
break;
|
||||
case PB_Main_storage_list_request_tag:
|
||||
message->content.storage_list_request.path = str_copy;
|
||||
message->content.storage_list_request.include_md5 = flag;
|
||||
break;
|
||||
case PB_Main_storage_mkdir_request_tag:
|
||||
message->content.storage_mkdir_request.path = str_copy;
|
||||
@@ -419,6 +421,7 @@ static void
|
||||
}
|
||||
mu_check(result_msg_file->size == expected_msg_file->size);
|
||||
mu_check(result_msg_file->type == expected_msg_file->type);
|
||||
mu_assert_string_eq(expected_msg_file->md5sum, result_msg_file->md5sum);
|
||||
|
||||
if(result_msg_file->data && result_msg_file->type != PB_Storage_File_FileType_DIR) {
|
||||
mu_check(!result_msg_file->data == !expected_msg_file->data); // Zlo: WTF???
|
||||
@@ -430,10 +433,10 @@ static void
|
||||
}
|
||||
|
||||
static void test_rpc_compare_messages(PB_Main* result, PB_Main* expected) {
|
||||
mu_check(result->command_id == expected->command_id);
|
||||
mu_check(result->command_status == expected->command_status);
|
||||
mu_check(result->has_next == expected->has_next);
|
||||
mu_check(result->which_content == expected->which_content);
|
||||
mu_assert_int_eq(expected->command_id, result->command_id);
|
||||
mu_assert_int_eq(expected->command_status, result->command_status);
|
||||
mu_assert_int_eq(expected->has_next, result->has_next);
|
||||
mu_assert_int_eq(expected->which_content, result->which_content);
|
||||
if(result->command_status != PB_CommandStatus_OK) {
|
||||
mu_check(result->which_content == PB_Main_empty_tag);
|
||||
}
|
||||
@@ -573,10 +576,15 @@ static void
|
||||
static void test_rpc_storage_list_create_expected_list(
|
||||
MsgList_t msg_list,
|
||||
const char* path,
|
||||
uint32_t command_id) {
|
||||
uint32_t command_id,
|
||||
bool append_md5) {
|
||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
||||
File* dir = storage_file_alloc(fs_api);
|
||||
|
||||
FuriString* md5 = furi_string_alloc();
|
||||
FuriString* md5_path = furi_string_alloc();
|
||||
File* file = storage_file_alloc(fs_api);
|
||||
|
||||
PB_Main response = {
|
||||
.command_id = command_id,
|
||||
.has_next = false,
|
||||
@@ -614,6 +622,17 @@ static void test_rpc_storage_list_create_expected_list(
|
||||
list->file[i].data = NULL;
|
||||
/* memory free inside rpc_encode_and_send() -> pb_release() */
|
||||
list->file[i].name = name;
|
||||
|
||||
if(append_md5 && !file_info_is_dir(&fileinfo)) {
|
||||
furi_string_printf(md5_path, "%s/%s", path, name);
|
||||
|
||||
if(md5_string_calc_file(file, furi_string_get_cstr(md5_path), md5, NULL)) {
|
||||
char* md5sum = list->file[i].md5sum;
|
||||
size_t md5sum_size = sizeof(list->file[i].md5sum);
|
||||
snprintf(md5sum, md5sum_size, "%s", furi_string_get_cstr(md5));
|
||||
}
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
} else {
|
||||
@@ -626,6 +645,10 @@ static void test_rpc_storage_list_create_expected_list(
|
||||
response.has_next = false;
|
||||
MsgList_push_back(msg_list, response);
|
||||
|
||||
furi_string_free(md5);
|
||||
furi_string_free(md5_path);
|
||||
storage_file_free(file);
|
||||
|
||||
storage_dir_close(dir);
|
||||
storage_file_free(dir);
|
||||
|
||||
@@ -675,16 +698,17 @@ static void test_rpc_free_msg_list(MsgList_t msg_list) {
|
||||
MsgList_clear(msg_list);
|
||||
}
|
||||
|
||||
static void test_rpc_storage_list_run(const char* path, uint32_t command_id) {
|
||||
static void test_rpc_storage_list_run(const char* path, uint32_t command_id, bool md5) {
|
||||
PB_Main request;
|
||||
MsgList_t expected_msg_list;
|
||||
MsgList_init(expected_msg_list);
|
||||
|
||||
test_rpc_create_simple_message(&request, PB_Main_storage_list_request_tag, path, command_id);
|
||||
test_rpc_create_simple_message(
|
||||
&request, PB_Main_storage_list_request_tag, path, command_id, md5);
|
||||
if(!strcmp(path, "/")) {
|
||||
test_rpc_storage_list_create_expected_list_root(expected_msg_list, command_id);
|
||||
} else {
|
||||
test_rpc_storage_list_create_expected_list(expected_msg_list, path, command_id);
|
||||
test_rpc_storage_list_create_expected_list(expected_msg_list, path, command_id, md5);
|
||||
}
|
||||
test_rpc_encode_and_feed_one(&request, 0);
|
||||
test_rpc_decode_and_compare(expected_msg_list, 0);
|
||||
@@ -694,15 +718,25 @@ static void test_rpc_storage_list_run(const char* path, uint32_t command_id) {
|
||||
}
|
||||
|
||||
MU_TEST(test_storage_list) {
|
||||
test_rpc_storage_list_run("/", ++command_id);
|
||||
test_rpc_storage_list_run(EXT_PATH("nfc"), ++command_id);
|
||||
test_rpc_storage_list_run("/", ++command_id, false);
|
||||
test_rpc_storage_list_run(EXT_PATH("nfc"), ++command_id, false);
|
||||
test_rpc_storage_list_run(STORAGE_INT_PATH_PREFIX, ++command_id, false);
|
||||
test_rpc_storage_list_run(STORAGE_EXT_PATH_PREFIX, ++command_id, false);
|
||||
test_rpc_storage_list_run(EXT_PATH("infrared"), ++command_id, false);
|
||||
test_rpc_storage_list_run(EXT_PATH("ibutton"), ++command_id, false);
|
||||
test_rpc_storage_list_run(EXT_PATH("lfrfid"), ++command_id, false);
|
||||
test_rpc_storage_list_run("error_path", ++command_id, false);
|
||||
}
|
||||
|
||||
test_rpc_storage_list_run(STORAGE_INT_PATH_PREFIX, ++command_id);
|
||||
test_rpc_storage_list_run(STORAGE_EXT_PATH_PREFIX, ++command_id);
|
||||
test_rpc_storage_list_run(EXT_PATH("infrared"), ++command_id);
|
||||
test_rpc_storage_list_run(EXT_PATH("ibutton"), ++command_id);
|
||||
test_rpc_storage_list_run(EXT_PATH("lfrfid"), ++command_id);
|
||||
test_rpc_storage_list_run("error_path", ++command_id);
|
||||
MU_TEST(test_storage_list_md5) {
|
||||
test_rpc_storage_list_run("/", ++command_id, true);
|
||||
test_rpc_storage_list_run(EXT_PATH("nfc"), ++command_id, true);
|
||||
test_rpc_storage_list_run(STORAGE_INT_PATH_PREFIX, ++command_id, true);
|
||||
test_rpc_storage_list_run(STORAGE_EXT_PATH_PREFIX, ++command_id, true);
|
||||
test_rpc_storage_list_run(EXT_PATH("infrared"), ++command_id, true);
|
||||
test_rpc_storage_list_run(EXT_PATH("ibutton"), ++command_id, true);
|
||||
test_rpc_storage_list_run(EXT_PATH("lfrfid"), ++command_id, true);
|
||||
test_rpc_storage_list_run("error_path", ++command_id, true);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -770,7 +804,8 @@ static void test_storage_read_run(const char* path, uint32_t command_id) {
|
||||
MsgList_init(expected_msg_list);
|
||||
|
||||
test_rpc_add_read_to_list_by_reading_real_file(expected_msg_list, path, command_id);
|
||||
test_rpc_create_simple_message(&request, PB_Main_storage_read_request_tag, path, command_id);
|
||||
test_rpc_create_simple_message(
|
||||
&request, PB_Main_storage_read_request_tag, path, command_id, false);
|
||||
test_rpc_encode_and_feed_one(&request, 0);
|
||||
test_rpc_decode_and_compare(expected_msg_list, 0);
|
||||
|
||||
@@ -824,7 +859,8 @@ static void test_rpc_storage_info_run(const char* path, uint32_t command_id) {
|
||||
MsgList_t expected_msg_list;
|
||||
MsgList_init(expected_msg_list);
|
||||
|
||||
test_rpc_create_simple_message(&request, PB_Main_storage_info_request_tag, path, command_id);
|
||||
test_rpc_create_simple_message(
|
||||
&request, PB_Main_storage_info_request_tag, path, command_id, false);
|
||||
|
||||
PB_Main* response = MsgList_push_new(expected_msg_list);
|
||||
response->command_id = command_id;
|
||||
@@ -856,7 +892,8 @@ static void test_rpc_storage_stat_run(const char* path, uint32_t command_id) {
|
||||
MsgList_t expected_msg_list;
|
||||
MsgList_init(expected_msg_list);
|
||||
|
||||
test_rpc_create_simple_message(&request, PB_Main_storage_stat_request_tag, path, command_id);
|
||||
test_rpc_create_simple_message(
|
||||
&request, PB_Main_storage_stat_request_tag, path, command_id, false);
|
||||
|
||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
||||
FileInfo fileinfo;
|
||||
@@ -968,7 +1005,11 @@ static void test_storage_write_read_run(
|
||||
test_rpc_add_empty_to_list(expected_msg_list, PB_CommandStatus_OK, *command_id);
|
||||
|
||||
test_rpc_create_simple_message(
|
||||
MsgList_push_raw(input_msg_list), PB_Main_storage_read_request_tag, path, ++*command_id);
|
||||
MsgList_push_raw(input_msg_list),
|
||||
PB_Main_storage_read_request_tag,
|
||||
path,
|
||||
++*command_id,
|
||||
false);
|
||||
test_rpc_add_read_or_write_to_list(
|
||||
expected_msg_list,
|
||||
READ_RESPONSE,
|
||||
@@ -1041,7 +1082,8 @@ MU_TEST(test_storage_interrupt_continuous_same_system) {
|
||||
MsgList_push_new(input_msg_list),
|
||||
PB_Main_storage_mkdir_request_tag,
|
||||
TEST_DIR "dir1",
|
||||
command_id + 1);
|
||||
command_id + 1,
|
||||
false);
|
||||
test_rpc_add_read_or_write_to_list(
|
||||
input_msg_list,
|
||||
WRITE_REQUEST,
|
||||
@@ -1121,7 +1163,8 @@ static void test_storage_delete_run(
|
||||
MsgList_t expected_msg_list;
|
||||
MsgList_init(expected_msg_list);
|
||||
|
||||
test_rpc_create_simple_message(&request, PB_Main_storage_delete_request_tag, path, command_id);
|
||||
test_rpc_create_simple_message(
|
||||
&request, PB_Main_storage_delete_request_tag, path, command_id, false);
|
||||
request.content.storage_delete_request.recursive = recursive;
|
||||
test_rpc_add_empty_to_list(expected_msg_list, status, command_id);
|
||||
|
||||
@@ -1202,7 +1245,8 @@ static void test_storage_mkdir_run(const char* path, size_t command_id, PB_Comma
|
||||
MsgList_t expected_msg_list;
|
||||
MsgList_init(expected_msg_list);
|
||||
|
||||
test_rpc_create_simple_message(&request, PB_Main_storage_mkdir_request_tag, path, command_id);
|
||||
test_rpc_create_simple_message(
|
||||
&request, PB_Main_storage_mkdir_request_tag, path, command_id, false);
|
||||
test_rpc_add_empty_to_list(expected_msg_list, status, command_id);
|
||||
|
||||
test_rpc_encode_and_feed_one(&request, 0);
|
||||
@@ -1229,33 +1273,15 @@ MU_TEST(test_storage_mkdir) {
|
||||
static void test_storage_calculate_md5sum(const char* path, char* md5sum, size_t md5sum_size) {
|
||||
Storage* api = furi_record_open(RECORD_STORAGE);
|
||||
File* file = storage_file_alloc(api);
|
||||
FuriString* md5 = furi_string_alloc();
|
||||
|
||||
if(storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
const uint16_t once_read_size = 512;
|
||||
const uint8_t hash_size = MD5SUM_SIZE;
|
||||
uint8_t* data = malloc(once_read_size);
|
||||
uint8_t* hash = malloc(sizeof(uint8_t) * hash_size);
|
||||
md5_context* md5_ctx = malloc(sizeof(md5_context));
|
||||
|
||||
md5_starts(md5_ctx);
|
||||
while(true) {
|
||||
uint16_t read_size = storage_file_read(file, data, once_read_size);
|
||||
if(read_size == 0) break;
|
||||
md5_update(md5_ctx, data, read_size);
|
||||
}
|
||||
md5_finish(md5_ctx, hash);
|
||||
free(md5_ctx);
|
||||
|
||||
for(uint8_t i = 0; i < hash_size; i++) {
|
||||
md5sum += snprintf(md5sum, md5sum_size, "%02x", hash[i]);
|
||||
}
|
||||
|
||||
free(hash);
|
||||
free(data);
|
||||
if(md5_string_calc_file(file, path, md5, NULL)) {
|
||||
snprintf(md5sum, md5sum_size, "%s", furi_string_get_cstr(md5));
|
||||
} else {
|
||||
furi_check(0);
|
||||
}
|
||||
|
||||
furi_string_free(md5);
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
|
||||
@@ -1271,11 +1297,12 @@ static void test_storage_md5sum_run(
|
||||
MsgList_t expected_msg_list;
|
||||
MsgList_init(expected_msg_list);
|
||||
|
||||
test_rpc_create_simple_message(&request, PB_Main_storage_md5sum_request_tag, path, command_id);
|
||||
test_rpc_create_simple_message(
|
||||
&request, PB_Main_storage_md5sum_request_tag, path, command_id, false);
|
||||
if(status == PB_CommandStatus_OK) {
|
||||
PB_Main* response = MsgList_push_new(expected_msg_list);
|
||||
test_rpc_create_simple_message(
|
||||
response, PB_Main_storage_md5sum_response_tag, md5sum, command_id);
|
||||
response, PB_Main_storage_md5sum_response_tag, md5sum, command_id, false);
|
||||
response->command_status = status;
|
||||
} else {
|
||||
test_rpc_add_empty_to_list(expected_msg_list, status, command_id);
|
||||
@@ -1433,6 +1460,7 @@ MU_TEST_SUITE(test_rpc_storage) {
|
||||
MU_RUN_TEST(test_storage_info);
|
||||
MU_RUN_TEST(test_storage_stat);
|
||||
MU_RUN_TEST(test_storage_list);
|
||||
MU_RUN_TEST(test_storage_list_md5);
|
||||
MU_RUN_TEST(test_storage_read);
|
||||
MU_RUN_TEST(test_storage_write_read);
|
||||
MU_RUN_TEST(test_storage_write);
|
||||
@@ -1731,7 +1759,8 @@ MU_TEST(test_rpc_multisession_storage) {
|
||||
MsgList_push_raw(input_0),
|
||||
PB_Main_storage_read_request_tag,
|
||||
TEST_DIR "file0.txt",
|
||||
++command_id);
|
||||
++command_id,
|
||||
false);
|
||||
test_rpc_add_read_or_write_to_list(
|
||||
expected_0, READ_RESPONSE, TEST_DIR "file0.txt", pattern, sizeof(pattern), 1, command_id);
|
||||
|
||||
@@ -1739,7 +1768,8 @@ MU_TEST(test_rpc_multisession_storage) {
|
||||
MsgList_push_raw(input_1),
|
||||
PB_Main_storage_read_request_tag,
|
||||
TEST_DIR "file1.txt",
|
||||
++command_id);
|
||||
++command_id,
|
||||
false);
|
||||
test_rpc_add_read_or_write_to_list(
|
||||
expected_1, READ_RESPONSE, TEST_DIR "file1.txt", pattern, sizeof(pattern), 1, command_id);
|
||||
|
||||
|
||||
@@ -582,6 +582,49 @@ MU_TEST(test_storage_common_migrate) {
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
#define MD5_HASH_SIZE (16)
|
||||
#include <lib/toolbox/md5_calc.h>
|
||||
|
||||
MU_TEST(test_md5_calc) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
File* file = storage_file_alloc(storage);
|
||||
|
||||
const char* path = UNIT_TESTS_PATH("storage/md5.txt");
|
||||
const char* md5_cstr = "2a456fa43e75088fdde41c93159d62a2";
|
||||
const uint8_t md5[MD5_HASH_SIZE] = {
|
||||
0x2a,
|
||||
0x45,
|
||||
0x6f,
|
||||
0xa4,
|
||||
0x3e,
|
||||
0x75,
|
||||
0x08,
|
||||
0x8f,
|
||||
0xdd,
|
||||
0xe4,
|
||||
0x1c,
|
||||
0x93,
|
||||
0x15,
|
||||
0x9d,
|
||||
0x62,
|
||||
0xa2,
|
||||
};
|
||||
|
||||
uint8_t md5_output[MD5_HASH_SIZE];
|
||||
FuriString* md5_output_str = furi_string_alloc();
|
||||
memset(md5_output, 0, MD5_HASH_SIZE);
|
||||
|
||||
mu_check(md5_calc_file(file, path, md5_output, NULL));
|
||||
mu_check(md5_string_calc_file(file, path, md5_output_str, NULL));
|
||||
|
||||
mu_assert_mem_eq(md5, md5_output, MD5_HASH_SIZE);
|
||||
mu_assert_string_eq(md5_cstr, furi_string_get_cstr(md5_output_str));
|
||||
|
||||
storage_file_free(file);
|
||||
furi_string_free(md5_output_str);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_data_path) {
|
||||
MU_RUN_TEST(test_storage_data_path);
|
||||
MU_RUN_TEST(test_storage_data_path_apps);
|
||||
@@ -591,11 +634,16 @@ MU_TEST_SUITE(test_storage_common) {
|
||||
MU_RUN_TEST(test_storage_common_migrate);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_md5_calc_suite) {
|
||||
MU_RUN_TEST(test_md5_calc);
|
||||
}
|
||||
|
||||
int run_minunit_test_storage() {
|
||||
MU_RUN_SUITE(storage_file);
|
||||
MU_RUN_SUITE(storage_dir);
|
||||
MU_RUN_SUITE(storage_rename);
|
||||
MU_RUN_SUITE(test_data_path);
|
||||
MU_RUN_SUITE(test_storage_common);
|
||||
MU_RUN_SUITE(test_md5_calc_suite);
|
||||
return MU_EXIT_CODE;
|
||||
}
|
||||
|
||||
@@ -330,7 +330,12 @@ bool subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestType type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FuriHalCortexTimer timer = furi_hal_cortex_timer_get(30000000);
|
||||
|
||||
while(!furi_hal_subghz_is_async_tx_complete()) {
|
||||
if(furi_hal_cortex_timer_is_expired(timer)) {
|
||||
return false;
|
||||
}
|
||||
furi_delay_ms(10);
|
||||
}
|
||||
furi_hal_subghz_stop_async_tx();
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
int run_minunit_test_furi();
|
||||
int run_minunit_test_furi_hal();
|
||||
int run_minunit_test_furi_hal_crypto();
|
||||
int run_minunit_test_furi_string();
|
||||
int run_minunit_test_infrared();
|
||||
int run_minunit_test_rpc();
|
||||
@@ -39,6 +40,7 @@ typedef struct {
|
||||
const UnitTest unit_tests[] = {
|
||||
{.name = "furi", .entry = run_minunit_test_furi},
|
||||
{.name = "furi_hal", .entry = run_minunit_test_furi_hal},
|
||||
{.name = "furi_hal_crypto", .entry = run_minunit_test_furi_hal_crypto},
|
||||
{.name = "furi_string", .entry = run_minunit_test_furi_string},
|
||||
{.name = "storage", .entry = run_minunit_test_storage},
|
||||
{.name = "stream", .entry = run_minunit_test_stream},
|
||||
@@ -88,7 +90,7 @@ void unit_tests_cli(Cli* cli, FuriString* args, void* context) {
|
||||
Loader* loader = furi_record_open(RECORD_LOADER);
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
// TODO: lock device while test running
|
||||
// TODO FL-3491: lock device while test running
|
||||
if(loader_is_locked(loader)) {
|
||||
printf("RPC: stop all applications to run tests\r\n");
|
||||
notification_message(notification, &sequence_blink_magenta_100);
|
||||
|
||||
@@ -7,6 +7,5 @@ App(
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=60,
|
||||
fap_icon="mouse_10px.png",
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -7,6 +7,7 @@
|
||||
#include <furi_hal_interrupt.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <furi_hal_bus.h>
|
||||
#include <furi_hal_subghz.h>
|
||||
|
||||
#include <stm32wbxx_ll_dma.h>
|
||||
#include <furi_hal_cortex.h>
|
||||
@@ -18,7 +19,10 @@
|
||||
#define TAG "SubGhz_Device_CC1101_Ext"
|
||||
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO &gpio_ext_pb2
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_EXTENDED_RANGE false
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_E07M20S_AMP_GPIO &gpio_ext_pc3
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_FORCE_EXTENDED_RANGE false
|
||||
|
||||
#define SUBGHZ_DEVICE_CC1101_CONFIG_VER 1
|
||||
|
||||
/* DMA Channels definition */
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_DMA DMA2
|
||||
@@ -78,6 +82,8 @@ typedef struct {
|
||||
const GpioPin* g0_pin;
|
||||
SubGhzDeviceCC1101ExtAsyncTx async_tx;
|
||||
SubGhzDeviceCC1101ExtAsyncRx async_rx;
|
||||
bool power_amp;
|
||||
bool extended_range;
|
||||
} SubGhzDeviceCC1101Ext;
|
||||
|
||||
static SubGhzDeviceCC1101Ext* subghz_device_cc1101_ext = NULL;
|
||||
@@ -187,25 +193,64 @@ static bool subghz_device_cc1101_ext_check_init() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool subghz_device_cc1101_ext_alloc() {
|
||||
bool subghz_device_cc1101_ext_alloc(SubGhzDeviceConf* conf) {
|
||||
furi_assert(subghz_device_cc1101_ext == NULL);
|
||||
subghz_device_cc1101_ext = malloc(sizeof(SubGhzDeviceCC1101Ext));
|
||||
subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateInit;
|
||||
subghz_device_cc1101_ext->regulation = SubGhzDeviceCC1101ExtRegulationTxRx;
|
||||
subghz_device_cc1101_ext->async_mirror_pin = NULL;
|
||||
subghz_device_cc1101_ext->spi_bus_handle = &furi_hal_spi_bus_handle_external;
|
||||
subghz_device_cc1101_ext->g0_pin = SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO;
|
||||
subghz_device_cc1101_ext->power_amp = false;
|
||||
subghz_device_cc1101_ext->extended_range = false;
|
||||
if(conf) {
|
||||
if(conf->ver == SUBGHZ_DEVICE_CC1101_CONFIG_VER) {
|
||||
subghz_device_cc1101_ext->power_amp = conf->power_amp;
|
||||
subghz_device_cc1101_ext->extended_range = conf->extended_range;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Config version mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
subghz_device_cc1101_ext->async_rx.capture_delta_duration = 0;
|
||||
|
||||
subghz_device_cc1101_ext->spi_bus_handle =
|
||||
(XTREME_SETTINGS()->spi_cc1101_handle == SpiDefault ?
|
||||
&furi_hal_spi_bus_handle_external :
|
||||
&furi_hal_spi_bus_handle_external_extra);
|
||||
|
||||
// this is needed if multiple SPI devices are connected to the same bus but with different CS pins
|
||||
if(XTREME_SETTINGS()->spi_cc1101_handle == SpiDefault &&
|
||||
!furi_hal_subghz_get_ext_power_amp()) {
|
||||
furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_write(&gpio_ext_pc3, true);
|
||||
} else if(XTREME_SETTINGS()->spi_cc1101_handle == SpiExtra) {
|
||||
furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_write(&gpio_ext_pa4, true);
|
||||
}
|
||||
|
||||
furi_hal_spi_bus_handle_init(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
if(subghz_device_cc1101_ext->power_amp) {
|
||||
furi_hal_gpio_init_simple(
|
||||
SUBGHZ_DEVICE_CC1101_EXT_E07M20S_AMP_GPIO, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_write(SUBGHZ_DEVICE_CC1101_EXT_E07M20S_AMP_GPIO, 0);
|
||||
}
|
||||
|
||||
return subghz_device_cc1101_ext_check_init();
|
||||
}
|
||||
|
||||
void subghz_device_cc1101_ext_free() {
|
||||
furi_assert(subghz_device_cc1101_ext != NULL);
|
||||
|
||||
furi_hal_spi_bus_handle_deinit(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
free(subghz_device_cc1101_ext);
|
||||
|
||||
// resetting the CS pins to floating
|
||||
if(XTREME_SETTINGS()->spi_nrf24_handle == SpiDefault || subghz_device_cc1101_ext->power_amp) {
|
||||
furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeAnalog);
|
||||
} else if(XTREME_SETTINGS()->spi_nrf24_handle == SpiExtra) {
|
||||
furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeAnalog);
|
||||
}
|
||||
|
||||
subghz_device_cc1101_ext = NULL;
|
||||
}
|
||||
|
||||
@@ -221,7 +266,7 @@ bool subghz_device_cc1101_ext_is_connect() {
|
||||
bool ret = false;
|
||||
|
||||
if(subghz_device_cc1101_ext == NULL) { // not initialized
|
||||
ret = subghz_device_cc1101_ext_alloc();
|
||||
ret = subghz_device_cc1101_ext_alloc(NULL);
|
||||
subghz_device_cc1101_ext_free();
|
||||
} else { // initialized
|
||||
furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
@@ -333,7 +378,7 @@ bool subghz_device_cc1101_ext_rx_pipe_not_empty() {
|
||||
(CC1101_STATUS_RXBYTES) | CC1101_BURST,
|
||||
(uint8_t*)status);
|
||||
furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
// TODO: you can add a buffer overflow flag if needed
|
||||
// TODO: Find reason why RXFIFO_OVERFLOW doesnt work correctly
|
||||
if(status->NUM_RXBYTES > 0) {
|
||||
return true;
|
||||
} else {
|
||||
@@ -381,12 +426,18 @@ void subghz_device_cc1101_ext_idle() {
|
||||
furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
cc1101_switch_to_idle(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
if(subghz_device_cc1101_ext->power_amp) {
|
||||
furi_hal_gpio_write(SUBGHZ_DEVICE_CC1101_EXT_E07M20S_AMP_GPIO, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void subghz_device_cc1101_ext_rx() {
|
||||
furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
cc1101_switch_to_rx(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
if(subghz_device_cc1101_ext->power_amp) {
|
||||
furi_hal_gpio_write(SUBGHZ_DEVICE_CC1101_EXT_E07M20S_AMP_GPIO, 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool subghz_device_cc1101_ext_tx() {
|
||||
@@ -394,6 +445,9 @@ bool subghz_device_cc1101_ext_tx() {
|
||||
furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
cc1101_switch_to_tx(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle);
|
||||
if(subghz_device_cc1101_ext->power_amp) {
|
||||
furi_hal_gpio_write(SUBGHZ_DEVICE_CC1101_EXT_E07M20S_AMP_GPIO, 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -432,14 +486,16 @@ bool subghz_device_cc1101_ext_is_frequency_valid(uint32_t value) {
|
||||
}
|
||||
|
||||
bool subghz_device_cc1101_ext_is_tx_allowed(uint32_t value) {
|
||||
if(!(SUBGHZ_DEVICE_CC1101_EXT_EXTENDED_RANGE) &&
|
||||
if(!(SUBGHZ_DEVICE_CC1101_EXT_FORCE_EXTENDED_RANGE ||
|
||||
subghz_device_cc1101_ext->extended_range) &&
|
||||
!(value >= 299999755 && value <= 350000335) && // was increased from 348 to 350
|
||||
!(value >= 386999938 && value <= 467750000) && // was increased from 464 to 467.75
|
||||
!(value >= 778999847 && value <= 928000000)) {
|
||||
FURI_LOG_I(TAG, "Frequency blocked - outside default range");
|
||||
return false;
|
||||
} else if(
|
||||
(SUBGHZ_DEVICE_CC1101_EXT_EXTENDED_RANGE) &&
|
||||
(SUBGHZ_DEVICE_CC1101_EXT_FORCE_EXTENDED_RANGE ||
|
||||
subghz_device_cc1101_ext->extended_range) &&
|
||||
!subghz_device_cc1101_ext_is_frequency_valid(value)) {
|
||||
FURI_LOG_I(TAG, "Frequency blocked - outside extended range");
|
||||
return false;
|
||||
@@ -529,7 +585,7 @@ void subghz_device_cc1101_ext_start_async_rx(
|
||||
furi_hal_bus_enable(FuriHalBusTIM17);
|
||||
|
||||
// Configure TIM
|
||||
//Set the timer resolution to 2 µs
|
||||
//Set the timer resolution to 2 us
|
||||
LL_TIM_SetPrescaler(TIM17, (64 << 1) - 1);
|
||||
LL_TIM_SetCounterMode(TIM17, LL_TIM_COUNTERMODE_UP);
|
||||
LL_TIM_SetAutoReload(TIM17, 0xFFFF);
|
||||
@@ -710,7 +766,7 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb
|
||||
furi_hal_bus_enable(FuriHalBusTIM17);
|
||||
|
||||
// Configure TIM
|
||||
// Set the timer resolution to 2 µs
|
||||
// Set the timer resolution to 2 us
|
||||
LL_TIM_SetPrescaler(TIM17, (64 << 1) - 1);
|
||||
LL_TIM_SetCounterMode(TIM17, LL_TIM_COUNTERMODE_UP);
|
||||
LL_TIM_SetAutoReload(TIM17, 0xFFFF);
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
|
||||
#pragma once
|
||||
#include <lib/subghz/devices/preset.h>
|
||||
#include <lib/subghz/devices/types.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <toolbox/level_duration.h>
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <xtreme.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -34,7 +36,7 @@ const GpioPin* subghz_device_cc1101_ext_get_data_gpio();
|
||||
*
|
||||
* @return true if success
|
||||
*/
|
||||
bool subghz_device_cc1101_ext_alloc();
|
||||
bool subghz_device_cc1101_ext_alloc(SubGhzDeviceConf* conf);
|
||||
|
||||
/** Deinitialize device
|
||||
*/
|
||||
|
||||
2
applications/external/4inrow/4inrow.c
vendored
@@ -235,7 +235,7 @@ int32_t four_in_row_app(void* p) {
|
||||
return 255;
|
||||
}
|
||||
|
||||
// dolphin_deed(DolphinDeedPluginGameStart);
|
||||
dolphin_deed(DolphinDeedPluginGameStart);
|
||||
|
||||
// Создаем новый view port
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
|
||||
1
applications/external/4inrow/application.fam
vendored
@@ -7,7 +7,6 @@ App(
|
||||
"gui",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=90,
|
||||
fap_icon="4inrow_10px.png",
|
||||
fap_category="Games",
|
||||
fap_author="leo-need-more-coffee",
|
||||
|
||||
13
applications/external/advanced_wifisniff/application.fam
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# For details & more options, see documentation/AppManifests.md in firmware repo
|
||||
|
||||
App(
|
||||
appid="wifisniffer", # Must be unique
|
||||
name="[ESP32 GPS] Advanced Wifi Sniffer", # Displayed in menus
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="wifisniffer_app",
|
||||
stack_size=2 * 1024,
|
||||
fap_category="WiFi",
|
||||
fap_icon="sniff.png", # 10x10 1-bit PNG
|
||||
fap_icon_assets="assets",
|
||||
fap_icon_assets_symbol="wifisniffer",
|
||||
)
|
||||
BIN
applications/external/advanced_wifisniff/assets/down.png
vendored
Normal file
|
After Width: | Height: | Size: 156 B |
BIN
applications/external/advanced_wifisniff/assets/up.png
vendored
Normal file
|
After Width: | Height: | Size: 155 B |
640
applications/external/advanced_wifisniff/helpers/minmea.c
vendored
Normal file
@@ -0,0 +1,640 @@
|
||||
/*
|
||||
* Copyright © 2014 Kosma Moczek <kosma@cloudyourcar.com>
|
||||
* This program is free software. It comes without any warranty, to the extent
|
||||
* permitted by applicable law. You can redistribute it and/or modify it under
|
||||
* the terms of the Do What The Fuck You Want To Public License, Version 2, as
|
||||
* published by Sam Hocevar. See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#include "minmea.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#define boolstr(s) ((s) ? "true" : "false")
|
||||
|
||||
static int hex2int(char c) {
|
||||
if(c >= '0' && c <= '9') return c - '0';
|
||||
if(c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||
if(c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t minmea_checksum(const char* sentence) {
|
||||
// Support senteces with or without the starting dollar sign.
|
||||
if(*sentence == '$') sentence++;
|
||||
|
||||
uint8_t checksum = 0x00;
|
||||
|
||||
// The optional checksum is an XOR of all bytes between "$" and "*".
|
||||
while(*sentence && *sentence != '*') checksum ^= *sentence++;
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
bool minmea_check(const char* sentence, bool strict) {
|
||||
uint8_t checksum = 0x00;
|
||||
|
||||
// A valid sentence starts with "$".
|
||||
if(*sentence++ != '$') return false;
|
||||
|
||||
// The optional checksum is an XOR of all bytes between "$" and "*".
|
||||
while(*sentence && *sentence != '*' && isprint((unsigned char)*sentence))
|
||||
checksum ^= *sentence++;
|
||||
|
||||
// If checksum is present...
|
||||
if(*sentence == '*') {
|
||||
// Extract checksum.
|
||||
sentence++;
|
||||
int upper = hex2int(*sentence++);
|
||||
if(upper == -1) return false;
|
||||
int lower = hex2int(*sentence++);
|
||||
if(lower == -1) return false;
|
||||
int expected = upper << 4 | lower;
|
||||
|
||||
// Check for checksum mismatch.
|
||||
if(checksum != expected) return false;
|
||||
} else if(strict) {
|
||||
// Discard non-checksummed frames in strict mode.
|
||||
return false;
|
||||
}
|
||||
|
||||
// The only stuff allowed at this point is a newline.
|
||||
while(*sentence == '\r' || *sentence == '\n') {
|
||||
sentence++;
|
||||
}
|
||||
|
||||
if(*sentence) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool minmea_scan(const char* sentence, const char* format, ...) {
|
||||
bool result = false;
|
||||
bool optional = false;
|
||||
|
||||
if(sentence == NULL) return false;
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
|
||||
const char* field = sentence;
|
||||
#define next_field() \
|
||||
do { \
|
||||
/* Progress to the next field. */ \
|
||||
while(minmea_isfield(*sentence)) sentence++; \
|
||||
/* Make sure there is a field there. */ \
|
||||
if(*sentence == ',') { \
|
||||
sentence++; \
|
||||
field = sentence; \
|
||||
} else { \
|
||||
field = NULL; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
while(*format) {
|
||||
char type = *format++;
|
||||
|
||||
if(type == ';') {
|
||||
// All further fields are optional.
|
||||
optional = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!field && !optional) {
|
||||
// Field requested but we ran out if input. Bail out.
|
||||
goto parse_error;
|
||||
}
|
||||
|
||||
switch(type) {
|
||||
case 'c': { // Single character field (char).
|
||||
char value = '\0';
|
||||
|
||||
if(field && minmea_isfield(*field)) value = *field;
|
||||
|
||||
*va_arg(ap, char*) = value;
|
||||
} break;
|
||||
|
||||
case 'd': { // Single character direction field (int).
|
||||
int value = 0;
|
||||
|
||||
if(field && minmea_isfield(*field)) {
|
||||
switch(*field) {
|
||||
case 'N':
|
||||
case 'E':
|
||||
value = 1;
|
||||
break;
|
||||
case 'S':
|
||||
case 'W':
|
||||
value = -1;
|
||||
break;
|
||||
default:
|
||||
goto parse_error;
|
||||
}
|
||||
}
|
||||
|
||||
*va_arg(ap, int*) = value;
|
||||
} break;
|
||||
|
||||
case 'f': { // Fractional value with scale (struct minmea_float).
|
||||
int sign = 0;
|
||||
int_least32_t value = -1;
|
||||
int_least32_t scale = 0;
|
||||
|
||||
if(field) {
|
||||
while(minmea_isfield(*field)) {
|
||||
if(*field == '+' && !sign && value == -1) {
|
||||
sign = 1;
|
||||
} else if(*field == '-' && !sign && value == -1) {
|
||||
sign = -1;
|
||||
} else if(isdigit((unsigned char)*field)) {
|
||||
int digit = *field - '0';
|
||||
if(value == -1) value = 0;
|
||||
if(value > (INT_LEAST32_MAX - digit) / 10) {
|
||||
/* we ran out of bits, what do we do? */
|
||||
if(scale) {
|
||||
/* truncate extra precision */
|
||||
break;
|
||||
} else {
|
||||
/* integer overflow. bail out. */
|
||||
goto parse_error;
|
||||
}
|
||||
}
|
||||
value = (10 * value) + digit;
|
||||
if(scale) scale *= 10;
|
||||
} else if(*field == '.' && scale == 0) {
|
||||
scale = 1;
|
||||
} else if(*field == ' ') {
|
||||
/* Allow spaces at the start of the field. Not NMEA
|
||||
* conformant, but some modules do this. */
|
||||
if(sign != 0 || value != -1 || scale != 0) goto parse_error;
|
||||
} else {
|
||||
goto parse_error;
|
||||
}
|
||||
field++;
|
||||
}
|
||||
}
|
||||
|
||||
if((sign || scale) && value == -1) goto parse_error;
|
||||
|
||||
if(value == -1) {
|
||||
/* No digits were scanned. */
|
||||
value = 0;
|
||||
scale = 0;
|
||||
} else if(scale == 0) {
|
||||
/* No decimal point. */
|
||||
scale = 1;
|
||||
}
|
||||
if(sign) value *= sign;
|
||||
|
||||
*va_arg(ap, struct minmea_float*) = (struct minmea_float){value, scale};
|
||||
} break;
|
||||
|
||||
case 'i': { // Integer value, default 0 (int).
|
||||
int value = 0;
|
||||
|
||||
if(field) {
|
||||
char* endptr;
|
||||
value = strtol(field, &endptr, 10);
|
||||
if(minmea_isfield(*endptr)) goto parse_error;
|
||||
}
|
||||
|
||||
*va_arg(ap, int*) = value;
|
||||
} break;
|
||||
|
||||
case 's': { // String value (char *).
|
||||
char* buf = va_arg(ap, char*);
|
||||
|
||||
if(field) {
|
||||
while(minmea_isfield(*field)) *buf++ = *field++;
|
||||
}
|
||||
|
||||
*buf = '\0';
|
||||
} break;
|
||||
|
||||
case 't': { // NMEA talker+sentence identifier (char *).
|
||||
// This field is always mandatory.
|
||||
if(!field) goto parse_error;
|
||||
|
||||
if(field[0] != '$') goto parse_error;
|
||||
for(int f = 0; f < 5; f++)
|
||||
if(!minmea_isfield(field[1 + f])) goto parse_error;
|
||||
|
||||
char* buf = va_arg(ap, char*);
|
||||
memcpy(buf, field + 1, 5);
|
||||
buf[5] = '\0';
|
||||
} break;
|
||||
|
||||
case 'D': { // Date (int, int, int), -1 if empty.
|
||||
struct minmea_date* date = va_arg(ap, struct minmea_date*);
|
||||
|
||||
int d = -1, m = -1, y = -1;
|
||||
|
||||
if(field && minmea_isfield(*field)) {
|
||||
// Always six digits.
|
||||
for(int f = 0; f < 6; f++)
|
||||
if(!isdigit((unsigned char)field[f])) goto parse_error;
|
||||
|
||||
char dArr[] = {field[0], field[1], '\0'};
|
||||
char mArr[] = {field[2], field[3], '\0'};
|
||||
char yArr[] = {field[4], field[5], '\0'};
|
||||
d = strtol(dArr, NULL, 10);
|
||||
m = strtol(mArr, NULL, 10);
|
||||
y = strtol(yArr, NULL, 10);
|
||||
}
|
||||
|
||||
date->day = d;
|
||||
date->month = m;
|
||||
date->year = y;
|
||||
} break;
|
||||
|
||||
case 'T': { // Time (int, int, int, int), -1 if empty.
|
||||
struct minmea_time* time_ = va_arg(ap, struct minmea_time*);
|
||||
|
||||
int h = -1, i = -1, s = -1, u = -1;
|
||||
|
||||
if(field && minmea_isfield(*field)) {
|
||||
// Minimum required: integer time.
|
||||
for(int f = 0; f < 6; f++)
|
||||
if(!isdigit((unsigned char)field[f])) goto parse_error;
|
||||
|
||||
char hArr[] = {field[0], field[1], '\0'};
|
||||
char iArr[] = {field[2], field[3], '\0'};
|
||||
char sArr[] = {field[4], field[5], '\0'};
|
||||
h = strtol(hArr, NULL, 10);
|
||||
i = strtol(iArr, NULL, 10);
|
||||
s = strtol(sArr, NULL, 10);
|
||||
field += 6;
|
||||
|
||||
// Extra: fractional time. Saved as microseconds.
|
||||
if(*field++ == '.') {
|
||||
uint32_t value = 0;
|
||||
uint32_t scale = 1000000LU;
|
||||
while(isdigit((unsigned char)*field) && scale > 1) {
|
||||
value = (value * 10) + (*field++ - '0');
|
||||
scale /= 10;
|
||||
}
|
||||
u = value * scale;
|
||||
} else {
|
||||
u = 0;
|
||||
}
|
||||
}
|
||||
|
||||
time_->hours = h;
|
||||
time_->minutes = i;
|
||||
time_->seconds = s;
|
||||
time_->microseconds = u;
|
||||
} break;
|
||||
|
||||
case '_': { // Ignore the field.
|
||||
} break;
|
||||
|
||||
default: { // Unknown.
|
||||
goto parse_error;
|
||||
}
|
||||
}
|
||||
|
||||
next_field();
|
||||
}
|
||||
|
||||
result = true;
|
||||
|
||||
parse_error:
|
||||
va_end(ap);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool minmea_talker_id(char talker[3], const char* sentence) {
|
||||
char type[6];
|
||||
if(!minmea_scan(sentence, "t", type)) return false;
|
||||
|
||||
talker[0] = type[0];
|
||||
talker[1] = type[1];
|
||||
talker[2] = '\0';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum minmea_sentence_id minmea_sentence_id(const char* sentence, bool strict) {
|
||||
if(!minmea_check(sentence, strict)) return MINMEA_INVALID;
|
||||
|
||||
char type[6];
|
||||
if(!minmea_scan(sentence, "t", type)) return MINMEA_INVALID;
|
||||
|
||||
if(!strcmp(type + 2, "GBS")) return MINMEA_SENTENCE_GBS;
|
||||
if(!strcmp(type + 2, "GGA")) return MINMEA_SENTENCE_GGA;
|
||||
if(!strcmp(type + 2, "GLL")) return MINMEA_SENTENCE_GLL;
|
||||
if(!strcmp(type + 2, "GSA")) return MINMEA_SENTENCE_GSA;
|
||||
if(!strcmp(type + 2, "GST")) return MINMEA_SENTENCE_GST;
|
||||
if(!strcmp(type + 2, "GSV")) return MINMEA_SENTENCE_GSV;
|
||||
if(!strcmp(type + 2, "RMC")) return MINMEA_SENTENCE_RMC;
|
||||
if(!strcmp(type + 2, "VTG")) return MINMEA_SENTENCE_VTG;
|
||||
if(!strcmp(type + 2, "ZDA")) return MINMEA_SENTENCE_ZDA;
|
||||
|
||||
return MINMEA_UNKNOWN;
|
||||
}
|
||||
|
||||
bool minmea_parse_gbs(struct minmea_sentence_gbs* frame, const char* sentence) {
|
||||
// $GNGBS,170556.00,3.0,2.9,8.3,,,,*5C
|
||||
char type[6];
|
||||
if(!minmea_scan(
|
||||
sentence,
|
||||
"tTfffifff",
|
||||
type,
|
||||
&frame->time,
|
||||
&frame->err_latitude,
|
||||
&frame->err_longitude,
|
||||
&frame->err_altitude,
|
||||
&frame->svid,
|
||||
&frame->prob,
|
||||
&frame->bias,
|
||||
&frame->stddev))
|
||||
return false;
|
||||
if(strcmp(type + 2, "GBS")) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool minmea_parse_rmc(struct minmea_sentence_rmc* frame, const char* sentence) {
|
||||
// $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
|
||||
char type[6];
|
||||
char validity;
|
||||
int latitude_direction;
|
||||
int longitude_direction;
|
||||
int variation_direction;
|
||||
if(!minmea_scan(
|
||||
sentence,
|
||||
"tTcfdfdffDfd",
|
||||
type,
|
||||
&frame->time,
|
||||
&validity,
|
||||
&frame->latitude,
|
||||
&latitude_direction,
|
||||
&frame->longitude,
|
||||
&longitude_direction,
|
||||
&frame->speed,
|
||||
&frame->course,
|
||||
&frame->date,
|
||||
&frame->variation,
|
||||
&variation_direction))
|
||||
return false;
|
||||
if(strcmp(type + 2, "RMC")) return false;
|
||||
|
||||
frame->valid = (validity == 'A');
|
||||
frame->latitude.value *= latitude_direction;
|
||||
frame->longitude.value *= longitude_direction;
|
||||
frame->variation.value *= variation_direction;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool minmea_parse_gga(struct minmea_sentence_gga* frame, const char* sentence) {
|
||||
// $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
|
||||
char type[6];
|
||||
int latitude_direction;
|
||||
int longitude_direction;
|
||||
|
||||
if(!minmea_scan(
|
||||
sentence,
|
||||
"tTfdfdiiffcfcf_",
|
||||
type,
|
||||
&frame->time,
|
||||
&frame->latitude,
|
||||
&latitude_direction,
|
||||
&frame->longitude,
|
||||
&longitude_direction,
|
||||
&frame->fix_quality,
|
||||
&frame->satellites_tracked,
|
||||
&frame->hdop,
|
||||
&frame->altitude,
|
||||
&frame->altitude_units,
|
||||
&frame->height,
|
||||
&frame->height_units,
|
||||
&frame->dgps_age))
|
||||
return false;
|
||||
if(strcmp(type + 2, "GGA")) return false;
|
||||
|
||||
frame->latitude.value *= latitude_direction;
|
||||
frame->longitude.value *= longitude_direction;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool minmea_parse_gsa(struct minmea_sentence_gsa* frame, const char* sentence) {
|
||||
// $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39
|
||||
char type[6];
|
||||
|
||||
if(!minmea_scan(
|
||||
sentence,
|
||||
"tciiiiiiiiiiiiifff",
|
||||
type,
|
||||
&frame->mode,
|
||||
&frame->fix_type,
|
||||
&frame->sats[0],
|
||||
&frame->sats[1],
|
||||
&frame->sats[2],
|
||||
&frame->sats[3],
|
||||
&frame->sats[4],
|
||||
&frame->sats[5],
|
||||
&frame->sats[6],
|
||||
&frame->sats[7],
|
||||
&frame->sats[8],
|
||||
&frame->sats[9],
|
||||
&frame->sats[10],
|
||||
&frame->sats[11],
|
||||
&frame->pdop,
|
||||
&frame->hdop,
|
||||
&frame->vdop))
|
||||
return false;
|
||||
if(strcmp(type + 2, "GSA")) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool minmea_parse_gll(struct minmea_sentence_gll* frame, const char* sentence) {
|
||||
// $GPGLL,3723.2475,N,12158.3416,W,161229.487,A,A*41$;
|
||||
char type[6];
|
||||
int latitude_direction;
|
||||
int longitude_direction;
|
||||
|
||||
if(!minmea_scan(
|
||||
sentence,
|
||||
"tfdfdTc;c",
|
||||
type,
|
||||
&frame->latitude,
|
||||
&latitude_direction,
|
||||
&frame->longitude,
|
||||
&longitude_direction,
|
||||
&frame->time,
|
||||
&frame->status,
|
||||
&frame->mode))
|
||||
return false;
|
||||
if(strcmp(type + 2, "GLL")) return false;
|
||||
|
||||
frame->latitude.value *= latitude_direction;
|
||||
frame->longitude.value *= longitude_direction;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool minmea_parse_gst(struct minmea_sentence_gst* frame, const char* sentence) {
|
||||
// $GPGST,024603.00,3.2,6.6,4.7,47.3,5.8,5.6,22.0*58
|
||||
char type[6];
|
||||
|
||||
if(!minmea_scan(
|
||||
sentence,
|
||||
"tTfffffff",
|
||||
type,
|
||||
&frame->time,
|
||||
&frame->rms_deviation,
|
||||
&frame->semi_major_deviation,
|
||||
&frame->semi_minor_deviation,
|
||||
&frame->semi_major_orientation,
|
||||
&frame->latitude_error_deviation,
|
||||
&frame->longitude_error_deviation,
|
||||
&frame->altitude_error_deviation))
|
||||
return false;
|
||||
if(strcmp(type + 2, "GST")) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool minmea_parse_gsv(struct minmea_sentence_gsv* frame, const char* sentence) {
|
||||
// $GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,00,13,06,292,00*74
|
||||
// $GPGSV,3,3,11,22,42,067,42,24,14,311,43,27,05,244,00,,,,*4D
|
||||
// $GPGSV,4,2,11,08,51,203,30,09,45,215,28*75
|
||||
// $GPGSV,4,4,13,39,31,170,27*40
|
||||
// $GPGSV,4,4,13*7B
|
||||
char type[6];
|
||||
|
||||
if(!minmea_scan(
|
||||
sentence,
|
||||
"tiii;iiiiiiiiiiiiiiii",
|
||||
type,
|
||||
&frame->total_msgs,
|
||||
&frame->msg_nr,
|
||||
&frame->total_sats,
|
||||
&frame->sats[0].nr,
|
||||
&frame->sats[0].elevation,
|
||||
&frame->sats[0].azimuth,
|
||||
&frame->sats[0].snr,
|
||||
&frame->sats[1].nr,
|
||||
&frame->sats[1].elevation,
|
||||
&frame->sats[1].azimuth,
|
||||
&frame->sats[1].snr,
|
||||
&frame->sats[2].nr,
|
||||
&frame->sats[2].elevation,
|
||||
&frame->sats[2].azimuth,
|
||||
&frame->sats[2].snr,
|
||||
&frame->sats[3].nr,
|
||||
&frame->sats[3].elevation,
|
||||
&frame->sats[3].azimuth,
|
||||
&frame->sats[3].snr)) {
|
||||
return false;
|
||||
}
|
||||
if(strcmp(type + 2, "GSV")) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool minmea_parse_vtg(struct minmea_sentence_vtg* frame, const char* sentence) {
|
||||
// $GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48
|
||||
// $GPVTG,156.1,T,140.9,M,0.0,N,0.0,K*41
|
||||
// $GPVTG,096.5,T,083.5,M,0.0,N,0.0,K,D*22
|
||||
// $GPVTG,188.36,T,,M,0.820,N,1.519,K,A*3F
|
||||
char type[6];
|
||||
char c_true, c_magnetic, c_knots, c_kph, c_faa_mode;
|
||||
|
||||
if(!minmea_scan(
|
||||
sentence,
|
||||
"t;fcfcfcfcc",
|
||||
type,
|
||||
&frame->true_track_degrees,
|
||||
&c_true,
|
||||
&frame->magnetic_track_degrees,
|
||||
&c_magnetic,
|
||||
&frame->speed_knots,
|
||||
&c_knots,
|
||||
&frame->speed_kph,
|
||||
&c_kph,
|
||||
&c_faa_mode))
|
||||
return false;
|
||||
if(strcmp(type + 2, "VTG")) return false;
|
||||
// values are only valid with the accompanying characters
|
||||
if(c_true != 'T') frame->true_track_degrees.scale = 0;
|
||||
if(c_magnetic != 'M') frame->magnetic_track_degrees.scale = 0;
|
||||
if(c_knots != 'N') frame->speed_knots.scale = 0;
|
||||
if(c_kph != 'K') frame->speed_kph.scale = 0;
|
||||
frame->faa_mode = (enum minmea_faa_mode)c_faa_mode;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool minmea_parse_zda(struct minmea_sentence_zda* frame, const char* sentence) {
|
||||
// $GPZDA,201530.00,04,07,2002,00,00*60
|
||||
char type[6];
|
||||
|
||||
if(!minmea_scan(
|
||||
sentence,
|
||||
"tTiiiii",
|
||||
type,
|
||||
&frame->time,
|
||||
&frame->date.day,
|
||||
&frame->date.month,
|
||||
&frame->date.year,
|
||||
&frame->hour_offset,
|
||||
&frame->minute_offset))
|
||||
return false;
|
||||
if(strcmp(type + 2, "ZDA")) return false;
|
||||
|
||||
// check offsets
|
||||
if(abs(frame->hour_offset) > 13 || frame->minute_offset > 59 || frame->minute_offset < 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int minmea_getdatetime(
|
||||
struct tm* tm,
|
||||
const struct minmea_date* date,
|
||||
const struct minmea_time* time_) {
|
||||
if(date->year == -1 || time_->hours == -1) return -1;
|
||||
|
||||
memset(tm, 0, sizeof(*tm));
|
||||
if(date->year < 80) {
|
||||
tm->tm_year = 2000 + date->year - 1900; // 2000-2079
|
||||
} else if(date->year >= 1900) {
|
||||
tm->tm_year = date->year - 1900; // 4 digit year, use directly
|
||||
} else {
|
||||
tm->tm_year = date->year; // 1980-1999
|
||||
}
|
||||
tm->tm_mon = date->month - 1;
|
||||
tm->tm_mday = date->day;
|
||||
tm->tm_hour = time_->hours;
|
||||
tm->tm_min = time_->minutes;
|
||||
tm->tm_sec = time_->seconds;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int minmea_gettime(
|
||||
struct timespec* ts,
|
||||
const struct minmea_date* date,
|
||||
const struct minmea_time* time_) {
|
||||
struct tm tm;
|
||||
if(minmea_getdatetime(&tm, date, time_)) return -1;
|
||||
|
||||
time_t timestamp = mktime(&tm); /* See README.md if your system lacks timegm(). */
|
||||
if(timestamp != (time_t)-1) {
|
||||
ts->tv_sec = timestamp;
|
||||
ts->tv_nsec = time_->microseconds * 1000;
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* vim: set ts=4 sw=4 et: */
|
||||
295
applications/external/advanced_wifisniff/helpers/minmea.h
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* Copyright © 2014 Kosma Moczek <kosma@cloudyourcar.com>
|
||||
* This program is free software. It comes without any warranty, to the extent
|
||||
* permitted by applicable law. You can redistribute it and/or modify it under
|
||||
* the terms of the Do What The Fuck You Want To Public License, Version 2, as
|
||||
* published by Sam Hocevar. See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#ifndef MINMEA_H
|
||||
#define MINMEA_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
#ifdef MINMEA_INCLUDE_COMPAT
|
||||
#include <minmea_compat.h>
|
||||
#endif
|
||||
|
||||
#ifndef MINMEA_MAX_SENTENCE_LENGTH
|
||||
#define MINMEA_MAX_SENTENCE_LENGTH 80
|
||||
#endif
|
||||
|
||||
enum minmea_sentence_id {
|
||||
MINMEA_INVALID = -1,
|
||||
MINMEA_UNKNOWN = 0,
|
||||
MINMEA_SENTENCE_GBS,
|
||||
MINMEA_SENTENCE_GGA,
|
||||
MINMEA_SENTENCE_GLL,
|
||||
MINMEA_SENTENCE_GSA,
|
||||
MINMEA_SENTENCE_GST,
|
||||
MINMEA_SENTENCE_GSV,
|
||||
MINMEA_SENTENCE_RMC,
|
||||
MINMEA_SENTENCE_VTG,
|
||||
MINMEA_SENTENCE_ZDA,
|
||||
};
|
||||
|
||||
struct minmea_float {
|
||||
int_least32_t value;
|
||||
int_least32_t scale;
|
||||
};
|
||||
|
||||
struct minmea_date {
|
||||
int day;
|
||||
int month;
|
||||
int year;
|
||||
};
|
||||
|
||||
struct minmea_time {
|
||||
int hours;
|
||||
int minutes;
|
||||
int seconds;
|
||||
int microseconds;
|
||||
};
|
||||
|
||||
struct minmea_sentence_gbs {
|
||||
struct minmea_time time;
|
||||
struct minmea_float err_latitude;
|
||||
struct minmea_float err_longitude;
|
||||
struct minmea_float err_altitude;
|
||||
int svid;
|
||||
struct minmea_float prob;
|
||||
struct minmea_float bias;
|
||||
struct minmea_float stddev;
|
||||
};
|
||||
|
||||
struct minmea_sentence_rmc {
|
||||
struct minmea_time time;
|
||||
bool valid;
|
||||
struct minmea_float latitude;
|
||||
struct minmea_float longitude;
|
||||
struct minmea_float speed;
|
||||
struct minmea_float course;
|
||||
struct minmea_date date;
|
||||
struct minmea_float variation;
|
||||
};
|
||||
|
||||
struct minmea_sentence_gga {
|
||||
struct minmea_time time;
|
||||
struct minmea_float latitude;
|
||||
struct minmea_float longitude;
|
||||
int fix_quality;
|
||||
int satellites_tracked;
|
||||
struct minmea_float hdop;
|
||||
struct minmea_float altitude;
|
||||
char altitude_units;
|
||||
struct minmea_float height;
|
||||
char height_units;
|
||||
struct minmea_float dgps_age;
|
||||
};
|
||||
|
||||
enum minmea_gll_status {
|
||||
MINMEA_GLL_STATUS_DATA_VALID = 'A',
|
||||
MINMEA_GLL_STATUS_DATA_NOT_VALID = 'V',
|
||||
};
|
||||
|
||||
// FAA mode added to some fields in NMEA 2.3.
|
||||
enum minmea_faa_mode {
|
||||
MINMEA_FAA_MODE_AUTONOMOUS = 'A',
|
||||
MINMEA_FAA_MODE_DIFFERENTIAL = 'D',
|
||||
MINMEA_FAA_MODE_ESTIMATED = 'E',
|
||||
MINMEA_FAA_MODE_MANUAL = 'M',
|
||||
MINMEA_FAA_MODE_SIMULATED = 'S',
|
||||
MINMEA_FAA_MODE_NOT_VALID = 'N',
|
||||
MINMEA_FAA_MODE_PRECISE = 'P',
|
||||
};
|
||||
|
||||
struct minmea_sentence_gll {
|
||||
struct minmea_float latitude;
|
||||
struct minmea_float longitude;
|
||||
struct minmea_time time;
|
||||
char status;
|
||||
char mode;
|
||||
};
|
||||
|
||||
struct minmea_sentence_gst {
|
||||
struct minmea_time time;
|
||||
struct minmea_float rms_deviation;
|
||||
struct minmea_float semi_major_deviation;
|
||||
struct minmea_float semi_minor_deviation;
|
||||
struct minmea_float semi_major_orientation;
|
||||
struct minmea_float latitude_error_deviation;
|
||||
struct minmea_float longitude_error_deviation;
|
||||
struct minmea_float altitude_error_deviation;
|
||||
};
|
||||
|
||||
enum minmea_gsa_mode {
|
||||
MINMEA_GPGSA_MODE_AUTO = 'A',
|
||||
MINMEA_GPGSA_MODE_FORCED = 'M',
|
||||
};
|
||||
|
||||
enum minmea_gsa_fix_type {
|
||||
MINMEA_GPGSA_FIX_NONE = 1,
|
||||
MINMEA_GPGSA_FIX_2D = 2,
|
||||
MINMEA_GPGSA_FIX_3D = 3,
|
||||
};
|
||||
|
||||
struct minmea_sentence_gsa {
|
||||
char mode;
|
||||
int fix_type;
|
||||
int sats[12];
|
||||
struct minmea_float pdop;
|
||||
struct minmea_float hdop;
|
||||
struct minmea_float vdop;
|
||||
};
|
||||
|
||||
struct minmea_sat_info {
|
||||
int nr;
|
||||
int elevation;
|
||||
int azimuth;
|
||||
int snr;
|
||||
};
|
||||
|
||||
struct minmea_sentence_gsv {
|
||||
int total_msgs;
|
||||
int msg_nr;
|
||||
int total_sats;
|
||||
struct minmea_sat_info sats[4];
|
||||
};
|
||||
|
||||
struct minmea_sentence_vtg {
|
||||
struct minmea_float true_track_degrees;
|
||||
struct minmea_float magnetic_track_degrees;
|
||||
struct minmea_float speed_knots;
|
||||
struct minmea_float speed_kph;
|
||||
enum minmea_faa_mode faa_mode;
|
||||
};
|
||||
|
||||
struct minmea_sentence_zda {
|
||||
struct minmea_time time;
|
||||
struct minmea_date date;
|
||||
int hour_offset;
|
||||
int minute_offset;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate raw sentence checksum. Does not check sentence integrity.
|
||||
*/
|
||||
uint8_t minmea_checksum(const char* sentence);
|
||||
|
||||
/**
|
||||
* Check sentence validity and checksum. Returns true for valid sentences.
|
||||
*/
|
||||
bool minmea_check(const char* sentence, bool strict);
|
||||
|
||||
/**
|
||||
* Determine talker identifier.
|
||||
*/
|
||||
bool minmea_talker_id(char talker[3], const char* sentence);
|
||||
|
||||
/**
|
||||
* Determine sentence identifier.
|
||||
*/
|
||||
enum minmea_sentence_id minmea_sentence_id(const char* sentence, bool strict);
|
||||
|
||||
/**
|
||||
* Scanf-like processor for NMEA sentences. Supports the following formats:
|
||||
* c - single character (char *)
|
||||
* d - direction, returned as 1/-1, default 0 (int *)
|
||||
* f - fractional, returned as value + scale (struct minmea_float *)
|
||||
* i - decimal, default zero (int *)
|
||||
* s - string (char *)
|
||||
* t - talker identifier and type (char *)
|
||||
* D - date (struct minmea_date *)
|
||||
* T - time stamp (struct minmea_time *)
|
||||
* _ - ignore this field
|
||||
* ; - following fields are optional
|
||||
* Returns true on success. See library source code for details.
|
||||
*/
|
||||
bool minmea_scan(const char* sentence, const char* format, ...);
|
||||
|
||||
/*
|
||||
* Parse a specific type of sentence. Return true on success.
|
||||
*/
|
||||
bool minmea_parse_gbs(struct minmea_sentence_gbs* frame, const char* sentence);
|
||||
bool minmea_parse_rmc(struct minmea_sentence_rmc* frame, const char* sentence);
|
||||
bool minmea_parse_gga(struct minmea_sentence_gga* frame, const char* sentence);
|
||||
bool minmea_parse_gsa(struct minmea_sentence_gsa* frame, const char* sentence);
|
||||
bool minmea_parse_gll(struct minmea_sentence_gll* frame, const char* sentence);
|
||||
bool minmea_parse_gst(struct minmea_sentence_gst* frame, const char* sentence);
|
||||
bool minmea_parse_gsv(struct minmea_sentence_gsv* frame, const char* sentence);
|
||||
bool minmea_parse_vtg(struct minmea_sentence_vtg* frame, const char* sentence);
|
||||
bool minmea_parse_zda(struct minmea_sentence_zda* frame, const char* sentence);
|
||||
|
||||
/**
|
||||
* Convert GPS UTC date/time representation to a UNIX calendar time.
|
||||
*/
|
||||
int minmea_getdatetime(
|
||||
struct tm* tm,
|
||||
const struct minmea_date* date,
|
||||
const struct minmea_time* time_);
|
||||
|
||||
/**
|
||||
* Convert GPS UTC date/time representation to a UNIX timestamp.
|
||||
*/
|
||||
int minmea_gettime(
|
||||
struct timespec* ts,
|
||||
const struct minmea_date* date,
|
||||
const struct minmea_time* time_);
|
||||
|
||||
/**
|
||||
* Rescale a fixed-point value to a different scale. Rounds towards zero.
|
||||
*/
|
||||
static inline int_least32_t minmea_rescale(const struct minmea_float* f, int_least32_t new_scale) {
|
||||
if(f->scale == 0) return 0;
|
||||
if(f->scale == new_scale) return f->value;
|
||||
if(f->scale > new_scale)
|
||||
return (f->value + ((f->value > 0) - (f->value < 0)) * f->scale / new_scale / 2) /
|
||||
(f->scale / new_scale);
|
||||
else
|
||||
return f->value * (new_scale / f->scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a fixed-point value to a floating-point value.
|
||||
* Returns NaN for "unknown" values.
|
||||
*/
|
||||
static inline float minmea_tofloat(const struct minmea_float* f) {
|
||||
if(f->scale == 0) return NAN;
|
||||
return (float)f->value / (float)f->scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a raw coordinate to a floating point DD.DDD... value.
|
||||
* Returns NaN for "unknown" values.
|
||||
*/
|
||||
static inline float minmea_tocoord(const struct minmea_float* f) {
|
||||
if(f->scale == 0) return NAN;
|
||||
if(f->scale > (INT_LEAST32_MAX / 100)) return NAN;
|
||||
if(f->scale < (INT_LEAST32_MIN / 100)) return NAN;
|
||||
int_least32_t degrees = f->value / (f->scale * 100);
|
||||
int_least32_t minutes = f->value % (f->scale * 100);
|
||||
return (float)degrees + (float)minutes / (60 * f->scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a character belongs to the set of characters allowed in a
|
||||
* sentence data field.
|
||||
*/
|
||||
static inline bool minmea_isfield(char c) {
|
||||
return isprint((unsigned char)c) && c != ',' && c != '*';
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MINMEA_H */
|
||||
|
||||
/* vim: set ts=4 sw=4 et: */
|
||||
BIN
applications/external/advanced_wifisniff/sniff.png
vendored
Normal file
|
After Width: | Height: | Size: 160 B |
835
applications/external/advanced_wifisniff/sniffer.c
vendored
Normal file
@@ -0,0 +1,835 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/gui.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <assets_icons.h>
|
||||
#include <toolbox/stream/file_stream.h>
|
||||
#include <xtreme.h>
|
||||
|
||||
#include "helpers/minmea.h"
|
||||
#include "wifisniffer_icons.h"
|
||||
|
||||
#define appname "ll-wifisniffer"
|
||||
|
||||
#define RX_BUF_SIZE 2048
|
||||
#define MAX_ACCESS_POINTS 2048 // imagine getting this many access points
|
||||
|
||||
#define MAX_SSID_LENGTH 32
|
||||
#define MAX_BSSID_LENGTH 18
|
||||
|
||||
#define UART_CH_ESP \
|
||||
(XTREME_SETTINGS()->uart_esp_channel == UARTDefault ? FuriHalUartIdUSART1 : \
|
||||
FuriHalUartIdLPUART1)
|
||||
|
||||
#define UART_CH_GPS \
|
||||
(XTREME_SETTINGS()->uart_nmea_channel == UARTDefault ? FuriHalUartIdUSART1 : \
|
||||
FuriHalUartIdLPUART1)
|
||||
|
||||
#define WORKER_ALL_RX_EVENTS (WorkerEvtStop | WorkerEvtRxDone)
|
||||
|
||||
typedef enum {
|
||||
WorkerEvtStop = (1 << 0),
|
||||
WorkerEvtRxDone = (1 << 1),
|
||||
} WorkerEvtFlags;
|
||||
|
||||
typedef enum {
|
||||
EventTypeKey,
|
||||
EventTypeTick,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} Event;
|
||||
|
||||
typedef struct {
|
||||
char* recievedMac;
|
||||
char* sentMac;
|
||||
} Packet;
|
||||
|
||||
typedef struct {
|
||||
char* ssid;
|
||||
char* bssid;
|
||||
int8_t rssi;
|
||||
uint8_t channel;
|
||||
FuriHalRtcDateTime datetime;
|
||||
uint16_t packetRxCount;
|
||||
uint16_t packetTxCount;
|
||||
float latitude;
|
||||
float longitude;
|
||||
} AccessPoint;
|
||||
|
||||
typedef struct {
|
||||
FuriMessageQueue* queue;
|
||||
FuriMutex* mutex;
|
||||
FuriString* buffer;
|
||||
FuriString* buffer2;
|
||||
NotificationApp* notifications;
|
||||
FuriThread* thread_esp;
|
||||
FuriStreamBuffer* rx_stream_esp;
|
||||
uint8_t rx_buf_esp[2048];
|
||||
FuriThread* thread_gps;
|
||||
FuriStreamBuffer* rx_stream_gps;
|
||||
uint8_t rx_buf_gps[2048];
|
||||
File* file;
|
||||
char* dataString;
|
||||
uint16_t access_points_count;
|
||||
AccessPoint access_points[MAX_ACCESS_POINTS];
|
||||
int16_t access_points_index;
|
||||
AccessPoint active_access_point;
|
||||
bool extra_info;
|
||||
bool pressedButton;
|
||||
float last_latitude;
|
||||
float last_longitude;
|
||||
} Context;
|
||||
|
||||
static void tick_callback(void* ctx_q) {
|
||||
furi_assert(ctx_q);
|
||||
FuriMessageQueue* queue = ctx_q;
|
||||
Event event = {.type = EventTypeTick};
|
||||
furi_message_queue_put(queue, &event, 0);
|
||||
}
|
||||
|
||||
static void input_callback(InputEvent* input_event, FuriMessageQueue* queue) {
|
||||
furi_assert(queue);
|
||||
Event event = {.type = EventTypeKey, .input = *input_event};
|
||||
furi_message_queue_put(queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void show_access_point(Canvas* canvas, Context* context) {
|
||||
Context* ctx = context;
|
||||
|
||||
AccessPoint ap = ctx->active_access_point;
|
||||
|
||||
canvas_draw_str_aligned(canvas, 62, 25, AlignCenter, AlignBottom, ap.ssid);
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 38 + (ctx->access_points_count > 99 ? 5 : 0), 12, AlignLeft, AlignBottom, ap.bssid);
|
||||
|
||||
furi_string_printf(ctx->buffer, "Signal strength: %ddBm", ap.rssi);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 3, 35, AlignLeft, AlignBottom, furi_string_get_cstr(ctx->buffer));
|
||||
|
||||
furi_string_printf(ctx->buffer, "CH: %d", ap.channel);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 3, 47, AlignLeft, AlignBottom, furi_string_get_cstr(ctx->buffer));
|
||||
|
||||
if(ap.latitude == 0 && ap.longitude == 0) {
|
||||
canvas_draw_str_aligned(canvas, 29, 47, AlignLeft, AlignBottom, "X");
|
||||
} else {
|
||||
canvas_draw_str_aligned(canvas, 29, 47, AlignLeft, AlignBottom, "O");
|
||||
}
|
||||
|
||||
furi_string_printf(ctx->buffer, "%d", ap.packetRxCount);
|
||||
canvas_draw_icon(canvas, 35, 39, &I_down);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 45, 47, AlignLeft, AlignBottom, furi_string_get_cstr(ctx->buffer));
|
||||
|
||||
furi_string_printf(ctx->buffer, "%d", ap.packetTxCount);
|
||||
canvas_draw_icon(canvas, 85, 38, &I_up);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 95, 47, AlignLeft, AlignBottom, furi_string_get_cstr(ctx->buffer));
|
||||
|
||||
furi_string_printf(
|
||||
ctx->buffer,
|
||||
"Seen: %d:%d:%d (%lds ago)",
|
||||
ap.datetime.hour,
|
||||
ap.datetime.minute,
|
||||
ap.datetime.second,
|
||||
furi_hal_rtc_get_timestamp() - furi_hal_rtc_datetime_to_timestamp(&ap.datetime));
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 3, 59, AlignLeft, AlignBottom, furi_string_get_cstr(ctx->buffer));
|
||||
}
|
||||
|
||||
static void render_callback(Canvas* canvas, void* context) {
|
||||
Context* ctx = context;
|
||||
|
||||
canvas_draw_frame(canvas, 0, 0, 128, 64);
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
|
||||
if(ctx->access_points_count >= MAX_ACCESS_POINTS) {
|
||||
canvas_draw_str(canvas, 118, 10, "!");
|
||||
}
|
||||
|
||||
if(ctx->access_points_count == 0) {
|
||||
canvas_draw_str(canvas, 80, 30, "No AP's");
|
||||
canvas_draw_str(canvas, 80, 40, "Found!");
|
||||
canvas_draw_icon(canvas, 1, 4, &I_DolphinWait_61x59);
|
||||
} else {
|
||||
canvas_draw_frame(canvas, 0, 0, 35 + (ctx->access_points_count > 99 ? 5 : 0), 15);
|
||||
|
||||
furi_string_printf(
|
||||
ctx->buffer, "%d/%d", ctx->access_points_index + 1, ctx->access_points_count);
|
||||
|
||||
canvas_draw_str(canvas, 3, 12, furi_string_get_cstr(ctx->buffer));
|
||||
|
||||
show_access_point(canvas, ctx);
|
||||
}
|
||||
// canvas_clear(canvas);
|
||||
furi_mutex_release(ctx->mutex);
|
||||
}
|
||||
|
||||
// order ctx->access_points by ssid alphabetically
|
||||
static void sort_access_points(Context* ctx) {
|
||||
for(int i = 0; i < ctx->access_points_count; i++) {
|
||||
for(int j = i + 1; j < ctx->access_points_count; j++) {
|
||||
if(strcmp(ctx->access_points[i].ssid, ctx->access_points[j].ssid) > 0) {
|
||||
AccessPoint temp = ctx->access_points[i];
|
||||
ctx->access_points[i] = ctx->access_points[j];
|
||||
ctx->access_points[j] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set the index from the active access point
|
||||
static void set_index_from_access_points(Context* ctx) {
|
||||
for(int i = 0; i < ctx->access_points_count; i++) {
|
||||
if(ctx->access_points[i].bssid == ctx->active_access_point.bssid) {
|
||||
ctx->access_points_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void removeSpaces(char* str) {
|
||||
// Remove spaces from the beginning of the string
|
||||
int i = 0;
|
||||
while(isspace((unsigned char)str[i])) {
|
||||
i++;
|
||||
}
|
||||
|
||||
// Move the remaining characters to the beginning of the string
|
||||
int j = 0;
|
||||
while(str[i] != '\0') {
|
||||
str[j++] = str[i++];
|
||||
}
|
||||
str[j] = '\0';
|
||||
|
||||
// Remove spaces from the end of the string
|
||||
int len = strlen(str);
|
||||
while(len > 0 && isspace((unsigned char)str[len - 1])) {
|
||||
str[--len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
static void parseLine(void* context, char* line) {
|
||||
Context* ctx = context;
|
||||
|
||||
AccessPoint ap = {.ssid = malloc(MAX_SSID_LENGTH + 1), .bssid = malloc(MAX_BSSID_LENGTH + 1)};
|
||||
|
||||
Packet pkt = {.recievedMac = malloc(18 + 1), .sentMac = malloc(18 + 1)};
|
||||
|
||||
char* token = strtok(line, ",");
|
||||
int i = 0;
|
||||
bool isAp = false;
|
||||
bool isValid = true;
|
||||
UNUSED(isValid);
|
||||
while(token != NULL) {
|
||||
switch(i) {
|
||||
case 0:
|
||||
if(strcmp(token, "AR") == 0) {
|
||||
isAp = true;
|
||||
isValid = true;
|
||||
} else if(strcmp(token, "PK") == 0) {
|
||||
isAp = false;
|
||||
isValid = true;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if(isAp && isValid) {
|
||||
removeSpaces(token);
|
||||
strcpy(ap.ssid, token);
|
||||
} else if(!isAp && isValid) {
|
||||
strncpy(pkt.recievedMac, token, 18);
|
||||
pkt.recievedMac[18] = '\0';
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if(isAp && isValid) {
|
||||
strcpy(ap.bssid, token);
|
||||
} else if(!isAp && isValid) {
|
||||
strncpy(pkt.sentMac, token, 18);
|
||||
pkt.sentMac[18] = '\0';
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if(isAp && isValid) {
|
||||
ap.rssi = atoi(token);
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
if(isAp && isValid) {
|
||||
ap.channel = atoi(token);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
token = strtok(NULL, ",");
|
||||
i++;
|
||||
}
|
||||
|
||||
if(isAp && isValid) {
|
||||
// free the packet
|
||||
free(pkt.recievedMac);
|
||||
free(pkt.sentMac);
|
||||
|
||||
// check if values are valid
|
||||
// bssid needs an ":"
|
||||
// rssi needs to be negative
|
||||
// channel needs to be between 1 and 14
|
||||
// ssid needs to be at least 1 character long
|
||||
if(ap.bssid[2] != ':' || ap.bssid[5] != ':' || ap.bssid[8] != ':' || ap.bssid[11] != ':' ||
|
||||
ap.bssid[14] != ':' || ap.rssi > 0 || ap.channel < 1 || ap.channel > 14 ||
|
||||
strlen(ap.ssid) < 1) {
|
||||
free(ap.ssid);
|
||||
free(ap.bssid);
|
||||
return;
|
||||
}
|
||||
|
||||
furi_hal_light_set(LightBlue, 0);
|
||||
furi_hal_light_set(LightGreen, 255);
|
||||
|
||||
furi_hal_rtc_get_datetime(&ap.datetime);
|
||||
|
||||
if(isnan(ctx->last_latitude) || isnan(ctx->last_longitude)) {
|
||||
ctx->last_latitude = 0;
|
||||
ctx->last_longitude = 0;
|
||||
} else {
|
||||
ap.latitude = ctx->last_latitude;
|
||||
ap.longitude = ctx->last_longitude;
|
||||
}
|
||||
|
||||
// check if ap is already in the list otherwise add it but update the rssi
|
||||
bool found = false;
|
||||
for(size_t i = 0; i < ctx->access_points_count; i++) {
|
||||
if(strcmp(ctx->access_points[i].bssid, ap.bssid) == 0) {
|
||||
found = true;
|
||||
//update rssi channel datetime
|
||||
ctx->access_points[i].rssi = ap.rssi;
|
||||
ctx->access_points[i].channel = ap.channel;
|
||||
ctx->access_points[i].datetime = ap.datetime;
|
||||
ctx->access_points[i].latitude = ap.latitude;
|
||||
ctx->access_points[i].longitude = ap.longitude;
|
||||
|
||||
if(strcmp(ctx->active_access_point.bssid, ap.bssid) == 0) {
|
||||
ctx->active_access_point.rssi = ap.rssi;
|
||||
ctx->active_access_point.channel = ap.channel;
|
||||
ctx->active_access_point.datetime = ap.datetime;
|
||||
ctx->active_access_point.latitude = ap.latitude;
|
||||
ctx->active_access_point.longitude = ap.longitude;
|
||||
}
|
||||
|
||||
free(ap.ssid);
|
||||
free(ap.bssid);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found) {
|
||||
memcpy(&ctx->access_points[ctx->access_points_count], &ap, sizeof(AccessPoint));
|
||||
ctx->access_points_count++;
|
||||
}
|
||||
|
||||
sort_access_points(ctx);
|
||||
set_index_from_access_points(ctx);
|
||||
} else {
|
||||
// it is a packet so screw the ap
|
||||
free(ap.ssid);
|
||||
free(ap.bssid);
|
||||
|
||||
// check if values are valid
|
||||
// mac needs to be 6 characters long
|
||||
if(strlen(pkt.recievedMac) != 17 || strlen(pkt.sentMac) != 17 ||
|
||||
ctx->access_points_count == 0) {
|
||||
free(pkt.recievedMac);
|
||||
free(pkt.sentMac);
|
||||
return;
|
||||
}
|
||||
|
||||
furi_hal_light_set(LightGreen, 0);
|
||||
furi_hal_light_set(LightBlue, 255);
|
||||
|
||||
for(size_t i = 0; i < ctx->access_points_count; i++) {
|
||||
if(strcmp(ctx->access_points[i].bssid, pkt.recievedMac) == 0) {
|
||||
ctx->access_points[i].packetRxCount++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < ctx->access_points_count; i++) {
|
||||
if(strcmp(ctx->access_points[i].bssid, pkt.sentMac) == 0) {
|
||||
ctx->access_points[i].packetTxCount++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(pkt.recievedMac);
|
||||
free(pkt.sentMac);
|
||||
}
|
||||
}
|
||||
|
||||
static void uart_cb_esp(UartIrqEvent ev, uint8_t data, void* context) {
|
||||
Context* ctx = (Context*)context;
|
||||
|
||||
if(ev == UartIrqEventRXNE) {
|
||||
furi_stream_buffer_send(ctx->rx_stream_esp, &data, 1, 0);
|
||||
furi_thread_flags_set(furi_thread_get_id(ctx->thread_esp), WorkerEvtRxDone);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t uart_worker_esp(void* context) {
|
||||
Context* ctx = (Context*)context;
|
||||
|
||||
size_t rx_offset = 0;
|
||||
|
||||
while(1) {
|
||||
uint32_t events =
|
||||
furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever);
|
||||
furi_check((events & FuriFlagError) == 0);
|
||||
|
||||
if(events & WorkerEvtStop) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(events & WorkerEvtRxDone) {
|
||||
size_t len = 0;
|
||||
do {
|
||||
// receive serial bytes into rx_buf, starting at rx_offset from the start of the buffer
|
||||
// the maximum we can receive is RX_BUF_SIZE - 1 - rx_offset
|
||||
len = furi_stream_buffer_receive(
|
||||
ctx->rx_stream_esp,
|
||||
ctx->rx_buf_esp + rx_offset,
|
||||
RX_BUF_SIZE - 1 - rx_offset,
|
||||
0);
|
||||
|
||||
if(len > 0) {
|
||||
// increase rx_offset by the number of bytes received, and null-terminate rx_buf
|
||||
rx_offset += len;
|
||||
ctx->rx_buf_esp[rx_offset] = '\0';
|
||||
|
||||
// look for strings ending in newlines, starting at the start of rx_buf
|
||||
char* line_current = (char*)ctx->rx_buf_esp;
|
||||
while(1) {
|
||||
// skip null characters
|
||||
while(*line_current == '\0' &&
|
||||
line_current < (char*)ctx->rx_buf_esp + rx_offset - 1) {
|
||||
line_current++;
|
||||
}
|
||||
|
||||
// find the next newline
|
||||
char* newline = strchr(line_current, '\n');
|
||||
if(newline) // newline found
|
||||
{
|
||||
// put a null terminator in place of the newline, to delimit the line string
|
||||
*newline = '\0';
|
||||
|
||||
parseLine(ctx, line_current);
|
||||
|
||||
// move the cursor to the character after the newline
|
||||
line_current = newline + 1;
|
||||
} else // no more newlines found
|
||||
{
|
||||
if(line_current >
|
||||
(char*)ctx->rx_buf_esp) // at least one line was found
|
||||
{
|
||||
// clear parsed lines, and move any leftover bytes to the start of rx_buf
|
||||
rx_offset = 0;
|
||||
while(
|
||||
*line_current) // stop when the original rx_offset terminator is reached
|
||||
{
|
||||
ctx->rx_buf_esp[rx_offset++] = *(line_current++);
|
||||
}
|
||||
}
|
||||
break; // go back to receiving bytes from the serial stream
|
||||
}
|
||||
}
|
||||
}
|
||||
} while(len > 0);
|
||||
}
|
||||
}
|
||||
|
||||
furi_hal_uart_set_irq_cb(UART_CH_ESP, NULL, NULL);
|
||||
|
||||
furi_stream_buffer_free(ctx->rx_stream_esp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gps_uart_parse_nmea(Context* ctx, char* line) {
|
||||
switch(minmea_sentence_id(line, false)) {
|
||||
case MINMEA_SENTENCE_RMC: {
|
||||
struct minmea_sentence_rmc frame;
|
||||
if(minmea_parse_rmc(&frame, line)) {
|
||||
ctx->last_latitude = minmea_tocoord(&frame.latitude);
|
||||
ctx->last_longitude = minmea_tocoord(&frame.longitude);
|
||||
}
|
||||
} break;
|
||||
|
||||
case MINMEA_SENTENCE_GGA: {
|
||||
struct minmea_sentence_gga frame;
|
||||
if(minmea_parse_gga(&frame, line)) {
|
||||
ctx->last_latitude = minmea_tocoord(&frame.latitude);
|
||||
ctx->last_longitude = minmea_tocoord(&frame.longitude);
|
||||
}
|
||||
} break;
|
||||
|
||||
case MINMEA_SENTENCE_GLL: {
|
||||
struct minmea_sentence_gll frame;
|
||||
if(minmea_parse_gll(&frame, line)) {
|
||||
ctx->last_latitude = minmea_tocoord(&frame.latitude);
|
||||
ctx->last_longitude = minmea_tocoord(&frame.longitude);
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void uart_cb_gps(UartIrqEvent ev, uint8_t data, void* context) {
|
||||
Context* ctx = (Context*)context;
|
||||
|
||||
if(ev == UartIrqEventRXNE) {
|
||||
furi_stream_buffer_send(ctx->rx_stream_gps, &data, 1, 0);
|
||||
furi_thread_flags_set(furi_thread_get_id(ctx->thread_gps), WorkerEvtRxDone);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t uart_worker_gps(void* context) {
|
||||
Context* ctx = (Context*)context;
|
||||
|
||||
size_t rx_offset = 0;
|
||||
|
||||
while(1) {
|
||||
uint32_t events =
|
||||
furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever);
|
||||
furi_check((events & FuriFlagError) == 0);
|
||||
|
||||
if(events & WorkerEvtStop) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(events & WorkerEvtRxDone) {
|
||||
size_t len = 0;
|
||||
do {
|
||||
// receive serial bytes into rx_buf, starting at rx_offset from the start of the buffer
|
||||
// the maximum we can receive is RX_BUF_SIZE - 1 - rx_offset
|
||||
len = furi_stream_buffer_receive(
|
||||
ctx->rx_stream_gps,
|
||||
ctx->rx_buf_gps + rx_offset,
|
||||
RX_BUF_SIZE - 1 - rx_offset,
|
||||
0);
|
||||
|
||||
if(len > 0) {
|
||||
// increase rx_offset by the number of bytes received, and null-terminate rx_buf
|
||||
rx_offset += len;
|
||||
ctx->rx_buf_gps[rx_offset] = '\0';
|
||||
|
||||
// look for strings ending in newlines, starting at the start of rx_buf
|
||||
char* line_current = (char*)ctx->rx_buf_gps;
|
||||
while(1) {
|
||||
// skip null characters
|
||||
while(*line_current == '\0' &&
|
||||
line_current < (char*)ctx->rx_buf_gps + rx_offset - 1) {
|
||||
line_current++;
|
||||
}
|
||||
|
||||
// find the next newline
|
||||
char* newline = strchr(line_current, '\n');
|
||||
if(newline) // newline found
|
||||
{
|
||||
// put a null terminator in place of the newline, to delimit the line string
|
||||
*newline = '\0';
|
||||
|
||||
// FURI_LOG_I(appname, "Received line: %s", line_current);
|
||||
|
||||
gps_uart_parse_nmea(ctx, line_current);
|
||||
|
||||
// move the cursor to the character after the newline
|
||||
line_current = newline + 1;
|
||||
} else // no more newlines found
|
||||
{
|
||||
if(line_current >
|
||||
(char*)ctx->rx_buf_gps) // at least one line was found
|
||||
{
|
||||
// clear parsed lines, and move any leftover bytes to the start of rx_buf
|
||||
rx_offset = 0;
|
||||
while(
|
||||
*line_current) // stop when the original rx_offset terminator is reached
|
||||
{
|
||||
ctx->rx_buf_gps[rx_offset++] = *(line_current++);
|
||||
}
|
||||
}
|
||||
break; // go back to receiving bytes from the serial stream
|
||||
}
|
||||
}
|
||||
}
|
||||
} while(len > 0);
|
||||
}
|
||||
}
|
||||
|
||||
furi_hal_uart_set_irq_cb(UART_CH_GPS, NULL, NULL);
|
||||
|
||||
furi_stream_buffer_free(ctx->rx_stream_gps);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t wifisniffer_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
// if(UART_CH_ESP == UART_CH_GPS) {
|
||||
// FURI_LOG_I(appname, "ESP and GPS uart can't be the same");
|
||||
// return -1;
|
||||
// }
|
||||
|
||||
// turn off 5v, so it gets reset on startup
|
||||
if(furi_hal_power_is_otg_enabled()) {
|
||||
furi_hal_power_disable_otg();
|
||||
}
|
||||
|
||||
// Enable 5v on startup
|
||||
uint8_t attempts = 0;
|
||||
while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
|
||||
furi_hal_power_enable_otg();
|
||||
furi_delay_ms(10);
|
||||
}
|
||||
furi_delay_ms(200);
|
||||
|
||||
// alloc everything
|
||||
Context* ctx = malloc(sizeof(Context));
|
||||
ctx->queue = furi_message_queue_alloc(8, sizeof(Event));
|
||||
ctx->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
ctx->buffer = furi_string_alloc();
|
||||
ctx->buffer2 = furi_string_alloc();
|
||||
ctx->notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
ctx->access_points_count = 0;
|
||||
ctx->access_points_index = 0;
|
||||
|
||||
ctx->pressedButton = false;
|
||||
|
||||
//esp uart
|
||||
ctx->rx_stream_esp = furi_stream_buffer_alloc(RX_BUF_SIZE * 5, 1);
|
||||
|
||||
ctx->thread_esp = furi_thread_alloc();
|
||||
furi_thread_set_name(ctx->thread_esp, "LLwifiSnifferUartWorkerESP");
|
||||
furi_thread_set_stack_size(ctx->thread_esp, 2048);
|
||||
furi_thread_set_context(ctx->thread_esp, ctx);
|
||||
furi_thread_set_callback(ctx->thread_esp, uart_worker_esp);
|
||||
|
||||
furi_thread_start(ctx->thread_esp);
|
||||
|
||||
if(UART_CH_ESP == FuriHalUartIdUSART1) {
|
||||
furi_hal_console_disable();
|
||||
} else if(UART_CH_ESP == FuriHalUartIdLPUART1) {
|
||||
furi_hal_uart_init(UART_CH_ESP, 115200);
|
||||
}
|
||||
furi_hal_uart_set_br(UART_CH_ESP, 115200);
|
||||
furi_hal_uart_set_irq_cb(UART_CH_ESP, uart_cb_esp, ctx);
|
||||
|
||||
furi_hal_uart_tx(UART_CH_ESP, (uint8_t*)"XFW#WIFISNIFF=1\r\n", strlen("XFW#WIFISNIFF=1\r\n"));
|
||||
//end esp uart
|
||||
|
||||
//gps uart
|
||||
if(UART_CH_ESP != UART_CH_GPS) {
|
||||
ctx->rx_stream_gps = furi_stream_buffer_alloc(RX_BUF_SIZE * 5, 1);
|
||||
|
||||
ctx->thread_gps = furi_thread_alloc();
|
||||
furi_thread_set_name(ctx->thread_gps, "LLwifiSnifferUartWorkerGPS");
|
||||
furi_thread_set_stack_size(ctx->thread_gps, 2048);
|
||||
furi_thread_set_context(ctx->thread_gps, ctx);
|
||||
furi_thread_set_callback(ctx->thread_gps, uart_worker_gps);
|
||||
|
||||
furi_thread_start(ctx->thread_gps);
|
||||
|
||||
if(UART_CH_GPS == FuriHalUartIdUSART1) {
|
||||
furi_hal_console_disable();
|
||||
} else if(UART_CH_GPS == FuriHalUartIdLPUART1) {
|
||||
furi_hal_uart_init(UART_CH_GPS, 9600);
|
||||
}
|
||||
furi_hal_uart_set_br(UART_CH_GPS, 9600);
|
||||
furi_hal_uart_set_irq_cb(UART_CH_GPS, uart_cb_gps, ctx);
|
||||
}
|
||||
//end gps uart
|
||||
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, render_callback, ctx);
|
||||
view_port_input_callback_set(view_port, input_callback, ctx->queue);
|
||||
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
FuriTimer* timer = furi_timer_alloc(tick_callback, FuriTimerTypePeriodic, ctx->queue);
|
||||
furi_timer_start(timer, 100);
|
||||
|
||||
// application loop
|
||||
Event event;
|
||||
bool processing = true;
|
||||
do {
|
||||
if(furi_message_queue_get(ctx->queue, &event, FuriWaitForever) == FuriStatusOk) {
|
||||
furi_mutex_acquire(ctx->mutex, FuriWaitForever);
|
||||
switch(event.type) {
|
||||
case EventTypeKey:
|
||||
// applicatie verlaten
|
||||
if(event.input.type == InputTypeShort && event.input.key == InputKeyBack) {
|
||||
processing = false;
|
||||
} else if(event.input.type == InputTypeLong && event.input.key == InputKeyBack) {
|
||||
processing = false;
|
||||
} else if(event.input.type == InputTypeLong && event.input.key == InputKeyOk) {
|
||||
// remove accespoint
|
||||
if(ctx->access_points_count > 0) {
|
||||
for(int i = ctx->access_points_index; i < ctx->access_points_count - 1;
|
||||
i++) {
|
||||
ctx->access_points[i] = ctx->access_points[i + 1];
|
||||
}
|
||||
ctx->access_points_count--;
|
||||
if(ctx->access_points_index >= ctx->access_points_count) {
|
||||
ctx->access_points_index = ctx->access_points_count - 1;
|
||||
}
|
||||
}
|
||||
} else if(event.input.type == InputTypePress && event.input.key == InputKeyDown) {
|
||||
ctx->access_points_index--;
|
||||
if(ctx->access_points_index < 0) {
|
||||
ctx->access_points_index = ctx->access_points_count - 1;
|
||||
}
|
||||
ctx->active_access_point = ctx->access_points[ctx->access_points_index];
|
||||
} else if(event.input.type == InputTypePress && event.input.key == InputKeyUp) {
|
||||
ctx->access_points_index++;
|
||||
if(ctx->access_points_index >= ctx->access_points_count) {
|
||||
ctx->access_points_index = 0;
|
||||
}
|
||||
ctx->active_access_point = ctx->access_points[ctx->access_points_index];
|
||||
} else if(event.input.type == InputTypePress && event.input.key == InputKeyLeft) {
|
||||
} else if(event.input.type == InputTypePress && event.input.key == InputKeyRight) {
|
||||
}
|
||||
ctx->pressedButton = true;
|
||||
break;
|
||||
case EventTypeTick:
|
||||
|
||||
// fix for the empty active access point when there was no interaction
|
||||
if(!ctx->pressedButton) {
|
||||
ctx->access_points_index = 0;
|
||||
ctx->active_access_point = ctx->access_points[ctx->access_points_index];
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
} else {
|
||||
processing = false;
|
||||
}
|
||||
} while(processing);
|
||||
|
||||
// save the data to the file
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FuriHalRtcDateTime datetime;
|
||||
furi_hal_rtc_get_datetime(&datetime);
|
||||
|
||||
FuriString* filename = furi_string_alloc();
|
||||
furi_string_printf(
|
||||
filename,
|
||||
"%d_%d_%d_%d_%d_%d.txt",
|
||||
datetime.year,
|
||||
datetime.month,
|
||||
datetime.day,
|
||||
datetime.hour,
|
||||
datetime.minute,
|
||||
datetime.second);
|
||||
|
||||
FuriString* path = furi_string_alloc();
|
||||
furi_string_printf(path, "/ext/apps_data/llsniffer/%s", furi_string_get_cstr(filename));
|
||||
|
||||
// open file
|
||||
ctx->file = storage_file_alloc(storage);
|
||||
|
||||
if(!storage_common_exists(storage, EXT_PATH("apps_data/llsniffer"))) {
|
||||
storage_common_mkdir(storage, EXT_PATH("apps_data/llsniffer"));
|
||||
}
|
||||
|
||||
if(!storage_file_open(ctx->file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
|
||||
FURI_LOG_E(appname, "Failed to open file");
|
||||
}
|
||||
|
||||
for(int i = 0; i < ctx->access_points_count; i++) {
|
||||
AccessPoint ap = ctx->access_points[i];
|
||||
furi_string_printf(
|
||||
ctx->buffer2,
|
||||
"%s,%s,%s,%d,%d,%d,%d,%d,%d,%d,%d,%f,%f\r\n",
|
||||
"Accesspoint",
|
||||
ap.ssid,
|
||||
ap.bssid,
|
||||
ap.rssi,
|
||||
ap.channel,
|
||||
ap.datetime.year,
|
||||
ap.datetime.month,
|
||||
ap.datetime.day,
|
||||
ap.datetime.hour,
|
||||
ap.datetime.minute,
|
||||
ap.datetime.second,
|
||||
(double)ap.latitude,
|
||||
(double)ap.longitude);
|
||||
|
||||
if(!storage_file_write(
|
||||
ctx->file,
|
||||
furi_string_get_cstr(ctx->buffer2),
|
||||
strlen(furi_string_get_cstr(ctx->buffer2)))) {
|
||||
FURI_LOG_E(appname, "Failed to write AP to file");
|
||||
}
|
||||
}
|
||||
|
||||
// free everything
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
furi_timer_free(timer);
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
view_port_free(view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_message_queue_free(ctx->queue);
|
||||
furi_mutex_free(ctx->mutex);
|
||||
|
||||
furi_thread_flags_set(furi_thread_get_id(ctx->thread_esp), WorkerEvtStop);
|
||||
furi_thread_join(ctx->thread_esp);
|
||||
furi_thread_free(ctx->thread_esp);
|
||||
|
||||
if(UART_CH_ESP != UART_CH_GPS) {
|
||||
furi_thread_flags_set(furi_thread_get_id(ctx->thread_gps), WorkerEvtStop);
|
||||
furi_thread_join(ctx->thread_gps);
|
||||
furi_thread_free(ctx->thread_gps);
|
||||
}
|
||||
|
||||
storage_file_close(ctx->file);
|
||||
storage_file_free(ctx->file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
free(ctx);
|
||||
|
||||
furi_hal_light_set(LightBlue, 0);
|
||||
furi_hal_light_set(LightGreen, 0);
|
||||
|
||||
if(UART_CH_ESP == FuriHalUartIdLPUART1) {
|
||||
furi_hal_uart_deinit(UART_CH_ESP);
|
||||
} else if(UART_CH_ESP == FuriHalUartIdUSART1) {
|
||||
furi_hal_console_enable();
|
||||
}
|
||||
|
||||
if(UART_CH_GPS == FuriHalUartIdLPUART1) {
|
||||
furi_hal_uart_deinit(UART_CH_GPS);
|
||||
} else if(UART_CH_GPS == FuriHalUartIdUSART1) {
|
||||
furi_hal_console_enable();
|
||||
}
|
||||
|
||||
if(furi_hal_power_is_otg_enabled()) {
|
||||
furi_hal_power_disable_otg();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -6,6 +6,6 @@ App(
|
||||
stack_size=10 * 1024,
|
||||
fap_category="GPIO",
|
||||
fap_icon="mouse_10px.png",
|
||||
fap_version="0.6",
|
||||
fap_version="0.8",
|
||||
sources=["*.c", "*.cc"],
|
||||
)
|
||||
|
||||
329
applications/external/airmouse/tracking/main_loop.cc
vendored
@@ -12,178 +12,219 @@
|
||||
static const float CURSOR_SPEED = 1024.0 / (M_PI / 4);
|
||||
static const float STABILIZE_BIAS = 16.0;
|
||||
|
||||
float g_yaw = 0;
|
||||
float g_pitch = 0;
|
||||
float g_dYaw = 0;
|
||||
float g_dPitch = 0;
|
||||
bool firstRead = true;
|
||||
bool stabilize = true;
|
||||
CalibrationData calibration;
|
||||
cardboard::OrientationTracker tracker(10000000l); // 10 ms / 100 Hz
|
||||
uint64_t ippms, ippms2;
|
||||
class TrackingState {
|
||||
private:
|
||||
float yaw;
|
||||
float pitch;
|
||||
float dYaw;
|
||||
float dPitch;
|
||||
bool firstRead;
|
||||
bool stabilize;
|
||||
CalibrationData calibration;
|
||||
cardboard::OrientationTracker tracker;
|
||||
uint64_t ippus, ippus2;
|
||||
|
||||
static inline float clamp(float val)
|
||||
{
|
||||
while (val <= -M_PI) {
|
||||
val += 2 * M_PI;
|
||||
}
|
||||
while (val >= M_PI) {
|
||||
val -= 2 * M_PI;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline float highpass(float oldVal, float newVal)
|
||||
{
|
||||
if (!stabilize) {
|
||||
return newVal;
|
||||
}
|
||||
float delta = clamp(oldVal - newVal);
|
||||
float alpha = (float) std::max(0.0, 1 - std::pow(std::fabs(delta) * CURSOR_SPEED / STABILIZE_BIAS, 3.0));
|
||||
return newVal + alpha * delta;
|
||||
}
|
||||
|
||||
void sendCurrentState(MouseMoveCallback mouse_move, void *context)
|
||||
{
|
||||
float dX = g_dYaw * CURSOR_SPEED;
|
||||
float dY = g_dPitch * CURSOR_SPEED;
|
||||
|
||||
// Scale the shift down to fit the protocol.
|
||||
if (dX > 127) {
|
||||
dY *= 127.0 / dX;
|
||||
dX = 127;
|
||||
}
|
||||
if (dX < -127) {
|
||||
dY *= -127.0 / dX;
|
||||
dX = -127;
|
||||
}
|
||||
if (dY > 127) {
|
||||
dX *= 127.0 / dY;
|
||||
dY = 127;
|
||||
}
|
||||
if (dY < -127) {
|
||||
dX *= -127.0 / dY;
|
||||
dY = -127;
|
||||
private:
|
||||
float clamp(float val) {
|
||||
while (val <= -M_PI) {
|
||||
val += 2 * M_PI;
|
||||
}
|
||||
while (val >= M_PI) {
|
||||
val -= 2 * M_PI;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
const int8_t x = (int8_t)std::floor(dX + 0.5);
|
||||
const int8_t y = (int8_t)std::floor(dY + 0.5);
|
||||
|
||||
mouse_move(x, y, context);
|
||||
|
||||
// Only subtract the part of the error that was already sent.
|
||||
if (x != 0) {
|
||||
g_dYaw -= x / CURSOR_SPEED;
|
||||
}
|
||||
if (y != 0) {
|
||||
g_dPitch -= y / CURSOR_SPEED;
|
||||
}
|
||||
}
|
||||
|
||||
void onOrientation(cardboard::Vector4& quaternion)
|
||||
{
|
||||
float q1 = quaternion[0]; // X * sin(T/2)
|
||||
float q2 = quaternion[1]; // Y * sin(T/2)
|
||||
float q3 = quaternion[2]; // Z * sin(T/2)
|
||||
float q0 = quaternion[3]; // cos(T/2)
|
||||
|
||||
float yaw = std::atan2(2 * (q0 * q3 - q1 * q2), (1 - 2 * (q1 * q1 + q3 * q3)));
|
||||
float pitch = std::asin(2 * (q0 * q1 + q2 * q3));
|
||||
// float roll = std::atan2(2 * (q0 * q2 - q1 * q3), (1 - 2 * (q1 * q1 + q2 * q2)));
|
||||
|
||||
if (yaw == NAN || pitch == NAN) {
|
||||
// NaN case, skip it
|
||||
return;
|
||||
float highpass(float oldVal, float newVal) {
|
||||
if (!stabilize) {
|
||||
return newVal;
|
||||
}
|
||||
float delta = clamp(oldVal - newVal);
|
||||
float alpha = (float) std::max(0.0, 1 - std::pow(std::fabs(delta) * CURSOR_SPEED / STABILIZE_BIAS, 3.0));
|
||||
return newVal + alpha * delta;
|
||||
}
|
||||
|
||||
if (firstRead) {
|
||||
g_yaw = yaw;
|
||||
g_pitch = pitch;
|
||||
firstRead = false;
|
||||
} else {
|
||||
const float newYaw = highpass(g_yaw, yaw);
|
||||
const float newPitch = highpass(g_pitch, pitch);
|
||||
void sendCurrentState(MouseMoveCallback mouse_move, void *context) {
|
||||
float dX = dYaw * CURSOR_SPEED;
|
||||
float dY = dPitch * CURSOR_SPEED;
|
||||
|
||||
float dYaw = clamp(g_yaw - newYaw);
|
||||
float dPitch = g_pitch - newPitch;
|
||||
g_yaw = newYaw;
|
||||
g_pitch = newPitch;
|
||||
// Scale the shift down to fit the protocol.
|
||||
if (dX > 127) {
|
||||
dY *= 127.0 / dX;
|
||||
dX = 127;
|
||||
}
|
||||
if (dX < -127) {
|
||||
dY *= -127.0 / dX;
|
||||
dX = -127;
|
||||
}
|
||||
if (dY > 127) {
|
||||
dX *= 127.0 / dY;
|
||||
dY = 127;
|
||||
}
|
||||
if (dY < -127) {
|
||||
dX *= -127.0 / dY;
|
||||
dY = -127;
|
||||
}
|
||||
|
||||
// Accumulate the error locally.
|
||||
g_dYaw += dYaw;
|
||||
g_dPitch += dPitch;
|
||||
const int8_t x = (int8_t)std::floor(dX + 0.5);
|
||||
const int8_t y = (int8_t)std::floor(dY + 0.5);
|
||||
|
||||
mouse_move(x, y, context);
|
||||
|
||||
// Only subtract the part of the error that was already sent.
|
||||
if (x != 0) {
|
||||
dYaw -= x / CURSOR_SPEED;
|
||||
}
|
||||
if (y != 0) {
|
||||
dPitch -= y / CURSOR_SPEED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onOrientation(cardboard::Vector4& quaternion) {
|
||||
float q1 = quaternion[0]; // X * sin(T/2)
|
||||
float q2 = quaternion[1]; // Y * sin(T/2)
|
||||
float q3 = quaternion[2]; // Z * sin(T/2)
|
||||
float q0 = quaternion[3]; // cos(T/2)
|
||||
|
||||
float yaw = std::atan2(2 * (q0 * q3 - q1 * q2), (1 - 2 * (q1 * q1 + q3 * q3)));
|
||||
float pitch = std::asin(2 * (q0 * q1 + q2 * q3));
|
||||
// float roll = std::atan2(2 * (q0 * q2 - q1 * q3), (1 - 2 * (q1 * q1 + q2 * q2)));
|
||||
|
||||
if (yaw == NAN || pitch == NAN) {
|
||||
// NaN case, skip it
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstRead) {
|
||||
this->yaw = yaw;
|
||||
this->pitch = pitch;
|
||||
firstRead = false;
|
||||
} else {
|
||||
const float newYaw = highpass(this->yaw, yaw);
|
||||
const float newPitch = highpass(this->pitch, pitch);
|
||||
|
||||
float dYaw = clamp(this->yaw - newYaw);
|
||||
float dPitch = this->pitch - newPitch;
|
||||
this->yaw = newYaw;
|
||||
this->pitch = newPitch;
|
||||
|
||||
// Accumulate the error locally.
|
||||
this->dYaw += dYaw;
|
||||
this->dPitch += dPitch;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
TrackingState()
|
||||
: yaw(0)
|
||||
, pitch(0)
|
||||
, dYaw(0)
|
||||
, dPitch(0)
|
||||
, firstRead(true)
|
||||
, stabilize(true)
|
||||
, tracker(10000000l) { // 10 ms / 100 Hz
|
||||
ippus = furi_hal_cortex_instructions_per_microsecond();
|
||||
ippus2 = ippus / 2;
|
||||
}
|
||||
|
||||
void beginCalibration() {
|
||||
calibration.reset();
|
||||
}
|
||||
|
||||
bool stepCalibration() {
|
||||
if (calibration.isComplete())
|
||||
return true;
|
||||
|
||||
double vec[6];
|
||||
if (imu_read(vec) & GYR_DATA_READY) {
|
||||
cardboard::Vector3 data(vec[3], vec[4], vec[5]);
|
||||
furi_delay_ms(9); // Artificially limit to ~100Hz
|
||||
return calibration.add(data);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void saveCalibration() {
|
||||
CalibrationMedian store;
|
||||
cardboard::Vector3 median = calibration.getMedian();
|
||||
store.x = median[0];
|
||||
store.y = median[1];
|
||||
store.z = median[2];
|
||||
CALIBRATION_DATA_SAVE(&store);
|
||||
}
|
||||
|
||||
void loadCalibration() {
|
||||
CalibrationMedian store;
|
||||
cardboard::Vector3 median = calibration.getMedian();
|
||||
if (CALIBRATION_DATA_LOAD(&store)) {
|
||||
median[0] = store.x;
|
||||
median[1] = store.y;
|
||||
median[2] = store.z;
|
||||
}
|
||||
|
||||
tracker.SetCalibration(median);
|
||||
}
|
||||
|
||||
void beginTracking() {
|
||||
loadCalibration();
|
||||
tracker.Resume();
|
||||
}
|
||||
|
||||
void stepTracking(MouseMoveCallback mouse_move, void *context) {
|
||||
double vec[6];
|
||||
int ret = imu_read(vec);
|
||||
if (ret != 0) {
|
||||
uint64_t t = (DWT->CYCCNT * 1000llu + ippus2) / ippus;
|
||||
if (ret & ACC_DATA_READY) {
|
||||
cardboard::AccelerometerData adata
|
||||
= { .system_timestamp = t, .sensor_timestamp_ns = t,
|
||||
.data = cardboard::Vector3(vec[0], vec[1], vec[2]) };
|
||||
tracker.OnAccelerometerData(adata);
|
||||
}
|
||||
if (ret & GYR_DATA_READY) {
|
||||
cardboard::GyroscopeData gdata
|
||||
= { .system_timestamp = t, .sensor_timestamp_ns = t,
|
||||
.data = cardboard::Vector3(vec[3], vec[4], vec[5]) };
|
||||
cardboard::Vector4 pose = tracker.OnGyroscopeData(gdata);
|
||||
onOrientation(pose);
|
||||
sendCurrentState(mouse_move, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void stopTracking() {
|
||||
tracker.Pause();
|
||||
}
|
||||
};
|
||||
|
||||
static TrackingState g_state;
|
||||
|
||||
extern "C" {
|
||||
|
||||
void calibration_begin() {
|
||||
calibration.reset();
|
||||
g_state.beginCalibration();
|
||||
FURI_LOG_I(TAG, "Calibrating");
|
||||
}
|
||||
|
||||
bool calibration_step() {
|
||||
if (calibration.isComplete())
|
||||
return true;
|
||||
|
||||
double vec[6];
|
||||
if (imu_read(vec) & GYR_DATA_READY) {
|
||||
cardboard::Vector3 data(vec[3], vec[4], vec[5]);
|
||||
furi_delay_ms(9); // Artificially limit to ~100Hz
|
||||
return calibration.add(data);
|
||||
}
|
||||
|
||||
return false;
|
||||
return g_state.stepCalibration();
|
||||
}
|
||||
|
||||
void calibration_end() {
|
||||
CalibrationMedian store;
|
||||
cardboard::Vector3 median = calibration.getMedian();
|
||||
store.x = median[0];
|
||||
store.y = median[1];
|
||||
store.z = median[2];
|
||||
CALIBRATION_DATA_SAVE(&store);
|
||||
g_state.saveCalibration();
|
||||
}
|
||||
|
||||
void tracking_begin() {
|
||||
CalibrationMedian store;
|
||||
cardboard::Vector3 median = calibration.getMedian();
|
||||
if (CALIBRATION_DATA_LOAD(&store)) {
|
||||
median[0] = store.x;
|
||||
median[1] = store.y;
|
||||
median[2] = store.z;
|
||||
}
|
||||
|
||||
ippms = furi_hal_cortex_instructions_per_microsecond();
|
||||
ippms2 = ippms / 2;
|
||||
tracker.SetCalibration(median);
|
||||
tracker.Resume();
|
||||
g_state.beginTracking();
|
||||
}
|
||||
|
||||
void tracking_step(MouseMoveCallback mouse_move, void *context) {
|
||||
double vec[6];
|
||||
int ret = imu_read(vec);
|
||||
if (ret != 0) {
|
||||
uint64_t t = (DWT->CYCCNT * 1000llu + ippms2) / ippms;
|
||||
if (ret & ACC_DATA_READY) {
|
||||
cardboard::AccelerometerData adata
|
||||
= { .system_timestamp = t, .sensor_timestamp_ns = t,
|
||||
.data = cardboard::Vector3(vec[0], vec[1], vec[2]) };
|
||||
tracker.OnAccelerometerData(adata);
|
||||
}
|
||||
if (ret & GYR_DATA_READY) {
|
||||
cardboard::GyroscopeData gdata
|
||||
= { .system_timestamp = t, .sensor_timestamp_ns = t,
|
||||
.data = cardboard::Vector3(vec[3], vec[4], vec[5]) };
|
||||
cardboard::Vector4 pose = tracker.OnGyroscopeData(gdata);
|
||||
onOrientation(pose);
|
||||
sendCurrentState(mouse_move, context);
|
||||
}
|
||||
}
|
||||
g_state.stepTracking(mouse_move, context);
|
||||
}
|
||||
|
||||
void tracking_end() {
|
||||
tracker.Pause();
|
||||
g_state.stopTracking();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ Vector4 OrientationTracker::OnGyroscopeData(const GyroscopeData& event)
|
||||
|
||||
sensor_fusion_->ProcessGyroscopeSample(data);
|
||||
|
||||
return OrientationTracker::GetPose(data.sensor_timestamp_ns + sampling_period_ns_);
|
||||
return GetPose(data.sensor_timestamp_ns + sampling_period_ns_);
|
||||
}
|
||||
|
||||
} // namespace cardboard
|
||||
|
||||
@@ -59,13 +59,14 @@ namespace {
|
||||
// angle = norm(a)
|
||||
// axis = a.normalized()
|
||||
// If norm(a) == 0, it returns an identity rotation.
|
||||
static inline Rotation RotationFromVector(const Vector3& a)
|
||||
static inline void RotationFromVector(const Vector3& a, Rotation& r)
|
||||
{
|
||||
const double norm_a = Length(a);
|
||||
if (norm_a < kEpsilon) {
|
||||
return Rotation::Identity();
|
||||
r = Rotation::Identity();
|
||||
return;
|
||||
}
|
||||
return Rotation::FromAxisAndAngle(a / norm_a, norm_a);
|
||||
r = Rotation::FromAxisAndAngle(a / norm_a, norm_a);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -199,7 +200,8 @@ void SensorFusionEkf::ComputeMeasurementJacobian()
|
||||
Vector3 delta = Vector3::Zero();
|
||||
delta[dof] = kFiniteDifferencingEpsilon;
|
||||
|
||||
const Rotation epsilon_rotation = RotationFromVector(delta);
|
||||
Rotation epsilon_rotation;
|
||||
RotationFromVector(delta, epsilon_rotation);
|
||||
const Vector3 delta_rotation
|
||||
= ComputeInnovation(epsilon_rotation * current_state_.sensor_from_start_rotation);
|
||||
|
||||
@@ -263,7 +265,8 @@ void SensorFusionEkf::ProcessAccelerometerSample(const AccelerometerData& sample
|
||||
* state_covariance_;
|
||||
|
||||
// Updates pose and associate covariance matrix.
|
||||
const Rotation rotation_from_state_update = RotationFromVector(state_update_);
|
||||
Rotation rotation_from_state_update;
|
||||
RotationFromVector(state_update_, rotation_from_state_update);
|
||||
|
||||
current_state_.sensor_from_start_rotation
|
||||
= rotation_from_state_update * current_state_.sensor_from_start_rotation;
|
||||
|
||||
94
applications/external/airmouse/views/bt_mouse.c
vendored
@@ -132,6 +132,11 @@ void bt_mouse_connection_status_changed_callback(BtStatus status, void* context)
|
||||
BtMouse* bt_mouse = context;
|
||||
|
||||
bt_mouse->connected = (status == BtStatusConnected);
|
||||
if(!bt_mouse->notifications) {
|
||||
tracking_end();
|
||||
return;
|
||||
}
|
||||
|
||||
if(bt_mouse->connected) {
|
||||
notification_internal_message(bt_mouse->notifications, &sequence_set_blue_255);
|
||||
tracking_begin();
|
||||
@@ -140,9 +145,6 @@ void bt_mouse_connection_status_changed_callback(BtStatus status, void* context)
|
||||
tracking_end();
|
||||
notification_internal_message(bt_mouse->notifications, &sequence_reset_blue);
|
||||
}
|
||||
|
||||
//with_view_model(
|
||||
// bt_mouse->view, void * model, { model->connected = connected; }, true);
|
||||
}
|
||||
|
||||
bool bt_mouse_move(int8_t dx, int8_t dy, void* context) {
|
||||
@@ -160,46 +162,6 @@ bool bt_mouse_move(int8_t dx, int8_t dy, void* context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void bt_mouse_enter_callback(void* context) {
|
||||
furi_assert(context);
|
||||
BtMouse* bt_mouse = context;
|
||||
|
||||
bt_mouse->bt = furi_record_open(RECORD_BT);
|
||||
bt_mouse->notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||
bt_set_status_changed_callback(
|
||||
bt_mouse->bt, bt_mouse_connection_status_changed_callback, bt_mouse);
|
||||
furi_assert(bt_set_profile(bt_mouse->bt, BtProfileHidKeyboard));
|
||||
furi_hal_bt_start_advertising();
|
||||
}
|
||||
|
||||
bool bt_mouse_custom_callback(uint32_t event, void* context) {
|
||||
UNUSED(event);
|
||||
furi_assert(context);
|
||||
BtMouse* bt_mouse = context;
|
||||
|
||||
tracking_step(bt_mouse_move, context);
|
||||
furi_delay_ms(3); // Magic! Removing this will break the buttons
|
||||
|
||||
view_dispatcher_send_custom_event(bt_mouse->view_dispatcher, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void bt_mouse_exit_callback(void* context) {
|
||||
furi_assert(context);
|
||||
BtMouse* bt_mouse = context;
|
||||
|
||||
tracking_end();
|
||||
notification_internal_message(bt_mouse->notifications, &sequence_reset_blue);
|
||||
|
||||
furi_hal_bt_stop_advertising();
|
||||
bt_set_profile(bt_mouse->bt, BtProfileSerial);
|
||||
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
bt_mouse->notifications = NULL;
|
||||
furi_record_close(RECORD_BT);
|
||||
bt_mouse->bt = NULL;
|
||||
}
|
||||
|
||||
static int8_t clamp(int t) {
|
||||
if(t < -128) {
|
||||
return -128;
|
||||
@@ -279,6 +241,50 @@ void bt_mouse_thread_stop(BtMouse* bt_mouse) {
|
||||
furi_thread_join(bt_mouse->thread);
|
||||
furi_thread_free(bt_mouse->thread);
|
||||
furi_mutex_free(bt_mouse->mutex);
|
||||
bt_mouse->mutex = NULL;
|
||||
bt_mouse->thread = NULL;
|
||||
}
|
||||
|
||||
void bt_mouse_enter_callback(void* context) {
|
||||
furi_assert(context);
|
||||
BtMouse* bt_mouse = context;
|
||||
|
||||
bt_mouse->bt = furi_record_open(RECORD_BT);
|
||||
bt_mouse->notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||
bt_set_status_changed_callback(
|
||||
bt_mouse->bt, bt_mouse_connection_status_changed_callback, bt_mouse);
|
||||
furi_assert(bt_set_profile(bt_mouse->bt, BtProfileHidKeyboard));
|
||||
furi_hal_bt_start_advertising();
|
||||
bt_mouse_thread_start(bt_mouse);
|
||||
}
|
||||
|
||||
bool bt_mouse_custom_callback(uint32_t event, void* context) {
|
||||
UNUSED(event);
|
||||
furi_assert(context);
|
||||
BtMouse* bt_mouse = context;
|
||||
|
||||
tracking_step(bt_mouse_move, context);
|
||||
furi_delay_ms(3); // Magic! Removing this will break the buttons
|
||||
|
||||
view_dispatcher_send_custom_event(bt_mouse->view_dispatcher, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void bt_mouse_exit_callback(void* context) {
|
||||
furi_assert(context);
|
||||
BtMouse* bt_mouse = context;
|
||||
|
||||
bt_mouse_thread_stop(bt_mouse);
|
||||
tracking_end();
|
||||
notification_internal_message(bt_mouse->notifications, &sequence_reset_blue);
|
||||
|
||||
furi_hal_bt_stop_advertising();
|
||||
bt_set_profile(bt_mouse->bt, BtProfileSerial);
|
||||
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
bt_mouse->notifications = NULL;
|
||||
furi_record_close(RECORD_BT);
|
||||
bt_mouse->bt = NULL;
|
||||
}
|
||||
|
||||
BtMouse* bt_mouse_alloc(ViewDispatcher* view_dispatcher) {
|
||||
@@ -293,13 +299,11 @@ BtMouse* bt_mouse_alloc(ViewDispatcher* view_dispatcher) {
|
||||
view_set_enter_callback(bt_mouse->view, bt_mouse_enter_callback);
|
||||
view_set_custom_callback(bt_mouse->view, bt_mouse_custom_callback);
|
||||
view_set_exit_callback(bt_mouse->view, bt_mouse_exit_callback);
|
||||
bt_mouse_thread_start(bt_mouse);
|
||||
return bt_mouse;
|
||||
}
|
||||
|
||||
void bt_mouse_free(BtMouse* bt_mouse) {
|
||||
furi_assert(bt_mouse);
|
||||
bt_mouse_thread_stop(bt_mouse);
|
||||
view_free(bt_mouse->view);
|
||||
free(bt_mouse);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ App(
|
||||
entry_point="arkanoid_game_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=20,
|
||||
fap_icon="arkanoid_10px.png",
|
||||
fap_category="Games",
|
||||
fap_author="@xMasterX & @gotnull",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <gui/view.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
#define TAG "Arkanoid"
|
||||
|
||||
@@ -398,7 +399,7 @@ int32_t arkanoid_game_app(void* p) {
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
// Call dolphin deed on game start
|
||||
// dolphin_deed(DolphinDeedPluginGameStart);
|
||||
dolphin_deed(DolphinDeedPluginGameStart);
|
||||
|
||||
GameEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
|
||||
@@ -6,7 +6,6 @@ App(
|
||||
cdefines=["APP_ASTEROIDS"],
|
||||
requires=["gui"],
|
||||
stack_size=8 * 1024,
|
||||
order=50,
|
||||
fap_icon="appicon.png",
|
||||
fap_icon_assets="assets",
|
||||
fap_category="Games",
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
App(
|
||||
appid="avr_isp",
|
||||
name="[AVR] Flasher",
|
||||
name="[AVR] AVR Flasher",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="avr_isp_app",
|
||||
requires=["gui"],
|
||||
stack_size=4 * 1024,
|
||||
order=20,
|
||||
fap_description="Application for flashing AVR microcontrollers",
|
||||
fap_version="1.0",
|
||||
fap_icon="avr_app_icon_10x10.png",
|
||||
fap_category="GPIO",
|
||||
fap_icon_assets="images",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "helpers/avr_isp_types.h"
|
||||
#include <avr_isp_icons.h>
|
||||
#include "avr_isp_icons.h"
|
||||
#include <assets_icons.h>
|
||||
|
||||
#include "scenes/avr_isp_scene.h"
|
||||
#include <gui/gui.h>
|
||||
@@ -41,4 +42,4 @@ typedef struct {
|
||||
AvrIspError error;
|
||||
} AvrIspApp;
|
||||
|
||||
bool avr_isp_load_from_file(AvrIspApp* app);
|
||||
bool avr_isp_load_from_file(AvrIspApp* app);
|
||||
@@ -174,7 +174,7 @@ static void avr_isp_commit(AvrIsp* instance, uint16_t addr, uint8_t data) {
|
||||
while((furi_get_tick() - starttime) < 30) {
|
||||
if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr)) != 0xFF) {
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,7 +357,7 @@ uint8_t avr_isp_read_lock_byte(AvrIsp* instance) {
|
||||
data = avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE);
|
||||
if(avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE) == data) {
|
||||
break;
|
||||
}
|
||||
};
|
||||
data = 0x00;
|
||||
}
|
||||
return data;
|
||||
@@ -377,7 +377,7 @@ bool avr_isp_write_lock_byte(AvrIsp* instance, uint8_t lock) {
|
||||
if(avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE) == lock) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
@@ -392,7 +392,7 @@ uint8_t avr_isp_read_fuse_low(AvrIsp* instance) {
|
||||
data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW);
|
||||
if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW) == data) {
|
||||
break;
|
||||
}
|
||||
};
|
||||
data = 0x00;
|
||||
}
|
||||
return data;
|
||||
@@ -412,7 +412,7 @@ bool avr_isp_write_fuse_low(AvrIsp* instance, uint8_t lfuse) {
|
||||
if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW) == lfuse) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
@@ -427,7 +427,7 @@ uint8_t avr_isp_read_fuse_high(AvrIsp* instance) {
|
||||
data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH);
|
||||
if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH) == data) {
|
||||
break;
|
||||
}
|
||||
};
|
||||
data = 0x00;
|
||||
}
|
||||
return data;
|
||||
@@ -447,7 +447,7 @@ bool avr_isp_write_fuse_high(AvrIsp* instance, uint8_t hfuse) {
|
||||
if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH) == hfuse) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
@@ -462,7 +462,7 @@ uint8_t avr_isp_read_fuse_extended(AvrIsp* instance) {
|
||||
data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED);
|
||||
if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED) == data) {
|
||||
break;
|
||||
}
|
||||
};
|
||||
data = 0x00;
|
||||
}
|
||||
return data;
|
||||
@@ -482,7 +482,7 @@ bool avr_isp_write_fuse_extended(AvrIsp* instance, uint8_t efuse) {
|
||||
if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED) == efuse) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
|
||||
@@ -350,12 +350,12 @@ static void avr_isp_worker_rw_get_dump_flash(AvrIspWorkerRW* instance, const cha
|
||||
sizeof(data));
|
||||
flipper_i32hex_file_bin_to_i32hex_set_data(
|
||||
flipper_hex_flash, data, avr_isp_chip_arr[instance->chip_arr_ind].pagesize);
|
||||
//FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_flash));
|
||||
FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_flash));
|
||||
instance->progress_flash =
|
||||
(float)(i) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2.0f);
|
||||
}
|
||||
flipper_i32hex_file_bin_to_i32hex_set_end_line(flipper_hex_flash);
|
||||
//FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_flash));
|
||||
FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_flash));
|
||||
flipper_i32hex_file_close(flipper_hex_flash);
|
||||
instance->progress_flash = 1.0f;
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ static uint8_t avr_isp_prog_getch(AvrIspProg* instance) {
|
||||
uint8_t data[1] = {0};
|
||||
while(furi_stream_buffer_receive(instance->stream_rx, &data, sizeof(int8_t), 30) == 0) {
|
||||
if(instance->exit) break;
|
||||
}
|
||||
};
|
||||
return data[0];
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ static void avr_isp_prog_set_cfg(AvrIspProg* instance) {
|
||||
instance->cfg->lockbytes = instance->buff[6];
|
||||
instance->cfg->fusebytes = instance->buff[7];
|
||||
instance->cfg->flashpoll = instance->buff[8];
|
||||
// ignore (instance->buff[9] == instance->buff[8]) //FLASH polling value. Same as <EFBFBD>flashpoll<EFBFBD>
|
||||
// ignore (instance->buff[9] == instance->buff[8]) //FLASH polling value. Same as “flashpoll”
|
||||
instance->cfg->eeprompoll = instance->buff[10] << 8 | instance->buff[11];
|
||||
instance->cfg->pagesize = instance->buff[12] << 8 | instance->buff[13];
|
||||
instance->cfg->eepromsize = instance->buff[14] << 8 | instance->buff[15];
|
||||
@@ -348,7 +348,7 @@ static void avr_isp_prog_commit(AvrIspProg* instance, uint16_t addr, uint8_t dat
|
||||
while((furi_get_tick() - starttime) < 30) {
|
||||
if(avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr)) != 0xFF) {
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "avr_isp_view_chip_detect.h"
|
||||
#include <avr_isp_icons.h>
|
||||
#include "avr_isp_icons.h"
|
||||
#include <assets_icons.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
#include "../helpers/avr_isp_worker_rw.h"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "avr_isp_view_programmer.h"
|
||||
#include <avr_isp_icons.h>
|
||||
#include "avr_isp_icons.h"
|
||||
#include <assets_icons.h>
|
||||
|
||||
#include "../helpers/avr_isp_worker.h"
|
||||
#include <gui/elements.h>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "barcode_app.h"
|
||||
|
||||
#include "barcode_app_icons.h"
|
||||
#include <assets_icons.h>
|
||||
|
||||
/**
|
||||
* Opens a file browser dialog and returns the filepath of the selected file
|
||||
|
||||
@@ -5,7 +5,6 @@ App(
|
||||
entry_point="blackjack_app",
|
||||
requires=["gui", "storage", "canvas"],
|
||||
stack_size=2 * 1024,
|
||||
order=30,
|
||||
fap_icon="blackjack_10px.png",
|
||||
fap_category="Games",
|
||||
fap_icon_assets="assets",
|
||||
|
||||
5
applications/external/blackjack/blackjack.c
vendored
@@ -15,6 +15,7 @@
|
||||
#include "ui.h"
|
||||
|
||||
#include "blackjack_icons.h"
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define DEALER_MAX 17
|
||||
|
||||
@@ -276,7 +277,7 @@ void dealer_tick(GameState* game_state) {
|
||||
|
||||
if(dealer_score >= DEALER_MAX) {
|
||||
if(dealer_score > 21 || dealer_score < player_score) {
|
||||
// dolphin_deed(DolphinDeedPluginGameWin);
|
||||
dolphin_deed(DolphinDeedPluginGameWin);
|
||||
enqueue(
|
||||
&(game_state->queue_state),
|
||||
game_state,
|
||||
@@ -571,7 +572,7 @@ int32_t blackjack_app(void* p) {
|
||||
AppEvent event;
|
||||
|
||||
// Call dolphin deed on game start
|
||||
// dolphin_deed(DolphinDeedPluginGameStart);
|
||||
dolphin_deed(DolphinDeedPluginGameStart);
|
||||
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
|
||||
@@ -7,7 +7,6 @@ App(
|
||||
"gui",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=90,
|
||||
fap_icon="bomb.png",
|
||||
fap_category="Games",
|
||||
fap_icon_assets="assets",
|
||||
|
||||
10
applications/external/bomberduck/bomberduck.c
vendored
@@ -6,6 +6,8 @@
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include "bomberduck_icons.h"
|
||||
#include <assets_icons.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
int max(int a, int b) {
|
||||
return (a > b) ? a : b;
|
||||
@@ -381,7 +383,7 @@ int32_t bomberduck_app(void* p) {
|
||||
return 255;
|
||||
}
|
||||
|
||||
// dolphin_deed(DolphinDeedPluginGameStart);
|
||||
dolphin_deed(DolphinDeedPluginGameStart);
|
||||
// Создаем новый view port
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
// Создаем callback отрисовки, без контекста
|
||||
@@ -455,9 +457,9 @@ int32_t bomberduck_app(void* p) {
|
||||
notification_message(notification, &end);
|
||||
world.running = 0;
|
||||
world.level += 1;
|
||||
// if(world.level % 5 == 0) {
|
||||
// dolphin_deed(DolphinDeedPluginGameWin);
|
||||
// }
|
||||
if(world.level % 5 == 0) {
|
||||
dolphin_deed(DolphinDeedPluginGameWin);
|
||||
}
|
||||
}
|
||||
for(int i = 0; i < world.bombs_count; i++) {
|
||||
if(furi_get_tick() - world.bombs[i].planted >
|
||||
|
||||
@@ -9,7 +9,6 @@ App(
|
||||
fap_icon="bpm_10px.png",
|
||||
fap_category="Media",
|
||||
fap_icon_assets="icons",
|
||||
order=15,
|
||||
fap_author="@panki27",
|
||||
fap_weburl="https://github.com/panki27/bpm-tapper",
|
||||
fap_version="1.0",
|
||||
|
||||
@@ -10,7 +10,6 @@ App(
|
||||
stack_size=2 * 1024,
|
||||
fap_icon="caesar_cipher_icon.png",
|
||||
fap_category="Tools",
|
||||
order=20,
|
||||
fap_author="@panki27",
|
||||
fap_weburl="https://github.com/panki27/caesar-cipher",
|
||||
fap_version="1.0",
|
||||
|
||||
@@ -6,7 +6,6 @@ App(
|
||||
cdefines=["APP_CALCULATOR"],
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=45,
|
||||
fap_icon="calcIcon.png",
|
||||
fap_category="Tools",
|
||||
fap_author="@n-o-T-I-n-s-a-n-e",
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
App(
|
||||
appid="camerasuite",
|
||||
appid="camera_suite",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
cdefines=["APP_CAMERA_SUITE"],
|
||||
entry_point="camera_suite_app",
|
||||
fap_author="Cody Tolene",
|
||||
fap_author="@CodyTolene @Z4urce @leedave",
|
||||
fap_category="GPIO",
|
||||
fap_description="A camera suite application for the Flipper Zero ESP32-CAM module.",
|
||||
fap_icon="icons/camera_suite.png",
|
||||
fap_weburl="https://github.com/CodyTolene/Flipper-Zero-Cam",
|
||||
name="[ESP32] Camera Suite",
|
||||
order=1,
|
||||
requires=["gui", "storage"],
|
||||
stack_size=8 * 1024,
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ void camera_suite_tick_event_callback(void* context) {
|
||||
scene_manager_handle_tick_event(app->scene_manager);
|
||||
}
|
||||
|
||||
//leave app if back button pressed
|
||||
// Leave app if back button pressed.
|
||||
bool camera_suite_navigation_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
CameraSuite* app = context;
|
||||
@@ -25,10 +25,10 @@ CameraSuite* camera_suite_app_alloc() {
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
//Turn backlight on, believe me this makes testing your app easier
|
||||
// Turn backlight on.
|
||||
notification_message(app->notification, &sequence_display_backlight_on);
|
||||
|
||||
//Scene additions
|
||||
// Scene additions
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
|
||||
@@ -60,17 +60,11 @@ CameraSuite* camera_suite_app_alloc() {
|
||||
CameraSuiteViewIdStartscreen,
|
||||
camera_suite_view_start_get_view(app->camera_suite_view_start));
|
||||
|
||||
app->camera_suite_view_style_1 = camera_suite_view_style_1_alloc();
|
||||
app->camera_suite_view_camera = camera_suite_view_camera_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
CameraSuiteViewIdScene1,
|
||||
camera_suite_view_style_1_get_view(app->camera_suite_view_style_1));
|
||||
|
||||
app->camera_suite_view_style_2 = camera_suite_view_style_2_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
CameraSuiteViewIdScene2,
|
||||
camera_suite_view_style_2_get_view(app->camera_suite_view_style_2));
|
||||
CameraSuiteViewIdCamera,
|
||||
camera_suite_view_camera_get_view(app->camera_suite_view_camera));
|
||||
|
||||
app->camera_suite_view_guide = camera_suite_view_guide_alloc();
|
||||
view_dispatcher_add_view(
|
||||
@@ -98,9 +92,9 @@ void camera_suite_app_free(CameraSuite* app) {
|
||||
scene_manager_free(app->scene_manager);
|
||||
|
||||
// View Dispatcher
|
||||
view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdStartscreen);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdMenu);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdScene1);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdScene2);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdCamera);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdGuide);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdSettings);
|
||||
submenu_free(app->submenu);
|
||||
@@ -110,8 +104,7 @@ void camera_suite_app_free(CameraSuite* app) {
|
||||
|
||||
// Free remaining resources
|
||||
camera_suite_view_start_free(app->camera_suite_view_start);
|
||||
camera_suite_view_style_1_free(app->camera_suite_view_style_1);
|
||||
camera_suite_view_style_2_free(app->camera_suite_view_style_2);
|
||||
camera_suite_view_camera_free(app->camera_suite_view_camera);
|
||||
camera_suite_view_guide_free(app->camera_suite_view_guide);
|
||||
button_menu_free(app->button_menu);
|
||||
variable_item_list_free(app->variable_item_list);
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
#include "scenes/camera_suite_scene.h"
|
||||
#include "views/camera_suite_view_guide.h"
|
||||
#include "views/camera_suite_view_start.h"
|
||||
#include "views/camera_suite_view_style_1.h"
|
||||
#include "views/camera_suite_view_style_2.h"
|
||||
#include <assets_icons.h>
|
||||
#include "views/camera_suite_view_camera.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/gui.h>
|
||||
@@ -29,8 +27,7 @@ typedef struct {
|
||||
SceneManager* scene_manager;
|
||||
VariableItemList* variable_item_list;
|
||||
CameraSuiteViewStart* camera_suite_view_start;
|
||||
CameraSuiteViewStyle1* camera_suite_view_style_1;
|
||||
CameraSuiteViewStyle2* camera_suite_view_style_2;
|
||||
CameraSuiteViewCamera* camera_suite_view_camera;
|
||||
CameraSuiteViewGuide* camera_suite_view_guide;
|
||||
uint32_t orientation;
|
||||
uint32_t haptic;
|
||||
@@ -42,8 +39,7 @@ typedef struct {
|
||||
typedef enum {
|
||||
CameraSuiteViewIdStartscreen,
|
||||
CameraSuiteViewIdMenu,
|
||||
CameraSuiteViewIdScene1,
|
||||
CameraSuiteViewIdScene2,
|
||||
CameraSuiteViewIdCamera,
|
||||
CameraSuiteViewIdGuide,
|
||||
CameraSuiteViewIdSettings,
|
||||
} CameraSuiteViewId;
|
||||
|
||||
@@ -8,20 +8,13 @@ typedef enum {
|
||||
CameraSuiteCustomEventStartRight,
|
||||
CameraSuiteCustomEventStartOk,
|
||||
CameraSuiteCustomEventStartBack,
|
||||
// Scene events: Camera style 1
|
||||
CameraSuiteCustomEventSceneStyle1Up,
|
||||
CameraSuiteCustomEventSceneStyle1Down,
|
||||
CameraSuiteCustomEventSceneStyle1Left,
|
||||
CameraSuiteCustomEventSceneStyle1Right,
|
||||
CameraSuiteCustomEventSceneStyle1Ok,
|
||||
CameraSuiteCustomEventSceneStyle1Back,
|
||||
// Scene events: Camera style 2
|
||||
CameraSuiteCustomEventSceneStyle2Up,
|
||||
CameraSuiteCustomEventSceneStyle2Down,
|
||||
CameraSuiteCustomEventSceneStyle2Left,
|
||||
CameraSuiteCustomEventSceneStyle2Right,
|
||||
CameraSuiteCustomEventSceneStyle2Ok,
|
||||
CameraSuiteCustomEventSceneStyle2Back,
|
||||
// Scene events: Camera
|
||||
CameraSuiteCustomEventSceneCameraUp,
|
||||
CameraSuiteCustomEventSceneCameraDown,
|
||||
CameraSuiteCustomEventSceneCameraLeft,
|
||||
CameraSuiteCustomEventSceneCameraRight,
|
||||
CameraSuiteCustomEventSceneCameraOk,
|
||||
CameraSuiteCustomEventSceneCameraBack,
|
||||
// Scene events: Guide
|
||||
CameraSuiteCustomEventSceneGuideUp,
|
||||
CameraSuiteCustomEventSceneGuideDown,
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
#include "../camera_suite.h"
|
||||
#include "../helpers/camera_suite_custom_event.h"
|
||||
#include "../views/camera_suite_view_style_1.h"
|
||||
#include "../views/camera_suite_view_camera.h"
|
||||
|
||||
static void camera_suite_view_style_1_callback(CameraSuiteCustomEvent event, void* context) {
|
||||
void camera_suite_view_camera_callback(CameraSuiteCustomEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
CameraSuite* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, event);
|
||||
}
|
||||
|
||||
void camera_suite_scene_style_1_on_enter(void* context) {
|
||||
void camera_suite_scene_camera_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
CameraSuite* app = context;
|
||||
camera_suite_view_style_1_set_callback(
|
||||
app->camera_suite_view_style_1, camera_suite_view_style_1_callback, app);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, CameraSuiteViewIdScene1);
|
||||
camera_suite_view_camera_set_callback(
|
||||
app->camera_suite_view_camera, camera_suite_view_camera_callback, app);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, CameraSuiteViewIdCamera);
|
||||
}
|
||||
|
||||
bool camera_suite_scene_style_1_on_event(void* context, SceneManagerEvent event) {
|
||||
bool camera_suite_scene_camera_on_event(void* context, SceneManagerEvent event) {
|
||||
CameraSuite* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case CameraSuiteCustomEventSceneStyle1Left:
|
||||
case CameraSuiteCustomEventSceneStyle1Right:
|
||||
case CameraSuiteCustomEventSceneStyle1Up:
|
||||
case CameraSuiteCustomEventSceneStyle1Down:
|
||||
case CameraSuiteCustomEventSceneStyle1Ok:
|
||||
case CameraSuiteCustomEventSceneCameraLeft:
|
||||
case CameraSuiteCustomEventSceneCameraRight:
|
||||
case CameraSuiteCustomEventSceneCameraUp:
|
||||
case CameraSuiteCustomEventSceneCameraDown:
|
||||
case CameraSuiteCustomEventSceneCameraOk:
|
||||
// Do nothing.
|
||||
break;
|
||||
case CameraSuiteCustomEventSceneStyle1Back:
|
||||
case CameraSuiteCustomEventSceneCameraBack:
|
||||
notification_message(app->notification, &sequence_reset_red);
|
||||
notification_message(app->notification, &sequence_reset_green);
|
||||
notification_message(app->notification, &sequence_reset_blue);
|
||||
@@ -46,7 +46,7 @@ bool camera_suite_scene_style_1_on_event(void* context, SceneManagerEvent event)
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void camera_suite_scene_style_1_on_exit(void* context) {
|
||||
void camera_suite_scene_camera_on_exit(void* context) {
|
||||
CameraSuite* app = context;
|
||||
UNUSED(app);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
ADD_SCENE(camera_suite, start, Start)
|
||||
ADD_SCENE(camera_suite, menu, Menu)
|
||||
ADD_SCENE(camera_suite, style_1, Style_1)
|
||||
ADD_SCENE(camera_suite, style_2, Style_2)
|
||||
ADD_SCENE(camera_suite, camera, Camera)
|
||||
ADD_SCENE(camera_suite, guide, Guide)
|
||||
ADD_SCENE(camera_suite, settings, Settings)
|
||||
@@ -1,10 +1,8 @@
|
||||
#include "../camera_suite.h"
|
||||
|
||||
enum SubmenuIndex {
|
||||
/** Atkinson Dithering Algorithm. */
|
||||
SubmenuIndexSceneStyle1 = 10,
|
||||
/** Floyd-Steinberg Dithering Algorithm. */
|
||||
SubmenuIndexSceneStyle2,
|
||||
/** Camera. */
|
||||
SubmenuIndexSceneCamera = 10,
|
||||
/** Guide/how-to. */
|
||||
SubmenuIndexGuide,
|
||||
/** Settings menu. */
|
||||
@@ -22,16 +20,9 @@ void camera_suite_scene_menu_on_enter(void* context) {
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Open Camera",
|
||||
SubmenuIndexSceneStyle1,
|
||||
SubmenuIndexSceneCamera,
|
||||
camera_suite_scene_menu_submenu_callback,
|
||||
app);
|
||||
// Staged view for the future.
|
||||
// submenu_add_item(
|
||||
// app->submenu,
|
||||
// "Test",
|
||||
// SubmenuIndexSceneStyle2,
|
||||
// camera_suite_scene_menu_submenu_callback,
|
||||
// app);
|
||||
submenu_add_item(
|
||||
app->submenu, "Guide", SubmenuIndexGuide, camera_suite_scene_menu_submenu_callback, app);
|
||||
submenu_add_item(
|
||||
@@ -56,15 +47,10 @@ bool camera_suite_scene_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
return true;
|
||||
} else if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexSceneStyle1) {
|
||||
if(event.event == SubmenuIndexSceneCamera) {
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, CameraSuiteSceneMenu, SubmenuIndexSceneStyle1);
|
||||
scene_manager_next_scene(app->scene_manager, CameraSuiteSceneStyle_1);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexSceneStyle2) {
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, CameraSuiteSceneMenu, SubmenuIndexSceneStyle2);
|
||||
scene_manager_next_scene(app->scene_manager, CameraSuiteSceneStyle_2);
|
||||
app->scene_manager, CameraSuiteSceneMenu, SubmenuIndexSceneCamera);
|
||||
scene_manager_next_scene(app->scene_manager, CameraSuiteSceneCamera);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexGuide) {
|
||||
scene_manager_set_scene_state(
|
||||
|
||||
@@ -24,9 +24,9 @@ bool camera_suite_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
switch(event.event) {
|
||||
case CameraSuiteCustomEventStartLeft:
|
||||
case CameraSuiteCustomEventStartRight:
|
||||
break;
|
||||
case CameraSuiteCustomEventStartUp:
|
||||
case CameraSuiteCustomEventStartDown:
|
||||
// Do nothing.
|
||||
break;
|
||||
case CameraSuiteCustomEventStartOk:
|
||||
scene_manager_next_scene(app->scene_manager, CameraSuiteSceneMenu);
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
#include "../camera_suite.h"
|
||||
#include "../helpers/camera_suite_custom_event.h"
|
||||
#include "../helpers/camera_suite_haptic.h"
|
||||
#include "../helpers/camera_suite_led.h"
|
||||
#include "../views/camera_suite_view_style_2.h"
|
||||
|
||||
void camera_suite_view_style_2_callback(CameraSuiteCustomEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
CameraSuite* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, event);
|
||||
}
|
||||
|
||||
void camera_suite_scene_style_2_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
CameraSuite* app = context;
|
||||
camera_suite_view_style_2_set_callback(
|
||||
app->camera_suite_view_style_2, camera_suite_view_style_2_callback, app);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, CameraSuiteViewIdScene2);
|
||||
}
|
||||
|
||||
bool camera_suite_scene_style_2_on_event(void* context, SceneManagerEvent event) {
|
||||
CameraSuite* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case CameraSuiteCustomEventSceneStyle2Left:
|
||||
case CameraSuiteCustomEventSceneStyle2Right:
|
||||
case CameraSuiteCustomEventSceneStyle2Up:
|
||||
case CameraSuiteCustomEventSceneStyle2Down:
|
||||
case CameraSuiteCustomEventSceneStyle2Ok:
|
||||
// Do nothing.
|
||||
break;
|
||||
case CameraSuiteCustomEventSceneStyle2Back:
|
||||
notification_message(app->notification, &sequence_reset_red);
|
||||
notification_message(app->notification, &sequence_reset_green);
|
||||
notification_message(app->notification, &sequence_reset_blue);
|
||||
if(!scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, CameraSuiteSceneMenu)) {
|
||||
scene_manager_stop(app->scene_manager);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
}
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void camera_suite_scene_style_2_on_exit(void* context) {
|
||||
CameraSuite* app = context;
|
||||
UNUSED(app);
|
||||
}
|
||||
@@ -8,23 +8,19 @@
|
||||
#include "../helpers/camera_suite_speaker.h"
|
||||
#include "../helpers/camera_suite_led.h"
|
||||
|
||||
static CameraSuiteViewStyle1* current_instance = NULL;
|
||||
// Dithering type:
|
||||
// 0 = Floyd Steinberg (default)
|
||||
// 1 = Atkinson
|
||||
static int current_dithering = 0;
|
||||
static CameraSuiteViewCamera* current_instance = NULL;
|
||||
|
||||
struct CameraSuiteViewStyle1 {
|
||||
CameraSuiteViewStyle1Callback callback;
|
||||
struct CameraSuiteViewCamera {
|
||||
CameraSuiteViewCameraCallback callback;
|
||||
FuriStreamBuffer* rx_stream;
|
||||
FuriThread* worker_thread;
|
||||
View* view;
|
||||
void* context;
|
||||
};
|
||||
|
||||
void camera_suite_view_style_1_set_callback(
|
||||
CameraSuiteViewStyle1* instance,
|
||||
CameraSuiteViewStyle1Callback callback,
|
||||
void camera_suite_view_camera_set_callback(
|
||||
CameraSuiteViewCamera* instance,
|
||||
CameraSuiteViewCameraCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
@@ -32,7 +28,29 @@ void camera_suite_view_style_1_set_callback(
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
static void camera_suite_view_style_1_draw(Canvas* canvas, UartDumpModel* model) {
|
||||
// Function to draw pixels on the canvas based on camera orientation
|
||||
static void draw_pixel_by_orientation(Canvas* canvas, uint8_t x, uint8_t y, uint8_t orientation) {
|
||||
switch(orientation) {
|
||||
case 0: // Camera rotated 0 degrees (right side up, default)
|
||||
canvas_draw_dot(canvas, x, y);
|
||||
break;
|
||||
case 1: // Camera rotated 90 degrees
|
||||
canvas_draw_dot(canvas, y, FRAME_WIDTH - 1 - x);
|
||||
break;
|
||||
case 2: // Camera rotated 180 degrees (upside down)
|
||||
canvas_draw_dot(canvas, FRAME_WIDTH - 1 - x, FRAME_HEIGHT - 1 - y);
|
||||
break;
|
||||
case 3: // Camera rotated 270 degrees
|
||||
canvas_draw_dot(canvas, FRAME_HEIGHT - 1 - y, x);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void camera_suite_view_camera_draw(Canvas* canvas, void* _model) {
|
||||
UartDumpModel* model = _model;
|
||||
|
||||
// Clear the screen.
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
@@ -41,60 +59,17 @@ static void camera_suite_view_style_1_draw(Canvas* canvas, UartDumpModel* model)
|
||||
|
||||
CameraSuite* app = current_instance->context;
|
||||
|
||||
// Draw the pixels with rotation.
|
||||
for(size_t p = 0; p < FRAME_BUFFER_LENGTH; ++p) {
|
||||
uint8_t x = p % ROW_BUFFER_LENGTH; // 0 .. 15
|
||||
uint8_t y = p / ROW_BUFFER_LENGTH; // 0 .. 63
|
||||
|
||||
// Apply rotation
|
||||
int16_t rotated_x, rotated_y;
|
||||
switch(app->orientation) {
|
||||
case 1: // 90 degrees
|
||||
rotated_x = y;
|
||||
rotated_y = FRAME_WIDTH - 1 - x;
|
||||
break;
|
||||
case 2: // 180 degrees
|
||||
rotated_x = FRAME_WIDTH - 1 - x;
|
||||
rotated_y = FRAME_HEIGHT - 1 - y;
|
||||
break;
|
||||
case 3: // 270 degrees
|
||||
rotated_x = FRAME_HEIGHT - 1 - y;
|
||||
rotated_y = x;
|
||||
break;
|
||||
case 0: // 0 degrees
|
||||
default:
|
||||
rotated_x = x;
|
||||
rotated_y = y;
|
||||
break;
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < 8; ++i) {
|
||||
if((model->pixels[p] & (1 << i)) != 0) {
|
||||
// Adjust the coordinates based on the new screen dimensions
|
||||
uint16_t screen_x, screen_y;
|
||||
switch(app->orientation) {
|
||||
case 1: // 90 degrees
|
||||
screen_x = rotated_x;
|
||||
screen_y = FRAME_HEIGHT - 8 + (rotated_y * 8) + i;
|
||||
break;
|
||||
case 2: // 180 degrees
|
||||
screen_x = FRAME_WIDTH - 8 + (rotated_x * 8) + i;
|
||||
screen_y = FRAME_HEIGHT - 1 - rotated_y;
|
||||
break;
|
||||
case 3: // 270 degrees
|
||||
screen_x = FRAME_WIDTH - 1 - rotated_x;
|
||||
screen_y = rotated_y * 8 + i;
|
||||
break;
|
||||
case 0: // 0 degrees
|
||||
default:
|
||||
screen_x = rotated_x * 8 + i;
|
||||
screen_y = rotated_y;
|
||||
break;
|
||||
}
|
||||
canvas_draw_dot(canvas, screen_x, screen_y);
|
||||
if((model->pixels[p] & (1 << (7 - i))) != 0) {
|
||||
draw_pixel_by_orientation(canvas, (x * 8) + i, y, app->orientation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the guide if the camera is not initialized.
|
||||
if(!model->initialized) {
|
||||
canvas_draw_icon(canvas, 74, 16, &I_DolphinCommon_56x48);
|
||||
@@ -107,15 +82,15 @@ static void camera_suite_view_style_1_draw(Canvas* canvas, UartDumpModel* model)
|
||||
}
|
||||
}
|
||||
|
||||
static void camera_suite_view_style_1_model_init(UartDumpModel* const model) {
|
||||
static void camera_suite_view_camera_model_init(UartDumpModel* const model) {
|
||||
for(size_t i = 0; i < FRAME_BUFFER_LENGTH; i++) {
|
||||
model->pixels[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static bool camera_suite_view_style_1_input(InputEvent* event, void* context) {
|
||||
static bool camera_suite_view_camera_input(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
CameraSuiteViewStyle1* instance = context;
|
||||
CameraSuiteViewCamera* instance = context;
|
||||
if(event->type == InputTypeRelease) {
|
||||
switch(event->key) {
|
||||
default: // Stop all sounds, reset the LED.
|
||||
@@ -144,7 +119,7 @@ static bool camera_suite_view_style_1_input(InputEvent* event, void* context) {
|
||||
UartDumpModel * model,
|
||||
{
|
||||
UNUSED(model);
|
||||
instance->callback(CameraSuiteCustomEventSceneStyle1Back, instance->context);
|
||||
instance->callback(CameraSuiteCustomEventSceneCameraBack, instance->context);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
@@ -159,7 +134,7 @@ static bool camera_suite_view_style_1_input(InputEvent* event, void* context) {
|
||||
camera_suite_play_happy_bump(instance->context);
|
||||
camera_suite_play_input_sound(instance->context);
|
||||
camera_suite_led_set_rgb(instance->context, 0, 0, 255);
|
||||
instance->callback(CameraSuiteCustomEventSceneStyle1Left, instance->context);
|
||||
instance->callback(CameraSuiteCustomEventSceneCameraLeft, instance->context);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
@@ -174,7 +149,7 @@ static bool camera_suite_view_style_1_input(InputEvent* event, void* context) {
|
||||
camera_suite_play_happy_bump(instance->context);
|
||||
camera_suite_play_input_sound(instance->context);
|
||||
camera_suite_led_set_rgb(instance->context, 0, 0, 255);
|
||||
instance->callback(CameraSuiteCustomEventSceneStyle1Right, instance->context);
|
||||
instance->callback(CameraSuiteCustomEventSceneCameraRight, instance->context);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
@@ -189,7 +164,7 @@ static bool camera_suite_view_style_1_input(InputEvent* event, void* context) {
|
||||
camera_suite_play_happy_bump(instance->context);
|
||||
camera_suite_play_input_sound(instance->context);
|
||||
camera_suite_led_set_rgb(instance->context, 0, 0, 255);
|
||||
instance->callback(CameraSuiteCustomEventSceneStyle1Up, instance->context);
|
||||
instance->callback(CameraSuiteCustomEventSceneCameraUp, instance->context);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
@@ -204,18 +179,13 @@ static bool camera_suite_view_style_1_input(InputEvent* event, void* context) {
|
||||
camera_suite_play_happy_bump(instance->context);
|
||||
camera_suite_play_input_sound(instance->context);
|
||||
camera_suite_led_set_rgb(instance->context, 0, 0, 255);
|
||||
instance->callback(CameraSuiteCustomEventSceneStyle1Down, instance->context);
|
||||
instance->callback(CameraSuiteCustomEventSceneCameraDown, instance->context);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyOk:
|
||||
if(current_dithering == 0) {
|
||||
data[0] = 'd'; // Update to Floyd Steinberg dithering.
|
||||
current_dithering = 1;
|
||||
} else {
|
||||
data[0] = 'D'; // Update to Atkinson dithering.
|
||||
current_dithering = 0;
|
||||
}
|
||||
// Switch dithering types.
|
||||
data[0] = 'D';
|
||||
with_view_model(
|
||||
instance->view,
|
||||
UartDumpModel * model,
|
||||
@@ -224,7 +194,7 @@ static bool camera_suite_view_style_1_input(InputEvent* event, void* context) {
|
||||
camera_suite_play_happy_bump(instance->context);
|
||||
camera_suite_play_input_sound(instance->context);
|
||||
camera_suite_led_set_rgb(instance->context, 0, 0, 255);
|
||||
instance->callback(CameraSuiteCustomEventSceneStyle1Ok, instance->context);
|
||||
instance->callback(CameraSuiteCustomEventSceneCameraOk, instance->context);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
@@ -232,21 +202,21 @@ static bool camera_suite_view_style_1_input(InputEvent* event, void* context) {
|
||||
break;
|
||||
}
|
||||
// Send `data` to the ESP32-CAM
|
||||
furi_hal_uart_tx(FuriHalUartIdUSART1, data, 1);
|
||||
furi_hal_uart_tx(UART_CH, data, 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void camera_suite_view_style_1_exit(void* context) {
|
||||
static void camera_suite_view_camera_exit(void* context) {
|
||||
furi_assert(context);
|
||||
}
|
||||
|
||||
static void camera_suite_view_style_1_enter(void* context) {
|
||||
static void camera_suite_view_camera_enter(void* context) {
|
||||
// Check `context` for null. If it is null, abort program, else continue.
|
||||
furi_assert(context);
|
||||
|
||||
// Cast `context` to `CameraSuiteViewStyle1*` and store it in `instance`.
|
||||
CameraSuiteViewStyle1* instance = (CameraSuiteViewStyle1*)context;
|
||||
// Cast `context` to `CameraSuiteViewCamera*` and store it in `instance`.
|
||||
CameraSuiteViewCamera* instance = (CameraSuiteViewCamera*)context;
|
||||
|
||||
// Assign the current instance to the global variable
|
||||
current_instance = instance;
|
||||
@@ -254,12 +224,12 @@ static void camera_suite_view_style_1_enter(void* context) {
|
||||
uint8_t data[1];
|
||||
data[0] = 'S'; // Uppercase `S` to start the camera
|
||||
// Send `data` to the ESP32-CAM
|
||||
furi_hal_uart_tx(FuriHalUartIdUSART1, data, 1);
|
||||
furi_hal_uart_tx(UART_CH, data, 1);
|
||||
|
||||
with_view_model(
|
||||
instance->view,
|
||||
UartDumpModel * model,
|
||||
{ camera_suite_view_style_1_model_init(model); },
|
||||
{ camera_suite_view_camera_model_init(model); },
|
||||
true);
|
||||
}
|
||||
|
||||
@@ -267,8 +237,8 @@ static void camera_on_irq_cb(UartIrqEvent uartIrqEvent, uint8_t data, void* cont
|
||||
// Check `context` for null. If it is null, abort program, else continue.
|
||||
furi_assert(context);
|
||||
|
||||
// Cast `context` to `CameraSuiteViewStyle1*` and store it in `instance`.
|
||||
CameraSuiteViewStyle1* instance = context;
|
||||
// Cast `context` to `CameraSuiteViewCamera*` and store it in `instance`.
|
||||
CameraSuiteViewCamera* instance = context;
|
||||
|
||||
// If `uartIrqEvent` is `UartIrqEventRXNE`, send the data to the
|
||||
// `rx_stream` and set the `WorkerEventRx` flag.
|
||||
@@ -319,7 +289,7 @@ static void process_ringbuffer(UartDumpModel* model, uint8_t byte) {
|
||||
|
||||
static int32_t camera_worker(void* context) {
|
||||
furi_assert(context);
|
||||
CameraSuiteViewStyle1* instance = context;
|
||||
CameraSuiteViewCamera* instance = context;
|
||||
|
||||
while(1) {
|
||||
uint32_t events =
|
||||
@@ -348,14 +318,17 @@ static int32_t camera_worker(void* context) {
|
||||
false);
|
||||
}
|
||||
} while(length > 0);
|
||||
|
||||
with_view_model(
|
||||
instance->view, UartDumpModel * model, { UNUSED(model); }, true);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
CameraSuiteViewStyle1* camera_suite_view_style_1_alloc() {
|
||||
CameraSuiteViewStyle1* instance = malloc(sizeof(CameraSuiteViewStyle1));
|
||||
CameraSuiteViewCamera* camera_suite_view_camera_alloc() {
|
||||
CameraSuiteViewCamera* instance = malloc(sizeof(CameraSuiteViewCamera));
|
||||
|
||||
instance->view = view_alloc();
|
||||
|
||||
@@ -364,38 +337,50 @@ CameraSuiteViewStyle1* camera_suite_view_style_1_alloc() {
|
||||
// Set up views
|
||||
view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(UartDumpModel));
|
||||
view_set_context(instance->view, instance); // furi_assert crashes in events without this
|
||||
view_set_draw_callback(instance->view, (ViewDrawCallback)camera_suite_view_style_1_draw);
|
||||
view_set_input_callback(instance->view, camera_suite_view_style_1_input);
|
||||
view_set_enter_callback(instance->view, camera_suite_view_style_1_enter);
|
||||
view_set_exit_callback(instance->view, camera_suite_view_style_1_exit);
|
||||
view_set_draw_callback(instance->view, (ViewDrawCallback)camera_suite_view_camera_draw);
|
||||
view_set_input_callback(instance->view, camera_suite_view_camera_input);
|
||||
view_set_enter_callback(instance->view, camera_suite_view_camera_enter);
|
||||
view_set_exit_callback(instance->view, camera_suite_view_camera_exit);
|
||||
|
||||
with_view_model(
|
||||
instance->view,
|
||||
UartDumpModel * model,
|
||||
{ camera_suite_view_style_1_model_init(model); },
|
||||
{ camera_suite_view_camera_model_init(model); },
|
||||
true);
|
||||
|
||||
instance->worker_thread = furi_thread_alloc_ex("UsbUartWorker", 2048, camera_worker, instance);
|
||||
furi_thread_start(instance->worker_thread);
|
||||
|
||||
// Enable uart listener
|
||||
furi_hal_console_disable();
|
||||
furi_hal_uart_set_br(FuriHalUartIdUSART1, 230400);
|
||||
furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, camera_on_irq_cb, instance);
|
||||
if(UART_CH == FuriHalUartIdUSART1) {
|
||||
furi_hal_console_disable();
|
||||
} else if(UART_CH == FuriHalUartIdLPUART1) {
|
||||
furi_hal_uart_init(UART_CH, 230400);
|
||||
}
|
||||
furi_hal_uart_set_br(UART_CH, 230400);
|
||||
furi_hal_uart_set_irq_cb(UART_CH, camera_on_irq_cb, instance);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void camera_suite_view_style_1_free(CameraSuiteViewStyle1* instance) {
|
||||
void camera_suite_view_camera_free(CameraSuiteViewCamera* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
with_view_model(
|
||||
instance->view, UartDumpModel * model, { UNUSED(model); }, true);
|
||||
view_free(instance->view);
|
||||
free(instance);
|
||||
|
||||
furi_hal_uart_set_irq_cb(UART_CH, NULL, NULL);
|
||||
|
||||
if(UART_CH == FuriHalUartIdLPUART1) {
|
||||
furi_hal_uart_deinit(UART_CH);
|
||||
} else {
|
||||
furi_hal_console_enable();
|
||||
}
|
||||
}
|
||||
|
||||
View* camera_suite_view_style_1_get_view(CameraSuiteViewStyle1* instance) {
|
||||
View* camera_suite_view_camera_get_view(CameraSuiteViewCamera* instance) {
|
||||
furi_assert(instance);
|
||||
return instance->view;
|
||||
}
|
||||
@@ -14,16 +14,23 @@
|
||||
#include <storage/filesystem_api_defines.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#include <xtreme.h>
|
||||
|
||||
#define UART_CH \
|
||||
(XTREME_SETTINGS()->uart_esp_channel == UARTDefault ? FuriHalUartIdUSART1 : \
|
||||
FuriHalUartIdLPUART1)
|
||||
|
||||
#pragma once
|
||||
|
||||
#define FRAME_WIDTH 128
|
||||
#define FRAME_HEIGHT 64
|
||||
#define FRAME_BIT_DEPTH 1
|
||||
#define FRAME_BUFFER_LENGTH \
|
||||
(FRAME_WIDTH * FRAME_HEIGHT * FRAME_BIT_DEPTH / 8) // 128*64*1 / 8 = 1024
|
||||
#define ROW_BUFFER_LENGTH (FRAME_WIDTH / 8) // 128/8 = 16
|
||||
#define RING_BUFFER_LENGTH (ROW_BUFFER_LENGTH + 3) // ROW_BUFFER_LENGTH + Header => 16 + 3 = 19
|
||||
#define LAST_ROW_INDEX (FRAME_BUFFER_LENGTH - ROW_BUFFER_LENGTH) // 1024 - 16 = 1008
|
||||
#define FRAME_BUFFER_LENGTH 1024
|
||||
#define ROW_BUFFER_LENGTH 16
|
||||
#define RING_BUFFER_LENGTH 19
|
||||
#define LAST_ROW_INDEX 1008
|
||||
|
||||
extern const Icon I_DolphinCommon_56x48;
|
||||
|
||||
typedef struct UartDumpModel UartDumpModel;
|
||||
|
||||
@@ -35,20 +42,20 @@ struct UartDumpModel {
|
||||
uint8_t row_ringbuffer[RING_BUFFER_LENGTH];
|
||||
};
|
||||
|
||||
typedef struct CameraSuiteViewStyle1 CameraSuiteViewStyle1;
|
||||
typedef struct CameraSuiteViewCamera CameraSuiteViewCamera;
|
||||
|
||||
typedef void (*CameraSuiteViewStyle1Callback)(CameraSuiteCustomEvent event, void* context);
|
||||
typedef void (*CameraSuiteViewCameraCallback)(CameraSuiteCustomEvent event, void* context);
|
||||
|
||||
void camera_suite_view_style_1_set_callback(
|
||||
CameraSuiteViewStyle1* camera_suite_view_style_1,
|
||||
CameraSuiteViewStyle1Callback callback,
|
||||
void camera_suite_view_camera_set_callback(
|
||||
CameraSuiteViewCamera* camera_suite_view_camera,
|
||||
CameraSuiteViewCameraCallback callback,
|
||||
void* context);
|
||||
|
||||
CameraSuiteViewStyle1* camera_suite_view_style_1_alloc();
|
||||
CameraSuiteViewCamera* camera_suite_view_camera_alloc();
|
||||
|
||||
void camera_suite_view_style_1_free(CameraSuiteViewStyle1* camera_suite_static);
|
||||
void camera_suite_view_camera_free(CameraSuiteViewCamera* camera_suite_static);
|
||||
|
||||
View* camera_suite_view_style_1_get_view(CameraSuiteViewStyle1* camera_suite_static);
|
||||
View* camera_suite_view_camera_get_view(CameraSuiteViewCamera* camera_suite_static);
|
||||
|
||||
typedef enum {
|
||||
// Reserved for StreamBuffer internal event
|
||||
@@ -1,249 +0,0 @@
|
||||
#include "../camera_suite.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <input/input.h>
|
||||
#include <gui/elements.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
#include "../helpers/camera_suite_haptic.h"
|
||||
#include "../helpers/camera_suite_speaker.h"
|
||||
#include "../helpers/camera_suite_led.h"
|
||||
|
||||
struct CameraSuiteViewStyle2 {
|
||||
View* view;
|
||||
CameraSuiteViewStyle2Callback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int screen_text;
|
||||
} CameraSuiteViewStyle2Model;
|
||||
|
||||
char buttonText[11][14] = {
|
||||
"",
|
||||
"Press Up",
|
||||
"Press Down",
|
||||
"Press Left",
|
||||
"Press Right",
|
||||
"Press Ok",
|
||||
"Release Up",
|
||||
"Release Down",
|
||||
"Release Left",
|
||||
"Release Right",
|
||||
"Release Ok",
|
||||
};
|
||||
|
||||
void camera_suite_view_style_2_set_callback(
|
||||
CameraSuiteViewStyle2* instance,
|
||||
CameraSuiteViewStyle2Callback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
void camera_suite_view_style_2_draw(Canvas* canvas, CameraSuiteViewStyle2Model* model) {
|
||||
canvas_clear(canvas);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, "Scene 2: Input Examples");
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
char* strInput = malloc(15);
|
||||
strcpy(strInput, buttonText[model->screen_text]);
|
||||
canvas_draw_str_aligned(canvas, 0, 22, AlignLeft, AlignTop, strInput);
|
||||
free(strInput);
|
||||
}
|
||||
|
||||
static void camera_suite_view_style_2_model_init(CameraSuiteViewStyle2Model* const model) {
|
||||
model->screen_text = 0;
|
||||
}
|
||||
|
||||
bool camera_suite_view_style_2_input(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
CameraSuiteViewStyle2* instance = context;
|
||||
if(event->type == InputTypeRelease) {
|
||||
switch(event->key) {
|
||||
case InputKeyBack:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
CameraSuiteViewStyle2Model * model,
|
||||
{
|
||||
UNUSED(model);
|
||||
camera_suite_stop_all_sound(instance->context);
|
||||
instance->callback(CameraSuiteCustomEventSceneStyle2Back, instance->context);
|
||||
camera_suite_play_long_bump(instance->context);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyUp:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
CameraSuiteViewStyle2Model * model,
|
||||
{
|
||||
model->screen_text = 6;
|
||||
camera_suite_play_bad_bump(instance->context);
|
||||
camera_suite_stop_all_sound(instance->context);
|
||||
camera_suite_led_set_rgb(instance->context, 255, 0, 255);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyDown:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
CameraSuiteViewStyle2Model * model,
|
||||
{
|
||||
model->screen_text = 7;
|
||||
camera_suite_play_bad_bump(instance->context);
|
||||
camera_suite_stop_all_sound(instance->context);
|
||||
camera_suite_led_set_rgb(instance->context, 255, 255, 0);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
CameraSuiteViewStyle2Model * model,
|
||||
{
|
||||
model->screen_text = 8;
|
||||
camera_suite_play_bad_bump(instance->context);
|
||||
camera_suite_stop_all_sound(instance->context);
|
||||
camera_suite_led_set_rgb(instance->context, 0, 255, 255);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyRight:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
CameraSuiteViewStyle2Model * model,
|
||||
{
|
||||
model->screen_text = 9;
|
||||
camera_suite_play_bad_bump(instance->context);
|
||||
camera_suite_stop_all_sound(instance->context);
|
||||
camera_suite_led_set_rgb(instance->context, 255, 0, 0);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyOk:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
CameraSuiteViewStyle2Model * model,
|
||||
{
|
||||
model->screen_text = 10;
|
||||
camera_suite_play_bad_bump(instance->context);
|
||||
camera_suite_stop_all_sound(instance->context);
|
||||
camera_suite_led_set_rgb(instance->context, 255, 255, 255);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyMAX:
|
||||
break;
|
||||
}
|
||||
} else if(event->type == InputTypePress) {
|
||||
switch(event->key) {
|
||||
case InputKeyUp:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
CameraSuiteViewStyle2Model * model,
|
||||
{
|
||||
model->screen_text = 1;
|
||||
camera_suite_play_happy_bump(instance->context);
|
||||
camera_suite_play_input_sound(instance->context);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyDown:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
CameraSuiteViewStyle2Model * model,
|
||||
{
|
||||
model->screen_text = 2;
|
||||
camera_suite_play_happy_bump(instance->context);
|
||||
camera_suite_play_input_sound(instance->context);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
CameraSuiteViewStyle2Model * model,
|
||||
{
|
||||
model->screen_text = 3;
|
||||
camera_suite_play_happy_bump(instance->context);
|
||||
camera_suite_play_input_sound(instance->context);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyRight:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
CameraSuiteViewStyle2Model * model,
|
||||
{
|
||||
model->screen_text = 4;
|
||||
camera_suite_play_happy_bump(instance->context);
|
||||
camera_suite_play_input_sound(instance->context);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyOk:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
CameraSuiteViewStyle2Model * model,
|
||||
{
|
||||
model->screen_text = 5;
|
||||
camera_suite_play_happy_bump(instance->context);
|
||||
camera_suite_play_input_sound(instance->context);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyBack:
|
||||
case InputKeyMAX:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void camera_suite_view_style_2_exit(void* context) {
|
||||
furi_assert(context);
|
||||
CameraSuite* app = context;
|
||||
camera_suite_stop_all_sound(app);
|
||||
//camera_suite_led_reset(app);
|
||||
}
|
||||
|
||||
void camera_suite_view_style_2_enter(void* context) {
|
||||
furi_assert(context);
|
||||
dolphin_deed(DolphinDeedPluginStart);
|
||||
}
|
||||
|
||||
CameraSuiteViewStyle2* camera_suite_view_style_2_alloc() {
|
||||
CameraSuiteViewStyle2* instance = malloc(sizeof(CameraSuiteViewStyle2));
|
||||
instance->view = view_alloc();
|
||||
view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(CameraSuiteViewStyle2Model));
|
||||
view_set_context(instance->view, instance);
|
||||
view_set_draw_callback(instance->view, (ViewDrawCallback)camera_suite_view_style_2_draw);
|
||||
view_set_input_callback(instance->view, camera_suite_view_style_2_input);
|
||||
//view_set_enter_callback(instance->view, camera_suite_view_style_2_enter);
|
||||
view_set_exit_callback(instance->view, camera_suite_view_style_2_exit);
|
||||
|
||||
with_view_model(
|
||||
instance->view,
|
||||
CameraSuiteViewStyle2Model * model,
|
||||
{ camera_suite_view_style_2_model_init(model); },
|
||||
true);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void camera_suite_view_style_2_free(CameraSuiteViewStyle2* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
view_free(instance->view);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
View* camera_suite_view_style_2_get_view(CameraSuiteViewStyle2* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
return instance->view;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include "../helpers/camera_suite_custom_event.h"
|
||||
|
||||
typedef struct CameraSuiteViewStyle2 CameraSuiteViewStyle2;
|
||||
|
||||
typedef void (*CameraSuiteViewStyle2Callback)(CameraSuiteCustomEvent event, void* context);
|
||||
|
||||
void camera_suite_view_style_2_set_callback(
|
||||
CameraSuiteViewStyle2* instance,
|
||||
CameraSuiteViewStyle2Callback callback,
|
||||
void* context);
|
||||
|
||||
CameraSuiteViewStyle2* camera_suite_view_style_2_alloc();
|
||||
|
||||
void camera_suite_view_style_2_free(CameraSuiteViewStyle2* camera_suite_static);
|
||||
|
||||
View* camera_suite_view_style_2_get_view(CameraSuiteViewStyle2* boilerpate_static);
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Roman Shchekin
|
||||
Copyright (c) 2023 Struan Clark
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
18
applications/external/chess/application.fam
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
App(
|
||||
appid="chess",
|
||||
name="Chess",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="flipchess_app",
|
||||
requires=[
|
||||
"gui",
|
||||
],
|
||||
stack_size=4 * 1024,
|
||||
fap_icon="flipchess_10px.png",
|
||||
fap_icon_assets="icons",
|
||||
fap_icon_assets_symbol="flipchess",
|
||||
fap_category="Games",
|
||||
fap_author="Struan Clark (xtruan)",
|
||||
fap_weburl="https://github.com/xtruan/flipper-chess",
|
||||
fap_version=(1, 9),
|
||||
fap_description="Chess for Flipper",
|
||||
)
|
||||
3492
applications/external/chess/chess/smallchesslib.h
vendored
Normal file
179
applications/external/chess/flipchess.c
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
#include "flipchess.h"
|
||||
#include "helpers/flipchess_haptic.h"
|
||||
|
||||
bool flipchess_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
FlipChess* app = context;
|
||||
return scene_manager_handle_custom_event(app->scene_manager, event);
|
||||
}
|
||||
|
||||
void flipchess_tick_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
FlipChess* app = context;
|
||||
scene_manager_handle_tick_event(app->scene_manager);
|
||||
}
|
||||
|
||||
//leave app if back button pressed
|
||||
bool flipchess_navigation_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
FlipChess* app = context;
|
||||
return scene_manager_handle_back_event(app->scene_manager);
|
||||
}
|
||||
|
||||
static void text_input_callback(void* context) {
|
||||
furi_assert(context);
|
||||
FlipChess* app = context;
|
||||
bool handled = false;
|
||||
|
||||
// check that there is text in the input
|
||||
if(strlen(app->input_text) > 0) {
|
||||
if(app->input_state == FlipChessTextInputGame) {
|
||||
if(app->import_game == 1) {
|
||||
strncpy(app->import_game_text, app->input_text, TEXT_SIZE);
|
||||
|
||||
uint8_t status = FlipChessStatusNone;
|
||||
if(status == FlipChessStatusNone) {
|
||||
//notification_message(app->notification, &sequence_blink_cyan_100);
|
||||
flipchess_play_happy_bump(app);
|
||||
} else {
|
||||
//notification_message(app->notification, &sequence_blink_red_100);
|
||||
flipchess_play_long_bump(app);
|
||||
}
|
||||
}
|
||||
// reset input state
|
||||
app->input_state = FlipChessTextInputDefault;
|
||||
handled = true;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdMenu);
|
||||
}
|
||||
}
|
||||
|
||||
if(!handled) {
|
||||
// reset input state
|
||||
app->input_state = FlipChessTextInputDefault;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdMenu);
|
||||
}
|
||||
}
|
||||
|
||||
FlipChess* flipchess_app_alloc() {
|
||||
FlipChess* app = malloc(sizeof(FlipChess));
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
//Turn backlight on, believe me this makes testing your app easier
|
||||
notification_message(app->notification, &sequence_display_backlight_on);
|
||||
|
||||
//Scene additions
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
|
||||
app->scene_manager = scene_manager_alloc(&flipchess_scene_handlers, app);
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
app->view_dispatcher, flipchess_navigation_event_callback);
|
||||
view_dispatcher_set_tick_event_callback(
|
||||
app->view_dispatcher, flipchess_tick_event_callback, 100);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
app->view_dispatcher, flipchess_custom_event_callback);
|
||||
app->submenu = submenu_alloc();
|
||||
|
||||
// Settings
|
||||
app->haptic = FlipChessHapticOn;
|
||||
app->white_mode = FlipChessPlayerHuman;
|
||||
app->black_mode = FlipChessPlayerAI1;
|
||||
|
||||
// Startscreen
|
||||
app->sound = 0;
|
||||
// Main menu
|
||||
app->import_game = 0;
|
||||
|
||||
// Text input
|
||||
app->input_state = FlipChessTextInputDefault;
|
||||
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, FlipChessViewIdMenu, submenu_get_view(app->submenu));
|
||||
app->flipchess_startscreen = flipchess_startscreen_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
FlipChessViewIdStartscreen,
|
||||
flipchess_startscreen_get_view(app->flipchess_startscreen));
|
||||
app->flipchess_scene_1 = flipchess_scene_1_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
FlipChessViewIdScene1,
|
||||
flipchess_scene_1_get_view(app->flipchess_scene_1));
|
||||
app->variable_item_list = variable_item_list_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
FlipChessViewIdSettings,
|
||||
variable_item_list_get_view(app->variable_item_list));
|
||||
|
||||
app->text_input = text_input_alloc();
|
||||
text_input_set_result_callback(
|
||||
app->text_input,
|
||||
text_input_callback,
|
||||
(void*)app,
|
||||
app->input_text,
|
||||
TEXT_BUFFER_SIZE,
|
||||
//clear default text
|
||||
true);
|
||||
text_input_set_header_text(app->text_input, "Input");
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, FlipChessViewIdTextInput, text_input_get_view(app->text_input));
|
||||
|
||||
//End Scene Additions
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
void flipchess_app_free(FlipChess* app) {
|
||||
furi_assert(app);
|
||||
|
||||
// Scene manager
|
||||
scene_manager_free(app->scene_manager);
|
||||
|
||||
text_input_free(app->text_input);
|
||||
|
||||
// View Dispatcher
|
||||
view_dispatcher_remove_view(app->view_dispatcher, FlipChessViewIdMenu);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, FlipChessViewIdScene1);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, FlipChessViewIdSettings);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, FlipChessViewIdTextInput);
|
||||
submenu_free(app->submenu);
|
||||
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
app->gui = NULL;
|
||||
app->notification = NULL;
|
||||
|
||||
//Remove whatever is left
|
||||
//memzero(app, sizeof(FlipChess));
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t flipchess_app(void* p) {
|
||||
UNUSED(p);
|
||||
FlipChess* app = flipchess_app_alloc();
|
||||
|
||||
// Disabled because causes exit on custom firmwares such as RM
|
||||
/*if(!furi_hal_region_is_provisioned()) {
|
||||
flipchess_app_free(app);
|
||||
return 1;
|
||||
}*/
|
||||
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
scene_manager_next_scene(
|
||||
app->scene_manager, FlipChessSceneStartscreen); //Start with start screen
|
||||
//scene_manager_next_scene(app->scene_manager, FlipChessSceneMenu); //if you want to directly start with Menu
|
||||
|
||||
furi_hal_random_init();
|
||||
// furi_hal_power_suppress_charge_enter();
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
// furi_hal_power_suppress_charge_exit();
|
||||
flipchess_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
78
applications/external/chess/flipchess.h
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_random.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
#include <gui/modules/text_input.h>
|
||||
#include "scenes/flipchess_scene.h"
|
||||
#include "views/flipchess_startscreen.h"
|
||||
#include "views/flipchess_scene_1.h"
|
||||
|
||||
#define FLIPCHESS_VERSION "v1.9.0"
|
||||
|
||||
#define TEXT_BUFFER_SIZE 96
|
||||
#define TEXT_SIZE (TEXT_BUFFER_SIZE - 1)
|
||||
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
NotificationApp* notification;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Submenu* submenu;
|
||||
SceneManager* scene_manager;
|
||||
VariableItemList* variable_item_list;
|
||||
TextInput* text_input;
|
||||
FlipChessStartscreen* flipchess_startscreen;
|
||||
FlipChessScene1* flipchess_scene_1;
|
||||
// Settings options
|
||||
int haptic;
|
||||
int white_mode;
|
||||
int black_mode;
|
||||
// Startscreen options
|
||||
uint8_t sound;
|
||||
// Main menu options
|
||||
uint8_t import_game;
|
||||
// Text input
|
||||
uint8_t input_state;
|
||||
char import_game_text[TEXT_BUFFER_SIZE];
|
||||
char input_text[TEXT_BUFFER_SIZE];
|
||||
} FlipChess;
|
||||
|
||||
typedef enum {
|
||||
FlipChessViewIdStartscreen,
|
||||
FlipChessViewIdMenu,
|
||||
FlipChessViewIdScene1,
|
||||
FlipChessViewIdSettings,
|
||||
FlipChessViewIdTextInput,
|
||||
} FlipChessViewId;
|
||||
|
||||
typedef enum {
|
||||
FlipChessHapticOff,
|
||||
FlipChessHapticOn,
|
||||
} FlipChessHapticState;
|
||||
|
||||
typedef enum {
|
||||
FlipChessPlayerHuman = 0,
|
||||
FlipChessPlayerAI1 = 1,
|
||||
FlipChessPlayerAI2 = 2,
|
||||
FlipChessPlayerAI3 = 3,
|
||||
} FlipChessPlayerMode;
|
||||
|
||||
typedef enum { FlipChessTextInputDefault, FlipChessTextInputGame } FlipChessTextInputState;
|
||||
|
||||
typedef enum {
|
||||
FlipChessStatusNone = 0,
|
||||
FlipChessStatusMovePlayer = 1,
|
||||
FlipChessStatusMoveAI = 2,
|
||||
FlipChessStatusMoveUndo = 3,
|
||||
FlipChessStatusReturn = 10,
|
||||
FlipChessStatusLoadError = 11,
|
||||
FlipChessStatusSaveError = 12,
|
||||
} FlipChessStatus;
|
||||
BIN
applications/external/chess/flipchess_10px.png
vendored
Normal file
|
After Width: | Height: | Size: 176 B |
16
applications/external/chess/helpers/flipchess_custom_event.h
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
typedef enum {
|
||||
FlipChessCustomEventStartscreenUp,
|
||||
FlipChessCustomEventStartscreenDown,
|
||||
FlipChessCustomEventStartscreenLeft,
|
||||
FlipChessCustomEventStartscreenRight,
|
||||
FlipChessCustomEventStartscreenOk,
|
||||
FlipChessCustomEventStartscreenBack,
|
||||
FlipChessCustomEventScene1Up,
|
||||
FlipChessCustomEventScene1Down,
|
||||
FlipChessCustomEventScene1Left,
|
||||
FlipChessCustomEventScene1Right,
|
||||
FlipChessCustomEventScene1Ok,
|
||||
FlipChessCustomEventScene1Back,
|
||||
} FlipChessCustomEvent;
|
||||
153
applications/external/chess/helpers/flipchess_file.c
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
#include "flipchess_file.h"
|
||||
#include <storage/storage.h>
|
||||
#include <loader/loader.h>
|
||||
|
||||
// #define FLIPCHESS_APP_BASE_FOLDER APP_BOARDA_PATH("flipchess")
|
||||
#define FLIPCHESS_APP_BASE_FOLDER EXT_PATH("apps_data/flipchess")
|
||||
#define FLIPCHESS_APP_BASE_FOLDER_PATH(path) FLIPCHESS_APP_BASE_FOLDER "/" path
|
||||
#define FLIPCHESS_BOARD_FILE_NAME "board_fen.txt"
|
||||
#define FLIPCHESS_BOARD_FILE_NAME_BAK "board_fen.bak"
|
||||
#define FLIPCHESS_BOARD_PATH FLIPCHESS_APP_BASE_FOLDER_PATH(FLIPCHESS_BOARD_FILE_NAME)
|
||||
#define FLIPCHESS_BOARD_PATH_BAK FLIPCHESS_APP_BASE_FOLDER_PATH(FLIPCHESS_BOARD_FILE_NAME_BAK)
|
||||
|
||||
#define FILE_MAX_PATH_LEN 48
|
||||
#define FILE_MAX_CHARS 94
|
||||
|
||||
bool flipchess_has_file(const FlipChessFile file_type, const char* file_name, const bool remove) {
|
||||
bool ret = false;
|
||||
const char* path;
|
||||
if(file_type == FlipChessFileBoard) {
|
||||
path = FLIPCHESS_BOARD_PATH;
|
||||
} else {
|
||||
char path_buf[FILE_MAX_PATH_LEN] = {0};
|
||||
strcpy(path_buf, FLIPCHESS_APP_BASE_FOLDER); // 22
|
||||
strcpy(path_buf + strlen(path_buf), "/");
|
||||
strcpy(path_buf + strlen(path_buf), file_name);
|
||||
path = path_buf;
|
||||
}
|
||||
|
||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
||||
if(remove) {
|
||||
ret = storage_simply_remove(fs_api, path);
|
||||
} else {
|
||||
ret = storage_file_exists(fs_api, path);
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool flipchess_load_file(char* contents, const FlipChessFile file_type, const char* file_name) {
|
||||
bool ret = false;
|
||||
const char* path;
|
||||
if(file_type == FlipChessFileBoard) {
|
||||
path = FLIPCHESS_BOARD_PATH;
|
||||
} else {
|
||||
char path_buf[FILE_MAX_PATH_LEN] = {0};
|
||||
strcpy(path_buf, FLIPCHESS_APP_BASE_FOLDER); // 22
|
||||
strcpy(path_buf + strlen(path_buf), "/");
|
||||
strcpy(path_buf + strlen(path_buf), file_name);
|
||||
path = path_buf;
|
||||
}
|
||||
|
||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
File* settings_file = storage_file_alloc(fs_api);
|
||||
if(storage_file_open(settings_file, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
char chr;
|
||||
int i = 0;
|
||||
while((storage_file_read(settings_file, &chr, 1) == 1) &&
|
||||
!storage_file_eof(settings_file)) {
|
||||
if(i < FILE_MAX_CHARS) {
|
||||
contents[i] = chr;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
ret = true;
|
||||
} else {
|
||||
contents[0] = '\0';
|
||||
ret = false;
|
||||
}
|
||||
storage_file_close(settings_file);
|
||||
storage_file_free(settings_file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
if(strlen(contents) > 0) {
|
||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
||||
FileInfo layout_file_info;
|
||||
FS_Error file_check_err = storage_common_stat(fs_api, path, &layout_file_info);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
if(file_check_err != FSE_OK) {
|
||||
contents[0] = '\0';
|
||||
ret = false;
|
||||
}
|
||||
// if(layout_file_info.size != 256) {
|
||||
// memzero(settings, strlen(settings));
|
||||
// settings[0] = '\0';
|
||||
// }
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool flipchess_save_file(
|
||||
const char* settings,
|
||||
const FlipChessFile file_type,
|
||||
const char* file_name,
|
||||
const bool append,
|
||||
const bool overwrite) {
|
||||
bool ret = false;
|
||||
const char* path;
|
||||
const char* path_bak;
|
||||
if(file_type == FlipChessFileBoard) {
|
||||
path = FLIPCHESS_BOARD_PATH;
|
||||
path_bak = FLIPCHESS_BOARD_PATH_BAK;
|
||||
} else {
|
||||
char path_buf[FILE_MAX_PATH_LEN] = {0};
|
||||
strcpy(path_buf, FLIPCHESS_APP_BASE_FOLDER); // 22
|
||||
strcpy(path_buf + strlen(path_buf), "/");
|
||||
strcpy(path_buf + strlen(path_buf), file_name);
|
||||
path = path_buf;
|
||||
path_bak = NULL;
|
||||
}
|
||||
int open_mode = FSOM_OPEN_ALWAYS;
|
||||
if(append) {
|
||||
open_mode = FSOM_OPEN_APPEND;
|
||||
}
|
||||
|
||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
// try to create the folder
|
||||
storage_simply_mkdir(fs_api, FLIPCHESS_APP_BASE_FOLDER);
|
||||
|
||||
if(overwrite) {
|
||||
storage_simply_remove(fs_api, path);
|
||||
}
|
||||
|
||||
File* settings_file = storage_file_alloc(fs_api);
|
||||
if(storage_file_open(settings_file, path, FSAM_WRITE, open_mode)) {
|
||||
storage_file_write(settings_file, settings, strlen(settings));
|
||||
storage_file_write(settings_file, "\n", 1);
|
||||
ret = true;
|
||||
}
|
||||
storage_file_close(settings_file);
|
||||
storage_file_free(settings_file);
|
||||
|
||||
if(path_bak != NULL) {
|
||||
if(overwrite) {
|
||||
storage_simply_remove(fs_api, path_bak);
|
||||
}
|
||||
|
||||
File* settings_file_bak = storage_file_alloc(fs_api);
|
||||
if(storage_file_open(settings_file_bak, path_bak, FSAM_WRITE, open_mode)) {
|
||||
storage_file_write(settings_file_bak, settings, strlen(settings));
|
||||
storage_file_write(settings_file_bak, "\n", 1);
|
||||
}
|
||||
storage_file_close(settings_file_bak);
|
||||
storage_file_free(settings_file_bak);
|
||||
}
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return ret;
|
||||
}
|
||||
15
applications/external/chess/helpers/flipchess_file.h
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum {
|
||||
FlipChessFileBoard,
|
||||
FlipChessFileOther,
|
||||
} FlipChessFile;
|
||||
|
||||
bool flipchess_has_file(const FlipChessFile file_type, const char* file_name, const bool remove);
|
||||
bool flipchess_load_file(char* contents, const FlipChessFile file_type, const char* file_name);
|
||||
bool flipchess_save_file(
|
||||
const char* contents,
|
||||
const FlipChessFile file_type,
|
||||
const char* file_name,
|
||||
const bool append,
|
||||
const bool overwrite);
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "flipbip_haptic.h"
|
||||
#include "../flipbip.h"
|
||||
#include "flipchess_haptic.h"
|
||||
#include "../flipchess.h"
|
||||
|
||||
void flipbip_play_happy_bump(void* context) {
|
||||
FlipBip* app = context;
|
||||
void flipchess_play_happy_bump(void* context) {
|
||||
FlipChess* app = context;
|
||||
if(app->haptic != 1) {
|
||||
return;
|
||||
}
|
||||
@@ -11,8 +11,8 @@ void flipbip_play_happy_bump(void* context) {
|
||||
notification_message(app->notification, &sequence_reset_vibro);
|
||||
}
|
||||
|
||||
void flipbip_play_bad_bump(void* context) {
|
||||
FlipBip* app = context;
|
||||
void flipchess_play_bad_bump(void* context) {
|
||||
FlipChess* app = context;
|
||||
if(app->haptic != 1) {
|
||||
return;
|
||||
}
|
||||
@@ -21,8 +21,8 @@ void flipbip_play_bad_bump(void* context) {
|
||||
notification_message(app->notification, &sequence_reset_vibro);
|
||||
}
|
||||
|
||||
void flipbip_play_long_bump(void* context) {
|
||||
FlipBip* app = context;
|
||||
void flipchess_play_long_bump(void* context) {
|
||||
FlipChess* app = context;
|
||||
if(app->haptic != 1) {
|
||||
return;
|
||||
}
|
||||
7
applications/external/chess/helpers/flipchess_haptic.h
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
void flipchess_play_happy_bump(void* context);
|
||||
|
||||
void flipchess_play_bad_bump(void* context);
|
||||
|
||||
void flipchess_play_long_bump(void* context);
|
||||
37
applications/external/chess/helpers/flipchess_voice.cpp
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
#include "flipchess_voice.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include "../sam/stm32_sam.h"
|
||||
STM32SAM voice;
|
||||
|
||||
void flipchess_voice_shall_we_play() {
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
voice.begin();
|
||||
voice.say("SHAAL WE PLAY AY GAME?");
|
||||
furi_hal_speaker_release();
|
||||
}
|
||||
}
|
||||
|
||||
void flipchess_voice_which_side() {
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
voice.begin();
|
||||
voice.say("WHICH SIDE DO YOU WANT?");
|
||||
furi_hal_speaker_release();
|
||||
}
|
||||
}
|
||||
|
||||
void flipchess_voice_how_about_chess() {
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
voice.begin();
|
||||
voice.say("HOW ABOUT A NICE GAME OF CHESS?");
|
||||
furi_hal_speaker_release();
|
||||
}
|
||||
}
|
||||
|
||||
void flipchess_voice_a_strange_game() {
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
|
||||
voice.begin();
|
||||
voice.say("A STRANGE GAME... THE ONLY WINNING MOVE IS NOT TO PLAY.");
|
||||
furi_hal_speaker_release();
|
||||
}
|
||||
}
|
||||
12
applications/external/chess/helpers/flipchess_voice.h
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
#ifdef __cplusplus
|
||||
#define EXTERNC extern "C"
|
||||
#else
|
||||
#define EXTERNC
|
||||
#endif
|
||||
|
||||
EXTERNC void flipchess_voice_shall_we_play();
|
||||
EXTERNC void flipchess_voice_which_side();
|
||||
EXTERNC void flipchess_voice_how_about_chess();
|
||||
EXTERNC void flipchess_voice_a_strange_game();
|
||||
|
||||
#undef EXTERNC
|
||||
BIN
applications/external/chess/icons/FLIPR_128x64.png
vendored
Normal file
|
After Width: | Height: | Size: 1017 B |
5703
applications/external/chess/sam/stm32_sam.cpp
vendored
Normal file
96
applications/external/chess/sam/stm32_sam.h
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
#include <furi.h>
|
||||
|
||||
#ifndef __STM32SAM__
|
||||
#define __STM32SAM__
|
||||
|
||||
// SAM Text-To-Speech (TTS), ported from https://github.com/s-macke/SAM
|
||||
|
||||
class STM32SAM {
|
||||
public:
|
||||
STM32SAM(uint32_t STM32SAM_SPEED);
|
||||
STM32SAM();
|
||||
|
||||
void begin(void);
|
||||
|
||||
void
|
||||
sam(const char* argv,
|
||||
unsigned char phonetic,
|
||||
unsigned char singmode,
|
||||
unsigned char pitch,
|
||||
unsigned char speed,
|
||||
unsigned char mouth,
|
||||
unsigned char throat);
|
||||
void
|
||||
sam(char* argv,
|
||||
unsigned char phonetic,
|
||||
unsigned char singmode,
|
||||
unsigned char pitch,
|
||||
unsigned char speed,
|
||||
unsigned char mouth,
|
||||
unsigned char throat);
|
||||
|
||||
void say(const char* argv);
|
||||
void say(char* argv);
|
||||
void sing(const char* argv);
|
||||
void sing(char* argv);
|
||||
void sayPhonetic(const char* argv);
|
||||
void sayPhonetic(char* argv);
|
||||
void singPhonetic(const char* argv);
|
||||
void singPhonetic(char* argv);
|
||||
void setVoice(
|
||||
unsigned char _pitch = 64,
|
||||
unsigned char _speed = 72,
|
||||
unsigned char _mouth = 128,
|
||||
unsigned char _throat = 128);
|
||||
void setPitch(unsigned char _pitch = 64);
|
||||
void setSpeed(unsigned char _speed = 72);
|
||||
void setMouth(unsigned char _mouth = 128);
|
||||
void setThroat(unsigned char _throat = 128);
|
||||
|
||||
private:
|
||||
void SetAUDIO(unsigned char main_volume);
|
||||
|
||||
void Output8BitAry(int index, unsigned char ary[5]);
|
||||
void Output8Bit(int index, unsigned char A);
|
||||
unsigned char Read(unsigned char p, unsigned char Y);
|
||||
void Write(unsigned char p, unsigned char Y, unsigned char value);
|
||||
void RenderSample(unsigned char* mem66);
|
||||
void Render();
|
||||
void AddInflection(unsigned char mem48, unsigned char phase1);
|
||||
void SetMouthThroat();
|
||||
unsigned char trans(unsigned char mem39212, unsigned char mem39213);
|
||||
void SetInput(char* _input);
|
||||
void Init();
|
||||
int SAMMain();
|
||||
void PrepareOutput();
|
||||
void Insert(
|
||||
unsigned char position /*var57*/,
|
||||
unsigned char mem60,
|
||||
unsigned char mem59,
|
||||
unsigned char mem58);
|
||||
void InsertBreath();
|
||||
void CopyStress();
|
||||
int Parser1();
|
||||
void SetPhonemeLength();
|
||||
void Code41240();
|
||||
void Parser2();
|
||||
void AdjustLengths();
|
||||
void Code47503(unsigned char mem52);
|
||||
void Code37055(unsigned char mem59);
|
||||
void Code37066(unsigned char mem58);
|
||||
unsigned char GetRuleByte(unsigned short mem62, unsigned char Y);
|
||||
int TextToPhonemes(unsigned char* input); // Code36484
|
||||
|
||||
uint32_t _STM32SAM_SPEED;
|
||||
|
||||
unsigned char speed;
|
||||
unsigned char pitch;
|
||||
unsigned char mouth;
|
||||
unsigned char throat;
|
||||
|
||||
unsigned char phonetic;
|
||||
unsigned char singmode;
|
||||
|
||||
}; // STM32SAM class
|
||||
|
||||
#endif
|
||||
30
applications/external/chess/scenes/flipchess_scene.c
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "flipchess_scene.h"
|
||||
|
||||
// Generate scene on_enter handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||
void (*const flipchess_on_enter_handlers[])(void*) = {
|
||||
#include "flipchess_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 flipchess_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||
#include "flipchess_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 flipchess_on_exit_handlers[])(void* context) = {
|
||||
#include "flipchess_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Initialize scene handlers configuration structure
|
||||
const SceneManagerHandlers flipchess_scene_handlers = {
|
||||
.on_enter_handlers = flipchess_on_enter_handlers,
|
||||
.on_event_handlers = flipchess_on_event_handlers,
|
||||
.on_exit_handlers = flipchess_on_exit_handlers,
|
||||
.scene_num = FlipChessSceneNum,
|
||||
};
|
||||
29
applications/external/chess/scenes/flipchess_scene.h
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
// Generate scene id and total number
|
||||
#define ADD_SCENE(prefix, name, id) FlipChessScene##id,
|
||||
typedef enum {
|
||||
#include "flipchess_scene_config.h"
|
||||
FlipChessSceneNum,
|
||||
} FlipChessScene;
|
||||
#undef ADD_SCENE
|
||||
|
||||
extern const SceneManagerHandlers flipchess_scene_handlers;
|
||||
|
||||
// Generate scene on_enter handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||
#include "flipchess_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 "flipchess_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 "flipchess_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
4
applications/external/chess/scenes/flipchess_scene_config.h
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
ADD_SCENE(flipchess, startscreen, Startscreen)
|
||||
ADD_SCENE(flipchess, menu, Menu)
|
||||
ADD_SCENE(flipchess, scene_1, Scene_1)
|
||||
ADD_SCENE(flipchess, settings, Settings)
|
||||
91
applications/external/chess/scenes/flipchess_scene_menu.c
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
#include "../flipchess.h"
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexScene1New = 10,
|
||||
SubmenuIndexScene1Resume,
|
||||
SubmenuIndexScene1Import,
|
||||
SubmenuIndexSettings,
|
||||
};
|
||||
|
||||
void flipchess_scene_menu_submenu_callback(void* context, uint32_t index) {
|
||||
FlipChess* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void flipchess_scene_menu_on_enter(void* context) {
|
||||
FlipChess* app = context;
|
||||
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"New Game",
|
||||
SubmenuIndexScene1New,
|
||||
flipchess_scene_menu_submenu_callback,
|
||||
app);
|
||||
|
||||
if(app->import_game == 1) {
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Resume Game",
|
||||
SubmenuIndexScene1Resume,
|
||||
flipchess_scene_menu_submenu_callback,
|
||||
app);
|
||||
}
|
||||
|
||||
// submenu_add_item(
|
||||
// app->submenu,
|
||||
// "Import Game",
|
||||
// SubmenuIndexScene1Import,
|
||||
// flipchess_scene_menu_submenu_callback,
|
||||
// app);
|
||||
|
||||
submenu_add_item(
|
||||
app->submenu, "Settings", SubmenuIndexSettings, flipchess_scene_menu_submenu_callback, app);
|
||||
|
||||
submenu_set_selected_item(
|
||||
app->submenu, scene_manager_get_scene_state(app->scene_manager, FlipChessSceneMenu));
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdMenu);
|
||||
}
|
||||
|
||||
bool flipchess_scene_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
FlipChess* app = context;
|
||||
//UNUSED(app);
|
||||
if(event.type == SceneManagerEventTypeBack) {
|
||||
//exit app
|
||||
scene_manager_stop(app->scene_manager);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
return true;
|
||||
} else if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexScene1New) {
|
||||
app->import_game = 0;
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, FlipChessSceneMenu, SubmenuIndexScene1New);
|
||||
scene_manager_next_scene(app->scene_manager, FlipChessSceneScene_1);
|
||||
return true;
|
||||
}
|
||||
if(event.event == SubmenuIndexScene1Resume) {
|
||||
app->import_game = 1;
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, FlipChessSceneMenu, SubmenuIndexScene1Resume);
|
||||
scene_manager_next_scene(app->scene_manager, FlipChessSceneScene_1);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexScene1Import) {
|
||||
app->import_game = 1;
|
||||
app->input_state = FlipChessTextInputGame;
|
||||
text_input_set_header_text(app->text_input, "Enter board FEN");
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdTextInput);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexSettings) {
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, FlipChessSceneMenu, SubmenuIndexSettings);
|
||||
scene_manager_next_scene(app->scene_manager, FlipChessSceneSettings);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void flipchess_scene_menu_on_exit(void* context) {
|
||||
FlipChess* app = context;
|
||||
submenu_reset(app->submenu);
|
||||
}
|
||||
55
applications/external/chess/scenes/flipchess_scene_scene_1.c
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "../flipchess.h"
|
||||
#include "../helpers/flipchess_file.h"
|
||||
#include "../helpers/flipchess_custom_event.h"
|
||||
#include "../views/flipchess_scene_1.h"
|
||||
|
||||
void flipchess_scene_1_callback(FlipChessCustomEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
FlipChess* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, event);
|
||||
}
|
||||
|
||||
void flipchess_scene_scene_1_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
FlipChess* app = context;
|
||||
|
||||
flipchess_scene_1_set_callback(app->flipchess_scene_1, flipchess_scene_1_callback, app);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdScene1);
|
||||
}
|
||||
|
||||
bool flipchess_scene_scene_1_on_event(void* context, SceneManagerEvent event) {
|
||||
FlipChess* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case FlipChessCustomEventScene1Left:
|
||||
case FlipChessCustomEventScene1Right:
|
||||
break;
|
||||
case FlipChessCustomEventScene1Up:
|
||||
case FlipChessCustomEventScene1Down:
|
||||
break;
|
||||
case FlipChessCustomEventScene1Back:
|
||||
notification_message(app->notification, &sequence_reset_red);
|
||||
notification_message(app->notification, &sequence_reset_green);
|
||||
notification_message(app->notification, &sequence_reset_blue);
|
||||
if(!scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, FlipChessSceneMenu)) {
|
||||
scene_manager_stop(app->scene_manager);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
}
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void flipchess_scene_scene_1_on_exit(void* context) {
|
||||
FlipChess* app = context;
|
||||
|
||||
if(app->import_game == 1 && strlen(app->import_game_text) > 0) {
|
||||
flipchess_save_file(app->import_game_text, FlipChessFileBoard, NULL, false, true);
|
||||
}
|
||||
}
|
||||
102
applications/external/chess/scenes/flipchess_scene_settings.c
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
#include "../flipchess.h"
|
||||
#include "../helpers/flipchess_voice.h"
|
||||
#include <lib/toolbox/value_index.h>
|
||||
|
||||
#define TEXT_LABEL_ON "ON"
|
||||
#define TEXT_LABEL_OFF "OFF"
|
||||
|
||||
const char* const haptic_text[2] = {
|
||||
TEXT_LABEL_OFF,
|
||||
TEXT_LABEL_ON,
|
||||
};
|
||||
const uint32_t haptic_value[2] = {
|
||||
FlipChessHapticOff,
|
||||
FlipChessHapticOn,
|
||||
};
|
||||
|
||||
const char* const player_mode_text[4] = {
|
||||
"Human",
|
||||
"CPU 1",
|
||||
"CPU 2",
|
||||
"CPU 3",
|
||||
};
|
||||
const uint32_t player_mode_value[4] = {
|
||||
FlipChessPlayerHuman,
|
||||
FlipChessPlayerAI1,
|
||||
FlipChessPlayerAI2,
|
||||
FlipChessPlayerAI3,
|
||||
};
|
||||
|
||||
static void flipchess_scene_settings_set_haptic(VariableItem* item) {
|
||||
FlipChess* app = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, haptic_text[index]);
|
||||
app->haptic = haptic_value[index];
|
||||
}
|
||||
|
||||
static void flipchess_scene_settings_set_white_mode(VariableItem* item) {
|
||||
FlipChess* app = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, player_mode_text[index]);
|
||||
app->white_mode = player_mode_value[index];
|
||||
}
|
||||
|
||||
static void flipchess_scene_settings_set_black_mode(VariableItem* item) {
|
||||
FlipChess* app = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, player_mode_text[index]);
|
||||
app->black_mode = player_mode_value[index];
|
||||
}
|
||||
|
||||
void flipchess_scene_settings_submenu_callback(void* context, uint32_t index) {
|
||||
FlipChess* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void flipchess_scene_settings_on_enter(void* context) {
|
||||
FlipChess* app = context;
|
||||
VariableItem* item;
|
||||
uint8_t value_index;
|
||||
|
||||
if(app->sound == 1) {
|
||||
flipchess_voice_which_side();
|
||||
}
|
||||
|
||||
// White mode
|
||||
item = variable_item_list_add(
|
||||
app->variable_item_list, "White:", 4, flipchess_scene_settings_set_white_mode, app);
|
||||
value_index = value_index_uint32(app->white_mode, player_mode_value, 4);
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, player_mode_text[value_index]);
|
||||
|
||||
// Black mode
|
||||
item = variable_item_list_add(
|
||||
app->variable_item_list, "Black:", 4, flipchess_scene_settings_set_black_mode, app);
|
||||
value_index = value_index_uint32(app->black_mode, player_mode_value, 4);
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, player_mode_text[value_index]);
|
||||
|
||||
// Vibro on/off
|
||||
item = variable_item_list_add(
|
||||
app->variable_item_list, "Vibro/Haptic:", 2, flipchess_scene_settings_set_haptic, app);
|
||||
value_index = value_index_uint32(app->haptic, haptic_value, 2);
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, haptic_text[value_index]);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdSettings);
|
||||
}
|
||||
|
||||
bool flipchess_scene_settings_on_event(void* context, SceneManagerEvent event) {
|
||||
FlipChess* app = context;
|
||||
UNUSED(app);
|
||||
bool consumed = false;
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void flipchess_scene_settings_on_exit(void* context) {
|
||||
FlipChess* app = context;
|
||||
variable_item_list_set_selected_item(app->variable_item_list, 0);
|
||||
variable_item_list_reset(app->variable_item_list);
|
||||
}
|
||||
67
applications/external/chess/scenes/flipchess_scene_startscreen.c
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "../flipchess.h"
|
||||
#include "../helpers/flipchess_voice.h"
|
||||
#include "../helpers/flipchess_file.h"
|
||||
#include "../helpers/flipchess_custom_event.h"
|
||||
#include "../views/flipchess_startscreen.h"
|
||||
|
||||
void flipchess_scene_startscreen_callback(FlipChessCustomEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
FlipChess* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, event);
|
||||
}
|
||||
|
||||
void flipchess_scene_startscreen_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
FlipChess* app = context;
|
||||
|
||||
if(flipchess_has_file(FlipChessFileBoard, NULL, false)) {
|
||||
if(flipchess_load_file(app->import_game_text, FlipChessFileBoard, NULL)) {
|
||||
app->import_game = 1;
|
||||
}
|
||||
}
|
||||
|
||||
flipchess_startscreen_set_callback(
|
||||
app->flipchess_startscreen, flipchess_scene_startscreen_callback, app);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdStartscreen);
|
||||
}
|
||||
|
||||
bool flipchess_scene_startscreen_on_event(void* context, SceneManagerEvent event) {
|
||||
FlipChess* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case FlipChessCustomEventStartscreenLeft:
|
||||
case FlipChessCustomEventStartscreenRight:
|
||||
break;
|
||||
case FlipChessCustomEventStartscreenUp:
|
||||
case FlipChessCustomEventStartscreenDown:
|
||||
break;
|
||||
case FlipChessCustomEventStartscreenOk:
|
||||
scene_manager_next_scene(app->scene_manager, FlipChessSceneMenu);
|
||||
consumed = true;
|
||||
break;
|
||||
case FlipChessCustomEventStartscreenBack:
|
||||
notification_message(app->notification, &sequence_reset_red);
|
||||
notification_message(app->notification, &sequence_reset_green);
|
||||
notification_message(app->notification, &sequence_reset_blue);
|
||||
if(!scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, FlipChessSceneStartscreen)) {
|
||||
scene_manager_stop(app->scene_manager);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
}
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void flipchess_scene_startscreen_on_exit(void* context) {
|
||||
FlipChess* app = context;
|
||||
|
||||
if(app->sound == 1) {
|
||||
flipchess_voice_shall_we_play();
|
||||
}
|
||||
}
|
||||
727
applications/external/chess/views/flipchess_scene_1.c
vendored
Normal file
@@ -0,0 +1,727 @@
|
||||
#include "../flipchess.h"
|
||||
#include <furi.h>
|
||||
// #include <furi_hal.h>
|
||||
// #include <furi_hal_random.h>
|
||||
#include <input/input.h>
|
||||
#include <gui/elements.h>
|
||||
//#include <dolphin/dolphin.h>
|
||||
#include <string.h>
|
||||
//#include "flipchess_icons.h"
|
||||
#include "../helpers/flipchess_voice.h"
|
||||
#include "../helpers/flipchess_haptic.h"
|
||||
|
||||
#define SCL_960_CASTLING 0 // setting to 1 compiles a 960 version of smolchess
|
||||
#define XBOARD_DEBUG 0 // will create files with xboard communication
|
||||
#define SCL_EVALUATION_FUNCTION SCL_boardEvaluateStatic
|
||||
#define SCL_DEBUG_AI 0
|
||||
|
||||
#include "../chess/smallchesslib.h"
|
||||
|
||||
#define ENABLE_960 0 // setting to 1 enables 960 chess
|
||||
#define MAX_TEXT_LEN 15 // 15 = max length of text
|
||||
#define MAX_TEXT_BUF (MAX_TEXT_LEN + 1) // max length of text + null terminator
|
||||
#define THREAD_WAIT_TIME 20 // time to wait for draw thread to finish
|
||||
|
||||
struct FlipChessScene1 {
|
||||
View* view;
|
||||
FlipChessScene1Callback callback;
|
||||
void* context;
|
||||
};
|
||||
typedef struct {
|
||||
uint8_t paramPlayerW;
|
||||
uint8_t paramPlayerB;
|
||||
|
||||
uint8_t paramAnalyze; // depth of analysis
|
||||
uint8_t paramMoves;
|
||||
uint8_t paramInfo;
|
||||
uint8_t paramFlipBoard;
|
||||
uint8_t paramExit;
|
||||
uint16_t paramStep;
|
||||
char* paramFEN;
|
||||
char* paramPGN;
|
||||
|
||||
int clockSeconds;
|
||||
SCL_Game game;
|
||||
SCL_Board startState;
|
||||
|
||||
#if ENABLE_960
|
||||
int16_t random960PosNumber;
|
||||
#endif
|
||||
|
||||
//uint8_t picture[SCL_BOARD_PICTURE_WIDTH * SCL_BOARD_PICTURE_WIDTH];
|
||||
uint8_t squareSelected;
|
||||
uint8_t squareSelectedLast;
|
||||
|
||||
char* msg;
|
||||
char* msg2;
|
||||
char* msg3;
|
||||
char moveString[MAX_TEXT_BUF];
|
||||
char moveString2[MAX_TEXT_BUF];
|
||||
char moveString3[MAX_TEXT_BUF];
|
||||
uint8_t thinking;
|
||||
|
||||
SCL_SquareSet moveHighlight;
|
||||
uint8_t squareFrom;
|
||||
uint8_t squareTo;
|
||||
uint8_t turnState;
|
||||
|
||||
} FlipChessScene1Model;
|
||||
|
||||
static uint8_t picture[SCL_BOARD_PICTURE_WIDTH * SCL_BOARD_PICTURE_WIDTH];
|
||||
|
||||
void flipchess_putImagePixel(uint8_t pixel, uint16_t index) {
|
||||
picture[index] = pixel;
|
||||
}
|
||||
|
||||
uint8_t flipchess_stringsEqual(const char* s1, const char* s2, int max) {
|
||||
for(int i = 0; i < max; ++i) {
|
||||
if(*s1 != *s2) return 0;
|
||||
|
||||
if(*s1 == 0) return 1;
|
||||
|
||||
s1++;
|
||||
s2++;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int16_t flipchess_makeAIMove(
|
||||
SCL_Board board,
|
||||
uint8_t* s0,
|
||||
uint8_t* s1,
|
||||
char* prom,
|
||||
FlipChessScene1Model* model) {
|
||||
uint8_t level = SCL_boardWhitesTurn(board) ? model->paramPlayerW : model->paramPlayerB;
|
||||
uint8_t depth = (level > 0) ? level : 1;
|
||||
uint8_t extraDepth = 3;
|
||||
uint8_t endgameDepth = 1;
|
||||
uint8_t randomness =
|
||||
model->game.ply < 2 ? 1 : 0; /* in first moves increase randomness for different
|
||||
openings */
|
||||
uint8_t rs0, rs1;
|
||||
|
||||
SCL_gameGetRepetiotionMove(&(model->game), &rs0, &rs1);
|
||||
|
||||
if(model->clockSeconds >= 0) // when using clock, choose AI params accordingly
|
||||
{
|
||||
if(model->clockSeconds <= 5) {
|
||||
depth = 1;
|
||||
extraDepth = 2;
|
||||
endgameDepth = 0;
|
||||
} else if(model->clockSeconds < 15) {
|
||||
depth = 2;
|
||||
extraDepth = 2;
|
||||
} else if(model->clockSeconds < 100) {
|
||||
depth = 2;
|
||||
} else if(model->clockSeconds < 5 * 60) {
|
||||
depth = 3;
|
||||
} else {
|
||||
depth = 3;
|
||||
extraDepth = 4;
|
||||
}
|
||||
}
|
||||
|
||||
return SCL_getAIMove(
|
||||
board,
|
||||
depth,
|
||||
extraDepth,
|
||||
endgameDepth,
|
||||
SCL_boardEvaluateStatic,
|
||||
SCL_randomBetter,
|
||||
randomness,
|
||||
rs0,
|
||||
rs1,
|
||||
s0,
|
||||
s1,
|
||||
prom);
|
||||
}
|
||||
|
||||
bool flipchess_isPlayerTurn(FlipChessScene1Model* model) {
|
||||
return (SCL_boardWhitesTurn(model->game.board) && model->paramPlayerW == 0) ||
|
||||
(!SCL_boardWhitesTurn(model->game.board) && model->paramPlayerB == 0);
|
||||
}
|
||||
|
||||
void flipchess_shiftMessages(FlipChessScene1Model* model) {
|
||||
// shift messages
|
||||
model->msg3 = model->msg2;
|
||||
model->msg2 = model->msg;
|
||||
strncpy(model->moveString3, model->moveString2, MAX_TEXT_LEN);
|
||||
strncpy(model->moveString2, model->moveString, MAX_TEXT_LEN);
|
||||
}
|
||||
|
||||
void flipchess_drawBoard(FlipChessScene1Model* model) {
|
||||
// draw chess board
|
||||
SCL_drawBoard(
|
||||
model->game.board,
|
||||
flipchess_putImagePixel,
|
||||
model->squareSelected,
|
||||
model->moveHighlight,
|
||||
model->paramFlipBoard);
|
||||
}
|
||||
|
||||
uint8_t flipchess_saveState(FlipChess* app, FlipChessScene1Model* model) {
|
||||
for(uint8_t i = 0; i < SCL_FEN_MAX_LENGTH; i++) {
|
||||
app->import_game_text[i] = '\0';
|
||||
}
|
||||
const uint8_t res = SCL_boardToFEN(model->game.board, app->import_game_text);
|
||||
if(res > 0) {
|
||||
app->import_game = 1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
uint8_t flipchess_turn(FlipChessScene1Model* model) {
|
||||
// 0: none, 1: player, 2: AI, 3: undo
|
||||
uint8_t moveType = FlipChessStatusNone;
|
||||
|
||||
// if(model->paramInfo) {
|
||||
|
||||
// if(model->random960PosNumber >= 0)
|
||||
// printf("960 random position number: %d\n", model->random960PosNumber);
|
||||
|
||||
// printf("ply number: %d\n", model->game.ply);
|
||||
|
||||
// int16_t eval = SCL_boardEvaluateStatic(model->game.board);
|
||||
// printf(
|
||||
// "board static evaluation: %lf (%d)\n",
|
||||
// ((double)eval) / ((double)SCL_VALUE_PAWN),
|
||||
// eval);
|
||||
// printf("board hash: %u\n", SCL_boardHash32(model->game.board));
|
||||
// printf("phase: ");
|
||||
|
||||
// switch(SCL_boardEstimatePhase(model->game.board)) {
|
||||
// case SCL_PHASE_OPENING:
|
||||
// puts("opening");
|
||||
// break;
|
||||
// case SCL_PHASE_ENDGAME:
|
||||
// puts("endgame");
|
||||
// break;
|
||||
// default:
|
||||
// puts("midgame");
|
||||
// break;
|
||||
// }
|
||||
|
||||
// printf(
|
||||
// "en passant: %d\n",
|
||||
// ((model->game.board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] & 0x0f) + 1) % 16);
|
||||
// printf(
|
||||
// "50 move rule count: %d\n", model->game.board[SCL_BOARD_MOVE_COUNT_BYTE]);
|
||||
|
||||
// if(model->paramFEN == NULL && model->paramPGN == NULL) {
|
||||
// printf("PGN: ");
|
||||
// SCL_printPGN(model->game.record, putCharacter, startState);
|
||||
// putchar('\n');
|
||||
// }
|
||||
// }
|
||||
|
||||
if(model->game.state != SCL_GAME_STATE_PLAYING) {
|
||||
model->paramExit = FlipChessStatusNone;
|
||||
|
||||
} else {
|
||||
char movePromote = 'q';
|
||||
|
||||
if(flipchess_isPlayerTurn(model)) {
|
||||
// if(stringsEqual(string, "undo", 5))
|
||||
// moveType = FlipChessStatusMoveUndo;
|
||||
// else if(stringsEqual(string, "quit", 5))
|
||||
// break;
|
||||
|
||||
if(model->turnState == 0 && model->squareSelected != 255) {
|
||||
model->squareFrom = model->squareSelected;
|
||||
model->turnState = 1;
|
||||
} else if(model->turnState == 1 && model->squareSelected != 255) {
|
||||
model->squareTo = model->squareSelected;
|
||||
model->turnState = 2;
|
||||
model->squareSelectedLast = model->squareSelected;
|
||||
//model->squareSelected = 255;
|
||||
}
|
||||
|
||||
if(model->turnState == 1 && model->squareFrom != 255) {
|
||||
if((model->game.board[model->squareFrom] != '.') &&
|
||||
(SCL_pieceIsWhite(model->game.board[model->squareFrom]) ==
|
||||
SCL_boardWhitesTurn(model->game.board))) {
|
||||
SCL_boardGetMoves(model->game.board, model->squareFrom, model->moveHighlight);
|
||||
}
|
||||
} else if(model->turnState == 2) {
|
||||
if(SCL_squareSetContains(model->moveHighlight, model->squareTo)) {
|
||||
moveType = FlipChessStatusMovePlayer;
|
||||
}
|
||||
model->turnState = 0;
|
||||
SCL_squareSetClear(model->moveHighlight);
|
||||
}
|
||||
|
||||
} else {
|
||||
model->squareSelected = 255;
|
||||
flipchess_makeAIMove(
|
||||
model->game.board, &(model->squareFrom), &(model->squareTo), &movePromote, model);
|
||||
moveType = FlipChessStatusMoveAI;
|
||||
model->turnState = 0;
|
||||
}
|
||||
|
||||
if(moveType == FlipChessStatusMovePlayer || moveType == FlipChessStatusMoveAI) {
|
||||
flipchess_shiftMessages(model);
|
||||
|
||||
SCL_moveToString(
|
||||
model->game.board,
|
||||
model->squareFrom,
|
||||
model->squareTo,
|
||||
movePromote,
|
||||
model->moveString);
|
||||
|
||||
SCL_gameMakeMove(&(model->game), model->squareFrom, model->squareTo, movePromote);
|
||||
|
||||
SCL_squareSetClear(model->moveHighlight);
|
||||
SCL_squareSetAdd(model->moveHighlight, model->squareFrom);
|
||||
SCL_squareSetAdd(model->moveHighlight, model->squareTo);
|
||||
} else if(moveType == FlipChessStatusMoveUndo) {
|
||||
flipchess_shiftMessages(model);
|
||||
|
||||
if(model->paramPlayerW != 0 || model->paramPlayerB != 0)
|
||||
SCL_gameUndoMove(&(model->game));
|
||||
|
||||
SCL_gameUndoMove(&(model->game));
|
||||
SCL_squareSetClear(model->moveHighlight);
|
||||
}
|
||||
|
||||
switch(model->game.state) {
|
||||
case SCL_GAME_STATE_WHITE_WIN:
|
||||
model->msg = "white wins";
|
||||
model->paramExit = FlipChessStatusReturn;
|
||||
break;
|
||||
|
||||
case SCL_GAME_STATE_BLACK_WIN:
|
||||
model->msg = "black wins";
|
||||
model->paramExit = FlipChessStatusReturn;
|
||||
break;
|
||||
|
||||
case SCL_GAME_STATE_DRAW_STALEMATE:
|
||||
model->msg = "stalemate";
|
||||
model->paramExit = FlipChessStatusReturn;
|
||||
break;
|
||||
|
||||
case SCL_GAME_STATE_DRAW_REPETITION:
|
||||
model->msg = "draw-repetition";
|
||||
model->paramExit = FlipChessStatusReturn;
|
||||
break;
|
||||
|
||||
case SCL_GAME_STATE_DRAW_DEAD:
|
||||
model->msg = "draw-dead pos.";
|
||||
model->paramExit = FlipChessStatusReturn;
|
||||
break;
|
||||
|
||||
case SCL_GAME_STATE_DRAW:
|
||||
model->msg = "draw";
|
||||
model->paramExit = FlipChessStatusReturn;
|
||||
break;
|
||||
|
||||
case SCL_GAME_STATE_DRAW_50:
|
||||
model->msg = "draw-50 moves";
|
||||
model->paramExit = FlipChessStatusReturn;
|
||||
break;
|
||||
|
||||
default:
|
||||
if(model->game.ply > 0) {
|
||||
const uint8_t whitesTurn = SCL_boardWhitesTurn(model->game.board);
|
||||
|
||||
if(SCL_boardCheck(model->game.board, whitesTurn)) {
|
||||
model->msg = (whitesTurn ? "black: check!" : "white: check!");
|
||||
} else {
|
||||
model->msg = (whitesTurn ? "black played" : "white played");
|
||||
}
|
||||
|
||||
uint8_t s0, s1;
|
||||
char p;
|
||||
|
||||
SCL_recordGetMove(model->game.record, model->game.ply - 1, &s0, &s1, &p);
|
||||
SCL_moveToString(model->game.board, s0, s1, p, model->moveString);
|
||||
}
|
||||
break;
|
||||
model->paramExit = moveType;
|
||||
}
|
||||
}
|
||||
|
||||
model->thinking = 0;
|
||||
return model->paramExit;
|
||||
}
|
||||
|
||||
void flipchess_scene_1_set_callback(
|
||||
FlipChessScene1* instance,
|
||||
FlipChessScene1Callback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
void flipchess_scene_1_draw(Canvas* canvas, FlipChessScene1Model* model) {
|
||||
//UNUSED(model);
|
||||
canvas_clear(canvas);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
//canvas_draw_icon(canvas, 0, 0, &I_FLIPR_128x64);
|
||||
|
||||
// Frame
|
||||
canvas_draw_frame(canvas, 0, 0, 66, 64);
|
||||
|
||||
// Message
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
if(model->thinking) {
|
||||
canvas_draw_str(canvas, 68, 10, "thinking...");
|
||||
} else {
|
||||
canvas_draw_str(canvas, 68, 10, model->msg);
|
||||
}
|
||||
canvas_draw_str(canvas, 68, 19, model->moveString);
|
||||
canvas_draw_str(canvas, 68, 31, model->msg2);
|
||||
canvas_draw_str(canvas, 68, 40, model->moveString2);
|
||||
canvas_draw_str(canvas, 68, 52, model->msg3);
|
||||
canvas_draw_str(canvas, 68, 61, model->moveString3);
|
||||
|
||||
// Board
|
||||
for(uint16_t y = 0; y < SCL_BOARD_PICTURE_WIDTH; y++) {
|
||||
for(uint16_t x = 0; x < SCL_BOARD_PICTURE_WIDTH; x++) {
|
||||
if(!picture[x + (y * SCL_BOARD_PICTURE_WIDTH)]) {
|
||||
canvas_draw_dot(canvas, x + 1, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int flipchess_scene_1_model_init(
|
||||
FlipChessScene1Model* const model,
|
||||
const int white_mode,
|
||||
const int black_mode,
|
||||
char* import_game_text) {
|
||||
model->paramPlayerW = white_mode;
|
||||
model->paramPlayerB = black_mode;
|
||||
|
||||
model->paramAnalyze = 255; // depth of analysis
|
||||
model->paramMoves = 0;
|
||||
model->paramInfo = 1;
|
||||
model->paramFlipBoard = 0;
|
||||
model->paramExit = FlipChessStatusNone;
|
||||
model->paramStep = 0;
|
||||
model->paramFEN = import_game_text;
|
||||
model->paramPGN = NULL;
|
||||
model->clockSeconds = -1;
|
||||
|
||||
SCL_Board emptyStartState = SCL_BOARD_START_STATE;
|
||||
memcpy(model->startState, &emptyStartState, sizeof(SCL_Board));
|
||||
|
||||
#if ENABLE_960
|
||||
model->random960PosNumber = -1;
|
||||
#endif
|
||||
|
||||
model->squareSelected = 255;
|
||||
model->squareSelectedLast = 28; // start selector near middle
|
||||
|
||||
model->msg = "init";
|
||||
model->moveString[0] = '\0';
|
||||
model->msg2 = "";
|
||||
model->moveString2[0] = '\0';
|
||||
model->msg3 = "";
|
||||
model->moveString3[0] = '\0';
|
||||
model->thinking = 0;
|
||||
|
||||
SCL_SquareSet emptySquareSet = SCL_SQUARE_SET_EMPTY;
|
||||
memcpy(model->moveHighlight, &emptySquareSet, sizeof(SCL_SquareSet));
|
||||
model->squareFrom = 255;
|
||||
model->squareTo = 255;
|
||||
model->turnState = 0;
|
||||
|
||||
SCL_randomBetterSeed(furi_hal_random_get());
|
||||
|
||||
#if ENABLE_960
|
||||
#if SCL_960_CASTLING
|
||||
if(model->random960PosNumber < 0) model->random960PosNumber = SCL_randomBetter();
|
||||
#endif
|
||||
if(model->random960PosNumber >= 0) model->random960PosNumber %= 960;
|
||||
#endif
|
||||
|
||||
if(model->paramFEN != NULL)
|
||||
SCL_boardFromFEN(model->startState, model->paramFEN);
|
||||
else if(model->paramPGN != NULL) {
|
||||
SCL_Record record;
|
||||
SCL_recordFromPGN(record, model->paramPGN);
|
||||
SCL_boardInit(model->startState);
|
||||
SCL_recordApply(record, model->startState, model->paramStep);
|
||||
}
|
||||
|
||||
#if ENABLE_960
|
||||
#if SCL_960_CASTLING
|
||||
else
|
||||
SCL_boardInit960(model->startState, model->random960PosNumber);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
SCL_gameInit(&(model->game), model->startState);
|
||||
|
||||
if(model->paramAnalyze != 255) {
|
||||
char p;
|
||||
uint8_t move[] = {0, 0};
|
||||
|
||||
model->paramPlayerW = model->paramAnalyze;
|
||||
model->paramPlayerB = model->paramAnalyze;
|
||||
|
||||
int16_t evaluation =
|
||||
flipchess_makeAIMove(model->game.board, &(move[0]), &(move[1]), &p, model);
|
||||
|
||||
if(model->paramAnalyze == 0) evaluation = SCL_boardEvaluateStatic(model->game.board);
|
||||
|
||||
char moveStr[5];
|
||||
moveStr[4] = 0;
|
||||
|
||||
SCL_squareToString(move[0], moveStr);
|
||||
SCL_squareToString(move[1], moveStr + 2);
|
||||
|
||||
//printf("%lf (%d)\n", ((double)evaluation) / ((double)SCL_VALUE_PAWN), evaluation);
|
||||
//puts(moveStr);
|
||||
|
||||
return evaluation;
|
||||
}
|
||||
|
||||
if(model->paramMoves) {
|
||||
char string[256];
|
||||
|
||||
for(int i = 0; i < 64; ++i)
|
||||
if(model->game.board[i] != '.' &&
|
||||
SCL_pieceIsWhite(model->game.board[i]) == SCL_boardWhitesTurn(model->game.board)) {
|
||||
SCL_SquareSet possibleMoves = SCL_SQUARE_SET_EMPTY;
|
||||
|
||||
SCL_boardGetMoves(model->game.board, i, possibleMoves);
|
||||
|
||||
SCL_SQUARE_SET_ITERATE_BEGIN(possibleMoves)
|
||||
SCL_moveToString(model->game.board, i, iteratedSquare, 'q', string);
|
||||
//printf("%s ", string);
|
||||
SCL_SQUARE_SET_ITERATE_END
|
||||
}
|
||||
|
||||
return FlipChessStatusReturn;
|
||||
}
|
||||
|
||||
model->msg = (SCL_boardWhitesTurn(model->game.board) ? "white to move" : "black to move");
|
||||
|
||||
// 0 = success
|
||||
return FlipChessStatusNone;
|
||||
}
|
||||
|
||||
bool flipchess_scene_1_input(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
FlipChessScene1* instance = context;
|
||||
FlipChess* app = instance->context;
|
||||
|
||||
if(event->type == InputTypeRelease) {
|
||||
switch(event->key) {
|
||||
case InputKeyBack:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
FlipChessScene1Model * model,
|
||||
{
|
||||
if(model->turnState == 1) {
|
||||
model->turnState = 0;
|
||||
SCL_squareSetClear(model->moveHighlight);
|
||||
flipchess_drawBoard(model);
|
||||
} else {
|
||||
instance->callback(FlipChessCustomEventScene1Back, instance->context);
|
||||
}
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyRight:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
FlipChessScene1Model * model,
|
||||
{
|
||||
if(model->squareSelectedLast != 255 && model->squareSelected == 255) {
|
||||
model->squareSelected = model->squareSelectedLast;
|
||||
} else {
|
||||
model->squareSelected = (model->squareSelected + 1) % 64;
|
||||
}
|
||||
flipchess_drawBoard(model);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyDown:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
FlipChessScene1Model * model,
|
||||
{
|
||||
if(model->squareSelectedLast != 255 && model->squareSelected == 255) {
|
||||
model->squareSelected = model->squareSelectedLast;
|
||||
} else {
|
||||
model->squareSelected = (model->squareSelected + 56) % 64;
|
||||
}
|
||||
flipchess_drawBoard(model);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
FlipChessScene1Model * model,
|
||||
{
|
||||
if(model->squareSelectedLast != 255 && model->squareSelected == 255) {
|
||||
model->squareSelected = model->squareSelectedLast;
|
||||
} else {
|
||||
model->squareSelected = (model->squareSelected + 63) % 64;
|
||||
}
|
||||
flipchess_drawBoard(model);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyUp:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
FlipChessScene1Model * model,
|
||||
{
|
||||
if(model->squareSelectedLast != 255 && model->squareSelected == 255) {
|
||||
model->squareSelected = model->squareSelectedLast;
|
||||
} else {
|
||||
model->squareSelected = (model->squareSelected + 8) % 64;
|
||||
}
|
||||
flipchess_drawBoard(model);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyOk:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
FlipChessScene1Model * model,
|
||||
{
|
||||
// if(model->paramExit == FlipChessStatusReturn) {
|
||||
// instance->callback(FlipChessCustomEventScene1Back, instance->context);
|
||||
// break;
|
||||
// }
|
||||
if(!flipchess_isPlayerTurn(model)) {
|
||||
model->thinking = 1;
|
||||
}
|
||||
},
|
||||
true);
|
||||
furi_thread_flags_wait(0, FuriFlagWaitAny, THREAD_WAIT_TIME);
|
||||
|
||||
with_view_model(
|
||||
instance->view,
|
||||
FlipChessScene1Model * model,
|
||||
{
|
||||
// first turn of round, probably player but could be AI
|
||||
if(flipchess_turn(model) == FlipChessStatusReturn) {
|
||||
if(app->sound == 1) flipchess_voice_a_strange_game();
|
||||
flipchess_play_long_bump(app);
|
||||
}
|
||||
flipchess_saveState(app, model);
|
||||
flipchess_drawBoard(model);
|
||||
},
|
||||
true);
|
||||
|
||||
with_view_model(
|
||||
instance->view,
|
||||
FlipChessScene1Model * model,
|
||||
{
|
||||
if(!flipchess_isPlayerTurn(model)) {
|
||||
model->thinking = 1;
|
||||
}
|
||||
},
|
||||
true);
|
||||
furi_thread_flags_wait(0, FuriFlagWaitAny, THREAD_WAIT_TIME);
|
||||
|
||||
with_view_model(
|
||||
instance->view,
|
||||
FlipChessScene1Model * model,
|
||||
{
|
||||
// if player played, let AI play
|
||||
if(!flipchess_isPlayerTurn(model)) {
|
||||
if(flipchess_turn(model) == FlipChessStatusReturn) {
|
||||
if(app->sound == 1) flipchess_voice_a_strange_game();
|
||||
flipchess_play_long_bump(app);
|
||||
}
|
||||
flipchess_saveState(app, model);
|
||||
flipchess_drawBoard(model);
|
||||
}
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyMAX:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void flipchess_scene_1_exit(void* context) {
|
||||
furi_assert(context);
|
||||
FlipChessScene1* instance = (FlipChessScene1*)context;
|
||||
|
||||
with_view_model(
|
||||
instance->view, FlipChessScene1Model * model, { model->paramExit = 0; }, true);
|
||||
}
|
||||
|
||||
void flipchess_scene_1_enter(void* context) {
|
||||
furi_assert(context);
|
||||
FlipChessScene1* instance = (FlipChessScene1*)context;
|
||||
FlipChess* app = instance->context;
|
||||
|
||||
flipchess_play_happy_bump(app);
|
||||
|
||||
with_view_model(
|
||||
instance->view,
|
||||
FlipChessScene1Model * model,
|
||||
{
|
||||
// load imported game if applicable
|
||||
char* import_game_text = NULL;
|
||||
if(app->import_game == 1 && strlen(app->import_game_text) > 0) {
|
||||
import_game_text = app->import_game_text;
|
||||
} else {
|
||||
if(app->sound == 1) flipchess_voice_how_about_chess();
|
||||
}
|
||||
|
||||
int init = flipchess_scene_1_model_init(
|
||||
model, app->white_mode, app->black_mode, import_game_text);
|
||||
|
||||
if(init == FlipChessStatusNone) {
|
||||
// perform initial turn, sets up and lets white
|
||||
// AI play if applicable
|
||||
const uint8_t turn = flipchess_turn(model);
|
||||
if(turn == FlipChessStatusReturn) {
|
||||
init = turn;
|
||||
} else {
|
||||
flipchess_saveState(app, model);
|
||||
flipchess_drawBoard(model);
|
||||
}
|
||||
}
|
||||
|
||||
// if return status, return from scene immediately
|
||||
// if(init == FlipChessStatusReturn) {
|
||||
// instance->callback(FlipChessCustomEventScene1Back, instance->context);
|
||||
// }
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
FlipChessScene1* flipchess_scene_1_alloc() {
|
||||
FlipChessScene1* instance = malloc(sizeof(FlipChessScene1));
|
||||
instance->view = view_alloc();
|
||||
view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(FlipChessScene1Model));
|
||||
view_set_context(instance->view, instance); // furi_assert crashes in events without this
|
||||
view_set_draw_callback(instance->view, (ViewDrawCallback)flipchess_scene_1_draw);
|
||||
view_set_input_callback(instance->view, flipchess_scene_1_input);
|
||||
view_set_enter_callback(instance->view, flipchess_scene_1_enter);
|
||||
view_set_exit_callback(instance->view, flipchess_scene_1_exit);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void flipchess_scene_1_free(FlipChessScene1* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
with_view_model(
|
||||
instance->view, FlipChessScene1Model * model, { UNUSED(model); }, true);
|
||||
|
||||
view_free(instance->view);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
View* flipchess_scene_1_get_view(FlipChessScene1* instance) {
|
||||
furi_assert(instance);
|
||||
return instance->view;
|
||||
}
|
||||
19
applications/external/chess/views/flipchess_scene_1.h
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include "../helpers/flipchess_custom_event.h"
|
||||
|
||||
typedef struct FlipChessScene1 FlipChessScene1;
|
||||
|
||||
typedef void (*FlipChessScene1Callback)(FlipChessCustomEvent event, void* context);
|
||||
|
||||
void flipchess_scene_1_set_callback(
|
||||
FlipChessScene1* flipchess_scene_1,
|
||||
FlipChessScene1Callback callback,
|
||||
void* context);
|
||||
|
||||
View* flipchess_scene_1_get_view(FlipChessScene1* flipchess_static);
|
||||
|
||||
FlipChessScene1* flipchess_scene_1_alloc();
|
||||
|
||||
void flipchess_scene_1_free(FlipChessScene1* flipchess_static);
|
||||
164
applications/external/chess/views/flipchess_startscreen.c
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
#include "../flipchess.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <input/input.h>
|
||||
#include <gui/elements.h>
|
||||
#include "flipchess_icons.h"
|
||||
#include <assets_icons.h>
|
||||
|
||||
struct FlipChessStartscreen {
|
||||
View* view;
|
||||
FlipChessStartscreenCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int some_value;
|
||||
} FlipChessStartscreenModel;
|
||||
|
||||
void flipchess_startscreen_set_callback(
|
||||
FlipChessStartscreen* instance,
|
||||
FlipChessStartscreenCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
void flipchess_startscreen_draw(Canvas* canvas, FlipChessStartscreenModel* model) {
|
||||
UNUSED(model);
|
||||
canvas_clear(canvas);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
canvas_draw_icon(canvas, 0, 0, &I_FLIPR_128x64);
|
||||
|
||||
#ifdef CANVAS_HAS_FONT_SCUMM_ROMAN_OUTLINE
|
||||
const uint8_t text_x_pos = 2;
|
||||
const uint8_t text_y_pos = 12;
|
||||
canvas_set_font(canvas, FontScummRomanOutline);
|
||||
#else
|
||||
const uint8_t text_x_pos = 4;
|
||||
const uint8_t text_y_pos = 11;
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
#endif
|
||||
canvas_draw_str(canvas, text_x_pos, text_y_pos, "Chess");
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str(canvas, 62, text_y_pos, FLIPCHESS_VERSION);
|
||||
|
||||
//canvas_set_font(canvas, FontSecondary);
|
||||
//canvas_draw_str(canvas, 10, 11, "How about a nice game of...");
|
||||
//canvas_draw_str(canvas, 99, 40, FLIPCHESS_VERSION);
|
||||
|
||||
//canvas_set_font(canvas, FontPrimary);
|
||||
//canvas_draw_str(canvas, 10, 23, "Chess");
|
||||
//canvas_draw_icon(canvas, 0, 40, &I_Background_128x11);
|
||||
//canvas_draw_str(canvas, 10, 61, "FLIPR");
|
||||
|
||||
elements_button_left(canvas, "Sound");
|
||||
elements_button_right(canvas, "Silent");
|
||||
}
|
||||
|
||||
static void flipchess_startscreen_model_init(FlipChessStartscreenModel* const model) {
|
||||
model->some_value = 1;
|
||||
}
|
||||
|
||||
bool flipchess_startscreen_input(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
FlipChessStartscreen* instance = context;
|
||||
FlipChess* app = instance->context;
|
||||
|
||||
if(event->type == InputTypeRelease) {
|
||||
switch(event->key) {
|
||||
case InputKeyBack:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
FlipChessStartscreenModel * model,
|
||||
{
|
||||
UNUSED(model);
|
||||
instance->callback(FlipChessCustomEventStartscreenBack, instance->context);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
// sound on, haptic off
|
||||
app->sound = 1;
|
||||
app->haptic = FlipChessHapticOff;
|
||||
with_view_model(
|
||||
instance->view,
|
||||
FlipChessStartscreenModel * model,
|
||||
{
|
||||
UNUSED(model);
|
||||
instance->callback(FlipChessCustomEventStartscreenOk, instance->context);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyRight:
|
||||
// sound off, haptic on
|
||||
app->sound = 0;
|
||||
app->haptic = FlipChessHapticOn;
|
||||
with_view_model(
|
||||
instance->view,
|
||||
FlipChessStartscreenModel * model,
|
||||
{
|
||||
UNUSED(model);
|
||||
instance->callback(FlipChessCustomEventStartscreenOk, instance->context);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyUp:
|
||||
case InputKeyDown:
|
||||
case InputKeyOk:
|
||||
case InputKeyMAX:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void flipchess_startscreen_exit(void* context) {
|
||||
furi_assert(context);
|
||||
}
|
||||
|
||||
void flipchess_startscreen_enter(void* context) {
|
||||
furi_assert(context);
|
||||
FlipChessStartscreen* instance = (FlipChessStartscreen*)context;
|
||||
with_view_model(
|
||||
instance->view,
|
||||
FlipChessStartscreenModel * model,
|
||||
{ flipchess_startscreen_model_init(model); },
|
||||
true);
|
||||
}
|
||||
|
||||
FlipChessStartscreen* flipchess_startscreen_alloc() {
|
||||
FlipChessStartscreen* instance = malloc(sizeof(FlipChessStartscreen));
|
||||
instance->view = view_alloc();
|
||||
view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(FlipChessStartscreenModel));
|
||||
view_set_context(instance->view, instance); // furi_assert crashes in events without this
|
||||
view_set_draw_callback(instance->view, (ViewDrawCallback)flipchess_startscreen_draw);
|
||||
view_set_input_callback(instance->view, flipchess_startscreen_input);
|
||||
//view_set_enter_callback(instance->view, flipchess_startscreen_enter);
|
||||
//view_set_exit_callback(instance->view, flipchess_startscreen_exit);
|
||||
|
||||
with_view_model(
|
||||
instance->view,
|
||||
FlipChessStartscreenModel * model,
|
||||
{ flipchess_startscreen_model_init(model); },
|
||||
true);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void flipchess_startscreen_free(FlipChessStartscreen* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
with_view_model(
|
||||
instance->view, FlipChessStartscreenModel * model, { UNUSED(model); }, true);
|
||||
view_free(instance->view);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
View* flipchess_startscreen_get_view(FlipChessStartscreen* instance) {
|
||||
furi_assert(instance);
|
||||
return instance->view;
|
||||
}
|
||||
19
applications/external/chess/views/flipchess_startscreen.h
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include "../helpers/flipchess_custom_event.h"
|
||||
|
||||
typedef struct FlipChessStartscreen FlipChessStartscreen;
|
||||
|
||||
typedef void (*FlipChessStartscreenCallback)(FlipChessCustomEvent event, void* context);
|
||||
|
||||
void flipchess_startscreen_set_callback(
|
||||
FlipChessStartscreen* flipchess_startscreen,
|
||||
FlipChessStartscreenCallback callback,
|
||||
void* context);
|
||||
|
||||
View* flipchess_startscreen_get_view(FlipChessStartscreen* flipchess_static);
|
||||
|
||||
FlipChessStartscreen* flipchess_startscreen_alloc();
|
||||
|
||||
void flipchess_startscreen_free(FlipChessStartscreen* flipchess_static);
|
||||
@@ -10,7 +10,6 @@ App(
|
||||
"gui",
|
||||
],
|
||||
stack_size=2 * 1024,
|
||||
order=20,
|
||||
fap_icon="cntdown_timer.png",
|
||||
fap_category="Tools",
|
||||
fap_author="@0w0mewo",
|
||||
|
||||
@@ -8,7 +8,8 @@ App(
|
||||
"dialogs",
|
||||
],
|
||||
stack_size=4 * 1024,
|
||||
order=20,
|
||||
fap_description="Enables use of Flipper as a debug probe for ARM devices, implements the CMSIS-DAP protocol",
|
||||
fap_version="1.0",
|
||||
fap_icon="dap_link.png",
|
||||
fap_category="GPIO",
|
||||
fap_private_libs=[
|
||||
|
||||